diff options
author | polwex <polwex@sortug.com> | 2025-05-15 20:32:25 +0700 |
---|---|---|
committer | polwex <polwex@sortug.com> | 2025-05-15 20:32:25 +0700 |
commit | fd86dc15734f3b7126d88f0130897c597100e30a (patch) | |
tree | 253890a5f0bde7bc460904ce1743581f53a23d5b /src/pages | |
parent | 3d4b740e5a512db8fbdd934af2fbc9585fa00f0f (diff) |
m
Diffstat (limited to 'src/pages')
-rw-r--r-- | src/pages/_layout.tsx | 18 | ||||
-rw-r--r-- | src/pages/api/proxy.ts | 2 | ||||
-rw-r--r-- | src/pages/picker.tsx | 29 | ||||
-rw-r--r-- | src/pages/test/index.tsx | 21 | ||||
-rw-r--r-- | src/pages/test/product-details-server.tsx | 340 | ||||
-rw-r--r-- | src/pages/zoom.tsx | 29 |
6 files changed, 71 insertions, 368 deletions
diff --git a/src/pages/_layout.tsx b/src/pages/_layout.tsx index 6d227c9..7f6a434 100644 --- a/src/pages/_layout.tsx +++ b/src/pages/_layout.tsx @@ -1,9 +1,9 @@ -import '../styles.css'; +import "../styles.css"; -import type { ReactNode } from 'react'; +import type { ReactNode } from "react"; -import { Header } from '../components/header'; -import { Footer } from '../components/footer'; +import { Header } from "../components/header"; +import { Footer } from "../components/footer"; type RootLayoutProps = { children: ReactNode }; @@ -14,19 +14,17 @@ export default async function RootLayout({ children }: RootLayoutProps) { <div className="font-['Nunito']"> <meta name="description" content={data.description} /> <link rel="icon" type="image/png" href={data.icon} /> - <Header /> - <main className="m-6 flex items-center *:min-h-64 *:min-w-64 lg:m-0 lg:min-h-svh lg:justify-center"> + <main className="m-6 items-center *:min-h-64 *:min-w-64 lg:m-0 lg:min-h-svh lg:justify-center"> {children} </main> - <Footer /> </div> ); } const getData = async () => { const data = { - description: 'An internet website!', - icon: '/images/favicon.png', + description: "An internet website!", + icon: "/images/favicon.png", }; return data; @@ -34,6 +32,6 @@ const getData = async () => { export const getConfig = async () => { return { - render: 'static', + render: "static", } as const; }; diff --git a/src/pages/api/proxy.ts b/src/pages/api/proxy.ts index 3114f6b..72e5fec 100644 --- a/src/pages/api/proxy.ts +++ b/src/pages/api/proxy.ts @@ -1,7 +1,7 @@ // import db from "../../lib/db"; import { z } from "zod"; -export const proxySchema = z.object({ +const proxySchema = z.object({ path: z.string().startsWith("/").optional(), url: z.string().url("Invalid urladdress"), body: z.any().optional(), diff --git a/src/pages/picker.tsx b/src/pages/picker.tsx new file mode 100644 index 0000000..9cd86f5 --- /dev/null +++ b/src/pages/picker.tsx @@ -0,0 +1,29 @@ +import { getContextData } from "waku/middleware/context"; +import App from "@/picker/App"; + +export default async function HomePage() { + const { user } = getContextData(); + + return ( + <div> + <h1 className="text-lg text-center">Interactive Language Explorer</h1> + <App />; + </div> + ); +} + +const getData = async () => { + const data = { + title: "Waku", + headline: "Waku", + body: "Hello world!", + }; + + return data; +}; + +export const getConfig = async () => { + return { + render: "dynamic", + } as const; +}; diff --git a/src/pages/test/index.tsx b/src/pages/test/index.tsx index 35ce5db..4c9325c 100644 --- a/src/pages/test/index.tsx +++ b/src/pages/test/index.tsx @@ -1,21 +1,12 @@ // This is a Server Component by default -import ProductDetailsServer from "./product-details-server"; +import ServerWord from "@/zoom/ServerWord"; import TriggerModalButton from "./trigger-modal-button"; // We'll make this a client component to manage state export default function SomePage() { const productIdForModal = "123"; // Or get this dynamically return ( - <main className="container p-8 mx-auto"> - <h1 className="mb-6 text-3xl font-bold"> - Modal with Server Component Content - </h1> - <p> - This page demonstrates opening a modal whose content is rendered by a - Server Component. The modal shell (open/close logic) is a Client - Component. - </p> - + <div> {/* The TriggerModalButton will manage the modal's open/close state. It will receive the Server Component as a child to pass to ClientModal. @@ -23,12 +14,8 @@ export default function SomePage() { <TriggerModalButton modalTitle={`Product Details for ID: ${productIdForModal}`} > - <ProductDetailsServer word={"fantastic"} /> + <ServerWord word={"fantastic"} lang={"en"} /> </TriggerModalButton> - - <div className="mt-8"> - <p>Other content on the page...</p> - </div> - </main> + </div> ); } diff --git a/src/pages/test/product-details-server.tsx b/src/pages/test/product-details-server.tsx deleted file mode 100644 index 552ff21..0000000 --- a/src/pages/test/product-details-server.tsx +++ /dev/null @@ -1,340 +0,0 @@ -// This is a Server Component -import React from "react"; -import db from "@/lib/db"; -import { - Card, - CardHeader, - CardDescription, - CardContent, - CardFooter, - CardTitle, -} from "@/components/ui/card"; -import { NLP } from "sortug-ai"; -import { - BookOpen, - Volume2, - Link as LinkIcon, - ChevronDown, - ChevronUp, - Search, - Info, - MessageSquareQuote, - Tags, - ListTree, - Lightbulb, -} from "lucide-react"; -import { - Example, - SubSense, - RelatedEntry, - Sense, - WordData, -} from "@/zoom/logic/types"; - -export default async function Wordd({ - word, - lang, -}: { - word: string; - lang: string; -}) { - const data = db.fetchWordBySpelling(word, "en"); - console.log({ data }); - - if (!data) return <p>oh...</p>; - return ( - <Card> - <CardHeader> - <CardTitle> - <h1 className="text-xl">{word}</h1> - </CardTitle> - <CardDescription> - <IpaDisplay ipaEntries={data.ipa} /> - </CardDescription> - </CardHeader> - <CardContent> - {/* Senses */} - <h2 className="text-2xl font-semibold text-gray-800 mb-4"> - Meanings & Definitions - </h2> - {data.senses.map((sense, index) => ( - <SenseCard key={index} senseData={sense} senseNumber={index + 1} /> - ))} - </CardContent> - <CardFooter></CardFooter> - </Card> - ); - // return ( - // <div className="p-6"> - // <h3 className="mb-2 text-2xl font-bold">{word}</h3> - // <p className="mb-1 text-xl text-green-600">${word.}</p> - // <p className="text-gray-700">{word}</p> - // <p className="mt-4 text-xs text-gray-500"> - // Content rendered on the server at: {new Date().toLocaleTimeString()} - // </p> - // </div> - // ); -} - -// Helper component for IPA display -const IpaDisplay = ({ - ipaEntries, -}: { - ipaEntries: Array<{ ipa: string; tags?: string[] }>; -}) => { - if (!ipaEntries || ipaEntries.length === 0) return null; - return ( - <div className="flex items-center space-x-2 flex-wrap"> - {ipaEntries.map((entry, index) => { - const tags = entry.tags ? entry.tags : []; - return ( - <span key={index} className="text-lg text-blue-600 font-serif"> - {entry.ipa}{" "} - {tags.length > 0 && ( - <span className="text-xs text-gray-500">({tags.join(", ")})</span> - )} - </span> - ); - })} - <button - className="p-1 text-blue-500 hover:text-blue-700 transition-colors" - title="Pronounce" - // onClick={() => { - // /* Pronunciation logic would be client-side or a server roundtrip for audio file. */ alert( - // "Pronunciation feature not implemented for server component.", - // ); - // }} - > - <Volume2 size={20} /> - </button> - </div> - ); -}; - -// Component for displaying examples -const ExampleDisplay = ({ examples }: { examples: Example[] }) => { - if (!examples || examples.length === 0) return null; - return ( - <div className="mt-2"> - <h5 className="text-xs font-semibold text-gray-600 mb-1 flex items-center"> - <MessageSquareQuote size={14} className="mr-1 text-gray-500" /> - Examples: - </h5> - <ul className="list-disc list-inside pl-2 space-y-1"> - {examples.map((ex, idx) => ( - <li key={idx} className="text-xs text-gray-600"> - <span className="italic">"{ex.text}"</span> - {ex.ref && ( - <span className="text-gray-400 text-xxs"> ({ex.ref})</span> - )} - {ex.type !== "quote" && ( - <span className="ml-1 text-xxs bg-sky-100 text-sky-700 px-1 rounded-sm"> - {ex.type} - </span> - )} - </li> - ))} - </ul> - </div> - ); -}; - -// Component for displaying related terms (synonyms, antonyms, etc.) -const RelatedTermsDisplay = ({ - terms, - type, -}: { - terms: RelatedEntry[] | undefined; - type: string; -}) => { - if (!terms || terms.length === 0) return null; - return ( - <div className="mt-1"> - <span className="text-xs font-semibold text-gray-500 capitalize"> - {type}:{" "} - </span> - {terms.map((term, idx) => ( - <React.Fragment key={idx}> - <a - href={`/search?q=${encodeURIComponent(term.word)}`} - className="text-xs text-blue-500 hover:text-blue-700 hover:underline" - > - {term.word} - </a> - {term.source && ( - <span className="text-xxs text-gray-400"> ({term.source})</span> - )} - {idx < terms.length - 1 && ", "} - </React.Fragment> - ))} - </div> - ); -}; - -// Component for displaying a SubSense -const SubSenseDisplay = ({ - subSense, - subSenseNumber, -}: { - subSense: SubSense; - subSenseNumber: number; -}) => { - return ( - <div className="mb-3 pl-4 border-l-2 border-indigo-200"> - {subSense.glosses.map((gloss, glossIdx) => ( - <p key={glossIdx} className="text-gray-700 mb-1"> - <span className="font-semibold"> - {subSenseNumber}.{glossIdx + 1} - </span>{" "} - {gloss} - </p> - ))} - {subSense.raw_glosses && - subSense.raw_glosses.length > 0 && - subSense.raw_glosses.join("") !== subSense.glosses.join("") && ( - <p className="text-xs text-gray-500 italic mb-1"> - (Raw: {subSense.raw_glosses.join("; ")}) - </p> - )} - - {subSense.categories && subSense.categories.length > 0 && ( - <div className="mt-1 mb-2"> - <h5 className="text-xs font-semibold text-gray-600 mb-0.5 flex items-center"> - <ListTree size={14} className="mr-1 text-gray-500" /> - Categories: - </h5> - <div className="flex flex-wrap gap-1"> - {subSense.categories.map((cat, idx) => ( - <span - key={idx} - className="text-xxs bg-gray-100 text-gray-700 px-1.5 py-0.5 rounded-full" - > - {cat} - </span> - ))} - </div> - </div> - )} - - <ExampleDisplay examples={subSense.examples || []} /> - <RelatedTermsDisplay terms={subSense.synonyms} type="Synonyms" /> - - {subSense.tags && subSense.tags.length > 0 && ( - <div className="mt-2"> - <h5 className="text-xs font-semibold text-gray-600 mb-0.5 flex items-center"> - <Tags size={14} className="mr-1 text-gray-500" /> - Tags: - </h5> - <div className="flex flex-wrap gap-1"> - {subSense.tags.map((tag, idx) => ( - <span - key={idx} - className="text-xxs bg-purple-100 text-purple-700 px-1.5 py-0.5 rounded-full" - > - {tag} - </span> - ))} - </div> - </div> - )} - - {subSense.links && subSense.links.length > 0 && ( - <div className="mt-2"> - {subSense.links.map(([type, target], linkIdx) => ( - <a - key={linkIdx} - href={target} // Assuming target is a full URL or a path - target="_blank" - rel="noopener noreferrer" - className="text-xs text-blue-500 hover:text-blue-700 hover:underline mr-2 inline-flex items-center" - > - <LinkIcon size={12} className="mr-1" /> {type} - </a> - ))} - </div> - )} - </div> - ); -}; - -// Component for individual sense -const SenseCard = ({ - senseData, - senseNumber, -}: { - senseData: Sense; - senseNumber: number; -}) => { - return ( - <div className="mb-6 p-4 border border-gray-200 rounded-lg shadow-sm bg-white"> - <div className="flex justify-between items-center mb-2"> - <h3 className="text-xl font-semibold text-indigo-700"> - {senseNumber}. {senseData.pos} - </h3> - </div> - - {senseData.etymology && ( - <details className="mb-3 group"> - <summary className="cursor-pointer flex items-center text-sm text-gray-600 hover:text-indigo-600 transition-colors list-none"> - Etymology - <ChevronDown size={16} className="ml-1 group-open:hidden" /> - <ChevronUp size={16} className="ml-1 hidden group-open:inline" /> - </summary> - <p className="mt-1 text-xs text-gray-500 italic bg-gray-50 p-2 rounded"> - {senseData.etymology} - </p> - </details> - )} - - {senseData.forms && senseData.forms.length > 0 && ( - <div className="mb-3"> - <h4 className="text-sm font-medium text-gray-700">Forms:</h4> - <div className="flex flex-wrap gap-2 mt-1"> - {senseData.forms.map((form, idx) => ( - <span - key={idx} - className="text-xs bg-gray-100 text-gray-700 px-2 py-1 rounded-full" - > - {form.form}{" "} - {form.tags.length > 0 && `(${form.tags.join(", ")})`} - </span> - ))} - </div> - </div> - )} - - {senseData.senses.map((subSense, idx) => ( - <SubSenseDisplay - key={idx} - subSense={subSense} - subSenseNumber={senseNumber} - /> - ))} - - {senseData.related && ( - <div className="mt-3 pt-3 border-t border-gray-100"> - <h4 className="text-sm font-medium text-gray-700 mb-1 flex items-center"> - <Lightbulb size={16} className="mr-1 text-gray-500" /> - Related Terms: - </h4> - <RelatedTermsDisplay - terms={senseData.related.related} - type="Related" - /> - <RelatedTermsDisplay - terms={senseData.related.synonyms} - type="Synonyms (POS)" - /> - <RelatedTermsDisplay - terms={senseData.related.antonyms} - type="Antonyms (POS)" - /> - <RelatedTermsDisplay - terms={senseData.related.derived} - type="Derived" - /> - </div> - )} - </div> - ); -}; diff --git a/src/pages/zoom.tsx b/src/pages/zoom.tsx new file mode 100644 index 0000000..d088553 --- /dev/null +++ b/src/pages/zoom.tsx @@ -0,0 +1,29 @@ +import { getContextData } from "waku/middleware/context"; +import * as Zoom from "@/zoom"; + +export default async function HomePage() { + const { user } = getContextData(); + + return ( + <div> + <h1 className="text-lg text-center">Interactive Language Explorer</h1> + <Zoom.App />; + </div> + ); +} + +const getData = async () => { + const data = { + title: "Waku", + headline: "Waku", + body: "Hello world!", + }; + + return data; +}; + +export const getConfig = async () => { + return { + render: "dynamic", + } as const; +}; |