Files
fete/frontend/src/views/EventStubView.vue
nitrix e56998b17c
All checks were successful
CI / backend-test (push) Successful in 57s
CI / frontend-test (push) Successful in 22s
CI / frontend-e2e (push) Successful in 1m4s
CI / build-and-publish (push) Has been skipped
Add event list feature (009-list-events)
Enable users to see all their saved events on the home screen, sorted
by date with upcoming events first. Key capabilities:

- EventCard with title, relative time display, and organizer/attendee
  role badge
- Sortable EventList with past-event visual distinction (faded style)
- Empty state when no events are stored
- Swipe-to-delete gesture with confirmation dialog
- Floating action button for quick event creation
- Rename router param :token → :eventToken across all views
- useRelativeTime composable (Intl.RelativeTimeFormat)
- useEventStorage: add validation, removeEvent(), reactive versioning
- Full E2E and unit test coverage for all new components

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:53:55 +01:00

133 lines
2.9 KiB
Vue

<template>
<main class="stub">
<header class="stub__header">
<RouterLink to="/" class="stub__back" aria-label="Back to home">&larr;</RouterLink>
<span class="stub__brand">fete</span>
</header>
<div class="stub__content">
<p class="stub__check">&check; Event created!</p>
<p class="stub__share-label">Share this link:</p>
<div class="stub__link-box">
<span class="stub__link">{{ eventUrl }}</span>
<button class="stub__copy" type="button" @click="copyLink" :aria-label="copyLabel">
{{ copyState === 'copied' ? 'Copied!' : copyState === 'failed' ? 'Failed' : 'Copy' }}
</button>
</div>
</div>
</main>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { RouterLink, useRoute } from 'vue-router'
const route = useRoute()
const copyState = ref<'idle' | 'copied' | 'failed'>('idle')
const eventUrl = computed(() => {
return window.location.origin + '/events/' + route.params.eventToken
})
const copyLabel = computed(() => {
if (copyState.value === 'copied') return 'Link copied to clipboard'
if (copyState.value === 'failed') return 'Copy failed — select the link to copy manually'
return 'Copy event link to clipboard'
})
async function copyLink() {
try {
await navigator.clipboard.writeText(eventUrl.value)
copyState.value = 'copied'
setTimeout(() => {
copyState.value = 'idle'
}, 2000)
} catch {
copyState.value = 'failed'
setTimeout(() => {
copyState.value = 'idle'
}, 3000)
}
}
</script>
<style scoped>
.stub {
display: flex;
flex-direction: column;
gap: var(--spacing-2xl);
padding-top: var(--spacing-lg);
}
.stub__header {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.stub__back {
color: var(--color-text-on-gradient);
font-size: 1.5rem;
text-decoration: none;
line-height: 1;
}
.stub__brand {
font-size: 1.3rem;
font-weight: 700;
color: var(--color-text-on-gradient);
}
.stub__content {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--spacing-lg);
text-align: center;
}
.stub__check {
font-size: 1.4rem;
font-weight: 700;
color: var(--color-text-on-gradient);
}
.stub__share-label {
font-size: 0.95rem;
color: var(--color-text-on-gradient);
opacity: 0.9;
}
.stub__link-box {
background: var(--color-card);
border-radius: var(--radius-card);
padding: var(--spacing-md);
box-shadow: var(--shadow-card);
display: flex;
align-items: center;
gap: var(--spacing-sm);
width: 100%;
word-break: break-all;
}
.stub__link {
flex: 1;
font-size: 0.85rem;
color: var(--color-text);
}
.stub__copy {
background: var(--color-accent);
color: var(--color-text);
border: none;
border-radius: var(--radius-button);
padding: 0.4rem 0.8rem;
font-family: inherit;
font-size: 0.85rem;
font-weight: 700;
cursor: pointer;
white-space: nowrap;
}
</style>