Skip to content

Commit f28e914

Browse files
committed
feat: enhance project creation modal with drag-and-drop milestones and markdown editor
1 parent 40e4d1d commit f28e914

10 files changed

Lines changed: 3208 additions & 522 deletions

File tree

components/landing-page/project/CreateProjectModal/Basic.tsx

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Info } from 'lucide-react';
99
import { cn } from '@/lib/utils';
1010
import { z } from 'zod';
1111
import FormHint from '@/components/form/FormHint';
12+
import Image from 'next/image';
1213

1314
interface BasicProps {
1415
onDataChange?: (data: BasicFormData) => void;
@@ -88,6 +89,7 @@ const Basic = React.forwardRef<{ validate: () => boolean }, BasicProps>(
8889
Partial<Record<keyof BasicFormData | 'socialLinks', boolean>>
8990
>({});
9091
const [submitted, setSubmitted] = useState(false);
92+
const [isDragOver, setIsDragOver] = useState(false);
9193

9294
const handleInputChange = (
9395
field: keyof BasicFormData,
@@ -136,6 +138,50 @@ const Basic = React.forwardRef<{ validate: () => boolean }, BasicProps>(
136138
}
137139
};
138140

141+
const handleDragOver = (event: React.DragEvent<HTMLLabelElement>) => {
142+
event.preventDefault();
143+
event.stopPropagation();
144+
setIsDragOver(true);
145+
};
146+
147+
const handleDragLeave = (event: React.DragEvent<HTMLLabelElement>) => {
148+
event.preventDefault();
149+
event.stopPropagation();
150+
setIsDragOver(false);
151+
};
152+
153+
const handleDrop = (event: React.DragEvent<HTMLLabelElement>) => {
154+
event.preventDefault();
155+
event.stopPropagation();
156+
setIsDragOver(false);
157+
158+
const files = event.dataTransfer.files;
159+
if (files && files.length > 0) {
160+
const file = files[0];
161+
setTouched(prev => ({ ...prev, logo: true }));
162+
163+
// Validate file type
164+
if (!file.type.match(/^image\/(jpeg|png)$/)) {
165+
setErrors(prev => ({
166+
...prev,
167+
logo: 'Only JPEG and PNG files are allowed',
168+
}));
169+
return;
170+
}
171+
172+
// Validate file size (2MB = 2 * 1024 * 1024 bytes)
173+
if (file.size > 2 * 1024 * 1024) {
174+
setErrors(prev => ({
175+
...prev,
176+
logo: 'File size must be less than 2MB',
177+
}));
178+
return;
179+
}
180+
181+
handleInputChange('logo', file);
182+
}
183+
};
184+
139185
const validateForm = (): boolean => {
140186
// Normalize social links (trim empties but keep array length for UI)
141187
const parsed = basicSchema.safeParse({
@@ -224,8 +270,9 @@ const Basic = React.forwardRef<{ validate: () => boolean }, BasicProps>(
224270
}
225271
} catch (e: unknown) {
226272
// Zod throws on parse; map first error
227-
if (e && typeof e === 'object' && 'issues' in (e as any)) {
228-
const issue = (e as any).issues?.[0];
273+
if (e && typeof e === 'object' && 'issues' in e) {
274+
const zodError = e as { issues: Array<{ message: string }> };
275+
const issue = zodError.issues?.[0];
229276
const msg = issue?.message as string | undefined;
230277
const k = field as keyof BasicFormData;
231278
setErrors(prev => ({ ...prev, [k]: msg || 'Invalid value' }));
@@ -278,24 +325,51 @@ const Basic = React.forwardRef<{ validate: () => boolean }, BasicProps>(
278325
/>
279326
<label
280327
htmlFor='logo-upload'
328+
onDragOver={handleDragOver}
329+
onDragLeave={handleDragLeave}
330+
onDrop={handleDrop}
281331
className={cn(
282332
'flex h-32 w-32 cursor-pointer items-center justify-center rounded-[12px] border border-[#2B2B2B] bg-[#101010] transition-colors xl:h-[200px] xl:w-[200px]',
283333
formData.logo
284334
? 'border-primary bg-primary/5'
285335
: 'hover:border-primary border-[#484848]',
286-
(submitted || touched.logo) && errors.logo && 'border-red-500'
336+
(submitted || touched.logo) && errors.logo && 'border-red-500',
337+
isDragOver && 'border-primary bg-primary/10 scale-105'
287338
)}
288339
>
289340
{formData.logo ? (
290341
<div className='flex flex-col items-center space-y-2'>
291-
{/* eslint-disable-next-line @next/next/no-img-element */}
292-
<img
342+
{}
343+
<Image
293344
src={URL.createObjectURL(formData.logo)}
294345
alt='Project logo'
295-
className='h-16 w-16 rounded object-cover'
346+
className='h-32 w-32 rounded object-cover'
347+
width={128}
348+
height={128}
296349
/>
297350
<span className='text-xs text-white'>Change</span>
298351
</div>
352+
) : isDragOver ? (
353+
<div className='flex flex-col items-center space-y-2'>
354+
<svg
355+
width='36'
356+
height='36'
357+
viewBox='0 0 36 36'
358+
fill='none'
359+
xmlns='http://www.w3.org/2000/svg'
360+
>
361+
<path
362+
d='M17.875 2.75012H9.65C6.70972 2.75012 5.23959 2.75012 4.11655 3.32234C3.1287 3.82567 2.32555 4.62882 1.82222 5.61667C1.25 6.73971 1.25 8.20985 1.25 11.1501V25.8501C1.25 28.7904 1.25 30.2605 1.82222 31.3836C2.32555 32.3714 3.1287 33.1746 4.11655 33.6779C5.23959 34.2501 6.70972 34.2501 9.65 34.2501H25.75C27.3775 34.2501 28.1912 34.2501 28.8588 34.0712C30.6705 33.5858 32.0857 32.1707 32.5711 30.3589C32.75 29.6913 32.75 28.8776 32.75 27.2501M29.25 11.5001V1.00012M24 6.25012H34.5M14.375 12.3751C14.375 14.3081 12.808 15.8751 10.875 15.8751C8.942 15.8751 7.375 14.3081 7.375 12.3751C7.375 10.4421 8.942 8.87512 10.875 8.87512C12.808 8.87512 14.375 10.4421 14.375 12.3751ZM22.2326 18.3569L7.42951 31.8142C6.59688 32.5711 6.18057 32.9496 6.14375 33.2775C6.11183 33.5616 6.22079 33.8435 6.43557 34.0323C6.68336 34.2501 7.24599 34.2501 8.37125 34.2501H24.798C27.3165 34.2501 28.5758 34.2501 29.5649 33.827C30.8065 33.2959 31.7957 32.3066 32.3269 31.065C32.75 30.0759 32.75 28.8166 32.75 26.2981C32.75 25.4507 32.75 25.027 32.6574 24.6324C32.5409 24.1365 32.3177 23.672 32.0032 23.2713C31.7529 22.9525 31.4221 22.6878 30.7604 22.1584L25.8652 18.2423C25.2029 17.7125 24.8718 17.4476 24.5071 17.3541C24.1857 17.2717 23.8475 17.2823 23.5319 17.3848C23.1739 17.5011 22.8601 17.7864 22.2326 18.3569Z'
363+
stroke='#A7F950'
364+
strokeWidth='2'
365+
strokeLinecap='round'
366+
strokeLinejoin='round'
367+
/>
368+
</svg>
369+
<span className='text-primary text-xs font-medium'>
370+
Drop image here
371+
</span>
372+
</div>
299373
) : (
300374
<div className='flex flex-col items-center space-y-2'>
301375
<svg
@@ -313,6 +387,9 @@ const Basic = React.forwardRef<{ validate: () => boolean }, BasicProps>(
313387
strokeLinejoin='round'
314388
/>
315389
</svg>
390+
<span className='text-xs text-[#919191]'>
391+
Click or drag to upload
392+
</span>
316393
</div>
317394
)}
318395
</label>

0 commit comments

Comments
 (0)