feat: placeholder images, Nostr inbox, order lookup, SEO, bigger logo
- 5 SVG placeholder product images (minimal dark style with watermark initials) - Seed data updated to reference .svg placeholders - Nostr DM inbox in admin (Messages tab) with shop npub display - GET /api/admin/nostr-info endpoint for shop pubkey - My Orders page: customers look up orders by NIP-07 Nostr identity - GET /api/orders/by-pubkey/:pubkey endpoint with hex validation - SeoMeta component for OG/Twitter meta tags - SEO meta on HomeView and ProductView - Base OG meta tags in index.html - "My Orders" link in shop header nav - Splash logo doubled in size on desktop (680px max) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
44
src/components/SeoMeta.vue
Normal file
44
src/components/SeoMeta.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
import { watchEffect } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
title?: string
|
||||
description?: string
|
||||
image?: string
|
||||
}>()
|
||||
|
||||
const siteName = 'Antonym'
|
||||
const defaultDescription = 'Fashion for the sovereign individual. Bitcoin only. No accounts. No tracking.'
|
||||
const defaultImage = '/logos/logo.svg'
|
||||
|
||||
watchEffect(() => {
|
||||
const fullTitle = props.title ? `${props.title} — ${siteName}` : siteName
|
||||
document.title = fullTitle
|
||||
|
||||
setMeta('description', props.description || defaultDescription)
|
||||
setMeta('og:title', fullTitle)
|
||||
setMeta('og:description', props.description || defaultDescription)
|
||||
setMeta('og:image', props.image || defaultImage)
|
||||
setMeta('og:type', 'website')
|
||||
setMeta('og:site_name', siteName)
|
||||
setMeta('twitter:card', 'summary_large_image')
|
||||
setMeta('twitter:title', fullTitle)
|
||||
setMeta('twitter:description', props.description || defaultDescription)
|
||||
setMeta('twitter:image', props.image || defaultImage)
|
||||
})
|
||||
|
||||
function setMeta(name: string, content: string) {
|
||||
const attr = name.startsWith('og:') || name.startsWith('twitter:') ? 'property' : 'name'
|
||||
let el = document.querySelector(`meta[${attr}="${name}"]`)
|
||||
if (!el) {
|
||||
el = document.createElement('meta')
|
||||
el.setAttribute(attr, name)
|
||||
document.head.appendChild(el)
|
||||
}
|
||||
el.setAttribute('content', content)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<slot />
|
||||
</template>
|
||||
@@ -23,6 +23,7 @@ async function handleLogout() {
|
||||
<nav class="sidebar-nav">
|
||||
<router-link :to="{ name: 'admin-orders' }" class="nav-item">Orders</router-link>
|
||||
<router-link :to="{ name: 'admin-products' }" class="nav-item">Products</router-link>
|
||||
<router-link :to="{ name: 'admin-messages' }" class="nav-item">Messages</router-link>
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
|
||||
@@ -14,6 +14,7 @@ const { itemCount } = useCart()
|
||||
|
||||
<nav class="nav-links">
|
||||
<router-link to="/" class="nav-link">Shop</router-link>
|
||||
<router-link to="/my-orders" class="nav-link">My Orders</router-link>
|
||||
</nav>
|
||||
|
||||
<div class="header-actions">
|
||||
|
||||
@@ -107,7 +107,7 @@ onMounted(() => {
|
||||
|
||||
/* --- Logo phase --- */
|
||||
.splash-logo {
|
||||
width: min(60vw, 340px);
|
||||
width: min(80vw, 680px);
|
||||
height: auto;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user