Added opaque join-token support across backend and frontend: new migration/model/service/endpoints, guest controllers now resolve tokens, and the demo seeder seeds a token. Tenant event details list/manage tokens with copy/revoke actions, and the guest PWA uses tokens end-to-end (routing, storage, uploads, achievements, etc.). Docs TODO updated to reflect completed steps.
This commit is contained in:
@@ -10,28 +10,58 @@ import { readGuestName } from '../context/GuestIdentityContext';
|
||||
|
||||
export default function LandingPage() {
|
||||
const nav = useNavigate();
|
||||
const [slug, setSlug] = useState('');
|
||||
const [eventCode, setEventCode] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isScanning, setIsScanning] = useState(false);
|
||||
const [scanner, setScanner] = useState<Html5Qrcode | null>(null);
|
||||
|
||||
async function join(eventSlug?: string) {
|
||||
const s = (eventSlug ?? slug).trim();
|
||||
if (!s) return;
|
||||
function extractEventKey(raw: string): string {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(trimmed);
|
||||
const inviteParam = url.searchParams.get('invite') ?? url.searchParams.get('token');
|
||||
if (inviteParam) {
|
||||
return inviteParam;
|
||||
}
|
||||
const segments = url.pathname.split('/').filter(Boolean);
|
||||
const eventIndex = segments.findIndex((segment) => segment === 'e');
|
||||
if (eventIndex >= 0 && segments.length > eventIndex + 1) {
|
||||
return decodeURIComponent(segments[eventIndex + 1]);
|
||||
}
|
||||
if (segments.length > 0) {
|
||||
return decodeURIComponent(segments[segments.length - 1]);
|
||||
}
|
||||
} catch {
|
||||
// Not a URL, treat as raw code
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
async function join(input?: string) {
|
||||
const provided = input ?? eventCode;
|
||||
const normalized = extractEventKey(provided);
|
||||
if (!normalized) return;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const res = await fetch(`/api/v1/events/${encodeURIComponent(s)}`);
|
||||
const res = await fetch(`/api/v1/events/${encodeURIComponent(normalized)}`);
|
||||
if (!res.ok) {
|
||||
setError('Event nicht gefunden oder geschlossen.');
|
||||
return;
|
||||
}
|
||||
const storedName = readGuestName(s);
|
||||
const data = await res.json();
|
||||
const targetKey = data.join_token ?? data.slug ?? normalized;
|
||||
const storedName = readGuestName(targetKey);
|
||||
if (!storedName) {
|
||||
nav(`/setup/${encodeURIComponent(s)}`);
|
||||
nav(`/setup/${encodeURIComponent(targetKey)}`);
|
||||
} else {
|
||||
nav(`/e/${encodeURIComponent(s)}`);
|
||||
nav(`/e/${encodeURIComponent(targetKey)}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Join request failed', e);
|
||||
@@ -136,14 +166,14 @@ export default function LandingPage() {
|
||||
|
||||
<div className="space-y-2">
|
||||
<Input
|
||||
value={slug}
|
||||
onChange={(event) => setSlug(event.target.value)}
|
||||
value={eventCode}
|
||||
onChange={(event) => setEventCode(event.target.value)}
|
||||
placeholder="Event-Code eingeben"
|
||||
disabled={loading}
|
||||
/>
|
||||
<Button
|
||||
className="w-full bg-gradient-to-r from-pink-500 to-pink-600 text-white hover:from-pink-600 hover:to-pink-700"
|
||||
disabled={loading || !slug.trim()}
|
||||
disabled={loading || !eventCode.trim()}
|
||||
onClick={() => join()}
|
||||
>
|
||||
{loading ? 'Pruefe...' : 'Event beitreten'}
|
||||
|
||||
Reference in New Issue
Block a user