From a7ef8604d180b4e991ce91dd586c5fb9a168d55c Mon Sep 17 00:00:00 2001 From: Louis Knight-Webb Date: Sat, 14 Jun 2025 17:22:55 -0400 Subject: [PATCH] Router and shadcn --- frontend/src/App.tsx | 291 ++-------------------- frontend/src/components/layout/navbar.tsx | 72 ++++++ frontend/src/pages/home.tsx | 242 ++++++++++++++++++ frontend/src/pages/projects.tsx | 23 ++ frontend/src/pages/users.tsx | 188 ++++++++++++++ 5 files changed, 551 insertions(+), 265 deletions(-) create mode 100644 frontend/src/components/layout/navbar.tsx create mode 100644 frontend/src/pages/home.tsx create mode 100644 frontend/src/pages/projects.tsx create mode 100644 frontend/src/pages/users.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ac43588c..5c5ae2db 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,22 +1,16 @@ import { useState, useEffect } from 'react' -import { Button } from '@/components/ui/button' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Alert, AlertDescription } from '@/components/ui/alert' -import { ProjectsPage } from '@/components/projects/projects-page' -import { UsersPage } from '@/components/users/users-page' +import { BrowserRouter, Routes, Route, useLocation } from 'react-router-dom' import { LoginForm } from '@/components/auth/login-form' -import { ApiResponse } from 'shared/types' -import { authStorage, isAuthenticated, logout, makeAuthenticatedRequest } from '@/lib/auth' -import { ArrowLeft, Heart, Activity, FolderOpen, Users, CheckCircle, AlertCircle, LogOut } from 'lucide-react' +import { Navbar } from '@/components/layout/navbar' +import { HomePage } from '@/pages/home' +import { Projects } from '@/pages/projects' +import { Users } from '@/pages/users' +import { isAuthenticated } from '@/lib/auth' -function App() { - const [currentPage, setCurrentPage] = useState<'home' | 'projects' | 'users'>('home') - const [message, setMessage] = useState('') - const [messageType, setMessageType] = useState<'success' | 'error'>('success') - const [loading, setLoading] = useState(false) +function AppContent() { + const location = useLocation() const [authenticated, setAuthenticated] = useState(false) - - const currentUser = authStorage.getUser() + const showNavbar = location.pathname !== '/' || authenticated useEffect(() => { setAuthenticated(isAuthenticated()) @@ -24,270 +18,37 @@ function App() { const handleLogin = () => { setAuthenticated(true) - setCurrentPage('home') } const handleLogout = () => { - logout() setAuthenticated(false) - setCurrentPage('home') - } - - const fetchHello = async () => { - setLoading(true) - try { - const response = await makeAuthenticatedRequest('/api/hello?name=Bloop') - const data = await response.json() - setMessage(data.message) - setMessageType('success') - } catch (error) { - setMessage('Error connecting to backend') - setMessageType('error') - } finally { - setLoading(false) - } - } - - const checkHealth = async () => { - setLoading(true) - try { - const response = await makeAuthenticatedRequest('/api/health') - const data: ApiResponse = await response.json() - setMessage(data.message || 'Health check completed') - setMessageType('success') - } catch (error) { - setMessage('Backend health check failed') - setMessageType('error') - } finally { - setLoading(false) - } } if (!authenticated) { return } - if (currentPage === 'projects' || currentPage === 'users') { - return ( -
-
-
-
-
-

Bloop

-
- - {currentUser?.is_admin && ( - - )} -
-
-
-
- Welcome, {currentUser?.email} -
- - -
-
-
-
-
- {currentPage === 'projects' ? : } -
-
- ) - } - return ( -
-
-
-
-
-
- -
-
-

- Welcome to Bloop -

-

- A modern full-stack monorepo built with Rust backend and React frontend. - Get started by exploring our features below. -

-
- -
- - -
-
- -
- API Test -
- - Test the connection between frontend and backend - -
- - - -
- - - -
-
- -
- Health Check -
- - Monitor the health status of your backend services - -
- - - -
- - - -
-
- -
- Projects -
- - Manage your projects with full CRUD operations - -
- - - -
- - {currentUser?.is_admin && ( - - -
-
- -
- Users -
- - Manage user accounts and permissions - -
- - - -
- )} - - - -
-
- -
- Account -
- - Logged in as {currentUser?.email} - -
- - - -
-
- - {message && ( - - {messageType === 'error' ? ( - - ) : ( - - )} - - {message} - - - )} - -
-

- Built with ❤️ using Rust, React, TypeScript, and Tailwind CSS -

-
-
+
+ {showNavbar && } +
+ + } /> + } /> + } /> + } /> +
) } +function App() { + return ( + + + + ) +} + export default App diff --git a/frontend/src/components/layout/navbar.tsx b/frontend/src/components/layout/navbar.tsx new file mode 100644 index 00000000..f890b855 --- /dev/null +++ b/frontend/src/components/layout/navbar.tsx @@ -0,0 +1,72 @@ +import { Link, useLocation } from 'react-router-dom' +import { Button } from '@/components/ui/button' +import { authStorage, logout } from '@/lib/auth' +import { ArrowLeft, FolderOpen, Users, LogOut } from 'lucide-react' + +interface NavbarProps { + onLogout: () => void +} + +export function Navbar({ onLogout }: NavbarProps) { + const location = useLocation() + const currentUser = authStorage.getUser() + const isHome = location.pathname === '/' + + const handleLogout = () => { + logout() + onLogout() + } + + return ( +
+
+
+
+

Bloop

+
+ + {currentUser?.is_admin && ( + + )} +
+
+
+
+ Welcome, {currentUser?.email} +
+ {!isHome && ( + + )} + +
+
+
+
+ ) +} diff --git a/frontend/src/pages/home.tsx b/frontend/src/pages/home.tsx new file mode 100644 index 00000000..e31f7f42 --- /dev/null +++ b/frontend/src/pages/home.tsx @@ -0,0 +1,242 @@ +import { useState } from 'react' +import { Link } from 'react-router-dom' +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Alert, AlertDescription } from '@/components/ui/alert' +import { Badge } from '@/components/ui/badge' +import { Separator } from '@/components/ui/separator' +import { ApiResponse } from 'shared/types' +import { authStorage, makeAuthenticatedRequest } from '@/lib/auth' +import { Heart, Activity, FolderOpen, Users, CheckCircle, AlertCircle, Zap, Shield, Code } from 'lucide-react' + +export function HomePage() { + const [message, setMessage] = useState('') + const [messageType, setMessageType] = useState<'success' | 'error'>('success') + const [loading, setLoading] = useState(false) + + const currentUser = authStorage.getUser() + + const fetchHello = async () => { + setLoading(true) + try { + const response = await makeAuthenticatedRequest('/api/hello?name=Bloop') + const data = await response.json() + setMessage(data.message) + setMessageType('success') + } catch (error) { + setMessage('Error connecting to backend') + setMessageType('error') + } finally { + setLoading(false) + } + } + + const checkHealth = async () => { + setLoading(true) + try { + const response = await makeAuthenticatedRequest('/api/health') + const data: ApiResponse = await response.json() + setMessage(data.message || 'Health check completed') + setMessageType('success') + } catch (error) { + setMessage('Backend health check failed') + setMessageType('error') + } finally { + setLoading(false) + } + } + + return ( +
+
+
+ {/* Hero Section */} +
+
+
+
+
+ +
+
+
+ + + Mission Control Dashboard + +

+ Welcome to Bloop +

+

+ A modern full-stack monorepo built with Rust backend and React frontend. + Get started by exploring our features below. +

+
+ + + Secure + + + + Type-Safe + + + + Real-time + +
+
+ + {/* Feature Cards */} +
+ + +
+
+
+ +
+ API Test +
+ Test +
+ + Test the connection between frontend and backend + +
+ + + +
+ + + +
+
+
+ +
+ Health Check +
+ Monitor +
+ + Monitor the health status of your backend services + +
+ + + +
+ + + +
+
+
+ +
+ Projects +
+ CRUD +
+ + Manage your projects with full CRUD operations + +
+ + + +
+ + {currentUser?.is_admin && ( + + +
+
+
+ +
+
+ + Users + + + Admin Only + + + + Manage user accounts and permissions + +
+
+
+
+ + + +
+ )} +
+ + {/* Status Alert */} + {message && ( +
+ + {messageType === 'error' ? ( + + ) : ( + + )} + + {message} + + +
+ )} + + {/* Footer */} + +
+

+ Built with ❤️ using modern technologies +

+
+ Rust + React + TypeScript + Tailwind CSS +
+
+
+
+
+ ) +} diff --git a/frontend/src/pages/projects.tsx b/frontend/src/pages/projects.tsx new file mode 100644 index 00000000..6fcd408a --- /dev/null +++ b/frontend/src/pages/projects.tsx @@ -0,0 +1,23 @@ +import { useParams, useNavigate } from 'react-router-dom' +import { ProjectList } from '@/components/projects/project-list' +import { ProjectDetail } from '@/components/projects/project-detail' + +export function Projects() { + const { projectId } = useParams<{ projectId: string }>() + const navigate = useNavigate() + + const handleBack = () => { + navigate('/projects') + } + + if (projectId) { + return ( + + ) + } + + return +} diff --git a/frontend/src/pages/users.tsx b/frontend/src/pages/users.tsx new file mode 100644 index 00000000..f626a4dc --- /dev/null +++ b/frontend/src/pages/users.tsx @@ -0,0 +1,188 @@ +import { useState, useEffect } from 'react' +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Badge } from '@/components/ui/badge' +import { Alert, AlertDescription } from '@/components/ui/alert' +import { User, ApiResponse } from 'shared/types' +import { UserForm } from '@/components/users/user-form' +import { makeAuthenticatedRequest, authStorage } from '@/lib/auth' +import { Plus, Edit, Trash2, Calendar, AlertCircle, Loader2, Shield, User as UserIcon } from 'lucide-react' + +export function Users() { + const [users, setUsers] = useState([]) + const [loading, setLoading] = useState(false) + const [showForm, setShowForm] = useState(false) + const [editingUser, setEditingUser] = useState(null) + const [error, setError] = useState('') + const currentUser = authStorage.getUser() + + const fetchUsers = async () => { + setLoading(true) + setError('') + try { + const response = await makeAuthenticatedRequest('/api/users') + const data: ApiResponse = await response.json() + if (data.success && data.data) { + setUsers(data.data) + } else { + setError('Failed to load users') + } + } catch (error) { + console.error('Failed to fetch users:', error) + setError('Failed to connect to server') + } finally { + setLoading(false) + } + } + + const handleDelete = async (id: string, email: string) => { + if (!confirm(`Are you sure you want to delete user "${email}"? This action cannot be undone.`)) return + + try { + const response = await makeAuthenticatedRequest(`/api/users/${id}`, { + method: 'DELETE', + }) + if (response.ok) { + fetchUsers() + } else if (response.status === 403) { + setError('You cannot delete this user') + } else { + setError('Failed to delete user') + } + } catch (error) { + console.error('Failed to delete user:', error) + setError('Failed to delete user') + } + } + + const handleEdit = (user: User) => { + setEditingUser(user) + setShowForm(true) + } + + const handleFormSuccess = () => { + setShowForm(false) + setEditingUser(null) + fetchUsers() + } + + useEffect(() => { + fetchUsers() + }, []) + + return ( +
+
+
+

Users

+

+ Manage user accounts and permissions +

+
+ {currentUser?.is_admin && ( + + )} +
+ + {error && ( + + + + {error} + + + )} + + {loading ? ( +
+ + Loading users... +
+ ) : users.length === 0 ? ( + + +
+ +
+

No users found

+

+ Get started by creating the first user account. +

+ {currentUser?.is_admin && ( + + )} +
+
+ ) : ( +
+ {users.map((user) => ( + + +
+ + {user.is_admin ? ( + + ) : ( + + )} + {user.email} + + + {user.is_admin ? "Admin" : "User"} + +
+ + + Joined {new Date(user.created_at).toLocaleDateString()} + +
+ +
+ + {currentUser?.is_admin && currentUser.id !== user.id && ( + + )} +
+
+
+ ))} +
+ )} + + { + setShowForm(false) + setEditingUser(null) + }} + onSuccess={handleFormSuccess} + user={editingUser} + /> +
+ ) +}