feat(app): add changelog list, byId and create page

This commit is contained in:
Lars Hampe 2024-10-03 22:47:47 +02:00
parent ee3faad379
commit b2ef80d58d
8 changed files with 265 additions and 18 deletions

View File

@ -13,13 +13,16 @@
"@boring.tools/schema": "workspace:*",
"@boring.tools/ui": "workspace:*",
"@clerk/clerk-react": "^5.9.4",
"@hookform/resolvers": "^3.9.0",
"@tanstack/react-query": "^5.59.0",
"@tanstack/react-router": "^1.58.15",
"axios": "^1.7.7",
"lucide-react": "^0.446.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"tailwindcss-animate": "^1.0.7"
"react-hook-form": "^7.53.0",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.23.8"
},
"devDependencies": {
"@eslint/js": "^9.9.0",

View File

@ -37,7 +37,6 @@ export const Navigation = () => {
to={route.to}
className="flex items-center gap-3 rounded-lg px-3 py-2 transition-all hover:text-primary"
activeProps={{ className: 'bg-muted text-primary' }}
activeOptions={{ exact: true }}
>
<route.icon className="h-4 w-4" />
{route.name}

View File

@ -28,7 +28,7 @@ export const useChangelogById = ({ id }: { id: string }) => {
return useQuery({
queryKey: ['changelogById', id],
queryFn: async (): Promise<ReadOnlyDict<Changelog>> =>
queryFn: async (): Promise<Readonly<Changelog>> =>
await queryFetch({
path: `changelog/${id}`,
method: 'get',
@ -44,7 +44,7 @@ export const useChangelogCreate = () => {
return useMutation({
mutationFn: async (
payload: ChangelogCreate,
): Promise<ReadonlySet<Changelog>> =>
): Promise<Readonly<Changelog>> =>
await queryFetch({
path: 'changelog',
data: payload,
@ -62,9 +62,7 @@ export const useChangelogRemove = () => {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async ({
id,
}: { id: string }): Promise<ReadOnlyDict<Changelog>> =>
mutationFn: async ({ id }: { id: string }): Promise<Readonly<Changelog>> =>
await queryFetch({
path: `changelog/${id}`,
method: 'delete',

View File

@ -19,6 +19,8 @@ import { Route as rootRoute } from './routes/__root'
const IndexLazyImport = createFileRoute('/')()
const UserIndexLazyImport = createFileRoute('/user/')()
const ChangelogIndexLazyImport = createFileRoute('/changelog/')()
const ChangelogCreateLazyImport = createFileRoute('/changelog/create')()
const ChangelogIdLazyImport = createFileRoute('/changelog/$id')()
// Create/Update Routes
@ -39,6 +41,18 @@ const ChangelogIndexLazyRoute = ChangelogIndexLazyImport.update({
import('./routes/changelog/index.lazy').then((d) => d.Route),
)
const ChangelogCreateLazyRoute = ChangelogCreateLazyImport.update({
path: '/changelog/create',
getParentRoute: () => rootRoute,
} as any).lazy(() =>
import('./routes/changelog/create.lazy').then((d) => d.Route),
)
const ChangelogIdLazyRoute = ChangelogIdLazyImport.update({
path: '/changelog/$id',
getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/changelog/$id.lazy').then((d) => d.Route))
// Populate the FileRoutesByPath interface
declare module '@tanstack/react-router' {
@ -50,6 +64,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexLazyImport
parentRoute: typeof rootRoute
}
'/changelog/$id': {
id: '/changelog/$id'
path: '/changelog/$id'
fullPath: '/changelog/$id'
preLoaderRoute: typeof ChangelogIdLazyImport
parentRoute: typeof rootRoute
}
'/changelog/create': {
id: '/changelog/create'
path: '/changelog/create'
fullPath: '/changelog/create'
preLoaderRoute: typeof ChangelogCreateLazyImport
parentRoute: typeof rootRoute
}
'/changelog/': {
id: '/changelog/'
path: '/changelog'
@ -71,12 +99,16 @@ declare module '@tanstack/react-router' {
export interface FileRoutesByFullPath {
'/': typeof IndexLazyRoute
'/changelog/$id': typeof ChangelogIdLazyRoute
'/changelog/create': typeof ChangelogCreateLazyRoute
'/changelog': typeof ChangelogIndexLazyRoute
'/user': typeof UserIndexLazyRoute
}
export interface FileRoutesByTo {
'/': typeof IndexLazyRoute
'/changelog/$id': typeof ChangelogIdLazyRoute
'/changelog/create': typeof ChangelogCreateLazyRoute
'/changelog': typeof ChangelogIndexLazyRoute
'/user': typeof UserIndexLazyRoute
}
@ -84,27 +116,44 @@ export interface FileRoutesByTo {
export interface FileRoutesById {
__root__: typeof rootRoute
'/': typeof IndexLazyRoute
'/changelog/$id': typeof ChangelogIdLazyRoute
'/changelog/create': typeof ChangelogCreateLazyRoute
'/changelog/': typeof ChangelogIndexLazyRoute
'/user/': typeof UserIndexLazyRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/changelog' | '/user'
fullPaths:
| '/'
| '/changelog/$id'
| '/changelog/create'
| '/changelog'
| '/user'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/changelog' | '/user'
id: '__root__' | '/' | '/changelog/' | '/user/'
to: '/' | '/changelog/$id' | '/changelog/create' | '/changelog' | '/user'
id:
| '__root__'
| '/'
| '/changelog/$id'
| '/changelog/create'
| '/changelog/'
| '/user/'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexLazyRoute: typeof IndexLazyRoute
ChangelogIdLazyRoute: typeof ChangelogIdLazyRoute
ChangelogCreateLazyRoute: typeof ChangelogCreateLazyRoute
ChangelogIndexLazyRoute: typeof ChangelogIndexLazyRoute
UserIndexLazyRoute: typeof UserIndexLazyRoute
}
const rootRouteChildren: RootRouteChildren = {
IndexLazyRoute: IndexLazyRoute,
ChangelogIdLazyRoute: ChangelogIdLazyRoute,
ChangelogCreateLazyRoute: ChangelogCreateLazyRoute,
ChangelogIndexLazyRoute: ChangelogIndexLazyRoute,
UserIndexLazyRoute: UserIndexLazyRoute,
}
@ -122,6 +171,8 @@ export const routeTree = rootRoute
"filePath": "__root.tsx",
"children": [
"/",
"/changelog/$id",
"/changelog/create",
"/changelog/",
"/user/"
]
@ -129,6 +180,12 @@ export const routeTree = rootRoute
"/": {
"filePath": "index.lazy.tsx"
},
"/changelog/$id": {
"filePath": "changelog/$id.lazy.tsx"
},
"/changelog/create": {
"filePath": "changelog/create.lazy.tsx"
},
"/changelog/": {
"filePath": "changelog/index.lazy.tsx"
},

View File

@ -0,0 +1,33 @@
import { Button } from '@boring.tools/ui'
import { createLazyFileRoute } from '@tanstack/react-router'
import { useChangelogById } from '../../hooks/useChangelog'
const Component = () => {
const { id } = Route.useParams()
const { data, error, isPending, refetch } = useChangelogById({ id })
if (error) {
return (
<div className="flex items-center justify-center mt-32 flex-col">
<h1 className="text-3xl">Changelogs</h1>
<p>Please try again later</p>
<Button onClick={() => refetch()}>Retry</Button>
</div>
)
}
return (
<div className="flex flex-col gap-5">
{!isPending && data && (
<div>
<h1 className="text-3xl">{data.title}</h1>
</div>
)}
</div>
)
}
export const Route = createLazyFileRoute('/changelog/$id')({
component: Component,
})

View File

@ -0,0 +1,117 @@
import { ChangelogCreateInput } from '@boring.tools/schema'
import {
Button,
Checkbox,
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
Input,
Textarea,
} from '@boring.tools/ui'
import { zodResolver } from '@hookform/resolvers/zod'
import { createLazyFileRoute } from '@tanstack/react-router'
import { useNavigate } from '@tanstack/react-router'
import { useForm } from 'react-hook-form'
import type { z } from 'zod'
import { useChangelogCreate } from '../../hooks/useChangelog'
const Component = () => {
const navigate = useNavigate({ from: '/changelog/create' })
const changelogCreate = useChangelogCreate()
const form = useForm<z.infer<typeof ChangelogCreateInput>>({
resolver: zodResolver(ChangelogCreateInput),
defaultValues: {
title: '',
description: '',
isSemver: true,
},
})
const onSubmit = (values: z.infer<typeof ChangelogCreateInput>) => {
changelogCreate.mutate(values, {
onSuccess(data) {
navigate({ to: '/changelog/$id', params: { id: data.id } })
},
})
}
return (
<div className="flex flex-col gap-5">
<h1 className="text-3xl">New changelog</h1>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-8 max-w-screen-md"
>
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Title</FormLabel>
<FormControl>
<Input placeholder="My changelog" {...field} autoFocus />
</FormControl>{' '}
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Description</FormLabel>
<FormControl>
<Textarea
placeholder="Some details about the changelog..."
{...field}
/>
</FormControl>{' '}
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="isSemver"
render={({ field }) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md ">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel>Using Semver</FormLabel>
<FormDescription>
If this changelog is following the{' '}
<a
href="https://semver.org/lang/de/"
className="text-emerald-700"
>
semantic versioning?
</a>
</FormDescription>
</div>
</FormItem>
)}
/>
<Button type="submit">Create</Button>
</form>
</Form>
</div>
)
}
export const Route = createLazyFileRoute('/changelog/create')({
component: Component,
})

View File

@ -1,27 +1,67 @@
import { createLazyFileRoute } from '@tanstack/react-router'
import {
Button,
Card,
CardContent,
CardHeader,
CardTitle,
} from '@boring.tools/ui'
import { Link, createLazyFileRoute } from '@tanstack/react-router'
import { PlusCircleIcon } from 'lucide-react'
import { useChangelogList } from '../../hooks/useChangelog'
const Component = () => {
const { data, error, isPending } = useChangelogList()
const { data, error, isPending, refetch } = useChangelogList()
if (error) {
return (
<div className="flex items-center justify-center mt-32 flex-col">
<h1 className="text-3xl">Changelogs</h1>
<p>Please try again later</p>
<Button onClick={() => refetch()}>Retry</Button>
</div>
)
}
return (
<div>
<div className="flex flex-col gap-5">
<h1 className="text-3xl">Changelogs</h1>
{!isPending &&
data &&
data.map((changelog) => {
return <div key={changelog.id}>{changelog.title}</div>
})}
<div className="flex gap-10 w-full">
{!isPending &&
data &&
data.map((changelog) => {
return (
<Link
to="/changelog/$id"
params={{ id: changelog.id }}
key={changelog.id}
>
<Card className="max-w-56 min-w-56 w-full h-36 hover:border-emerald-700 transition">
<CardHeader className="flex items-center justify-center">
<CardTitle>{changelog.title}</CardTitle>
</CardHeader>
<CardContent className="flex items-center justify-center flex-col">
<span>Versions: {changelog.computed.versionCount}</span>
<span>Commits: {changelog.computed.commitCount}</span>
</CardContent>
</Card>
</Link>
)
})}
<Link to="/changelog/create">
<Card className="max-w-56 min-w-56 w-full h-36 hover:border-emerald-700 transition">
<CardHeader className="flex items-center justify-center">
<CardTitle>New Changelog</CardTitle>
</CardHeader>
<CardContent className="flex items-center justify-center">
<PlusCircleIcon strokeWidth={1.5} className="w-10 h-10" />
</CardContent>
</Card>
</Link>
</div>
</div>
)
}

BIN
bun.lockb

Binary file not shown.