diff --git a/frontend/src/app/providers.tsx b/frontend/src/app/providers.tsx index eb6b006..5e6a69f 100644 --- a/frontend/src/app/providers.tsx +++ b/frontend/src/app/providers.tsx @@ -1,6 +1,6 @@ "use client"; -import { Scheme } from "@/components/schemes/schemes-list"; +import { SearchResScheme } from "@/components/schemes/schemes-list"; import { NextUIProvider } from "@nextui-org/react"; import React, { createContext, @@ -21,8 +21,8 @@ type ChatContextType = { setUserQuery: React.Dispatch>; messages: Message[]; setMessages: React.Dispatch>; - schemes: Scheme[]; - setSchemes: React.Dispatch>; + schemes: SearchResScheme[]; + setSchemes: React.Dispatch>; }; const ChatContext = createContext(undefined); @@ -30,7 +30,7 @@ const ChatContext = createContext(undefined); export const ChatProvider = ({ children }: { children: ReactNode }) => { const [messages, setMessages] = useState([]); const [userQuery, setUserQuery] = useState(""); - const [schemes, setSchemes] = useState([]); + const [schemes, setSchemes] = useState([]); useEffect(() => { const storedSchemes = localStorage.getItem('schemes'); diff --git a/frontend/src/app/schemes/[schemeId]/page.tsx b/frontend/src/app/schemes/[schemeId]/page.tsx index d1c8c41..6aed740 100644 --- a/frontend/src/app/schemes/[schemeId]/page.tsx +++ b/frontend/src/app/schemes/[schemeId]/page.tsx @@ -1,65 +1,201 @@ 'use client'; -import { useChat } from "@/app/providers"; -import { Chip, Divider, Image, Link, Spacer } from "@nextui-org/react"; +import { useEffect, useState } from "react"; +import { Chip, Divider, Image, Link, Skeleton, Spacer } from "@nextui-org/react"; import { useParams } from "next/navigation"; -import classes from "./scheme.module.css" +import classes from "./scheme.module.css"; +import { SearchResScheme } from "@/components/schemes/schemes-list"; +// Type for full scheme properties +type Scheme = SearchResScheme & { + lastUpdated?: string; + eligibility?: { + criteria: string; + requiredDocuments: string[]; + }; + application?: { + process: string; + deadline: string; + formLink: string; + termsAndConditions: string; + }; + contact?: { + phone: string; + email: string; + address: string; + website: string; + socialMedia?: { + facebook?: string; + instagram?: string; + linkedin?: string; + }; + feedbackLink: string; + }; + additionalInfo?: { + faqs?: { question: string; answer: string }[]; + successStories?: { title: string; url: string }[]; + relatedSchemes?: { id: string; scheme: string; agency: string; link: string }[]; + additionalResources?: { title: string; url: string }[]; + programmeDuration?: string; + languageOptions?: string[]; + }; +}; + +const mapToFullScheme = (rawData: any): Scheme => { + return { + // Properties from Scheme + schemeType: rawData["Scheme Type"] || "", + schemeName: rawData["Scheme"] || "", + targetAudience: rawData["Who's it for"] || "", + agency: rawData["Agency"] || "", + description: rawData["Description"] || "", + scrapedText: rawData["scraped_text"] || "", + benefits: rawData["What it gives"] || "", + link: rawData["Link"] || "", + image: rawData["Image"] || "", + searchBooster: rawData["search_booster(WL)"] || "", + schemeId: rawData["scheme_id"] || "", + query: rawData["query"] || "", + similarity: rawData["Similarity"] || 0, + quintile: rawData["Quintile"] || 0, + + // Additional properties for FullScheme + lastUpdated: rawData["Last Updated"] || "", + eligibility: rawData["Eligibility"] || undefined, + application: rawData["Application"] || undefined, + contact: rawData["Contact"] || undefined, + additionalInfo: rawData["Additional Info"] || undefined, + }; +}; export default function SchemePage() { - const { schemes } = useChat(); const { schemeId } = useParams(); + const [scheme, setScheme] = useState(null); + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(true); - const getScheme = (schemeId: string) => { - const idx = schemes.findIndex(scheme => scheme.schemeId === schemeId); - if (idx >= 0) { - return schemes[idx] - } else { - console.error("No scheme found.") + useEffect(() => { + async function fetchScheme() { + if (!schemeId) return; + + try { + const id = Array.isArray(schemeId) ? schemeId[0] : schemeId; + const response = await fetch( + `http://localhost:5001/schemessg-v3-dev/asia-southeast1/schemes/${id}` + ); + if (!response.ok) { + throw new Error("Failed to fetch scheme"); + } + const res = await response.json(); + const schemeRes = mapToFullScheme(res.data); + setScheme(schemeRes); + } catch (err: any) { + console.log(err) + setError("An error occurred"); + } finally { + setIsLoading(false); } - } - // useParams returns string | string[] - const scheme = getScheme(Array.isArray(schemeId) ? schemeId[0] : schemeId); + } - const getSchemeTypes = (schemeTypeStr: string) => { - return schemeTypeStr.split(","); + fetchScheme(); + }, [schemeId]); + + if (error) { + return

{error}

; } - return ( - scheme && -
-
{getSchemeTypes(scheme.schemeType).map((schemeType: string) => {schemeType})}
-
- {`${scheme.agency} -
-

{scheme.schemeName}

-

{scheme.agency}

-
-
+ if (isLoading) { + return ( +
+ {/* Skeleton loader for scheme type */} +
+ -

{scheme.description}

- -
-

Target Audience

- -

{scheme.targetAudience}

-
- -
-

Benefits

-

{scheme.benefits}

-
- -
-

Contact

- {scheme.link} +
+ + {/* Skeleton loader for scheme title */} +
+ +
+ +
+
+ + + + {/* Skeleton loader for description */} +
+ + +
+ + + {/* Skeleton loader for other sections */} + + + +
+ ); + } + + // Helper to extract scheme types + const getSchemeTypes = (schemeTypeStr: string) => schemeTypeStr.split(","); + + return (scheme && +
+
+ {getSchemeTypes(scheme.schemeType).map((type) => ( + + {type} + + ))} +
+ +
+ {`${scheme.agency} +
+

{scheme.schemeName}

+

{scheme.agency}

+
+
+ + + +

{scheme.description}

+ + +
+

Target Audience

+ +

{scheme.targetAudience}

+
+ + + + {/* Benefits */} +
+

What It Gives

+

{scheme.benefits}

+
+ + + + {scheme.contact && ( +
+

Contact

+

Phone: {scheme.contact.phone}

+

Email: {scheme.contact.email}

+

Website: {scheme.contact.website}

+

Address: {scheme.contact.address}

- ); + )} +
+ ); } diff --git a/frontend/src/components/schemes/schemes-list.tsx b/frontend/src/components/schemes/schemes-list.tsx index e17e5ce..1a75e35 100644 --- a/frontend/src/components/schemes/schemes-list.tsx +++ b/frontend/src/components/schemes/schemes-list.tsx @@ -1,7 +1,8 @@ import { Card, CardHeader, CardBody, Image, Spacer } from "@nextui-org/react"; import { Link } from "@nextui-org/react"; -export type Scheme = { +// Type for scheme from search results +export type SearchResScheme = { schemeType: string, schemeName: string, targetAudience: string, @@ -19,7 +20,7 @@ export type Scheme = { } interface SchemesListProps { - schemes: Scheme[] + schemes: SearchResScheme[] } export default function SchemesList({ schemes }: SchemesListProps) { diff --git a/frontend/src/components/search-bar/search-bar.tsx b/frontend/src/components/search-bar/search-bar.tsx index 6f19719..76c1313 100644 --- a/frontend/src/components/search-bar/search-bar.tsx +++ b/frontend/src/components/search-bar/search-bar.tsx @@ -5,13 +5,13 @@ import { SearchIcon } from '../../assets/icons/search-icon'; import classes from './search-bar.module.css'; import { useState } from "react"; import { useChat } from "@/app/providers"; -import { Scheme } from "../schemes/schemes-list"; +import { SearchResScheme } from "../schemes/schemes-list"; interface SearchBarProps { setSessionId: (val: string) => void; } -const mapToScheme = (rawData: any): Scheme => { +const mapToSearchResScheme = (rawData: any): SearchResScheme => { return { schemeType: rawData["Scheme Type"] || "", schemeName: rawData["Scheme"] || "", @@ -69,7 +69,7 @@ export default function SearchBar({ setSessionId }: SearchBarProps) { const res = await response.json(); const sessionId: string = res["sessionID"]; setIsBotResponseGenerating(false); - const schemesRes: Scheme[] = res.data.map(mapToScheme); + const schemesRes: SearchResScheme[] = res.data.map(mapToSearchResScheme); return { schemesRes, sessionId }; } catch (error) { console.error("Error making POST request:", error);