diff --git a/.gitignore b/.gitignore index 00bba9b..e19370c 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts +firebaseAdmin.json diff --git a/.vscode/settings.json b/.vscode/settings.json index 45b94f3..ceb83a9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,13 @@ { "cSpell.words": [ + "admindb", + "autosize", + "Bookingaction", "chatpage", "Deafultchatpage", "firstvisitpopup", "geocode", + "googlemaps", "Horizonal", "Jsons", "Loca", diff --git a/app/api/gemini/route.ts b/app/api/gemini/route.ts index bc97806..44c844f 100644 --- a/app/api/gemini/route.ts +++ b/app/api/gemini/route.ts @@ -9,7 +9,7 @@ export async function POST(req: NextRequest) { if (!userMessage || !latitude || !longitude) { return new Response( - JSON.stringify({ error: "Missing required fields: userMessage, latitude, or longitude" }), + JSON.stringify({ error: "----- Missing required fields: userMessage, latitude, or longitude -----" }), { status: 400 } ); } @@ -46,7 +46,7 @@ export async function POST(req: NextRequest) { await writeChunk({ type: 'text', data: chunkText }); } } catch (error) { - console.error("Server Error:", error); + console.error(" ---- Server Error:", error); await writeChunk({ type: 'error', data: "An error occurred while processing your request." }); } finally { writer.close(); diff --git a/app/chat/booking/page.tsx b/app/chat/booking/page.tsx new file mode 100644 index 0000000..71657d1 --- /dev/null +++ b/app/chat/booking/page.tsx @@ -0,0 +1,12 @@ +import { BookingForm } from "@/components/bookingForm"; +import React from "react"; + +const Booking = () => { + return ( +
+ +
+ ); +}; + +export default Booking; diff --git a/app/chat/layout.tsx b/app/chat/layout.tsx new file mode 100644 index 0000000..f5d7a12 --- /dev/null +++ b/app/chat/layout.tsx @@ -0,0 +1,38 @@ +import { Nav } from "@/components/nav"; +import Sidebar from "@/components/sidebar"; +import type { Metadata } from "next"; +import { MantineProvider } from "@mantine/core"; +import { Suspense } from "react"; +import { SkeletonCard } from "@/components/loading"; + +export const metadata: Metadata = { + title: "Chat with Loca", + description: "Instantly connect with Local Expert", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + }> + +
+
+ +
+ +
+
+
+
+
+ + + ); +} diff --git a/app/chat/page.tsx b/app/chat/page.tsx index f6d5e66..b014ce0 100644 --- a/app/chat/page.tsx +++ b/app/chat/page.tsx @@ -1,60 +1,12 @@ "use client"; import Main from "@/components/main"; -import Sidebar from "@/components/sidebar"; -import { onAuthStateChanged } from "firebase/auth"; -import { useEffect, useState } from "react"; -import { auth } from "@/lib/firebase"; -import local from "@/public/png/logo-black.png"; -import Image from "next/image"; import FirstVisitPopup from "@/components/firstvisitpopup"; -import { SignOut } from "@/lib/signIn"; -import { LogOut } from "lucide-react"; export default function Chat() { - const [user, setUser] = useState(auth.currentUser); - const image = user?.photoURL || local; - - useEffect(() => { - const unsubscribe = onAuthStateChanged(auth, (currentUser) => { - setUser(currentUser); - }); - - return () => unsubscribe(); - }); return ( -
+
-
- -
-
+
-
- -
-
diff --git a/app/faq/data.ts b/app/faq/data.ts new file mode 100644 index 0000000..0782c2c --- /dev/null +++ b/app/faq/data.ts @@ -0,0 +1,77 @@ +export const faqs = [ + { + emoji: "❓", + question: "What is Loca?", + answer: "Loca is a Localized Service Finder that helps you discover and book local services based on your needs. It uses natural language processing to provide relevant information quickly.", + }, + { + emoji: "🔍", + question: "How do I search for a service?", + answer: "Simply type your query in the search bar, and Loca will provide you with a list of available local services matching your criteria.", + }, + { + emoji: "📞", + question: "Can I contact the service providers directly?", + answer: "Yes, you can! Loca provides the contact number for each service provider, allowing you to call them directly for more information or to book a service.", + }, + { + emoji: "🖥️", + question: "Can I visit the service provider's website?", + answer: "Yes, Loca includes links to the service providers' websites, where available, so you can learn more about their offerings and services.", + }, + { + emoji: "📅", + question: "Can I book a service through Loca?", + answer: "Absolutely! Loca allows you to book services directly through the platform, making it easy and convenient to arrange appointments.", + }, + { + emoji: "🔄", + question: "How frequently is the service information updated?", + answer: "Loca updates service information regularly to ensure accuracy and relevance, providing you with the latest details available.", + }, + { + emoji: "🌐", + question: "Is Loca available in multiple languages?", + answer: "Currently, Loca primarily supports English, but we are working on adding more language options to serve a wider audience.", + }, + { + emoji: "🛠️", + question: "What types of services can I find on Loca?", + answer: "Loca offers a wide range of services, including healthcare, beauty, home maintenance, and more. Whatever you need, Loca can help you find it.", + }, + { + emoji: "💸", + question: "Are there any costs associated with using Loca?", + answer: "Loca is free to use for finding and booking services. However, the cost of the services themselves depends on the provider.", + }, + { + emoji: "📧", + question: "How can I provide feedback or suggest improvements?", + answer: "We welcome your feedback! You can contact us through our support page or email us directly with your suggestions and comments.", + }, + { + emoji: "👥", + question: "Can businesses register their services on Loca?", + answer: "Yes, businesses can register to offer their services on Loca. Please visit our 'For Business' section to learn more and get started.", + }, + { + emoji: "📈", + question: "How does Loca prioritize the services shown?", + answer: "Loca uses a combination of user ratings, relevance, and proximity to prioritize services, ensuring you get the best options available.", + }, + { + emoji: "🔐", + question: "Is my data safe with Loca?", + answer: "We take your privacy seriously. Loca uses advanced security measures to protect your data and ensure it is used only for service-related purposes.", + }, + { + emoji: "🚀", + question: "How quickly can I expect to find and book a service?", + answer: "Loca is designed for speed and convenience. You can typically find and book a service within minutes, depending on availability and your specific needs.", + }, + { + emoji: "🏆", + question: "What makes Loca different from other service finders?", + answer: "Loca stands out with its localized approach, natural language processing, and easy-to-use interface, making it simple to find and book the best local services.", + }, + ]; \ No newline at end of file diff --git a/app/faq/page.tsx b/app/faq/page.tsx index 21ac489..dfbcfad 100644 --- a/app/faq/page.tsx +++ b/app/faq/page.tsx @@ -1,19 +1,28 @@ -"use client" -import React, { useState } from 'react' - +"use client"; +import Navbar from "@/components/navbar"; +import React, { useState } from "react"; +import { Accordion } from "@mantine/core"; +import { faqs } from "./data"; const FAQ = () => { - const [open, setOpen] = useState(false) + const [open, setOpen] = useState(false); + const items = faqs.map((item) => ( + + {item.question} + {item.answer} + + )); return ( -
-
-

LOCA AI FAQ

-
-

What&s LOCA?

- +
+
+ +
+

What is Loca AI?

+ {items}
-
+
- ) -} + ); +}; + +export default FAQ; -export default FAQ \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index 024718d..3197419 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -17,8 +17,9 @@ export default function RootLayout({ }>) { return ( - - {children} + + {children} + ); } diff --git a/components/CardCarousel.tsx b/components/CardCarousel.tsx index 8290745..c1eb7fc 100644 --- a/components/CardCarousel.tsx +++ b/components/CardCarousel.tsx @@ -23,8 +23,8 @@ export const CardCarousel = () => { const image = user?.photoURL || local; const robotText = `Hi ${user?.displayName}, Yes! I found a great one nearby. Check it out and book now.`; return ( -
-
+
+
{
{!showResponse && text && ( -
+

Loca AI

@@ -46,7 +46,7 @@ export const CardCarousel = () => { {showResponse && (
{ + onInit={(t:any) => { t.typeString(robotText) .callFunction(() => { setShowCard(true); @@ -98,7 +98,7 @@ export const CardCarousel = () => {
{!showResponse && text && ( -
+

Loca AI

diff --git a/components/LocalServiceCard.tsx b/components/LocalServiceCard.tsx index b8098a9..16fee37 100644 --- a/components/LocalServiceCard.tsx +++ b/components/LocalServiceCard.tsx @@ -8,6 +8,9 @@ export const LocalServiceCard: React.FC = ({ rating, user_ratings_total, place_id, + phone_number, + website, + email, }) => { return (
@@ -25,7 +28,7 @@ export const LocalServiceCard: React.FC = ({ Book Now */} - +
); }; \ No newline at end of file diff --git a/components/booking.tsx b/components/booking.tsx index 3db1ad8..578ed7a 100644 --- a/components/booking.tsx +++ b/components/booking.tsx @@ -1,7 +1,8 @@ +"use client"; + import * as React from "react"; import { cn } from "@/lib/utils"; -// import { useMediaQuery } from "@/hooks/use-media-query" import { Button } from "@/components/ui/button"; import { Dialog, @@ -27,17 +28,37 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Separator } from "@/components/ui/separator"; import { useMediaQuery } from "@custom-react-hooks/all"; +import { useClipboard } from "@mantine/hooks"; +import { CopyCheckIcon, CopyIcon, CopyleftIcon } from "lucide-react"; +import Link from "next/link"; + export function Booking({ mapLink, locationName, providerName, + providerEmail, + providerWebsite, + providerPhone, }: { mapLink: string; locationName: string; providerName: string; + providerEmail?: string; + providerPhone?: string; + providerWebsite?: string; }) { const [open, setOpen] = React.useState(false); const isDesktop = useMediaQuery("(min-width: 768px)"); + const [mapError, setMapError] = React.useState(false); + const clipboard = useClipboard({ timeout: 500 }); + React.useEffect(() => { + // Reset error state when mapLink changes + setMapError(false); + }, [mapLink]); + + const handleMapError = () => { + setMapError(true); + }; if (isDesktop) { return ( @@ -61,40 +82,78 @@ export function Booking({ - +
+ +
+ {clipboard.copied ? ( + + ) : ( + clipboard.copy(providerPhone)} + /> + )} +
+
- +
+ +
+ {clipboard.copied ? ( + + ) : ( + clipboard.copy(providerEmail)} + /> + )} +
+
- {mapLink} + -{" "}
- + {!mapError ? ( + + ) : ( +
+

Unable to load map. Please check the link below:

+ + Open Map + +
+ )} +
- - ReadMore on How we use Loca to Book you a service provider - - + + ReadMore on How we use Loca to Book you a service provider + + {/* Booking by loca is still in development and will be available soon.. - + */}
@@ -169,37 +228,74 @@ export function Booking({ - +
+ +
+ {clipboard.copied ? ( + + ) : ( + clipboard.copy(providerPhone)} /> + )} +
+
- +
+ +
+ {clipboard.copied ? ( + + ) : ( + clipboard.copy(providerEmail)} /> + )} +
+
+ -{" "}
- + {!mapError ? ( + + ) : ( +
+

Unable to load map. Please check the link below:

+ + Open Map + +
+ )} +
- - ReadMore on How we use Loca to Book you a service provider - - - Booking by loca is still in development and will be available soon.. - + + ReadMore on How we use Loca to Book you a service provider + + {/* + Booking by loca is still in development and will be available + soon.. + */}
diff --git a/components/bookingForm.tsx b/components/bookingForm.tsx new file mode 100644 index 0000000..5b47e58 --- /dev/null +++ b/components/bookingForm.tsx @@ -0,0 +1,94 @@ +"use client"; +import { Fieldset, TextInput, Textarea } from "@mantine/core"; +import { Button } from "./ui/button"; +import { zodResolver } from "mantine-form-zod-resolver"; +import { z } from "zod"; +import { useForm } from "@mantine/form"; +import { db } from "@/lib/firebase"; +import { collection, addDoc } from "firebase/firestore"; +import { useRouter } from "next/navigation"; + +export function BookingForm() { + const router = useRouter() + const schema = z.object({ + name: z.string().min(2, { message: "Name is required" }), + email: z.string().email({ message: "Invalid email" }), + message: z.string().min(2, { message: "Message is required" }), + }); + + const form = useForm({ + initialValues: { + name: "", + email: "", + message: "", + }, + validate: zodResolver(schema), + }); + + const handleSubmit = async (values: { + name: string; + email: string; + message: string; + }) => { + try { + if (form.validate()) { + const docRef = await addDoc(collection(db, "bookings"), { + name: values.name, + email: values.email, + message: values.message, + createdAt: new Date(), + }); + alert("Submitted successfully ") + console.log("Document written with ID: ", docRef.id); + router.push("/chat") + }else{ + alert("Wrong credential") + } + + form.reset(); + } catch (e) { + console.error("Error adding document: ", e); + } + }; + + return ( +
+
+
+ + +