Implement shadcn/ui + Tailwind CSS UI layer
- Design token system via CSS custom properties (light/dark mode) - Theme context hook + ThemeToggle component - AppShell + collapsible Sidebar replace inline Nav - LoginPage redesigned: two-column grid with hero panel - shadcn/ui Button and Input components - Tailwind config wired to CSS variable tokens - All pages de-Nav'd; PrivateRoute/AdminRoute wrap with AppShell - TypeScript passes clean (npm run typecheck) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||
import Nav from "../components/Nav";
|
||||
import { getAISettings, updateAISettings, testAIConnection } from "../api/client";
|
||||
|
||||
type Provider = "anthropic" | "ollama" | "lmstudio";
|
||||
@@ -96,17 +95,11 @@ export default function AIAdminSettingsPage() {
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<>
|
||||
<Nav />
|
||||
<div style={{ padding: 32 }}>Loading…</div>
|
||||
</>
|
||||
);
|
||||
return <div style={{ padding: 32 }}>Loading…</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Nav />
|
||||
<div style={{ padding: 32, maxWidth: 600, margin: "0 auto" }}>
|
||||
<h1 style={{ fontSize: 24, marginBottom: 32 }}>AI Service — Settings</h1>
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
type AdminUserCreate,
|
||||
type UserData,
|
||||
} from "../api/client";
|
||||
import Nav from "../components/Nav";
|
||||
|
||||
export default function AdminPage() {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -68,7 +67,6 @@ export default function AdminPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Nav />
|
||||
<div style={{ padding: 32, maxWidth: 800 }}>
|
||||
<h1>User Management</h1>
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import Nav from "../components/Nav";
|
||||
import { getMe } from "../api/client";
|
||||
|
||||
interface AppCard {
|
||||
@@ -36,7 +35,6 @@ export default function AppsPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Nav />
|
||||
<div style={{ padding: 32, maxWidth: 900, margin: "0 auto" }}>
|
||||
<h1>Apps</h1>
|
||||
<div style={{ display: "flex", gap: 24, flexWrap: "wrap", marginTop: 24 }}>
|
||||
@@ -102,3 +100,4 @@ export default function AppsPage() {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getMe } from "../api/client";
|
||||
import Nav from "../components/Nav";
|
||||
|
||||
export default function DashboardPage() {
|
||||
const { data: user } = useQuery({ queryKey: ["me"], queryFn: getMe });
|
||||
|
||||
return (
|
||||
<>
|
||||
<Nav />
|
||||
<div style={{ padding: 32 }}>
|
||||
<h1>Dashboard</h1>
|
||||
{user && <p>Welcome, {user.full_name ?? user.email}</p>}
|
||||
</div>
|
||||
</>
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold text-foreground mb-2">Dashboard</h1>
|
||||
{user && <p className="text-muted">Welcome, {user.full_name ?? user.email}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||
import Nav from "../components/Nav";
|
||||
import { getDocumentLimits, updateDocumentLimits } from "../api/client";
|
||||
|
||||
const inputStyle: React.CSSProperties = {
|
||||
@@ -34,17 +33,11 @@ export default function DocumentAdminSettingsPage() {
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<>
|
||||
<Nav />
|
||||
<div style={{ padding: 32 }}>Loading…</div>
|
||||
</>
|
||||
);
|
||||
return <div style={{ padding: 32 }}>Loading…</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Nav />
|
||||
<div style={{ padding: 32, maxWidth: 600, margin: "0 auto" }}>
|
||||
<h1 style={{ fontSize: 24, marginBottom: 32 }}>Documents — Settings</h1>
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useRef, useState, useEffect, useCallback } from "react";
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import Nav from "../components/Nav";
|
||||
import {
|
||||
listDocuments,
|
||||
uploadDocument,
|
||||
@@ -707,7 +706,6 @@ export default function DocumentsPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Nav />
|
||||
<div style={{ padding: 32, maxWidth: 960, margin: "0 auto" }}>
|
||||
<h1>Documents</h1>
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import axios from "axios";
|
||||
import { useState } from "react";
|
||||
import { Briefcase, LayoutDashboard } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import ThemeToggle from "@/components/ThemeToggle";
|
||||
import { useAuth } from "../hooks/useAuth";
|
||||
|
||||
// ── Customise these two constants for each deployment ────────────────────────
|
||||
// ── Customise for each deployment ────────────────────────────────────────────
|
||||
const BUSINESS_NAME = "Your Business Name";
|
||||
// Replace the src with the actual logo URL or import, or swap the placeholder
|
||||
// div below for an <img> tag once a logo is available.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
export default function LoginPage() {
|
||||
@@ -30,62 +32,59 @@ export default function LoginPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
minHeight: "100vh",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}>
|
||||
<div style={{ width: 360, padding: 32 }}>
|
||||
|
||||
{/* Logo placeholder — swap for <img src="..." alt="Logo" /> */}
|
||||
<div style={{
|
||||
width: 96,
|
||||
height: 96,
|
||||
margin: "0 auto 24px",
|
||||
border: "2px dashed #aaa",
|
||||
borderRadius: 8,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
color: "#aaa",
|
||||
fontSize: 12,
|
||||
}}>
|
||||
Logo
|
||||
<div className="grid md:grid-cols-2 min-h-screen">
|
||||
{/* ── Left column: login form ── */}
|
||||
<div className="relative flex flex-col items-center justify-center p-8 bg-surface">
|
||||
{/* Theme toggle — top-right of this panel */}
|
||||
<div className="absolute top-4 right-4">
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
|
||||
<h1 style={{ textAlign: "center", marginBottom: 24 }}>{BUSINESS_NAME}</h1>
|
||||
<div className="w-full max-w-sm space-y-6">
|
||||
{/* Logo placeholder */}
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<div className="flex h-16 w-16 items-center justify-center rounded-lg bg-primary/10">
|
||||
<Briefcase className="h-8 w-8 text-primary" />
|
||||
</div>
|
||||
<h1 className="text-xl font-semibold text-primary">{BUSINESS_NAME}</h1>
|
||||
<p className="text-sm text-muted">Sign in to your account</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<label style={{ display: "block", marginBottom: 4 }}>Email</label>
|
||||
<input
|
||||
{/* Form */}
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="you@company.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
style={{ width: "100%", padding: "6px 8px", boxSizing: "border-box" }}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<label style={{ display: "block", marginBottom: 4 }}>Password</label>
|
||||
<input
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="••••••••"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
style={{ width: "100%", padding: "6px 8px", boxSizing: "border-box" }}
|
||||
/>
|
||||
</div>
|
||||
{error && <p style={{ color: "red", margin: "8px 0" }}>{error}</p>}
|
||||
<button
|
||||
type="submit"
|
||||
style={{ width: "100%", padding: "8px 0", marginTop: 8, cursor: "pointer" }}
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
</form>
|
||||
{error && (
|
||||
<p className="text-sm text-red-500">{error}</p>
|
||||
)}
|
||||
<Button type="submit" className="w-full">
|
||||
Sign In
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── Right column: hero panel ── */}
|
||||
<div className="hidden md:flex flex-col items-center justify-center bg-primary p-8 gap-4">
|
||||
<LayoutDashboard className="h-20 w-20 text-white/80" />
|
||||
<h2 className="text-2xl font-bold text-white text-center">
|
||||
Manage your team, your way
|
||||
</h2>
|
||||
<p className="text-sm text-white/70 text-center">
|
||||
The all-in-one employer platform.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { getMe, getProfile, updateProfile, type ProfileUpdate } from "../api/client";
|
||||
import Nav from "../components/Nav";
|
||||
|
||||
export default function ProfilePage() {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -54,11 +53,10 @@ export default function ProfilePage() {
|
||||
mutation.mutate(payload);
|
||||
};
|
||||
|
||||
if (isLoading) return <><Nav /><div style={{ padding: 32 }}>Loading…</div></>;
|
||||
if (isLoading) return <div style={{ padding: 32 }}>Loading…</div>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Nav />
|
||||
<div style={{ padding: 32, maxWidth: 480 }}>
|
||||
<h1>Profile</h1>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user