This commit is contained in:
corsacca 2026-04-21 12:35:27 +01:00
parent 6f5313f990
commit dd5255ba69
15 changed files with 80 additions and 78 deletions

View file

@ -1,7 +1,5 @@
<template> <template>
<UApp> <NuxtLayout>
<NuxtLayout> <NuxtPage />
<NuxtPage /> </NuxtLayout>
</NuxtLayout>
</UApp>
</template> </template>

View file

@ -1,6 +1,10 @@
@import "tailwindcss"; @import "tailwindcss";
@import "@nuxt/ui"; @import "@nuxt/ui";
@source "../../pages/admin";
@source "../../components/admin";
@source "../../layouts/admin.vue";
@theme static { @theme static {
--font-sans: 'Public Sans', sans-serif; --font-sans: 'Public Sans', sans-serif;

View file

@ -0,0 +1,6 @@
// Public site stylesheet entry point.
// Phase 1 will `@use` the ported CUBE CSS partials here
// (tokens, base/reset, composition, blocks, utilities).
//
// For Phase 0 this is intentionally empty so the public layout
// has something to import without producing CSS.

View file

@ -25,8 +25,9 @@ watch(() => route.path, () => {
</script> </script>
<template> <template>
<div class="min-h-screen bg-(--ui-bg) text-(--ui-text)"> <UApp>
<!-- Mobile top bar --> <div class="min-h-screen bg-(--ui-bg) text-(--ui-text)">
<!-- Mobile top bar -->
<header class="lg:hidden bg-(--ui-bg-elevated) border-b border-(--ui-border) py-3 px-4 sticky top-0 z-40 flex items-center justify-between"> <header class="lg:hidden bg-(--ui-bg-elevated) border-b border-(--ui-border) py-3 px-4 sticky top-0 z-40 flex items-center justify-between">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<UButton <UButton
@ -126,4 +127,7 @@ watch(() => route.path, () => {
</main> </main>
</div> </div>
</div> </div>
</UApp>
</template> </template>
<style src="~/assets/css/admin.css"></style>

View file

@ -1,35 +1,5 @@
<script setup lang="ts">
const { user } = useAuth()
const config = useRuntimeConfig()
</script>
<template> <template>
<div class="min-h-screen bg-(--ui-bg) text-(--ui-text)"> <slot />
<header class="bg-(--ui-bg-elevated) border-b border-(--ui-border) py-4 sticky top-0 z-50">
<div class="max-w-7xl mx-auto px-4 flex justify-between items-center">
<div class="flex items-center gap-4">
<slot name="header-left">
<NuxtLink to="/" class="text-xl font-semibold hover:text-(--ui-text-muted) transition-colors">
{{ config.public.appName }}
</NuxtLink>
</slot>
</div>
<div class="flex items-center gap-3">
<slot name="header-right">
<span class="text-(--ui-text-muted) text-sm hidden sm:inline">
{{ user?.display_name || user?.email }}
</span>
<UButton to="/profile" variant="ghost" size="sm">
Profile
</UButton>
<ThemeToggle />
</slot>
</div>
</div>
</header>
<main class="max-w-7xl mx-auto px-4 py-8">
<slot />
</main>
</div>
</template> </template>
<style lang="scss" src="~/assets/styles/main.scss"></style>

View file

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
definePageMeta({ definePageMeta({
layout: 'admin',
middleware: 'auth' middleware: 'auth'
}) })
</script> </script>

View file

@ -1,34 +1,11 @@
<script setup lang="ts">
definePageMeta({
layout: false
})
const { isLoggedIn } = useAuth()
</script>
<template> <template>
<div class="min-h-screen bg-(--ui-bg)"> <div style="padding: 2rem; font-family: system-ui, sans-serif;">
<div class="flex justify-end p-4"> <h1>Doxa</h1>
<ThemeToggle /> <p>
</div> Marketing-rebuild scaffold. The real homepage is ported in Phase 3.
<div class="text-center py-16 px-4"> </p>
<h1 class="text-4xl font-bold text-(--ui-text) mb-4">Welcome</h1> <p>
<p class="text-(--ui-text-muted) mb-8 max-w-lg mx-auto"> <a href="/admin">Admin →</a>
Get started by customizing this page. </p>
</p>
<div v-if="!isLoggedIn" class="flex items-center justify-center gap-3">
<UButton to="/login" size="lg">
Sign in
</UButton>
<UButton to="/register" size="lg" variant="outline">
Create an account
</UButton>
</div>
<div v-else>
<UButton to="/dashboard" size="lg">
Go to dashboard
</UButton>
</div>
</div>
</div> </div>
</template> </template>

View file

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
definePageMeta({ definePageMeta({
layout: 'admin',
middleware: 'auth' middleware: 'auth'
}) })

View file

@ -27,6 +27,7 @@
"@types/nodemailer": "^7.0.1", "@types/nodemailer": "^7.0.1",
"@types/nodemailer-mailgun-transport": "^1.4.6", "@types/nodemailer-mailgun-transport": "^1.4.6",
"eslint": "^10.2.0", "eslint": "^10.2.0",
"sass": "^1.99.0",
"typescript": "^6.0.2", "typescript": "^6.0.2",
"vue-tsc": "^3.2.6", "vue-tsc": "^3.2.6",
}, },
@ -1455,6 +1456,8 @@
"image-meta": ["image-meta@0.2.2", "", {}, "sha512-3MOLanc3sb3LNGWQl1RlQlNWURE5g32aUphrDyFeCsxBTk08iE3VNe4CwsUZ0Qs1X+EfX0+r29Sxdpza4B+yRA=="], "image-meta": ["image-meta@0.2.2", "", {}, "sha512-3MOLanc3sb3LNGWQl1RlQlNWURE5g32aUphrDyFeCsxBTk08iE3VNe4CwsUZ0Qs1X+EfX0+r29Sxdpza4B+yRA=="],
"immutable": ["immutable@5.1.5", "", {}, "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A=="],
"impound": ["impound@1.1.5", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.31", "es-module-lexer": "2.0.0", "pathe": "2.0.3", "unplugin": "3.0.0", "unplugin-utils": "0.3.1" } }, "sha512-5AUn+QE0UofqNHu5f2Skf6Svukdg4ehOIq8O0EtqIx4jta0CDZYBPqpIHt0zrlUTiFVYlLpeH39DoikXBjPKpA=="], "impound": ["impound@1.1.5", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.31", "es-module-lexer": "2.0.0", "pathe": "2.0.3", "unplugin": "3.0.0", "unplugin-utils": "0.3.1" } }, "sha512-5AUn+QE0UofqNHu5f2Skf6Svukdg4ehOIq8O0EtqIx4jta0CDZYBPqpIHt0zrlUTiFVYlLpeH39DoikXBjPKpA=="],
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
@ -1949,6 +1952,8 @@
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"sass": ["sass@1.99.0", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.1.5", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q=="],
"sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
"scslre": ["scslre@0.3.0", "", { "dependencies": { "@eslint-community/regexpp": "4.12.2", "refa": "0.12.1", "regexp-ast-analysis": "0.7.1" } }, "sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ=="], "scslre": ["scslre@0.3.0", "", { "dependencies": { "@eslint-community/regexpp": "4.12.2", "refa": "0.12.1", "regexp-ast-analysis": "0.7.1" } }, "sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ=="],
@ -2369,6 +2374,8 @@
"rollup-plugin-visualizer/open": ["open@11.0.0", "", { "dependencies": { "default-browser": "5.5.0", "define-lazy-prop": "3.0.0", "is-in-ssh": "1.0.0", "is-inside-container": "1.0.0", "powershell-utils": "0.1.0", "wsl-utils": "0.3.1" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="], "rollup-plugin-visualizer/open": ["open@11.0.0", "", { "dependencies": { "default-browser": "5.5.0", "define-lazy-prop": "3.0.0", "is-in-ssh": "1.0.0", "is-inside-container": "1.0.0", "powershell-utils": "0.1.0", "wsl-utils": "0.3.1" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="],
"sass/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "4.1.2" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"send/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], "send/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
"source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
@ -2457,6 +2464,8 @@
"rollup-plugin-visualizer/open/wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "3.1.1", "powershell-utils": "0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="], "rollup-plugin-visualizer/open/wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "3.1.1", "powershell-utils": "0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="],
"sass/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"send/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], "send/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],

View file

@ -1,6 +1,39 @@
// @ts-check // @ts-check
import withNuxt from './.nuxt/eslint.config.mjs' import withNuxt from './.nuxt/eslint.config.mjs'
// Guardrail: Nuxt UI components (`<U*>`) and their CSS only load inside the
// admin layout. Using a `<U*>` component on a public page would render
// unstyled because `admin.css` isn't imported there. This rule blocks that
// at lint time. Admin pages/components are exempt via overrides below.
const forbidNuxtUI = {
rules: {
'vue/no-restricted-syntax': ['error', {
selector: "VElement[rawName=/^U[A-Z]/]",
message: 'Nuxt UI components (<U*>) are only allowed inside app/pages/admin/** and app/components/admin/**. Public pages must use ported SCSS components.'
}]
}
}
export default withNuxt( export default withNuxt(
// Your custom configs here {
files: ['app/pages/**/*.vue', 'app/components/**/*.vue', 'app/layouts/**/*.vue'],
...forbidNuxtUI
},
{
files: [
'app/pages/admin/**/*.vue',
'app/components/admin/**/*.vue',
'app/components/ThemeToggle.vue',
'app/layouts/admin.vue',
'app/layouts/auth.vue',
'app/pages/login.vue',
'app/pages/register.vue',
'app/pages/reset-password.vue',
'app/pages/dashboard.vue',
'app/pages/profile.vue'
],
rules: {
'vue/no-restricted-syntax': 'off'
}
}
) )

View file

@ -15,8 +15,6 @@ export default defineNuxtConfig({
ssr: false, ssr: false,
css: ['~/assets/css/main.css'],
ui: { ui: {
theme: { theme: {
colors: ['primary', 'secondary', 'info', 'success', 'warning', 'error', 'neutral'] colors: ['primary', 'secondary', 'info', 'success', 'warning', 'error', 'neutral']

View file

@ -33,6 +33,7 @@
"@types/nodemailer": "^7.0.1", "@types/nodemailer": "^7.0.1",
"@types/nodemailer-mailgun-transport": "^1.4.6", "@types/nodemailer-mailgun-transport": "^1.4.6",
"eslint": "^10.2.0", "eslint": "^10.2.0",
"sass": "^1.99.0",
"typescript": "^6.0.2", "typescript": "^6.0.2",
"vue-tsc": "^3.2.6" "vue-tsc": "^3.2.6"
} }

View file

@ -37,7 +37,7 @@ export default defineEventHandler(async (event) => {
.selectAll() .selectAll()
.where('token', '=', sanitizedToken) .where('token', '=', sanitizedToken)
.where('used', '=', false) .where('used', '=', false)
.where('expires', '>', new Date().toISOString()) .where('expires', '>', new Date())
.executeTakeFirst() .executeTakeFirst()
if (!resetRequest) { if (!resetRequest) {
@ -72,7 +72,7 @@ export default defineEventHandler(async (event) => {
.where((eb) => .where((eb) =>
eb.or([ eb.or([
eb('used', '=', true), eb('used', '=', true),
eb('expires', '<=', new Date().toISOString()), eb('expires', '<=', new Date()),
]) ])
) )
.execute() .execute()

View file

@ -2,7 +2,7 @@ import { db } from './database'
import crypto from 'crypto' import crypto from 'crypto'
import type { H3Event } from 'h3' import type { H3Event } from 'h3'
import type { Kysely } from 'kysely' import type { Kysely } from 'kysely'
import type { Database } from '~/server/database/schema' import type { Database } from '../database/schema'
import { getAuthUser } from './auth' import { getAuthUser } from './auth'
interface LogEventOptions { interface LogEventOptions {

View file

@ -1,7 +1,7 @@
import { Kysely, sql } from 'kysely' import { Kysely, sql } from 'kysely'
import { PostgresJSDialect } from 'kysely-postgres-js' import { PostgresJSDialect } from 'kysely-postgres-js'
import postgres from 'postgres' import postgres from 'postgres'
import type { Database } from '~/server/database/schema' import type { Database } from '../database/schema'
let _db: Kysely<Database> | null = null let _db: Kysely<Database> | null = null