trretretret commited on
Commit
49e7bf6
·
1 Parent(s): 2460f5c

Comprehensive frontend fixes: Updated gitignore, fixed all import paths, enhanced lib utilities

Browse files
.gitignore CHANGED
@@ -15,8 +15,10 @@ dist/
15
  downloads/
16
  eggs/
17
  .eggs/
 
18
  lib/
19
  lib64/
 
20
  parts/
21
  sdist/
22
  var/
 
15
  downloads/
16
  eggs/
17
  .eggs/
18
+ # Ignore system lib directories but allow our src/lib
19
  lib/
20
  lib64/
21
+ !/src/lib/
22
  parts/
23
  sdist/
24
  var/
src/api-client.ts DELETED
@@ -1,54 +0,0 @@
1
- // API Client for RM Research Assistant
2
- const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
3
-
4
- class ApiClient {
5
- private baseURL: string;
6
-
7
- constructor(baseURL: string = API_BASE_URL) {
8
- this.baseURL = baseURL;
9
- }
10
-
11
- async request(endpoint: string, options: RequestInit = {}) {
12
- const url = `${this.baseURL}${endpoint}`;
13
- const config = {
14
- headers: {
15
- 'Content-Type': 'application/json',
16
- ...options.headers,
17
- },
18
- ...options,
19
- };
20
-
21
- const response = await fetch(url, config);
22
-
23
- if (!response.ok) {
24
- throw new Error(`API Error: ${response.status} ${response.statusText}`);
25
- }
26
-
27
- return response.json();
28
- }
29
-
30
- async get(endpoint: string) {
31
- return this.request(endpoint, { method: 'GET' });
32
- }
33
-
34
- async post(endpoint: string, data?: any) {
35
- return this.request(endpoint, {
36
- method: 'POST',
37
- body: data ? JSON.stringify(data) : undefined,
38
- });
39
- }
40
-
41
- async put(endpoint: string, data?: any) {
42
- return this.request(endpoint, {
43
- method: 'PUT',
44
- body: data ? JSON.stringify(data) : undefined,
45
- });
46
- }
47
-
48
- async delete(endpoint: string) {
49
- return this.request(endpoint, { method: 'DELETE' });
50
- }
51
- }
52
-
53
- export const api = new ApiClient();
54
- export default api;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/(dashboard)/pico/page.tsx CHANGED
@@ -26,7 +26,7 @@ import { Spinner } from "@/components/atoms/Spinner";
26
 
27
  // Hooks & Utils
28
  import { useApi } from "@/hooks/useApi";
29
- import { api } from "@/api-client";
30
 
31
  /**
32
  * PICO Extraction Page (Final Build)
 
26
 
27
  // Hooks & Utils
28
  import { useApi } from "@/hooks/useApi";
29
+ import { api } from "@/lib/api-client";
30
 
31
  /**
32
  * PICO Extraction Page (Final Build)
src/app/(dashboard)/settings/page.tsx CHANGED
@@ -14,7 +14,7 @@ import {
14
  import { DashboardTemplate } from "@/components/templates";
15
  import { Icon } from "@/components/atoms/Icon";
16
  import { Badge } from "@/components/atoms/Badge";
17
- import { cn } from "@/cn";
18
 
19
  /**
20
  * Utility: Safe JWT Decoder
 
14
  import { DashboardTemplate } from "@/components/templates";
15
  import { Icon } from "@/components/atoms/Icon";
16
  import { Badge } from "@/components/atoms/Badge";
17
+ import { cn } from "@/lib/utils";
18
 
19
  /**
20
  * Utility: Safe JWT Decoder
src/app/layout.tsx CHANGED
@@ -1,7 +1,7 @@
1
  import type { Metadata, Viewport } from "next";
2
  import { Inter } from "next/font/google";
3
  import "./globals.css";
4
- import { cn } from "@/cn"; // Assumes a standard utility for class merging
5
 
6
  // 1. Optimized Font Loading: Prevents Layout Shift (CLS)
7
  const inter = Inter({
 
1
  import type { Metadata, Viewport } from "next";
2
  import { Inter } from "next/font/google";
3
  import "./globals.css";
4
+ import { cn } from "@/lib/utils"; // Assumes a standard utility for class merging
5
 
6
  // 1. Optimized Font Loading: Prevents Layout Shift (CLS)
7
  const inter = Inter({
src/components/atoms/Avatar/index.tsx CHANGED
@@ -2,7 +2,7 @@
2
  "use client";
3
 
4
  import React from "react";
5
- import { cn } from "@/cn";
6
 
7
  /** Props for the Avatar root */
8
  export interface AvatarProps {
 
2
  "use client";
3
 
4
  import React from "react";
5
+ import { cn } from "@/lib/utils";
6
 
7
  /** Props for the Avatar root */
8
  export interface AvatarProps {
src/components/atoms/Badge/index.tsx CHANGED
@@ -1,6 +1,6 @@
1
  import * as React from "react";
2
  import { cva, type VariantProps } from "class-variance-authority";
3
- import { cn } from "@/cn";
4
 
5
  // 1. Defined variants to support academic status indicators
6
  const badgeVariants = cva(
 
1
  import * as React from "react";
2
  import { cva, type VariantProps } from "class-variance-authority";
3
+ import { cn } from "@/lib/utils";
4
 
5
  // 1. Defined variants to support academic status indicators
6
  const badgeVariants = cva(
src/components/atoms/Button/index.tsx CHANGED
@@ -1,6 +1,6 @@
1
  import * as React from "react";
2
  import { cva, type VariantProps } from "class-variance-authority";
3
- import { cn } from "@/cn";
4
 
5
  // 1. Define variants using CVA for clean state management
6
  const buttonVariants = cva(
 
1
  import * as React from "react";
2
  import { cva, type VariantProps } from "class-variance-authority";
3
+ import { cn } from "@/lib/utils";
4
 
5
  // 1. Define variants using CVA for clean state management
6
  const buttonVariants = cva(
src/components/atoms/Icon/index.tsx CHANGED
@@ -1,5 +1,5 @@
1
  import { LucideIcon, LucideProps } from "lucide-react";
2
- import { cn } from "@/cn";
3
 
4
  export interface IconProps extends LucideProps {
5
  // 1. The 'icon' prop accepts any Lucide icon component
 
1
  import { LucideIcon, LucideProps } from "lucide-react";
2
+ import { cn } from "@/lib/utils";
3
 
4
  export interface IconProps extends LucideProps {
5
  // 1. The 'icon' prop accepts any Lucide icon component
src/components/atoms/Spinner/index.tsx CHANGED
@@ -2,7 +2,7 @@
2
 
3
  import * as React from "react";
4
  import { Loader2 } from "lucide-react";
5
- import { cn } from "@/cn";
6
 
7
  export interface SpinnerProps extends React.ComponentPropsWithoutRef<typeof Loader2> {}
8
 
 
2
 
3
  import * as React from "react";
4
  import { Loader2 } from "lucide-react";
5
+ import { cn } from "@/lib/utils";
6
 
7
  export interface SpinnerProps extends React.ComponentPropsWithoutRef<typeof Loader2> {}
8
 
src/components/atoms/StatCard/index.tsx CHANGED
@@ -3,11 +3,7 @@
3
 
4
  import React from "react";
5
  import { LucideProps } from "lucide-react";
6
-
7
- /** Utility to merge class names */
8
- function cn(...classes: (string | undefined | false | null)[]) {
9
- return classes.filter(Boolean).join(" ");
10
- }
11
 
12
  /** Props for StatCard */
13
  export interface StatCardProps {
 
3
 
4
  import React from "react";
5
  import { LucideProps } from "lucide-react";
6
+ import { cn } from "@/lib/utils";
 
 
 
 
7
 
8
  /** Props for StatCard */
9
  export interface StatCardProps {
src/components/atoms/Tooltip/index.tsx CHANGED
@@ -1,7 +1,7 @@
1
  "use client";
2
 
3
  import React, { useState, useRef } from "react";
4
- import { cn } from "@/cn"; // keep your utility function
5
 
6
  // Custom TooltipProvider (no-op for compatibility)
7
  const TooltipProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
 
1
  "use client";
2
 
3
  import React, { useState, useRef } from "react";
4
+ import { cn } from "@/lib/utils"; // keep your utility function
5
 
6
  // Custom TooltipProvider (no-op for compatibility)
7
  const TooltipProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
src/components/organisms/Header/index.tsx CHANGED
@@ -8,7 +8,7 @@ import {
8
  TooltipContent,
9
  TooltipProvider,
10
  } from "@/components/atoms/Tooltip";
11
- import { cn } from "@/cn"; // your utility for classnames
12
 
13
  const Header: React.FC = () => {
14
  return (
 
8
  TooltipContent,
9
  TooltipProvider,
10
  } from "@/components/atoms/Tooltip";
11
+ import { cn } from "@/lib/utils"; // your utility for classnames
12
 
13
  const Header: React.FC = () => {
14
  return (
src/components/organisms/Sidebar/index.tsx CHANGED
@@ -18,7 +18,7 @@ import {
18
  import { Avatar, AvatarFallback } from "@/components/atoms/Avatar";
19
  import { Icon } from "@/components/atoms/Icon";
20
  import { Spinner } from "@/components/atoms/Spinner";
21
- import { cn } from "@/cn";
22
 
23
  const navItems = [
24
  { label: "Dashboard", href: "/dashboard", icon: LayoutDashboard },
 
18
  import { Avatar, AvatarFallback } from "@/components/atoms/Avatar";
19
  import { Icon } from "@/components/atoms/Icon";
20
  import { Spinner } from "@/components/atoms/Spinner";
21
+ import { cn } from "@/lib/utils";
22
 
23
  const navItems = [
24
  { label: "Dashboard", href: "/dashboard", icon: LayoutDashboard },
src/components/templates/DashboardTemplate/index.tsx CHANGED
@@ -3,7 +3,7 @@
3
  import * as React from "react";
4
  import { Sidebar } from "@/components/organisms/Sidebar";
5
  import { Header } from "@/components/organisms/Header";
6
- import { cn } from "@/cn";
7
 
8
  interface DashboardTemplateProps {
9
  children: React.ReactNode;
 
3
  import * as React from "react";
4
  import { Sidebar } from "@/components/organisms/Sidebar";
5
  import { Header } from "@/components/organisms/Header";
6
+ import { cn } from "@/lib/utils";
7
 
8
  interface DashboardTemplateProps {
9
  children: React.ReactNode;
src/constants/api.ts DELETED
@@ -1,22 +0,0 @@
1
- // API Configuration Constants
2
- export const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
3
-
4
- export const API_ENDPOINTS = {
5
- // Authentication
6
- LOGIN: '/api/v1/auth/login',
7
- REGISTER: '/api/v1/auth/register',
8
- TOKEN_REFRESH: '/api/v1/auth/refresh',
9
-
10
- // Papers
11
- PAPERS: '/api/v1/papers',
12
- PAPER_DETAIL: (id: string) => `/api/v1/papers/${id}`,
13
-
14
- // PICO Analysis
15
- PICO_ANALYZE: '/api/v1/pico/analyze',
16
- PICO_RESULTS: (id: string) => `/api/v1/pico/results/${id}`,
17
-
18
- // Library
19
- LIBRARY: '/api/v1/library',
20
- LIBRARY_ADD: '/api/v1/library/add',
21
- LIBRARY_REMOVE: (id: string) => `/api/v1/library/remove/${id}`,
22
- } as const;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/api-client.ts ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Production-Grade API Client
3
+ * - Unified Fetch Wrapper with JWT injection.
4
+ * - Centralized 401 Interception (Auto-Logout).
5
+ * - Support for JSON and Multipart/Form-Data.
6
+ */
7
+
8
+ const BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000/api/v1";
9
+
10
+ export type ApiError = {
11
+ message: string;
12
+ status?: number;
13
+ detail?: any;
14
+ };
15
+
16
+ async function request<T>(
17
+ endpoint: string,
18
+ options: RequestInit & { params?: Record<string, string> } = {}
19
+ ): Promise<T> {
20
+ const { params, headers, ...config } = options;
21
+
22
+ // 1. Construct URL with Search Params
23
+ const url = new URL(`${BASE_URL}${endpoint}`);
24
+ if (params) {
25
+ Object.entries(params).forEach(([key, val]) => url.searchParams.append(key, val));
26
+ }
27
+
28
+ // 2. Token Retrieval (Direct localStorage for non-React context utility)
29
+ const token = typeof window !== "undefined" ? localStorage.getItem("token") : null;
30
+
31
+ // 3. Header Synthesis
32
+ const authHeader = token ? { Authorization: `Bearer ${token}` } : {};
33
+
34
+ // Don't set Content-Type if sending FormData (browser handles it)
35
+ const isFormData = config.body instanceof FormData;
36
+ const contentTypeHeader = isFormData ? {} : { "Content-Type": "application/json" };
37
+
38
+ const finalConfig: RequestInit = {
39
+ ...config,
40
+ headers: {
41
+ ...contentTypeHeader,
42
+ ...authHeader,
43
+ ...headers,
44
+ },
45
+ };
46
+
47
+ try {
48
+ const response = await fetch(url.toString(), finalConfig);
49
+
50
+ // 4. Global Interceptor: Handle Unauthorized
51
+ if (response.status === 401) {
52
+ if (typeof window !== "undefined") {
53
+ localStorage.removeItem("token");
54
+ // Force hard-redirect to clear state if token is dead
55
+ window.location.href = "/login?error=session_expired";
56
+ }
57
+ throw new Error("Unauthorized access. Please log in again.");
58
+ }
59
+
60
+ // 5. Success Handlers
61
+ if (response.status === 204) return {} as T;
62
+
63
+ const data = await response.json();
64
+
65
+ if (!response.ok) {
66
+ // Return the specific backend detail if available (FastAPI style)
67
+ const errorMsg = data.detail || "The research server encountered an issue.";
68
+ return Promise.reject({ message: errorMsg, status: response.status, detail: data });
69
+ }
70
+
71
+ return data as T;
72
+ } catch (err: any) {
73
+ // Handle Network Failures
74
+ return Promise.reject({
75
+ message: err.message || "Unable to connect to the research server. Check your connection.",
76
+ status: err.status || 500
77
+ });
78
+ }
79
+ }
80
+
81
+ export const api = {
82
+ get: <T>(url: string, p?: Record<string, string>) => request<T>(url, { method: "GET", params: p }),
83
+ post: <T>(url: string, body: any) => request<T>(url, { method: "POST", body: JSON.stringify(body) }),
84
+ put: <T>(url: string, body: any) => request<T>(url, { method: "PUT", body: JSON.stringify(body) }),
85
+ delete: <T>(url: string) => request<T>(url, { method: "DELETE" }),
86
+ // For PICO/Avatar uploads later:
87
+ upload: <T>(url: string, formData: FormData) => request<T>(url, { method: "POST", body: formData }),
88
+ };
src/lib/constants/api.ts ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const API_BASE_URL = 'https://example.com/api'; // Base URL for your API
2
+
3
+ // Default request timeout in milliseconds
4
+ export const TIMEOUT = 5000;
5
+
6
+ // Default headers for API requests
7
+ export const DEFAULT_HEADERS = {
8
+ 'Content-Type': 'application/json',
9
+ Accept: 'application/json',
10
+ };
11
+
12
+ // Helper to build full API URLs
13
+ export const buildApiUrl = (path: string) => {
14
+ const trimmedPath = path.startsWith('/') ? path.slice(1) : path;
15
+ return `${API_BASE_URL}/${trimmedPath}`;
16
+ };
17
+
18
+ // Common API endpoints
19
+ export const ENDPOINTS = {
20
+ USERS: 'users',
21
+ POSTS: 'posts',
22
+ AUTH_LOGIN: 'auth/login',
23
+ AUTH_REGISTER: 'auth/register',
24
+ };
src/{cn.ts → lib/utils.ts} RENAMED
@@ -1,6 +1,10 @@
1
  import { type ClassValue, clsx } from "clsx"
2
  import { twMerge } from "tailwind-merge"
3
 
 
 
 
 
4
  export function cn(...inputs: ClassValue[]) {
5
  return twMerge(clsx(inputs))
6
  }
 
1
  import { type ClassValue, clsx } from "clsx"
2
  import { twMerge } from "tailwind-merge"
3
 
4
+ /**
5
+ * cn - Class Names Utility
6
+ * Combines clsx and tailwind-merge for optimal class handling
7
+ */
8
  export function cn(...inputs: ClassValue[]) {
9
  return twMerge(clsx(inputs))
10
  }
src/lib/utils/index.ts ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ // Re-export utils and validators for easy imports
2
+ export * from '../utils';
3
+ export * from './validators';
src/lib/utils/validators.ts ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Research Data Validators
3
+ * Pure functions to validate scientific identifiers and form inputs.
4
+ */
5
+
6
+ // 1. Digital Object Identifier (DOI) Regex
7
+ // Matches standard DOI formats (e.g., 10.1038/s41586-021-03491-6)
8
+ const DOI_REGEX = /^10.\d{4,9}\/[-._;()/:A-Z0-9]+$/i;
9
+
10
+ // 2. Email Regex (Standard RFC 5322)
11
+ const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
12
+
13
+ export const validators = {
14
+ /** Checks if a string is a valid DOI */
15
+ isDOI: (val: string): boolean => DOI_REGEX.test(val.trim()),
16
+
17
+ /** Checks if a string is a valid institutional email */
18
+ isEmail: (val: string): boolean => EMAIL_REGEX.test(val),
19
+
20
+ /** Ensures PICO instructions aren't just empty whitespace */
21
+ isValidInstructions: (val: string): boolean => val.trim().length >= 10,
22
+
23
+ /** Validates PICO Data object from the backend */
24
+ hasCompletePico: (pico: any): boolean => {
25
+ return !!(
26
+ pico?.pico_population &&
27
+ pico?.pico_intervention &&
28
+ pico?.pico_outcome
29
+ );
30
+ }
31
+ };