diff --git a/.gitignore b/.gitignore index dc150eb5..cea7dc9b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /node_modules .env +.github/copilot-instructions.md +/dist \ No newline at end of file diff --git a/index.html b/index.html index 5c1f6b62..f69fdfb1 100644 --- a/index.html +++ b/index.html @@ -4,27 +4,29 @@ - GoBuild | Hire Construction labour in Jammu & Delhi - - + GoBuild | Hire Construction labour in Jammu & Delhi + + + - + - + - + + diff --git a/package-lock.json b/package-lock.json index 2ff7da86..bb551077 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,19 +47,19 @@ "framer-motion": "^12.23.24", "i18next": "^25.0.1", "i18next-browser-languagedetector": "^8.0.5", - "input-otp": "^1.2.4", "lucide-react": "^0.462.0", "next-themes": "^0.3.0", "react": "^18.3.1", "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", + "react-helmet-async": "^1.3.0", "react-hook-form": "^7.53.0", "react-i18next": "^15.4.1", - "react-icons": "^5.5.0", "react-resizable-panels": "^2.1.3", "react-router-dom": "^6.26.2", + "react-zoom-pan-pinch": "^3.7.0", "recharts": "^2.12.7", - "sonner": "^1.5.0", + "sonner": "^2.0.7", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", "twilio": "^5.5.2", @@ -6045,16 +6045,6 @@ "node": ">=0.8.19" } }, - "node_modules/input-otp": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.2.4.tgz", - "integrity": "sha512-md6rhmD+zmMnUh5crQNSQxq3keBRYvE3odbr4Qb9g2NWzQv9azi+t1a3X4TBTbh98fsGHgEEJlzbe1q860uGCA==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - } - }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -8067,6 +8057,29 @@ "react": "^18.3.1" } }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT" + }, + "node_modules/react-helmet-async": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-1.3.0.tgz", + "integrity": "sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.12.5", + "invariant": "^2.2.4", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.2.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": "^16.6.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.6.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-hook-form": { "version": "7.53.1", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.53.1.tgz", @@ -8105,15 +8118,6 @@ } } }, - "node_modules/react-icons": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", - "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", - "license": "MIT", - "peerDependencies": { - "react": "*" - } - }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -8263,6 +8267,20 @@ "react-dom": ">=16.6.0" } }, + "node_modules/react-zoom-pan-pinch": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-3.7.0.tgz", + "integrity": "sha512-UmReVZ0TxlKzxSbYiAj+LeGRW8s8LraAFTXRAxzMYnNRgGPsxCudwZKVkjvGmjtx7SW/hZamt69NUmGf4xrkXA==", + "license": "MIT", + "engines": { + "node": ">=8", + "npm": ">=5" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -8623,6 +8641,12 @@ "node": ">= 0.4" } }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -8729,13 +8753,13 @@ } }, "node_modules/sonner": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.5.0.tgz", - "integrity": "sha512-FBjhG/gnnbN6FY0jaNnqZOMmB73R+5IiyYAw8yBj7L54ER7HB3fOSE5OFiQiE2iXWxeXKvg6fIP4LtVppHEdJA==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", "license": "MIT", "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "node_modules/source-map-js": { diff --git a/package.json b/package.json index 0f8acfc2..d0b88643 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,9 @@ "build": "vite build", "build:dev": "vite build --mode development", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "postbuild": "node ./scripts/generate-sitemap.js", + "generate-sitemap": "node ./scripts/generate-sitemap.js" }, "dependencies": { "@hookform/resolvers": "^3.9.0", @@ -50,19 +52,19 @@ "framer-motion": "^12.23.24", "i18next": "^25.0.1", "i18next-browser-languagedetector": "^8.0.5", - "input-otp": "^1.2.4", "lucide-react": "^0.462.0", "next-themes": "^0.3.0", "react": "^18.3.1", "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", + "react-helmet-async": "^1.3.0", "react-hook-form": "^7.53.0", "react-i18next": "^15.4.1", - "react-icons": "^5.5.0", "react-resizable-panels": "^2.1.3", "react-router-dom": "^6.26.2", + "react-zoom-pan-pinch": "^3.7.0", "recharts": "^2.12.7", - "sonner": "^1.5.0", + "sonner": "^2.0.7", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", "twilio": "^5.5.2", diff --git a/public/AppBadge(blue).png b/public/AppBadge(blue).png new file mode 100644 index 00000000..197fed35 Binary files /dev/null and b/public/AppBadge(blue).png differ diff --git a/public/AppBadge(green).png b/public/AppBadge(green).png new file mode 100644 index 00000000..7ce472a5 Binary files /dev/null and b/public/AppBadge(green).png differ diff --git a/public/AppLogo.png b/public/AppLogo.png new file mode 100644 index 00000000..947ab4e4 Binary files /dev/null and b/public/AppLogo.png differ diff --git a/public/AppScanner.jpg b/public/AppScanner.jpg new file mode 100644 index 00000000..048ef726 Binary files /dev/null and b/public/AppScanner.jpg differ diff --git a/public/AppView.png b/public/AppView.png new file mode 100644 index 00000000..054241b2 Binary files /dev/null and b/public/AppView.png differ diff --git a/public/Logo.png b/public/Logo.png new file mode 100644 index 00000000..ad5e5bbf Binary files /dev/null and b/public/Logo.png differ diff --git a/public/google-logo.svg b/public/google-logo.svg new file mode 100644 index 00000000..d6b2366a --- /dev/null +++ b/public/google-logo.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/robots.txt b/public/robots.txt index 6018e701..58faf696 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -12,3 +12,5 @@ Allow: / User-agent: * Allow: / + +Sitemap: https://www.gobuild.in/sitemap.xml diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 100644 index 00000000..8262e769 --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1,69 @@ + + + + https://www.gobuild.in/ + 2025-11-23T08:24:40.779Z + weekly + 0.7 + + + https://www.gobuild.in/services + 2025-11-23T08:24:40.779Z + weekly + 0.7 + + + https://www.gobuild.in/blog + 2025-11-23T08:24:40.779Z + weekly + 0.7 + + + https://www.gobuild.in/about + 2025-11-23T08:24:40.779Z + weekly + 0.7 + + + https://www.gobuild.in/contact + 2025-11-23T08:24:40.779Z + weekly + 0.7 + + + https://www.gobuild.in/profile + 2025-11-23T08:24:40.779Z + weekly + 0.7 + + + https://www.gobuild.in/pricing + 2025-11-23T08:24:40.779Z + weekly + 0.7 + + + https://www.gobuild.in/policy + 2025-11-23T08:24:40.779Z + weekly + 0.7 + + + https://www.gobuild.in/terms + 2025-11-23T08:24:40.779Z + weekly + 0.7 + + + https://www.gobuild.in/refund-policy + 2025-11-23T08:24:40.779Z + weekly + 0.7 + + + https://www.gobuild.in/categories + 2025-11-23T08:24:40.779Z + weekly + 0.7 + + \ No newline at end of file diff --git a/scripts/generate-sitemap.js b/scripts/generate-sitemap.js new file mode 100644 index 00000000..93f0b7d5 --- /dev/null +++ b/scripts/generate-sitemap.js @@ -0,0 +1,43 @@ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +// List of canonical routes to include in sitemap +const baseUrl = 'https://www.gobuild.in'; +const routes = [ + '/', + '/services', + '/blog', + '/about', + '/contact', + '/profile', + '/pricing', + '/policy', + '/terms', + '/refund-policy', + '/categories', +]; + +function buildSitemap() { + const now = new Date().toISOString(); + const urlset = routes + .map((route) => { + return ` \n ${baseUrl}${route}\n ${now}\n weekly\n 0.7\n `; + }) + .join('\n'); + + const xml = `\n\n${urlset}\n`; + + const __dirname = path.dirname(fileURLToPath(import.meta.url)); + const outDir = path.join(__dirname, '..', 'public'); + if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true }); + const outPath = path.join(outDir, 'sitemap.xml'); + fs.writeFileSync(outPath, xml, 'utf8'); + console.log('Sitemap written to', outPath); +} + +if (process.argv[1] && process.argv[1].endsWith('generate-sitemap.js')) { + buildSitemap(); +} + +export { buildSitemap }; diff --git a/src/App.tsx b/src/App.tsx index e7ee60e8..ffcc4960 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,15 @@ import { Toaster } from "@/components/ui/toaster"; import { Toaster as Sonner } from "@/components/ui/sonner"; import { TooltipProvider } from "@/components/ui/tooltip"; +import { HelmetProvider } from "react-helmet-async"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { BrowserRouter, Routes, Route } from "react-router-dom"; import { AuthProvider, useAuth } from "./contexts/AuthContext"; import { LanguageProvider } from "./contexts/LanguageContext"; +import ContractorDetail from "./pages/categories/ContractorDetail"; + + + import Index from "./pages/Index"; import NotFound from "./pages/NotFound"; import AuthLayout from "./pages/auth/AuthLayout"; @@ -17,6 +22,7 @@ import ContactUs from "./pages/ContactUs"; import ScrollToTop from "./components/ScrollToTop"; import Profile from "./pages/Profile"; import ForgotPassword from "./pages/auth/Forgot"; +import ResetPassword from "./pages/auth/ResetPassword"; import Workers from "./pages/categories/Workers"; import Suppliers from "./pages/categories/Suppliers"; import HouseOwner from "./pages/categories/HouseOwner"; @@ -29,8 +35,17 @@ import ProtectedRoute from "./components/ProtectedRoute"; import ArchitectDashboard from "./pages/ArchitectDashboard.tsx"; import MaterialSupplierDetail from "./pages/categories/MaterialSupplierDetail"; import SupplierDashboard from "./pages/SupplierDashboard"; +import Policy from "./pages/Policy.tsx"; +import TermsAndConditions from "./pages/TermsAndConditions"; +import RefundPolicy from "./pages/RefundPolicy"; +import Pricing from "@/pages/Pricing"; +import UpdatePassword from "./pages/auth/UpdatePassword.tsx"; +import MaterialDetails from "./pages/categories/MaterialDetails.tsx"; + + // Import i18n configuration import './i18n'; +import DeveloperDetail from "./pages/categories/DeveloperDetail.tsx"; const queryClient = new QueryClient(); @@ -57,13 +72,29 @@ const AppRoutes = () => { } /> } /> } /> + } /> + } +/> + + + + } /> }> } /> } /> } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} } /> @@ -72,18 +103,20 @@ const AppRoutes = () => { const App = () => ( - - - - - - - - - - - - + + + + + + + + + + + + + + ); diff --git a/src/components/ApplyAsProfessionalForm.tsx b/src/components/ApplyAsProfessionalForm.tsx index bfcd5758..7827e586 100644 --- a/src/components/ApplyAsProfessionalForm.tsx +++ b/src/components/ApplyAsProfessionalForm.tsx @@ -9,6 +9,9 @@ import { Input } from "@/components/ui/input"; import { useToast } from "@/hooks/use-toast"; import { supabase } from "@/integrations/supabase/client"; import { Textarea } from "@/components/ui/textarea"; +import { MapPin } from "lucide-react"; + + const formSchema = z.object({ Name: z.string().min(2, { message: "Name must be at least 2 characters." }), @@ -24,7 +27,9 @@ type FormValues = z.infer; export function ApplyAsProfessionalForm() { const { toast } = useToast(); - + const [baseLat, setBaseLat] = React.useState(null); + const [baseLng, setBaseLng] = React.useState(null); + const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { @@ -34,11 +39,22 @@ export function ApplyAsProfessionalForm() { MobileNo: undefined, Area: "", Skill: "", - Description:"", + Description: "", }, }); const onSubmit = async (data: FormValues) => { + + if (!baseLat || !baseLng) { + toast({ + title: "Location required", + description: "Please click 'Use Current Location'", + variant: "destructive", + }); + return; + } + + try { // Make sure MobileNo is present before submitting if (!data.MobileNo) { @@ -52,10 +68,12 @@ export function ApplyAsProfessionalForm() { // Insert a single object, not an array const { error } = await supabase - .from('GoBuild') - .insert(data); - - if (error) throw error; + .from("GoBuild") + .insert({ + ...data, + base_lat: baseLat, + base_lng: baseLng, + }); toast({ title: "Application Submitted", @@ -72,6 +90,33 @@ export function ApplyAsProfessionalForm() { } }; + const handleUseCurrentLocation = () => { + if (!navigator.geolocation) { + toast({ + title: "Error", + description: "Geolocation not supported", + variant: "destructive", + }); + return; + } + + navigator.geolocation.getCurrentPosition( + (pos) => { + setBaseLat(pos.coords.latitude); + setBaseLng(pos.coords.longitude); + }, + (err) => { + toast({ + title: "Error", + description: err.message, + variant: "destructive", + }); + }, + { enableHighAccuracy: true } + ); + }; + + return (
@@ -97,10 +142,10 @@ export function ApplyAsProfessionalForm() { Age - field.onChange(e.target.value ? parseInt(e.target.value) : undefined)} /> @@ -116,9 +161,9 @@ export function ApplyAsProfessionalForm() { Years of Experience - field.onChange(e.target.value ? parseInt(e.target.value) : undefined)} /> @@ -136,9 +181,9 @@ export function ApplyAsProfessionalForm() { Mobile Number - field.onChange(e.target.value ? parseInt(e.target.value) : undefined)} required @@ -149,19 +194,65 @@ export function ApplyAsProfessionalForm() { )} /> - ( - - Area/Location - - - - - - )} - /> + {/* Location */} +{/* Area / Location */} +{/* Area / Location */} +
+ + +
+ {/* ✅ Blinking Pin */} + + + + + + {/* Current Location Button */} + + + {/* Enter Location Input */} + ( + + + + + + + )} + /> +
+ + {baseLat && baseLng && ( +

+ Location saved ✔ +

+ )} +
+ + + Submit + + + ); } diff --git a/src/components/ArchitectForm.tsx b/src/components/ArchitectForm.tsx new file mode 100644 index 00000000..12c7f663 --- /dev/null +++ b/src/components/ArchitectForm.tsx @@ -0,0 +1,233 @@ +import React, { useState } from "react"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import * as z from "zod"; +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { useToast } from "@/hooks/use-toast"; +import { supabase } from "@/integrations/supabase/client"; +import { XCircle } from "lucide-react"; + +// Validation schema +const formSchema = z.object({ + client_name: z.string().min(2, { message: "Name must be at least 2 characters." }), + phoneNumber: z + .string() + .regex(/^[0-9]{10}$/, { message: "Phone number must be exactly 10 digits." }), + location: z.string().min(2, { message: "Enter a valid location." }), + budget: z + .number({ + required_error: "Please enter your estimated budget.", + }) + .min(0, { message: "Budget must be greater than 0." }), + message: z.string().optional(), +}); + +type FormValues = z.infer; + +export function ArchitectForm() { + const { toast } = useToast(); + const [showPopup, setShowPopup] = useState(false); + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + client_name: "", + phoneNumber: "", + location: "", + budget: undefined, + message: "", + }, + }); + + const onSubmit = async (data: FormValues) => { + try { + const { error } = await supabase.from("ArchitectRequest").insert({ + arcID: null, + client_name: data.client_name, + phoneNumber: data.phoneNumber, + location: data.location, + budget: data.budget, + message: data.message, + status: "Pending", + }); + + if (error) throw error; + + toast({ + title: "Application Submitted 🎉", + description: "Your request has been sent successfully!", + }); + + setShowPopup(true); // Show popup + form.reset(); + } catch (error: any) { + toast({ + title: "Error", + description: error.message, + variant: "destructive", + }); + } + }; + + return ( +
+
+

+ Book Arcthitect Services +

+ +
+ +
+ {/* Full Name */} + ( + + Full Name + + + + + + )} + /> + + {/* Phone Number */} + ( + + Phone Number + + + + + + )} + /> +
+ + {/* Location */} + ( + + Location + + + + + + )} + /> + + {/* Budget */} + ( + + Estimated Budget (₹) + + + field.onChange( + e.target.value ? parseInt(e.target.value) : undefined + ) + } + /> + + + + )} + /> + + {/* Message */} + ( + + Message / Description + + + setDate(e.target.value)} /> + + +
+
+ )} + + {/* Gallery */} +
+

Work Gallery

+ + {photos.length === 0 ? ( +

No photos uploaded yet.

+ ) : ( +
+ {photos.map((url, i) => ( + { + setCurrentIndex(i); + setViewerOpen(true); + }} + /> + + ))} +
+ )} +
+ +