feat(story-1.2): Vue 3 SPA scaffold, base component library, User entity, SpaController
Frontend: - Vue 3 + Vite + TypeScript strict in frontend/; builds to public/build/ - Vue Router (hash-history, requiresAuth guard → /login) and Pinia - Global SCSS design tokens with 6 full themes (Warm Craft, Playful Pop, Sage & Cream, Dusty Mauve, Ocean Dusk, Honey & Slate) - Base components: BaseButton (5 variants), BaseInput (floating label, error state), BaseBottomSheet (slide-up, focus trap, tap-outside dismiss), BaseCard, BaseChip, BaseToast (2.5s auto-dismiss, aria-live polite), BottomNav (4 tabs, hides at 960px) - Type stubs for all API shapes: User, Device, Image, StickerLayer, RenderedAsset, Token Backend: - SpaController catch-all serves public/build/index.html; excludes api/setup/token/login/register - User entity (email+password+roles+theme); UserRepository with PasswordUpgrader - SecurityController with /login, /logout, /register stubs; Twig login form - security.yaml: form_login firewall, remember_me, role_hierarchy, access_control - Migration: create user table Verified: npm run build succeeds, GET / → 302 /login (unauthenticated) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import type { User } from '@/types'
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
// Bootstrapped from the SPA shell injected by SpaController
|
||||
const user = ref<User | null>(
|
||||
(window as unknown as Record<string, unknown>).__PF_USER__ as User | null ?? null
|
||||
)
|
||||
|
||||
const isAuthenticated = computed(() => user.value !== null)
|
||||
|
||||
function setUser(u: User | null) {
|
||||
user.value = u
|
||||
}
|
||||
|
||||
return { user, isAuthenticated, setUser }
|
||||
})
|
||||
@@ -0,0 +1,27 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export interface ToastMessage {
|
||||
id: number
|
||||
message: string
|
||||
type: 'info' | 'success' | 'error'
|
||||
}
|
||||
|
||||
let nextId = 0
|
||||
|
||||
export const useToastStore = defineStore('toast', () => {
|
||||
const toasts = ref<ToastMessage[]>([])
|
||||
|
||||
function show(message: string, type: ToastMessage['type'] = 'info') {
|
||||
const id = ++nextId
|
||||
toasts.value.push({ id, message, type })
|
||||
setTimeout(() => dismiss(id), 2500)
|
||||
}
|
||||
|
||||
function dismiss(id: number) {
|
||||
const idx = toasts.value.findIndex((t) => t.id === id)
|
||||
if (idx !== -1) toasts.value.splice(idx, 1)
|
||||
}
|
||||
|
||||
return { toasts, show, dismiss }
|
||||
})
|
||||
Reference in New Issue
Block a user