From 6c0f9d26d6b850583b7dc00d5850492030fc4b98 Mon Sep 17 00:00:00 2001 From: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Date: Fri, 6 Mar 2026 20:51:39 +0100 Subject: [PATCH 1/4] UI fixes (#451) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form --- .../hackathons/submissions/SubmissionForm.tsx | 20 ++++++++++--------- .../hackathons/submissions/submissionTab.tsx | 3 ++- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/components/hackathons/submissions/SubmissionForm.tsx b/components/hackathons/submissions/SubmissionForm.tsx index abb2e0e4f..3c48a5347 100644 --- a/components/hackathons/submissions/SubmissionForm.tsx +++ b/components/hackathons/submissions/SubmissionForm.tsx @@ -1081,15 +1081,17 @@ const SubmissionFormContent: React.FC = ({
- + {process.env.NODE_ENV === 'development' && ( + + )}
= ({ {!isLoadingMySubmission && !mySubmission && isAuthenticated && - isRegistered && ( + isRegistered && + status !== 'upcoming' && (

You haven't submitted a project yet. From ba798d79bb3b9906840848feb14df052da6d732b Mon Sep 17 00:00:00 2001 From: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Date: Sat, 7 Mar 2026 16:32:42 +0100 Subject: [PATCH 2/4] UI fixes (#454) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * fix: fix hackathon submission and participant page * fix: fix hackathon submission and participant page --- .../hackathons/[slug]/HackathonPageClient.tsx | 38 ++- .../hackathons/[slug]/submit/page.tsx | 120 +++++++++ components/hackathons/hackathonBanner.tsx | 6 +- components/hackathons/hackathonStickyCard.tsx | 25 +- .../hackathons/submissions/SubmissionForm.tsx | 43 +++- .../hackathons/submissions/submissionCard.tsx | 62 ++--- .../hackathons/submissions/submissionTab.tsx | 53 ++-- .../settings/GeneralSettingsTab.tsx | 43 +++- components/stepper/Stepper.tsx | 40 ++- hooks/hackathon/use-participants.ts | 230 ++++++++++++------ lib/providers/hackathonProvider.tsx | 2 - 11 files changed, 478 insertions(+), 184 deletions(-) create mode 100644 app/(landing)/hackathons/[slug]/submit/page.tsx diff --git a/app/(landing)/hackathons/[slug]/HackathonPageClient.tsx b/app/(landing)/hackathons/[slug]/HackathonPageClient.tsx index 0c4d1adf3..3f38dce02 100644 --- a/app/(landing)/hackathons/[slug]/HackathonPageClient.tsx +++ b/app/(landing)/hackathons/[slug]/HackathonPageClient.tsx @@ -5,6 +5,8 @@ import { useRouter, useSearchParams, useParams } from 'next/navigation'; import { useHackathonData } from '@/lib/providers/hackathonProvider'; import { useRegisterHackathon } from '@/hooks/hackathon/use-register-hackathon'; import { useLeaveHackathon } from '@/hooks/hackathon/use-leave-hackathon'; +import { useSubmission } from '@/hooks/hackathon/use-submission'; +import { useAuthStatus } from '@/hooks/use-auth'; import { RegisterHackathonModal } from '@/components/hackathons/overview/RegisterHackathonModal'; import { HackathonBanner } from '@/components/hackathons/hackathonBanner'; import { HackathonNavTabs } from '@/components/hackathons/hackathonNavTabs'; @@ -45,6 +47,13 @@ export default function HackathonPageClient() { refreshCurrentHackathon, } = useHackathonData(); + const { isAuthenticated } = useAuthStatus(); + + const { submission: mySubmission } = useSubmission({ + hackathonSlugOrId: currentHackathon?.id || '', + autoFetch: !!currentHackathon && isAuthenticated, + }); + const timeline_Events = useTimelineEvents(currentHackathon, { includeEndDate: false, dateFormat: { month: 'short', day: 'numeric', year: 'numeric' }, @@ -232,7 +241,7 @@ export default function HackathonPageClient() { // Registration status const { isRegistered, - hasSubmitted, + hasSubmitted: participantHasSubmitted, setParticipant, register: registerForHackathon, } = useRegisterHackathon({ @@ -246,6 +255,8 @@ export default function HackathonPageClient() { organizationId: undefined, }); + const hasSubmitted = !!mySubmission || participantHasSubmitted; + // Leave hackathon functionality const { isLeaving, leave: leaveHackathon } = useLeaveHackathon({ hackathonSlugOrId: currentHackathon?.id || '', @@ -296,7 +307,7 @@ export default function HackathonPageClient() { }; const handleSubmitClick = () => { - router.push('?tab=submission'); + router.push(`/hackathons/${currentHackathon?.slug}/submit`); }; const handleViewSubmissionClick = () => { @@ -308,10 +319,25 @@ export default function HackathonPageClient() { }; // Set current hackathon on mount + const [isInitializing, setIsInitializing] = useState(true); + useEffect(() => { - if (hackathonId) { - setCurrentHackathon(hackathonId); - } + let isMounted = true; + + const initHackathon = async () => { + if (hackathonId) { + await setCurrentHackathon(hackathonId); + } + if (isMounted) { + setIsInitializing(false); + } + }; + + initHackathon(); + + return () => { + isMounted = false; + }; }, [hackathonId, setCurrentHackathon]); // Handle tab changes from URL @@ -349,7 +375,7 @@ export default function HackathonPageClient() { }; // Loading state - if (loading) { + if (loading || isInitializing) { return ; } diff --git a/app/(landing)/hackathons/[slug]/submit/page.tsx b/app/(landing)/hackathons/[slug]/submit/page.tsx new file mode 100644 index 000000000..f5ca054e2 --- /dev/null +++ b/app/(landing)/hackathons/[slug]/submit/page.tsx @@ -0,0 +1,120 @@ +'use client'; + +import { use, useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import { useHackathonData } from '@/lib/providers/hackathonProvider'; +import { useAuthStatus } from '@/hooks/use-auth'; +import { useSubmission } from '@/hooks/hackathon/use-submission'; +import { SubmissionFormContent } from '@/components/hackathons/submissions/SubmissionForm'; +import LoadingScreen from '@/features/projects/components/CreateProjectModal/LoadingScreen'; +import { Button } from '@/components/ui/button'; +import { ArrowLeft } from 'lucide-react'; +import { toast } from 'sonner'; + +export default function SubmitProjectPage({ + params, +}: { + params: Promise<{ slug: string }>; +}) { + const router = useRouter(); + const { isAuthenticated, isLoading } = useAuthStatus(); + + const resolvedParams = use(params); + const hackathonSlug = resolvedParams.slug; + + const { + currentHackathon, + loading: hackathonLoading, + setCurrentHackathon, + } = useHackathonData(); + + useEffect(() => { + if (hackathonSlug) { + setCurrentHackathon(hackathonSlug); + } + }, [hackathonSlug, setCurrentHackathon]); + + const hackathonId = currentHackathon?.id || ''; + const orgId = currentHackathon?.organizationId || undefined; + + const { + submission: mySubmission, + isFetching: isLoadingMySubmission, + fetchMySubmission, + } = useSubmission({ + hackathonSlugOrId: hackathonId || '', + autoFetch: isAuthenticated && !!hackathonId, + }); + + // Authentication check + useEffect(() => { + if (!isLoading && !isAuthenticated) { + toast.error('You must be logged in to submit a project'); + router.push( + `/auth?mode=signin&callbackUrl=/hackathons/${hackathonSlug}/submit` + ); + } + }, [isAuthenticated, isLoading, router, hackathonSlug]); + + const handleClose = () => { + router.push(`/hackathons/${hackathonSlug}`); + }; + + const handleSuccess = () => { + fetchMySubmission(); + toast.success( + mySubmission + ? 'Submission updated successfully!' + : 'Project submitted successfully!' + ); + router.push(`/hackathons/${hackathonSlug}?tab=submission`); + }; + + if ( + isLoading || + hackathonLoading || + isLoadingMySubmission || + !currentHackathon + ) { + return ; + } + + return ( +

+
+ + +
+ +
+
+
+ ); +} diff --git a/components/hackathons/hackathonBanner.tsx b/components/hackathons/hackathonBanner.tsx index dcb8ccd55..f08d27b0a 100644 --- a/components/hackathons/hackathonBanner.tsx +++ b/components/hackathons/hackathonBanner.tsx @@ -315,14 +315,14 @@ export function HackathonBanner({ {status === 'ongoing' && isRegistered && hasSubmitted && - onViewSubmissionClick && ( + onSubmitClick && ( )} diff --git a/components/hackathons/hackathonStickyCard.tsx b/components/hackathons/hackathonStickyCard.tsx index 470562408..9a4a9c2a0 100644 --- a/components/hackathons/hackathonStickyCard.tsx +++ b/components/hackathons/hackathonStickyCard.tsx @@ -238,20 +238,17 @@ export function HackathonStickyCard(props: HackathonStickyCardProps) { )} - {/* View Submission Button */} - {status === 'ongoing' && - isRegistered && - hasSubmitted && - onViewSubmissionClick && ( - - )} + {/* Edit / View Submission Button */} + {status === 'ongoing' && isRegistered && hasSubmitted && ( + + )} {/* Find Team Button */} {status === 'ongoing' && diff --git a/components/hackathons/submissions/SubmissionForm.tsx b/components/hackathons/submissions/SubmissionForm.tsx index 3c48a5347..1604189b6 100644 --- a/components/hackathons/submissions/SubmissionForm.tsx +++ b/components/hackathons/submissions/SubmissionForm.tsx @@ -125,6 +125,7 @@ interface SubmissionFormContentProps { initialData?: Partial; submissionId?: string; onSuccess?: () => void; + onClose?: () => void; } const INITIAL_STEPS: Step[] = [ @@ -198,8 +199,19 @@ const SubmissionFormContent: React.FC = ({ initialData, submissionId, onSuccess, + onClose, }) => { - const { collapse, isExpanded: open } = useExpandableScreen(); + // Use context carefully since it might not be available when used standalone + let collapse = () => {}; + let open = true; + try { + const expandableCtx = useExpandableScreen(); + collapse = expandableCtx.collapse; + open = expandableCtx.isExpanded; + } catch (e) { + // Standalone mode, not in ExpandableScreen + } + const { currentHackathon } = useHackathonData(); const { user } = useAuthStatus(); @@ -773,7 +785,11 @@ const SubmissionFormContent: React.FC = ({ } else { await create(submissionData); } - collapse(); + if (onClose) { + onClose(); + } else { + collapse(); + } onSuccess?.(); } catch { // Error handled in hook @@ -1503,22 +1519,31 @@ const SubmissionFormContent: React.FC = ({
-
+
-
+
{renderStepContent()}
{currentStep < steps.length - 1 ? ( - - - - - Edit Submission - - e.stopPropagation()}> + + + + + - - Delete Submission - - - + onEditClick?.()} + className='cursor-pointer text-gray-300 focus:bg-gray-800 focus:text-white' + > + + Edit Submission + + onDeleteClick?.()} + className='cursor-pointer text-red-500 focus:bg-red-900/20 focus:text-red-400' + > + + Delete Submission + + + +
)}
diff --git a/components/hackathons/submissions/submissionTab.tsx b/components/hackathons/submissions/submissionTab.tsx index 61f17bec7..fff6a4974 100644 --- a/components/hackathons/submissions/submissionTab.tsx +++ b/components/hackathons/submissions/submissionTab.tsx @@ -54,6 +54,7 @@ interface SubmissionTabContentProps extends SubmissionTabProps { fetchMySubmission: () => Promise; removeSubmission: (id: string) => Promise; hackathonId: string; + hackathonSlug: string; } const SubmissionTabContent: React.FC = ({ @@ -64,10 +65,10 @@ const SubmissionTabContent: React.FC = ({ fetchMySubmission, removeSubmission, hackathonId, + hackathonSlug, }) => { const { isAuthenticated } = useAuthStatus(); const router = useRouter(); - const { expand } = useExpandableScreen(); const [viewMode, setViewMode] = useState('grid'); @@ -82,7 +83,8 @@ const SubmissionTabContent: React.FC = ({ setSelectedSort, setSelectedCategory, } = useSubmissions(); - const { currentHackathon } = useHackathonData(); + const { currentHackathon, loading: isHackathonDataLoading } = + useHackathonData(); const { status } = useHackathonStatus( currentHackathon?.startDate, currentHackathon?.submissionDeadline @@ -129,6 +131,7 @@ const SubmissionTabContent: React.FC = ({ await removeSubmission(submissionToDelete); setSubmissionToDelete(null); toast.success('Submission deleted successfully'); + window.location.reload(); } catch (error) { reportError(error, { context: 'submission-delete', @@ -263,6 +266,14 @@ const SubmissionTabContent: React.FC = ({
+ {/* Loading State */} + {(isLoadingMySubmission || isHackathonDataLoading) && ( +
+ + Loading submissions... +
+ )} + {/* Submissions Grid with Create Button if no submission */} {!isLoadingMySubmission && !mySubmission && @@ -274,7 +285,7 @@ const SubmissionTabContent: React.FC = ({ You haven't submitted a project yet.

+
+ )} + {!isAuthenticated && ( -
+
setIsOpen(false)} - className='inline-flex h-9 w-full items-center justify-center gap-2 rounded-[10px] bg-[#a7f950] px-4 py-2 text-sm font-medium whitespace-nowrap text-black shadow-sm shadow-[#a7f950]/20 transition-all hover:bg-[#a7f950]/90' + className='inline-flex min-h-[44px] w-full items-center justify-center rounded-[10px] bg-[#a7f950] px-4 py-3 text-sm font-medium text-black shadow-sm shadow-[#a7f950]/20 transition-colors hover:bg-[#a7f950]/90' > Get Started setIsOpen(false)} - className='inline-flex h-9 w-full items-center justify-center gap-2 rounded-[10px] border border-white/30 px-4 py-2 text-sm font-medium whitespace-nowrap text-white transition-all hover:border-white/40 hover:bg-white/10' + className='inline-flex min-h-[44px] w-full items-center justify-center rounded-[10px] border border-white/30 px-4 py-3 text-sm font-medium text-white transition-colors hover:border-white/40 hover:bg-white/10' > Sign In From 84b1d35bb5baff35f1c586b66c31079ca493b23b Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Sat, 7 Mar 2026 19:46:14 +0100 Subject: [PATCH 4/4] feat(announcements): enhance announcement rendering and markdown support - Implemented a `stripMarkdown` function to convert Markdown content to plain text for better preview handling in the announcement page. - Created an `AnnouncementPreview` component to render announcements with Markdown support, improving content display in the announcements tab. - Updated the announcement editor to utilize a dynamic Markdown editor, enhancing the editing experience for users. --- .../[hackathonId]/announcement/page.tsx | 19 +- .../announcements/AnnouncementsTab.tsx | 24 +- .../shadcn-io/announcement-editor/index.tsx | 466 ++---------------- 3 files changed, 79 insertions(+), 430 deletions(-) diff --git a/app/(landing)/organizations/[id]/hackathons/[hackathonId]/announcement/page.tsx b/app/(landing)/organizations/[id]/hackathons/[hackathonId]/announcement/page.tsx index 60116acbd..20a2d8177 100644 --- a/app/(landing)/organizations/[id]/hackathons/[hackathonId]/announcement/page.tsx +++ b/app/(landing)/organizations/[id]/hackathons/[hackathonId]/announcement/page.tsx @@ -29,6 +29,23 @@ import { import { Switch } from '@/components/ui/switch'; import { reportError } from '@/lib/error-reporting'; +/** Strip Markdown to plain text for list preview (headings, bold, links, etc.). */ +function stripMarkdown(md: string): string { + if (!md || typeof md !== 'string') return ''; + return md + .replace(/!\[[^\]]*\]\([^)]*\)/g, '') + .replace(/\[([^\]]*)\]\([^)]*\)/g, '$1') + .replace(/#{1,6}\s*/g, '') + .replace(/\*\*([^*]+)\*\*/g, '$1') + .replace(/\*([^*]+)\*/g, '$1') + .replace(/__([^_]+)__/g, '$1') + .replace(/_([^_]+)_/g, '$1') + .replace(/`([^`]+)`/g, '$1') + .replace(/<[^>]*>/g, '') + .replace(/\n+/g, ' ') + .trim(); +} + export default function AnnouncementPage() { const params = useParams(); const organizationId = params.id as string; @@ -298,7 +315,7 @@ export default function AnnouncementPage() { )}

- {item.content.replace(/<[^>]*>/g, '')} + {stripMarkdown(item.content)}

diff --git a/components/hackathons/announcements/AnnouncementsTab.tsx b/components/hackathons/announcements/AnnouncementsTab.tsx index 621b2304e..e10c9b69d 100644 --- a/components/hackathons/announcements/AnnouncementsTab.tsx +++ b/components/hackathons/announcements/AnnouncementsTab.tsx @@ -5,6 +5,26 @@ import { Megaphone, Pin, ArrowUpDown, ExternalLink } from 'lucide-react'; import { cn } from '@/lib/utils'; import type { HackathonAnnouncement } from '@/lib/api/hackathons/index'; import Link from 'next/link'; +import { useMarkdown } from '@/hooks/use-markdown'; + +/** Renders announcement body as Markdown (supports both Markdown and legacy HTML). */ +function AnnouncementPreview({ content }: { content: string }) { + const raw = content?.trim() || ''; + const isLikelyHtml = raw.startsWith('<'); + const markdown = isLikelyHtml ? raw.replace(/<[^>]*>/g, ' ') : raw; + const { styledContent, loading } = useMarkdown(markdown, { + loadingDelay: 0, + }); + + if (!raw) return No content; + if (loading) return ; + + return ( +
+ {styledContent} +
+ ); +} interface AnnouncementsTabProps { announcements: HackathonAnnouncement[]; @@ -90,9 +110,7 @@ export function AnnouncementsTab({
-

- {announcement.content.replace(/<[^>]*>/g, '')} -

+
diff --git a/components/ui/shadcn-io/announcement-editor/index.tsx b/components/ui/shadcn-io/announcement-editor/index.tsx index d29c90eb1..60d8efb9d 100644 --- a/components/ui/shadcn-io/announcement-editor/index.tsx +++ b/components/ui/shadcn-io/announcement-editor/index.tsx @@ -1,40 +1,21 @@ 'use client'; import * as React from 'react'; -import { EditorContent, useEditor } from '@tiptap/react'; -import StarterKit from '@tiptap/starter-kit'; -import { Button } from '@/components/ui/button'; -import { Separator } from '@/components/ui/separator'; -import { Toggle } from '@/components/ui/toggle'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import { - Bold, - Italic, - Strikethrough, - Code, - Quote, - Link as LinkIcon, - Image as ImageIcon, - Undo, - Redo, - Code2, -} from 'lucide-react'; +import dynamic from 'next/dynamic'; +import { Loader2 } from 'lucide-react'; import { cn } from '@/lib/utils'; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogTrigger, -} from '@/components/ui/dialog'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; + +const MDEditor = dynamic( + () => import('@uiw/react-md-editor').then(mod => mod.default), + { + ssr: false, + loading: () => ( +
+ +
+ ), + } +); interface AnnouncementEditorProps { content?: string; @@ -51,129 +32,6 @@ function AnnouncementEditor({ editable = true, className, }: AnnouncementEditorProps) { - const [linkUrl, setLinkUrl] = React.useState(''); - const [linkText, setLinkText] = React.useState(''); - const [imageUrl, setImageUrl] = React.useState(''); - const [embedUrl, setEmbedUrl] = React.useState(''); - const [isLinkDialogOpen, setIsLinkDialogOpen] = React.useState(false); - const [isImageDialogOpen, setIsImageDialogOpen] = React.useState(false); - const [isEmbedDialogOpen, setIsEmbedDialogOpen] = React.useState(false); - - const editor = useEditor({ - extensions: [ - StarterKit.configure({ - bulletList: { - keepMarks: true, - keepAttributes: false, - }, - orderedList: { - keepMarks: true, - keepAttributes: false, - }, - }), - ], - content, - editable, - immediatelyRender: false, - onUpdate: ({ editor }) => { - onChange?.(editor.getHTML()); - }, - editorProps: { - attributes: { - class: cn( - 'prose prose-sm sm:prose-base lg:prose-lg xl:prose-2xl mx-auto focus:outline-none', - 'min-h-[400px] border-0 p-6 text-white' - ), - }, - }, - }); - - React.useEffect(() => { - if (editor && content !== editor.getHTML()) { - editor.commands.setContent(content); - } - }, [content, editor]); - - React.useEffect(() => { - if (editor) { - editor.setOptions({ - editorProps: { - ...editor.options.editorProps, - handleDOMEvents: { - ...editor.options.editorProps?.handleDOMEvents, - drop: (view, event) => { - const files = event.dataTransfer?.files; - if (files && files.length > 0) { - const file = files[0]; - if (file.type.startsWith('image/')) { - const reader = new FileReader(); - reader.onload = e => { - const src = e.target?.result as string; - editor - .chain() - .focus() - .insertContent( - `Image` - ) - .run(); - }; - reader.readAsDataURL(file); - return true; - } - } - return false; - }, - }, - }, - }); - } - }, [editor]); - - if (!editor) { - return null; - } - - const handleInsertLink = () => { - if (linkUrl && linkText) { - editor - .chain() - .focus() - .insertContent(`${linkText}`) - .run(); - setLinkUrl(''); - setLinkText(''); - setIsLinkDialogOpen(false); - } - }; - - const handleInsertImage = () => { - if (imageUrl) { - editor - .chain() - .focus() - .insertContent( - `Image` - ) - .run(); - setImageUrl(''); - setIsImageDialogOpen(false); - } - }; - - const handleInsertEmbed = () => { - if (embedUrl) { - editor - .chain() - .focus() - .insertContent( - `` - ) - .run(); - setEmbedUrl(''); - setIsEmbedDialogOpen(false); - } - }; - return (
-
- - - - - - - - - - editor.chain().focus().toggleBold().run()} - disabled={!editor.can().chain().focus().toggleBold().run()} - className='h-8 w-8 p-0 data-[state=on]:bg-gray-800 data-[state=on]:text-white' - > - - - - editor.chain().focus().toggleItalic().run()} - disabled={!editor.can().chain().focus().toggleItalic().run()} - className='h-8 w-8 p-0 data-[state=on]:bg-gray-800 data-[state=on]:text-white' - > - - - - editor.chain().focus().toggleStrike().run()} - disabled={!editor.can().chain().focus().toggleStrike().run()} - className='h-8 w-8 p-0 data-[state=on]:bg-gray-800 data-[state=on]:text-white' - > - - - - editor.chain().focus().toggleCode().run()} - disabled={!editor.can().chain().focus().toggleCode().run()} - className='h-8 w-8 p-0 data-[state=on]:bg-gray-800 data-[state=on]:text-white' - > - - - - - editor.chain().focus().toggleBlockquote().run() - } - className='h-8 w-8 p-0 data-[state=on]:bg-gray-800 data-[state=on]:text-white' - > - - - - - - - - - - Insert Link - -
-
- - setLinkText(e.target.value)} - placeholder='Link text' - className='bg-background border-gray-800 text-white' - /> -
-
- - setLinkUrl(e.target.value)} - placeholder='https://example.com' - className='bg-background border-gray-800 text-white' - /> -
-
- - -
-
-
-
- - - - - - - - Insert Image - -
-
- - setImageUrl(e.target.value)} - placeholder='https://example.com/image.jpg' - className='bg-background border-gray-800 text-white' - /> -
-
- - -
-
-
-
- - - - - - - - Insert Embed - -
-
- - setEmbedUrl(e.target.value)} - placeholder='https://example.com/embed' - className='bg-background border-gray-800 text-white' - /> -
-
- - -
-
-
-
-
- -
- - {(!editor.getHTML() || editor.getHTML() === '

') && ( -
- {placeholder} -
- )} -
+ onChange?.(value ?? '')} + height={400} + data-color-mode='dark' + preview='edit' + hideToolbar={!editable} + visibleDragbar={editable} + textareaProps={{ + placeholder, + readOnly: !editable, + style: { + fontSize: 14, + lineHeight: 1.6, + color: '#ffffff', + backgroundColor: '#18181b', + fontFamily: 'inherit', + border: 'none', + }, + }} + style={{ + backgroundColor: '#18181b', + color: '#ffffff', + border: 'none', + }} + />
); }