diff --git a/app/config/config.ts b/app/config/config.ts index b94de1a..85138aa 100644 --- a/app/config/config.ts +++ b/app/config/config.ts @@ -2,12 +2,7 @@ import { type NavLink } from '#app/model/nav.ts' export const mainNav: NavLink[] = [ { - title: 'Translate', - href: '/translate', + title: 'Settings', + href: '/settings', }, - { - title: 'elaborate', - href: '/elaborate', - disabled: true, - }, -].filter(({ disabled }) => !disabled) +] diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx index 3cfb71f..d4adca6 100644 --- a/app/routes/_index.tsx +++ b/app/routes/_index.tsx @@ -1,21 +1,207 @@ -import { Link } from '@remix-run/react' -import { mainNav } from '#app/config/config.ts' +import { getFormProps, getTextareaProps, useForm } from '@conform-to/react' +import { getZodConstraint, parseWithZod } from '@conform-to/zod' +import { + type LoaderFunctionArgs, + type ActionFunctionArgs, + data, +} from '@remix-run/node' +import { Form, Link, useActionData, useLoaderData } from '@remix-run/react' +import { AlertCircle, ArrowUp, LoaderCircle, Settings2 } from 'lucide-react' +import { z } from 'zod' +import { + Alert, + AlertDescription, + AlertTitle, +} from '#app/components/ui/alert.tsx' +import { Button } from '#app/components/ui/button.tsx' +import { Card, CardContent } from '#app/components/ui/card.tsx' +import { ScrollArea } from '#app/components/ui/scroll-area.tsx' +import { type ScreenSizeHandle } from '#app/utils/screen-size.ts' +import { targetLangConfig } from '#app/utils/translation.ts' +import { getSettingsSession, Translator } from '#app/utils/translator.server.ts' +import { useIsPending } from '#app/utils/ui.ts' + +export const schema = z.object({ + expression: z + .string({ + required_error: 'Expression is required for an polyglotization!', + }) + .max(120, 'Expression is limited to 120 characters.'), +}) + +export const handle: ScreenSizeHandle = { + screenSize: true, +} + +export const loader = async ({ request }: LoaderFunctionArgs) => { + const settings = await getSettingsSession(request) + + return { + valid: !!settings.data.targetLanguages, + } +} + +export const action = async ({ request }: ActionFunctionArgs) => { + const translationSession = await getSettingsSession(request) + const sourceLanguage = translationSession.get('sourceLanguage') + const targetLanguages = translationSession.get('targetLanguages') + + if (!targetLanguages) { + throw new Error('TODO') + } + + const formData = await request.formData() + const submission = parseWithZod(formData, { schema }) + + if (submission.status !== 'success') { + return data( + { result: submission.reply(), data: null }, + { status: submission.status === 'error' ? 400 : 200 }, + ) + } + + const { expression } = submission.value + const translator = new Translator(process.env.DEEPL_KEY!) + const translation = await translator.translate( + expression, + sourceLanguage, + targetLanguages, + ) + + if (!translation) { + return data({ result: submission.reply(), data: null }, { status: 400 }) + } + + return data({ + result: submission.reply({ resetForm: true }), + data: { + expression, + translation, + }, + }) +} export default function Page() { + const data = useLoaderData() + const actionData = useActionData() + const isPending = useIsPending() + + const [form, fields] = useForm({ + lastResult: actionData?.result, + constraint: getZodConstraint(schema), + onValidate: ({ formData }) => parseWithZod(formData, { schema }), + }) + + const allErrors = Object.values(form.allErrors).flat() + return ( -
-

Try it out

- -
+ ! + + + )} + {allErrors.length > 0 && ( + + + There is something wrong + {allErrors[0]} + + )} +
+
+