Extract BackLink component into App layout
Move back navigation (chevron + "fete" brand) from per-view definitions into a shared BackLink component rendered in App.vue. Shown on all pages except home. Hero overlay gets pointer-events: none so the link stays clickable on the event detail page. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -65,7 +65,7 @@ test.describe('US1: Watch event from detail page', () => {
|
||||
await expect(bookmark).toHaveAttribute('aria-label', 'Stop watching this event')
|
||||
|
||||
// Navigate to event list via back link
|
||||
await page.locator('.detail__back').click()
|
||||
await page.getByLabel('Back to home').click()
|
||||
|
||||
// Event appears with "Watching" label
|
||||
await expect(page.getByText('Summer BBQ')).toBeVisible()
|
||||
@@ -89,7 +89,7 @@ test.describe('US2: Un-watch event from detail page', () => {
|
||||
await expect(bookmark).toHaveAttribute('aria-label', 'Watch this event')
|
||||
|
||||
// Navigate to event list via back link (avoid page.goto re-running addInitScript)
|
||||
await page.locator('.detail__back').click()
|
||||
await page.getByLabel('Back to home').click()
|
||||
|
||||
// Event is gone
|
||||
await expect(page.getByText('Summer BBQ')).not.toBeVisible()
|
||||
@@ -109,7 +109,7 @@ test.describe('US3: Bookmark reflects attending status', () => {
|
||||
await expect(bookmark).not.toBeVisible()
|
||||
|
||||
// Navigate to list via back link
|
||||
await page.locator('.detail__back').click()
|
||||
await page.getByLabel('Back to home').click()
|
||||
await expect(page.getByText('Attending')).toBeVisible()
|
||||
await expect(page.getByText('Watching')).not.toBeVisible()
|
||||
})
|
||||
@@ -137,7 +137,7 @@ test.describe('US4: RSVP cancellation preserves watch status', () => {
|
||||
await expect(bookmark).toHaveAttribute('aria-label', 'Stop watching this event')
|
||||
|
||||
// Navigate to list via back link
|
||||
await page.locator('.detail__back').click()
|
||||
await page.getByLabel('Back to home').click()
|
||||
await expect(page.getByText('Watching')).toBeVisible()
|
||||
await expect(page.getByText('Attending')).not.toBeVisible()
|
||||
})
|
||||
@@ -211,7 +211,7 @@ test.describe('US7: Watcher upgrades to attendee', () => {
|
||||
await expect(bookmark).not.toBeVisible()
|
||||
|
||||
// Navigate to list via back link
|
||||
await page.locator('.detail__back').click()
|
||||
await page.getByLabel('Back to home').click()
|
||||
await expect(page.getByText('Attending')).toBeVisible()
|
||||
await expect(page.getByText('Watching')).not.toBeVisible()
|
||||
})
|
||||
|
||||
@@ -1,9 +1,26 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<header v-if="route.name !== 'home'" class="app-header">
|
||||
<BackLink />
|
||||
</header>
|
||||
<RouterView />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RouterView } from 'vue-router'
|
||||
import { RouterView, useRoute } from 'vue-router'
|
||||
import BackLink from '@/components/BackLink.vue'
|
||||
|
||||
const route = useRoute()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-header {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
padding-top: var(--spacing-lg);
|
||||
}
|
||||
</style>
|
||||
|
||||
28
frontend/src/components/BackLink.vue
Normal file
28
frontend/src/components/BackLink.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<RouterLink to="/" class="back-link" aria-label="Back to home">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="15 18 9 12 15 6" />
|
||||
</svg>
|
||||
<span class="back-link__brand">fete</span>
|
||||
</RouterLink>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RouterLink } from 'vue-router'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.back-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.15rem;
|
||||
color: var(--color-text-on-gradient);
|
||||
text-decoration: none;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.back-link__brand {
|
||||
font-size: 1.3rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
@@ -1,9 +1,6 @@
|
||||
<template>
|
||||
<main class="create">
|
||||
<header class="create__header">
|
||||
<RouterLink to="/" class="create__back" aria-label="Back to home">←</RouterLink>
|
||||
<h1 class="create__title">Create</h1>
|
||||
</header>
|
||||
<h1 class="create__title">Create</h1>
|
||||
|
||||
<form class="create__form" novalidate @submit.prevent="handleSubmit">
|
||||
<div class="form-group">
|
||||
@@ -76,7 +73,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import { RouterLink, useRouter } from 'vue-router'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { api } from '@/api/client'
|
||||
import { useEventStorage } from '@/composables/useEventStorage'
|
||||
|
||||
@@ -194,20 +191,7 @@ async function handleSubmit() {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-lg);
|
||||
padding-top: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.create__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.create__back {
|
||||
color: var(--color-text-on-gradient);
|
||||
font-size: 1.5rem;
|
||||
text-decoration: none;
|
||||
line-height: 1;
|
||||
padding-top: calc(var(--spacing-lg) + 2.5rem);
|
||||
}
|
||||
|
||||
.create__title {
|
||||
|
||||
@@ -8,10 +8,6 @@
|
||||
alt=""
|
||||
/>
|
||||
<div class="detail__hero-overlay" />
|
||||
<header class="detail__header">
|
||||
<RouterLink to="/" class="detail__back" aria-label="Back to home">←</RouterLink>
|
||||
<span class="detail__brand">fete</span>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div class="detail__body">
|
||||
@@ -168,7 +164,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { RouterLink, useRoute } from 'vue-router'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { api } from '@/api/client'
|
||||
import { useEventStorage } from '@/composables/useEventStorage'
|
||||
import AttendeeList from '@/components/AttendeeList.vue'
|
||||
@@ -437,32 +433,7 @@ onMounted(fetchEvent)
|
||||
var(--color-glass-overlay) 0%,
|
||||
transparent 50%
|
||||
);
|
||||
}
|
||||
|
||||
.detail__header {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
padding: var(--spacing-lg) var(--content-padding);
|
||||
padding-top: env(safe-area-inset-top, var(--spacing-lg));
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.detail__back {
|
||||
color: var(--color-text-on-gradient);
|
||||
font-size: 1.5rem;
|
||||
text-decoration: none;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.detail__brand {
|
||||
font-size: 1.3rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-text-on-gradient);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.detail__body {
|
||||
|
||||
Reference in New Issue
Block a user