mirror of
https://github.com/Oak-and-Sprout/sprout-track.git
synced 2026-02-09 09:09:16 -06:00
updates to calendar items to use family auth context
This commit is contained in:
@@ -4,7 +4,6 @@ import { ApiResponse } from '../types';
|
||||
import { CalendarEventType, RecurrencePattern } from '@prisma/client';
|
||||
import { withAuthContext, AuthResult } from '../utils/auth';
|
||||
import { toUTC, formatForResponse } from '../utils/timezone';
|
||||
import { getFamilyIdFromRequest } from '../utils/family';
|
||||
|
||||
// Type for calendar event response
|
||||
interface CalendarEventResponse {
|
||||
@@ -46,6 +45,7 @@ interface CalendarEventResponse {
|
||||
address: string | null;
|
||||
notes: string | null;
|
||||
}>;
|
||||
contactIds: string[];
|
||||
}
|
||||
|
||||
// Type for calendar event create/update
|
||||
@@ -71,6 +71,11 @@ interface CalendarEventCreate {
|
||||
|
||||
async function handleGet(req: NextRequest, authContext: AuthResult) {
|
||||
try {
|
||||
const { familyId: userFamilyId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
return NextResponse.json<ApiResponse<null>>({ success: false, error: 'User is not associated with a family.' }, { status: 403 });
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(req.url);
|
||||
const id = searchParams.get('id');
|
||||
const babyId = searchParams.get('babyId');
|
||||
@@ -81,19 +86,12 @@ async function handleGet(req: NextRequest, authContext: AuthResult) {
|
||||
const typeParam = searchParams.get('type');
|
||||
const recurringParam = searchParams.get('recurring');
|
||||
|
||||
// Get family ID from request
|
||||
const familyId = await getFamilyIdFromRequest(req);
|
||||
|
||||
// Build where clause
|
||||
const where: any = {
|
||||
deletedAt: null,
|
||||
familyId: userFamilyId,
|
||||
};
|
||||
|
||||
// Add family filter if available
|
||||
if (familyId) {
|
||||
where.familyId = familyId;
|
||||
}
|
||||
|
||||
// Add filters
|
||||
if (id) {
|
||||
where.id = id;
|
||||
@@ -140,8 +138,8 @@ async function handleGet(req: NextRequest, authContext: AuthResult) {
|
||||
|
||||
// If ID is provided, fetch a single event
|
||||
if (id) {
|
||||
const event = await prisma.calendarEvent.findUnique({
|
||||
where: { id },
|
||||
const event = await prisma.calendarEvent.findFirst({
|
||||
where: { id, familyId: userFamilyId },
|
||||
include: {
|
||||
babies: {
|
||||
include: {
|
||||
@@ -177,18 +175,7 @@ async function handleGet(req: NextRequest, authContext: AuthResult) {
|
||||
return NextResponse.json<ApiResponse<CalendarEventResponse>>(
|
||||
{
|
||||
success: false,
|
||||
error: 'Calendar event not found',
|
||||
},
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
// Check family access
|
||||
if (familyId && event.familyId !== familyId) {
|
||||
return NextResponse.json<ApiResponse<CalendarEventResponse>>(
|
||||
{
|
||||
success: false,
|
||||
error: 'Calendar event not found',
|
||||
error: 'Calendar event not found or access denied',
|
||||
},
|
||||
{ status: 404 }
|
||||
);
|
||||
@@ -206,6 +193,7 @@ async function handleGet(req: NextRequest, authContext: AuthResult) {
|
||||
babies: event.babies.map(be => be.baby),
|
||||
caretakers: event.caretakers.map(ce => ce.caretaker),
|
||||
contacts: event.contacts.map(ce => ce.contact),
|
||||
contactIds: event.contacts.map(ce => ce.contact.id),
|
||||
};
|
||||
|
||||
return NextResponse.json<ApiResponse<CalendarEventResponse>>({
|
||||
@@ -263,6 +251,7 @@ async function handleGet(req: NextRequest, authContext: AuthResult) {
|
||||
babies: event.babies.map(be => be.baby),
|
||||
caretakers: event.caretakers.map(ce => ce.caretaker),
|
||||
contacts: event.contacts.map(ce => ce.contact),
|
||||
contactIds: event.contacts.map(ce => ce.contact.id),
|
||||
}));
|
||||
|
||||
return NextResponse.json<ApiResponse<CalendarEventResponse[]>>({
|
||||
@@ -283,26 +272,59 @@ async function handleGet(req: NextRequest, authContext: AuthResult) {
|
||||
|
||||
async function handlePost(req: NextRequest, authContext: AuthResult) {
|
||||
try {
|
||||
const { familyId: userFamilyId, caretakerId: userCaretakerId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
return NextResponse.json<ApiResponse<null>>({ success: false, error: 'User is not associated with a family.' }, { status: 403 });
|
||||
}
|
||||
|
||||
const body: CalendarEventCreate = await req.json();
|
||||
|
||||
// Validate required fields
|
||||
if (!body.title || !body.startTime || body.type === undefined || body.allDay === undefined) {
|
||||
return NextResponse.json<ApiResponse<CalendarEventResponse>>(
|
||||
{
|
||||
success: false,
|
||||
error: 'Missing required fields',
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
return NextResponse.json<ApiResponse<null>>({ success: false, error: 'Missing required fields' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Validate that all associated entities belong to the user's family
|
||||
if (body.babyIds.length > 0) {
|
||||
const babiesCount = await prisma.baby.count({
|
||||
where: {
|
||||
id: { in: body.babyIds },
|
||||
familyId: userFamilyId,
|
||||
},
|
||||
});
|
||||
if (babiesCount !== body.babyIds.length) {
|
||||
return NextResponse.json<ApiResponse<null>>({ success: false, error: 'One or more babies not found in this family.' }, { status: 404 });
|
||||
}
|
||||
}
|
||||
|
||||
if (body.caretakerIds.length > 0) {
|
||||
const caretakersCount = await prisma.caretaker.count({
|
||||
where: {
|
||||
id: { in: body.caretakerIds },
|
||||
familyId: userFamilyId,
|
||||
},
|
||||
});
|
||||
if (caretakersCount !== body.caretakerIds.length) {
|
||||
return NextResponse.json<ApiResponse<null>>({ success: false, error: 'One or more caretakers not found in this family.' }, { status: 404 });
|
||||
}
|
||||
}
|
||||
|
||||
if (body.contactIds.length > 0) {
|
||||
const contactsCount = await prisma.contact.count({
|
||||
where: {
|
||||
id: { in: body.contactIds },
|
||||
familyId: userFamilyId,
|
||||
},
|
||||
});
|
||||
if (contactsCount !== body.contactIds.length) {
|
||||
return NextResponse.json<ApiResponse<null>>({ success: false, error: 'One or more contacts not found in this family.' }, { status: 404 });
|
||||
}
|
||||
}
|
||||
|
||||
// Get family ID from request (with fallback to body)
|
||||
const familyId = await getFamilyIdFromRequest(req) || body.familyId;
|
||||
|
||||
// Convert dates to UTC for storage
|
||||
const startTimeUTC = toUTC(body.startTime);
|
||||
const endTimeUTC = body.endTime ? toUTC(body.endTime) : undefined;
|
||||
const recurrenceEndUTC = body.recurrenceEnd ? toUTC(body.recurrenceEnd) : undefined;
|
||||
const endTimeUTC = body.endTime ? toUTC(body.endTime) : null;
|
||||
const recurrenceEndUTC = body.recurrenceEnd ? toUTC(body.recurrenceEnd) : null;
|
||||
|
||||
// Create event
|
||||
const event = await prisma.calendarEvent.create({
|
||||
@@ -310,7 +332,7 @@ async function handlePost(req: NextRequest, authContext: AuthResult) {
|
||||
title: body.title,
|
||||
description: body.description || null,
|
||||
startTime: startTimeUTC,
|
||||
endTime: endTimeUTC || null,
|
||||
endTime: endTimeUTC,
|
||||
allDay: body.allDay,
|
||||
type: body.type,
|
||||
location: body.location || null,
|
||||
@@ -321,23 +343,17 @@ async function handlePost(req: NextRequest, authContext: AuthResult) {
|
||||
customRecurrence: body.customRecurrence || null,
|
||||
reminderTime: body.reminderTime || null,
|
||||
notificationSent: false,
|
||||
familyId: familyId || null,
|
||||
familyId: userFamilyId || null,
|
||||
|
||||
// Create relationships
|
||||
babies: {
|
||||
create: (body.babyIds || []).map(babyId => ({
|
||||
baby: { connect: { id: babyId } },
|
||||
})),
|
||||
create: body.babyIds.map(babyId => ({ babyId })),
|
||||
},
|
||||
caretakers: {
|
||||
create: (body.caretakerIds || []).map(caretakerId => ({
|
||||
caretaker: { connect: { id: caretakerId } },
|
||||
})),
|
||||
create: body.caretakerIds.map(caretakerId => ({ caretakerId })),
|
||||
},
|
||||
contacts: {
|
||||
create: (body.contactIds || []).map(contactId => ({
|
||||
contact: { connect: { id: contactId } },
|
||||
})),
|
||||
create: body.contactIds.map(contactId => ({ contactId })),
|
||||
},
|
||||
},
|
||||
include: {
|
||||
@@ -383,6 +399,7 @@ async function handlePost(req: NextRequest, authContext: AuthResult) {
|
||||
babies: event.babies.map(be => be.baby),
|
||||
caretakers: event.caretakers.map(ce => ce.caretaker),
|
||||
contacts: event.contacts.map(ce => ce.contact),
|
||||
contactIds: event.contacts.map(ce => ce.contact.id),
|
||||
};
|
||||
|
||||
return NextResponse.json<ApiResponse<CalendarEventResponse>>({
|
||||
@@ -391,7 +408,7 @@ async function handlePost(req: NextRequest, authContext: AuthResult) {
|
||||
}, { status: 201 });
|
||||
} catch (error) {
|
||||
console.error('Error creating calendar event:', error);
|
||||
return NextResponse.json<ApiResponse<CalendarEventResponse>>(
|
||||
return NextResponse.json<ApiResponse<null>>(
|
||||
{
|
||||
success: false,
|
||||
error: 'Failed to create calendar event',
|
||||
@@ -403,51 +420,57 @@ async function handlePost(req: NextRequest, authContext: AuthResult) {
|
||||
|
||||
async function handlePut(req: NextRequest, authContext: AuthResult) {
|
||||
try {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const id = searchParams.get('id');
|
||||
const body: CalendarEventCreate = await req.json();
|
||||
|
||||
if (!id) {
|
||||
return NextResponse.json<ApiResponse<CalendarEventResponse>>(
|
||||
{
|
||||
success: false,
|
||||
error: 'Calendar event ID is required',
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
const { familyId: userFamilyId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
return NextResponse.json<ApiResponse<null>>({ success: false, error: 'User is not associated with a family.' }, { status: 403 });
|
||||
}
|
||||
|
||||
// Get family ID from request (with fallback to body)
|
||||
const familyId = await getFamilyIdFromRequest(req) || body.familyId;
|
||||
const { searchParams } = new URL(req.url);
|
||||
const id = searchParams.get('id');
|
||||
const body: Partial<CalendarEventCreate> = await req.json();
|
||||
|
||||
// Check if event exists and belongs to the family
|
||||
const existingEvent = await prisma.calendarEvent.findUnique({
|
||||
where: { id },
|
||||
if (!id) {
|
||||
return NextResponse.json<ApiResponse<null>>({ success: false, error: 'Calendar event ID is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
const existingEvent = await prisma.calendarEvent.findFirst({
|
||||
where: { id, familyId: userFamilyId },
|
||||
});
|
||||
|
||||
if (!existingEvent || existingEvent.deletedAt) {
|
||||
return NextResponse.json<ApiResponse<CalendarEventResponse>>(
|
||||
{
|
||||
success: false,
|
||||
error: 'Calendar event not found',
|
||||
},
|
||||
{ status: 404 }
|
||||
);
|
||||
return NextResponse.json<ApiResponse<null>>({ success: false, error: 'Calendar event not found or access denied' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Check family access
|
||||
if (familyId && existingEvent.familyId !== familyId) {
|
||||
return NextResponse.json<ApiResponse<CalendarEventResponse>>(
|
||||
{
|
||||
success: false,
|
||||
error: 'Calendar event not found',
|
||||
},
|
||||
{ status: 404 }
|
||||
);
|
||||
// Validate associated entities
|
||||
if (body.babyIds && body.babyIds.length > 0) {
|
||||
const babiesCount = await prisma.baby.count({
|
||||
where: { id: { in: body.babyIds }, familyId: userFamilyId },
|
||||
});
|
||||
if (babiesCount !== body.babyIds.length) {
|
||||
return NextResponse.json<ApiResponse<null>>({ success: false, error: 'One or more babies not found in this family.' }, { status: 404 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (body.caretakerIds && body.caretakerIds.length > 0) {
|
||||
const caretakersCount = await prisma.caretaker.count({
|
||||
where: { id: { in: body.caretakerIds }, familyId: userFamilyId },
|
||||
});
|
||||
if (caretakersCount !== body.caretakerIds.length) {
|
||||
return NextResponse.json<ApiResponse<null>>({ success: false, error: 'One or more caretakers not found in this family.' }, { status: 404 });
|
||||
}
|
||||
}
|
||||
|
||||
if (body.contactIds && body.contactIds.length > 0) {
|
||||
const contactsCount = await prisma.contact.count({
|
||||
where: { id: { in: body.contactIds }, familyId: userFamilyId },
|
||||
});
|
||||
if (contactsCount !== body.contactIds.length) {
|
||||
return NextResponse.json<ApiResponse<null>>({ success: false, error: 'One or more contacts not found in this family.' }, { status: 404 });
|
||||
}
|
||||
}
|
||||
|
||||
// Convert dates to UTC for storage
|
||||
const startTimeUTC = toUTC(body.startTime);
|
||||
const startTimeUTC = body.startTime ? toUTC(body.startTime) : undefined;
|
||||
const endTimeUTC = body.endTime ? toUTC(body.endTime) : undefined;
|
||||
const recurrenceEndUTC = body.recurrenceEnd ? toUTC(body.recurrenceEnd) : undefined;
|
||||
|
||||
@@ -475,24 +498,21 @@ async function handlePut(req: NextRequest, authContext: AuthResult) {
|
||||
recurrenceEnd: recurrenceEndUTC || null,
|
||||
customRecurrence: body.customRecurrence || null,
|
||||
reminderTime: body.reminderTime || null,
|
||||
familyId: familyId || existingEvent.familyId, // Preserve existing familyId if not provided
|
||||
familyId: userFamilyId || existingEvent.familyId, // Preserve existing familyId if not provided
|
||||
|
||||
// Create new relationships
|
||||
babies: {
|
||||
create: (body.babyIds || []).map(babyId => ({
|
||||
baby: { connect: { id: babyId } },
|
||||
})),
|
||||
},
|
||||
caretakers: {
|
||||
create: (body.caretakerIds || []).map(caretakerId => ({
|
||||
caretaker: { connect: { id: caretakerId } },
|
||||
})),
|
||||
},
|
||||
contacts: {
|
||||
create: (body.contactIds || []).map(contactId => ({
|
||||
contact: { connect: { id: contactId } },
|
||||
})),
|
||||
},
|
||||
babies: body.babyIds ? {
|
||||
deleteMany: {},
|
||||
create: body.babyIds.map(babyId => ({ babyId })),
|
||||
} : undefined,
|
||||
caretakers: body.caretakerIds ? {
|
||||
deleteMany: {},
|
||||
create: body.caretakerIds.map(caretakerId => ({ caretakerId })),
|
||||
} : undefined,
|
||||
contacts: body.contactIds ? {
|
||||
deleteMany: {},
|
||||
create: body.contactIds.map(contactId => ({ contactId })),
|
||||
} : undefined,
|
||||
},
|
||||
include: {
|
||||
babies: {
|
||||
@@ -540,6 +560,7 @@ async function handlePut(req: NextRequest, authContext: AuthResult) {
|
||||
babies: updatedEvent.babies.map(be => be.baby),
|
||||
caretakers: updatedEvent.caretakers.map(ce => ce.caretaker),
|
||||
contacts: updatedEvent.contacts.map(ce => ce.contact),
|
||||
contactIds: updatedEvent.contacts.map(ce => ce.contact.id),
|
||||
};
|
||||
|
||||
return NextResponse.json<ApiResponse<CalendarEventResponse>>({
|
||||
@@ -548,7 +569,7 @@ async function handlePut(req: NextRequest, authContext: AuthResult) {
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error updating calendar event:', error);
|
||||
return NextResponse.json<ApiResponse<CalendarEventResponse>>(
|
||||
return NextResponse.json<ApiResponse<null>>(
|
||||
{
|
||||
success: false,
|
||||
error: 'Failed to update calendar event',
|
||||
@@ -560,50 +581,25 @@ async function handlePut(req: NextRequest, authContext: AuthResult) {
|
||||
|
||||
async function handleDelete(req: NextRequest, authContext: AuthResult) {
|
||||
try {
|
||||
const { familyId: userFamilyId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
return NextResponse.json<ApiResponse<null>>({ success: false, error: 'User is not associated with a family.' }, { status: 403 });
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(req.url);
|
||||
const id = searchParams.get('id');
|
||||
|
||||
if (!id) {
|
||||
return NextResponse.json<ApiResponse<void>>(
|
||||
{
|
||||
success: false,
|
||||
error: 'Calendar event ID is required',
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
return NextResponse.json<ApiResponse<null>>({ success: false, error: 'Calendar event ID is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get family ID from request
|
||||
const familyId = await getFamilyIdFromRequest(req);
|
||||
|
||||
// Check if event exists
|
||||
const existingEvent = await prisma.calendarEvent.findUnique({
|
||||
where: { id },
|
||||
const existingEvent = await prisma.calendarEvent.findFirst({
|
||||
where: { id, familyId: userFamilyId },
|
||||
});
|
||||
|
||||
if (!existingEvent) {
|
||||
return NextResponse.json<ApiResponse<void>>(
|
||||
{
|
||||
success: false,
|
||||
error: 'Calendar event not found',
|
||||
},
|
||||
{ status: 404 }
|
||||
);
|
||||
return NextResponse.json<ApiResponse<null>>({ success: false, error: 'Calendar event not found or access denied' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Check family access
|
||||
if (familyId && existingEvent.familyId !== familyId) {
|
||||
return NextResponse.json<ApiResponse<void>>(
|
||||
{
|
||||
success: false,
|
||||
error: 'Calendar event not found',
|
||||
},
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
// Allow deleting even if it's already marked as deleted
|
||||
// This prevents errors when trying to delete an event multiple times
|
||||
|
||||
// Soft delete the event
|
||||
await prisma.calendarEvent.update({
|
||||
@@ -613,12 +609,10 @@ async function handleDelete(req: NextRequest, authContext: AuthResult) {
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json<ApiResponse<void>>({
|
||||
success: true,
|
||||
});
|
||||
return NextResponse.json<ApiResponse<null>>({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Error deleting calendar event:', error);
|
||||
return NextResponse.json<ApiResponse<void>>(
|
||||
return NextResponse.json<ApiResponse<null>>(
|
||||
{
|
||||
success: false,
|
||||
error: 'Failed to delete calendar event',
|
||||
|
||||
@@ -121,7 +121,7 @@ This checklist tracks the progress of refactoring each component and its associa
|
||||
- API: `app/api/sleep-log/`
|
||||
|
||||
### Other Components
|
||||
- [ ] **Calendar**
|
||||
- [x] **Calendar**
|
||||
- Form: `src/components/forms/CalendarEventForm/`
|
||||
- API: `app/api/calendar-event/`
|
||||
- [x] **Contacts**
|
||||
|
||||
@@ -20,22 +20,11 @@ A responsive calendar component for the Baby Tracker application that displays a
|
||||
import { Calendar } from '@/src/components/Calendar';
|
||||
|
||||
function CalendarPage() {
|
||||
const { selectedBaby } = useBaby();
|
||||
const { userTimezone } = useTimezone();
|
||||
|
||||
return (
|
||||
<div className="h-full">
|
||||
{selectedBaby ? (
|
||||
<Calendar
|
||||
selectedBabyId={selectedBaby.id}
|
||||
userTimezone={userTimezone}
|
||||
/>
|
||||
) : (
|
||||
<div className="text-center py-12">
|
||||
<h2 className="text-2xl font-semibold">No Baby Selected</h2>
|
||||
<p className="mt-2 text-gray-500">Please select a baby from the dropdown menu above.</p>
|
||||
</div>
|
||||
)}
|
||||
<Calendar userTimezone={userTimezone} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -51,7 +40,7 @@ Main component for displaying a monthly calendar with activity indicators.
|
||||
|
||||
| Prop | Type | Description | Default |
|
||||
|------|------|-------------|---------|
|
||||
| `selectedBabyId` | `string \| undefined` | The ID of the currently selected baby | Required |
|
||||
| `onDateSelect` | `(date: Date) => void` | Optional callback when a date is selected | `undefined` |
|
||||
| `userTimezone` | `string` | The user's timezone for date calculations | Required |
|
||||
|
||||
## Visual Behavior
|
||||
@@ -63,6 +52,7 @@ Main component for displaying a monthly calendar with activity indicators.
|
||||
- Month and year are displayed in the header
|
||||
- Navigation buttons allow moving to previous/next months
|
||||
- "Today" button returns to the current month
|
||||
- The component will handle sending this timezone to the API
|
||||
|
||||
## Implementation Details
|
||||
|
||||
|
||||
@@ -101,8 +101,6 @@ export function Calendar({ selectedBabyId, userTimezone, onDateSelect }: Calenda
|
||||
* Fetch events for the selected month
|
||||
*/
|
||||
const fetchEvents = async () => {
|
||||
if (!selectedBabyId) return;
|
||||
|
||||
try {
|
||||
// Create start date (first day of month) and end date (last day of month)
|
||||
const year = currentDate.getFullYear();
|
||||
@@ -115,11 +113,10 @@ export function Calendar({ selectedBabyId, userTimezone, onDateSelect }: Calenda
|
||||
endDate.setHours(23, 59, 59, 999);
|
||||
|
||||
console.log(`Fetching events for date range: ${startDate.toISOString()} to ${endDate.toISOString()}`);
|
||||
console.log(`Baby ID: ${selectedBabyId}, Timezone: ${userTimezone}`);
|
||||
|
||||
// Fetch calendar events
|
||||
const eventsResponse = await fetch(
|
||||
`/api/calendar-event?babyId=${selectedBabyId}&startDate=${startDate.toISOString()}&endDate=${endDate.toISOString()}&timezone=${encodeURIComponent(userTimezone)}`
|
||||
`/api/calendar-event?startDate=${startDate.toISOString()}&endDate=${endDate.toISOString()}`
|
||||
);
|
||||
const eventsData = await eventsResponse.json();
|
||||
|
||||
@@ -166,7 +163,7 @@ export function Calendar({ selectedBabyId, userTimezone, onDateSelect }: Calenda
|
||||
*/
|
||||
useEffect(() => {
|
||||
fetchEvents();
|
||||
}, [selectedBabyId, currentDate, userTimezone]);
|
||||
}, [currentDate, userTimezone]);
|
||||
|
||||
// Removed fetchEventsForSelectedDay and its useEffect hook
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
import CalendarEventForm from '@/src/components/forms/CalendarEventForm';
|
||||
import { CalendarEventFormData } from '@/src/components/forms/CalendarEventForm/calendar-event-form.types';
|
||||
import './calendar-day-view.css';
|
||||
import { useFamily } from '@/src/context/family';
|
||||
|
||||
/**
|
||||
* CalendarDayView Component
|
||||
@@ -41,7 +40,6 @@ export const CalendarDayView: React.FC<CalendarDayViewProps> = ({
|
||||
onClose,
|
||||
isOpen,
|
||||
}) => {
|
||||
const { family } = useFamily();
|
||||
|
||||
// State for event form
|
||||
const [showEventForm, setShowEventForm] = useState(false);
|
||||
@@ -54,22 +52,16 @@ export const CalendarDayView: React.FC<CalendarDayViewProps> = ({
|
||||
React.useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
// Build URLs with family ID for proper data filtering
|
||||
const urlParams = new URLSearchParams();
|
||||
if (family?.id) {
|
||||
urlParams.append('familyId', family.id);
|
||||
}
|
||||
|
||||
// Fetch babies
|
||||
const babiesResponse = await fetch(`/api/baby?${urlParams.toString()}`);
|
||||
const babiesResponse = await fetch('/api/baby');
|
||||
const babiesData = await babiesResponse.json();
|
||||
|
||||
// Fetch caretakers
|
||||
const caretakersResponse = await fetch(`/api/caretaker?${urlParams.toString()}`);
|
||||
const caretakersResponse = await fetch('/api/caretaker');
|
||||
const caretakersData = await caretakersResponse.json();
|
||||
|
||||
// Fetch contacts
|
||||
const contactsResponse = await fetch(`/api/contact?${urlParams.toString()}`);
|
||||
const contactsResponse = await fetch('/api/contact');
|
||||
const contactsData = await contactsResponse.json();
|
||||
|
||||
// Update state with fetched data
|
||||
@@ -84,7 +76,7 @@ export const CalendarDayView: React.FC<CalendarDayViewProps> = ({
|
||||
if (isOpen) {
|
||||
fetchData();
|
||||
}
|
||||
}, [isOpen, family?.id]);
|
||||
}, [isOpen]);
|
||||
|
||||
// Format date for display
|
||||
const formattedDate = useMemo(() => {
|
||||
@@ -250,26 +242,14 @@ export const CalendarDayView: React.FC<CalendarDayViewProps> = ({
|
||||
|
||||
// Handle event delete
|
||||
const handleDeleteEvent = async (eventId: string) => {
|
||||
if (!eventId) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/calendar-event?id=${eventId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ familyId: family?.id }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
// Close form
|
||||
setShowEventForm(false);
|
||||
|
||||
// Notify parent component to refresh
|
||||
if (onAddEvent) {
|
||||
onAddEvent(date);
|
||||
onAddEvent(date); // Trigger refresh
|
||||
}
|
||||
} else {
|
||||
console.error('Error deleting event:', data.error);
|
||||
@@ -304,81 +284,83 @@ export const CalendarDayView: React.FC<CalendarDayViewProps> = ({
|
||||
|
||||
// Events state - grouped by time of day
|
||||
return (
|
||||
<div className="calendar-day-view">
|
||||
{/* Morning events */}
|
||||
{groupedEvents.morning.length > 0 && (
|
||||
<div className={styles.eventGroup}>
|
||||
<div className={styles.eventGroupHeader}>
|
||||
<Sun className={styles.eventGroupIcon} />
|
||||
<h3 className={cn(
|
||||
styles.eventGroupTitle,
|
||||
'calendar-day-view-group-title'
|
||||
)}>
|
||||
Morning
|
||||
</h3>
|
||||
<div className="calendar-day-view px-3">
|
||||
<div className="max-w-2xl mx-auto mt-2">
|
||||
{/* Morning events */}
|
||||
{groupedEvents.morning.length > 0 && (
|
||||
<div className={styles.eventGroup}>
|
||||
<div className={styles.eventGroupHeader}>
|
||||
<Sun className={styles.eventGroupIcon} />
|
||||
<h3 className={cn(
|
||||
styles.eventGroupTitle,
|
||||
'calendar-day-view-group-title'
|
||||
)}>
|
||||
Morning
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className={styles.eventsList}>
|
||||
{groupedEvents.morning.map(event => (
|
||||
<CalendarEventItem
|
||||
key={event.id}
|
||||
event={event}
|
||||
onClick={handleEventClick}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.eventsList}>
|
||||
{groupedEvents.morning.map(event => (
|
||||
<CalendarEventItem
|
||||
key={event.id}
|
||||
event={event}
|
||||
onClick={handleEventClick}
|
||||
/>
|
||||
))}
|
||||
)}
|
||||
|
||||
{/* Afternoon events */}
|
||||
{groupedEvents.afternoon.length > 0 && (
|
||||
<div className={styles.eventGroup}>
|
||||
<div className={styles.eventGroupHeader}>
|
||||
<Coffee className={styles.eventGroupIcon} />
|
||||
<h3 className={cn(
|
||||
styles.eventGroupTitle,
|
||||
'calendar-day-view-group-title'
|
||||
)}>
|
||||
Afternoon
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className={styles.eventsList}>
|
||||
{groupedEvents.afternoon.map(event => (
|
||||
<CalendarEventItem
|
||||
key={event.id}
|
||||
event={event}
|
||||
onClick={handleEventClick}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Afternoon events */}
|
||||
{groupedEvents.afternoon.length > 0 && (
|
||||
<div className={styles.eventGroup}>
|
||||
<div className={styles.eventGroupHeader}>
|
||||
<Coffee className={styles.eventGroupIcon} />
|
||||
<h3 className={cn(
|
||||
styles.eventGroupTitle,
|
||||
'calendar-day-view-group-title'
|
||||
)}>
|
||||
Afternoon
|
||||
</h3>
|
||||
)}
|
||||
|
||||
{/* Evening events */}
|
||||
{groupedEvents.evening.length > 0 && (
|
||||
<div className={styles.eventGroup}>
|
||||
<div className={styles.eventGroupHeader}>
|
||||
<Moon className={styles.eventGroupIcon} />
|
||||
<h3 className={cn(
|
||||
styles.eventGroupTitle,
|
||||
'calendar-day-view-group-title'
|
||||
)}>
|
||||
Evening
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className={styles.eventsList}>
|
||||
{groupedEvents.evening.map(event => (
|
||||
<CalendarEventItem
|
||||
key={event.id}
|
||||
event={event}
|
||||
onClick={handleEventClick}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.eventsList}>
|
||||
{groupedEvents.afternoon.map(event => (
|
||||
<CalendarEventItem
|
||||
key={event.id}
|
||||
event={event}
|
||||
onClick={handleEventClick}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Evening events */}
|
||||
{groupedEvents.evening.length > 0 && (
|
||||
<div className={styles.eventGroup}>
|
||||
<div className={styles.eventGroupHeader}>
|
||||
<Moon className={styles.eventGroupIcon} />
|
||||
<h3 className={cn(
|
||||
styles.eventGroupTitle,
|
||||
'calendar-day-view-group-title'
|
||||
)}>
|
||||
Evening
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className={styles.eventsList}>
|
||||
{groupedEvents.evening.map(event => (
|
||||
<CalendarEventItem
|
||||
key={event.id}
|
||||
event={event}
|
||||
onClick={handleEventClick}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -390,27 +372,22 @@ export const CalendarDayView: React.FC<CalendarDayViewProps> = ({
|
||||
isOpen={isOpen}
|
||||
onClose={handleClose}
|
||||
title={formattedDate}
|
||||
className={cn('calendar-day-view-slide-in', className)}
|
||||
className={cn(styles.container, className)}
|
||||
>
|
||||
<FormPageContent className="calendar-day-view-content">
|
||||
{renderContent()}
|
||||
<FormPageContent className="p-0">
|
||||
<div className="flex flex-col h-full">
|
||||
{renderContent()}
|
||||
</div>
|
||||
</FormPageContent>
|
||||
|
||||
<FormPageFooter>
|
||||
<Button variant="outline" onClick={handleClose}>
|
||||
Close
|
||||
</Button>
|
||||
|
||||
{onAddEvent && (
|
||||
<div className="flex justify-end w-full">
|
||||
<Button onClick={handleAddEvent}>
|
||||
<PlusCircle className="h-4 w-4 mr-2" />
|
||||
<PlusCircle className="mr-2 h-4 w-4" />
|
||||
Add Event
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</FormPageFooter>
|
||||
</FormPage>
|
||||
|
||||
{/* Calendar Event Form */}
|
||||
<CalendarEventForm
|
||||
isOpen={showEventForm}
|
||||
onClose={handleEventFormClose}
|
||||
@@ -420,7 +397,6 @@ export const CalendarDayView: React.FC<CalendarDayViewProps> = ({
|
||||
babies={babies}
|
||||
caretakers={caretakers}
|
||||
contacts={contacts}
|
||||
familyId={family?.id}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -15,7 +15,6 @@ A comprehensive form component for creating and editing calendar events in the B
|
||||
- Responsive design for mobile and desktop
|
||||
- Dark mode support
|
||||
- Accessible UI with proper semantic structure
|
||||
- Multi-family support with family ID association
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -90,7 +89,6 @@ Main component for creating and editing calendar events.
|
||||
| `caretakers` | `Caretaker[]` | Available caretakers to select | Required |
|
||||
| `contacts` | `Contact[]` | Available contacts to select | Required |
|
||||
| `isLoading` | `boolean` | Whether the form is in a loading state | `false` |
|
||||
| `familyId` | `string` | The ID of the family this event belongs to (for multi-family support) | `undefined` |
|
||||
|
||||
### CalendarEventFormData
|
||||
|
||||
@@ -199,34 +197,6 @@ The component follows a modular structure:
|
||||
- `calendar-event-form.types.ts` - TypeScript type definitions
|
||||
- `calendar-event-form.css` - Additional CSS for dark mode and animations
|
||||
|
||||
### Multi-Family Support
|
||||
|
||||
The component supports multi-family functionality by:
|
||||
- Accepting a `familyId` prop to associate the calendar event with a specific family
|
||||
- Including the family ID in the API request payload for create, update, and delete operations
|
||||
- Adding the family ID to the CalendarEventFormData interface
|
||||
|
||||
When using this component in a multi-family context, you should:
|
||||
1. Import and use the family context to get the current family ID
|
||||
2. Pass the family ID to the CalendarEventForm component
|
||||
3. The component will handle sending this ID to the API
|
||||
|
||||
```tsx
|
||||
import { useFamily } from '@/src/context/family';
|
||||
import { CalendarEventForm } from '@/src/components/forms/CalendarEventForm';
|
||||
|
||||
function CalendarPage() {
|
||||
const { family } = useFamily(); // Get current family from context
|
||||
|
||||
return (
|
||||
<CalendarEventForm
|
||||
// Other props...
|
||||
familyId={family?.id} // Pass the current family ID
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
The component includes:
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
DropdownMenuItem,
|
||||
} from '@/src/components/ui/dropdown-menu';
|
||||
import './calendar-event-form.css';
|
||||
import { useFamily } from '@/src/context/family';
|
||||
|
||||
/**
|
||||
* CalendarEventForm Component
|
||||
@@ -39,9 +38,7 @@ const CalendarEventForm: React.FC<CalendarEventFormProps> = ({
|
||||
caretakers,
|
||||
contacts,
|
||||
isLoading = false,
|
||||
familyId,
|
||||
}) => {
|
||||
const { family } = useFamily();
|
||||
|
||||
// Helper function to get initial form data
|
||||
const getInitialFormData = (
|
||||
@@ -168,7 +165,7 @@ const CalendarEventForm: React.FC<CalendarEventFormProps> = ({
|
||||
const handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, checked } = e.target;
|
||||
setFormData(prev => ({ ...prev, [name]: checked }));
|
||||
}; // <-- Added missing closing brace
|
||||
};
|
||||
|
||||
// Handle start date/time change with DateTimePicker
|
||||
const handleStartDateTimeChange = (date: Date) => {
|
||||
@@ -399,13 +396,8 @@ const CalendarEventForm: React.FC<CalendarEventFormProps> = ({
|
||||
// Handle form submission
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (validateForm()) {
|
||||
// Include familyId in the form data when submitting
|
||||
onSave({
|
||||
...formData,
|
||||
familyId: familyId || family?.id || undefined,
|
||||
});
|
||||
onSave(formData);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -414,27 +406,20 @@ const CalendarEventForm: React.FC<CalendarEventFormProps> = ({
|
||||
onClose();
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<FormPage
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
title={event ? 'Edit Event' : 'Add Event'}
|
||||
description="Schedule and manage calendar events"
|
||||
className="calendar-event-form-container"
|
||||
>
|
||||
<form onSubmit={handleSubmit} className="flex flex-col h-full">
|
||||
<FormPageContent className="flex-1">
|
||||
<div className="space-y-6 pb-20">
|
||||
<FormPage isOpen={isOpen} onClose={handleClose} title={event ? 'Edit Event' : 'New Event'}>
|
||||
<form onSubmit={handleSubmit} className="flex flex-col h-full">
|
||||
<FormPageContent>
|
||||
<div className="space-y-6">
|
||||
{/* Event details section */}
|
||||
<div className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Event Details</h3>
|
||||
|
||||
{/* Title */}
|
||||
<div className={styles.fieldGroup}>
|
||||
<label
|
||||
htmlFor="title"
|
||||
className="form-label"
|
||||
>
|
||||
<label htmlFor="title" className={styles.fieldLabel}>
|
||||
Title
|
||||
<span className={styles.fieldRequired}>*</span>
|
||||
</label>
|
||||
@@ -454,22 +439,16 @@ const CalendarEventForm: React.FC<CalendarEventFormProps> = ({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
{/* Event type */}
|
||||
<div className={styles.fieldGroup}>
|
||||
<label
|
||||
htmlFor="type"
|
||||
className="form-label"
|
||||
>
|
||||
<label htmlFor="type" className={styles.fieldLabel}>
|
||||
Event Type
|
||||
<span className={styles.fieldRequired}>*</span>
|
||||
</label>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full justify-between"
|
||||
>
|
||||
<Button variant="outline" className="w-full justify-between">
|
||||
{formData.type.charAt(0) + formData.type.slice(1).toLowerCase().replace('_', ' ')}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
@@ -513,15 +492,12 @@ const CalendarEventForm: React.FC<CalendarEventFormProps> = ({
|
||||
All day event
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Date and time - each on its own row */}
|
||||
<div className="space-y-4">
|
||||
{/* Start Date/Time - Full width */}
|
||||
<div className={styles.fieldGroup}>
|
||||
<label
|
||||
htmlFor="startTime"
|
||||
className="form-label"
|
||||
>
|
||||
<label htmlFor="startTime" className={styles.fieldLabel}>
|
||||
Start Time
|
||||
<span className={styles.fieldRequired}>*</span>
|
||||
</label>
|
||||
@@ -544,10 +520,7 @@ const CalendarEventForm: React.FC<CalendarEventFormProps> = ({
|
||||
|
||||
{/* End Date/Time - Full width */}
|
||||
<div className={styles.fieldGroup}>
|
||||
<label
|
||||
htmlFor="endTime"
|
||||
className="form-label"
|
||||
>
|
||||
<label htmlFor="endTime" className={styles.fieldLabel}>
|
||||
End Time
|
||||
</label>
|
||||
<div className="grid w-full">
|
||||
@@ -570,10 +543,7 @@ const CalendarEventForm: React.FC<CalendarEventFormProps> = ({
|
||||
|
||||
{/* Location */}
|
||||
<div className={styles.fieldGroup}>
|
||||
<label
|
||||
htmlFor="location"
|
||||
className="form-label"
|
||||
>
|
||||
<label htmlFor="location" className={styles.fieldLabel}>
|
||||
Location
|
||||
</label>
|
||||
<div className="relative">
|
||||
@@ -592,10 +562,7 @@ const CalendarEventForm: React.FC<CalendarEventFormProps> = ({
|
||||
|
||||
{/* Description */}
|
||||
<div className={styles.fieldGroup}>
|
||||
<label
|
||||
htmlFor="description"
|
||||
className="form-label"
|
||||
>
|
||||
<label htmlFor="description" className={styles.fieldLabel}>
|
||||
Description
|
||||
</label>
|
||||
<Textarea
|
||||
@@ -610,10 +577,7 @@ const CalendarEventForm: React.FC<CalendarEventFormProps> = ({
|
||||
|
||||
{/* Color */}
|
||||
<div className={styles.fieldGroup}>
|
||||
<label
|
||||
htmlFor="color"
|
||||
className="form-label"
|
||||
>
|
||||
<label htmlFor="color" className={styles.fieldLabel}>
|
||||
Color
|
||||
</label>
|
||||
<div className="flex items-center space-x-2">
|
||||
@@ -637,9 +601,9 @@ const CalendarEventForm: React.FC<CalendarEventFormProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Recurrence section - commented out as functionality is not fully implemented yet */}
|
||||
{/*
|
||||
{/*
|
||||
<div className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Recurrence</h3>
|
||||
|
||||
@@ -657,7 +621,7 @@ const CalendarEventForm: React.FC<CalendarEventFormProps> = ({
|
||||
/>
|
||||
</div>
|
||||
*/}
|
||||
|
||||
|
||||
{/* Reminder section - commented out as functionality is not fully implemented yet */}
|
||||
{/*
|
||||
<div className={styles.section}>
|
||||
@@ -729,7 +693,7 @@ const CalendarEventForm: React.FC<CalendarEventFormProps> = ({
|
||||
{/* Babies - Only show if there's more than one active baby */}
|
||||
{babies.filter(baby => baby.inactive !== true).length > 1 ? (
|
||||
<div className={styles.fieldGroup}>
|
||||
<label className="form-label">
|
||||
<label className={styles.fieldLabel}>
|
||||
Babies
|
||||
</label>
|
||||
<div className="space-y-2">
|
||||
@@ -789,7 +753,7 @@ const CalendarEventForm: React.FC<CalendarEventFormProps> = ({
|
||||
|
||||
{/* Caretakers */}
|
||||
<div className={styles.fieldGroup}>
|
||||
<label className="form-label">
|
||||
<label className={styles.fieldLabel}>
|
||||
Caretakers
|
||||
</label>
|
||||
<div className="space-y-2">
|
||||
@@ -816,7 +780,7 @@ const CalendarEventForm: React.FC<CalendarEventFormProps> = ({
|
||||
|
||||
{/* Contacts */}
|
||||
<div className={styles.fieldGroup}>
|
||||
<label className="form-label">
|
||||
<label className={styles.fieldLabel}>
|
||||
Contacts
|
||||
</label>
|
||||
<ContactSelector
|
||||
@@ -834,73 +798,75 @@ const CalendarEventForm: React.FC<CalendarEventFormProps> = ({
|
||||
</FormPageContent>
|
||||
|
||||
<FormPageFooter>
|
||||
<div className="flex justify-end space-x-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleClose}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<div className="flex justify-between items-center w-full">
|
||||
<div>
|
||||
{event && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="destructive"
|
||||
onClick={async () => {
|
||||
if (!event.id) return;
|
||||
|
||||
try {
|
||||
// Delete the event
|
||||
const response = await fetch(`/api/calendar-event?id=${event.id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
// Close the form
|
||||
onClose();
|
||||
|
||||
// Call onSave with a special flag to indicate deletion
|
||||
// This will trigger a refresh in the parent components
|
||||
onSave({
|
||||
...event,
|
||||
_deleted: true, // Special flag to indicate deletion
|
||||
});
|
||||
} else {
|
||||
console.error('Error deleting event:', data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting event:', error);
|
||||
}
|
||||
}}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 mr-1.5" />
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{event && (
|
||||
<div className="flex justify-end space-x-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="destructive"
|
||||
onClick={async () => {
|
||||
if (!event.id) return;
|
||||
|
||||
try {
|
||||
// Delete the event
|
||||
const response = await fetch(`/api/calendar-event?id=${event.id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ familyId }), // Include familyId in the request body
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
// Close the form
|
||||
onClose();
|
||||
|
||||
// Call onSave with a special flag to indicate deletion
|
||||
// This will trigger a refresh in the parent components
|
||||
onSave({
|
||||
...event,
|
||||
_deleted: true, // Special flag to indicate deletion
|
||||
familyId, // Include familyId in the event data
|
||||
});
|
||||
} else {
|
||||
console.error('Error deleting event:', data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting event:', error);
|
||||
}
|
||||
}}
|
||||
variant="outline"
|
||||
onClick={handleClose}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 mr-1.5" />
|
||||
Delete
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 mr-1.5 animate-spin" />
|
||||
Saving...
|
||||
</>
|
||||
) : (
|
||||
'Save Event'
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 mr-1.5 animate-spin" />
|
||||
Saving...
|
||||
</>
|
||||
) : (
|
||||
'Save Event'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</FormPageFooter>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user