diff --git a/src/components/auth/EventAuth.ts b/src/components/auth/EventAuth.ts
new file mode 100644
index 0000000..a86d70f
--- /dev/null
+++ b/src/components/auth/EventAuth.ts
@@ -0,0 +1,412 @@
+import PocketBase from "pocketbase";
+import yaml from "js-yaml";
+import configYaml from "../../data/storeConfig.yaml?raw";
+
+// Configuration type definitions
+interface Config {
+ api: {
+ baseUrl: string;
+ };
+ ui: {
+ messages: {
+ event: {
+ saving: string;
+ success: string;
+ error: string;
+ deleting: string;
+ deleteSuccess: string;
+ deleteError: string;
+ messageTimeout: number;
+ };
+ };
+ defaults: {
+ pageSize: number;
+ sortField: string;
+ };
+ };
+}
+
+// Parse YAML configuration with type
+const config = yaml.load(configYaml) as Config;
+
+interface Event {
+ id: string;
+ event_id: string;
+ event_name: string;
+ event_code: string;
+ registered_users: string; // JSON string
+ points_to_reward: number;
+ start_date: string;
+ end_date: string;
+ collectionId: string;
+ collectionName: string;
+}
+
+interface AuthElements {
+ eventList: HTMLTableSectionElement;
+ eventSearch: HTMLInputElement;
+ searchEvents: HTMLButtonElement;
+ addEvent: HTMLButtonElement;
+ eventEditor: HTMLDialogElement;
+ editorEventId: HTMLInputElement;
+ editorEventName: HTMLInputElement;
+ editorEventCode: HTMLInputElement;
+ editorStartDate: HTMLInputElement;
+ editorEndDate: HTMLInputElement;
+ editorPointsToReward: HTMLInputElement;
+ saveEventButton: HTMLButtonElement;
+}
+
+export class EventAuth {
+ private pb: PocketBase;
+ private elements: AuthElements;
+ private cachedEvents: Event[] = [];
+
+ constructor() {
+ this.pb = new PocketBase(config.api.baseUrl);
+ this.elements = this.getElements();
+ this.init();
+ }
+
+ private getElements(): AuthElements {
+ const eventList = document.getElementById("eventList") as HTMLTableSectionElement;
+ const eventSearch = document.getElementById("eventSearch") as HTMLInputElement;
+ const searchEvents = document.getElementById("searchEvents") as HTMLButtonElement;
+ const addEvent = document.getElementById("addEvent") as HTMLButtonElement;
+ const eventEditor = document.getElementById("eventEditor") as HTMLDialogElement;
+ const editorEventId = document.getElementById("editorEventId") as HTMLInputElement;
+ const editorEventName = document.getElementById("editorEventName") as HTMLInputElement;
+ const editorEventCode = document.getElementById("editorEventCode") as HTMLInputElement;
+ const editorStartDate = document.getElementById("editorStartDate") as HTMLInputElement;
+ const editorEndDate = document.getElementById("editorEndDate") as HTMLInputElement;
+ const editorPointsToReward = document.getElementById("editorPointsToReward") as HTMLInputElement;
+ const saveEventButton = document.getElementById("saveEventButton") as HTMLButtonElement;
+
+ if (
+ !eventList ||
+ !eventSearch ||
+ !searchEvents ||
+ !addEvent ||
+ !eventEditor ||
+ !editorEventId ||
+ !editorEventName ||
+ !editorEventCode ||
+ !editorStartDate ||
+ !editorEndDate ||
+ !editorPointsToReward ||
+ !saveEventButton
+ ) {
+ throw new Error("Required DOM elements not found");
+ }
+
+ return {
+ eventList,
+ eventSearch,
+ searchEvents,
+ addEvent,
+ eventEditor,
+ editorEventId,
+ editorEventName,
+ editorEventCode,
+ editorStartDate,
+ editorEndDate,
+ editorPointsToReward,
+ saveEventButton,
+ };
+ }
+
+ private getRegisteredUsersCount(registeredUsers: string): number {
+ try {
+ if (!registeredUsers) return 0;
+ const users = JSON.parse(registeredUsers);
+ return Array.isArray(users) ? users.length : 0;
+ } catch (err) {
+ console.warn("Failed to parse registered_users:", err);
+ return 0;
+ }
+ }
+
+ private async fetchEvents(searchQuery: string = "") {
+ try {
+ // Only fetch from API if we don't have cached data
+ if (this.cachedEvents.length === 0) {
+ const records = await this.pb.collection("events").getList(1, config.ui.defaults.pageSize, {
+ sort: config.ui.defaults.sortField,
+ });
+ this.cachedEvents = records.items as Event[];
+ }
+
+ // Filter cached data based on search query
+ let filteredEvents = this.cachedEvents;
+ if (searchQuery) {
+ const terms = searchQuery.toLowerCase().split(" ").filter(term => term.length > 0);
+ if (terms.length > 0) {
+ filteredEvents = this.cachedEvents.filter(event => {
+ return terms.every(term =>
+ (event.event_name?.toLowerCase().includes(term) ||
+ event.event_id?.toLowerCase().includes(term) ||
+ event.event_code?.toLowerCase().includes(term))
+ );
+ });
+ }
+ }
+
+ const { eventList } = this.elements;
+ const fragment = document.createDocumentFragment();
+
+ if (filteredEvents.length === 0) {
+ const row = document.createElement("tr");
+ row.innerHTML = `
+
+ ${searchQuery ? "No events found matching your search." : "No events found."}
+ |
+ `;
+ fragment.appendChild(row);
+ } else {
+ filteredEvents.forEach((event) => {
+ const row = document.createElement("tr");
+ const startDate = event.start_date ? new Date(event.start_date).toLocaleString() : "N/A";
+ const endDate = event.end_date ? new Date(event.end_date).toLocaleString() : "N/A";
+ const registeredCount = this.getRegisteredUsersCount(event.registered_users);
+
+ row.innerHTML = `
+
+
+
+ ${event.event_name || "N/A"}
+ Event ID: ${event.event_id || "N/A"}
+ Code: ${event.event_code || "N/A"}
+ Start: ${startDate}
+ End: ${endDate}
+ Points: ${event.points_to_reward || 0}
+ Registered: ${registeredCount}
+
+
+
+
+
+
+
+ ${event.event_name || "N/A"}
+ |
+ ${event.event_id || "N/A"} |
+ ${event.event_code || "N/A"} |
+ ${startDate} |
+ ${endDate} |
+ ${event.points_to_reward || 0} |
+ ${registeredCount} |
+
+
+
+
+
+ |
+ `;
+
+ fragment.appendChild(row);
+ });
+ }
+
+ eventList.innerHTML = "";
+ eventList.appendChild(fragment);
+
+ // Setup event listeners for edit and delete buttons
+ const editButtons = eventList.querySelectorAll(".edit-event");
+ editButtons.forEach((button) => {
+ button.addEventListener("click", () => {
+ const eventId = (button as HTMLButtonElement).dataset.eventId;
+ if (eventId) {
+ this.handleEventEdit(eventId);
+ }
+ });
+ });
+
+ const deleteButtons = eventList.querySelectorAll(".delete-event");
+ deleteButtons.forEach((button) => {
+ button.addEventListener("click", () => {
+ const eventId = (button as HTMLButtonElement).dataset.eventId;
+ if (eventId) {
+ this.handleEventDelete(eventId);
+ }
+ });
+ });
+ } catch (err) {
+ console.error("Failed to fetch events:", err);
+ const { eventList } = this.elements;
+ eventList.innerHTML = `
+
+
+ Failed to fetch events. Please try again.
+ |
+
+ `;
+ }
+ }
+
+ private async handleEventEdit(eventId: string) {
+ try {
+ const event = await this.pb.collection("events").getOne(eventId);
+ const {
+ eventEditor,
+ editorEventId,
+ editorEventName,
+ editorEventCode,
+ editorStartDate,
+ editorEndDate,
+ editorPointsToReward,
+ saveEventButton,
+ } = this.elements;
+
+ // Populate the form
+ editorEventId.value = event.event_id || "";
+ editorEventName.value = event.event_name || "";
+ editorEventCode.value = event.event_code || "";
+ editorStartDate.value = event.start_date ? new Date(event.start_date).toISOString().slice(0, 16) : "";
+ editorEndDate.value = event.end_date ? new Date(event.end_date).toISOString().slice(0, 16) : "";
+ editorPointsToReward.value = event.points_to_reward?.toString() || "0";
+
+ // Store the event ID for saving
+ saveEventButton.dataset.eventId = eventId;
+
+ // Disable event_id field for existing events
+ editorEventId.disabled = true;
+
+ // Show the dialog
+ eventEditor.showModal();
+ } catch (err) {
+ console.error("Failed to load event for editing:", err);
+ }
+ }
+
+ private async handleEventSave() {
+ const {
+ eventEditor,
+ editorEventId,
+ editorEventName,
+ editorEventCode,
+ editorStartDate,
+ editorEndDate,
+ editorPointsToReward,
+ saveEventButton,
+ } = this.elements;
+
+ const eventId = saveEventButton.dataset.eventId;
+ const isNewEvent = !eventId;
+
+ try {
+ const eventData: Record = {
+ event_name: editorEventName.value,
+ event_code: editorEventCode.value,
+ start_date: editorStartDate.value,
+ end_date: editorEndDate.value,
+ points_to_reward: parseInt(editorPointsToReward.value) || 0,
+ };
+
+ // Only set registered_users for new events
+ if (isNewEvent) {
+ eventData.event_id = editorEventId.value;
+ eventData.registered_users = "[]";
+ }
+
+ if (isNewEvent) {
+ await this.pb.collection("events").create(eventData);
+ } else {
+ await this.pb.collection("events").update(eventId, eventData);
+ }
+
+ // Close the dialog and refresh the table
+ eventEditor.close();
+ this.cachedEvents = []; // Clear cache to force refresh
+ this.fetchEvents();
+ } catch (err) {
+ console.error("Failed to save event:", err);
+ }
+ }
+
+ private async handleEventDelete(eventId: string) {
+ if (confirm("Are you sure you want to delete this event?")) {
+ try {
+ await this.pb.collection("events").delete(eventId);
+ this.cachedEvents = []; // Clear cache to force refresh
+ this.fetchEvents();
+ } catch (err) {
+ console.error("Failed to delete event:", err);
+ }
+ }
+ }
+
+ private init() {
+ // Initial fetch
+ this.fetchEvents();
+
+ // Search functionality
+ const handleSearch = () => {
+ const searchQuery = this.elements.eventSearch.value.trim();
+ this.fetchEvents(searchQuery);
+ };
+
+ // Real-time search
+ this.elements.eventSearch.addEventListener("input", handleSearch);
+
+ // Search button click handler
+ this.elements.searchEvents.addEventListener("click", handleSearch);
+
+ // Add event button
+ this.elements.addEvent.addEventListener("click", () => {
+ const { eventEditor, editorEventId, saveEventButton } = this.elements;
+
+ // Clear the form
+ this.elements.editorEventId.value = "";
+ this.elements.editorEventName.value = "";
+ this.elements.editorEventCode.value = "";
+ this.elements.editorStartDate.value = "";
+ this.elements.editorEndDate.value = "";
+ this.elements.editorPointsToReward.value = "0";
+
+ // Enable event_id field for new events
+ editorEventId.disabled = false;
+
+ // Clear the event ID to indicate this is a new event
+ saveEventButton.dataset.eventId = "";
+
+ // Show the dialog
+ eventEditor.showModal();
+ });
+
+ // Event editor dialog
+ const { eventEditor, saveEventButton } = this.elements;
+
+ // Close dialog when clicking outside
+ eventEditor.addEventListener("click", (e) => {
+ if (e.target === eventEditor) {
+ eventEditor.close();
+ }
+ });
+
+ // Save event button
+ saveEventButton.addEventListener("click", (e) => {
+ e.preventDefault();
+ this.handleEventSave();
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/components/store/EventEditor.astro b/src/components/store/EventEditor.astro
new file mode 100644
index 0000000..062574c
--- /dev/null
+++ b/src/components/store/EventEditor.astro
@@ -0,0 +1,96 @@
+
+
diff --git a/src/components/store/EventManagement.astro b/src/components/store/EventManagement.astro
new file mode 100644
index 0000000..11428b6
--- /dev/null
+++ b/src/components/store/EventManagement.astro
@@ -0,0 +1,82 @@
+---
+import EventEditor from "./EventEditor.astro";
+---
+
+
+
Event Management
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/store/MemberManagement.astro b/src/components/store/MemberManagement.astro
new file mode 100644
index 0000000..ca2a9da
--- /dev/null
+++ b/src/components/store/MemberManagement.astro
@@ -0,0 +1,67 @@
+
+
Member Management
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/data/storeConfig.yaml b/src/data/storeConfig.yaml
index 106c9d1..3cf86d0 100644
--- a/src/data/storeConfig.yaml
+++ b/src/data/storeConfig.yaml
@@ -53,6 +53,15 @@ ui:
deleteError: Failed to delete resume. Please try again.
messageTimeout: 3000
+ event:
+ saving: Saving event...
+ success: Event saved successfully!
+ error: Failed to save event. Please try again.
+ deleting: Deleting event...
+ deleteSuccess: Event deleted successfully!
+ deleteError: Failed to delete event. Please try again.
+ messageTimeout: 3000
+
auth:
loginError: Failed to start authentication
notSignedIn: Not signed in