Initial commit

This commit is contained in:
Harivansh Rathi 2024-12-05 15:59:08 -05:00
commit ae239a2849
42 changed files with 6674 additions and 0 deletions

16
src/App.tsx Normal file
View 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;

View 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>
);
}

View 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>
)
}

View 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;

View 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;

View 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>
);
}

View 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>
);
}

View 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}
/>
);
}

View 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}
/>
);
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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
View 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
View 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
View 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
View 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
View file

@ -0,0 +1 @@
/// <reference types="vite/client" />