Add authentication #17

Manually merged
Webmaster merged 225 commits from auth into main 2025-03-08 10:37:06 +00:00
4 changed files with 718 additions and 419 deletions
Showing only changes of commit f641ee722a - Show all commits

View file

@ -4,6 +4,8 @@ import { Get } from "../../scripts/pocketbase/Get";
import EventRequestForm from "./Officer_EventRequestForm/EventRequestForm"; import EventRequestForm from "./Officer_EventRequestForm/EventRequestForm";
import UserEventRequests from "./Officer_EventRequestForm/UserEventRequests"; import UserEventRequests from "./Officer_EventRequestForm/UserEventRequests";
import { Collections } from "../../schemas/pocketbase/schema"; import { Collections } from "../../schemas/pocketbase/schema";
import { DataSyncService } from "../../scripts/database/DataSyncService";
import { EventRequestFormPreviewModal } from "./Officer_EventRequestForm/EventRequestFormPreview";
// Import the EventRequest type from UserEventRequests to ensure consistency // Import the EventRequest type from UserEventRequests to ensure consistency
import type { EventRequest as UserEventRequest } from "./Officer_EventRequestForm/UserEventRequests"; import type { EventRequest as UserEventRequest } from "./Officer_EventRequestForm/UserEventRequests";
@ -29,7 +31,7 @@ if (auth.isAuthenticated()) {
userEventRequests = await get.getAll<EventRequest>( userEventRequests = await get.getAll<EventRequest>(
Collections.EVENT_REQUESTS, Collections.EVENT_REQUESTS,
`requested_user="${userId}"`, `requested_user="${userId}"`,
"-created" "-created",
); );
} }
} catch (err) { } catch (err) {
@ -44,8 +46,8 @@ if (auth.isAuthenticated()) {
<h1 class="text-3xl font-bold text-white mb-2">Event Request Form</h1> <h1 class="text-3xl font-bold text-white mb-2">Event Request Form</h1>
<p class="text-gray-300 mb-4"> <p class="text-gray-300 mb-4">
Submit your event request at least 6 weeks before your event. After Submit your event request at least 6 weeks before your event. After
submitting, please notify PR and/or Coordinators in the #-events submitting, please notify PR and/or Coordinators in the #-events Slack
Slack channel. channel.
</p> </p>
<div class="bg-base-300/50 p-4 rounded-lg text-sm text-gray-300"> <div class="bg-base-300/50 p-4 rounded-lg text-sm text-gray-300">
<p class="font-medium mb-2">This form includes sections for:</p> <p class="font-medium mb-2">This form includes sections for:</p>
@ -107,20 +109,105 @@ if (auth.isAuthenticated()) {
{ {
!error && ( !error && (
<UserEventRequests <UserEventRequests client:load eventRequests={userEventRequests} />
client:load
eventRequests={userEventRequests}
/>
) )
} }
</div> </div>
</div> </div>
</div> </div>
<style is:global>
/* Ensure the modal container is always visible */
#event-request-preview-modal-container {
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
z-index: 99999 !important;
}
/* Style for the modal backdrop */
#event-request-preview-modal-container > div > div:first-child {
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100vw !important;
height: 100vh !important;
z-index: 99999 !important;
background-color: rgba(0, 0, 0, 0.8) !important;
backdrop-filter: blur(8px) !important;
}
/* Style for the modal content */
#event-request-preview-modal-container > div > div > div {
z-index: 100000 !important;
position: relative !important;
}
</style>
<!-- Add the modal component -->
<EventRequestFormPreviewModal client:load />
<div class="dashboard-section hidden" id="eventRequestFormSection"> <div class="dashboard-section hidden" id="eventRequestFormSection">
<!-- ... existing code ... --> <!-- ... existing code ... -->
</div> </div>
<script is:inline>
// Define the global function immediately to ensure it's available
window.showEventRequestFormPreview = function (formData) {
console.log(
"Global showEventRequestFormPreview called with data",
formData,
);
// Remove any elements that might be obstructing the view
const removeObstructions = () => {
// Find any elements with high z-index that might be obstructing
document.querySelectorAll('[style*="z-index"]').forEach((el) => {
if (
el.id !== "event-request-preview-modal-container" &&
!el.closest("#event-request-preview-modal-container")
) {
// Store original z-index to restore later
if (!el.dataset.originalZIndex) {
el.dataset.originalZIndex = el.style.zIndex;
}
// Temporarily lower z-index
el.style.zIndex = "0";
}
});
};
// Create a custom event to trigger the preview
const event = new CustomEvent("showEventRequestPreviewModal", {
detail: { formData },
});
// Remove obstructions before showing modal
removeObstructions();
// Dispatch event to show modal
document.dispatchEvent(event);
console.log("showEventRequestPreviewModal event dispatched");
// Ensure modal container is visible
setTimeout(() => {
const modalContainer = document.getElementById(
"event-request-preview-modal-container",
);
if (modalContainer) {
modalContainer.style.zIndex = "99999";
modalContainer.style.position = "fixed";
modalContainer.style.top = "0";
modalContainer.style.left = "0";
modalContainer.style.width = "100%";
modalContainer.style.height = "100%";
}
}, 100);
};
</script>
<script> <script>
// Import the DataSyncService for client-side use // Import the DataSyncService for client-side use
import { DataSyncService } from "../../scripts/database/DataSyncService"; import { DataSyncService } from "../../scripts/database/DataSyncService";
@ -142,11 +229,9 @@ if (auth.isAuthenticated()) {
await dataSync.syncCollection( await dataSync.syncCollection(
Collections.EVENT_REQUESTS, Collections.EVENT_REQUESTS,
`requested_user="${userId}"`, `requested_user="${userId}"`,
"-created" "-created",
);
console.log(
"Initial data sync complete for user event requests"
); );
console.log("Initial data sync complete for user event requests");
} }
} catch (err) { } catch (err) {
console.error("Error during initial data sync:", err); console.error("Error during initial data sync:", err);
@ -156,16 +241,14 @@ if (auth.isAuthenticated()) {
const formTab = document.getElementById("form-tab"); const formTab = document.getElementById("form-tab");
const submissionsTab = document.getElementById("submissions-tab"); const submissionsTab = document.getElementById("submissions-tab");
const formContent = document.getElementById("form-content"); const formContent = document.getElementById("form-content");
const submissionsContent = document.getElementById( const submissionsContent = document.getElementById("submissions-content");
"submissions-content"
);
// Function to switch tabs // Function to switch tabs
const switchTab = ( const switchTab = (
activeTab: HTMLElement, activeTab: HTMLElement,
activeContent: HTMLElement, activeContent: HTMLElement,
inactiveTab: HTMLElement, inactiveTab: HTMLElement,
inactiveContent: HTMLElement inactiveContent: HTMLElement,
) => { ) => {
// Update tab classes // Update tab classes
activeTab.classList.add("tab-active"); activeTab.classList.add("tab-active");
@ -186,24 +269,14 @@ if (auth.isAuthenticated()) {
formTab?.addEventListener("click", (e) => { formTab?.addEventListener("click", (e) => {
e.preventDefault(); e.preventDefault();
if (formContent && submissionsContent && submissionsTab) { if (formContent && submissionsContent && submissionsTab) {
switchTab( switchTab(formTab, formContent, submissionsTab, submissionsContent);
formTab,
formContent,
submissionsTab,
submissionsContent
);
} }
}); });
submissionsTab?.addEventListener("click", (e) => { submissionsTab?.addEventListener("click", (e) => {
e.preventDefault(); e.preventDefault();
if (formContent && submissionsContent && formTab) { if (formContent && submissionsContent && formTab) {
switchTab( switchTab(submissionsTab, submissionsContent, formTab, formContent);
submissionsTab,
submissionsContent,
formTab,
formContent
);
} }
}); });

View file

@ -720,7 +720,7 @@ const EventRequestForm: React.FC = () => {
</div> </div>
<div className="bg-base-200/50 p-6 rounded-lg"> <div className="bg-base-200/50 p-6 rounded-lg">
<EventRequestFormPreview formData={formData} /> <EventRequestFormPreview formData={formData} isModal={false} />
<div className="divider my-6">Ready to Submit?</div> <div className="divider my-6">Ready to Submit?</div>

View file

@ -1,15 +1,104 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { motion } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import type { EventRequestFormData } from './EventRequestForm'; import type { EventRequestFormData } from './EventRequestForm';
import type { InvoiceItem } from './InvoiceBuilder'; import type { InvoiceItem } from './InvoiceBuilder';
import type { EventRequest } from '../../../schemas/pocketbase'; import type { EventRequest } from '../../../schemas/pocketbase';
import { FlyerTypes, LogoOptions, EventRequestStatus } from '../../../schemas/pocketbase'; import { FlyerTypes, LogoOptions, EventRequestStatus } from '../../../schemas/pocketbase';
// Create a standalone component that can be used to show the preview as a modal
export const EventRequestFormPreviewModal: React.FC = () => {
const [isOpen, setIsOpen] = useState(false);
const [formData, setFormData] = useState<EventRequestFormData | null>(null);
// Function to handle showing the modal
const showModal = (data: any) => {
console.log('showModal called with data', data);
setFormData(data);
setIsOpen(true);
};
// Add the global function to the window object directly from the component
useEffect(() => {
// Store the original function if it exists
const originalFunction = window.showEventRequestFormPreview;
// Define the global function
window.showEventRequestFormPreview = (data: any) => {
console.log('Global showEventRequestFormPreview called with data', data);
showModal(data);
};
// Listen for the custom event as a fallback
const handleShowModal = (event: CustomEvent) => {
console.log('Received showEventRequestPreviewModal event', event.detail);
if (event.detail && event.detail.formData) {
showModal(event.detail.formData);
} else {
console.error('Event detail or formData is missing', event.detail);
}
};
// Add event listener
document.addEventListener('showEventRequestPreviewModal', handleShowModal as EventListener);
console.log('Event listener for showEventRequestPreviewModal added');
// Clean up
return () => {
// Restore the original function if it existed
if (originalFunction) {
window.showEventRequestFormPreview = originalFunction;
} else {
// Otherwise delete our function
delete window.showEventRequestFormPreview;
}
document.removeEventListener('showEventRequestPreviewModal', handleShowModal as EventListener);
console.log('Event listener for showEventRequestPreviewModal removed');
};
}, []); // Empty dependency array - only run once on mount
const handleClose = () => {
console.log('Modal closed');
setIsOpen(false);
};
// Force the modal to be in the document body to avoid nesting issues
return (
<div
id="event-request-preview-modal-container"
style={{
position: 'fixed',
top: 0,
left: 0,
width: '100%',
height: '100%',
zIndex: 99999,
pointerEvents: isOpen ? 'auto' : 'none'
}}
>
<EventRequestFormPreview
formData={formData || undefined}
isOpen={isOpen}
onClose={handleClose}
isModal={true}
/>
</div>
);
};
interface EventRequestFormPreviewProps { interface EventRequestFormPreviewProps {
formData?: EventRequestFormData; // Optional prop to directly pass form data formData?: EventRequestFormData; // Optional prop to directly pass form data
isOpen?: boolean; // Control whether the modal is open
onClose?: () => void; // Callback when modal is closed
isModal?: boolean; // Whether to render as a modal or inline component
} }
const EventRequestFormPreview: React.FC<EventRequestFormPreviewProps> = ({ formData: propFormData }) => { const EventRequestFormPreview: React.FC<EventRequestFormPreviewProps> = ({
formData: propFormData,
isOpen = true,
onClose = () => { },
isModal = false
}) => {
const [formData, setFormData] = useState<EventRequestFormData | null>(propFormData || null); const [formData, setFormData] = useState<EventRequestFormData | null>(propFormData || null);
const [loading, setLoading] = useState<boolean>(!propFormData); const [loading, setLoading] = useState<boolean>(!propFormData);
@ -55,6 +144,27 @@ const EventRequestFormPreview: React.FC<EventRequestFormPreviewProps> = ({ formD
}; };
}, [propFormData]); }, [propFormData]);
// Format date and time for display
const formatDateTime = (dateTimeString: string) => {
if (!dateTimeString) return 'Not specified';
try {
const date = new Date(dateTimeString);
return date.toLocaleString();
} catch (e) {
return dateTimeString;
}
};
// Handle click on the backdrop to close the modal
const handleBackdropClick = (e: React.MouseEvent<HTMLDivElement>) => {
if (e.target === e.currentTarget) {
onClose();
}
};
// Render the content of the preview
const renderContent = () => {
if (loading) { if (loading) {
return ( return (
<div className="flex justify-center items-center h-64"> <div className="flex justify-center items-center h-64">
@ -72,29 +182,17 @@ const EventRequestFormPreview: React.FC<EventRequestFormPreviewProps> = ({ formD
); );
} }
// Format date and time for display
const formatDateTime = (dateTimeString: string) => {
if (!dateTimeString) return 'Not specified';
try {
const date = new Date(dateTimeString);
return date.toLocaleString();
} catch (e) {
return dateTimeString;
}
};
return ( return (
<motion.div <div className="space-y-8">
initial={{ opacity: 0 }} <div className={`${isModal ? 'bg-base-300' : ''} p-6 rounded-lg`}>
animate={{ opacity: 1 }} {isModal && (
className="space-y-8" <>
>
<div className="bg-base-300 p-6 rounded-lg">
<h2 className="text-2xl font-bold mb-4">Event Request Preview</h2> <h2 className="text-2xl font-bold mb-4">Event Request Preview</h2>
<p className="text-sm text-gray-400 mb-6"> <p className="text-sm text-gray-400 mb-6">
This is a preview of your event request. Please review all information before submitting. This is a preview of your event request. Please review all information before submitting.
</p> </p>
</>
)}
{/* Event Details Section */} {/* Event Details Section */}
<div className="mb-8"> <div className="mb-8">
@ -319,8 +417,75 @@ const EventRequestFormPreview: React.FC<EventRequestFormPreviewProps> = ({ formD
</div> </div>
)} )}
</div> </div>
</div>
);
};
// If not a modal, render the content directly
if (!isModal) {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="w-full"
>
{renderContent()}
</motion.div> </motion.div>
); );
}
// If it's a modal, render with the modal wrapper
return (
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 flex items-center justify-center bg-black/80 backdrop-blur-md overflow-hidden"
onClick={handleBackdropClick}
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
zIndex: 99999,
width: '100vw',
height: '100vh',
margin: 0,
padding: 0
}}
>
<motion.div
initial={{ scale: 0.95, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.95, opacity: 0 }}
transition={{ type: "spring", stiffness: 300, damping: 25 }}
className="bg-base-100 rounded-xl shadow-2xl w-full max-w-5xl max-h-[90vh] overflow-y-auto m-4"
onClick={(e) => e.stopPropagation()}
style={{
position: 'relative',
zIndex: 100000
}}
>
<div className="sticky top-0 z-10 bg-base-100 px-6 py-4 border-b border-base-300 flex justify-between items-center">
<h2 className="text-xl font-bold">Event Request Preview</h2>
<button
className="btn btn-sm btn-circle btn-ghost"
onClick={onClose}
>
</button>
</div>
<div className="p-6">
{renderContent()}
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
);
}; };
export default EventRequestFormPreview; export default EventRequestFormPreview;

View file

@ -5,6 +5,13 @@ import { DataSyncService } from '../../../scripts/database/DataSyncService';
import { Collections } from '../../../schemas/pocketbase/schema'; import { Collections } from '../../../schemas/pocketbase/schema';
import type { EventRequest as SchemaEventRequest } from '../../../schemas/pocketbase'; import type { EventRequest as SchemaEventRequest } from '../../../schemas/pocketbase';
// Declare the global window interface to include our custom function
declare global {
interface Window {
showEventRequestFormPreview?: (formData: any) => void;
}
}
// Extended EventRequest interface with additional properties needed for this component // Extended EventRequest interface with additional properties needed for this component
export interface EventRequest extends SchemaEventRequest { export interface EventRequest extends SchemaEventRequest {
invoice_data?: any; invoice_data?: any;
@ -98,15 +105,37 @@ const UserEventRequests: React.FC<UserEventRequestsProps> = ({ eventRequests: in
switch (status.toLowerCase()) { switch (status.toLowerCase()) {
case 'approved': case 'approved':
return 'badge-success'; case 'completed':
return 'badge-success text-white';
case 'rejected': case 'rejected':
return 'badge-error'; case 'declined':
return 'badge-error text-white';
case 'pending': case 'pending':
return 'badge-warning'; return 'badge-warning text-black';
case 'submitted': case 'submitted':
return 'badge-info'; return 'badge-info text-white';
default: default:
return 'badge-warning'; return 'badge-warning text-black';
}
};
// Get card border class based on status
const getCardBorderClass = (status?: string) => {
if (!status) return 'border-l-warning';
switch (status.toLowerCase()) {
case 'approved':
case 'completed':
return 'border-l-success';
case 'rejected':
case 'declined':
return 'border-l-error';
case 'pending':
return 'border-l-warning';
case 'submitted':
return 'border-l-info';
default:
return 'border-l-warning';
} }
}; };
@ -226,7 +255,7 @@ const UserEventRequests: React.FC<UserEventRequestsProps> = ({ eventRequests: in
</thead> </thead>
<tbody> <tbody>
{eventRequests.map((request) => ( {eventRequests.map((request) => (
<tr key={request.id} className="hover"> <tr key={request.id} className={`hover border-l-4 ${getCardBorderClass(request.status)}`}>
<td className="font-medium">{request.name}</td> <td className="font-medium">{request.name}</td>
<td>{formatDate(request.start_date_time)}</td> <td>{formatDate(request.start_date_time)}</td>
<td>{request.location}</td> <td>{request.location}</td>
@ -274,7 +303,7 @@ const UserEventRequests: React.FC<UserEventRequestsProps> = ({ eventRequests: in
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }} transition={{ duration: 0.3 }}
className="card bg-base-200 shadow-sm hover:shadow-md transition-shadow" className={`card bg-base-200 shadow-sm hover:shadow-md transition-shadow border-l-4 ${getCardBorderClass(request.status)}`}
> >
<div className="card-body p-5"> <div className="card-body p-5">
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
@ -371,7 +400,38 @@ const UserEventRequests: React.FC<UserEventRequestsProps> = ({ eventRequests: in
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<div className="sticky top-0 z-10 bg-base-100 px-6 py-4 border-b border-base-300 flex justify-between items-center"> <div className="sticky top-0 z-10 bg-base-100 px-6 py-4 border-b border-base-300 flex justify-between items-center">
<div className="flex items-center gap-2">
<h2 className="text-xl font-bold">{selectedRequest.name}</h2> <h2 className="text-xl font-bold">{selectedRequest.name}</h2>
<span className={`badge ${getStatusBadge(selectedRequest.status)}`}>
{selectedRequest.status || 'Pending'}
</span>
</div>
<div className="flex items-center gap-2">
<button
className="btn btn-sm btn-primary"
onClick={(e) => {
e.stopPropagation();
console.log('Full Preview button clicked', selectedRequest);
try {
// Direct call to the global function
if (typeof window.showEventRequestFormPreview === 'function') {
window.showEventRequestFormPreview(selectedRequest);
} else {
console.error('showEventRequestFormPreview is not a function', window.showEventRequestFormPreview);
// Fallback to event dispatch if function is not available
const event = new CustomEvent("showEventRequestPreviewModal", {
detail: { formData: selectedRequest }
});
document.dispatchEvent(event);
console.log('Fallback: showEventRequestPreviewModal event dispatched');
}
} catch (error) {
console.error('Error showing full preview:', error);
}
}}
>
Full Preview
</button>
<button <button
className="btn btn-sm btn-circle btn-ghost" className="btn btn-sm btn-circle btn-ghost"
onClick={closeModal} onClick={closeModal}
@ -379,6 +439,7 @@ const UserEventRequests: React.FC<UserEventRequestsProps> = ({ eventRequests: in
</button> </button>
</div> </div>
</div>
<div className="p-6"> <div className="p-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-8"> <div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-8">