add event checking

This commit is contained in:
chark1es 2025-03-02 00:49:42 -08:00
parent 2224d18ce8
commit e64f2ab882
3 changed files with 422 additions and 56 deletions

View file

@ -1,4 +1,4 @@
import { useState } from "react"; import { useState, useEffect } from "react";
import { Get } from "../../../scripts/pocketbase/Get"; import { Get } from "../../../scripts/pocketbase/Get";
import { Authentication } from "../../../scripts/pocketbase/Authentication"; import { Authentication } from "../../../scripts/pocketbase/Authentication";
import { Update } from "../../../scripts/pocketbase/Update"; import { Update } from "../../../scripts/pocketbase/Update";
@ -23,6 +23,14 @@ const EventCheckIn = () => {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [foodInput, setFoodInput] = useState(""); const [foodInput, setFoodInput] = useState("");
// SECURITY FIX: Purge event codes when component mounts
useEffect(() => {
const dataSync = DataSyncService.getInstance();
dataSync.purgeEventCodes().catch(err => {
console.error("Error purging event codes:", err);
});
}, []);
async function handleEventCheckIn(eventCode: string): Promise<void> { async function handleEventCheckIn(eventCode: string): Promise<void> {
try { try {
const get = Get.getInstance(); const get = Get.getInstance();
@ -48,18 +56,19 @@ const EventCheckIn = () => {
`User ${currentUser.id} attempted to check in with code: ${eventCode}` `User ${currentUser.id} attempted to check in with code: ${eventCode}`
); );
// Find the event with the given code using IndexedDB // SECURITY FIX: Instead of syncing and querying IndexedDB with the event code,
// Force sync to ensure we have the latest data // directly query PocketBase for the event with the given code
await dataSync.syncCollection(Collections.EVENTS, `event_code = "${eventCode}"`); // This prevents the event code from being stored in IndexedDB
const pb = auth.getPocketBase();
const records = await pb.collection(Collections.EVENTS).getList(1, 1, {
filter: `event_code = "${eventCode}"`,
});
// Get the event from IndexedDB // Convert the first result to our Event type
const events = await dataSync.getData<Event>( let event: Event | null = null;
Collections.EVENTS, if (records.items.length > 0) {
false, // Don't force sync again event = Get.convertUTCToLocal(records.items[0] as unknown as Event);
`event_code = "${eventCode}"` }
);
const event = events.length > 0 ? events[0] : null;
if (!event) { if (!event) {
await logger.send( await logger.send(
@ -186,8 +195,11 @@ const EventCheckIn = () => {
// Update attendees array with the new entry // Update attendees array with the new entry
await update.updateField("events", event.id, "attendees", updatedAttendees); await update.updateField("events", event.id, "attendees", updatedAttendees);
// Force sync the events collection to update IndexedDB // SECURITY FIX: Instead of syncing the entire events collection which would store event codes in IndexedDB,
await dataSync.syncCollection(Collections.EVENTS); // only sync the user's collection to update their points
if (event.points_to_reward > 0) {
await dataSync.syncCollection(Collections.USERS);
}
// If food selection was made, log it // If food selection was made, log it
if (foodSelection) { if (foodSelection) {
@ -208,9 +220,6 @@ const EventCheckIn = () => {
userPoints + event.points_to_reward userPoints + event.points_to_reward
); );
// Force sync the users collection to update IndexedDB
await dataSync.syncCollection(Collections.USERS);
// Log the points award // Log the points award
await logger.send( await logger.send(
"update", "update",
@ -313,6 +322,37 @@ const EventCheckIn = () => {
</div> </div>
</div> </div>
</div> </div>
{/* Food Selection Modal */}
<dialog id="foodSelectionModal" className="modal">
<div className="modal-box">
<h3 className="font-bold text-lg mb-4">Food Selection</h3>
<p className="mb-4">This event has food! Please let us know what you'd like to eat:</p>
<form onSubmit={handleSubmit}>
<div className="form-control">
<input
type="text"
placeholder="Enter your food preference"
className="input input-bordered w-full"
value={foodInput}
onChange={(e) => setFoodInput(e.target.value)}
required
/>
</div>
<div className="modal-action">
<button type="button" className="btn" onClick={() => {
const modal = document.getElementById("foodSelectionModal") as HTMLDialogElement;
modal.close();
setCurrentCheckInEvent(null);
}}>Cancel</button>
<button type="submit" className="btn btn-primary">Submit</button>
</div>
</form>
</div>
<form method="dialog" className="modal-backdrop">
<button>close</button>
</form>
</dialog>
</> </>
); );
}; };

View file

@ -22,10 +22,16 @@ import { Stats } from "./ProfileSection/Stats";
<Icon name="heroicons:eye" class="h-5 w-5" /> <Icon name="heroicons:eye" class="h-5 w-5" />
</div> </div>
Recent Activity Recent Activity
<div class="badge badge-ghost text-xs font-normal">
Real-time updates
</div>
</h3> </h3>
<p class="text-sm opacity-70">
Track your recent interactions with the IEEE UCSD platform
</p>
<div class="divider"></div> <div class="divider"></div>
<div class=""> <div class="">
<div class="min-h-[200px]"> <div class="min-h-[300px]">
<ShowProfileLogs client:load /> <ShowProfileLogs client:load />
</div> </div>
</div> </div>

View file

@ -4,6 +4,7 @@ import { DataSyncService } from "../../../scripts/database/DataSyncService";
import { Collections } from "../../../schemas/pocketbase/schema"; import { Collections } from "../../../schemas/pocketbase/schema";
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import type { Log } from "../../../schemas/pocketbase"; import type { Log } from "../../../schemas/pocketbase";
import { SendLog } from "../../../scripts/pocketbase/SendLog";
const LOGS_PER_PAGE = 5; const LOGS_PER_PAGE = 5;
@ -17,6 +18,20 @@ export default function ShowProfileLogs() {
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const [allLogs, setAllLogs] = useState<Log[]>([]); const [allLogs, setAllLogs] = useState<Log[]>([]);
const [isFetchingAll, setIsFetchingAll] = useState(false); const [isFetchingAll, setIsFetchingAll] = useState(false);
const [autoRefresh, setAutoRefresh] = useState(true);
// Auto-refresh logs every 30 seconds if enabled
useEffect(() => {
if (!autoRefresh) return;
const interval = setInterval(() => {
if (!isFetchingAll) {
fetchLogs(true);
}
}, 30000); // 30 seconds
return () => clearInterval(interval);
}, [autoRefresh, isFetchingAll]);
const fetchLogs = async (skipCache = false) => { const fetchLogs = async (skipCache = false) => {
setLoading(true); setLoading(true);
@ -34,6 +49,7 @@ export default function ShowProfileLogs() {
try { try {
setIsFetchingAll(true); setIsFetchingAll(true);
console.log("Fetching logs for user:", userId);
// Use DataSyncService to fetch logs // Use DataSyncService to fetch logs
const dataSync = DataSyncService.getInstance(); const dataSync = DataSyncService.getInstance();
@ -41,21 +57,49 @@ export default function ShowProfileLogs() {
// First sync logs for this user // First sync logs for this user
await dataSync.syncCollection( await dataSync.syncCollection(
Collections.LOGS, Collections.LOGS,
`user_id = "${userId}"`, `user = "${userId}"`,
"-created" "-created",
{ expand: "user" }
); );
// Then get all logs from IndexedDB // Then get all logs from IndexedDB
const fetchedLogs = await dataSync.getData<Log>( const fetchedLogs = await dataSync.getData<Log>(
Collections.LOGS, Collections.LOGS,
false, // Don't force sync again false, // Don't force sync again
`user_id = "${userId}"`, `user = "${userId}"`,
"-created" "-created"
); );
setAllLogs(fetchedLogs); console.log("Fetched logs:", fetchedLogs.length);
setTotalPages(Math.ceil(fetchedLogs.length / LOGS_PER_PAGE));
setTotalLogs(fetchedLogs.length); if (fetchedLogs.length === 0) {
// If no logs found, try to fetch directly from PocketBase
console.log("No logs found in IndexedDB, trying direct fetch from PocketBase");
try {
const sendLog = SendLog.getInstance();
const directLogs = await sendLog.getUserLogs(userId);
console.log("Direct fetch logs:", directLogs.length);
if (directLogs.length > 0) {
setAllLogs(directLogs);
setTotalPages(Math.ceil(directLogs.length / LOGS_PER_PAGE));
setTotalLogs(directLogs.length);
} else {
setAllLogs(fetchedLogs);
setTotalPages(Math.ceil(fetchedLogs.length / LOGS_PER_PAGE));
setTotalLogs(fetchedLogs.length);
}
} catch (directError) {
console.error("Failed to fetch logs directly:", directError);
setAllLogs(fetchedLogs);
setTotalPages(Math.ceil(fetchedLogs.length / LOGS_PER_PAGE));
setTotalLogs(fetchedLogs.length);
}
} else {
setAllLogs(fetchedLogs);
setTotalPages(Math.ceil(fetchedLogs.length / LOGS_PER_PAGE));
setTotalLogs(fetchedLogs.length);
}
} catch (error) { } catch (error) {
console.error("Failed to fetch logs:", error); console.error("Failed to fetch logs:", error);
setError("Error loading activity"); setError("Error loading activity");
@ -67,6 +111,8 @@ export default function ShowProfileLogs() {
// Memoized search function // Memoized search function
const filteredLogs = useMemo(() => { const filteredLogs = useMemo(() => {
if (!allLogs.length) return [];
if (!searchQuery.trim()) { if (!searchQuery.trim()) {
// When not searching, return only the current page of logs // When not searching, return only the current page of logs
const startIndex = (currentPage - 1) * LOGS_PER_PAGE; const startIndex = (currentPage - 1) * LOGS_PER_PAGE;
@ -78,6 +124,8 @@ export default function ShowProfileLogs() {
return allLogs.filter(log => { return allLogs.filter(log => {
return ( return (
log.message?.toLowerCase().includes(query) || log.message?.toLowerCase().includes(query) ||
log.type?.toLowerCase().includes(query) ||
log.part?.toLowerCase().includes(query) ||
(log.created && new Date(log.created).toLocaleString().toLowerCase().includes(query)) (log.created && new Date(log.created).toLocaleString().toLowerCase().includes(query))
); );
}); });
@ -86,6 +134,7 @@ export default function ShowProfileLogs() {
// Update displayed logs whenever filtered results change // Update displayed logs whenever filtered results change
useEffect(() => { useEffect(() => {
setLogs(filteredLogs); setLogs(filteredLogs);
console.log("Filtered logs updated:", filteredLogs.length, "logs");
}, [filteredLogs]); }, [filteredLogs]);
// Debounced search handler // Debounced search handler
@ -121,9 +170,128 @@ export default function ShowProfileLogs() {
}; };
useEffect(() => { useEffect(() => {
fetchLogs(); const loadLogsWithRetry = async () => {
try {
await fetchLogs();
// Wait a moment for state to update
setTimeout(async () => {
// Check if logs were loaded
if (allLogs.length === 0) {
console.log("No logs found after initial fetch, trying direct fetch");
await directFetchLogs();
}
}, 1000);
} catch (error) {
console.error("Failed to load logs with retry:", error);
}
};
loadLogsWithRetry();
checkLogsExist();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
// Check if there are any logs in the database at all
const checkLogsExist = async () => {
try {
const auth = Authentication.getInstance();
const pb = auth.getPocketBase();
// Check if the logs collection exists and has any records
const result = await pb.collection(Collections.LOGS).getList(1, 1);
console.log("Logs collection check:", {
totalItems: result.totalItems,
page: result.page,
perPage: result.perPage,
totalPages: result.totalPages
});
} catch (error) {
console.error("Failed to check logs collection:", error);
}
};
// Calculate log statistics
const logStats = useMemo(() => {
if (!allLogs.length) return null;
const today = new Date();
today.setHours(0, 0, 0, 0);
const lastWeek = new Date(today);
lastWeek.setDate(lastWeek.getDate() - 7);
const todayLogs = allLogs.filter(log => new Date(log.created) >= today);
const weekLogs = allLogs.filter(log => new Date(log.created) >= lastWeek);
// Count by type
const typeCount: Record<string, number> = {};
allLogs.forEach(log => {
const type = log.type || 'unknown';
typeCount[type] = (typeCount[type] || 0) + 1;
});
return {
total: allLogs.length,
today: todayLogs.length,
week: weekLogs.length,
types: typeCount
};
}, [allLogs]);
// Direct fetch from PocketBase as a fallback
const directFetchLogs = async () => {
try {
setLoading(true);
setError(null);
const auth = Authentication.getInstance();
const pb = auth.getPocketBase();
const userId = auth.getPocketBase().authStore.model?.id;
if (!userId) {
setError("Not authenticated");
setLoading(false);
return;
}
console.log("Direct fetching logs for user:", userId);
// Fetch logs directly from PocketBase
const result = await pb.collection(Collections.LOGS).getList<Log>(1, 100, {
filter: `user = "${userId}"`,
sort: "-created",
expand: "user"
});
console.log("Direct fetch result:", {
totalItems: result.totalItems,
items: result.items.length
});
if (result.items.length > 0) {
setAllLogs(result.items);
setTotalPages(Math.ceil(result.items.length / LOGS_PER_PAGE));
setTotalLogs(result.items.length);
}
} catch (error) {
console.error("Failed to direct fetch logs:", error);
setError("Error loading activity");
} finally {
setLoading(false);
}
};
// Add a button to try direct fetch
const renderDirectFetchButton = () => (
<button
className="btn btn-sm btn-outline mt-4"
onClick={directFetchLogs}
>
Try Direct Fetch
</button>
);
if (loading && !allLogs.length) { if (loading && !allLogs.length) {
return ( return (
<p className="text-base-content/70 flex items-center gap-2"> <p className="text-base-content/70 flex items-center gap-2">
@ -146,19 +314,83 @@ export default function ShowProfileLogs() {
); );
} }
if (logs.length === 0 && !searchQuery && !loading) { // Debug logs
console.log("Render state:", {
logsLength: logs.length,
allLogsLength: allLogs.length,
searchQuery,
loading,
currentPage
});
if (allLogs.length === 0 && !searchQuery && !loading) {
return ( return (
<p className="text-base-content/70 flex items-center gap-2"> <div>
<svg className="h-5 w-5 opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"> <p className="text-base-content/70 flex items-center gap-2 mb-4">
<path d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z" /> <svg className="h-5 w-5 opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
</svg> <path d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z" />
No recent activity to display. </svg>
</p> No recent activity to display.
</p>
<div className="flex gap-2">
<button
className="btn btn-sm btn-primary"
onClick={async () => {
try {
const auth = Authentication.getInstance();
const userId = auth.getPocketBase().authStore.model?.id;
if (!userId) return;
const sendLog = SendLog.getInstance();
await sendLog.send(
"create",
"test",
"Test log created for debugging",
userId
);
console.log("Created test log");
setTimeout(() => fetchLogs(true), 1000);
} catch (error) {
console.error("Failed to create test log:", error);
}
}}
>
Create Test Log
</button>
<button
className="btn btn-sm btn-outline"
onClick={directFetchLogs}
>
Try Direct Fetch
</button>
</div>
</div>
); );
} }
return ( return (
<div> <div>
{/* Activity Summary */}
{logStats && !searchQuery && (
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<div className="stat bg-base-200 rounded-lg p-4">
<div className="stat-title">Today</div>
<div className="stat-value">{logStats.today}</div>
<div className="stat-desc">Activities recorded today</div>
</div>
<div className="stat bg-base-200 rounded-lg p-4">
<div className="stat-title">This Week</div>
<div className="stat-value">{logStats.week}</div>
<div className="stat-desc">Activities in the last 7 days</div>
</div>
<div className="stat bg-base-200 rounded-lg p-4">
<div className="stat-title">Total</div>
<div className="stat-value">{logStats.total}</div>
<div className="stat-desc">All-time activities</div>
</div>
</div>
)}
{/* Search and Refresh Controls */} {/* Search and Refresh Controls */}
<div className="flex gap-4 mb-4"> <div className="flex gap-4 mb-4">
<div className="flex-1"> <div className="flex-1">
@ -169,16 +401,35 @@ export default function ShowProfileLogs() {
className="input input-bordered w-full" className="input input-bordered w-full"
/> />
</div> </div>
<button <div className="dropdown dropdown-end dropdown-hover">
onClick={handleRefresh} <button
className={`btn btn-ghost btn-square `} onClick={handleRefresh}
title="Refresh logs" className={`btn btn-ghost btn-square ${isFetchingAll ? 'loading' : ''}`}
disabled={isFetchingAll} title="Refresh logs"
> disabled={isFetchingAll}
<svg className={`h-5 w-5 ${isFetchingAll ? 'animate-spin' : ''}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" fill="currentColor"> >
<path fill="currentColor" d="M771.776 794.88A384 384 0 0 1 128 512h64a320 320 0 0 0 555.712 216.448H654.72a32 32 0 1 1 0-64h149.056a32 32 0 0 1 32 32v148.928a32 32 0 1 1-64 0v-50.56zM276.288 295.616h92.992a32 32 0 0 1 0 64H220.16a32 32 0 0 1-32-32V178.56a32 32 0 0 1 64 0v50.56A384 384 0 0 1 896.128 512h-64a320 320 0 0 0-555.776-216.384z" /> <svg className="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
</svg> <path fillRule="evenodd" d="M4.755 10.059a7.5 7.5 0 0112.548-3.364l1.903 1.903h-3.183a.75.75 0 100 1.5h4.992a.75.75 0 00.75-.75V4.356a.75.75 0 00-1.5 0v3.18l-1.9-1.9A9 9 0 003.306 9.67a.75.75 0 101.45.388zm15.408 3.352a.75.75 0 00-.919.53 7.5 7.5 0 01-12.548 3.364l-1.902-1.903h3.183a.75.75 0 000-1.5H2.984a.75.75 0 00-.75.75v4.992a.75.75 0 001.5 0v-3.18l1.9 1.9a9 9 0 0015.059-4.035.75.75 0 00-.53-.918z" clipRule="evenodd" />
</button> </svg>
</button>
<ul tabIndex={0} className="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52">
<li>
<button
onClick={() => setAutoRefresh(!autoRefresh)}
className="flex justify-between"
>
<span>Auto-refresh</span>
<input
type="checkbox"
className="toggle toggle-primary toggle-sm"
checked={autoRefresh}
onChange={() => { }}
/>
</button>
</li>
<li><button onClick={directFetchLogs}>Direct fetch from server</button></li>
</ul>
</div>
</div> </div>
{isFetchingAll && ( {isFetchingAll && (
@ -199,24 +450,27 @@ export default function ShowProfileLogs() {
{logs.map((log) => ( {logs.map((log) => (
<div key={log.id} className="flex items-start gap-4 p-4 rounded-lg hover:bg-base-200 transition-colors duration-200"> <div key={log.id} className="flex items-start gap-4 p-4 rounded-lg hover:bg-base-200 transition-colors duration-200">
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<div className="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center"> <div className={`w-10 h-10 rounded-full flex items-center justify-center ${getLogTypeColor(log.type)}`}>
<svg className="h-5 w-5 text-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"> {getLogTypeIcon(log.type)}
<path d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2zm0 18c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8zm1-13h-2v6l5.25 3.15.75-1.23-4-2.37z" />
</svg>
</div> </div>
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-base font-medium">{log.message}</p> <p className="text-base font-medium">{log.message}</p>
<p className="text-sm opacity-50 mt-1"> <div className="flex items-center gap-2 mt-1">
{new Date(log.created).toLocaleString(undefined, { {log.part && (
weekday: 'long', <span className="badge badge-sm">{log.part}</span>
year: 'numeric', )}
month: 'long', <p className="text-sm opacity-50">
day: 'numeric', {new Date(log.created).toLocaleString(undefined, {
hour: '2-digit', weekday: 'long',
minute: '2-digit' year: 'numeric',
})} month: 'long',
</p> day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}
</p>
</div>
</div> </div>
</div> </div>
))} ))}
@ -257,3 +511,69 @@ export default function ShowProfileLogs() {
</div> </div>
); );
} }
function getLogTypeColor(type: string): string {
switch (type?.toLowerCase()) {
case 'error':
return 'bg-error/10 text-error';
case 'update':
return 'bg-info/10 text-info';
case 'delete':
return 'bg-warning/10 text-warning';
case 'create':
return 'bg-success/10 text-success';
case 'login':
case 'logout':
return 'bg-primary/10 text-primary';
default:
return 'bg-base-300/50 text-base-content';
}
}
function getLogTypeIcon(type: string) {
switch (type?.toLowerCase()) {
case 'error':
return (
<svg className="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
</svg>
);
case 'update':
return (
<svg className="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M21.731 2.269a2.625 2.625 0 00-3.712 0l-1.157 1.157 3.712 3.712 1.157-1.157a2.625 2.625 0 000-3.712zM19.513 8.199l-3.712-3.712-8.4 8.4a5.25 5.25 0 00-1.32 2.214l-.8 2.685a.75.75 0 00.933.933l2.685-.8a5.25 5.25 0 002.214-1.32l8.4-8.4z" />
<path d="M5.25 5.25a3 3 0 00-3 3v10.5a3 3 0 003 3h10.5a3 3 0 003-3V13.5a.75.75 0 00-1.5 0v5.25a1.5 1.5 0 01-1.5 1.5H5.25a1.5 1.5 0 01-1.5-1.5V8.25a1.5 1.5 0 011.5-1.5h5.25a.75.75 0 000-1.5H5.25z" />
</svg>
);
case 'delete':
return (
<svg className="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path fillRule="evenodd" d="M16.5 4.478v.227a48.816 48.816 0 013.878.512.75.75 0 11-.256 1.478l-.209-.035-1.005 13.07a3 3 0 01-2.991 2.77H8.084a3 3 0 01-2.991-2.77L4.087 6.66l-.209.035a.75.75 0 01-.256-1.478A48.567 48.567 0 017.5 4.705v-.227c0-1.564 1.213-2.9 2.816-2.951a52.662 52.662 0 013.369 0c1.603.051 2.815 1.387 2.815 2.951zm-6.136-1.452a51.196 51.196 0 013.273 0C14.39 3.05 15 3.684 15 4.478v.113a49.488 49.488 0 00-6 0v-.113c0-.794.609-1.428 1.364-1.452zm-.355 5.945a.75.75 0 10-1.5.058l.347 9a.75.75 0 101.499-.058l-.346-9zm5.48.058a.75.75 0 10-1.498-.058l-.347 9a.75.75 0 001.5.058l.345-9z" clipRule="evenodd" />
</svg>
);
case 'create':
return (
<svg className="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path fillRule="evenodd" d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z" clipRule="evenodd" />
</svg>
);
case 'login':
return (
<svg className="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path fillRule="evenodd" d="M7.5 3.75A1.5 1.5 0 006 5.25v13.5a1.5 1.5 0 001.5 1.5h6a1.5 1.5 0 001.5-1.5V15a.75.75 0 011.5 0v3.75a3 3 0 01-3 3h-6a3 3 0 01-3-3V5.25a3 3 0 013-3h6a3 3 0 013 3V9A.75.75 0 0115 9V5.25a1.5 1.5 0 00-1.5-1.5h-6zm10.72 4.72a.75.75 0 011.06 0l3 3a.75.75 0 010 1.06l-3 3a.75.75 0 11-1.06-1.06l1.72-1.72H9a.75.75 0 010-1.5h10.94l-1.72-1.72a.75.75 0 010-1.06z" clipRule="evenodd" />
</svg>
);
case 'logout':
return (
<svg className="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path fillRule="evenodd" d="M7.5 3.75A1.5 1.5 0 006 5.25v13.5a1.5 1.5 0 001.5 1.5h6a1.5 1.5 0 001.5-1.5V15a.75.75 0 011.5 0v3.75a3 3 0 01-3 3h-6a3 3 0 01-3-3V5.25a3 3 0 013-3h6a3 3 0 013 3V9A.75.75 0 0115 9V5.25a1.5 1.5 0 00-1.5-1.5h-6zm5.03 4.72a.75.75 0 010 1.06l-1.72 1.72h10.94a.75.75 0 010 1.5H10.81l1.72 1.72a.75.75 0 11-1.06 1.06l-3-3a.75.75 0 010-1.06l3-3a.75.75 0 011.06 0z" clipRule="evenodd" />
</svg>
);
default:
return (
<svg className="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2zm0 18c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8zm1-13h-2v6l5.25 3.15.75-1.23-4-2.37z" />
</svg>
);
}
}