diff --git a/src/app/admin/embed/page.tsx b/src/app/admin/embed/page.tsx
new file mode 100644
index 0000000..0b531e5
--- /dev/null
+++ b/src/app/admin/embed/page.tsx
@@ -0,0 +1,73 @@
+
+'use client';
+
+import React, { useState, useEffect } from 'react';
+import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { Textarea } from '@/components/ui/textarea';
+import { useToast } from '@/hooks/use-toast';
+import { Copy } from 'lucide-react';
+
+export default function EmbedPage() {
+ const [embedCode, setEmbedCode] = useState('');
+ const [origin, setOrigin] = useState('');
+ const { toast } = useToast();
+
+ useEffect(() => {
+ if (typeof window !== 'undefined') {
+ setOrigin(window.location.origin);
+ }
+ }, []);
+
+ useEffect(() => {
+ if (origin) {
+ const code = ``;
+ setEmbedCode(code);
+ }
+ }, [origin]);
+
+ const handleCopy = () => {
+ navigator.clipboard.writeText(embedCode);
+ toast({
+ title: 'Copied!',
+ description: 'The embed code has been copied to your clipboard.',
+ });
+ };
+
+ return (
+
+
+
+
Embed
+
+ Copy the code below to embed the estimator on your website.
+
+
+
+
+
+ Embed Code
+
+ Paste this HTML code into your website's source where you want the estimator to appear.
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/admin/layout.tsx b/src/app/admin/layout.tsx
new file mode 100644
index 0000000..814b129
--- /dev/null
+++ b/src/app/admin/layout.tsx
@@ -0,0 +1,103 @@
+
+"use client"
+
+import * as React from "react";
+import Link from 'next/link';
+import {
+ Sidebar,
+ SidebarHeader,
+ SidebarMenu,
+ SidebarMenuItem,
+ SidebarMenuButton,
+ SidebarFooter,
+ SidebarContent,
+ SidebarInset,
+ SidebarProvider,
+ SidebarTrigger,
+ useSidebar,
+} from "@/components/ui/sidebar"
+import {
+ Home,
+ User,
+ Settings,
+ LogOut,
+ Code2
+} from "lucide-react"
+import { Button } from "@/components/ui/button";
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
+
+function AdminLayout({
+ children,
+}: {
+ children: React.ReactNode
+}) {
+ return (
+
+
+
+
+
+
EstimateFlow
+
+
+
+
+
+
+
+ Dashboard
+
+
+
+
+
+
+
+ Embed
+
+
+
+
+
+
+
+ My Account
+
+
+
+
+
+
+
+ Settings
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+ )
+}
+
+export default AdminLayout;
diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx
new file mode 100644
index 0000000..b2db8b6
--- /dev/null
+++ b/src/app/admin/page.tsx
@@ -0,0 +1,53 @@
+
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
+import { DollarSign, Users, Activity } from 'lucide-react';
+
+export default function AdminDashboard() {
+ return (
+
+
Dashboard
+
+
+
+
+ Total Revenue
+
+
+
+
+ $45,231.89
+
+ +20.1% from last month
+
+
+
+
+
+
+ Subscriptions
+
+
+
+
+ +2350
+
+ +180.1% from last month
+
+
+
+
+
+ Active Now
+
+
+
+ +573
+
+ +201 since last hour
+
+
+
+
+
+ );
+}
diff --git a/src/app/admin/settings/general/page.tsx b/src/app/admin/settings/general/page.tsx
new file mode 100644
index 0000000..8b57159
--- /dev/null
+++ b/src/app/admin/settings/general/page.tsx
@@ -0,0 +1,109 @@
+
+'use client';
+
+import { useState, useEffect } from 'react';
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { useToast } from '@/hooks/use-toast';
+import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
+
+// Mock server actions
+async function getHourlyRate(): Promise {
+ console.log('Fetching hourly rate...');
+ const response = await fetch('/api/settings/hourly_rate');
+ if (!response.ok) {
+ throw new Error('Failed to fetch hourly rate');
+ }
+ const data = await response.json();
+ return data.value;
+}
+
+async function updateHourlyRate(rate: string): Promise<{ success: boolean; message: string }> {
+ console.log(`Updating hourly rate to: ${rate}`);
+ const response = await fetch('/api/settings/hourly_rate', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ value: rate }),
+ });
+ if (!response.ok) {
+ const errorData = await response.json();
+ return { success: false, message: errorData.message || 'Failed to update hourly rate' };
+ }
+ return { success: true, message: 'Hourly rate updated successfully' };
+}
+
+
+export default function GeneralSettingsPage() {
+ const [hourlyRate, setHourlyRate] = useState('');
+ const [isLoading, setIsLoading] = useState(true);
+ const { toast } = useToast();
+
+ useEffect(() => {
+ async function fetchRate() {
+ try {
+ const rate = await getHourlyRate();
+ setHourlyRate(rate);
+ } catch (error) {
+ console.error(error);
+ toast({
+ variant: 'destructive',
+ title: 'Error',
+ description: 'Could not load hourly rate.',
+ });
+ } finally {
+ setIsLoading(false);
+ }
+ }
+ fetchRate();
+ }, [toast]);
+
+ const handleSave = async () => {
+ try {
+ const result = await updateHourlyRate(hourlyRate);
+ if (result.success) {
+ toast({
+ title: 'Success',
+ description: result.message,
+ });
+ } else {
+ throw new Error(result.message);
+ }
+ } catch (error: any) {
+ toast({
+ variant: 'destructive',
+ title: 'Error',
+ description: error.message || 'Failed to save settings.',
+ });
+ }
+ };
+
+ return (
+
+
+
General Settings
+
Manage your application settings.
+
+
+
+ Hourly Rate
+ Set the hourly rate used in project estimations.
+
+
+
+
+ setHourlyRate(e.target.value)}
+ placeholder="Enter hourly rate"
+ disabled={isLoading}
+ />
+
+
+
+
+
+ );
+}
diff --git a/src/app/admin/settings/user/page.tsx b/src/app/admin/settings/user/page.tsx
new file mode 100644
index 0000000..052b47e
--- /dev/null
+++ b/src/app/admin/settings/user/page.tsx
@@ -0,0 +1,180 @@
+
+'use client';
+
+import { useEffect } from 'react';
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { z } from 'zod';
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { useToast } from '@/hooks/use-toast';
+import {
+ Card,
+ CardContent,
+ CardHeader,
+ CardTitle,
+ CardDescription,
+} from '@/components/ui/card';
+import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
+
+const formSchema = z.object({
+ name: z.string().min(1, 'Name is required'),
+ email: z.string().email('Invalid email address'),
+ password: z.string().optional(),
+});
+
+type UserFormValues = z.infer;
+
+// Mock server actions
+async function getUser() {
+ console.log("Mocking getUser");
+ return { id: '1', name: 'Admin User', email: 'admin@example.com' };
+}
+
+async function updateUser(data: UserFormValues) {
+ console.log("Mocking updateUser with data:", data);
+ // Simulate API call
+ return new Promise<{ success: boolean; message: string }>((resolve) => {
+ setTimeout(() => {
+ if (data.email.includes('fail')) {
+ resolve({ success: false, message: 'This email is already taken.' });
+ } else {
+ resolve({ success: true, message: 'Profile updated successfully!' });
+ }
+ }, 1000);
+ });
+}
+
+
+export default function UserProfilePage() {
+ const { toast } = useToast();
+ const form = useForm({
+ resolver: zodResolver(formSchema),
+ defaultValues: {
+ name: '',
+ email: '',
+ password: '',
+ },
+ });
+
+ useEffect(() => {
+ async function loadUserProfile() {
+ try {
+ const user = await getUser();
+ if (user) {
+ form.reset({
+ name: user.name,
+ email: user.email,
+ password: '',
+ });
+ }
+ } catch (error) {
+ toast({
+ variant: 'destructive',
+ title: 'Error',
+ description: 'Could not load user profile.',
+ });
+ }
+ }
+ loadUserProfile();
+ }, [form, toast]);
+
+ const onSubmit = async (data: UserFormValues) => {
+ try {
+ const result = await updateUser(data);
+ if (result.success) {
+ toast({
+ title: 'Success',
+ description: result.message,
+ });
+ form.reset({ ...data, password: '' });
+ } else {
+ toast({
+ variant: 'destructive',
+ title: 'Update failed',
+ description: result.message,
+ });
+ }
+ } catch (error) {
+ toast({
+ variant: 'destructive',
+ title: 'Error',
+ description: 'An unexpected error occurred.',
+ });
+ }
+ };
+
+ return (
+
+
+
My Account
+
Update your profile information.
+
+
+
+
+ );
+}
diff --git a/src/app/api/settings/hourly_rate/route.ts b/src/app/api/settings/hourly_rate/route.ts
new file mode 100644
index 0000000..ac8d4a1
--- /dev/null
+++ b/src/app/api/settings/hourly_rate/route.ts
@@ -0,0 +1,38 @@
+
+import { NextRequest, NextResponse } from 'next/server';
+import db from '@/lib/db';
+
+export async function GET(req: NextRequest) {
+ try {
+ const stmt = db.prepare('SELECT value FROM settings WHERE key = ?');
+ const setting = stmt.get('hourly_rate') as { value: string } | undefined;
+
+ if (!setting) {
+ // If not set, return a default or handle as an error
+ return NextResponse.json({ value: '100' });
+ }
+
+ return NextResponse.json({ value: setting.value });
+ } catch (error) {
+ console.error('Failed to get hourly rate:', error);
+ return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
+ }
+}
+
+export async function POST(req: NextRequest) {
+ try {
+ const { value } = await req.json();
+
+ if (typeof value !== 'string' || isNaN(parseFloat(value))) {
+ return NextResponse.json({ message: 'Invalid hourly rate value' }, { status: 400 });
+ }
+
+ const stmt = db.prepare('INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)');
+ stmt.run('hourly_rate', value);
+
+ return NextResponse.json({ message: 'Hourly rate updated successfully' });
+ } catch (error) {
+ console.error('Failed to update hourly rate:', error);
+ return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
+ }
+}
diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx
new file mode 100644
index 0000000..718351a
--- /dev/null
+++ b/src/app/login/page.tsx
@@ -0,0 +1,113 @@
+
+'use client';
+
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import * as z from 'zod';
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { useToast } from '@/hooks/use-toast';
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card';
+import { useRouter } from 'next/navigation';
+
+const loginSchema = z.object({
+ email: z.string().email({ message: 'Invalid email address.' }),
+ password: z.string().min(1, { message: 'Password is required.' }),
+});
+
+type LoginFormValues = z.infer;
+
+export default function LoginPage() {
+ const router = useRouter();
+ const { toast } = useToast();
+ const {
+ register,
+ handleSubmit,
+ formState: { errors, isSubmitting },
+ } = useForm({
+ resolver: zodResolver(loginSchema),
+ });
+
+ const onSubmit = async (data: LoginFormValues) => {
+ try {
+ // This is a mock login. In a real app, you'd call an authentication service.
+ console.log('Attempting to log in with:', data.email);
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ if (data.email === 'admin@example.com' && data.password === 'password') {
+ toast({
+ title: 'Login Successful',
+ description: 'Redirecting to your dashboard...',
+ });
+ router.push('/admin');
+ } else {
+ throw new Error('Invalid email or password.');
+ }
+ } catch (error: any) {
+ toast({
+ variant: 'destructive',
+ title: 'Login Failed',
+ description: error.message || 'An unexpected error occurred.',
+ });
+ }
+ };
+
+ return (
+
+
+
+
+ EstimateFlow Admin
+
+
+ Welcome back! Please sign in to continue.
+
+
+
+
+
+
+ Use admin@example.com and 'password' to sign in.
+
+
+
+ );
+}