diff --git a/frontend/src/components/forms/RichTextEdit.tsx b/frontend/src/components/forms/RichTextEdit.tsx index 6f02fec..8a026ee 100644 --- a/frontend/src/components/forms/RichTextEdit.tsx +++ b/frontend/src/components/forms/RichTextEdit.tsx @@ -135,6 +135,8 @@ export default () => { // Get field with predefined text type const field = useFieldContext(); + if (field.state.value === null) return
Loading...
; + // Configure editor const editor = useEditor({ onUpdate: ({ editor }) => field.handleChange(editor.getHTML()), @@ -150,6 +152,7 @@ export default () => { + {!field.state.meta.isValid && ( {field.state.meta.errors.map((e) => e.message).join(", ")} diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index dfbcd86..980cb12 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -16,6 +16,7 @@ import { Route as TemplatesIndexRouteImport } from './routes/templates/index' import { Route as TemplatesCreateRouteImport } from './routes/templates/create' import { Route as CoverCreateRouteImport } from './routes/cover/create' import { Route as CoverCoverIdRouteImport } from './routes/cover/$coverId' +import { Route as CoverEditCoverIdRouteImport } from './routes/cover/edit.$coverId' const RegisterRoute = RegisterRouteImport.update({ id: '/register', @@ -52,6 +53,11 @@ const CoverCoverIdRoute = CoverCoverIdRouteImport.update({ path: '/cover/$coverId', getParentRoute: () => rootRouteImport, } as any) +const CoverEditCoverIdRoute = CoverEditCoverIdRouteImport.update({ + id: '/cover/edit/$coverId', + path: '/cover/edit/$coverId', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -61,6 +67,7 @@ export interface FileRoutesByFullPath { '/cover/create': typeof CoverCreateRoute '/templates/create': typeof TemplatesCreateRoute '/templates': typeof TemplatesIndexRoute + '/cover/edit/$coverId': typeof CoverEditCoverIdRoute } export interface FileRoutesByTo { '/': typeof IndexRoute @@ -70,6 +77,7 @@ export interface FileRoutesByTo { '/cover/create': typeof CoverCreateRoute '/templates/create': typeof TemplatesCreateRoute '/templates': typeof TemplatesIndexRoute + '/cover/edit/$coverId': typeof CoverEditCoverIdRoute } export interface FileRoutesById { __root__: typeof rootRouteImport @@ -80,6 +88,7 @@ export interface FileRoutesById { '/cover/create': typeof CoverCreateRoute '/templates/create': typeof TemplatesCreateRoute '/templates/': typeof TemplatesIndexRoute + '/cover/edit/$coverId': typeof CoverEditCoverIdRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath @@ -91,6 +100,7 @@ export interface FileRouteTypes { | '/cover/create' | '/templates/create' | '/templates' + | '/cover/edit/$coverId' fileRoutesByTo: FileRoutesByTo to: | '/' @@ -100,6 +110,7 @@ export interface FileRouteTypes { | '/cover/create' | '/templates/create' | '/templates' + | '/cover/edit/$coverId' id: | '__root__' | '/' @@ -109,6 +120,7 @@ export interface FileRouteTypes { | '/cover/create' | '/templates/create' | '/templates/' + | '/cover/edit/$coverId' fileRoutesById: FileRoutesById } export interface RootRouteChildren { @@ -119,6 +131,7 @@ export interface RootRouteChildren { CoverCreateRoute: typeof CoverCreateRoute TemplatesCreateRoute: typeof TemplatesCreateRoute TemplatesIndexRoute: typeof TemplatesIndexRoute + CoverEditCoverIdRoute: typeof CoverEditCoverIdRoute } declare module '@tanstack/react-router' { @@ -172,6 +185,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof CoverCoverIdRouteImport parentRoute: typeof rootRouteImport } + '/cover/edit/$coverId': { + id: '/cover/edit/$coverId' + path: '/cover/edit/$coverId' + fullPath: '/cover/edit/$coverId' + preLoaderRoute: typeof CoverEditCoverIdRouteImport + parentRoute: typeof rootRouteImport + } } } @@ -183,6 +203,7 @@ const rootRouteChildren: RootRouteChildren = { CoverCreateRoute: CoverCreateRoute, TemplatesCreateRoute: TemplatesCreateRoute, TemplatesIndexRoute: TemplatesIndexRoute, + CoverEditCoverIdRoute: CoverEditCoverIdRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/frontend/src/routes/cover/$coverId.tsx b/frontend/src/routes/cover/$coverId.tsx index 042e23c..e679a78 100644 --- a/frontend/src/routes/cover/$coverId.tsx +++ b/frontend/src/routes/cover/$coverId.tsx @@ -3,12 +3,12 @@ import Authorised from "@/layouts/Authorised"; import requests from "@/lib/requests"; import type { CoverLetter } from "@/types/api"; import { useQuery } from "@tanstack/react-query"; -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, Link } from "@tanstack/react-router"; import "../../editor.css"; import { toPng } from "html-to-image"; import { useRef } from "react"; import { Button } from "@/components/ui/button"; -import { DownloadIcon } from "lucide-react"; +import { DownloadIcon, EditIcon } from "lucide-react"; export const Route = createFileRoute("/cover/$coverId")({ component: RouteComponent, @@ -51,10 +51,20 @@ function RouteComponent() {

{cover.data?.cover.name || "Loading..."}

- - {/* edit buttons */} + +
+ + + + +
diff --git a/frontend/src/routes/cover/edit.$coverId.tsx b/frontend/src/routes/cover/edit.$coverId.tsx new file mode 100644 index 0000000..ba6b11c --- /dev/null +++ b/frontend/src/routes/cover/edit.$coverId.tsx @@ -0,0 +1,85 @@ +import renderQueryState from "@/components/RenderQueryState"; +import { Button } from "@/components/ui/button"; +import { useAppForm } from "@/hooks/formHook"; +import Authorised from "@/layouts/Authorised"; +import requests from "@/lib/requests"; +import type { CoverLetter } from "@/types/api"; +import { useQuery } from "@tanstack/react-query"; +import { createFileRoute, useNavigate } from "@tanstack/react-router"; +import { useState } from "react"; +import { z } from "zod/v4"; + +export const Route = createFileRoute("/cover/edit/$coverId")({ + component: RouteComponent, +}); + +const editSchema = z.object({ + name: z.string().min(1, "Name is required"), + letter: z.string().min(50, "Application is too short"), +}); + +function RouteComponent() { + const { coverId } = Route.useParams(); + const navigate = useNavigate(); + const loading = useState(false); + + const cover = useQuery({ + queryKey: ["cover", coverId], + queryFn: () => requests.get<{ cover: CoverLetter }>(`/cover/${coverId}`, {}), + }); + const coverState = renderQueryState({ + query: cover, + noFound: "cover letter", + skeleton: { + count: 1, + className: "h-[400px]", + }, + }); + + const edit = useAppForm({ + defaultValues: { + name: cover.data?.cover.name || "", + letter: cover.data?.cover.letter || null, + }, + validators: { + onBlur: editSchema, + }, + onSubmit({ value }) { + requests.post(`/cover/${coverId}`, { + data: value, + before() { + loading[1](true); + }, + finally() { + loading[1](false); + }, + success() { + navigate({ to: "/cover/$coverId", params: { coverId: coverId } }); + }, + }); + }, + }); + + return ( + +

Edit cover letter

+ +
+ } + /> + + {coverState !== null ? ( + coverState + ) : ( + } /> + )} +
+ + +
+ ); +} diff --git a/frontend/src/routes/templates/index.tsx b/frontend/src/routes/templates/index.tsx index f80791f..5004d18 100644 --- a/frontend/src/routes/templates/index.tsx +++ b/frontend/src/routes/templates/index.tsx @@ -1,11 +1,10 @@ import { Button } from "@/components/ui/button"; import Authorised from "@/layouts/Authorised"; import requests from "@/lib/requests"; -import { useQuery, type UseQueryResult } from "@tanstack/react-query"; +import { useQuery } from "@tanstack/react-query"; import { createFileRoute, Link } from "@tanstack/react-router"; import { Plus } from "lucide-react"; import type { Template } from "@/types/api"; -import { Skeleton } from "@/components/ui/skeleton"; import renderQueryState from "@/components/RenderQueryState"; export const Route = createFileRoute("/templates/")({