feat(cover): Display generated cover letters

This commit is contained in:
Leons Aleksandrovs
2025-07-12 17:11:11 +03:00
parent ad822f3abc
commit d0de05e0a2
8 changed files with 120 additions and 4 deletions

View File

@@ -29,7 +29,7 @@ func Get(c *gin.Context) {
return
}
covers, err := cover.Get("user_id = $1", user.Id)
covers, err := cover.Get("user_id = $1 ORDER BY created_at DESC", user.Id)
if err != nil {
res.Error(c, err.Error(), http.StatusInternalServerError)
return

View File

@@ -69,7 +69,7 @@ func Get(c *gin.Context) {
}
// Get all user templates
templates, err := template.Get("user_id = $1", user.Id)
templates, err := template.Get("user_id = $1 ORDER BY created_at DESC", user.Id)
if err != nil {
res.Error(c, err.Error(), http.StatusInternalServerError)
return

View File

@@ -1,3 +1,3 @@
export default function Container({ children, className = "" }: React.ComponentProps<"div">) {
return <div className={`container mx-auto max-w-6xl px-4 ${className}`}>{children}</div>;
return <div className={`container mx-auto max-w-6xl px-4 mb-16 ${className}`}>{children}</div>;
}

View File

@@ -24,6 +24,11 @@
margin-top: 0;
}
/* Text */
p {
margin-top: 0.5rem;
}
/* Links */
a {
color: var(--editor-accent);

View File

@@ -15,6 +15,7 @@ import { Route as IndexRouteImport } from './routes/index'
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'
const RegisterRoute = RegisterRouteImport.update({
id: '/register',
@@ -46,11 +47,17 @@ const CoverCreateRoute = CoverCreateRouteImport.update({
path: '/cover/create',
getParentRoute: () => rootRouteImport,
} as any)
const CoverCoverIdRoute = CoverCoverIdRouteImport.update({
id: '/cover/$coverId',
path: '/cover/$coverId',
getParentRoute: () => rootRouteImport,
} as any)
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/login': typeof LoginRoute
'/register': typeof RegisterRoute
'/cover/$coverId': typeof CoverCoverIdRoute
'/cover/create': typeof CoverCreateRoute
'/templates/create': typeof TemplatesCreateRoute
'/templates': typeof TemplatesIndexRoute
@@ -59,6 +66,7 @@ export interface FileRoutesByTo {
'/': typeof IndexRoute
'/login': typeof LoginRoute
'/register': typeof RegisterRoute
'/cover/$coverId': typeof CoverCoverIdRoute
'/cover/create': typeof CoverCreateRoute
'/templates/create': typeof TemplatesCreateRoute
'/templates': typeof TemplatesIndexRoute
@@ -68,6 +76,7 @@ export interface FileRoutesById {
'/': typeof IndexRoute
'/login': typeof LoginRoute
'/register': typeof RegisterRoute
'/cover/$coverId': typeof CoverCoverIdRoute
'/cover/create': typeof CoverCreateRoute
'/templates/create': typeof TemplatesCreateRoute
'/templates/': typeof TemplatesIndexRoute
@@ -78,6 +87,7 @@ export interface FileRouteTypes {
| '/'
| '/login'
| '/register'
| '/cover/$coverId'
| '/cover/create'
| '/templates/create'
| '/templates'
@@ -86,6 +96,7 @@ export interface FileRouteTypes {
| '/'
| '/login'
| '/register'
| '/cover/$coverId'
| '/cover/create'
| '/templates/create'
| '/templates'
@@ -94,6 +105,7 @@ export interface FileRouteTypes {
| '/'
| '/login'
| '/register'
| '/cover/$coverId'
| '/cover/create'
| '/templates/create'
| '/templates/'
@@ -103,6 +115,7 @@ export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
LoginRoute: typeof LoginRoute
RegisterRoute: typeof RegisterRoute
CoverCoverIdRoute: typeof CoverCoverIdRoute
CoverCreateRoute: typeof CoverCreateRoute
TemplatesCreateRoute: typeof TemplatesCreateRoute
TemplatesIndexRoute: typeof TemplatesIndexRoute
@@ -152,6 +165,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof CoverCreateRouteImport
parentRoute: typeof rootRouteImport
}
'/cover/$coverId': {
id: '/cover/$coverId'
path: '/cover/$coverId'
fullPath: '/cover/$coverId'
preLoaderRoute: typeof CoverCoverIdRouteImport
parentRoute: typeof rootRouteImport
}
}
}
@@ -159,6 +179,7 @@ const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
LoginRoute: LoginRoute,
RegisterRoute: RegisterRoute,
CoverCoverIdRoute: CoverCoverIdRoute,
CoverCreateRoute: CoverCreateRoute,
TemplatesCreateRoute: TemplatesCreateRoute,
TemplatesIndexRoute: TemplatesIndexRoute,

View File

@@ -0,0 +1,48 @@
import renderQueryState from "@/components/RenderQueryState";
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 "../../editor.css";
export const Route = createFileRoute("/cover/$coverId")({
component: RouteComponent,
});
function RouteComponent() {
const { coverId } = Route.useParams();
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]",
},
});
return (
<Authorised>
<div>
<h1 className="text-2xl font-semibold">{cover.data?.cover.name || "Loading..."}</h1>
{/* edit buttons */}
</div>
<div className="mt-8 p-4 border rounded-md">
{coverState !== null ? (
coverState
) : (
<div
className="tiptap"
dangerouslySetInnerHTML={{ __html: cover.data?.cover.letter || "" }}
/>
)}
</div>
</Authorised>
);
}

View File

@@ -2,16 +2,31 @@ import { createFileRoute, Link } from "@tanstack/react-router";
import Authorised from "@/layouts/Authorised";
import { Button } from "@/components/ui/button";
import { Plus } from "lucide-react";
import { useQuery } from "@tanstack/react-query";
import requests from "@/lib/requests";
import type { CoverLetterPreview } from "@/types/api";
import renderQueryState from "@/components/RenderQueryState";
export const Route = createFileRoute("/")({
component: App,
});
function App() {
const letters = useQuery({
queryKey: ["cover_letters"],
queryFn: () => requests.get<{ covers: CoverLetterPreview[] }>("/cover", {}),
});
const lettersState = renderQueryState({
query: letters,
noFound: "cover letters",
});
return (
<Authorised>
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold text-primary">0 Cover letters</h1>
<h1 className="text-2xl font-bold text-primary">
{letters.data?.covers.length} Cover letters
</h1>
<Link to="/cover/create">
<Button icon={<Plus />} variant="secondary">
@@ -19,6 +34,21 @@ function App() {
</Button>
</Link>
</div>
<div className="flex flex-col gap-2 mt-4">
{lettersState !== null
? lettersState
: letters.data?.covers.map((l) => (
<Link
className="px-3 py-2 cursor-pointer rounded hover:bg-secondary"
to={"/cover/$coverId"}
params={{ coverId: l.id.toString() }}
key={l.id}
>
<p>{l.name}</p>
</Link>
))}
</div>
</Authorised>
);
}

View File

@@ -27,3 +27,15 @@ export interface Template {
template: string;
created_at: string;
}
// -------- Cover letters --------
export interface CoverLetterPreview {
id: number;
name: string;
}
export interface CoverLetter extends CoverLetterPreview {
user_id: number;
letter: string;
created_at: string;
}