Project Code Backup

This file contains the full content of all files in your project. You can use this to manually restore your project.

.env

apphosting.yaml

# Settings to manage and configure a Firebase App Hosting backend.
# https://firebase.google.com/docs/app-hosting/configure

runConfig:
  # Increase this value if you'd like to automatically spin up
  # more instances in response to increased traffic.
  maxInstances: 1
        
components.json

{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "default",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "config": "tailwind.config.ts",
    "css": "src/app/globals.css",
    "baseColor": "neutral",
    "cssVariables": true,
    "prefix": ""
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui",
    "lib": "@/lib",
    "hooks": "@/hooks"
  },
  "iconLibrary": "lucide"
}
        
next.config.ts

import type {NextConfig} from 'next';

const nextConfig: NextConfig = {
  /* config options here */
  typescript: {
    ignoreBuildErrors: true,
  },
  eslint: {
    ignoreDuringBuilds: true,
  },
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'placehold.co',
        port: '',
        pathname: '/**',
      },
      {
        protocol: 'https',
        hostname: 'picsum.photos',
        port: '',
        pathname: '/**',
      }
    ],
  },
  
  allowedDevOrigins: ["*.cloudworkstations.dev"],
};

export default nextConfig;
        
package.json

{
  "name": "nextn",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev --turbopack",
    "genkit:dev": "genkit start -- tsx src/ai/dev.ts",
    "genkit:watch": "genkit start -- tsx --watch src/ai/dev.ts",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "@genkit-ai/googleai": "^1.16.0",
    "@genkit-ai/next": "1.16.0",
    "@hookform/resolvers": "^4.1.3",
    "@radix-ui/react-accordion": "^1.2.3",
    "@radix-ui/react-alert-dialog": "^1.1.6",
    "@radix-ui/react-aspect-ratio": "^1.1.0",
    "@radix-ui/react-avatar": "^1.1.3",
    "@radix-ui/react-checkbox": "^1.1.4",
    "@radix-ui/react-collapsible": "^1.1.11",
    "@radix-ui/react-dialog": "^1.1.6",
    "@radix-ui/react-dropdown-menu": "^2.1.6",
    "@radix-ui/react-label": "^2.1.2",
    "@radix-ui/react-menubar": "^1.1.6",
    "@radix-ui/react-popover": "^1.1.6",
    "@radix-ui/react-progress": "^1.1.2",
    "@radix-ui/react-radio-group": "^1.2.3",
    "@radix-ui/react-scroll-area": "^1.2.3",
    "@radix-ui/react-select": "^2.1.6",
    "@radix-ui/react-separator": "^1.1.2",
    "@radix-ui/react-slider": "^1.2.3",
    "@radix-ui/react-slot": "^1.2.3",
    "@radix-ui/react-switch": "^1.1.3",
    "@radix-ui/react-tabs": "^1.1.3",
    "@radix-ui/react-toast": "^1.2.6",
    "@radix-ui/react-tooltip": "^1.1.8",
    "@stripe/react-stripe-js": "^2.7.3",
    "@stripe/stripe-js": "^4.1.0",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "date-fns": "^3.6.0",
    "dotenv": "^16.4.5",
    "embla-carousel-react": "^8.1.7",
    "firebase": "^10.12.3",
    "firebase-admin": "^12.2.0",
    "framer-motion": "^11.3.8",
    "genkit": "^1.16.0",
    "lodash": "^4.17.21",
    "lucide-react": "^0.475.0",
    "next": "15.3.3",
    "papaparse": "^5.4.1",
    "patch-package": "^8.0.0",
    "react": "^18.3.1",
    "react-bootstrap-icons": "^1.11.4",
    "react-day-picker": "^8.10.1",
    "react-dom": "^18.3.1",
    "react-hook-form": "^7.54.2",
    "recharts": "^2.15.1",
    "stripe": "^16.2.0",
    "tailwind-merge": "^3.0.1",
    "tailwindcss-animate": "^1.0.7",
    "zod": "^3.24.2"
  },
  "devDependencies": {
    "@types/lodash": "^4.17.7",
    "@types/node": "^20",
    "@types/papaparse": "^5.3.14",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "genkit-cli": "^1.16.0",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "typescript": "^5"
  }
}
        
src/.env

src/ai/flows/project-scope-flow.ts

'use server';
/**
 * @fileOverview An AI flow to generate a list of potential project scopes
 * and questions based on a selected room or area in a house.
 */

import {ai} from '@/ai/genkit';
import {
  projectScopeInputSchema,
  projectScopeOutputSchema,
  type ProjectScopeInput,
  type ProjectScopeOutput,
} from './schemas';

const projectScopePrompt = ai.definePrompt({
  name: 'projectScopePrompt',
  input: {schema: projectScopeInputSchema},
  output: {schema: projectScopeOutputSchema},
  prompt: `You are an expert home renovation project manager. A user has selected a room in their house they want to work on.

Your task is to generate a list of 5-7 common project areas or scopes of work for the specified room: {{{room}}}.

For each scope, provide:
1.  A unique, simple, kebab-case ID (e.g., "flooring-installation").
2.  A clear title for the work (e.g., "Flooring Installation").
3.  A specific question to ask the user to get more details about that item.

Return the list in the 'scopes' array.`,
});

const generateProjectScopeFlow = ai.defineFlow(
  {
    name: 'generateProjectScopeFlow',
    inputSchema: projectScopeInputSchema,
    outputSchema: projectScopeOutputSchema,
  },
  async (input) => {
    const {output} = await projectScopePrompt(input);
    return output!;
  }
);

export async function generateProjectScope(
  input: ProjectScopeInput
): Promise {
  return await generateProjectScopeFlow(input);
}
        
src/ai/flows/schemas.ts

/**
 * @fileOverview Zod schemas and TypeScript types for AI flows.
 */
import {z} from 'zod';

// Schemas for project-scope-flow.ts
export const projectScopeInputSchema = z.object({
  room: z
    .string()
    .describe(
      'The room or area selected by the user (e.g., "Kitchen", "Bathroom").'
    ),
});
export type ProjectScopeInput = z.infer;

const ScopeItemSchema = z.object({
  id: z
    .string()
    .describe(
      'A unique slug-like ID for the scope item (e.g., "cabinet-replacement").'
    ),
  title: z
    .string()
    .describe('The title of the project area (e.g., "Cabinet Replacement").'),
  question: z
    .string()
    .describe(
      'A question to ask the user to gather more details (e.g., "What kind of new cabinets are you interested in?").'
    ),
});

export const projectScopeOutputSchema = z.object({
  scopes: z
    .array(ScopeItemSchema)
    .describe(
      'An array of potential project scopes relevant to the selected room.'
    ),
});
export type ProjectScopeOutput = z.infer;
        
src/ai/flows/send-verification-code-flow.ts

'use server';
/**
 * @fileOverview A Genkit flow to send verification codes for sign-up and password reset.
 * This is a mock implementation and does not actually send emails.
 */

import { ai } from '@/ai/genkit';
import { z } from 'zod';

// In-memory store for codes (for demonstration purposes only)
// In a real application, use a secure, persistent store like Firestore with TTL.
const codeStore: Record = {};

const sendCodeInputSchema = z.object({
  email: z.string().email().describe('The email address to send the code to.'),
  type: z.enum(['signup', 'reset']).describe('The purpose of the verification code.'),
});

const sendCodeOutputSchema = z.object({
  success: z.boolean(),
  message: z.string(),
});

const verifyCodeInputSchema = z.object({
  email: z.string().email(),
  code: z.string().length(6),
  type: z.enum(['signup', 'reset']),
});

const verifyCodeOutputSchema = z.boolean();

/**
 * MOCK: Generates a 6-digit code, stores it, and simulates sending an email.
 */
const sendVerificationCodeFlow = ai.defineFlow(
  {
    name: 'sendVerificationCodeFlow',
    inputSchema: sendCodeInputSchema,
    outputSchema: sendCodeOutputSchema,
  },
  async ({ email, type }) => {
    const code = Math.floor(100000 + Math.random() * 900000).toString();
    const expires = Date.now() + 10 * 60 * 1000; // 10 minutes

    // Store the code
    codeStore[email] = { code, expires, type };

    console.log(`
      ================================================
      MOCK EMAIL SENDER
      ================================================
      To: ${email}
      Subject: Your Verification Code
      Body: Your verification code is: ${code}
      Purpose: ${type}
      ================================================
    `);

    return {
      success: true,
      message: 'Verification code sent successfully.',
    };
  }
);

/**
 * MOCK: Verifies a code against the in-memory store.
 */
const verifyCodeFlow = ai.defineFlow(
  {
    name: 'verifyCodeFlow',
    inputSchema: verifyCodeInputSchema,
    outputSchema: verifyCodeOutputSchema,
  },
  async ({ email, code, type }) => {
    const stored = codeStore[email];

    if (!stored) {
      console.error(`Verification failed for ${email}: No code found.`);
      return false;
    }

    if (stored.expires < Date.now()) {
      console.error(`Verification failed for ${email}: Code expired.`);
      delete codeStore[email];
      return false;
    }

    if (stored.code === code && stored.type === type) {
      console.log(`Verification successful for ${email}.`);
      delete codeStore[email]; // One-time use
      return true;
    }

    console.error(`Verification failed for ${email}: Code mismatch.`);
    return false;
  }
);

// Exported wrapper functions for client-side use
export async function sendVerificationCode(input: z.infer) {
    return await sendVerificationCodeFlow(input);
}

export async function verifyCode(input: z.infer): Promise {
    return await verifyCodeFlow(input);
}
        
src/ai/flows/story-flow.ts

'use server';
/**
 * @fileOverview A simple story generation AI flow.
 */

import { ai } from '@/ai/genkit';
import { z } from 'zod';

export const storyInputSchema = z.object({
  prompt: z.string().describe('The user prompt for the story'),
});
export type StoryInput = z.infer;

export const storyOutputSchema = z.object({
  story: z.string().describe('The generated story'),
});
export type StoryOutput = z.infer;

const storyPrompt = ai.definePrompt({
  name: 'storyPrompt',
  input: { schema: storyInputSchema },
  output: { schema: storyOutputSchema },
  prompt: `Write a short story based on the following prompt: {{{prompt}}}`,
});

const storyFlow = ai.defineFlow(
  {
    name: 'storyFlow',
    inputSchema: storyInputSchema,
    outputSchema: storyOutputSchema,
  },
  async (input) => {
    const { output } = await storyPrompt(input);
    return output!;
  }
);

export async function generateStory(input: StoryInput): Promise {
  return await storyFlow(input);
}
        
src/ai/genkit.ts

/**
 * @fileoverview This file initializes the Genkit AI provider.
 */
import {genkit} from 'genkit';
import {googleAI} from '@genkit-ai/googleai';

export const ai = genkit({
  plugins: [
    googleAI(),
  ],
  enableTracingAndMetrics: true,
});
        
src/app/(main)/about/page.tsx

import React from 'react';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import Image from 'next/image';

export default function AboutPage() {
  return (
    
EHG Renovations Team
About EHG Renovations
Your vision, our expertise. Building trust one project at a time.

Founded on the principles of quality craftsmanship, integrity, and unparalleled customer service, EHG Renovations has grown to be a trusted name in the home improvement industry. We believe that every project is an opportunity to create something beautiful and lasting.

Our team of skilled professionals, from designers to builders, is dedicated to bringing your vision to life. We handle every aspect of your project with meticulous attention to detail, ensuring a seamless process from the initial consultation to the final walkthrough.

Whether it's a minor repair or a major renovation, we are committed to delivering results that not only meet but exceed your expectations. Thank you for considering EHG Renovations for your next project. We look forward to building with you.

); }
src/app/(main)/estimate/_components/ClientInfoStep.tsx

'use client';

import { useForm, type SubmitHandler } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useEstimate } from '@/app/layout';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
import { Input } from '@/components/ui/input';

const clientInfoSchema = z.object({
  name: z.string().min(2, 'Name is required'),
  email: z.string().email('Invalid email address'),
  phone: z.string().min(10, 'A valid phone number is required'),
});

type ClientInfoForm = z.infer;

export default function ClientInfoStep() {
  const { setStep, setFormData, formData } = useEstimate();
  const form = useForm({
    resolver: zodResolver(clientInfoSchema),
    defaultValues: formData.clientInfo || {
      name: '',
      email: '',
      phone: '',
    },
  });

  const onSubmit: SubmitHandler = (data) => {
    setFormData({ ...formData, clientInfo: data });
    setStep('project-selection');
  };

  return (
    
      
Step 1: Client Information Let's start with your contact details. ( Full Name )} /> ( Email Address )} /> ( Phone Number )} />
); }
src/app/(main)/estimate/_components/ConfirmationStep.tsx

'use client';

import { useEstimate } from '@/app/layout';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { CheckCircle2 } from 'lucide-react';
import Link from 'next/link';

export default function ConfirmationStep() {
  const { formData } = useEstimate();

  return (
    
      
        
Thank You, {formData.clientInfo?.name}! Your estimate request has been successfully submitted. We will review the details and get back to you within 24-48 hours.

A confirmation has been sent to {formData.clientInfo?.email}.

); }
src/app/(main)/estimate/_components/EstimateTOC.tsx

'use client';

import { useEstimate, type EstimateStep } from '@/app/layout';

const estimateSteps: { step: EstimateStep; label: string }[] = [
  { step: 'client-info', label: 'Client Info' },
  { step: 'project-selection', label: 'Project Selection' },
  { step: 'project-scope', label: 'Project Scope' },
  { step: 'review', label: 'Review & Finalize' },
];

interface EstimateTOCProps {
  onNavigate: (step: EstimateStep) => void;
}

export function EstimateTOC({ onNavigate }: EstimateTOCProps) {
  const { step: currentStep } = useEstimate();

  return (
    <>
      {estimateSteps.map(({ step, label }) => (
        
      ))}
    
  );
}
        
src/app/(main)/estimate/_components/FinalizingStep.tsx

'use client';

import { useEffect } from 'react';
import { useEstimate } from '@/app/layout';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { Progress } from '@/components/ui/progress';

export default function FinalizingStep() {
  const { setStep } = useEstimate();

  useEffect(() => {
    const timer = setTimeout(() => {
      setStep('confirmation');
    }, 3000); // Simulate a 3-second processing time

    return () => clearTimeout(timer);
  }, [setStep]);

  return (
    
      
        Finalizing Your Request
        We're compiling your details and submitting them to our team.
      
      
        

Please wait a moment...

); }
src/app/(main)/estimate/_components/ProjectScopeStep.tsx

'use client';

import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useEstimate } from '@/app/layout';
import { generateProjectScope, type ProjectScopeOutput } from '@/ai/flows/project-scope-flow';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
import { Checkbox } from '@/components/ui/checkbox';
import { Form, FormControl, FormField, FormItem, FormLabel } from '@/components/ui/form';
import { Skeleton } from '@/components/ui/skeleton';
import { Textarea } from '@/components/ui/textarea';
import { useToast } from '@/hooks/use-toast';

type Scope = ProjectScopeOutput['scopes'][0];

export default function ProjectScopeStep() {
  const { setStep, setFormData, formData } = useEstimate();
  const [scopes, setScopes] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const { toast } = useToast();
  const form = useForm<{ scopes: string[]; details: Record }>({
    defaultValues: {
      scopes: formData.projectScope?.scopes || [],
      details: formData.projectScope?.details || {},
    },
  });
  const selectedScopes = form.watch('scopes');

  useEffect(() => {
    const fetchScope = async () => {
      if (!formData.projectSelection?.room) {
        toast({
          variant: 'destructive',
          title: 'Error',
          description: 'Project area not selected. Please go back.',
        });
        setStep('project-selection');
        return;
      }

      setIsLoading(true);
      try {
        const result = await generateProjectScope({ room: formData.projectSelection.room });
        setScopes(result.scopes);
      } catch (error) {
        console.error(error);
        toast({
          variant: 'destructive',
          title: 'AI Error',
          description: 'Could not generate project scopes. Please try again.',
        });
      } finally {
        setIsLoading(false);
      }
    };

    fetchScope();
  }, [formData.projectSelection, setStep, toast]);

  const onSubmit = (data: { scopes: string[]; details: Record }) => {
    setFormData({ ...formData, projectScope: data });
    setStep('review');
  };

  if (isLoading) {
    return (
      
        
          
          
        
        
          {[...Array(5)].map((_, i) => (
            
))}
); } return (
Step 3: Project Scope Select the items that apply to your {formData.projectSelection?.room.toLowerCase()} project. ( {scopes.map((item) => ( { const isSelected = field.value?.includes(item.id); return (
{ return checked ? field.onChange([...field.value, item.id]) : field.onChange(field.value?.filter((value) => value !== item.id)); }} /> {item.title}
{isSelected && ( ( {item.question}