diff --git a/middleware.ts b/middleware.ts
deleted file mode 100644
index bb437d8..0000000
--- a/middleware.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-
-import { auth } from '@/app/api/auth/[...nextauth]/route';
-import { NextResponse } from 'next/server';
-import type { NextRequest } from 'next/server';
-
-export async function middleware(request: NextRequest) {
- const session = await auth();
- const { pathname } = request.nextUrl;
-
- const isAuthPage = pathname === '/login';
-
- if (isAuthPage) {
- if (session) {
- return NextResponse.redirect(new URL('/admin', request.url));
- }
- return null;
- }
-
- if (!session && pathname.startsWith('/admin')) {
- const signInUrl = new URL('/login', request.url);
- signInUrl.searchParams.set('callbackUrl', pathname);
- return NextResponse.redirect(signInUrl);
- }
-
- return NextResponse.next();
-}
-
-export const config = {
- matcher: ['/admin/:path*', '/login'],
-};
diff --git a/scripts/seed.ts b/scripts/seed.ts
index 39eb0b0..83b452c 100644
--- a/scripts/seed.ts
+++ b/scripts/seed.ts
@@ -5,16 +5,6 @@ const db = new Database('local.db');
function seed() {
console.log('Seeding database...');
- // Create users table if it doesn't exist
- db.exec(`
- CREATE TABLE IF NOT EXISTS users (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- email TEXT UNIQUE NOT NULL,
- password TEXT NOT NULL,
- name TEXT NOT NULL
- )
- `);
-
// Create settings table if it doesn't exist
db.exec(`
CREATE TABLE IF NOT EXISTS settings (
@@ -23,22 +13,6 @@ function seed() {
)
`);
- // Check if the admin user already exists
- const userStmt = db.prepare('SELECT * FROM users WHERE email = ?');
- const adminUser = userStmt.get('admin@example.com');
-
- if (!adminUser) {
- // Insert the default admin user
- // In a real application, you should hash the password!
- const insertUser = db.prepare(
- "INSERT INTO users (email, password, name) VALUES (?, ?, ?)"
- );
- insertUser.run("admin@example.com", "password", "Admin User");
- console.log('Admin user created.');
- } else {
- console.log('Admin user already exists.');
- }
-
// Check if the hourly_rate setting already exists
const settingStmt = db.prepare('SELECT * FROM settings WHERE key = ?');
const hourlyRateSetting = settingStmt.get('hourly_rate');
diff --git a/src/app/admin/embed/page.tsx b/src/app/admin/embed/page.tsx
deleted file mode 100644
index b9afa76..0000000
--- a/src/app/admin/embed/page.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-
-'use client';
-
-import { useEffect, useState } from 'react';
-import {
- Card,
- CardContent,
- CardDescription,
- CardHeader,
- CardTitle,
- CardFooter,
-} from '@/components/ui/card';
-import { Label } from '@/components/ui/label';
-import { Textarea } from '@/components/ui/textarea';
-import { Button } from '@/components/ui/button';
-import { Clipboard } from 'lucide-react';
-import { useToast } from '@/hooks/use-toast';
-
-export default function EmbedPage() {
- const [embedCode, setEmbedCode] = useState('');
- const [origin, setOrigin] = useState('');
- const { toast } = useToast();
-
- useEffect(() => {
- // This ensures window is defined, as it's only available on the client
- if (typeof window !== 'undefined') {
- const currentOrigin = window.location.origin;
- setOrigin(currentOrigin);
- setEmbedCode(
- ``
- );
- }
- }, []);
-
- const handleCopy = () => {
- navigator.clipboard.writeText(embedCode).then(
- () => {
- toast({
- title: 'Copied to Clipboard',
- description: 'The embed code has been copied successfully.',
- });
- },
- (err) => {
- toast({
- title: 'Failed to Copy',
- description: 'Could not copy the code. Please try again.',
- variant: 'destructive',
- });
- console.error('Could not copy text: ', err);
- }
- );
- };
-
- return (
-
-
- Embed EstimateFlow
-
- Copy and paste the code below into your website's HTML to embed the
- cost estimator flow.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/app/admin/layout.tsx b/src/app/admin/layout.tsx
deleted file mode 100644
index 311fbc6..0000000
--- a/src/app/admin/layout.tsx
+++ /dev/null
@@ -1,149 +0,0 @@
-
-import * as React from 'react';
-import Link from 'next/link';
-import {
- Sidebar,
- SidebarContent,
- SidebarHeader,
- SidebarMenu,
- SidebarMenuItem,
- SidebarMenuButton,
- SidebarMenuSub,
- SidebarMenuSubButton,
- SidebarMenuSubItem,
- SidebarProvider,
- SidebarInset,
- SidebarTrigger,
- SidebarFooter,
-} from '@/components/ui/sidebar';
-import { LayoutDashboard, LogOut, Settings, Mail, User as UserIcon, Code2 } from 'lucide-react';
-import { signOut } from '@/lib/auth';
-import { Button } from '@/components/ui/button';
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from '@/components/ui/dropdown-menu';
-import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
-import { auth } from '@/app/api/auth/[...nextauth]/route';
-
-export default async function AdminLayout({
- children,
-}: {
- children: React.ReactNode;
-}) {
- const session = await auth();
- const user = session?.user;
-
- const userInitials = user?.name
- ? user.name
- .split(' ')
- .map((n) => n[0])
- .join('')
- : '';
-
- return (
-
-
-
-
-
-
-
-
-
-
-
- Dashboard
-
-
-
-
-
-
-
- Embed
-
-
-
-
-
-
- Settings
-
-
-
-
-
- User
-
-
-
-
-
- Email
-
-
-
-
-
- Hourly Rate
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Dashboard
-
-
-
-
-
-
- {user?.name}
-
-
- Settings
-
-
-
-
-
-
- {children}
-
-
- );
-}
diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx
deleted file mode 100644
index d465028..0000000
--- a/src/app/admin/page.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-
-import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
-import { DollarSign, Users, Activity, Clock } from "lucide-react";
-
-export default function AdminDashboard() {
- return (
-
-
-
- Total Revenue
-
-
-
- $45,231.89
- +20.1% from last month
-
-
-
-
- New Users
-
-
-
- +2350
- +180.1% from last month
-
-
-
-
- Active Projects
-
-
-
- +573
- +20 since last hour
-
-
-
-
- Average Estimate
-
-
-
- 128 hours
- Based on recent activity
-
-
-
- );
-}
diff --git a/src/app/admin/settings/email/page.tsx b/src/app/admin/settings/email/page.tsx
deleted file mode 100644
index 0305987..0000000
--- a/src/app/admin/settings/email/page.tsx
+++ /dev/null
@@ -1,172 +0,0 @@
-
-"use client";
-
-import { useForm, type SubmitHandler } from "react-hook-form";
-import { zodResolver } from "@hookform/resolvers/zod";
-import { z } from "zod";
-import { Button } from "@/components/ui/button";
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card";
-import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
-import { useToast } from "@/hooks/use-toast";
-import { useState, useTransition } from "react";
-import { sendTestEmail } from "@/lib/actions/email";
-import { Mail } from "lucide-react";
-
-const emailSettingsSchema = z.object({
- smtpHost: z.string().min(1, "SMTP Host is required"),
- smtpPort: z.coerce.number().min(1, "Port is required"),
- smtpUsername: z.string().min(1, "Username is required"),
- smtpPassword: z.string().min(1, "Password is required"),
- fromName: z.string().min(1, "From Name is required"),
- fromEmail: z.string().email("Invalid email address"),
-});
-
-type EmailSettingsFormValues = z.infer;
-
-export default function EmailSettingsPage() {
- const { toast } = useToast();
- const [isSaving, startSavingTransition] = useTransition();
- const [isTesting, setIsTesting] = useState(false);
-
- const {
- register,
- handleSubmit,
- getValues,
- formState: { errors },
- } = useForm({
- resolver: zodResolver(emailSettingsSchema),
- defaultValues: {
- // In a real application, you would fetch these values from a secure backend
- smtpHost: "smtp.example.com",
- smtpPort: 587,
- smtpUsername: "user@example.com",
- fromName: "EstimateFlow",
- fromEmail: "noreply@estimateflow.com",
- }
- });
-
- const onSubmit: SubmitHandler = async (data) => {
- startSavingTransition(async () => {
- // Here you would typically send the data to your backend to save it
- console.log("Saving email settings:", data);
-
- // Simulate an API call
- await new Promise(resolve => setTimeout(resolve, 1000));
-
- toast({
- title: "Settings Saved",
- description: "Your SMTP email settings have been updated successfully.",
- });
- });
- };
-
- const handleTestEmail = async () => {
- setIsTesting(true);
- try {
- const settings = getValues();
- const result = await sendTestEmail(settings);
- if (result.success) {
- toast({
- title: "Test Email Sent",
- description: "The test email was sent successfully.",
- });
- } else {
- toast({
- title: "Failed to Send Email",
- description: result.error || "An unknown error occurred.",
- variant: "destructive",
- });
- }
- } catch (error) {
- toast({
- title: "Error",
- description: "An unexpected error occurred while sending the test email.",
- variant: "destructive",
- });
- } finally {
- setIsTesting(false);
- }
- };
-
- return (
-
-
-
- );
-}
diff --git a/src/app/admin/settings/hourly-rate/page.tsx b/src/app/admin/settings/hourly-rate/page.tsx
deleted file mode 100644
index 26c9770..0000000
--- a/src/app/admin/settings/hourly-rate/page.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-"use client";
-
-import { useEffect, useState, useTransition } from 'react';
-import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
-import { Label } from "@/components/ui/label";
-import { Input } from "@/components/ui/input";
-import { Button } from "@/components/ui/button";
-import { useToast } from "@/hooks/use-toast";
-import { getSetting, setSetting } from '@/lib/actions/settings';
-
-const HourlyRateSettingsPage = () => {
- const { toast } = useToast();
- const [hourlyRate, setHourlyRate] = useState('');
- const [isPending, startTransition] = useTransition();
-
- useEffect(() => {
- async function fetchHourlyRate() {
- const rate = await getSetting('hourly_rate');
- if (rate) {
- setHourlyRate(rate);
- }
- }
- fetchHourlyRate();
- }, []);
-
- const handleSubmit = (event: React.FormEvent) => {
- event.preventDefault();
- startTransition(async () => {
- try {
- await setSetting('hourly_rate', hourlyRate);
- toast({ title: "Hourly Rate Saved", description: "The hourly rate has been updated successfully." });
- } catch (error) {
- console.error("Failed to save hourly rate:", error);
- toast({ title: "Error", description: "Failed to save hourly rate.", variant: "destructive" });
- }
- });
- };
-
- return (
-
-
- Hourly Rate Settings
-
- Set the default hourly rate used for project estimations.
-
-
-
-
-
-
- );
-};
-
-export default HourlyRateSettingsPage;
diff --git a/src/app/admin/settings/user/page.tsx b/src/app/admin/settings/user/page.tsx
deleted file mode 100644
index 54a04a3..0000000
--- a/src/app/admin/settings/user/page.tsx
+++ /dev/null
@@ -1,156 +0,0 @@
-
-"use client";
-
-import { useForm, type SubmitHandler } from "react-hook-form";
-import { zodResolver } from "@hookform/resolvers/zod";
-import { z } from "zod";
-import { Button } from "@/components/ui/button";
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card";
-import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
-import { useToast } from "@/hooks/use-toast";
-import { useEffect, useState, useTransition } from "react";
-import { getUser, updateUser } from "@/lib/actions/user";
-import { Skeleton } from "@/components/ui/skeleton";
-
-const userProfileSchema = z.object({
- name: z.string().min(1, "Name is required"),
- email: z.string().email("Invalid email address"),
- password: z.string().min(6, "Password must be at least 6 characters").optional().or(z.literal('')),
- confirmPassword: z.string().optional(),
-}).refine((data) => data.password === data.confirmPassword, {
- message: "Passwords don't match",
- path: ["confirmPassword"],
-});
-
-type UserProfileFormValues = z.infer;
-
-export default function UserProfilePage() {
- const { toast } = useToast();
- const [isSaving, startSavingTransition] = useTransition();
- const [isLoading, setIsLoading] = useState(true);
-
- const {
- register,
- handleSubmit,
- reset,
- formState: { errors, isDirty },
- } = useForm({
- resolver: zodResolver(userProfileSchema),
- defaultValues: {
- name: "",
- email: "",
- password: "",
- confirmPassword: ""
- }
- });
-
- useEffect(() => {
- const fetchUser = async () => {
- setIsLoading(true);
- const user = await getUser();
- if (user) {
- reset({
- name: user.name,
- email: user.email,
- password: "",
- confirmPassword: ""
- });
- } else {
- toast({
- title: "Error",
- description: "Could not load user profile.",
- variant: "destructive",
- });
- }
- setIsLoading(false);
- };
- fetchUser();
- }, [reset, toast]);
-
- const onSubmit: SubmitHandler = (data) => {
- startSavingTransition(async () => {
- const result = await updateUser({
- name: data.name,
- email: data.email,
- password: data.password || undefined,
- });
-
- if (result.success) {
- toast({
- title: "Profile Updated",
- description: "Your profile has been updated successfully.",
- });
- reset({ ...data, password: '', confirmPassword: '' });
- } else {
- toast({
- title: "Update Failed",
- description: result.error || "An unknown error occurred.",
- variant: "destructive",
- });
- }
- });
- };
-
- return (
-
-
-
- );
-}
diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts
deleted file mode 100644
index 3909143..0000000
--- a/src/app/api/auth/[...nextauth]/route.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-
-import NextAuth from 'next-auth';
-import CredentialsProvider from 'next-auth/providers/credentials';
-import db from '@/lib/db';
-import type { User } from '@/lib/types';
-
-export const {
- handlers: { GET, POST },
- auth,
- signIn,
- signOut,
-} = NextAuth({
- secret: process.env.AUTH_SECRET,
- providers: [
- CredentialsProvider({
- name: 'Credentials',
- type: 'credentials',
- credentials: {
- email: { label: 'Email', type: 'email' },
- password: { label: 'Password', type: 'password' },
- },
- async authorize(credentials) {
- if (!credentials?.email || !credentials.password) {
- return null;
- }
-
- const email = credentials.email as string;
- const password = credentials.password as string;
-
- try {
- const stmt = db.prepare('SELECT * FROM users WHERE email = ?');
- const user = stmt.get(email) as User | undefined;
-
- if (user && user.password === password) {
- return {
- id: user.id.toString(),
- name: user.name,
- email: user.email,
- };
- } else {
- return null;
- }
- } catch (error) {
- console.error('Database error during authorization:', error);
- return null;
- }
- },
- }),
- ],
- callbacks: {
- jwt({ token, user }) {
- if (user) {
- token.id = user.id;
- }
- return token;
- },
- session({ session, token }) {
- if (session.user) {
- session.user.id = token.id as string;
- }
- return session;
- },
- },
- pages: {
- signIn: '/login',
- },
-});
diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx
deleted file mode 100644
index 6033d55..0000000
--- a/src/app/login/page.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-
-import { LogIn } from 'lucide-react';
-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 { signIn } from '@/app/api/auth/[...nextauth]/route';
-
-async function handleSignIn(formData: FormData) {
- 'use server';
- await signIn('credentials', formData);
-}
-
-export default async function LoginPage() {
- return (
-
-
-
- Admin Login
- Enter your credentials to access the dashboard.
-
-
-
-
-
-
- );
-}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index e0b8bcb..d340584 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,4 +1,6 @@
import { CostEstimatorForm } from '@/components/cost-estimator/cost-estimator-form';
+import { suggestFeatures } from '@/ai/flows/suggest-features';
+import { classifyComplexity } from '@/ai/flows/classify-complexity';
export default function Home() {
return (
diff --git a/src/lib/actions/email.ts b/src/lib/actions/email.ts
deleted file mode 100644
index 1e6e913..0000000
--- a/src/lib/actions/email.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-
-'use server';
-
-import nodemailer from 'nodemailer';
-import { z } from 'zod';
-
-const emailSettingsSchema = z.object({
- smtpHost: z.string(),
- smtpPort: z.number(),
- smtpUsername: z.string(),
- smtpPassword: z.string(),
- fromName: z.string(),
- fromEmail: z.string().email(),
-});
-
-type EmailSettings = z.infer;
-
-export async function sendTestEmail(
- settings: EmailSettings
-): Promise<{ success: boolean; error?: string }> {
- const { smtpHost, smtpPort, smtpUsername, smtpPassword, fromName, fromEmail } = settings;
-
- try {
- const transporter = nodemailer.createTransport({
- host: smtpHost,
- port: smtpPort,
- secure: smtpPort === 465, // true for 465, false for other ports
- auth: {
- user: smtpUsername,
- pass: smtpPassword,
- },
- // In a real app, you might want more robust TLS options
- // For example, rejecting unauthorized connections
- tls: {
- rejectUnauthorized: false
- }
- });
-
- await transporter.verify();
-
- const mailOptions = {
- from: `"${fromName}" <${fromEmail}>`,
- to: fromEmail, // Send the test to the 'from' address
- subject: 'SMTP Test Email from EstimateFlow',
- text: 'This is a test email to verify your SMTP settings. If you received this, your configuration is correct.',
- html: 'This is a test email to verify your SMTP settings. If you received this, your configuration is correct.
',
- };
-
- await transporter.sendMail(mailOptions);
-
- return { success: true };
- } catch (error: any) {
- console.error('Failed to send test email:', error);
- // Provide a more user-friendly error message
- let errorMessage = 'An unknown error occurred.';
- if (error.code === 'ECONNREFUSED') {
- errorMessage = `Connection refused. Check if the SMTP host (${smtpHost}) and port (${smtpPort}) are correct and accessible.`;
- } else if (error.code === 'EAUTH') {
- errorMessage = 'Authentication failed. Please check your SMTP username and password.';
- } else if (error.message) {
- errorMessage = error.message;
- }
- return { success: false, error: errorMessage };
- }
-}
diff --git a/src/lib/actions/user.ts b/src/lib/actions/user.ts
deleted file mode 100644
index 4067ead..0000000
--- a/src/lib/actions/user.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-
-'use server';
-
-import db from '@/lib/db';
-import { z } from 'zod';
-import { auth } from '@/app/api/auth/[...nextauth]/route';
-import { User as DbUser } from '@/lib/types';
-
-const UserUpdateSchema = z.object({
- name: z.string().min(1, 'Name is required'),
- email: z.string().email('Invalid email address'),
- password: z.string().optional(),
-});
-
-type UserForClient = {
- id: string;
- name: string;
- email: string;
-}
-
-export async function getUser(): Promise {
- const session = await auth();
- if (!session?.user?.id) {
- console.error('getUser: Not authenticated');
- return null;
- }
- try {
- const stmt = db.prepare('SELECT id, name, email FROM users WHERE id = ?');
- const user = stmt.get(session.user.id) as DbUser | undefined;
- if (!user) {
- console.error('getUser: User not found in DB');
- return null;
- }
- return { id: user.id.toString(), name: user.name, email: user.email };
- } catch (error) {
- console.error('Failed to get user:', error);
- return null;
- }
-}
-
-export async function updateUser(
- data: z.infer
-): Promise<{ success: boolean; error?: string }> {
- const session = await auth();
- if (!session?.user?.id) {
- return { success: false, error: 'Not authenticated. Please log in again.' };
- }
-
- const validated = UserUpdateSchema.safeParse(data);
- if (!validated.success) {
- const errors = validated.error.flatten().fieldErrors;
- const firstError = Object.values(errors)[0]?.[0] ?? 'Invalid data provided.';
- return { success: false, error: firstError };
- }
-
- const { name, email, password } = validated.data;
- const userId = session.user.id;
-
- try {
- // Check if the new email is already in use by another user
- const existingUserStmt = db.prepare('SELECT id FROM users WHERE email = ? AND id != ?');
- const existingUser = existingUserStmt.get(email, userId);
-
- if (existingUser) {
- return { success: false, error: 'Email already in use by another account.' };
- }
-
- // Determine if a new password was provided
- if (password && password.trim().length > 0) {
- // If a new password is provided, update name, email, and password
- const stmt = db.prepare(
- 'UPDATE users SET name = ?, email = ?, password = ? WHERE id = ?'
- );
- stmt.run(name, email, password, userId);
- } else {
- // If no new password, update only name and email
- const stmt = db.prepare('UPDATE users SET name = ?, email = ? WHERE id = ?');
- stmt.run(name, email, userId);
- }
- return { success: true };
- } catch (error: any) {
- console.error('Failed to update user:', error);
- return { success: false, error: 'Failed to update user profile due to a server error.' };
- }
-}
diff --git a/src/lib/auth.ts b/src/lib/auth.ts
deleted file mode 100644
index c6ad43d..0000000
--- a/src/lib/auth.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-
-'use server';
-import { signOut as nextAuthSignOut, auth as nextAuth } from '@/app/api/auth/[...nextauth]/route';
-
-export async function signOut() {
- await nextAuthSignOut({ redirectTo: '/login' });
-}
-
-export async function auth() {
- return nextAuth();
-}
diff --git a/src/lib/types.ts b/src/lib/types.ts
index 76de8b8..5e68c11 100644
--- a/src/lib/types.ts
+++ b/src/lib/types.ts
@@ -5,16 +5,3 @@ export interface User {
password?: string; // Should be handled securely, not sent to client
name: string;
}
-
-// Augment the default NextAuth session and user types
-declare module 'next-auth' {
- interface Session {
- user: {
- id: string;
- } & DefaultSession['user'];
- }
-
- interface User {
- id: string;
- }
-}