mirror of
https://github.com/harivansh-afk/RAG-ui.git
synced 2026-04-15 05:02:10 +00:00
Initial commit
This commit is contained in:
commit
ae239a2849
42 changed files with 6674 additions and 0 deletions
3
.bolt/config.json
Normal file
3
.bolt/config.json
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"template": "bolt-vite-react-ts"
|
||||||
|
}
|
||||||
8
.bolt/prompt
Normal file
8
.bolt/prompt
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
For all designs I ask you to make, have them be beautiful, not cookie cutter. Make webpages that are fully featured and worthy for production.
|
||||||
|
|
||||||
|
By default, this template supports JSX syntax with Tailwind CSS classes, React hooks, and Lucide React for icons. Do not install other packages for UI themes, icons, etc unless absolutely necessary or I request them.
|
||||||
|
|
||||||
|
Use icons from lucide-react for logos.
|
||||||
|
|
||||||
|
Use stock photos from unsplash where appropriate, only valid URLs you know exist. Do not download the images, only link to them in image tags.
|
||||||
|
|
||||||
217
.bolt/technical_documentation.md
Normal file
217
.bolt/technical_documentation.md
Normal file
|
|
@ -0,0 +1,217 @@
|
||||||
|
# Technical Documentation
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
This is a modern web application built as a study/learning platform with features for question asking, material management, and progress tracking.
|
||||||
|
|
||||||
|
## Optimized Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
Root/
|
||||||
|
├── src/
|
||||||
|
│ ├── components/
|
||||||
|
│ │ ├── common/ # Shared components
|
||||||
|
│ │ │ ├── Button/
|
||||||
|
│ │ │ ├── Input/
|
||||||
|
│ │ │ └── Card/
|
||||||
|
│ │ ├── layout/ # Layout components
|
||||||
|
│ │ │ ├── Header/
|
||||||
|
│ │ │ ├── Sidebar/
|
||||||
|
│ │ │ └── DashboardLayout/
|
||||||
|
│ │ └── features/ # Feature-specific components
|
||||||
|
│ │ ├── auth/
|
||||||
|
│ │ ├── study/
|
||||||
|
│ │ └── dashboard/
|
||||||
|
│ ├── pages/ # Page components
|
||||||
|
│ │ ├── auth/ # Authentication pages
|
||||||
|
│ │ │ ├── Login.tsx
|
||||||
|
│ │ │ └── Signup.tsx
|
||||||
|
│ │ ├── dashboard/ # Dashboard pages
|
||||||
|
│ │ │ ├── index.tsx
|
||||||
|
│ │ │ ├── ask.tsx
|
||||||
|
│ │ │ ├── upload.tsx
|
||||||
|
│ │ │ ├── resources.tsx
|
||||||
|
│ │ │ ├── history.tsx
|
||||||
|
│ │ │ └── settings.tsx
|
||||||
|
│ │ └── Home.tsx
|
||||||
|
│ ├── routes/ # Routing configuration
|
||||||
|
│ │ ├── index.tsx # Root router
|
||||||
|
│ │ ├── PrivateRoute.tsx
|
||||||
|
│ │ └── routes.ts # Route definitions
|
||||||
|
│ ├── hooks/ # Custom hooks
|
||||||
|
│ ├── utils/ # Utility functions
|
||||||
|
│ ├── types/ # TypeScript types
|
||||||
|
│ ├── constants/ # Constants and configurations
|
||||||
|
│ ├── services/ # API services
|
||||||
|
│ ├── context/ # React context providers
|
||||||
|
│ ├── styles/ # Global styles
|
||||||
|
│ ├── App.tsx
|
||||||
|
│ └── main.tsx
|
||||||
|
├── public/
|
||||||
|
└── config/ # Configuration files
|
||||||
|
```
|
||||||
|
|
||||||
|
## Routing Implementation
|
||||||
|
|
||||||
|
### Route Structure
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
routes/
|
||||||
|
├── index.tsx # Main router configuration
|
||||||
|
├── PrivateRoute.tsx # Protected route wrapper
|
||||||
|
└── routes.ts # Route definitions
|
||||||
|
|
||||||
|
// Route Configuration
|
||||||
|
const routes = {
|
||||||
|
public: {
|
||||||
|
home: '/',
|
||||||
|
login: '/auth/login',
|
||||||
|
signup: '/auth/signup',
|
||||||
|
},
|
||||||
|
private: {
|
||||||
|
dashboard: {
|
||||||
|
root: '/dashboard',
|
||||||
|
ask: '/dashboard/ask',
|
||||||
|
upload: '/dashboard/upload',
|
||||||
|
resources: '/dashboard/resources',
|
||||||
|
history: '/dashboard/history',
|
||||||
|
settings: '/dashboard/settings',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Route Guards
|
||||||
|
|
||||||
|
- Implementation of protected routes using `PrivateRoute` component
|
||||||
|
- Authentication state management
|
||||||
|
- Role-based access control
|
||||||
|
|
||||||
|
## Component Architecture
|
||||||
|
|
||||||
|
### Layout Components
|
||||||
|
|
||||||
|
- **DashboardLayout**: HOC for dashboard pages with sidebar and header
|
||||||
|
- **AuthLayout**: HOC for authentication pages
|
||||||
|
- **PublicLayout**: HOC for public pages
|
||||||
|
|
||||||
|
### Feature Components
|
||||||
|
|
||||||
|
1. **Auth Module**
|
||||||
|
|
||||||
|
- LoginForm
|
||||||
|
- SignupForm
|
||||||
|
- PasswordReset
|
||||||
|
|
||||||
|
2. **Dashboard Module**
|
||||||
|
|
||||||
|
- QuestionForm
|
||||||
|
- MaterialUploader
|
||||||
|
- ResourceViewer
|
||||||
|
- HistoryTracker
|
||||||
|
|
||||||
|
3. **Common Components**
|
||||||
|
- Button
|
||||||
|
- Input
|
||||||
|
- Card
|
||||||
|
- Modal
|
||||||
|
- Loader
|
||||||
|
|
||||||
|
## State Management
|
||||||
|
|
||||||
|
- React Context for global state
|
||||||
|
- Custom hooks for shared logic
|
||||||
|
- Local component state for UI
|
||||||
|
|
||||||
|
## API Integration
|
||||||
|
|
||||||
|
- Axios for HTTP requests
|
||||||
|
- Service layer for API calls
|
||||||
|
- Request/Response interceptors
|
||||||
|
- Error handling middleware
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
- Global error boundary
|
||||||
|
- Form validation
|
||||||
|
- API error handling
|
||||||
|
- User feedback system
|
||||||
|
|
||||||
|
## Performance Optimizations
|
||||||
|
|
||||||
|
- Route-based code splitting
|
||||||
|
- Lazy loading of components
|
||||||
|
- Memoization of expensive computations
|
||||||
|
- Image optimization
|
||||||
|
|
||||||
|
## Security Measures
|
||||||
|
|
||||||
|
- Protected routes
|
||||||
|
- JWT token management
|
||||||
|
- XSS prevention
|
||||||
|
- CSRF protection
|
||||||
|
- Input sanitization
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
- Unit tests for utilities
|
||||||
|
- Component tests
|
||||||
|
- Integration tests
|
||||||
|
- E2E tests for critical flows
|
||||||
|
|
||||||
|
## Build Configuration
|
||||||
|
|
||||||
|
- Development and production builds
|
||||||
|
- Environment variables
|
||||||
|
- Bundle optimization
|
||||||
|
- Asset management
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
### Core Dependencies
|
||||||
|
|
||||||
|
- React
|
||||||
|
- React DOM
|
||||||
|
- React Router
|
||||||
|
|
||||||
|
### UI Dependencies
|
||||||
|
|
||||||
|
- Radix UI components
|
||||||
|
- Lucide React icons
|
||||||
|
|
||||||
|
### Development Dependencies
|
||||||
|
|
||||||
|
- TypeScript
|
||||||
|
- ESLint
|
||||||
|
- Vite
|
||||||
|
- TailwindCSS
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
The application follows a modern React application architecture with:
|
||||||
|
|
||||||
|
- Modular and reusable components
|
||||||
|
- Centralized routing in App.tsx
|
||||||
|
- Tailwind CSS for styling
|
||||||
|
- TypeScript for type safety
|
||||||
|
- Separated configuration files
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
1. User Authentication
|
||||||
|
|
||||||
|
- Login
|
||||||
|
- Signup
|
||||||
|
- Session management
|
||||||
|
|
||||||
|
2. Study Tools
|
||||||
|
|
||||||
|
- Question submission
|
||||||
|
- Material uploads
|
||||||
|
- Resource management
|
||||||
|
- Progress tracking
|
||||||
|
|
||||||
|
3. User Settings
|
||||||
|
- Profile management
|
||||||
|
- Preferences
|
||||||
|
- History tracking
|
||||||
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
28
eslint.config.js
Normal file
28
eslint.config.js
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import js from '@eslint/js';
|
||||||
|
import globals from 'globals';
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks';
|
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
{ ignores: ['dist'] },
|
||||||
|
{
|
||||||
|
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
globals: globals.browser,
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
'react-hooks': reactHooks,
|
||||||
|
'react-refresh': reactRefresh,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...reactHooks.configs.recommended.rules,
|
||||||
|
'react-refresh/only-export-components': [
|
||||||
|
'warn',
|
||||||
|
{ allowConstantExport: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
13
index.html
Normal file
13
index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite + React + TS</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
5079
package-lock.json
generated
Normal file
5079
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
46
package.json
Normal file
46
package.json
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
"name": "vite-react-typescript-starter",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-avatar": "^1.0.4",
|
||||||
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
|
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||||
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
|
"@radix-ui/react-navigation-menu": "^1.1.4",
|
||||||
|
"@radix-ui/react-scroll-area": "^1.0.5",
|
||||||
|
"@radix-ui/react-separator": "^1.0.3",
|
||||||
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
|
"@radix-ui/react-tabs": "^1.0.4",
|
||||||
|
"class-variance-authority": "^0.7.0",
|
||||||
|
"clsx": "^2.1.0",
|
||||||
|
"lucide-react": "^0.344.0",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-router-dom": "^6.22.3",
|
||||||
|
"tailwind-merge": "^2.2.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.9.1",
|
||||||
|
"@types/react": "^18.3.5",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"@vitejs/plugin-react": "^4.3.1",
|
||||||
|
"autoprefixer": "^10.4.18",
|
||||||
|
"eslint": "^9.9.1",
|
||||||
|
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.11",
|
||||||
|
"globals": "^15.9.0",
|
||||||
|
"postcss": "^8.4.35",
|
||||||
|
"tailwindcss": "^3.4.1",
|
||||||
|
"typescript": "^5.5.3",
|
||||||
|
"typescript-eslint": "^8.3.0",
|
||||||
|
"vite": "^5.4.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
16
src/App.tsx
Normal file
16
src/App.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
import { AppRouter } from './routes';
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
|
const App: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<BrowserRouter>
|
||||||
|
<div className="min-h-screen bg-background text-foreground">
|
||||||
|
<AppRouter />
|
||||||
|
</div>
|
||||||
|
</BrowserRouter>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
||||||
33
src/components/dashboard/QuestionList.tsx
Normal file
33
src/components/dashboard/QuestionList.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { MessageSquare } from 'lucide-react';
|
||||||
|
|
||||||
|
interface Question {
|
||||||
|
id: string;
|
||||||
|
text: string;
|
||||||
|
timestamp: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QuestionListProps {
|
||||||
|
questions: Question[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function QuestionList({ questions }: QuestionListProps) {
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{questions.map((question) => (
|
||||||
|
<div
|
||||||
|
key={question.id}
|
||||||
|
className="flex items-start space-x-4 rounded-lg border p-4 hover:bg-muted/50"
|
||||||
|
>
|
||||||
|
<MessageSquare className="h-5 w-5 text-muted-foreground" />
|
||||||
|
<div className="flex-1">
|
||||||
|
<p className="font-medium">{question.text}</p>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{question.timestamp.toLocaleDateString()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
29
src/components/dashboard/StatsCard.tsx
Normal file
29
src/components/dashboard/StatsCard.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { Card } from '../ui/card'
|
||||||
|
|
||||||
|
interface StatsCardProps {
|
||||||
|
title: string
|
||||||
|
value: string
|
||||||
|
icon: React.ReactNode
|
||||||
|
trend: {
|
||||||
|
value: string
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StatsCard({ title, value, icon, trend }: StatsCardProps) {
|
||||||
|
return (
|
||||||
|
<Card className="p-6">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h3 className="text-sm font-medium text-muted-foreground">{title}</h3>
|
||||||
|
{icon}
|
||||||
|
</div>
|
||||||
|
<div className="mt-2">
|
||||||
|
<p className="text-2xl font-bold">{value}</p>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
<span className="text-green-600">{trend.value}</span> {trend.label}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
16
src/components/layout/AuthLayout/index.tsx
Normal file
16
src/components/layout/AuthLayout/index.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
|
import { Header } from '../Header';
|
||||||
|
|
||||||
|
const AuthLayout: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-background">
|
||||||
|
<Header />
|
||||||
|
<div className="container mx-auto px-4 py-8">
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthLayout;
|
||||||
20
src/components/layout/DashboardLayout/index.tsx
Normal file
20
src/components/layout/DashboardLayout/index.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
|
import { Header } from '../Header';
|
||||||
|
import { Sidebar } from '../Sidebar';
|
||||||
|
|
||||||
|
const DashboardLayout: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-screen flex-col">
|
||||||
|
<Header />
|
||||||
|
<div className="flex flex-1">
|
||||||
|
<Sidebar />
|
||||||
|
<main className="flex-1 overflow-y-auto bg-muted/50 p-6">
|
||||||
|
<Outlet />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DashboardLayout;
|
||||||
28
src/components/layout/Header/index.tsx
Normal file
28
src/components/layout/Header/index.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { BookOpen } from 'lucide-react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { Button } from '../../ui/Button';
|
||||||
|
|
||||||
|
export function Header() {
|
||||||
|
return (
|
||||||
|
<header className="border-b bg-background">
|
||||||
|
<div className="mx-auto flex max-w-7xl items-center justify-between px-4 py-4">
|
||||||
|
<Link to="/" className="flex items-center gap-2 text-xl font-bold">
|
||||||
|
<BookOpen className="h-6 w-6 text-foreground" />
|
||||||
|
<span>StudyAI</span>
|
||||||
|
</Link>
|
||||||
|
<nav className="flex items-center gap-4">
|
||||||
|
<Link to="/dashboard">
|
||||||
|
<Button variant="ghost">Dashboard</Button>
|
||||||
|
</Link>
|
||||||
|
<Link to="/auth/login">
|
||||||
|
<Button variant="ghost">Log in</Button>
|
||||||
|
</Link>
|
||||||
|
<Link to="/auth/signup">
|
||||||
|
<Button>Sign up</Button>
|
||||||
|
</Link>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
74
src/components/layout/Sidebar/index.tsx
Normal file
74
src/components/layout/Sidebar/index.tsx
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
|
import {
|
||||||
|
Settings,
|
||||||
|
MessageSquarePlus,
|
||||||
|
Upload,
|
||||||
|
BookOpen,
|
||||||
|
GraduationCap,
|
||||||
|
History
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { cn } from '../../../lib/utils';
|
||||||
|
|
||||||
|
const sidebarItems = [
|
||||||
|
{
|
||||||
|
icon: MessageSquarePlus,
|
||||||
|
label: 'Ask a Question',
|
||||||
|
path: '/dashboard/ask'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Upload,
|
||||||
|
label: 'Upload Materials',
|
||||||
|
path: '/dashboard/upload'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: BookOpen,
|
||||||
|
label: 'Study Resources',
|
||||||
|
path: '/dashboard/resources'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: History,
|
||||||
|
label: 'Study History',
|
||||||
|
path: '/dashboard/history'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Settings,
|
||||||
|
label: 'Settings',
|
||||||
|
path: '/dashboard/settings'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function Sidebar() {
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-64 flex-col border-r bg-background">
|
||||||
|
<div className="flex h-14 items-center border-b px-4">
|
||||||
|
<GraduationCap className="h-6 w-6" />
|
||||||
|
<span className="ml-2 font-semibold">Study Dashboard</span>
|
||||||
|
</div>
|
||||||
|
<nav className="flex-1 space-y-1 px-2 py-4">
|
||||||
|
{sidebarItems.map((item) => {
|
||||||
|
const Icon = item.icon;
|
||||||
|
const isActive = location.pathname === item.path;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={item.path}
|
||||||
|
to={item.path}
|
||||||
|
className={cn(
|
||||||
|
'flex items-center rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
||||||
|
isActive
|
||||||
|
? 'bg-muted text-foreground'
|
||||||
|
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon className="mr-3 h-5 w-5" />
|
||||||
|
{item.label}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
41
src/components/ui/Button.tsx
Normal file
41
src/components/ui/Button.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { cva, type VariantProps } from 'class-variance-authority';
|
||||||
|
import { cn } from '../../lib/utils';
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background',
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||||
|
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
||||||
|
outline: 'border border-input hover:bg-accent hover:text-accent-foreground',
|
||||||
|
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||||
|
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||||
|
link: 'underline-offset-4 hover:underline text-primary',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: 'h-10 py-2 px-4',
|
||||||
|
sm: 'h-9 px-3 rounded-md',
|
||||||
|
lg: 'h-11 px-8 rounded-md',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'default',
|
||||||
|
size: 'default',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface ButtonProps
|
||||||
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
|
VariantProps<typeof buttonVariants> {}
|
||||||
|
|
||||||
|
export function Button({ className, variant, size, ...props }: ButtonProps) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
16
src/components/ui/card.tsx
Normal file
16
src/components/ui/card.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { cn } from '../../lib/utils';
|
||||||
|
|
||||||
|
type CardProps = React.HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
|
export function Card({ className, ...props }: CardProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'rounded-lg border bg-card text-card-foreground shadow-sm',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
23
src/components/ui/separator.tsx
Normal file
23
src/components/ui/separator.tsx
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { cn } from '../../lib/utils';
|
||||||
|
|
||||||
|
interface SeparatorProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
|
orientation?: 'horizontal' | 'vertical';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Separator({
|
||||||
|
className,
|
||||||
|
orientation = 'horizontal',
|
||||||
|
...props
|
||||||
|
}: SeparatorProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'shrink-0 bg-border',
|
||||||
|
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
59
src/index.css
Normal file
59
src/index.css
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
--foreground: 0 0% 3.9%;
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
--card-foreground: 0 0% 3.9%;
|
||||||
|
--popover: 0 0% 100%;
|
||||||
|
--popover-foreground: 0 0% 3.9%;
|
||||||
|
--primary: 0 0% 9%;
|
||||||
|
--primary-foreground: 0 0% 98%;
|
||||||
|
--secondary: 0 0% 96.1%;
|
||||||
|
--secondary-foreground: 0 0% 9%;
|
||||||
|
--muted: 0 0% 96.1%;
|
||||||
|
--muted-foreground: 0 0% 45.1%;
|
||||||
|
--accent: 0 0% 96.1%;
|
||||||
|
--accent-foreground: 0 0% 9%;
|
||||||
|
--destructive: 0 84.2% 60.2%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
--border: 0 0% 89.8%;
|
||||||
|
--input: 0 0% 89.8%;
|
||||||
|
--ring: 0 0% 3.9%;
|
||||||
|
--radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: 0 0% 3.9%;
|
||||||
|
--foreground: 0 0% 98%;
|
||||||
|
--card: 0 0% 3.9%;
|
||||||
|
--card-foreground: 0 0% 98%;
|
||||||
|
--popover: 0 0% 3.9%;
|
||||||
|
--popover-foreground: 0 0% 98%;
|
||||||
|
--primary: 0 0% 98%;
|
||||||
|
--primary-foreground: 0 0% 9%;
|
||||||
|
--secondary: 0 0% 14.9%;
|
||||||
|
--secondary-foreground: 0 0% 98%;
|
||||||
|
--muted: 0 0% 14.9%;
|
||||||
|
--muted-foreground: 0 0% 63.9%;
|
||||||
|
--accent: 0 0% 14.9%;
|
||||||
|
--accent-foreground: 0 0% 98%;
|
||||||
|
--destructive: 0 62.8% 30.6%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
--border: 0 0% 14.9%;
|
||||||
|
--input: 0 0% 14.9%;
|
||||||
|
--ring: 0 0% 83.1%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/lib/utils.ts
Normal file
6
src/lib/utils.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { type ClassValue, clsx } from 'clsx';
|
||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs));
|
||||||
|
}
|
||||||
13
src/main.tsx
Normal file
13
src/main.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { StrictMode } from 'react';
|
||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
import App from './App';
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
|
const rootElement = document.getElementById('root');
|
||||||
|
if (!rootElement) throw new Error('Root element not found');
|
||||||
|
|
||||||
|
createRoot(rootElement).render(
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>
|
||||||
|
);
|
||||||
72
src/pages/Home.tsx
Normal file
72
src/pages/Home.tsx
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { ArrowRight, Brain, Sparkles, Users } from 'lucide-react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { Button } from '../components/ui/Button';
|
||||||
|
import { Header } from '../components/layout/Header';
|
||||||
|
|
||||||
|
function Home() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Header />
|
||||||
|
<main>
|
||||||
|
<section className="bg-muted/50 px-4 py-20">
|
||||||
|
<div className="mx-auto max-w-7xl text-center">
|
||||||
|
<h1 className="text-5xl font-bold tracking-tight">
|
||||||
|
Your AI Study Buddy
|
||||||
|
</h1>
|
||||||
|
<p className="mx-auto mt-6 max-w-2xl text-lg text-muted-foreground">
|
||||||
|
Get instant help with your coursework using advanced AI. Upload your materials,
|
||||||
|
ask questions, and receive detailed explanations.
|
||||||
|
</p>
|
||||||
|
<div className="mt-10">
|
||||||
|
<Link to="/auth/signup">
|
||||||
|
<Button size="lg" className="gap-2">
|
||||||
|
Get Started <ArrowRight className="h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="py-20">
|
||||||
|
<div className="mx-auto max-w-7xl px-4">
|
||||||
|
<h2 className="text-center text-3xl font-bold">Why Choose StudyAI?</h2>
|
||||||
|
<div className="mt-12 grid grid-cols-1 gap-8 md:grid-cols-3">
|
||||||
|
{[
|
||||||
|
{
|
||||||
|
icon: Brain,
|
||||||
|
title: 'Smart Learning',
|
||||||
|
description:
|
||||||
|
'Our AI understands your questions and provides detailed, accurate answers.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Sparkles,
|
||||||
|
title: 'Instant Help',
|
||||||
|
description:
|
||||||
|
'Get immediate assistance with your coursework, available 24/7.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Users,
|
||||||
|
title: 'Personalized Experience',
|
||||||
|
description:
|
||||||
|
'The more you use StudyAI, the better it understands your learning style.',
|
||||||
|
},
|
||||||
|
].map((feature) => (
|
||||||
|
<div
|
||||||
|
key={feature.title}
|
||||||
|
className="rounded-xl border bg-card p-6 text-center shadow-sm"
|
||||||
|
>
|
||||||
|
<feature.icon className="mx-auto h-12 w-12 text-primary" />
|
||||||
|
<h3 className="mt-4 text-xl font-semibold">{feature.title}</h3>
|
||||||
|
<p className="mt-2 text-muted-foreground">{feature.description}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Home;
|
||||||
76
src/pages/auth/Login.tsx
Normal file
76
src/pages/auth/Login.tsx
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { Button } from '../../components/ui/Button';
|
||||||
|
|
||||||
|
function Login() {
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
// Handle login
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-[calc(100vh-4rem)] items-center justify-center bg-muted/50 py-12 px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="w-full max-w-md space-y-8">
|
||||||
|
<div>
|
||||||
|
<h2 className="mt-6 text-center text-3xl font-bold tracking-tight">
|
||||||
|
Sign in to your account
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
||||||
|
<div className="space-y-4 rounded-md">
|
||||||
|
<div>
|
||||||
|
<label htmlFor="email" className="sr-only">
|
||||||
|
Email address
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
autoComplete="email"
|
||||||
|
required
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
className="relative block w-full rounded-md border-0 py-1.5 px-3 bg-background text-foreground shadow-sm ring-1 ring-inset ring-input placeholder:text-muted-foreground focus:ring-2 focus:ring-inset focus:ring-primary sm:text-sm sm:leading-6"
|
||||||
|
placeholder="Email address"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="password" className="sr-only">
|
||||||
|
Password
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
autoComplete="current-password"
|
||||||
|
required
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
className="relative block w-full rounded-md border-0 py-1.5 px-3 bg-background text-foreground shadow-sm ring-1 ring-inset ring-input placeholder:text-muted-foreground focus:ring-2 focus:ring-inset focus:ring-primary sm:text-sm sm:leading-6"
|
||||||
|
placeholder="Password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Button type="submit" className="w-full">
|
||||||
|
Sign in
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p className="mt-2 text-center text-sm text-muted-foreground">
|
||||||
|
Don't have an account?{' '}
|
||||||
|
<Link to="/signup" className="font-medium text-primary hover:text-primary/90">
|
||||||
|
Sign up
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Login;
|
||||||
92
src/pages/auth/Signup.tsx
Normal file
92
src/pages/auth/Signup.tsx
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { Button } from '../../components/ui/Button';
|
||||||
|
|
||||||
|
function Signup() {
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const [name, setName] = useState('');
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
// Handle signup
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-[calc(100vh-4rem)] items-center justify-center bg-muted/50 py-12 px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="w-full max-w-md space-y-8">
|
||||||
|
<div>
|
||||||
|
<h2 className="mt-6 text-center text-3xl font-bold tracking-tight">
|
||||||
|
Create your account
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
||||||
|
<div className="space-y-4 rounded-md">
|
||||||
|
<div>
|
||||||
|
<label htmlFor="name" className="sr-only">
|
||||||
|
Full name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
className="relative block w-full rounded-md border-0 py-1.5 px-3 bg-background text-foreground shadow-sm ring-1 ring-inset ring-input placeholder:text-muted-foreground focus:ring-2 focus:ring-inset focus:ring-primary sm:text-sm sm:leading-6"
|
||||||
|
placeholder="Full name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="email" className="sr-only">
|
||||||
|
Email address
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
autoComplete="email"
|
||||||
|
required
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
className="relative block w-full rounded-md border-0 py-1.5 px-3 bg-background text-foreground shadow-sm ring-1 ring-inset ring-input placeholder:text-muted-foreground focus:ring-2 focus:ring-inset focus:ring-primary sm:text-sm sm:leading-6"
|
||||||
|
placeholder="Email address"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="password" className="sr-only">
|
||||||
|
Password
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
autoComplete="new-password"
|
||||||
|
required
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
className="relative block w-full rounded-md border-0 py-1.5 px-3 bg-background text-foreground shadow-sm ring-1 ring-inset ring-input placeholder:text-muted-foreground focus:ring-2 focus:ring-inset focus:ring-primary sm:text-sm sm:leading-6"
|
||||||
|
placeholder="Password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Button type="submit" className="w-full">
|
||||||
|
Sign up
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p className="mt-2 text-center text-sm text-muted-foreground">
|
||||||
|
Already have an account?{' '}
|
||||||
|
<Link to="/login" className="font-medium text-primary hover:text-primary/90">
|
||||||
|
Sign in
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Signup;
|
||||||
68
src/pages/dashboard/Settings.tsx
Normal file
68
src/pages/dashboard/Settings.tsx
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Button } from '../../components/ui/Button';
|
||||||
|
import { Card } from '../../components/ui/card';
|
||||||
|
|
||||||
|
function Settings() {
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<h1 className="text-3xl font-bold">Settings</h1>
|
||||||
|
|
||||||
|
<Card className="p-6">
|
||||||
|
<h2 className="text-xl font-semibold">Profile Settings</h2>
|
||||||
|
<div className="mt-4 space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-foreground">
|
||||||
|
Display Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="mt-1 block w-full rounded-md border border-input bg-background px-3 py-2"
|
||||||
|
placeholder="Your name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-foreground">
|
||||||
|
Email
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
className="mt-1 block w-full rounded-md border border-input bg-background px-3 py-2"
|
||||||
|
placeholder="your@email.com"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="p-6">
|
||||||
|
<h2 className="text-xl font-semibold">Notification Settings</h2>
|
||||||
|
<div className="mt-4 space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 className="font-medium">Email Notifications</h3>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Receive updates about your study progress
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button variant="outline">Configure</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="p-6">
|
||||||
|
<h2 className="text-xl font-semibold">Account Settings</h2>
|
||||||
|
<div className="mt-4 space-y-4">
|
||||||
|
<div>
|
||||||
|
<Button variant="outline" className="text-destructive">
|
||||||
|
Delete Account
|
||||||
|
</Button>
|
||||||
|
<p className="mt-2 text-sm text-muted-foreground">
|
||||||
|
This action cannot be undone.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Settings;
|
||||||
53
src/pages/dashboard/ask.tsx
Normal file
53
src/pages/dashboard/ask.tsx
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Send, Upload } from 'lucide-react';
|
||||||
|
import { Button } from '../../components/ui/Button';
|
||||||
|
|
||||||
|
function AskQuestion() {
|
||||||
|
const [question, setQuestion] = useState('');
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
// Handle question submission
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<h1 className="text-3xl font-bold">Ask a Question</h1>
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
|
<div className="rounded-lg bg-card p-6 shadow-sm">
|
||||||
|
<label className="block">
|
||||||
|
<span className="text-sm font-medium text-foreground">
|
||||||
|
What would you like to know?
|
||||||
|
</span>
|
||||||
|
<textarea
|
||||||
|
value={question}
|
||||||
|
onChange={(e) => setQuestion(e.target.value)}
|
||||||
|
className="mt-2 block w-full rounded-lg border border-input px-4 py-2 focus:border-primary focus:ring-primary"
|
||||||
|
rows={4}
|
||||||
|
placeholder="Type your question here..."
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg bg-card p-6 shadow-sm">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm font-medium text-foreground">
|
||||||
|
Upload Study Materials (Optional)
|
||||||
|
</span>
|
||||||
|
<Button variant="secondary" type="button" className="gap-2">
|
||||||
|
<Upload className="h-4 w-4" />
|
||||||
|
Upload Files
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button type="submit" className="w-full gap-2">
|
||||||
|
<Send className="h-5 w-5" />
|
||||||
|
Submit Question
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AskQuestion;
|
||||||
65
src/pages/dashboard/history.tsx
Normal file
65
src/pages/dashboard/history.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Calendar } from 'lucide-react';
|
||||||
|
import { Card } from '../../components/ui/card';
|
||||||
|
|
||||||
|
const mockHistory = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
date: new Date('2024-03-10'),
|
||||||
|
activity: 'Studied Biology',
|
||||||
|
duration: '2 hours',
|
||||||
|
topics: ['Photosynthesis', 'Cell Structure'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
date: new Date('2024-03-09'),
|
||||||
|
activity: 'Practice Problems',
|
||||||
|
duration: '1.5 hours',
|
||||||
|
topics: ['Algebra', 'Calculus'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
date: new Date('2024-03-08'),
|
||||||
|
activity: 'Reading Assignment',
|
||||||
|
duration: '1 hour',
|
||||||
|
topics: ['Literature', 'Poetry Analysis'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function StudyHistory() {
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<h1 className="text-3xl font-bold">Study History</h1>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{mockHistory.map((item) => (
|
||||||
|
<Card key={item.id} className="p-4">
|
||||||
|
<div className="flex items-start space-x-4">
|
||||||
|
<Calendar className="h-5 w-5 text-muted-foreground" />
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h3 className="font-medium">{item.activity}</h3>
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{item.date.toLocaleDateString()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground">Duration: {item.duration}</p>
|
||||||
|
<div className="mt-2 flex flex-wrap gap-2">
|
||||||
|
{item.topics.map((topic) => (
|
||||||
|
<span
|
||||||
|
key={topic}
|
||||||
|
className="rounded-full bg-primary/10 px-2 py-1 text-xs text-primary"
|
||||||
|
>
|
||||||
|
{topic}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StudyHistory;
|
||||||
73
src/pages/dashboard/index.tsx
Normal file
73
src/pages/dashboard/index.tsx
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Users, CreditCard, Activity, PlusCircle } from 'lucide-react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { Button } from '../../components/ui/Button';
|
||||||
|
import { QuestionList } from '../../components/dashboard/QuestionList';
|
||||||
|
import { StatsCard } from '../../components/dashboard/StatsCard';
|
||||||
|
import { Card } from '../../components/ui/card';
|
||||||
|
import { Separator } from '../../components/ui/separator';
|
||||||
|
|
||||||
|
const mockQuestions = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
text: 'How does photosynthesis work?',
|
||||||
|
timestamp: new Date('2024-03-10'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
text: 'Explain the concept of supply and demand in economics.',
|
||||||
|
timestamp: new Date('2024-03-09'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function Dashboard() {
|
||||||
|
return (
|
||||||
|
<div className="space-y-8">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h1 className="text-3xl font-bold">Dashboard</h1>
|
||||||
|
<Link to="/dashboard/ask">
|
||||||
|
<Button className="gap-2">
|
||||||
|
<PlusCircle className="h-5 w-5" />
|
||||||
|
Ask a Question
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-4 md:grid-cols-3">
|
||||||
|
<StatsCard
|
||||||
|
title="Total Questions"
|
||||||
|
value="156"
|
||||||
|
icon={<Users className="h-4 w-4 text-muted-foreground" />}
|
||||||
|
trend={{ value: "+12%", label: "from last month" }}
|
||||||
|
/>
|
||||||
|
<StatsCard
|
||||||
|
title="Study Sessions"
|
||||||
|
value="32"
|
||||||
|
icon={<CreditCard className="h-4 w-4 text-muted-foreground" />}
|
||||||
|
trend={{ value: "+8", label: "from last week" }}
|
||||||
|
/>
|
||||||
|
<StatsCard
|
||||||
|
title="Active Streak"
|
||||||
|
value="7 days"
|
||||||
|
icon={<Activity className="h-4 w-4 text-muted-foreground" />}
|
||||||
|
trend={{ value: "+2", label: "days" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<div className="p-6">
|
||||||
|
<h2 className="text-lg font-semibold">Recent Questions</h2>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Your most recent study questions and their status.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<div className="p-6">
|
||||||
|
<QuestionList questions={mockQuestions} />
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Dashboard;
|
||||||
63
src/pages/dashboard/resources.tsx
Normal file
63
src/pages/dashboard/resources.tsx
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Book, File, Folder } from 'lucide-react';
|
||||||
|
import { Card } from '../../components/ui/card';
|
||||||
|
|
||||||
|
const mockResources = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: 'folder',
|
||||||
|
name: 'Biology',
|
||||||
|
itemCount: 12,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: 'folder',
|
||||||
|
name: 'Physics',
|
||||||
|
itemCount: 8,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
type: 'file',
|
||||||
|
name: 'Math Notes.pdf',
|
||||||
|
size: '2.4 MB',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
type: 'book',
|
||||||
|
name: 'Chemistry Textbook',
|
||||||
|
author: 'John Smith',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function StudyResources() {
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<h1 className="text-3xl font-bold">Study Resources</h1>
|
||||||
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{mockResources.map((resource) => (
|
||||||
|
<Card key={resource.id} className="p-4 hover:bg-muted/50 cursor-pointer">
|
||||||
|
<div className="flex items-start space-x-4">
|
||||||
|
{resource.type === 'folder' && <Folder className="h-6 w-6 text-blue-500" />}
|
||||||
|
{resource.type === 'file' && <File className="h-6 w-6 text-green-500" />}
|
||||||
|
{resource.type === 'book' && <Book className="h-6 w-6 text-purple-500" />}
|
||||||
|
<div>
|
||||||
|
<h3 className="font-medium">{resource.name}</h3>
|
||||||
|
{resource.type === 'folder' && (
|
||||||
|
<p className="text-sm text-muted-foreground">{resource.itemCount} items</p>
|
||||||
|
)}
|
||||||
|
{resource.type === 'file' && (
|
||||||
|
<p className="text-sm text-muted-foreground">{resource.size}</p>
|
||||||
|
)}
|
||||||
|
{resource.type === 'book' && (
|
||||||
|
<p className="text-sm text-muted-foreground">By {resource.author}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StudyResources;
|
||||||
25
src/pages/dashboard/upload.tsx
Normal file
25
src/pages/dashboard/upload.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Upload } from 'lucide-react';
|
||||||
|
import { Button } from '../../components/ui/Button';
|
||||||
|
|
||||||
|
function UploadMaterials() {
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<h1 className="text-3xl font-bold">Upload Study Materials</h1>
|
||||||
|
<div className="rounded-lg bg-card p-6 shadow-sm">
|
||||||
|
<div className="flex flex-col items-center justify-center space-y-4 py-12">
|
||||||
|
<Upload className="h-12 w-12 text-muted-foreground" />
|
||||||
|
<div className="text-center">
|
||||||
|
<h2 className="text-lg font-semibold">Drop your files here</h2>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
or click to browse from your computer
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button variant="outline">Choose Files</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UploadMaterials;
|
||||||
25
src/routes/PrivateRoute.tsx
Normal file
25
src/routes/PrivateRoute.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Navigate, useLocation } from 'react-router-dom';
|
||||||
|
import { routes } from './routes';
|
||||||
|
|
||||||
|
// TODO: Replace with your actual auth hook
|
||||||
|
const useAuth = () => {
|
||||||
|
return {
|
||||||
|
isAuthenticated: true, // Temporarily set to true for development
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
interface PrivateRouteProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PrivateRoute({ children }: PrivateRouteProps) {
|
||||||
|
const { isAuthenticated } = useAuth();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
return <Navigate to={routes.public.login} state={{ from: location }} replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
73
src/routes/index.tsx
Normal file
73
src/routes/index.tsx
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
import React, { Suspense } from 'react';
|
||||||
|
import { Routes, Route, Navigate } from 'react-router-dom';
|
||||||
|
import { routes } from './routes';
|
||||||
|
import { PrivateRoute } from './PrivateRoute';
|
||||||
|
|
||||||
|
// Loading component
|
||||||
|
const Loading: React.FC = () => (
|
||||||
|
<div className="flex h-screen items-center justify-center">
|
||||||
|
<div className="text-lg">Loading...</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Layouts
|
||||||
|
const DashboardLayout = React.lazy(() => import('../components/layout/DashboardLayout'));
|
||||||
|
const AuthLayout = React.lazy(() => import('../components/layout/AuthLayout'));
|
||||||
|
|
||||||
|
// Public Pages
|
||||||
|
const Home = React.lazy(() => import('../pages/Home'));
|
||||||
|
const Login = React.lazy(() => import('../pages/auth/Login'));
|
||||||
|
const Signup = React.lazy(() => import('../pages/auth/Signup'));
|
||||||
|
|
||||||
|
// Dashboard Pages
|
||||||
|
const Dashboard = React.lazy(() => import('../pages/dashboard'));
|
||||||
|
const AskQuestion = React.lazy(() => import('../pages/dashboard/ask'));
|
||||||
|
const UploadMaterials = React.lazy(() => import('../pages/dashboard/upload'));
|
||||||
|
const StudyResources = React.lazy(() => import('../pages/dashboard/resources'));
|
||||||
|
const StudyHistory = React.lazy(() => import('../pages/dashboard/history'));
|
||||||
|
const Settings = React.lazy(() => import('../pages/dashboard/Settings'));
|
||||||
|
|
||||||
|
const withSuspense = (Component: React.LazyExoticComponent<React.ComponentType<object>>) => {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<Loading />}>
|
||||||
|
<Component />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AppRouter: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<Loading />}>
|
||||||
|
<Routes>
|
||||||
|
{/* Public Routes */}
|
||||||
|
<Route path={routes.public.home} element={withSuspense(Home)} />
|
||||||
|
|
||||||
|
{/* Auth Routes */}
|
||||||
|
<Route path="/auth" element={withSuspense(AuthLayout)}>
|
||||||
|
<Route path="login" element={withSuspense(Login)} />
|
||||||
|
<Route path="signup" element={withSuspense(Signup)} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
{/* Protected Dashboard Routes */}
|
||||||
|
<Route
|
||||||
|
path="/dashboard"
|
||||||
|
element={
|
||||||
|
<PrivateRoute>
|
||||||
|
{withSuspense(DashboardLayout)}
|
||||||
|
</PrivateRoute>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route index element={withSuspense(Dashboard)} />
|
||||||
|
<Route path="ask" element={withSuspense(AskQuestion)} />
|
||||||
|
<Route path="upload" element={withSuspense(UploadMaterials)} />
|
||||||
|
<Route path="resources" element={withSuspense(StudyResources)} />
|
||||||
|
<Route path="history" element={withSuspense(StudyHistory)} />
|
||||||
|
<Route path="settings" element={withSuspense(Settings)} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
{/* Catch-all route */}
|
||||||
|
<Route path="*" element={<Navigate to="/" replace />} />
|
||||||
|
</Routes>
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
};
|
||||||
21
src/routes/routes.ts
Normal file
21
src/routes/routes.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
export const routes = {
|
||||||
|
public: {
|
||||||
|
home: '/',
|
||||||
|
login: '/auth/login',
|
||||||
|
signup: '/auth/signup',
|
||||||
|
},
|
||||||
|
private: {
|
||||||
|
dashboard: {
|
||||||
|
root: '/dashboard',
|
||||||
|
ask: '/dashboard/ask',
|
||||||
|
upload: '/dashboard/upload',
|
||||||
|
resources: '/dashboard/resources',
|
||||||
|
history: '/dashboard/history',
|
||||||
|
settings: '/dashboard/settings',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type Routes = typeof routes;
|
||||||
|
export type PublicRoutes = keyof typeof routes.public;
|
||||||
|
export type PrivateRoutes = keyof typeof routes.private.dashboard;
|
||||||
11
src/types/index.ts
Normal file
11
src/types/index.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
export interface Question {
|
||||||
|
id: string;
|
||||||
|
text: string;
|
||||||
|
timestamp: Date;
|
||||||
|
answer?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface User {
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
26
src/utils/cn.ts
Normal file
26
src/utils/cn.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
type ClassValue = string | number | boolean | undefined | null;
|
||||||
|
type ClassArray = ClassValue[];
|
||||||
|
type ClassObject = { [key: string]: any };
|
||||||
|
type ClassInput = ClassValue | ClassArray | ClassObject;
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassInput[]): string {
|
||||||
|
const classes = [];
|
||||||
|
|
||||||
|
for (const input of inputs) {
|
||||||
|
if (!input) continue;
|
||||||
|
|
||||||
|
if (typeof input === 'string') {
|
||||||
|
classes.push(input);
|
||||||
|
} else if (Array.isArray(input)) {
|
||||||
|
classes.push(cn(...input));
|
||||||
|
} else if (typeof input === 'object') {
|
||||||
|
for (const key in input) {
|
||||||
|
if (input[key]) {
|
||||||
|
classes.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return classes.join(' ');
|
||||||
|
}
|
||||||
1
src/vite-env.d.ts
vendored
Normal file
1
src/vite-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/// <reference types="vite/client" />
|
||||||
63
tailwind.config.js
Normal file
63
tailwind.config.js
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
darkMode: ["class"],
|
||||||
|
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
||||||
|
theme: {
|
||||||
|
container: {
|
||||||
|
center: true,
|
||||||
|
padding: "2rem",
|
||||||
|
screens: {
|
||||||
|
"2xl": "1400px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
border: "hsl(0 0% 20%)",
|
||||||
|
input: "hsl(0 0% 20%)",
|
||||||
|
ring: "hsl(0 0% 20%)",
|
||||||
|
background: "hsl(0 0% 100%)",
|
||||||
|
foreground: "hsl(0 0% 0%)",
|
||||||
|
primary: {
|
||||||
|
DEFAULT: "hsl(0 0% 0%)",
|
||||||
|
foreground: "hsl(0 0% 100%)",
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
DEFAULT: "hsl(0 0% 96%)",
|
||||||
|
foreground: "hsl(0 0% 0%)",
|
||||||
|
},
|
||||||
|
destructive: {
|
||||||
|
DEFAULT: "hsl(0 0% 0%)",
|
||||||
|
foreground: "hsl(0 0% 100%)",
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
DEFAULT: "hsl(0 0% 96%)",
|
||||||
|
foreground: "hsl(0 0% 40%)",
|
||||||
|
},
|
||||||
|
accent: {
|
||||||
|
DEFAULT: "hsl(0 0% 96%)",
|
||||||
|
foreground: "hsl(0 0% 0%)",
|
||||||
|
},
|
||||||
|
popover: {
|
||||||
|
DEFAULT: "hsl(0 0% 100%)",
|
||||||
|
foreground: "hsl(0 0% 0%)",
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
DEFAULT: "hsl(0 0% 100%)",
|
||||||
|
foreground: "hsl(0 0% 0%)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
lg: "0.5rem",
|
||||||
|
md: "calc(0.5rem - 2px)",
|
||||||
|
sm: "calc(0.5rem - 4px)",
|
||||||
|
},
|
||||||
|
boxShadow: {
|
||||||
|
sm: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
|
||||||
|
DEFAULT: "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
|
||||||
|
md: "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
|
||||||
|
lg: "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
30
tsconfig.app.json
Normal file
30
tsconfig.app.json
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|
||||||
|
/* Path aliases */
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
7
tsconfig.json
Normal file
7
tsconfig.json
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
22
tsconfig.node.json
Normal file
22
tsconfig.node.json
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
10
vite.config.ts
Normal file
10
vite.config.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
optimizeDeps: {
|
||||||
|
exclude: ['lucide-react'],
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue