feat(app): extend version create/update with status and releasedAt date
This commit is contained in:
parent
5fc2592bd5
commit
4ac4304862
@ -131,3 +131,43 @@ export const useChangelogVersionCreate = () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useChangelogVersionById = ({ id }: { id: string }) => {
|
||||||
|
const { getToken } = useAuth()
|
||||||
|
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['changelogVersionById', id],
|
||||||
|
queryFn: async (): Promise<Readonly<Version>> =>
|
||||||
|
await queryFetch({
|
||||||
|
path: `changelog/version/${id}`,
|
||||||
|
method: 'get',
|
||||||
|
token: await getToken(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useChangelogVersionUpdate = () => {
|
||||||
|
const { getToken } = useAuth()
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({
|
||||||
|
id,
|
||||||
|
payload,
|
||||||
|
}: {
|
||||||
|
id: string
|
||||||
|
payload: VersionUpdate
|
||||||
|
}): Promise<Readonly<Version>> =>
|
||||||
|
await queryFetch({
|
||||||
|
path: `changelog/version/${id}`,
|
||||||
|
data: payload,
|
||||||
|
method: 'put',
|
||||||
|
token: await getToken(),
|
||||||
|
}),
|
||||||
|
onSuccess: (data) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ['changelogById', data.id],
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -7,21 +7,9 @@ import {
|
|||||||
} from '@boring.tools/ui'
|
} from '@boring.tools/ui'
|
||||||
import { Link, createLazyFileRoute } from '@tanstack/react-router'
|
import { Link, createLazyFileRoute } from '@tanstack/react-router'
|
||||||
import { PlusCircleIcon } from 'lucide-react'
|
import { PlusCircleIcon } from 'lucide-react'
|
||||||
|
import { VersionStatus } from '../components/Changelog/VersionStatus'
|
||||||
import { useChangelogById } from '../hooks/useChangelog'
|
import { useChangelogById } from '../hooks/useChangelog'
|
||||||
|
|
||||||
const VersionStatus = ({ status }: { status: string }) => {
|
|
||||||
switch (status) {
|
|
||||||
case 'draft':
|
|
||||||
return <div className="w-3 h-3 rounded-full bg-amber-600" />
|
|
||||||
case 'published':
|
|
||||||
return <div className="w-3 h-3 rounded-full bg-emerald-600" />
|
|
||||||
case 'review':
|
|
||||||
return <div className="w-3 h-3 rounded-full bg-sky-600" />
|
|
||||||
default:
|
|
||||||
return <div className="w-3 h-3 rounded-full bg-neutral-600" />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Component = () => {
|
const Component = () => {
|
||||||
const { id } = Route.useParams()
|
const { id } = Route.useParams()
|
||||||
const { data, isPending } = useChangelogById({ id })
|
const { data, isPending } = useChangelogById({ id })
|
||||||
|
@ -1,10 +1,80 @@
|
|||||||
import { Button } from '@boring.tools/ui'
|
import { VersionUpdateInput } from '@boring.tools/schema'
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import {
|
||||||
import { useChangelogById } from '../hooks/useChangelog'
|
Button,
|
||||||
|
Calendar,
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
cn,
|
||||||
|
} from '@boring.tools/ui'
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod'
|
||||||
|
import {
|
||||||
|
BlockTypeSelect,
|
||||||
|
BoldItalicUnderlineToggles,
|
||||||
|
ListsToggle,
|
||||||
|
MDXEditor,
|
||||||
|
type MDXEditorMethods,
|
||||||
|
UndoRedo,
|
||||||
|
headingsPlugin,
|
||||||
|
listsPlugin,
|
||||||
|
quotePlugin,
|
||||||
|
thematicBreakPlugin,
|
||||||
|
toolbarPlugin,
|
||||||
|
} from '@mdxeditor/editor'
|
||||||
|
import { createFileRoute, useNavigate } from '@tanstack/react-router'
|
||||||
|
import { useForm } from 'react-hook-form'
|
||||||
|
import type { z } from 'zod'
|
||||||
|
import {
|
||||||
|
useChangelogVersionById,
|
||||||
|
useChangelogVersionUpdate,
|
||||||
|
} from '../hooks/useChangelog'
|
||||||
|
import '@mdxeditor/editor/style.css'
|
||||||
|
import { format } from 'date-fns'
|
||||||
|
import { CalendarIcon } from 'lucide-react'
|
||||||
|
import { useEffect, useRef } from 'react'
|
||||||
|
import { VersionStatus } from '../components/Changelog/VersionStatus'
|
||||||
|
|
||||||
const Component = () => {
|
const Component = () => {
|
||||||
const { id } = Route.useParams()
|
const { id, versionId } = Route.useParams()
|
||||||
const { data, error, isPending, refetch } = useChangelogById({ id })
|
const mdxEditorRef = useRef<MDXEditorMethods>(null)
|
||||||
|
const navigate = useNavigate({ from: `/changelog/${id}/versionCreate` })
|
||||||
|
const versionUpdate = useChangelogVersionUpdate()
|
||||||
|
const { data, error, isPending, refetch } = useChangelogVersionById({
|
||||||
|
id: versionId,
|
||||||
|
})
|
||||||
|
const form = useForm<z.infer<typeof VersionUpdateInput>>({
|
||||||
|
resolver: zodResolver(VersionUpdateInput),
|
||||||
|
defaultValues: data,
|
||||||
|
})
|
||||||
|
|
||||||
|
const onSubmit = (values: z.infer<typeof VersionUpdateInput>) => {
|
||||||
|
versionUpdate.mutate(
|
||||||
|
{ id: versionId, payload: values },
|
||||||
|
{
|
||||||
|
onSuccess() {
|
||||||
|
navigate({ to: '/changelog/$id', params: { id } })
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data) {
|
||||||
|
mdxEditorRef.current?.setMarkdown(data.markdown)
|
||||||
|
form.reset(data)
|
||||||
|
}
|
||||||
|
}, [data, form.reset])
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
@ -19,7 +89,153 @@ const Component = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-5">
|
<div className="flex flex-col gap-5">
|
||||||
{!isPending && data && <div>version page</div>}
|
<hr />
|
||||||
|
{!isPending && data && (
|
||||||
|
<div>
|
||||||
|
<h1 className="text-xl mb-2">Version: {data.version}</h1>
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
className="space-y-8 max-w-screen-md"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="markdown"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Notes</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<MDXEditor
|
||||||
|
className="dark-theme"
|
||||||
|
contentEditableClassName="prose dark:prose-invert max-w-none"
|
||||||
|
markdown={''}
|
||||||
|
ref={mdxEditorRef}
|
||||||
|
onChange={field.onChange}
|
||||||
|
onBlur={field.onBlur}
|
||||||
|
plugins={[
|
||||||
|
headingsPlugin(),
|
||||||
|
listsPlugin(),
|
||||||
|
thematicBreakPlugin(),
|
||||||
|
quotePlugin(),
|
||||||
|
|
||||||
|
toolbarPlugin({
|
||||||
|
toolbarContents: () => (
|
||||||
|
<>
|
||||||
|
<BlockTypeSelect />
|
||||||
|
<BoldItalicUnderlineToggles />
|
||||||
|
<ListsToggle />
|
||||||
|
<UndoRedo />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</FormControl>{' '}
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex gap-5 items-center">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="status"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Status</FormLabel>
|
||||||
|
<Select
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
defaultValue={field.value}
|
||||||
|
value={field.value}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select your version status" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="draft">
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
<VersionStatus status={'draft'} />
|
||||||
|
<span>Draft</span>
|
||||||
|
</div>
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="review">
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
<VersionStatus status={'review'} />
|
||||||
|
<span>Review</span>
|
||||||
|
</div>
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="published">
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
<VersionStatus status={'published'} />
|
||||||
|
<span>Published</span>
|
||||||
|
</div>
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="releasedAt"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-col">
|
||||||
|
<FormLabel className="mb-2">Released at</FormLabel>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<FormControl>
|
||||||
|
<Button
|
||||||
|
variant={'outline'}
|
||||||
|
size={'lg'}
|
||||||
|
className={cn(
|
||||||
|
'w-[240px] pl-3 text-left font-normal',
|
||||||
|
!field.value && 'text-muted-foreground',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{field.value ? (
|
||||||
|
format(field.value, 'PPP')
|
||||||
|
) : (
|
||||||
|
<span>Pick a date</span>
|
||||||
|
)}
|
||||||
|
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</FormControl>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0" align="start">
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={field.value as Date}
|
||||||
|
onSelect={(date) => field.onChange(date)}
|
||||||
|
weekStartsOn={1}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-5">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant={'ghost'}
|
||||||
|
onClick={() =>
|
||||||
|
navigate({ to: '/changelog/$id', params: { id } })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button type="submit">Update</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { VersionCreateInput } from '@boring.tools/schema'
|
import { VersionCreateInput } from '@boring.tools/schema'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
Calendar,
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormField,
|
FormField,
|
||||||
@ -8,6 +9,15 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
Input,
|
Input,
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
cn,
|
||||||
} from '@boring.tools/ui'
|
} from '@boring.tools/ui'
|
||||||
import { zodResolver } from '@hookform/resolvers/zod'
|
import { zodResolver } from '@hookform/resolvers/zod'
|
||||||
import {
|
import {
|
||||||
@ -28,6 +38,9 @@ import { useForm } from 'react-hook-form'
|
|||||||
import type { z } from 'zod'
|
import type { z } from 'zod'
|
||||||
import { useChangelogVersionCreate } from '../hooks/useChangelog'
|
import { useChangelogVersionCreate } from '../hooks/useChangelog'
|
||||||
import '@mdxeditor/editor/style.css'
|
import '@mdxeditor/editor/style.css'
|
||||||
|
import { format } from 'date-fns'
|
||||||
|
import { CalendarIcon } from 'lucide-react'
|
||||||
|
import { VersionStatus } from '../components/Changelog/VersionStatus'
|
||||||
|
|
||||||
const Component = () => {
|
const Component = () => {
|
||||||
const { id } = Route.useParams()
|
const { id } = Route.useParams()
|
||||||
@ -39,6 +52,7 @@ const Component = () => {
|
|||||||
changelogId: id,
|
changelogId: id,
|
||||||
version: '',
|
version: '',
|
||||||
markdown: '',
|
markdown: '',
|
||||||
|
status: 'draft',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -52,7 +66,8 @@ const Component = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-5">
|
<div className="flex flex-col gap-5">
|
||||||
<h1 className="text-3xl">New version</h1>
|
<hr />
|
||||||
|
<h1 className="text-xl mb-2">New version</h1>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
@ -108,7 +123,100 @@ const Component = () => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button type="submit">Create</Button>
|
<div className="flex gap-5 items-center">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="status"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Status</FormLabel>
|
||||||
|
<Select
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
defaultValue={field.value}
|
||||||
|
value={field.value}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select your version status" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="draft">
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
<VersionStatus status={'draft'} />
|
||||||
|
<span>Draft</span>
|
||||||
|
</div>
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="review">
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
<VersionStatus status={'review'} />
|
||||||
|
<span>Review</span>
|
||||||
|
</div>
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="published">
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
<VersionStatus status={'published'} />
|
||||||
|
<span>Published</span>
|
||||||
|
</div>
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="releasedAt"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-col">
|
||||||
|
<FormLabel className="mb-2">Released at</FormLabel>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<FormControl>
|
||||||
|
<Button
|
||||||
|
variant={'outline'}
|
||||||
|
size={'lg'}
|
||||||
|
className={cn(
|
||||||
|
'w-[240px] pl-3 text-left font-normal',
|
||||||
|
!field.value && 'text-muted-foreground',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{field.value ? (
|
||||||
|
format(field.value, 'PPP')
|
||||||
|
) : (
|
||||||
|
<span>Pick a date</span>
|
||||||
|
)}
|
||||||
|
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</FormControl>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0" align="start">
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={field.value as Date}
|
||||||
|
onSelect={(date) => field.onChange(date)}
|
||||||
|
weekStartsOn={1}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-5">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant={'ghost'}
|
||||||
|
onClick={() => navigate({ to: '/changelog/$id', params: { id } })}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button type="submit">Create</Button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user