diff --git a/src/App.tsx b/src/App.tsx
index 9304030..9214bec 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,9 +1,33 @@
+import { Routes, Route } from 'react-router'
+import { useSearchParams } from 'react-router'
+import { useEffect } from 'react'
import { ThemeProvider } from './components/ThemeProvider'
+import { LoginPage } from './pages/auth/LoginPage'
+import { RegisterPage } from './pages/auth/RegisterPage'
+
+function Dashboard() {
+ const [searchParams, setSearchParams] = useSearchParams()
+
+ useEffect(() => {
+ if (searchParams.get('auth') === 'success') {
+ // Clear the auth parameter from URL
+ setSearchParams({})
+ // You could show a success toast here
+ console.log('OAuth authentication successful!')
+ }
+ }, [searchParams, setSearchParams])
+
+ return
Dashboard - Coming Soon
+}
function App() {
return (
- App
+
+ } />
+ } />
+ } />
+
)
}
diff --git a/src/components/ui/sidebar.tsx b/src/components/ui/sidebar.tsx
index 1ee5a45..30638ac 100644
--- a/src/components/ui/sidebar.tsx
+++ b/src/components/ui/sidebar.tsx
@@ -2,7 +2,7 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
-import { cva, VariantProps } from "class-variance-authority"
+import { cva, type VariantProps } from "class-variance-authority"
import { PanelLeftIcon } from "lucide-react"
import { useIsMobile } from "@/hooks/use-mobile"
diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx
index cd62aff..33d2d2a 100644
--- a/src/components/ui/sonner.tsx
+++ b/src/components/ui/sonner.tsx
@@ -1,5 +1,5 @@
import { useTheme } from "next-themes"
-import { Toaster as Sonner, ToasterProps } from "sonner"
+import { Toaster as Sonner, type ToasterProps } from "sonner"
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme()
diff --git a/src/lib/api.ts b/src/lib/api.ts
new file mode 100644
index 0000000..7a7b035
--- /dev/null
+++ b/src/lib/api.ts
@@ -0,0 +1,125 @@
+const API_BASE_URL = 'http://localhost:8000/api/v1'
+
+export interface LoginRequest {
+ email: string
+ password: string
+}
+
+export interface RegisterRequest {
+ email: string
+ password: string
+ name: string
+}
+
+export interface UserResponse {
+ id: number
+ email: string
+ name: string
+ picture?: string
+ role: string
+ credits: number
+ is_active: boolean
+ plan: Record
+ created_at: string
+ updated_at: string
+}
+
+class ApiError extends Error {
+ public status: number
+ public details?: unknown
+
+ constructor(
+ message: string,
+ status: number,
+ details?: unknown
+ ) {
+ super(message)
+ this.name = 'ApiError'
+ this.status = status
+ this.details = details
+ }
+}
+
+async function handleResponse(response: Response): Promise {
+ if (!response.ok) {
+ const errorData = await response.json().catch(() => ({}))
+ throw new ApiError(
+ errorData.detail || `HTTP error! status: ${response.status}`,
+ response.status,
+ errorData
+ )
+ }
+ return response.json()
+}
+
+export interface OAuthAuthorizationResponse {
+ authorization_url: string
+ state: string
+}
+
+export interface OAuthProvidersResponse {
+ providers: string[]
+}
+
+export const authApi = {
+ async login(data: LoginRequest): Promise {
+ const response = await fetch(`${API_BASE_URL}/auth/login`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ credentials: 'include',
+ body: JSON.stringify(data),
+ })
+ return handleResponse(response)
+ },
+
+ async register(data: RegisterRequest): Promise {
+ const response = await fetch(`${API_BASE_URL}/auth/register`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ credentials: 'include',
+ body: JSON.stringify(data),
+ })
+ return handleResponse(response)
+ },
+
+ async getCurrentUser(): Promise {
+ const response = await fetch(`${API_BASE_URL}/auth/me`, {
+ credentials: 'include',
+ })
+ return handleResponse(response)
+ },
+
+ async logout(): Promise {
+ const response = await fetch(`${API_BASE_URL}/auth/logout`, {
+ method: 'POST',
+ credentials: 'include',
+ })
+ await response.json()
+ },
+
+ async refreshToken(): Promise {
+ const response = await fetch(`${API_BASE_URL}/auth/refresh`, {
+ method: 'POST',
+ credentials: 'include',
+ })
+ await response.json()
+ },
+}
+
+export const oauthApi = {
+ async getProviders(): Promise {
+ const response = await fetch(`${API_BASE_URL}/oauth/providers`)
+ return handleResponse(response)
+ },
+
+ async getAuthorizationUrl(provider: string): Promise {
+ const response = await fetch(`${API_BASE_URL}/oauth/${provider}/authorize`)
+ return handleResponse(response)
+ },
+}
+
+export { ApiError }
\ No newline at end of file
diff --git a/src/pages/auth/LoginPage.tsx b/src/pages/auth/LoginPage.tsx
new file mode 100644
index 0000000..ac76504
--- /dev/null
+++ b/src/pages/auth/LoginPage.tsx
@@ -0,0 +1,193 @@
+import { useState, useEffect } from 'react'
+import { Link } from 'react-router'
+import { Button } from '@/components/ui/button'
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
+import { Input } from '@/components/ui/input'
+import { Label } from '@/components/ui/label'
+import { Separator } from '@/components/ui/separator'
+import { Github } from 'lucide-react'
+import { authApi, oauthApi, type LoginRequest } from '@/lib/api'
+
+export function LoginPage() {
+ const [formData, setFormData] = useState({
+ email: '',
+ password: '',
+ })
+ const [isLoading, setIsLoading] = useState(false)
+ const [isOAuthLoading, setIsOAuthLoading] = useState(null)
+ const [error, setError] = useState(null)
+ const [providers, setProviders] = useState([])
+
+ useEffect(() => {
+ oauthApi.getProviders()
+ .then(response => setProviders(response.providers))
+ .catch(error => console.error('Failed to load OAuth providers:', error))
+ }, [])
+
+ const handleInputChange = (e: React.ChangeEvent) => {
+ const { name, value } = e.target
+ setFormData(prev => ({
+ ...prev,
+ [name]: value,
+ }))
+ }
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault()
+ setIsLoading(true)
+ setError(null)
+
+ try {
+ await authApi.login(formData)
+ // Redirect to dashboard or home page
+ window.location.href = '/'
+ } catch (error: unknown) {
+ setError(error instanceof Error ? error.message : 'Login failed')
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ const handleOAuthLogin = async (provider: string) => {
+ setIsOAuthLoading(provider)
+ setError(null)
+
+ try {
+ // Get authorization URL from backend and redirect
+ const authResponse = await oauthApi.getAuthorizationUrl(provider)
+ window.location.href = authResponse.authorization_url
+ } catch (error: unknown) {
+ setError(error instanceof Error ? error.message : `${provider} login failed`)
+ setIsOAuthLoading(null)
+ }
+ }
+
+ const getProviderIcon = (provider: string) => {
+ switch (provider.toLowerCase()) {
+ case 'github':
+ return
+ case 'google':
+ return (
+
+ )
+ default:
+ return null
+ }
+ }
+
+ const getProviderName = (provider: string) => {
+ return provider.charAt(0).toUpperCase() + provider.slice(1)
+ }
+
+ return (
+
+
+
+
+ Sign in
+
+
+ Enter your email and password to access your account
+
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+ {/* OAuth Buttons */}
+ {providers.length > 0 && (
+
+ {providers.map((provider) => (
+
+ ))}
+
+
+
+
+
+
+
+ Or continue with email
+
+
+
+
+ )}
+
+ {/* Email/Password Form */}
+
+
+
+ Don't have an account?{' '}
+
+ Sign up
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/pages/auth/RegisterPage.tsx b/src/pages/auth/RegisterPage.tsx
new file mode 100644
index 0000000..9d5c803
--- /dev/null
+++ b/src/pages/auth/RegisterPage.tsx
@@ -0,0 +1,160 @@
+import { useState } from 'react'
+import { Link } from 'react-router'
+import { Button } from '@/components/ui/button'
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
+import { Input } from '@/components/ui/input'
+import { Label } from '@/components/ui/label'
+import { authApi, type RegisterRequest } from '@/lib/api'
+
+export function RegisterPage() {
+ const [formData, setFormData] = useState({
+ email: '',
+ password: '',
+ name: '',
+ })
+ const [confirmPassword, setConfirmPassword] = useState('')
+ const [isLoading, setIsLoading] = useState(false)
+ const [error, setError] = useState(null)
+
+ const handleInputChange = (e: React.ChangeEvent) => {
+ const { name, value } = e.target
+ if (name === 'confirmPassword') {
+ setConfirmPassword(value)
+ } else {
+ setFormData(prev => ({
+ ...prev,
+ [name]: value,
+ }))
+ }
+ }
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault()
+ setIsLoading(true)
+ setError(null)
+
+ if (formData.password !== confirmPassword) {
+ setError('Passwords do not match')
+ setIsLoading(false)
+ return
+ }
+
+ if (formData.password.length < 8) {
+ setError('Password must be at least 8 characters long')
+ setIsLoading(false)
+ return
+ }
+
+ try {
+ await authApi.register(formData)
+ // Redirect to dashboard or home page
+ window.location.href = '/'
+ } catch (error: unknown) {
+ setError(error instanceof Error ? error.message : 'Registration failed')
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ return (
+
+
+
+
+ Create account
+
+
+ Enter your information to create a new account
+
+
+
+
+
+
+ Already have an account?{' '}
+
+ Sign in
+
+
+
+
+
+ )
+}
\ No newline at end of file