-
-
-
-
-
-
- (
- setEndDate(date as Date)}
- initialFocus
- />
- )}
+
+ {
+ console.log("Date selected:", newDate);
+ setDate(newDate);
+ if (newDate?.from && newDate?.to) {
+ setIsDatePickerOpen(false);
+ }
+ }}
+ numberOfMonths={1}
+ className="rounded-md border"
/>
diff --git a/app/[locale]/(protected)/contributor/planning/mediahub/components/mediahub-table.tsx b/app/[locale]/(protected)/contributor/planning/mediahub/components/mediahub-table.tsx
index 2dadb124..96719606 100644
--- a/app/[locale]/(protected)/contributor/planning/mediahub/components/mediahub-table.tsx
+++ b/app/[locale]/(protected)/contributor/planning/mediahub/components/mediahub-table.tsx
@@ -54,7 +54,6 @@ import { useRouter, useSearchParams } from "next/navigation";
import TablePagination from "@/components/table/table-pagination";
import columns from "./columns";
import { getPlanningSentPagination } from "@/service/planning/planning";
-import search from "@/app/[locale]/(protected)/app/chat/components/search";
import { CardHeader, CardTitle } from "@/components/ui/card";
import { useTranslations } from "next-intl";
import useTableColumns from "./columns";
diff --git a/app/[locale]/(protected)/dashboard/routine-task/components/recent-activity.tsx b/app/[locale]/(protected)/dashboard/routine-task/components/recent-activity.tsx
index ad649f48..dfbfc559 100644
--- a/app/[locale]/(protected)/dashboard/routine-task/components/recent-activity.tsx
+++ b/app/[locale]/(protected)/dashboard/routine-task/components/recent-activity.tsx
@@ -11,7 +11,6 @@ import {
import { DockIcon, ImageIcon, MicIcon, YoutubeIcon } from "lucide-react";
import { useRouter, useSearchParams } from "next/navigation";
import React from "react";
-import search from "../../../app/chat/components/search";
import { useTranslations } from "next-intl";
type StatusFilter = string[];
diff --git a/app/[locale]/auth/forgot-password/layout.tsx b/app/[locale]/auth/forgot-password/layout.tsx
new file mode 100644
index 00000000..2d394e06
--- /dev/null
+++ b/app/[locale]/auth/forgot-password/layout.tsx
@@ -0,0 +1,11 @@
+import { Metadata } from "next";
+
+export const metadata: Metadata = {
+ title: "Media Hub | POLRI",
+ description: "Media Hub merupakan situs resmi milik Divisi Humas Polri di mana di dalamnya berisi konten-konten yang dapat diakses secara gratis oleh Internal Polri, Jurnalis, Masyarakat Umum, dan KSP.",
+};
+const Layout = ({ children }: { children: React.ReactNode }) => {
+ return <>{children}>;
+};
+
+export default Layout;
\ No newline at end of file
diff --git a/app/[locale]/auth/forgot-password/page.tsx b/app/[locale]/auth/forgot-password/page.tsx
new file mode 100644
index 00000000..06636a14
--- /dev/null
+++ b/app/[locale]/auth/forgot-password/page.tsx
@@ -0,0 +1,48 @@
+import { Link } from "@/i18n/routing";
+import ForgotPass from "@/components/partials/auth/forgot-pass";
+import Image from "next/image";
+
+const ForgotPassPage = () => {
+ return (
+
+
+
+
+
+
+
+ {/*
+
+ */}
+
+
+
Forgot Your Password?
+
+
Enter your Username and instructions will be sent to you!
+
+
+
+ Forget It,
+
+ Send me Back
+
+ to The Sign In
+
+
+
+
+
+
+ );
+};
+
+export default ForgotPassPage;
diff --git a/components/auth/identity-form.tsx b/components/auth/identity-form.tsx
index 6adf6fef..a6493082 100644
--- a/components/auth/identity-form.tsx
+++ b/components/auth/identity-form.tsx
@@ -105,9 +105,7 @@ export const IdentityForm: React.FC
= ({
{t("member", { defaultValue: "Member" })} *
- {errors.association && (
+ {/* {errors.association && (
{errors.association.message}
- )}
+ )} */}
diff --git a/components/auth/profile-form.tsx b/components/auth/profile-form.tsx
index e5c48afe..cf9c5d19 100644
--- a/components/auth/profile-form.tsx
+++ b/components/auth/profile-form.tsx
@@ -122,6 +122,7 @@ export const ProfileForm: React.FC
= ({
}
const instituteData: InstituteData = {
+ id: "0",
name: customInstituteName,
address: instituteAddress,
};
@@ -159,7 +160,7 @@ export const ProfileForm: React.FC = ({
- {institutes.map((institute) => (
+ {institutes?.map((institute) => (
diff --git a/components/form/content/task-ta/video-update-form.tsx b/components/form/content/task-ta/video-update-form.tsx
index c1ebd894..93716f82 100644
--- a/components/form/content/task-ta/video-update-form.tsx
+++ b/components/form/content/task-ta/video-update-form.tsx
@@ -48,7 +48,6 @@ import "swiper/css/thumbs";
import "swiper/css";
import "swiper/css/navigation";
import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
-import { files } from "@/app/[locale]/(protected)/app/projects/[id]/data";
import { useDropzone } from "react-dropzone";
import Image from "next/image";
import { Icon } from "@iconify/react/dist/iconify.js";
diff --git a/components/form/content/video-update-form.tsx b/components/form/content/video-update-form.tsx
index 6c50ecf3..3810fbe7 100644
--- a/components/form/content/video-update-form.tsx
+++ b/components/form/content/video-update-form.tsx
@@ -48,7 +48,6 @@ import "swiper/css/thumbs";
import "swiper/css";
import "swiper/css/navigation";
import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
-import { files } from "@/app/[locale]/(protected)/app/projects/[id]/data";
import { useDropzone } from "react-dropzone";
import Image from "next/image";
import { Icon } from "@iconify/react/dist/iconify.js";
diff --git a/components/form/shared/form-checkbox.tsx b/components/form/shared/form-checkbox.tsx
new file mode 100644
index 00000000..736556df
--- /dev/null
+++ b/components/form/shared/form-checkbox.tsx
@@ -0,0 +1,244 @@
+/**
+ * FormCheckbox Component
+ *
+ * A reusable checkbox component that handles:
+ * - Single checkbox
+ * - Checkbox groups
+ * - Label
+ * - Controller (react-hook-form)
+ * - Error handling
+ *
+ * This component reduces code duplication for checkbox fields across forms.
+ */
+
+import React from 'react';
+import { Control, Controller, FieldError, FieldPath, FieldValues } from 'react-hook-form';
+import { Label } from '@/components/ui/label';
+import { Checkbox } from '@/components/ui/checkbox';
+import { cn } from '@/lib/utils';
+
+// =============================================================================
+// TYPES
+// =============================================================================
+
+export interface CheckboxOption {
+ value: string | number;
+ label: string;
+ disabled?: boolean;
+}
+
+export interface FormCheckboxProps<
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath
+> {
+ // Form control
+ control: Control;
+ name: TName;
+
+ // Field configuration
+ label?: string;
+ required?: boolean;
+
+ // Checkbox configuration
+ single?: boolean; // Single checkbox vs checkbox group
+ options?: CheckboxOption[]; // For checkbox groups
+
+ // Styling
+ className?: string;
+ labelClassName?: string;
+ checkboxClassName?: string;
+ errorClassName?: string;
+ groupClassName?: string;
+
+ // Layout
+ layout?: 'horizontal' | 'vertical';
+ columns?: number; // For grid layout
+
+ // Validation
+ validation?: {
+ required?: boolean | string;
+ };
+
+ // Additional props
+ disabled?: boolean;
+ size?: 'sm' | 'md' | 'lg';
+
+ // Custom render functions
+ renderOption?: (option: CheckboxOption, checked: boolean, onChange: (checked: boolean) => void) => React.ReactElement;
+}
+
+// =============================================================================
+// COMPONENT
+// =============================================================================
+
+export function FormCheckbox<
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath
+>({
+ control,
+ name,
+ label,
+ required = false,
+ single = false,
+ options = [],
+ className = '',
+ labelClassName = '',
+ checkboxClassName = '',
+ errorClassName = '',
+ groupClassName = '',
+ layout = 'horizontal',
+ columns = 1,
+ validation,
+ disabled = false,
+ size = 'md',
+ renderOption,
+}: FormCheckboxProps) {
+
+ // =============================================================================
+ // HELPER FUNCTIONS
+ // =============================================================================
+
+ const getCheckboxSize = (size: 'sm' | 'md' | 'lg') => {
+ switch (size) {
+ case 'sm':
+ return 'h-4 w-4';
+ case 'lg':
+ return 'h-6 w-6';
+ default:
+ return 'h-5 w-5';
+ }
+ };
+
+ const getGroupLayout = () => {
+ if (layout === 'vertical') {
+ return 'flex flex-col gap-2';
+ }
+
+ if (columns > 1) {
+ return `grid grid-cols-${columns} gap-2`;
+ }
+
+ return 'flex flex-wrap gap-4';
+ };
+
+ // =============================================================================
+ // RENDER FUNCTIONS
+ // =============================================================================
+
+ const renderSingleCheckbox = (field: any, fieldState: { error?: FieldError }) => (
+
+
+
+
+ );
+
+ const renderCheckboxGroup = (field: any, fieldState: { error?: FieldError }) => {
+ const selectedValues = field.value || [];
+
+ const handleOptionChange = (optionValue: string | number, checked: boolean) => {
+ const newValues = checked
+ ? [...selectedValues, optionValue]
+ : selectedValues.filter((value: any) => value !== optionValue);
+ field.onChange(newValues);
+ };
+
+ const renderDefaultOption = (option: CheckboxOption) => {
+ const checked = selectedValues.includes(option.value);
+
+ if (renderOption) {
+ return renderOption(option, checked, (checked) => handleOptionChange(option.value, checked));
+ }
+
+ return (
+
+ handleOptionChange(option.value, checked as boolean)}
+ disabled={disabled || option.disabled}
+ className={getCheckboxSize(size)}
+ />
+
+
+ );
+ };
+
+ return (
+
+ {options.map(renderDefaultOption)}
+
+ );
+ };
+
+ // =============================================================================
+ // MAIN RENDER
+ // =============================================================================
+
+ return (
+
+ {/* Group Label */}
+ {label && !single && (
+
+ )}
+
+ {/* Controller */}
+
(
+
+ {single ?
+ renderSingleCheckbox(field, fieldState) :
+ renderCheckboxGroup(field, fieldState)
+ }
+
+ {/* Error Message */}
+ {fieldState.error && (
+
+ {fieldState.error.message}
+
+ )}
+
+ )}
+ />
+
+ );
+}
+
+export default FormCheckbox;
\ No newline at end of file
diff --git a/components/form/shared/form-components-demo.tsx b/components/form/shared/form-components-demo.tsx
new file mode 100644
index 00000000..8981d0a1
--- /dev/null
+++ b/components/form/shared/form-components-demo.tsx
@@ -0,0 +1,280 @@
+/**
+ * Form Components Demo
+ *
+ * This component demonstrates how to use the new reusable form components.
+ * It shows examples of all the form field types and layout components.
+ */
+
+import React from 'react';
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import * as z from 'zod';
+import { Button } from '@/components/ui/button';
+import { Card } from '@/components/ui/card';
+import {
+ FormField,
+ FormSelect,
+ FormCheckbox,
+ FormRadio,
+ FormDatePicker,
+ FormSection,
+ FormGrid,
+ FormGridItem,
+ SelectOption,
+ CheckboxOption,
+ RadioOption,
+} from './index';
+
+// =============================================================================
+// SCHEMA
+// =============================================================================
+
+const demoFormSchema = z.object({
+ title: z.string().min(1, 'Title is required'),
+ description: z.string().min(10, 'Description must be at least 10 characters'),
+ category: z.string().min(1, 'Category is required'),
+ priority: z.string().min(1, 'Priority is required'),
+ tags: z.array(z.string()).min(1, 'At least one tag is required'),
+ status: z.string().min(1, 'Status is required'),
+ dueDate: z.date().optional(),
+ isPublic: z.boolean().default(false),
+ notifications: z.array(z.string()).default([]),
+});
+
+type DemoFormData = z.infer;
+
+// =============================================================================
+// OPTIONS DATA
+// =============================================================================
+
+const categoryOptions: SelectOption[] = [
+ { value: 'feature', label: 'Feature Request' },
+ { value: 'bug', label: 'Bug Report' },
+ { value: 'improvement', label: 'Improvement' },
+ { value: 'documentation', label: 'Documentation' },
+];
+
+const priorityOptions: RadioOption[] = [
+ { value: 'low', label: 'Low' },
+ { value: 'medium', label: 'Medium' },
+ { value: 'high', label: 'High' },
+ { value: 'urgent', label: 'Urgent' },
+];
+
+const tagOptions: CheckboxOption[] = [
+ { value: 'frontend', label: 'Frontend' },
+ { value: 'backend', label: 'Backend' },
+ { value: 'ui', label: 'UI/UX' },
+ { value: 'database', label: 'Database' },
+ { value: 'security', label: 'Security' },
+ { value: 'performance', label: 'Performance' },
+];
+
+const statusOptions: SelectOption[] = [
+ { value: 'draft', label: 'Draft' },
+ { value: 'in-progress', label: 'In Progress' },
+ { value: 'review', label: 'Under Review' },
+ { value: 'completed', label: 'Completed' },
+];
+
+const notificationOptions: CheckboxOption[] = [
+ { value: 'email', label: 'Email Notifications' },
+ { value: 'push', label: 'Push Notifications' },
+ { value: 'sms', label: 'SMS Notifications' },
+];
+
+// =============================================================================
+// COMPONENT
+// =============================================================================
+
+export function FormComponentsDemo() {
+ const form = useForm({
+ resolver: zodResolver(demoFormSchema),
+ defaultValues: {
+ title: '',
+ description: '',
+ category: '',
+ priority: 'medium',
+ tags: [],
+ status: 'draft',
+ dueDate: undefined,
+ isPublic: false,
+ notifications: ['email'],
+ },
+ });
+
+ const onSubmit = (data: DemoFormData) => {
+ console.log('Form submitted:', data);
+ alert('Form submitted! Check console for data.');
+ };
+
+ return (
+
+
+
Form Components Demo
+
+ Examples of using the new reusable form components
+
+
+
+
+
+ {/* Form Data Display */}
+
+ Current Form Data
+
+ {JSON.stringify(form.watch(), null, 2)}
+
+
+
+ );
+}
+
+export default FormComponentsDemo;
\ No newline at end of file
diff --git a/components/form/shared/form-date-picker.tsx b/components/form/shared/form-date-picker.tsx
new file mode 100644
index 00000000..fd6c9acc
--- /dev/null
+++ b/components/form/shared/form-date-picker.tsx
@@ -0,0 +1,231 @@
+/**
+ * FormDatePicker Component
+ *
+ * A reusable date picker component that handles:
+ * - Single date selection
+ * - Date range selection
+ * - Label
+ * - Controller (react-hook-form)
+ * - Error handling
+ *
+ * This component reduces code duplication for date picker fields across forms.
+ */
+
+import React from 'react';
+import { Control, Controller, FieldError, FieldPath, FieldValues } from 'react-hook-form';
+import { Label } from '@/components/ui/label';
+import { Button } from '@/components/ui/button';
+import { Calendar } from '@/components/ui/calendar';
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from '@/components/ui/popover';
+import { CalendarIcon } from 'lucide-react';
+import { format } from 'date-fns';
+import { DateRange } from 'react-day-picker';
+import { cn } from '@/lib/utils';
+
+// =============================================================================
+// TYPES
+// =============================================================================
+
+export interface FormDatePickerProps<
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath
+> {
+ // Form control
+ control: Control;
+ name: TName;
+
+ // Field configuration
+ label?: string;
+ placeholder?: string;
+ required?: boolean;
+
+ // Date picker configuration
+ mode?: 'single' | 'range';
+ numberOfMonths?: number;
+
+ // Styling
+ className?: string;
+ labelClassName?: string;
+ buttonClassName?: string;
+ errorClassName?: string;
+
+ // Validation
+ validation?: {
+ required?: boolean | string;
+ };
+
+ // Additional props
+ disabled?: boolean;
+ size?: 'sm' | 'md' | 'lg';
+
+ // Date formatting
+ dateFormat?: string;
+
+ // Custom render functions
+ renderTrigger?: (props: {
+ field: any;
+ fieldState: { error?: FieldError };
+ selectedDate: Date | DateRange | undefined;
+ placeholder: string;
+ }) => React.ReactElement;
+}
+
+// =============================================================================
+// COMPONENT
+// =============================================================================
+
+export function FormDatePicker<
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath
+>({
+ control,
+ name,
+ label,
+ placeholder = 'Pick a date',
+ required = false,
+ mode = 'single',
+ numberOfMonths = 1,
+ className = '',
+ labelClassName = '',
+ buttonClassName = '',
+ errorClassName = '',
+ validation,
+ disabled = false,
+ size = 'md',
+ dateFormat = 'LLL dd, y',
+ renderTrigger,
+}: FormDatePickerProps) {
+
+ // =============================================================================
+ // HELPER FUNCTIONS
+ // =============================================================================
+
+ const getButtonSize = (size: 'sm' | 'md' | 'lg') => {
+ switch (size) {
+ case 'sm':
+ return 'h-8 text-sm';
+ case 'lg':
+ return 'h-12 text-base';
+ default:
+ return 'h-10 text-sm';
+ }
+ };
+
+ const formatDate = (date: Date | DateRange | undefined): string => {
+ if (!date) return '';
+
+ if (mode === 'range' && 'from' in date) {
+ if (date.from) {
+ if (date.to) {
+ return `${format(date.from, dateFormat)} - ${format(date.to, dateFormat)}`;
+ }
+ return format(date.from, dateFormat);
+ }
+ } else if (date instanceof Date) {
+ return format(date, dateFormat);
+ }
+
+ return '';
+ };
+
+ // =============================================================================
+ // RENDER FUNCTIONS
+ // =============================================================================
+
+ const renderDefaultTrigger = (
+ field: any,
+ fieldState: { error?: FieldError },
+ selectedDate: Date | DateRange | undefined,
+ placeholder: string
+ ) => (
+
+ );
+
+ // =============================================================================
+ // MAIN RENDER
+ // =============================================================================
+
+ return (
+
+ {/* Label */}
+ {label && (
+
+ )}
+
+ {/* Controller */}
+
{
+ const selectedDate = field.value;
+
+ return (
+
+
+
+ {renderTrigger ?
+ renderTrigger({ field, fieldState, selectedDate, placeholder }) :
+ renderDefaultTrigger(field, fieldState, selectedDate, placeholder)
+ }
+
+
+
+
+
+
+
+ {/* Error Message */}
+ {fieldState.error && (
+
+ {fieldState.error.message}
+
+ )}
+
+ );
+ }}
+ />
+
+ );
+}
+
+export default FormDatePicker;
\ No newline at end of file
diff --git a/components/form/shared/form-field.tsx b/components/form/shared/form-field.tsx
new file mode 100644
index 00000000..f81aecf1
--- /dev/null
+++ b/components/form/shared/form-field.tsx
@@ -0,0 +1,228 @@
+/**
+ * FormField Component
+ *
+ * A reusable form field component that handles the common pattern of:
+ * - Label
+ * - Controller (react-hook-form)
+ * - Input/Select/Textarea
+ * - Error handling
+ *
+ * This component reduces code duplication across all form components.
+ */
+
+import React from 'react';
+import { Control, Controller, FieldError, FieldPath, FieldValues } from 'react-hook-form';
+import { Label } from '@/components/ui/label';
+import { Input } from '@/components/ui/input';
+import { Textarea } from '@/components/ui/textarea';
+import { cn } from '@/lib/utils';
+
+// =============================================================================
+// TYPES
+// =============================================================================
+
+export interface FormFieldProps<
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath
+> {
+ // Form control
+ control: Control;
+ name: TName;
+
+ // Field configuration
+ label?: string;
+ placeholder?: string;
+ type?: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url';
+ required?: boolean;
+
+ // Field type
+ fieldType?: 'input' | 'textarea' | 'select';
+
+ // Styling
+ className?: string;
+ labelClassName?: string;
+ inputClassName?: string;
+ errorClassName?: string;
+
+ // Validation
+ validation?: {
+ required?: boolean | string;
+ minLength?: { value: number; message: string };
+ maxLength?: { value: number; message: string };
+ pattern?: { value: RegExp; message: string };
+ };
+
+ // Additional props
+ disabled?: boolean;
+ readOnly?: boolean;
+ autoComplete?: string;
+ size?: 'sm' | 'md' | 'lg';
+
+ // Custom render function
+ render?: (props: {
+ field: any;
+ fieldState: { error?: FieldError };
+ }) => React.ReactElement;
+}
+
+// =============================================================================
+// COMPONENT
+// =============================================================================
+
+export function FormField<
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath
+>({
+ control,
+ name,
+ label,
+ placeholder,
+ type = 'text',
+ required = false,
+ fieldType = 'input',
+ className = '',
+ labelClassName = '',
+ inputClassName = '',
+ errorClassName = '',
+ validation,
+ disabled = false,
+ readOnly = false,
+ autoComplete,
+ size = 'md',
+ render,
+}: FormFieldProps) {
+
+ // =============================================================================
+ // HELPER FUNCTIONS
+ // =============================================================================
+
+ const getInputSize = (size: 'sm' | 'md' | 'lg') => {
+ switch (size) {
+ case 'sm':
+ return 'h-8 text-sm';
+ case 'lg':
+ return 'h-12 text-base';
+ default:
+ return 'h-10 text-sm';
+ }
+ };
+
+ const getTextareaSize = (size: 'sm' | 'md' | 'lg') => {
+ switch (size) {
+ case 'sm':
+ return 'min-h-[80px] text-sm';
+ case 'lg':
+ return 'min-h-[120px] text-base';
+ default:
+ return 'min-h-[100px] text-sm';
+ }
+ };
+
+ // =============================================================================
+ // RENDER FUNCTIONS
+ // =============================================================================
+
+ const renderInput = (field: any, fieldState: { error?: FieldError }) => {
+ const inputProps = {
+ ...field,
+ type,
+ placeholder,
+ disabled,
+ readOnly,
+ autoComplete,
+ className: cn(
+ 'w-full transition-colors',
+ getInputSize(size),
+ fieldState.error && 'border-destructive focus:border-destructive',
+ inputClassName
+ ),
+ };
+
+ return ;
+ };
+
+ const renderTextarea = (field: any, fieldState: { error?: FieldError }) => {
+ const textareaProps = {
+ ...field,
+ placeholder,
+ disabled,
+ readOnly,
+ autoComplete,
+ className: cn(
+ 'w-full resize-none transition-colors',
+ getTextareaSize(size),
+ fieldState.error && 'border-destructive focus:border-destructive',
+ inputClassName
+ ),
+ };
+
+ return ;
+ };
+
+ const renderField = (field: any, fieldState: { error?: FieldError }) => {
+ if (render) {
+ return render({ field, fieldState });
+ }
+
+ switch (fieldType) {
+ case 'textarea':
+ return renderTextarea(field, fieldState);
+ case 'select':
+ // Select component will be handled by FormSelect component
+ return renderInput(field, fieldState);
+ default:
+ return renderInput(field, fieldState);
+ }
+ };
+
+ // =============================================================================
+ // MAIN RENDER
+ // =============================================================================
+
+ return (
+
+ {/* Label */}
+ {label && (
+
+ )}
+
+ {/* Controller */}
+
(
+
+ {renderField(field, fieldState)}
+
+ {/* Error Message */}
+ {fieldState.error && (
+
+ {fieldState.error.message}
+
+ )}
+
+ )}
+ />
+
+ );
+}
+
+export default FormField;
\ No newline at end of file
diff --git a/components/form/shared/form-grid.tsx b/components/form/shared/form-grid.tsx
new file mode 100644
index 00000000..8c30ae99
--- /dev/null
+++ b/components/form/shared/form-grid.tsx
@@ -0,0 +1,271 @@
+/**
+ * FormGrid Component
+ *
+ * A reusable grid layout component that:
+ * - Provides responsive grid layouts for form fields
+ * - Handles different column configurations
+ * - Supports gap spacing
+ * - Maintains consistent alignment
+ *
+ * This component improves form layout and responsiveness.
+ */
+
+import React from 'react';
+import { cn } from '@/lib/utils';
+
+// =============================================================================
+// TYPES
+// =============================================================================
+
+export interface FormGridProps {
+ // Grid configuration
+ cols?: 1 | 2 | 3 | 4 | 6 | 12;
+ sm?: 1 | 2 | 3 | 4 | 6 | 12;
+ md?: 1 | 2 | 3 | 4 | 6 | 12;
+ lg?: 1 | 2 | 3 | 4 | 6 | 12;
+ xl?: 1 | 2 | 3 | 4 | 6 | 12;
+
+ // Spacing
+ gap?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
+ gapX?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
+ gapY?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
+
+ // Alignment
+ align?: 'start' | 'center' | 'end' | 'stretch';
+ justify?: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly';
+
+ // Styling
+ className?: string;
+
+ // Children
+ children: React.ReactNode;
+}
+
+// =============================================================================
+// COMPONENT
+// =============================================================================
+
+export function FormGrid({
+ cols = 1,
+ sm,
+ md,
+ lg,
+ xl,
+ gap = 'md',
+ gapX,
+ gapY,
+ align = 'start',
+ justify = 'start',
+ className = '',
+ children,
+}: FormGridProps) {
+
+ // =============================================================================
+ // HELPER FUNCTIONS
+ // =============================================================================
+
+ const getGridCols = (cols: number) => {
+ return `grid-cols-${cols}`;
+ };
+
+ const getResponsiveCols = () => {
+ const classes = [getGridCols(cols)];
+
+ if (sm) classes.push(`sm:grid-cols-${sm}`);
+ if (md) classes.push(`md:grid-cols-${md}`);
+ if (lg) classes.push(`lg:grid-cols-${lg}`);
+ if (xl) classes.push(`xl:grid-cols-${xl}`);
+
+ return classes.join(' ');
+ };
+
+ const getGap = (gap: 'xs' | 'sm' | 'md' | 'lg' | 'xl') => {
+ switch (gap) {
+ case 'xs':
+ return 'gap-1';
+ case 'sm':
+ return 'gap-2';
+ case 'lg':
+ return 'gap-6';
+ case 'xl':
+ return 'gap-8';
+ default:
+ return 'gap-4';
+ }
+ };
+
+ const getGapX = (gapX: 'xs' | 'sm' | 'md' | 'lg' | 'xl') => {
+ switch (gapX) {
+ case 'xs':
+ return 'gap-x-1';
+ case 'sm':
+ return 'gap-x-2';
+ case 'lg':
+ return 'gap-x-6';
+ case 'xl':
+ return 'gap-x-8';
+ default:
+ return 'gap-x-4';
+ }
+ };
+
+ const getGapY = (gapY: 'xs' | 'sm' | 'md' | 'lg' | 'xl') => {
+ switch (gapY) {
+ case 'xs':
+ return 'gap-y-1';
+ case 'sm':
+ return 'gap-y-2';
+ case 'lg':
+ return 'gap-y-6';
+ case 'xl':
+ return 'gap-y-8';
+ default:
+ return 'gap-y-4';
+ }
+ };
+
+ const getAlign = (align: 'start' | 'center' | 'end' | 'stretch') => {
+ switch (align) {
+ case 'center':
+ return 'items-center';
+ case 'end':
+ return 'items-end';
+ case 'stretch':
+ return 'items-stretch';
+ default:
+ return 'items-start';
+ }
+ };
+
+ const getJustify = (justify: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly') => {
+ switch (justify) {
+ case 'center':
+ return 'justify-center';
+ case 'end':
+ return 'justify-end';
+ case 'between':
+ return 'justify-between';
+ case 'around':
+ return 'justify-around';
+ case 'evenly':
+ return 'justify-evenly';
+ default:
+ return 'justify-start';
+ }
+ };
+
+ // =============================================================================
+ // MAIN RENDER
+ // =============================================================================
+
+ const gridClasses = cn(
+ 'grid',
+ getResponsiveCols(),
+ gapX ? getGapX(gapX) : gapY ? getGapY(gapY) : getGap(gap),
+ getAlign(align),
+ getJustify(justify),
+ className
+ );
+
+ return (
+
+ {children}
+
+ );
+}
+
+// =============================================================================
+// GRID ITEM COMPONENT
+// =============================================================================
+
+export interface FormGridItemProps {
+ // Span configuration
+ span?: 1 | 2 | 3 | 4 | 6 | 12;
+ sm?: 1 | 2 | 3 | 4 | 6 | 12;
+ md?: 1 | 2 | 3 | 4 | 6 | 12;
+ lg?: 1 | 2 | 3 | 4 | 6 | 12;
+ xl?: 1 | 2 | 3 | 4 | 6 | 12;
+
+ // Alignment
+ align?: 'start' | 'center' | 'end' | 'stretch';
+ justify?: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly';
+
+ // Styling
+ className?: string;
+
+ // Children
+ children: React.ReactNode;
+}
+
+export function FormGridItem({
+ span = 1,
+ sm,
+ md,
+ lg,
+ xl,
+ align,
+ justify,
+ className = '',
+ children,
+}: FormGridItemProps) {
+
+ const getSpan = (span: number) => {
+ return `col-span-${span}`;
+ };
+
+ const getResponsiveSpan = () => {
+ const classes = [getSpan(span)];
+
+ if (sm) classes.push(`sm:col-span-${sm}`);
+ if (md) classes.push(`md:col-span-${md}`);
+ if (lg) classes.push(`lg:col-span-${lg}`);
+ if (xl) classes.push(`xl:col-span-${xl}`);
+
+ return classes.join(' ');
+ };
+
+ const getAlign = (align: 'start' | 'center' | 'end' | 'stretch') => {
+ switch (align) {
+ case 'center':
+ return 'self-center';
+ case 'end':
+ return 'self-end';
+ case 'stretch':
+ return 'self-stretch';
+ default:
+ return 'self-start';
+ }
+ };
+
+ const getJustify = (justify: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly') => {
+ switch (justify) {
+ case 'center':
+ return 'justify-self-center';
+ case 'end':
+ return 'justify-self-end';
+ case 'between':
+ return 'justify-self-stretch';
+ case 'around':
+ return 'justify-self-stretch';
+ case 'evenly':
+ return 'justify-self-stretch';
+ default:
+ return 'justify-self-start';
+ }
+ };
+
+ const itemClasses = cn(
+ getResponsiveSpan(),
+ align && getAlign(align),
+ justify && getJustify(justify),
+ className
+ );
+
+ return (
+
+ {children}
+
+ );
+}
+
+export default FormGrid;
\ No newline at end of file
diff --git a/components/form/shared/form-radio.tsx b/components/form/shared/form-radio.tsx
new file mode 100644
index 00000000..6a14a4c2
--- /dev/null
+++ b/components/form/shared/form-radio.tsx
@@ -0,0 +1,210 @@
+/**
+ * FormRadio Component
+ *
+ * A reusable radio button component that handles:
+ * - Radio button groups
+ * - Label
+ * - Controller (react-hook-form)
+ * - Error handling
+ *
+ * This component reduces code duplication for radio button fields across forms.
+ */
+
+import React from 'react';
+import { Control, Controller, FieldError, FieldPath, FieldValues } from 'react-hook-form';
+import { Label } from '@/components/ui/label';
+import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
+import { cn } from '@/lib/utils';
+
+// =============================================================================
+// TYPES
+// =============================================================================
+
+export interface RadioOption {
+ value: string | number;
+ label: string;
+ disabled?: boolean;
+}
+
+export interface FormRadioProps<
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath
+> {
+ // Form control
+ control: Control;
+ name: TName;
+
+ // Field configuration
+ label?: string;
+ required?: boolean;
+
+ // Radio configuration
+ options: RadioOption[];
+
+ // Styling
+ className?: string;
+ labelClassName?: string;
+ radioClassName?: string;
+ errorClassName?: string;
+ groupClassName?: string;
+
+ // Layout
+ layout?: 'horizontal' | 'vertical';
+ columns?: number; // For grid layout
+
+ // Validation
+ validation?: {
+ required?: boolean | string;
+ };
+
+ // Additional props
+ disabled?: boolean;
+ size?: 'sm' | 'md' | 'lg';
+
+ // Custom render functions
+ renderOption?: (option: RadioOption, checked: boolean, onChange: (value: string) => void) => React.ReactElement;
+}
+
+// =============================================================================
+// COMPONENT
+// =============================================================================
+
+export function FormRadio<
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath
+>({
+ control,
+ name,
+ label,
+ required = false,
+ options,
+ className = '',
+ labelClassName = '',
+ radioClassName = '',
+ errorClassName = '',
+ groupClassName = '',
+ layout = 'horizontal',
+ columns = 1,
+ validation,
+ disabled = false,
+ size = 'md',
+ renderOption,
+}: FormRadioProps) {
+
+ // =============================================================================
+ // HELPER FUNCTIONS
+ // =============================================================================
+
+ const getRadioSize = (size: 'sm' | 'md' | 'lg') => {
+ switch (size) {
+ case 'sm':
+ return 'h-4 w-4';
+ case 'lg':
+ return 'h-6 w-6';
+ default:
+ return 'h-5 w-5';
+ }
+ };
+
+ const getGroupLayout = () => {
+ if (layout === 'vertical') {
+ return 'flex flex-col gap-2';
+ }
+
+ if (columns > 1) {
+ return `grid grid-cols-${columns} gap-2`;
+ }
+
+ return 'flex flex-wrap gap-3';
+ };
+
+ // =============================================================================
+ // RENDER FUNCTIONS
+ // =============================================================================
+
+ const renderDefaultOption = (option: RadioOption, checked: boolean, onChange: (value: string) => void) => {
+ if (renderOption) {
+ return renderOption(option, checked, onChange);
+ }
+
+ return (
+
+
+
+
+ );
+ };
+
+ // =============================================================================
+ // MAIN RENDER
+ // =============================================================================
+
+ return (
+
+ {/* Group Label */}
+ {label && (
+
+ )}
+
+ {/* Controller */}
+
(
+
+
+ {options.map((option) =>
+ renderDefaultOption(
+ option,
+ field.value === String(option.value),
+ field.onChange
+ )
+ )}
+
+
+ {/* Error Message */}
+ {fieldState.error && (
+
+ {fieldState.error.message}
+
+ )}
+
+ )}
+ />
+
+ );
+}
+
+export default FormRadio;
\ No newline at end of file
diff --git a/components/form/shared/form-section.tsx b/components/form/shared/form-section.tsx
new file mode 100644
index 00000000..30830132
--- /dev/null
+++ b/components/form/shared/form-section.tsx
@@ -0,0 +1,184 @@
+/**
+ * FormSection Component
+ *
+ * A reusable form section component that:
+ * - Groups related form fields
+ * - Provides consistent section headers
+ * - Handles section styling and spacing
+ * - Supports collapsible sections
+ *
+ * This component improves form organization and readability.
+ */
+
+import React, { useState } from 'react';
+import { Card } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { ChevronDown, ChevronRight } from 'lucide-react';
+import { cn } from '@/lib/utils';
+
+// =============================================================================
+// TYPES
+// =============================================================================
+
+export interface FormSectionProps {
+ // Section configuration
+ title?: string;
+ description?: string;
+ collapsible?: boolean;
+ defaultExpanded?: boolean;
+
+ // Styling
+ className?: string;
+ headerClassName?: string;
+ contentClassName?: string;
+ titleClassName?: string;
+ descriptionClassName?: string;
+
+ // Layout
+ variant?: 'default' | 'bordered' | 'minimal';
+ spacing?: 'sm' | 'md' | 'lg';
+
+ // Actions
+ actions?: React.ReactNode;
+
+ // Children
+ children: React.ReactNode;
+}
+
+// =============================================================================
+// COMPONENT
+// =============================================================================
+
+export function FormSection({
+ title,
+ description,
+ collapsible = false,
+ defaultExpanded = true,
+ className = '',
+ headerClassName = '',
+ contentClassName = '',
+ titleClassName = '',
+ descriptionClassName = '',
+ variant = 'default',
+ spacing = 'md',
+ actions,
+ children,
+}: FormSectionProps) {
+
+ // =============================================================================
+ // STATE
+ // =============================================================================
+
+ const [isExpanded, setIsExpanded] = useState(defaultExpanded);
+
+ // =============================================================================
+ // HELPER FUNCTIONS
+ // =============================================================================
+
+ const getSpacing = (spacing: 'sm' | 'md' | 'lg') => {
+ switch (spacing) {
+ case 'sm':
+ return 'space-y-3';
+ case 'lg':
+ return 'space-y-6';
+ default:
+ return 'space-y-4';
+ }
+ };
+
+ const getVariantStyles = (variant: 'default' | 'bordered' | 'minimal') => {
+ switch (variant) {
+ case 'bordered':
+ return 'border border-border rounded-lg p-4';
+ case 'minimal':
+ return '';
+ default:
+ return 'bg-card rounded-lg p-4 shadow-sm';
+ }
+ };
+
+ // =============================================================================
+ // RENDER FUNCTIONS
+ // =============================================================================
+
+ const renderHeader = () => {
+ if (!title && !description && !actions) return null;
+
+ return (
+
+
+ {title && (
+
+ {collapsible && (
+
+ )}
+
+
+ {title}
+
+
+ )}
+
+ {description && (
+
+ {description}
+
+ )}
+
+
+ {actions && (
+
+ {actions}
+
+ )}
+
+ );
+ };
+
+ const renderContent = () => {
+ if (collapsible && !isExpanded) return null;
+
+ return (
+
+ {children}
+
+ );
+ };
+
+ // =============================================================================
+ // MAIN RENDER
+ // =============================================================================
+
+ const sectionContent = (
+
+ {renderHeader()}
+ {renderContent()}
+
+ );
+
+ // Wrap in Card for default variant
+ if (variant === 'default') {
+ return {sectionContent};
+ }
+
+ return sectionContent;
+}
+
+export default FormSection;
\ No newline at end of file
diff --git a/components/form/shared/form-select.tsx b/components/form/shared/form-select.tsx
new file mode 100644
index 00000000..f3b2f26c
--- /dev/null
+++ b/components/form/shared/form-select.tsx
@@ -0,0 +1,215 @@
+/**
+ * FormSelect Component
+ *
+ * A reusable select/dropdown component that handles:
+ * - Label
+ * - Controller (react-hook-form)
+ * - Select with options
+ * - Error handling
+ * - Loading states
+ *
+ * This component reduces code duplication for select fields across forms.
+ */
+
+import React from 'react';
+import { Control, Controller, FieldError, FieldPath, FieldValues } from 'react-hook-form';
+import { Label } from '@/components/ui/label';
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select';
+import { cn } from '@/lib/utils';
+
+// =============================================================================
+// TYPES
+// =============================================================================
+
+export interface SelectOption {
+ value: string | number;
+ label: string;
+ disabled?: boolean;
+}
+
+export interface FormSelectProps<
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath
+> {
+ // Form control
+ control: Control;
+ name: TName;
+
+ // Field configuration
+ label?: string;
+ placeholder?: string;
+ required?: boolean;
+
+ // Options
+ options: SelectOption[];
+
+ // Styling
+ className?: string;
+ labelClassName?: string;
+ selectClassName?: string;
+ errorClassName?: string;
+
+ // Validation
+ validation?: {
+ required?: boolean | string;
+ };
+
+ // Additional props
+ disabled?: boolean;
+ size?: 'sm' | 'md' | 'lg';
+ loading?: boolean;
+
+ // Custom render functions
+ renderOption?: (option: SelectOption) => React.ReactElement;
+ renderTrigger?: (props: {
+ field: any;
+ fieldState: { error?: FieldError };
+ }) => React.ReactElement;
+}
+
+// =============================================================================
+// COMPONENT
+// =============================================================================
+
+export function FormSelect<
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath
+>({
+ control,
+ name,
+ label,
+ placeholder = 'Select an option',
+ required = false,
+ options,
+ className = '',
+ labelClassName = '',
+ selectClassName = '',
+ errorClassName = '',
+ validation,
+ disabled = false,
+ size = 'md',
+ loading = false,
+ renderOption,
+ renderTrigger,
+}: FormSelectProps) {
+
+ // =============================================================================
+ // HELPER FUNCTIONS
+ // =============================================================================
+
+ const getSelectSize = (size: 'sm' | 'md' | 'lg') => {
+ switch (size) {
+ case 'sm':
+ return 'h-8 text-sm';
+ case 'lg':
+ return 'h-12 text-base';
+ default:
+ return 'h-10 text-sm';
+ }
+ };
+
+ // =============================================================================
+ // RENDER FUNCTIONS
+ // =============================================================================
+
+ const renderDefaultOption = (option: SelectOption) => (
+
+ {option.label}
+
+ );
+
+ const renderDefaultTrigger = (field: any, fieldState: { error?: FieldError }) => (
+
+
+
+ );
+
+ // =============================================================================
+ // MAIN RENDER
+ // =============================================================================
+
+ return (
+
+ {/* Label */}
+ {label && (
+
+ )}
+
+ {/* Controller */}
+
(
+
+
+
+ {/* Error Message */}
+ {fieldState.error && (
+
+ {fieldState.error.message}
+
+ )}
+
+ )}
+ />
+
+ );
+}
+
+export default FormSelect;
\ No newline at end of file
diff --git a/components/form/shared/index.ts b/components/form/shared/index.ts
new file mode 100644
index 00000000..67b4abaf
--- /dev/null
+++ b/components/form/shared/index.ts
@@ -0,0 +1,32 @@
+/**
+ * Form Components Index
+ *
+ * This file exports all reusable form components for easy importing.
+ * These components reduce code duplication and improve consistency across forms.
+ */
+
+// Core form field components
+export { default as FormField } from './form-field';
+export type { FormFieldProps } from './form-field';
+
+export { default as FormSelect } from './form-select';
+export type { FormSelectProps, SelectOption } from './form-select';
+
+export { default as FormCheckbox } from './form-checkbox';
+export type { FormCheckboxProps, CheckboxOption } from './form-checkbox';
+
+export { default as FormRadio } from './form-radio';
+export type { FormRadioProps, RadioOption } from './form-radio';
+
+export { default as FormDatePicker } from './form-date-picker';
+export type { FormDatePickerProps } from './form-date-picker';
+
+// Layout components
+export { default as FormSection } from './form-section';
+export type { FormSectionProps } from './form-section';
+
+export { default as FormGrid, FormGridItem } from './form-grid';
+export type { FormGridProps, FormGridItemProps } from './form-grid';
+
+// Re-export commonly used types
+export type { FieldValues, FieldPath, Control } from 'react-hook-form';
\ No newline at end of file
diff --git a/components/landing-page/event-calender.tsx b/components/landing-page/event-calender.tsx
index 32252217..fb7ffca4 100644
--- a/components/landing-page/event-calender.tsx
+++ b/components/landing-page/event-calender.tsx
@@ -315,11 +315,11 @@ const EventCalender = () => {
-