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>
133 lines
2.9 KiB
Vue
133 lines
2.9 KiB
Vue
<template>
|
|
<main class="stub">
|
|
<header class="stub__header">
|
|
<RouterLink to="/" class="stub__back" aria-label="Back to home">←</RouterLink>
|
|
<span class="stub__brand">fete</span>
|
|
</header>
|
|
|
|
<div class="stub__content">
|
|
<p class="stub__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>
|