diff --git a/app/src/components/ui/badge.tsx b/app/src/components/ui/badge.tsx index fd3a406..933164a 100644 --- a/app/src/components/ui/badge.tsx +++ b/app/src/components/ui/badge.tsx @@ -43,4 +43,5 @@ function Badge({ ) } +// eslint-disable-next-line react-refresh/only-export-components export { Badge, badgeVariants } diff --git a/app/src/components/ui/button-group.tsx b/app/src/components/ui/button-group.tsx index 8600af0..9c3ca29 100644 --- a/app/src/components/ui/button-group.tsx +++ b/app/src/components/ui/button-group.tsx @@ -79,5 +79,6 @@ export { ButtonGroup, ButtonGroupSeparator, ButtonGroupText, + // eslint-disable-next-line react-refresh/only-export-components buttonGroupVariants, } diff --git a/app/src/components/ui/button.tsx b/app/src/components/ui/button.tsx index 37a7d4b..aa07f35 100644 --- a/app/src/components/ui/button.tsx +++ b/app/src/components/ui/button.tsx @@ -59,4 +59,5 @@ function Button({ ) } +// eslint-disable-next-line react-refresh/only-export-components export { Button, buttonVariants } diff --git a/app/src/components/ui/form.tsx b/app/src/components/ui/form.tsx index 2b529e6..c5a6dab 100644 --- a/app/src/components/ui/form.tsx +++ b/app/src/components/ui/form.tsx @@ -156,6 +156,7 @@ function FormMessage({ className, ...props }: React.ComponentProps<"p">) { } export { + // eslint-disable-next-line react-refresh/only-export-components useFormField, Form, FormItem, diff --git a/app/src/components/ui/navigation-menu.tsx b/app/src/components/ui/navigation-menu.tsx index 1199945..be1cded 100644 --- a/app/src/components/ui/navigation-menu.tsx +++ b/app/src/components/ui/navigation-menu.tsx @@ -164,5 +164,6 @@ export { NavigationMenuLink, NavigationMenuIndicator, NavigationMenuViewport, + // eslint-disable-next-line react-refresh/only-export-components navigationMenuTriggerStyle, } diff --git a/app/src/components/ui/sidebar.tsx b/app/src/components/ui/sidebar.tsx index 30638ac..1fef2ba 100644 --- a/app/src/components/ui/sidebar.tsx +++ b/app/src/components/ui/sidebar.tsx @@ -607,9 +607,7 @@ function SidebarMenuSkeleton({ showIcon?: boolean }) { // Random width between 50 to 90%. - const width = React.useMemo(() => { - return `${Math.floor(Math.random() * 40) + 50}%` - }, []) + const [width] = React.useState(() => `${Math.floor(Math.random() * 40) + 50}%`) return (
; diff --git a/app/src/sections/CommunityForum.tsx b/app/src/sections/CommunityForum.tsx index beff79d..02aaa01 100644 --- a/app/src/sections/CommunityForum.tsx +++ b/app/src/sections/CommunityForum.tsx @@ -1,4 +1,6 @@ import { useState, useEffect, useCallback } from 'react'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; import { motion, AnimatePresence } from 'framer-motion'; import { ArrowLeft, @@ -24,6 +26,7 @@ import { } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { useAuth } from '@/contexts/AuthContext'; +import { createPostSchema, TITLE_MAX, CONTENT_MAX } from '@/lib/validators'; import { getPosts, getPost, @@ -95,11 +98,27 @@ export function CommunityForum({ onBack, onAuthClick }: CommunityForumProps) { // Create post state const [showCreateForm, setShowCreateForm] = useState(false); - const [newTitle, setNewTitle] = useState(''); - const [newContent, setNewContent] = useState(''); - const [newCategory, setNewCategory] = useState('general'); const [createSubmitting, setCreateSubmitting] = useState(false); + const { + register, + handleSubmit, + watch, + reset, + formState: { errors, isValid }, + } = useForm({ + resolver: zodResolver(createPostSchema), + defaultValues: { + title: '', + content: '', + category: 'general', + }, + mode: 'onChange', + }); + + const watchTitle = watch('title'); + const watchContent = watch('content'); + // Fetch posts const fetchPosts = useCallback(async () => { setLoading(true); @@ -130,23 +149,20 @@ export function CommunityForum({ onBack, onAuthClick }: CommunityForumProps) { } }; - const handleCreatePost = async () => { + const handleCreatePost = async (data: { title: string; content: string; category: string }) => { if (!user) { onAuthClick('login'); return; } - if (!newTitle.trim() || !newContent.trim()) return; setCreateSubmitting(true); try { await createPost({ - title: newTitle.trim(), - content: newContent.trim(), - category: newCategory + title: data.title.trim(), + content: data.content.trim(), + category: data.category, }); - setNewTitle(''); - setNewContent(''); - setNewCategory('general'); + reset(); setShowCreateForm(false); fetchPosts(); } catch (err) { @@ -488,34 +504,64 @@ export function CommunityForum({ onBack, onAuthClick }: CommunityForumProps) { exit={{ opacity: 0, height: 0 }} className="overflow-hidden mb-6" > -
+

Create New Post

-
- setNewTitle(e.target.value)} - placeholder="Post title..." - className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-white/30 focus:outline-none focus:border-[#a088ff]/50 mb-3" - /> +
+ +
+ {errors.title ? ( + {errors.title.message} + ) : ( + + )} + = TITLE_MAX ? 'text-red-400' : 'text-white/30'}`}> + {watchTitle.length} / {TITLE_MAX} + +
+
-