This commit is contained in:
@@ -4,6 +4,7 @@ import { saveArticle } from '@/lib/feature/article/article.external';
|
||||
import { Button } from '@/ui/components/shadcn/button';
|
||||
import {
|
||||
Field,
|
||||
FieldDescription,
|
||||
FieldError,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
@@ -14,19 +15,21 @@ import {
|
||||
InputGroupTextarea,
|
||||
} from '@/ui/components/shadcn/input-group';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useEffect } from 'react';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { Controller, useForm, useWatch } from 'react-hook-form';
|
||||
import slugify from 'slugify';
|
||||
import { toast } from 'sonner';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const CreateArticleForm = () => {
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const formSchema = z.object({
|
||||
title: z.string().min(3).max(255),
|
||||
slug: z.string().min(3),
|
||||
description: z.string().min(10),
|
||||
coverImageUrl: z.string().url(),
|
||||
content: z.string().min(10),
|
||||
coverImageUrl: z.url(),
|
||||
content: z.instanceof(File),
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
@@ -36,7 +39,7 @@ export const CreateArticleForm = () => {
|
||||
slug: '',
|
||||
description: '',
|
||||
coverImageUrl: '',
|
||||
content: '',
|
||||
content: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -50,27 +53,41 @@ export const CreateArticleForm = () => {
|
||||
form.setValue('slug', slugify(title).toLowerCase());
|
||||
}, [form, title]);
|
||||
|
||||
async function onSubmit(data: z.infer<typeof formSchema>) {
|
||||
try {
|
||||
const result = await saveArticle(data);
|
||||
toast.success('Article created successfully!', {
|
||||
description: `Article "${result.title}" has been created.`,
|
||||
position: 'bottom-right',
|
||||
});
|
||||
form.reset();
|
||||
} catch (error) {
|
||||
toast.error('Failed to create article', {
|
||||
description:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'An error occurred',
|
||||
position: 'bottom-right',
|
||||
});
|
||||
}
|
||||
}
|
||||
const handleFormSubmit = useCallback(
|
||||
async (data: z.infer<typeof formSchema>) => {
|
||||
try {
|
||||
const result = await saveArticle({
|
||||
...data,
|
||||
content: await data.content.text(),
|
||||
});
|
||||
toast.success('Article created successfully!', {
|
||||
description: `Article "${result.title}" has been created.`,
|
||||
position: 'bottom-right',
|
||||
});
|
||||
form.reset();
|
||||
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = '';
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('Failed to create article', {
|
||||
description:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'An error occurred',
|
||||
position: 'bottom-right',
|
||||
});
|
||||
}
|
||||
},
|
||||
[form]
|
||||
);
|
||||
|
||||
return (
|
||||
<form id='form-create-article' onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<form
|
||||
id='form-create-article'
|
||||
// eslint-disable-next-line react-hooks/refs
|
||||
onSubmit={form.handleSubmit(handleFormSubmit)}
|
||||
>
|
||||
<FieldGroup>
|
||||
<Controller
|
||||
name='title'
|
||||
@@ -166,18 +183,24 @@ export const CreateArticleForm = () => {
|
||||
render={({ field, fieldState }) => (
|
||||
<Field data-invalid={fieldState.invalid}>
|
||||
<FieldLabel htmlFor='form-create-article-content'>
|
||||
Content
|
||||
Content (Markdown File)
|
||||
</FieldLabel>
|
||||
<InputGroup>
|
||||
<InputGroupTextarea
|
||||
{...field}
|
||||
id='form-create-article-content'
|
||||
placeholder='Write your article content here...'
|
||||
rows={12}
|
||||
className='min-h-48 resize-none font-mono text-sm'
|
||||
aria-invalid={fieldState.invalid}
|
||||
/>
|
||||
</InputGroup>
|
||||
<Input
|
||||
ref={fileInputRef}
|
||||
id='form-create-article-content'
|
||||
type='file'
|
||||
accept='.md,.markdown'
|
||||
aria-invalid={fieldState.invalid}
|
||||
onChange={(event) =>
|
||||
field.onChange(
|
||||
event.target.files &&
|
||||
event.target.files[0]
|
||||
)
|
||||
}
|
||||
/>
|
||||
<FieldDescription>
|
||||
Select your article.
|
||||
</FieldDescription>
|
||||
{fieldState.invalid && (
|
||||
<FieldError errors={[fieldState.error]} />
|
||||
)}
|
||||
|
||||
@@ -3,8 +3,8 @@ import { BlankUserButton } from '@/ui/components/internal/user-profile/user-prof
|
||||
|
||||
export const StaticDesktopHeader = () => {
|
||||
const links = [
|
||||
{ href: '/home', label: 'Home' },
|
||||
{ href: '/about', label: 'About' },
|
||||
{ href: '/home', label: 'Home', condition: true },
|
||||
{ href: '/about', label: 'About', condition: true },
|
||||
];
|
||||
|
||||
return <BaseDesktopHeader links={links} userButton={<BlankUserButton />} />;
|
||||
|
||||
1441
src/ui/components/shadcn/file-upload.tsx
Normal file
1441
src/ui/components/shadcn/file-upload.tsx
Normal file
File diff suppressed because it is too large
Load Diff
14
src/ui/hooks/use-as-ref.ts
Normal file
14
src/ui/hooks/use-as-ref.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { useIsomorphicLayoutEffect } from '@/ui/hooks/use-isomorphic-layout-effect';
|
||||
import * as React from 'react';
|
||||
|
||||
function useAsRef<T>(props: T) {
|
||||
const ref = React.useRef<T>(props);
|
||||
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
ref.current = props;
|
||||
});
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
export { useAsRef };
|
||||
6
src/ui/hooks/use-isomorphic-layout-effect.ts
Normal file
6
src/ui/hooks/use-isomorphic-layout-effect.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import * as React from 'react';
|
||||
|
||||
const useIsomorphicLayoutEffect =
|
||||
typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
|
||||
|
||||
export { useIsomorphicLayoutEffect };
|
||||
13
src/ui/hooks/use-lazy-ref.ts
Normal file
13
src/ui/hooks/use-lazy-ref.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import * as React from 'react';
|
||||
|
||||
function useLazyRef<T>(fn: () => T) {
|
||||
const ref = React.useRef<T | null>(null);
|
||||
|
||||
if (ref.current === null) {
|
||||
ref.current = fn();
|
||||
}
|
||||
|
||||
return ref as React.RefObject<T>;
|
||||
}
|
||||
|
||||
export { useLazyRef };
|
||||
Reference in New Issue
Block a user