OpChan/app/src/components/CreateCellDialog.tsx

206 lines
5.9 KiB
TypeScript
Raw Normal View History

2025-04-15 16:28:03 +05:30
import React from 'react';
2025-08-30 18:34:50 +05:30
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Loader2 } from 'lucide-react';
import { useForumActions, usePermissions } from '@/hooks';
2025-04-15 16:28:03 +05:30
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
2025-08-30 18:34:50 +05:30
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Button } from '@/components/ui/button';
2025-04-15 16:28:03 +05:30
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
2025-08-30 18:34:50 +05:30
} from '@/components/ui/dialog';
import { useToast } from '@/hooks/use-toast';
import { urlLoads } from '@/lib/utils/urlLoads';
2025-04-15 16:28:03 +05:30
const formSchema = z.object({
2025-08-30 18:34:50 +05:30
title: z
.string()
.min(3, 'Cell name must be at least 3 characters')
.max(50, 'Cell name must be less than 50 characters'),
2025-08-30 18:34:50 +05:30
description: z
.string()
.min(10, 'Description must be at least 10 characters')
.max(500, 'Description must be less than 500 characters'),
icon: z
.string()
.optional()
.refine(
val => {
if (!val) return true;
return urlLoads(val);
},
{
message: 'Icon must be a valid URL',
}
),
2025-04-15 16:28:03 +05:30
});
interface CreateCellDialogProps {
open?: boolean;
onOpenChange?: (open: boolean) => void;
}
2025-08-30 18:34:50 +05:30
export function CreateCellDialog({
open: externalOpen,
onOpenChange,
}: CreateCellDialogProps = {}) {
const { createCell, isCreatingCell } = useForumActions();
const { canCreateCell } = usePermissions();
const { toast } = useToast();
const [internalOpen, setInternalOpen] = React.useState(false);
2025-08-30 18:34:50 +05:30
const open = externalOpen ?? internalOpen;
const setOpen = onOpenChange ?? setInternalOpen;
2025-08-30 18:34:50 +05:30
2025-04-15 16:28:03 +05:30
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
2025-08-30 18:34:50 +05:30
title: '',
description: '',
icon: '',
2025-04-15 16:28:03 +05:30
},
});
const onSubmit = async (values: z.infer<typeof formSchema>) => {
if (!canCreateCell) {
toast({
title: 'Permission Denied',
description: 'You need to verify Ordinal ownership to create cells.',
variant: 'destructive',
});
return;
}
// ✅ All validation handled in hook
2025-08-30 18:34:50 +05:30
const cell = await createCell(
values.title,
values.description,
values.icon
2025-08-30 18:34:50 +05:30
);
2025-04-15 16:28:03 +05:30
if (cell) {
form.reset();
setOpen(false);
2025-04-15 16:28:03 +05:30
}
};
2025-09-11 12:10:51 +05:30
// Handle keyboard shortcuts
const handleKeyDown = (e: React.KeyboardEvent) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
if (!isCreatingCell && canCreateCell) {
form.handleSubmit(onSubmit)();
}
}
};
2025-04-15 16:28:03 +05:30
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button className="bg-cyber-accent hover:bg-cyber-accent/80">
Create Cell
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-md bg-cyber-dark border-cyber-muted">
2025-04-15 16:28:03 +05:30
<DialogHeader>
<DialogTitle className="text-glow">Create New Cell</DialogTitle>
2025-04-15 16:28:03 +05:30
</DialogHeader>
<Form {...form}>
2025-09-11 12:10:51 +05:30
<form onSubmit={form.handleSubmit(onSubmit)} onKeyDown={handleKeyDown} className="space-y-4">
2025-04-15 16:28:03 +05:30
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Cell Name</FormLabel>
2025-04-15 16:28:03 +05:30
<FormControl>
2025-08-30 18:34:50 +05:30
<Input
placeholder="Enter cell name"
className="bg-cyber-muted/50 border-cyber-muted"
disabled={isCreatingCell}
2025-08-30 18:34:50 +05:30
{...field}
/>
2025-04-15 16:28:03 +05:30
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Description</FormLabel>
<FormControl>
2025-08-30 18:34:50 +05:30
<Textarea
placeholder="Describe your cell"
className="bg-cyber-muted/50 border-cyber-muted resize-none"
disabled={isCreatingCell}
2025-04-15 16:28:03 +05:30
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="icon"
render={({ field }) => (
<FormItem>
<FormLabel>Icon URL (Optional)</FormLabel>
2025-04-15 16:28:03 +05:30
<FormControl>
2025-08-30 18:34:50 +05:30
<Input
placeholder="https://example.com/icon.png"
className="bg-cyber-muted/50 border-cyber-muted"
disabled={isCreatingCell}
2025-04-15 16:28:03 +05:30
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end gap-3">
<Button
type="button"
variant="outline"
onClick={() => setOpen(false)}
disabled={isCreatingCell}
>
Cancel
</Button>
<Button
type="submit"
disabled={isCreatingCell || !canCreateCell}
className="bg-cyber-accent hover:bg-cyber-accent/80"
>
{isCreatingCell ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Creating...
</>
) : (
'Create Cell'
)}
</Button>
</div>
2025-04-15 16:28:03 +05:30
</form>
</Form>
</DialogContent>
</Dialog>
);
}