Compare commits

..

1 Commits

Author SHA1 Message Date
jmug
9cc10e47e6 Handmade content changes 2026-03-07 06:17:39 +00:00
22 changed files with 112 additions and 72 deletions

View File

@@ -2,6 +2,7 @@ services:
# Database # Database
postgres: postgres:
image: postgres:15-alpine image: postgres:15-alpine
restart: unless-stopped
container_name: cactoide-db container_name: cactoide-db
environment: environment:
POSTGRES_DB: ${POSTGRES_DB:-cactoide_database} POSTGRES_DB: ${POSTGRES_DB:-cactoide_database}
@@ -28,7 +29,8 @@ services:
# Application # Application
app: app:
image: ghcr.io/polaroi8d/cactoide/cactoide:${APP_VERSION:-latest} image: cactoide:handmade
restart: unless-stopped
build: . build: .
container_name: cactoide-app container_name: cactoide-app
ports: ports:
@@ -46,7 +48,6 @@ services:
condition: service_healthy condition: service_healthy
networks: networks:
- cactoide-network - cactoide-network
restart: unless-stopped
volumes: volumes:
postgres_data: postgres_data:

View File

@@ -6,12 +6,6 @@
<link rel="icon" type="image/x-icon" href="/favicon.ico" /> <link rel="icon" type="image/x-icon" href="/favicon.ico" />
%sveltekit.head% %sveltekit.head%
<!-- Remove if you don't want to use analytics -->
<script
defer
src="https://analytics.dalev.hu/script.js"
data-website-id="7425d098-e340-4464-bd03-c2e47b004cd9"
></script>
</head> </head>
<body data-sveltekit-preload-data="hover"> <body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div> <div style="display: contents">%sveltekit.body%</div>

View File

@@ -27,16 +27,17 @@ export const handle: Handle = async ({ event, resolve }) => {
} }
const cactoideUserId = event.cookies.get('cactoideUserId'); const cactoideUserId = event.cookies.get('cactoideUserId');
const userId = generateUserId();
const DAYS = 400; // practical upper bound in many browsers for cookies const DAYS = 400; // practical upper bound in many browsers for cookies
const MAX_AGE = 60 * 60 * 24 * DAYS; const MAX_AGE = 60 * 60 * 24 * DAYS;
const PATH = '/'; const PATH = '/';
if (!cactoideUserId) { if (!cactoideUserId) {
logger.debug('No cactoideUserId cookie found, generating new one'); logger.debug({ userId }, 'No cactoideUserId cookie found, generating new one');
event.cookies.set('cactoideUserId', generateUserId(), { path: PATH, maxAge: MAX_AGE }); event.cookies.set('cactoideUserId', userId, { path: PATH, maxAge: MAX_AGE });
} else { } else {
logger.debug('cactoideUserId cookie found, using existing one'); logger.debug({ cactoideUserId }, 'cactoideUserId cookie found, using existing one');
} }
return resolve(event); return resolve(event);

View File

@@ -1,5 +1,5 @@
import { randomUUID } from 'crypto'; export const generateUserId = () => {
const userId = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
// This id is the only credential the app has — it must not be guessable, return userId;
// and it must never be serialized to the client. };
export const generateUserId = () => 'user_' + randomUUID();

View File

@@ -259,6 +259,8 @@
"layout": { "layout": {
"defaultTitle": "Cactoide -", "defaultTitle": "Cactoide -",
"defaultDescription": "Crea e gestisci gli RSVP degli eventi", "defaultDescription": "Crea e gestisci gli RSVP degli eventi",
"userIdCookieText": "Il tuo UserID memorizzato come cookie:",
"firstTimeVisiting": "Prima visita. Generazione di un nuovo UserID...",
"copyright": "© 2025 Cactoide" "copyright": "© 2025 Cactoide"
} }
} }

View File

@@ -103,16 +103,16 @@
"instance": "Instance" "instance": "Instance"
}, },
"home": { "home": {
"title": "Cactoide - The RSVP site", "title": "RSVP | Handmade Cities",
"description": "Create and manage event RSVPs. No registration required, instant sharing.", "description": "Create and manage event RSVPs. No registration required, instant sharing.",
"mainTitle": "Cactoide(ea)", "mainTitle": "Cactoide",
"subtitle": "The Ultimate RSVP Platform", "subtitle": "Handmade's Preferred RSVP System",
"tagline": "A federated mobile-first event RSVP platform that lets you create events, share unique URLs, and collect RSVPs without any registration required. With built-in federation, discover and share events across a decentralized network of instances.", "tagline": "Create, share, and manage events with zero friction.",
"openSourceTitle": "Open Source & Self-Hostable", "openSourceTitle": "Open Source & Self-Hostable",
"openSourceDescription": "Cactoide is open source and easily self-hostable. View the source code, contribute, or host your own instance.", "openSourceDescription": "Cactoide is open source and easily self-hostable. View the source code, contribute, or host your own instance.",
"viewOnGitHub": "View on GitHub", "viewOnGitHub": "View on GitHub",
"whyCactoideTitle": "Why Cactoide(ae)?🌵", "whyCactoideTitle": "Why Cactoide?",
"whyCactoideDescription": "Like the cactus, great events bloom under any condition when managed with care. Cactoide(ae) helps you streamline RSVPs, simplify coordination, and keep every detail efficient—so your gatherings are resilient, vibrant, and unforgettable.", "whyCactoideDescription": "Cactoide is lightweight and open source. Meetup Hosts should ALWAYS create private events. We currently don't prevent strangers from spamming public ones:",
"createEventNow": "Create Event Now", "createEventNow": "Create Event Now",
"discoverPublicEventsTitle": "Discover Public Events", "discoverPublicEventsTitle": "Discover Public Events",
"discoverPublicEventsDescription": "See what others are planning and get inspired", "discoverPublicEventsDescription": "See what others are planning and get inspired",
@@ -146,7 +146,7 @@
"ctaButton": "Create" "ctaButton": "Create"
}, },
"create": { "create": {
"title": "Create Event - Cactoide", "title": "Create Event - Handmade Cities",
"formTitle": "Create New Event", "formTitle": "Create New Event",
"eventNameLabel": "Name", "eventNameLabel": "Name",
"eventNamePlaceholder": "Enter event name", "eventNamePlaceholder": "Enter event name",
@@ -179,10 +179,10 @@
"createEventButton": "Create Event" "createEventButton": "Create Event"
}, },
"event": { "event": {
"title": "{eventName} - Cactoide", "title": "{eventName} - Handmade Cities",
"eventTitle": "Event - Cactoide", "eventTitle": "Event - Handmade Cities",
"editTitle": "Edit Event - {eventName} - Cactoide", "editTitle": "Edit Event - {eventName} - Handmade Cities",
"myEventsTitle": "My Events - Cactoide", "myEventsTitle": "My Events - Handmade Cities",
"eventNotFoundTitle": "Event Not Found", "eventNotFoundTitle": "Event Not Found",
"eventNotFoundDescription": "The event you're looking for doesn't exist or has been removed.", "eventNotFoundDescription": "The event you're looking for doesn't exist or has been removed.",
"joinThisEvent": "Join This Event", "joinThisEvent": "Join This Event",
@@ -230,7 +230,7 @@
"inviteLinkExpiresAt": "This link expires when the event starts: {time}" "inviteLinkExpiresAt": "This link expires when the event starts: {time}"
}, },
"discover": { "discover": {
"title": "Discover Events - Cactoide", "title": "Discover Events - Handmade Cities",
"noPublicEventsTitle": "No Public Events Yet", "noPublicEventsTitle": "No Public Events Yet",
"noPublicEventsDescription": "There are no public events available at the moment. Be the first to create one!", "noPublicEventsDescription": "There are no public events available at the moment. Be the first to create one!",
"createButton": "Create", "createButton": "Create",
@@ -283,14 +283,16 @@
"downloadICalDescription": "Download .ics file for any calendar app" "downloadICalDescription": "Download .ics file for any calendar app"
}, },
"errors": { "errors": {
"title": "Error - Cactoide", "title": "Error - Handmade Cities",
"errorTitle": "Error", "errorTitle": "Error",
"anUnexpectedErrorOccurred": "An unexpected error occurred.", "anUnexpectedErrorOccurred": "An unexpected error occurred.",
"homeButton": "Home" "homeButton": "Home"
}, },
"layout": { "layout": {
"defaultTitle": "Cactoide -", "defaultTitle": "Handmade Cities -",
"defaultDescription": "Create and manage event RSVPs", "defaultDescription": "Create and manage event RSVPs",
"userIdCookieText": "Your UserID stored as a cookie:",
"firstTimeVisiting": "First time visiting. Generating new UserID...",
"copyright": "© 2025 Cactoide" "copyright": "© 2025 Cactoide"
} }
} }

View File

@@ -14,7 +14,7 @@ export interface Event {
type: EventType; type: EventType;
attendee_limit?: number; attendee_limit?: number;
visibility: EventVisibility; visibility: EventVisibility;
is_creator?: boolean; // Optional: absent on events fetched from federated instances user_id: string;
created_at: string; created_at: string;
updated_at: string; updated_at: string;
federation?: boolean; // Optional: true if event is from a federated instance federation?: boolean; // Optional: true if event is from a federated instance
@@ -25,7 +25,7 @@ export interface RSVP {
id: string; id: string;
event_id: string; event_id: string;
name: string; name: string;
is_mine: boolean; user_id: string;
created_at: string; created_at: string;
} }

View File

@@ -0,0 +1,7 @@
export function load({ cookies }) {
const cactoideUserId = cookies.get('cactoideUserId');
return {
cactoideUserId
};
}

View File

@@ -3,7 +3,7 @@
import Navbar from '$lib/components/Navbar.svelte'; import Navbar from '$lib/components/Navbar.svelte';
import { t } from '$lib/i18n/i18n.js'; import { t } from '$lib/i18n/i18n.js';
let { children } = $props(); let { data, children } = $props();
</script> </script>
<svelte:head> <svelte:head>
@@ -28,6 +28,12 @@
<footer class="py-12"> <footer class="py-12">
<div class="container mx-auto px-4 text-center"> <div class="container mx-auto px-4 text-center">
<div class="text-sm"> <div class="text-sm">
<p class="mb-4 text-gray-100/80">
{t('layout.userIdCookieText')}
<span class="font-bold text-violet-400"
>{data.cactoideUserId ? data.cactoideUserId : t('layout.firstTimeVisiting')}</span
>
</p>
<p>{t('layout.copyright')}</p> <p>{t('layout.copyright')}</p>
</div> </div>
</div> </div>

View File

@@ -33,6 +33,7 @@ export const GET: RequestHandler = async () => {
federation: true, federation: true,
attendee_limit: event.attendeeLimit, attendee_limit: event.attendeeLimit,
visibility: event.visibility, visibility: event.visibility,
user_id: event.userId,
created_at: event.createdAt?.toISOString() || '', created_at: event.createdAt?.toISOString() || '',
updated_at: event.updatedAt?.toISOString() || '' updated_at: event.updatedAt?.toISOString() || ''
})); }));

View File

@@ -20,6 +20,7 @@
let errors: Record<string, string> = {}; let errors: Record<string, string> = {};
let isSubmitting = false; let isSubmitting = false;
let currentUserId = '';
// Get today's date in YYYY-MM-DD format for min attribute // Get today's date in YYYY-MM-DD format for min attribute
const today = new Date().toISOString().split('T')[0]; const today = new Date().toISOString().split('T')[0];
@@ -94,6 +95,7 @@
}} }}
class="space-y-6" class="space-y-6"
> >
<input type="hidden" name="userId" value={currentUserId} />
<input type="hidden" name="type" value={eventData.type} /> <input type="hidden" name="type" value={eventData.type} />
<input type="hidden" name="visibility" value={eventData.visibility} /> <input type="hidden" name="visibility" value={eventData.visibility} />
<input type="hidden" name="location_type" value={eventData.location_type} /> <input type="hidden" name="location_type" value={eventData.location_type} />

View File

@@ -26,6 +26,7 @@ export const load: PageServerLoad = async () => {
type: event.type, type: event.type,
attendee_limit: event.attendeeLimit, attendee_limit: event.attendeeLimit,
visibility: event.visibility, visibility: event.visibility,
user_id: event.userId,
created_at: event.createdAt?.toISOString(), created_at: event.createdAt?.toISOString(),
updated_at: event.updatedAt?.toISOString(), updated_at: event.updatedAt?.toISOString(),
federation: false // Add false for local events federation: false // Add false for local events

View File

@@ -30,6 +30,7 @@ export const load = async ({ cookies }) => {
type: event.type, type: event.type,
attendee_limit: event.attendeeLimit, attendee_limit: event.attendeeLimit,
visibility: event.visibility, visibility: event.visibility,
user_id: event.userId,
created_at: event.createdAt?.toISOString() || new Date().toISOString(), created_at: event.createdAt?.toISOString() || new Date().toISOString(),
updated_at: event.updatedAt?.toISOString() || new Date().toISOString() updated_at: event.updatedAt?.toISOString() || new Date().toISOString()
})); }));

View File

@@ -7,6 +7,7 @@
export let data: { events: Event[] }; export let data: { events: Event[] };
let userEvents: Event[] = []; let userEvents: Event[] = [];
let currentUserId = '';
let showDeleteModal = false; let showDeleteModal = false;
let eventToDelete: Event | null = null; let eventToDelete: Event | null = null;
@@ -27,6 +28,7 @@
// Use server-side action for deletion // Use server-side action for deletion
const formData = new FormData(); const formData = new FormData();
formData.append('eventId', eventId); formData.append('eventId', eventId);
formData.append('userId', currentUserId);
const response = await fetch('?/deleteEvent', { const response = await fetch('?/deleteEvent', {
method: 'POST', method: 'POST',

View File

@@ -7,7 +7,6 @@ import { logger } from '$lib/logger';
export const load: PageServerLoad = async ({ params, cookies }) => { export const load: PageServerLoad = async ({ params, cookies }) => {
const eventId = params.id; const eventId = params.id;
const userId = cookies.get('cactoideUserId');
if (!eventId) { if (!eventId) {
throw error(404, 'EventId not found'); throw error(404, 'EventId not found');
@@ -30,6 +29,7 @@ export const load: PageServerLoad = async ({ params, cookies }) => {
// Check if this is an invite-only event // Check if this is an invite-only event
if (event.visibility === 'invite-only') { if (event.visibility === 'invite-only') {
// For invite-only events, check if user is the event creator // For invite-only events, check if user is the event creator
const userId = cookies.get('cactoideUserId');
if (event.userId !== userId) { if (event.userId !== userId) {
// User is not the creator, redirect to a message about needing invite // User is not the creator, redirect to a message about needing invite
throw error(403, 'This event requires an invite link to view'); throw error(403, 'This event requires an invite link to view');
@@ -48,8 +48,7 @@ export const load: PageServerLoad = async ({ params, cookies }) => {
type: event.type, type: event.type,
attendee_limit: event.attendeeLimit, attendee_limit: event.attendeeLimit,
visibility: event.visibility, visibility: event.visibility,
// Never send raw user ids to the client — they are the credential user_id: event.userId,
is_creator: !!userId && event.userId === userId,
created_at: event.createdAt?.toISOString() || new Date().toISOString(), created_at: event.createdAt?.toISOString() || new Date().toISOString(),
updated_at: event.updatedAt?.toISOString() || new Date().toISOString() updated_at: event.updatedAt?.toISOString() || new Date().toISOString()
}; };
@@ -58,13 +57,16 @@ export const load: PageServerLoad = async ({ params, cookies }) => {
id: rsvp.id, id: rsvp.id,
event_id: rsvp.eventId, event_id: rsvp.eventId,
name: rsvp.name, name: rsvp.name,
is_mine: !!userId && rsvp.userId === userId, user_id: rsvp.userId,
created_at: rsvp.createdAt?.toISOString() || new Date().toISOString() created_at: rsvp.createdAt?.toISOString() || new Date().toISOString()
})); }));
const userId = cookies.get('cactoideUserId');
return { return {
event: transformedEvent, event: transformedEvent,
rsvps: transformedRsvps rsvps: transformedRsvps,
userId: userId
}; };
} catch (err) { } catch (err) {
if (err instanceof Response) throw err; // This is the 404 error if (err instanceof Response) throw err; // This is the 404 error

View File

@@ -8,8 +8,9 @@
import CalendarModal from '$lib/components/CalendarModal.svelte'; import CalendarModal from '$lib/components/CalendarModal.svelte';
import type { CalendarEvent } from '$lib/calendarHelpers.js'; import type { CalendarEvent } from '$lib/calendarHelpers.js';
import { t } from '$lib/i18n/i18n.js'; import { t } from '$lib/i18n/i18n.js';
import { onMount } from 'svelte';
export let data: { event: Event; rsvps: RSVP[] }; export let data: { event: Event; rsvps: RSVP[]; userId: string };
type FormDataLocal = { success?: boolean; error?: string; type?: 'add' | 'remove' | 'copy' }; type FormDataLocal = { success?: boolean; error?: string; type?: 'add' | 'remove' | 'copy' };
export let form: FormDataLocal | undefined; export let form: FormDataLocal | undefined;
@@ -27,10 +28,22 @@
let typeToShow: 'add' | 'remove' | 'copy' | undefined; let typeToShow: 'add' | 'remove' | 'copy' | undefined;
let successHideTimer: number | null = null; let successHideTimer: number | null = null;
// Compute eventId early so reactive blocks can use it.
const eventId = $page.params.id || '';
// client-only origin (empty during SSR).
let origin = '';
// Safe: Only runs in browser.
onMount(() => {
origin = window.location.origin;
});
// Use server-side data // Use server-side data
$: event = data.event; $: event = data.event;
$: rsvps = data.rsvps; $: rsvps = data.rsvps;
$: isEventCreator = event.is_creator ?? false; $: currentUserId = data.userId;
$: isEventCreator = event.user_id === currentUserId;
// Create calendar event object when event data changes // Create calendar event object when event data changes
$: if (event && browser) { $: if (event && browser) {
@@ -39,10 +52,26 @@
date: event.date, date: event.date,
time: event.time, time: event.time,
location: event.location, location: event.location,
url: `${$page.url.origin}/event/${eventId}` // Fallback to relative path on server render.
url: origin ? `${origin}/event/${eventId}` : `/event/${eventId}`
}; };
} }
const copyEventLink = () => {
if (browser && isEventCreator) {
const url = origin ? `${origin}/event/${eventId}` : `${location.origin}/event/${eventId}`;
navigator.clipboard.writeText(url).then(() => {
toastType = 'copy';
success = t('event.eventLinkCopied');
setTimeout(() => {
success = '';
toastType = null;
}, 3000);
});
}
};
// Handle form errors from server // Handle form errors from server
$: if (form?.error) { $: if (form?.error) {
error = String(form.error); error = String(form.error);
@@ -78,22 +107,7 @@
// Derive toast type from local or server form // Derive toast type from local or server form
$: typeToShow = toastType ?? form?.type; $: typeToShow = toastType ?? form?.type;
const eventId = $page.params.id || '';
const copyEventLink = () => {
if (browser && isEventCreator) {
const url = `${$page.url.origin}/event/${eventId}`;
navigator.clipboard.writeText(url).then(() => {
toastType = 'copy';
success = t('event.eventLinkCopied');
setTimeout(() => {
success = '';
toastType = null;
}, 3000);
});
}
};
const clearMessages = () => { const clearMessages = () => {
error = ''; error = '';
@@ -265,6 +279,7 @@
}} }}
class="space-y-4" class="space-y-4"
> >
<input type="hidden" name="userId" value={currentUserId} />
<div> <div>
<label for="attendeeName" class=" mb-2 block text-sm font-semibold"> <label for="attendeeName" class=" mb-2 block text-sm font-semibold">
{t('event.yourNameLabel')} {t('event.yourNameLabel')}
@@ -399,7 +414,7 @@
</div> </div>
</div> </div>
{#if attendee.is_mine} {#if attendee.user_id === currentUserId}
<form <form
method="POST" method="POST"
action="?/removeRSVP" action="?/removeRSVP"
@@ -474,7 +489,7 @@
bind:isOpen={showCalendarModal} bind:isOpen={showCalendarModal}
event={calendarEvent} event={calendarEvent}
{eventId} {eventId}
baseUrl={$page.url.origin} baseUrl={origin}
on:close={closeCalendarModal} on:close={closeCalendarModal}
/> />
{/if} {/if}

View File

@@ -44,13 +44,10 @@ export const load: PageServerLoad = async ({ params, cookies }) => {
} }
} }
// Strip the owner id — it is the credential, and the load above already proved ownership
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { userId: _owner, ...eventRow } = event[0];
return { return {
event: eventRow, event: event[0],
inviteToken inviteToken,
userId
}; };
}; };

View File

@@ -394,7 +394,7 @@
</div> </div>
<!-- Invite Link Section (only for invite-only events and event creator) --> <!-- Invite Link Section (only for invite-only events and event creator) -->
{#if eventData.visibility === 'invite-only' && inviteToken} {#if eventData.visibility === 'invite-only' && inviteToken && data.event.userId === data.userId}
<div class="rounded-sm border border-amber-500/30 bg-amber-900/20 p-4"> <div class="rounded-sm border border-amber-500/30 bg-amber-900/20 p-4">
<div class="mb-3 flex items-center justify-between"> <div class="mb-3 flex items-center justify-between">
<h3 class="text-lg font-semibold text-amber-400">Invite Link</h3> <h3 class="text-lg font-semibold text-amber-400">Invite Link</h3>

View File

@@ -8,7 +8,6 @@ import { isTokenValid } from '$lib/inviteTokenHelpers.js';
export const load: PageServerLoad = async ({ params, cookies }) => { export const load: PageServerLoad = async ({ params, cookies }) => {
const eventId = params.id; const eventId = params.id;
const token = params.token; const token = params.token;
const userId = cookies.get('cactoideUserId');
if (!eventId || !token) { if (!eventId || !token) {
throw error(404, 'Event or token not found'); throw error(404, 'Event or token not found');
@@ -60,8 +59,7 @@ export const load: PageServerLoad = async ({ params, cookies }) => {
type: event.type, type: event.type,
attendee_limit: event.attendeeLimit, attendee_limit: event.attendeeLimit,
visibility: event.visibility, visibility: event.visibility,
// Never send raw user ids to the client — they are the credential user_id: event.userId,
is_creator: !!userId && event.userId === userId,
created_at: event.createdAt?.toISOString() || new Date().toISOString(), created_at: event.createdAt?.toISOString() || new Date().toISOString(),
updated_at: event.updatedAt?.toISOString() || new Date().toISOString() updated_at: event.updatedAt?.toISOString() || new Date().toISOString()
}; };
@@ -70,13 +68,16 @@ export const load: PageServerLoad = async ({ params, cookies }) => {
id: rsvp.id, id: rsvp.id,
event_id: rsvp.eventId, event_id: rsvp.eventId,
name: rsvp.name, name: rsvp.name,
is_mine: !!userId && rsvp.userId === userId, user_id: rsvp.userId,
created_at: rsvp.createdAt?.toISOString() || new Date().toISOString() created_at: rsvp.createdAt?.toISOString() || new Date().toISOString()
})); }));
const userId = cookies.get('cactoideUserId');
return { return {
event: transformedEvent, event: transformedEvent,
rsvps: transformedRsvps, rsvps: transformedRsvps,
userId: userId,
inviteToken: { inviteToken: {
id: inviteToken.id, id: inviteToken.id,
event_id: inviteToken.eventId, event_id: inviteToken.eventId,

View File

@@ -9,7 +9,7 @@
import type { CalendarEvent } from '$lib/calendarHelpers.js'; import type { CalendarEvent } from '$lib/calendarHelpers.js';
import { t } from '$lib/i18n/i18n.js'; import { t } from '$lib/i18n/i18n.js';
export let data: { event: Event; rsvps: RSVP[]; inviteToken: InviteToken }; export let data: { event: Event; rsvps: RSVP[]; userId: string; inviteToken: InviteToken };
export let form; export let form;
let event: Event; let event: Event;
@@ -26,7 +26,8 @@
// Use server-side data // Use server-side data
$: event = data.event; $: event = data.event;
$: rsvps = data.rsvps; $: rsvps = data.rsvps;
$: isEventCreator = event.is_creator ?? false; $: currentUserId = data.userId;
$: isEventCreator = event.user_id === currentUserId;
// Create calendar event object when event data changes // Create calendar event object when event data changes
$: if (event && browser) { $: if (event && browser) {
@@ -239,6 +240,7 @@
}} }}
class="space-y-4" class="space-y-4"
> >
<input type="hidden" name="userId" value={currentUserId} />
<div> <div>
<label for="attendeeName" class=" mb-2 block text-sm font-semibold"> <label for="attendeeName" class=" mb-2 block text-sm font-semibold">
{t('event.yourNameLabel')} {t('event.yourNameLabel')}
@@ -372,7 +374,7 @@
</div> </div>
</div> </div>
{#if attendee.is_mine} {#if attendee.user_id === currentUserId}
<form <form
method="POST" method="POST"
action="?/removeRSVP" action="?/removeRSVP"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 162 KiB

View File

@@ -14,7 +14,10 @@ const config = {
// see "split" mode in https://github.com/sveltejs/kit/tree/main/packages/adapter-netlify // see "split" mode in https://github.com/sveltejs/kit/tree/main/packages/adapter-netlify
edge: false, edge: false,
split: false split: false
}) }),
csrf: {
checkOrigin: false
}
} }
}; };