Compare commits
4 Commits
6d5d58581e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2dd819444e | ||
|
|
25f4701b54 | ||
|
|
66a3cc7cdb | ||
|
|
cdcacadb5f |
8
Makefile
@@ -1,4 +1,4 @@
|
|||||||
.PHONY: tw-build tw-watch htmx-copy flowbite-copy assets server test db-reset fmt
|
.PHONY: tw-build tw-watch htmx-copy flags-copy assets server test db-reset fmt
|
||||||
|
|
||||||
tw-build:
|
tw-build:
|
||||||
npm run tw:build
|
npm run tw:build
|
||||||
@@ -9,10 +9,10 @@ tw-watch:
|
|||||||
htmx-copy:
|
htmx-copy:
|
||||||
mkdir -p web/static/vendor && cp node_modules/htmx.org/dist/htmx.min.js web/static/vendor/htmx.min.js
|
mkdir -p web/static/vendor && cp node_modules/htmx.org/dist/htmx.min.js web/static/vendor/htmx.min.js
|
||||||
|
|
||||||
flowbite-copy:
|
flags-copy:
|
||||||
mkdir -p web/static/vendor && cp node_modules/flowbite/dist/flowbite.min.js web/static/vendor/flowbite.js
|
mkdir -p web/static/vendor/flags && cp assets/flags/*.svg web/static/vendor/flags/
|
||||||
|
|
||||||
assets: htmx-copy flowbite-copy tw-build
|
assets: htmx-copy flags-copy tw-build
|
||||||
|
|
||||||
server:
|
server:
|
||||||
go run ./cmd/server
|
go run ./cmd/server
|
||||||
|
|||||||
12
README.md
@@ -18,6 +18,14 @@ Terminale 2:
|
|||||||
make server
|
make server
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Admin SPA (Quasar):
|
||||||
|
- il backend serve `quasar/admin_section/dist/spa` sotto `/admin` (protetto da auth + ruolo admin)
|
||||||
|
- build frontend admin: `cd quasar/admin_section && npm i && npm run build`
|
||||||
|
|
||||||
|
Private SPA (Quasar):
|
||||||
|
- il backend serve `quasar/private_section/dist/spa` sotto `/private` (protetto da auth)
|
||||||
|
- build frontend private: `cd quasar/private_section && npm i && npm run build`
|
||||||
|
|
||||||
`make assets` esegue:
|
`make assets` esegue:
|
||||||
- copia di `node_modules/flowbite/dist/flowbite.min.js` in `web/static/vendor/flowbite.js`
|
- copia di `node_modules/flowbite/dist/flowbite.min.js` in `web/static/vendor/flowbite.js`
|
||||||
- build Tailwind in `web/static/css/app.css`
|
- build Tailwind in `web/static/css/app.css`
|
||||||
@@ -65,8 +73,8 @@ DB_PG_DSN=postgres://trustcontact:trustcontact@localhost:5432/trustcontact?sslmo
|
|||||||
## Template Directories
|
## Template Directories
|
||||||
|
|
||||||
- Public: `web/templates/public`
|
- Public: `web/templates/public`
|
||||||
- Private: `web/templates/private`
|
- Private: `quasar/private_section/dist/spa` (SPA servita da Go sotto `/private`)
|
||||||
- Admin: `web/templates/admin`
|
- Admin: `quasar/admin_section/dist/spa` (SPA servita da Go sotto `/admin`)
|
||||||
|
|
||||||
## Email in Develop
|
## Email in Develop
|
||||||
|
|
||||||
|
|||||||
5
assets/flags/ch.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" role="img" aria-label="Swiss flag">
|
||||||
|
<rect width="32" height="32" fill="#d52b1e"/>
|
||||||
|
<rect x="13" y="6" width="6" height="20" fill="#ffffff"/>
|
||||||
|
<rect x="6" y="13" width="20" height="6" fill="#ffffff"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 271 B |
5
assets/flags/de.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 32" role="img" aria-label="German flag">
|
||||||
|
<rect width="48" height="10.67" x="0" y="0" fill="#000000"/>
|
||||||
|
<rect width="48" height="10.67" x="0" y="10.67" fill="#dd0000"/>
|
||||||
|
<rect width="48" height="10.66" x="0" y="21.34" fill="#ffce00"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 301 B |
9
assets/flags/en.svg
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 32" role="img" aria-label="English flag">
|
||||||
|
<rect width="48" height="32" fill="#012169"/>
|
||||||
|
<path d="M0 0 48 32M48 0 0 32" stroke="#ffffff" stroke-width="6"/>
|
||||||
|
<path d="M0 0 48 32M48 0 0 32" stroke="#c8102e" stroke-width="3"/>
|
||||||
|
<rect x="20" y="0" width="8" height="32" fill="#ffffff"/>
|
||||||
|
<rect x="0" y="12" width="48" height="8" fill="#ffffff"/>
|
||||||
|
<rect x="22" y="0" width="4" height="32" fill="#c8102e"/>
|
||||||
|
<rect x="0" y="14" width="48" height="4" fill="#c8102e"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 531 B |
53
assets/flags/en_us.svg
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 32" role="img" aria-label="United States flag">
|
||||||
|
<rect width="48" height="32" fill="#b22234"/>
|
||||||
|
<g fill="#ffffff">
|
||||||
|
<rect y="2.46" width="48" height="2.46"/>
|
||||||
|
<rect y="7.38" width="48" height="2.46"/>
|
||||||
|
<rect y="12.30" width="48" height="2.46"/>
|
||||||
|
<rect y="17.22" width="48" height="2.46"/>
|
||||||
|
<rect y="22.14" width="48" height="2.46"/>
|
||||||
|
<rect y="27.06" width="48" height="2.46"/>
|
||||||
|
</g>
|
||||||
|
<rect width="20" height="17.23" fill="#3c3b6e"/>
|
||||||
|
<g fill="#ffffff">
|
||||||
|
<circle cx="2.2" cy="2.2" r="0.7"/>
|
||||||
|
<circle cx="5.4" cy="2.2" r="0.7"/>
|
||||||
|
<circle cx="8.6" cy="2.2" r="0.7"/>
|
||||||
|
<circle cx="11.8" cy="2.2" r="0.7"/>
|
||||||
|
<circle cx="15.0" cy="2.2" r="0.7"/>
|
||||||
|
<circle cx="18.2" cy="2.2" r="0.7"/>
|
||||||
|
<circle cx="3.8" cy="4.4" r="0.7"/>
|
||||||
|
<circle cx="7.0" cy="4.4" r="0.7"/>
|
||||||
|
<circle cx="10.2" cy="4.4" r="0.7"/>
|
||||||
|
<circle cx="13.4" cy="4.4" r="0.7"/>
|
||||||
|
<circle cx="16.6" cy="4.4" r="0.7"/>
|
||||||
|
<circle cx="2.2" cy="6.6" r="0.7"/>
|
||||||
|
<circle cx="5.4" cy="6.6" r="0.7"/>
|
||||||
|
<circle cx="8.6" cy="6.6" r="0.7"/>
|
||||||
|
<circle cx="11.8" cy="6.6" r="0.7"/>
|
||||||
|
<circle cx="15.0" cy="6.6" r="0.7"/>
|
||||||
|
<circle cx="18.2" cy="6.6" r="0.7"/>
|
||||||
|
<circle cx="3.8" cy="8.8" r="0.7"/>
|
||||||
|
<circle cx="7.0" cy="8.8" r="0.7"/>
|
||||||
|
<circle cx="10.2" cy="8.8" r="0.7"/>
|
||||||
|
<circle cx="13.4" cy="8.8" r="0.7"/>
|
||||||
|
<circle cx="16.6" cy="8.8" r="0.7"/>
|
||||||
|
<circle cx="2.2" cy="11" r="0.7"/>
|
||||||
|
<circle cx="5.4" cy="11" r="0.7"/>
|
||||||
|
<circle cx="8.6" cy="11" r="0.7"/>
|
||||||
|
<circle cx="11.8" cy="11" r="0.7"/>
|
||||||
|
<circle cx="15.0" cy="11" r="0.7"/>
|
||||||
|
<circle cx="18.2" cy="11" r="0.7"/>
|
||||||
|
<circle cx="3.8" cy="13.2" r="0.7"/>
|
||||||
|
<circle cx="7.0" cy="13.2" r="0.7"/>
|
||||||
|
<circle cx="10.2" cy="13.2" r="0.7"/>
|
||||||
|
<circle cx="13.4" cy="13.2" r="0.7"/>
|
||||||
|
<circle cx="16.6" cy="13.2" r="0.7"/>
|
||||||
|
<circle cx="2.2" cy="15.4" r="0.7"/>
|
||||||
|
<circle cx="5.4" cy="15.4" r="0.7"/>
|
||||||
|
<circle cx="8.6" cy="15.4" r="0.7"/>
|
||||||
|
<circle cx="11.8" cy="15.4" r="0.7"/>
|
||||||
|
<circle cx="15.0" cy="15.4" r="0.7"/>
|
||||||
|
<circle cx="18.2" cy="15.4" r="0.7"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
5
assets/flags/fr.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 32" role="img" aria-label="French flag">
|
||||||
|
<rect width="16" height="32" x="0" y="0" fill="#0055a4"/>
|
||||||
|
<rect width="16" height="32" x="16" y="0" fill="#ffffff"/>
|
||||||
|
<rect width="16" height="32" x="32" y="0" fill="#ef4135"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 286 B |
5
assets/flags/it.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 32" role="img" aria-label="Italian flag">
|
||||||
|
<rect width="16" height="32" x="0" y="0" fill="#009246"/>
|
||||||
|
<rect width="16" height="32" x="16" y="0" fill="#ffffff"/>
|
||||||
|
<rect width="16" height="32" x="32" y="0" fill="#ce2b37"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 287 B |
@@ -1,4 +1,5 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
@custom-variant dark (&:where(.dark, .dark *));
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
.flag-lang {
|
.flag-lang {
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
TASK: Integra Flowbite (UI + JS behavior) e aggiungi Makefile per Tailwind CLI (build/watch). Uso più terminali: non aggiungere tool per runner multi-process.
|
|
||||||
|
|
||||||
1) Dipendenze Node
|
|
||||||
- Se non esiste package.json in root, crearlo.
|
|
||||||
- Installare:
|
|
||||||
- npm install -D @tailwindcss/cli tailwindcss
|
|
||||||
- npm install flowbite
|
|
||||||
|
|
||||||
2) Tailwind config
|
|
||||||
- Creare tailwind.config.js con:
|
|
||||||
content: [
|
|
||||||
"./web/templates/**/*.{html,gohtml}",
|
|
||||||
"./web/static/**/*.js",
|
|
||||||
"./node_modules/flowbite/**/*.js"
|
|
||||||
],
|
|
||||||
theme: { extend: {} },
|
|
||||||
plugins: [ require("flowbite/plugin") ]
|
|
||||||
|
|
||||||
3) CSS input
|
|
||||||
- Creare ./assets/tailwind/input.css con:
|
|
||||||
@import "tailwindcss";
|
|
||||||
|
|
||||||
4) Scripts npm
|
|
||||||
- In package.json aggiungere:
|
|
||||||
"scripts": {
|
|
||||||
"tw:build": "npx @tailwindcss/cli -i ./assets/tailwind/input.css -o ./web/static/css/app.css --minify",
|
|
||||||
"tw:watch": "npx @tailwindcss/cli -i ./assets/tailwind/input.css -o ./web/static/css/app.css --watch"
|
|
||||||
}
|
|
||||||
|
|
||||||
5) Copia Flowbite JS
|
|
||||||
- Creare directory web/static/vendor se manca
|
|
||||||
- Copiare:
|
|
||||||
node_modules/flowbite/dist/flowbite.min.js -> web/static/vendor/flowbite.js
|
|
||||||
|
|
||||||
6) Layout
|
|
||||||
- Aggiornare /web/templates/layout.html per includere:
|
|
||||||
<link rel="stylesheet" href="/static/css/app.css?v={{.BuildHash}}">
|
|
||||||
<script src="/static/vendor/htmx.min.js"></script>
|
|
||||||
<script src="/static/vendor/flowbite.js"></script>
|
|
||||||
- Rimuovere dal layout riferimenti attivi al vecchio UI kit Svelte (ma non cancellare /ui-kit dal repo)
|
|
||||||
|
|
||||||
7) Makefile
|
|
||||||
- Creare/aggiornare Makefile con target:
|
|
||||||
- tw-build: npm run tw:build
|
|
||||||
- tw-watch: npm run tw:watch
|
|
||||||
- flowbite-copy: mkdir -p web/static/vendor && cp node_modules/flowbite/dist/flowbite.min.js web/static/vendor/flowbite.js
|
|
||||||
- assets: flowbite-copy tw-build
|
|
||||||
- server: go run ./cmd/server
|
|
||||||
- Non creare target che avvia più processi insieme.
|
|
||||||
|
|
||||||
8) Partials Flowbite
|
|
||||||
- Creare /web/templates/components:
|
|
||||||
- navbar.html
|
|
||||||
- modal.html
|
|
||||||
- dropdown.html
|
|
||||||
- tabs.html
|
|
||||||
- collapse.html
|
|
||||||
Con markup Flowbite + data-attributes standard, senza JS custom.
|
|
||||||
|
|
||||||
9) Licenze
|
|
||||||
- Creare /licenses/FLOWBITE-MIT.txt e THIRD_PARTY_NOTICES.md (Flowbite MIT, Tailwind MIT).
|
|
||||||
|
|
||||||
10) README
|
|
||||||
- Aggiornare README con istruzioni:
|
|
||||||
Terminale 1: npm i && make assets && make tw-watch
|
|
||||||
Terminale 2: make server
|
|
||||||
|
|
||||||
Criteri:
|
|
||||||
- make assets genera CSS e copia flowbite.js
|
|
||||||
- modals/dropdowns Flowbite funzionano
|
|
||||||
- progetto compilabile e avviabile.
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
TASK: Aggiungere Dark Mode globale con toggle nel footer (Flowbite + Tailwind).
|
|
||||||
|
|
||||||
Vincoli:
|
|
||||||
- Usare Tailwind dark mode con strategia "class" (non media)
|
|
||||||
- Toggle nel footer visibile su tutte le pagine (layout globale)
|
|
||||||
- Stato persistente con localStorage
|
|
||||||
- Default: se non c’è preferenza salvata, usare prefers-color-scheme
|
|
||||||
- Nessun framework JS aggiuntivo
|
|
||||||
- Non rompere HTMX e Flowbite
|
|
||||||
- Accessibile (aria-label, stato, focus)
|
|
||||||
|
|
||||||
1) Tailwind config
|
|
||||||
- Aggiornare tailwind.config.js:
|
|
||||||
- impostare `darkMode: 'class'`
|
|
||||||
- assicurarsi che content includa templates e node_modules/flowbite come già configurato
|
|
||||||
|
|
||||||
2) JS globale
|
|
||||||
- Creare file /web/static/vendor/theme.js (o /web/static/js/theme.js se preferisci) con:
|
|
||||||
- all’avvio (prima del render visibile):
|
|
||||||
- leggere localStorage key `theme` ('dark'|'light')
|
|
||||||
- se non presente, leggere `window.matchMedia('(prefers-color-scheme: dark)')`
|
|
||||||
- applicare/rimuovere classe `dark` su <html> (document.documentElement)
|
|
||||||
- esporre funzione toggleTheme() che:
|
|
||||||
- toggla classe `dark`
|
|
||||||
- salva preferenza in localStorage
|
|
||||||
- aggiorna label testo del bottone e aria-pressed
|
|
||||||
- gestire aggiornamento se l’utente non ha preferenza salvata e cambia prefers-color-scheme (listener matchMedia), opzionale ma gradito
|
|
||||||
|
|
||||||
3) Includere theme.js nel layout
|
|
||||||
- In /web/templates/layout.html:
|
|
||||||
- includere theme.js nel <head> prima del CSS per evitare FOUC:
|
|
||||||
<script src="/static/vendor/theme.js"></script>
|
|
||||||
- poi link CSS e script htmx/flowbite come già presenti
|
|
||||||
- aggiungere classi base al body per dark:
|
|
||||||
- bg-white dark:bg-gray-900
|
|
||||||
- text-gray-900 dark:text-gray-100
|
|
||||||
- min-h-screen flex flex-col
|
|
||||||
- contenuto in main con flex-1
|
|
||||||
|
|
||||||
4) Footer + toggle button (Flowbite style)
|
|
||||||
- Nel footer del layout aggiungere un bottone:
|
|
||||||
- posizionato a destra (o center se preferisci) con icona (testo va bene senza icone)
|
|
||||||
- classi Flowbite/Tailwind:
|
|
||||||
- px-3 py-2 text-sm font-medium rounded-lg
|
|
||||||
- bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700
|
|
||||||
- attributi accessibilità:
|
|
||||||
- id="themeToggle"
|
|
||||||
- type="button"
|
|
||||||
- aria-label="Toggle dark mode"
|
|
||||||
- aria-pressed="false" (aggiornato via JS)
|
|
||||||
- testo dinamico: "Dark mode" / "Light mode" o "Tema: Scuro/Chiaro"
|
|
||||||
|
|
||||||
- Aggiungere onclick:
|
|
||||||
- onclick="window.toggleTheme && window.toggleTheme()"
|
|
||||||
|
|
||||||
5) Aggiornare componenti base per dark
|
|
||||||
- Aggiornare /web/templates/components/navbar.html (se esiste) e altri partial principali con classi dark:
|
|
||||||
- Navbar: bg-white dark:bg-gray-900, border-gray-200 dark:border-gray-700
|
|
||||||
- Dropdown: bg-white dark:bg-gray-800, text colors
|
|
||||||
- Cards: bg-white dark:bg-gray-800
|
|
||||||
- Tables: dark divide colors
|
|
||||||
Non serve perfezione totale, ma assicurare leggibilità.
|
|
||||||
|
|
||||||
6) README
|
|
||||||
- Aggiornare README con nota:
|
|
||||||
- dark mode persistente in localStorage key "theme"
|
|
||||||
|
|
||||||
7) Criteri di accettazione
|
|
||||||
- Il toggle è visibile nel footer su tutte le pagine
|
|
||||||
- Il tema persiste al reload
|
|
||||||
- Default segue prefers-color-scheme se non impostato manualmente
|
|
||||||
- Nessun flash di tema sbagliato (FOUC minimizzato)
|
|
||||||
- Non rompe Flowbite JS, modals, dropdown e HTMX
|
|
||||||
- Accessibile: bottone focusable e aria-pressed aggiornato
|
|
||||||
|
|
||||||
Esegui:
|
|
||||||
- make tw-build (o make tw-watch per verificare)
|
|
||||||
- Avvia server e verifica cambio tema su /login e /users.
|
|
||||||
Correggi eventuali classi mancanti.
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
TASK: Convertire tutti i template HTML esistenti per usare componenti Flowbite (markup + behavior JS) mantenendo logica MVC e HTMX.
|
|
||||||
|
|
||||||
Non modificare controller, services, repo.
|
|
||||||
Modificare solo template e layout.
|
|
||||||
|
|
||||||
-------------------------------------
|
|
||||||
1) LAYOUT GLOBALE
|
|
||||||
-------------------------------------
|
|
||||||
|
|
||||||
Aggiornare /web/templates/layout.html:
|
|
||||||
|
|
||||||
- Layout container moderno Tailwind
|
|
||||||
- Navbar Flowbite responsive con:
|
|
||||||
- Logo/AppName a sinistra
|
|
||||||
- Link:
|
|
||||||
- Public: Login / Signup
|
|
||||||
- Private: Dashboard / Users
|
|
||||||
- Admin: Admin (solo se role=admin)
|
|
||||||
- Dropdown utente con logout
|
|
||||||
- Include:
|
|
||||||
<link rel="stylesheet" href="/static/css/app.css?v={{.BuildHash}}">
|
|
||||||
<script src="/static/vendor/htmx.min.js"></script>
|
|
||||||
<script src="/static/vendor/flowbite.js"></script>
|
|
||||||
|
|
||||||
Struttura:
|
|
||||||
- Navbar top
|
|
||||||
- Container max-w-7xl mx-auto p-6
|
|
||||||
- Footer minimale
|
|
||||||
|
|
||||||
-------------------------------------
|
|
||||||
2) PUBLIC TEMPLATES
|
|
||||||
-------------------------------------
|
|
||||||
|
|
||||||
Convertire:
|
|
||||||
|
|
||||||
/web/templates/public/login.html
|
|
||||||
/web/templates/public/signup.html
|
|
||||||
/web/templates/public/forgot_password.html
|
|
||||||
/web/templates/public/reset_password.html
|
|
||||||
|
|
||||||
Usare:
|
|
||||||
- Card Flowbite per form
|
|
||||||
- Input Flowbite style
|
|
||||||
- Button primary
|
|
||||||
- Alert Flowbite per flash messages
|
|
||||||
- Layout centrato verticalmente (flex items-center justify-center min-h-screen)
|
|
||||||
|
|
||||||
-------------------------------------
|
|
||||||
3) PRIVATE USERS
|
|
||||||
-------------------------------------
|
|
||||||
|
|
||||||
/web/templates/private/users/index.html
|
|
||||||
|
|
||||||
- Header sezione con:
|
|
||||||
- Titolo
|
|
||||||
- Pulsante "Nuovo Utente" (modal Flowbite)
|
|
||||||
- Search input Flowbite
|
|
||||||
- Table Flowbite styled (striped, hover)
|
|
||||||
- Pagination button Flowbite
|
|
||||||
- Modal Flowbite per dettaglio utente
|
|
||||||
|
|
||||||
Assicurarsi che:
|
|
||||||
- hx-get
|
|
||||||
- hx-target
|
|
||||||
- hx-swap
|
|
||||||
rimangano funzionanti
|
|
||||||
|
|
||||||
-------------------------------------
|
|
||||||
4) ADMIN
|
|
||||||
-------------------------------------
|
|
||||||
|
|
||||||
/web/templates/admin/dashboard.html
|
|
||||||
/web/templates/admin/users.html
|
|
||||||
/web/templates/admin/audit_logs.html
|
|
||||||
|
|
||||||
Usare:
|
|
||||||
- Card summary (stats)
|
|
||||||
- Table Flowbite
|
|
||||||
- Badge per ruoli (admin = red, user = blue)
|
|
||||||
- Tabs Flowbite se presenti più sezioni
|
|
||||||
|
|
||||||
-------------------------------------
|
|
||||||
5) FLASH MESSAGES
|
|
||||||
-------------------------------------
|
|
||||||
|
|
||||||
Creare partial:
|
|
||||||
- /web/templates/components/flash.html
|
|
||||||
|
|
||||||
Usare Flowbite alert component:
|
|
||||||
- success -> green
|
|
||||||
- error -> red
|
|
||||||
- warning -> yellow
|
|
||||||
|
|
||||||
Includere in layout sopra {{embed}}
|
|
||||||
|
|
||||||
-------------------------------------
|
|
||||||
6) MODAL STANDARD
|
|
||||||
-------------------------------------
|
|
||||||
|
|
||||||
Creare partial riusabile:
|
|
||||||
/web/templates/components/modal.html
|
|
||||||
|
|
||||||
Markup Flowbite standard con:
|
|
||||||
- id dinamico
|
|
||||||
- header con titolo
|
|
||||||
- body slot
|
|
||||||
- footer slot opzionale
|
|
||||||
- data-modal-toggle attributes
|
|
||||||
|
|
||||||
Usare nelle pagine private.
|
|
||||||
|
|
||||||
-------------------------------------
|
|
||||||
7) ACCESSIBILITÀ
|
|
||||||
-------------------------------------
|
|
||||||
|
|
||||||
- Usare aria attributes corretti come in documentazione Flowbite
|
|
||||||
- Non rompere keyboard interaction
|
|
||||||
- Mantenere form method e csrf se presente
|
|
||||||
|
|
||||||
-------------------------------------
|
|
||||||
8) RESPONSIVE DESIGN
|
|
||||||
-------------------------------------
|
|
||||||
|
|
||||||
- Navbar collapse mobile
|
|
||||||
- Table scrollable mobile
|
|
||||||
- Forms full width mobile
|
|
||||||
|
|
||||||
-------------------------------------
|
|
||||||
9) CRITERI DI ACCETTAZIONE
|
|
||||||
-------------------------------------
|
|
||||||
|
|
||||||
- Tutte le pagine hanno layout moderno Flowbite
|
|
||||||
- Modals funzionano
|
|
||||||
- Dropdown funzionano
|
|
||||||
- Navbar responsive
|
|
||||||
- HTMX partial update continua a funzionare
|
|
||||||
- Nessuna modifica backend richiesta
|
|
||||||
- Nessun errore JS in console
|
|
||||||
|
|
||||||
Scrivere codice pulito, leggibile, con commenti minimi.
|
|
||||||
Non eliminare logica Go template esistente.
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
Sei Codex in VS Code. Lavora direttamente nel workspace.
|
|
||||||
|
|
||||||
Obiettivo: creare un boilerplate riusabile “GoFiber MVC + HTMX + Svelte Custom Elements UI kit + GORM + SQLite/Postgres + Auth + Email sink + CORS + template directory public/private/admin + role admin”.
|
|
||||||
|
|
||||||
1) Scansiona il workspace e dimmi cosa esiste già.
|
|
||||||
2) Crea/aggiorna la struttura cartelle secondo questa convenzione:
|
|
||||||
/cmd/server
|
|
||||||
/internal/{app,config,http,middleware,db,models,repo,services,controllers,auth,mailer}
|
|
||||||
/web/{templates/{public,private,admin},emails/templates,static/{vendor,ui,css}}
|
|
||||||
/ui-kit
|
|
||||||
/data (solo dev)
|
|
||||||
3) Crea una TODO checklist in README.md con i passi rimanenti.
|
|
||||||
Non implementare ancora logica: solo struttura + README e .gitignore.
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
Implementa internal/config e internal/app.
|
|
||||||
|
|
||||||
- Aggiungi internal/config/config.go:
|
|
||||||
- carica .env se presente (godotenv)
|
|
||||||
- espone Config con: AppName, Env (develop|prod), Port, BaseURL, BuildHash
|
|
||||||
DBDriver (sqlite|postgres), SQLitePath, PostgresDSN
|
|
||||||
CORS settings (origins/headers/methods/credentials)
|
|
||||||
SessionKey
|
|
||||||
SMTP settings + EmailSinkDir
|
|
||||||
Flags: AutoMigrate, SeedEnabled
|
|
||||||
- valida i campi essenziali (es. DB DSN se postgres)
|
|
||||||
|
|
||||||
- Aggiungi internal/app/app.go:
|
|
||||||
- crea fiber.App
|
|
||||||
- registra CORS middleware
|
|
||||||
- registra session store
|
|
||||||
- init DB (internal/db) + migrate/seed (in base ai flag)
|
|
||||||
- registra router (internal/http/router.go)
|
|
||||||
- espone NewApp(cfg) (*fiber.App, error)
|
|
||||||
|
|
||||||
- Aggiorna cmd/server/main.go per usare internal/app.
|
|
||||||
|
|
||||||
Crea/aggiorna .env.example e .gitignore (escludi .env, /data, db sqlite, email sink).
|
|
||||||
Scrivi codice compilabile.
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
Aggiungi DX boilerplate.
|
|
||||||
|
|
||||||
- Makefile:
|
|
||||||
- make dev (go run ./cmd/server)
|
|
||||||
- make ui-build (cd ui-kit && npm i && npm run build)
|
|
||||||
- make ui-dev (watch)
|
|
||||||
- make test (go test ./...)
|
|
||||||
- make db-reset (solo sqlite: rimuovi ./data/app.db)
|
|
||||||
- make fmt (gofmt)
|
|
||||||
|
|
||||||
- docker-compose.yml:
|
|
||||||
- postgres service (porta 5432)
|
|
||||||
- env compatibile con DB_PG_DSN
|
|
||||||
|
|
||||||
- README.md:
|
|
||||||
- Quickstart sqlite
|
|
||||||
- Quickstart postgres (docker compose)
|
|
||||||
- dove stanno templates public/private/admin
|
|
||||||
- email in develop: ./data/emails
|
|
||||||
- build UI kit
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
Implementa internal/db (db.go, migrate.go, seed.go) con GORM.
|
|
||||||
|
|
||||||
- db.go: Open(cfg) (*gorm.DB, error) con switch sqlite/postgres.
|
|
||||||
- sqlite usa cfg.SQLitePath, crea directory se serve.
|
|
||||||
- postgres usa cfg.PostgresDSN.
|
|
||||||
- logger più verboso in develop.
|
|
||||||
|
|
||||||
- migrate.go: Migrate(db) che fa AutoMigrate su tutti i modelli (User, EmailVerificationToken, PasswordResetToken).
|
|
||||||
- esegui solo se cfg.AutoMigrate=true (gestisci in app.go o in migrate.go).
|
|
||||||
|
|
||||||
- seed.go: Seed(db) idempotente se cfg.SeedEnabled=true:
|
|
||||||
- in develop crea:
|
|
||||||
- admin@example.com (role=admin, verified=true, password="password")
|
|
||||||
- user@example.com (role=user, verified=true, password="password")
|
|
||||||
- crea anche utenti demo aggiuntivi per tabella.
|
|
||||||
- usa upsert by email (GORM clauses OnConflict dove possibile).
|
|
||||||
- NON loggare password in chiaro.
|
|
||||||
|
|
||||||
Aggiorna/crea internal/models con i modelli necessari.
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
Implementa internal/models e internal/auth.
|
|
||||||
|
|
||||||
- internal/models/user.go:
|
|
||||||
- User: ID, Email unique, PasswordHash, EmailVerified, Role (default user), timestamps.
|
|
||||||
|
|
||||||
- internal/models/auth_tokens.go:
|
|
||||||
- EmailVerificationToken: UserID, TokenHash unique, ExpiresAt, timestamps
|
|
||||||
- PasswordResetToken: UserID, TokenHash unique, ExpiresAt, timestamps
|
|
||||||
|
|
||||||
- internal/auth/passwords.go:
|
|
||||||
- HashPassword(plain) -> hash (bcrypt)
|
|
||||||
- ComparePassword(hash, plain) -> bool/error
|
|
||||||
|
|
||||||
- internal/auth/tokens.go:
|
|
||||||
- NewToken() -> plainToken (base64url random 32+ bytes)
|
|
||||||
- HashToken(plainToken) -> hex/bytes SHA-256 string
|
|
||||||
- ExpiresAt helpers (verify 24h, reset 1h)
|
|
||||||
|
|
||||||
Assicurati che nel DB venga salvato SOLO l’hash del token.
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
Implementa internal/mailer.
|
|
||||||
|
|
||||||
Requisiti:
|
|
||||||
- directory template email: /web/emails/templates
|
|
||||||
- verify_email.html + .txt
|
|
||||||
- reset_password.html + .txt
|
|
||||||
|
|
||||||
- internal/mailer/templates.go:
|
|
||||||
- carica e renderizza template (html+txt) con dati: AppName, BaseURL, VerifyURL/ResetURL, UserEmail.
|
|
||||||
|
|
||||||
- internal/mailer/mailer.go:
|
|
||||||
- interfaccia Mailer { Send(ctx, to, subject, htmlBody, textBody) error }
|
|
||||||
- factory NewMailer(cfg) che ritorna:
|
|
||||||
- sink mailer se cfg.Env==develop
|
|
||||||
- smtp mailer altrimenti
|
|
||||||
|
|
||||||
- internal/mailer/sink.go:
|
|
||||||
- salva in cfg.EmailSinkDir file con timestamp__type__to.eml (o .txt/.html)
|
|
||||||
- includi subject, to, bodies e link.
|
|
||||||
|
|
||||||
- internal/mailer/smtp.go:
|
|
||||||
- invio via SMTP usando cfg.SMTPHost/Port/User/Password/From/FromName.
|
|
||||||
|
|
||||||
Aggiorna README con “in develop le email sono salvate in ./data/emails”.
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
Aggiungi session e middleware.
|
|
||||||
|
|
||||||
- Usa Fiber session middleware (cookie session). Configura key da cfg.SessionKey, cookie secure in prod, SameSite Lax, HttpOnly.
|
|
||||||
|
|
||||||
- Implementa internal/http/middleware:
|
|
||||||
- RequireAuth: se non loggato redirect /login
|
|
||||||
- RequireAdmin: se role != admin -> 403 (pagina admin/forbidden o testo)
|
|
||||||
- CurrentUser helper (legge user_id da sessione, carica user da DB con repo)
|
|
||||||
|
|
||||||
- Implementa flash messages (success/error) in sessione:
|
|
||||||
- SetFlashSuccess/SetFlashError
|
|
||||||
- ConsumeFlash middleware che aggiunge al template data
|
|
||||||
|
|
||||||
Aggiorna layout.html per mostrare flash e navbar diversa per public/private/admin.
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
Implementa AUTH completo (server-rendered) e templates in /web/templates/public.
|
|
||||||
|
|
||||||
Routes:
|
|
||||||
- GET/POST /signup
|
|
||||||
- GET/POST /login
|
|
||||||
- POST /logout
|
|
||||||
- GET /verify-email?token=...
|
|
||||||
- GET/POST /forgot-password
|
|
||||||
- GET/POST /reset-password?token=...
|
|
||||||
|
|
||||||
Comportamento:
|
|
||||||
- Signup crea user (role=user, verified=false), genera verify token (hash in DB), invia email (mailer).
|
|
||||||
- Login: blocca se email non verificata.
|
|
||||||
- Verify-email: valida token, set EmailVerified=true, elimina token.
|
|
||||||
- Forgot-password: risposta sempre generica; se user esiste+verified, genera reset token e invia email.
|
|
||||||
- Reset-password: valida token, aggiorna password, elimina token.
|
|
||||||
|
|
||||||
Crea templates:
|
|
||||||
- public/login.html
|
|
||||||
- public/signup.html
|
|
||||||
- public/forgot_password.html
|
|
||||||
- public/reset_password.html
|
|
||||||
- public/verify_notice.html
|
|
||||||
- public/home.html (opzionale)
|
|
||||||
Aggiungi partial per flash (public/_flash.html) e includilo nel layout.
|
|
||||||
|
|
||||||
Usa repo/service per accesso DB e logica (non tutto nel controller).
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
Implementa modulo “users” sotto /web/templates/private/users.
|
|
||||||
|
|
||||||
Routes protette (RequireAuth):
|
|
||||||
- GET /users -> pagina con search + container tabella
|
|
||||||
- GET /users/table -> partial HTML tabella (htmx)
|
|
||||||
- GET /users/:id/modal -> partial HTML contenuto modal
|
|
||||||
|
|
||||||
Requisiti tabella:
|
|
||||||
- query params: q, sort (id|name|email whitelist), dir (asc|desc), page, pageSize
|
|
||||||
- server-driven paging/sort/search usando GORM (Count + Limit/Offset + Order)
|
|
||||||
- _table.html deve includere:
|
|
||||||
- header th cliccabili con hx-get (toggle dir)
|
|
||||||
- pager prev/next con hx-get
|
|
||||||
- bottone “Apri” che hx-get sul modal e hx-target="#userModal" hx-swap="innerHTML"
|
|
||||||
- apri modal via JS minimal: setAttribute('open','') dopo swap (o onclick)
|
|
||||||
|
|
||||||
Crea template:
|
|
||||||
- private/users/index.html
|
|
||||||
- private/users/_table.html
|
|
||||||
- private/users/_modal.html
|
|
||||||
|
|
||||||
Integra <ui-modal id="userModal"> nella index privata.
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
Implementa area admin.
|
|
||||||
|
|
||||||
Routes protette (RequireAuth + RequireAdmin):
|
|
||||||
- GET /admin -> admin/dashboard.html
|
|
||||||
- GET /admin/users -> pagina elenco utenti (server-rendered semplice)
|
|
||||||
|
|
||||||
Templates:
|
|
||||||
- admin/dashboard.html
|
|
||||||
- admin/users.html
|
|
||||||
|
|
||||||
Navbar nel layout deve mostrare link Admin solo se role=admin.
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
Crea /ui-kit come progetto Vite + Svelte per custom elements.
|
|
||||||
|
|
||||||
Requisiti:
|
|
||||||
- build deve scrivere direttamente in ../web/static/ui:
|
|
||||||
- ui.esm.js
|
|
||||||
- ui.css (tokens+base)
|
|
||||||
- src/index.ts registra:
|
|
||||||
- ui-modal
|
|
||||||
- ui-drop-down
|
|
||||||
- ui-data-table-shell (driver htmx per aggiornare un target)
|
|
||||||
|
|
||||||
Componenti:
|
|
||||||
1) UiModal.svelte:
|
|
||||||
- <svelte:options customElement="ui-modal" />
|
|
||||||
- attributi: title, open (boolean presence)
|
|
||||||
- close on ESC, backdrop click
|
|
||||||
- focus trap minimale
|
|
||||||
- emette evento "ui:close" (bubbles+composed)
|
|
||||||
- slot contenuto (HTMX swappa dentro al tag)
|
|
||||||
|
|
||||||
2) UiDropDown.svelte:
|
|
||||||
- usa <option> del light DOM
|
|
||||||
- espone value/name/placeholder/disabled
|
|
||||||
- integra con form MVC (hidden input name=...)
|
|
||||||
- emette change + ui:change
|
|
||||||
|
|
||||||
3) UiDataTableShell.svelte:
|
|
||||||
- attributi: endpoint, target, page-size
|
|
||||||
- input search -> usa htmx.ajax('GET', url, {target}) se disponibile
|
|
||||||
- non renderizza tabella, solo toolbar
|
|
||||||
|
|
||||||
Aggiorna layout per includere /static/ui/ui.css e /static/ui/ui.esm.js con ?v={{.BuildHash}}.
|
|
||||||
Aggiorna README con comandi npm.
|
|
||||||
@@ -1,49 +1,27 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"path/filepath"
|
||||||
"html/template"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AdminController struct{}
|
type AdminController struct {
|
||||||
|
spaDir string
|
||||||
|
}
|
||||||
|
|
||||||
func NewAdminController() *AdminController {
|
func NewAdminController(spaDir string) *AdminController {
|
||||||
return &AdminController{}
|
return &AdminController{spaDir: spaDir}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ac *AdminController) Dashboard(c *fiber.Ctx) error {
|
func (ac *AdminController) Dashboard(c *fiber.Ctx) error {
|
||||||
return renderAdminPage(c, "Admin")
|
return c.SendFile(filepath.Join(ac.spaDir, "index.html"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderAdminPage(c *fiber.Ctx, title string) error {
|
func (ac *AdminController) Fallback(c *fiber.Ctx) error {
|
||||||
viewData := map[string]any{
|
return c.SendFile(filepath.Join(ac.spaDir, "index.html"))
|
||||||
"Title": title,
|
}
|
||||||
"NavSection": "admin",
|
|
||||||
}
|
func (ac *AdminController) Favicon(c *fiber.Ctx) error {
|
||||||
for k, v := range localsTemplateData(c) {
|
return c.SendFile(filepath.Join(ac.spaDir, "favicon.ico"))
|
||||||
viewData[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
files := []string{
|
|
||||||
"web/templates/layout.html",
|
|
||||||
"web/templates/public/_navbar.html",
|
|
||||||
"web/templates/partials/language_dropdown.html",
|
|
||||||
"web/templates/public/_flash.html",
|
|
||||||
"web/templates/admin/admin.html",
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpl, err := template.ParseFiles(files...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var out bytes.Buffer
|
|
||||||
if err := tmpl.ExecuteTemplate(&out, "layout.html", viewData); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Type("html", "utf-8")
|
|
||||||
return c.Send(out.Bytes())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,13 +25,6 @@ func (ac *AuthController) ShowHome(c *fiber.Ctx) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ac *AuthController) ShowWelcome(c *fiber.Ctx) error {
|
|
||||||
return renderPrivate(c, "welcome.html", map[string]any{
|
|
||||||
"Title": "Welcome",
|
|
||||||
"NavSection": "private",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AuthController) ShowSignup(c *fiber.Ctx) error {
|
func (ac *AuthController) ShowSignup(c *fiber.Ctx) error {
|
||||||
return renderPublic(c, "signup.html", map[string]any{
|
return renderPublic(c, "signup.html", map[string]any{
|
||||||
"Title": "Sign up",
|
"Title": "Sign up",
|
||||||
@@ -97,7 +90,7 @@ func (ac *AuthController) Login(c *fiber.Ctx) error {
|
|||||||
if err := httpmw.SetFlashSuccess(c, "Login effettuato"); err != nil {
|
if err := httpmw.SetFlashSuccess(c, "Login effettuato"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return c.Redirect("/welcome")
|
return c.Redirect("/private")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ac *AuthController) Logout(c *fiber.Ctx) error {
|
func (ac *AuthController) Logout(c *fiber.Ctx) error {
|
||||||
|
|||||||
27
internal/controllers/private_controller.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PrivateController struct {
|
||||||
|
spaDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPrivateController(spaDir string) *PrivateController {
|
||||||
|
return &PrivateController{spaDir: spaDir}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *PrivateController) Dashboard(c *fiber.Ctx) error {
|
||||||
|
return c.SendFile(filepath.Join(ac.spaDir, "index.html"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *PrivateController) Fallback(c *fiber.Ctx) error {
|
||||||
|
return c.SendFile(filepath.Join(ac.spaDir, "index.html"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *PrivateController) Favicon(c *fiber.Ctx) error {
|
||||||
|
return c.SendFile(filepath.Join(ac.spaDir, "favicon.ico"))
|
||||||
|
}
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package controllers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"trustcontact/internal/services"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UsersController struct {
|
|
||||||
usersService *services.UsersService
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUsersController(usersService *services.UsersService) *UsersController {
|
|
||||||
return &UsersController{usersService: usersService}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uc *UsersController) Index(c *fiber.Ctx) error {
|
|
||||||
return renderAdminPage(c, "Admin")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uc *UsersController) Table(c *fiber.Ctx) error {
|
|
||||||
return renderAdminPage(c, "Admin")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uc *UsersController) Modal(c *fiber.Ctx) error {
|
|
||||||
return renderAdminPage(c, "Admin")
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@ package http
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
"trustcontact/internal/config"
|
"trustcontact/internal/config"
|
||||||
"trustcontact/internal/controllers"
|
"trustcontact/internal/controllers"
|
||||||
httpmw "trustcontact/internal/http/middleware"
|
httpmw "trustcontact/internal/http/middleware"
|
||||||
@@ -28,9 +29,10 @@ func RegisterRoutes(app *fiber.App, store *session.Store, database *gorm.DB, cfg
|
|||||||
return fmt.Errorf("init auth service: %w", err)
|
return fmt.Errorf("init auth service: %w", err)
|
||||||
}
|
}
|
||||||
authController := controllers.NewAuthController(authService)
|
authController := controllers.NewAuthController(authService)
|
||||||
usersService := services.NewUsersService(database)
|
privateSPADir := filepath.FromSlash("quasar/private_section/dist/spa")
|
||||||
usersController := controllers.NewUsersController(usersService)
|
privateController := controllers.NewPrivateController(privateSPADir)
|
||||||
adminController := controllers.NewAdminController()
|
adminSPADir := filepath.FromSlash("quasar/admin_section/dist/spa")
|
||||||
|
adminController := controllers.NewAdminController(adminSPADir)
|
||||||
|
|
||||||
app.Get("/healthz", func(c *fiber.Ctx) error {
|
app.Get("/healthz", func(c *fiber.Ctx) error {
|
||||||
return c.SendStatus(fiber.StatusOK)
|
return c.SendStatus(fiber.StatusOK)
|
||||||
@@ -49,20 +51,32 @@ func RegisterRoutes(app *fiber.App, store *session.Store, database *gorm.DB, cfg
|
|||||||
app.Get("/reset-password", authController.ShowResetPassword)
|
app.Get("/reset-password", authController.ShowResetPassword)
|
||||||
app.Post("/reset-password", authController.ResetPassword)
|
app.Post("/reset-password", authController.ResetPassword)
|
||||||
app.Get("/forbidden", authController.ShowForbidden)
|
app.Get("/forbidden", authController.ShowForbidden)
|
||||||
app.Get("/welcome", httpmw.RequireAuth(), authController.ShowWelcome)
|
|
||||||
app.Post("/preferences/lang", httpmw.RequireAuth(), authController.UpdateLanguage)
|
app.Post("/preferences/lang", httpmw.RequireAuth(), authController.UpdateLanguage)
|
||||||
app.Post("/preferences/theme", httpmw.RequireAuth(), authController.UpdateTheme)
|
app.Post("/preferences/theme", httpmw.RequireAuth(), authController.UpdateTheme)
|
||||||
|
|
||||||
|
// Quasar admin SPA assets are emitted with absolute paths (/assets, /icons, /favicon.ico).
|
||||||
|
// Protect them with the same auth/admin middleware used by /admin.
|
||||||
|
app.Use("/assets", httpmw.RequireAuth(), httpmw.RequireAdmin())
|
||||||
|
app.Use("/icons", httpmw.RequireAuth(), httpmw.RequireAdmin())
|
||||||
|
app.Get("/favicon.ico", httpmw.RequireAuth(), httpmw.RequireAdmin(), privateController.Favicon)
|
||||||
|
app.Static("/assets", filepath.Join(privateSPADir, "assets"))
|
||||||
|
app.Static("/icons", filepath.Join(privateSPADir, "icons"))
|
||||||
|
|
||||||
private := app.Group("/private", httpmw.RequireAuth(), httpmw.RequireAdmin())
|
private := app.Group("/private", httpmw.RequireAuth(), httpmw.RequireAdmin())
|
||||||
private.Get("/", func(c *fiber.Ctx) error {
|
private.Get("/", privateController.Dashboard)
|
||||||
return c.Redirect("/admin/users")
|
private.Get("/*", privateController.Fallback)
|
||||||
})
|
|
||||||
|
// Quasar admin SPA assets are emitted with absolute paths (/assets, /icons, /favicon.ico).
|
||||||
|
// Protect them with the same auth/admin middleware used by /admin.
|
||||||
|
app.Use("/assets", httpmw.RequireAuth(), httpmw.RequireAdmin())
|
||||||
|
app.Use("/icons", httpmw.RequireAuth(), httpmw.RequireAdmin())
|
||||||
|
app.Get("/favicon.ico", httpmw.RequireAuth(), httpmw.RequireAdmin(), adminController.Favicon)
|
||||||
|
app.Static("/assets", filepath.Join(adminSPADir, "assets"))
|
||||||
|
app.Static("/icons", filepath.Join(adminSPADir, "icons"))
|
||||||
|
|
||||||
admin := app.Group("/admin", httpmw.RequireAuth(), httpmw.RequireAdmin())
|
admin := app.Group("/admin", httpmw.RequireAuth(), httpmw.RequireAdmin())
|
||||||
admin.Get("/", adminController.Dashboard)
|
admin.Get("/", adminController.Dashboard)
|
||||||
admin.Get("/users", usersController.Index)
|
admin.Get("/*", adminController.Fallback)
|
||||||
admin.Get("/users/table", usersController.Table)
|
|
||||||
admin.Get("/users/:id/modal", usersController.Modal)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
1063
package-lock.json
generated
Normal file
@@ -11,7 +11,6 @@
|
|||||||
"tailwindcss": "^4.1.13"
|
"tailwindcss": "^4.1.13"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"flowbite": "^3.1.2",
|
|
||||||
"htmx.org": "^2.0.6"
|
"htmx.org": "^2.0.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
|
|
||||||
import { defineConfig } from '#q-app/wrappers';
|
import { defineConfig } from '#q-app/wrappers';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { resolve } from 'node:path';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
|
||||||
|
dotenv.config({ path: resolve(__dirname, '../../.env') });
|
||||||
|
|
||||||
export default defineConfig((ctx) => {
|
export default defineConfig((ctx) => {
|
||||||
return {
|
return {
|
||||||
@@ -33,6 +37,9 @@ export default defineConfig((ctx) => {
|
|||||||
|
|
||||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#build
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#build
|
||||||
build: {
|
build: {
|
||||||
|
env: {
|
||||||
|
SITE_URL: process.env.SITE_URL || '',
|
||||||
|
},
|
||||||
target: {
|
target: {
|
||||||
browser: ['es2022', 'firefox115', 'chrome115', 'safari14'],
|
browser: ['es2022', 'firefox115', 'chrome115', 'safari14'],
|
||||||
node: 'node20',
|
node: 'node20',
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
<template>
|
|
||||||
<q-item clickable tag="a" target="_blank" :href="link">
|
|
||||||
<q-item-section v-if="icon" avatar>
|
|
||||||
<q-icon :name="icon" />
|
|
||||||
</q-item-section>
|
|
||||||
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>{{ title }}</q-item-label>
|
|
||||||
<q-item-label caption>{{ caption }}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
export interface EssentialLinkProps {
|
|
||||||
title: string;
|
|
||||||
caption?: string;
|
|
||||||
link?: string;
|
|
||||||
icon?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
withDefaults(defineProps<EssentialLinkProps>(), {
|
|
||||||
caption: '',
|
|
||||||
link: '#',
|
|
||||||
icon: '',
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<p>{{ title }}</p>
|
|
||||||
<ul>
|
|
||||||
<li v-for="todo in todos" :key="todo.id" @click="increment">
|
|
||||||
{{ todo.id }} - {{ todo.content }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p>Count: {{ todoCount }} / {{ meta.totalCount }}</p>
|
|
||||||
<p>Active: {{ active ? 'yes' : 'no' }}</p>
|
|
||||||
<p>Clicks on todos: {{ clickCount }}</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed, ref } from 'vue';
|
|
||||||
import type { Todo, Meta } from './models';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
title: string;
|
|
||||||
todos?: Todo[];
|
|
||||||
meta: Meta;
|
|
||||||
active: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
|
||||||
todos: () => [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const clickCount = ref(0);
|
|
||||||
function increment() {
|
|
||||||
clickCount.value += 1;
|
|
||||||
return clickCount.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const todoCount = computed(() => props.todos.length);
|
|
||||||
</script>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
export interface Todo {
|
|
||||||
id: number;
|
|
||||||
content: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Meta {
|
|
||||||
totalCount: number;
|
|
||||||
}
|
|
||||||
1
quasar/admin_section/src/env.d.ts
vendored
@@ -1,6 +1,7 @@
|
|||||||
declare namespace NodeJS {
|
declare namespace NodeJS {
|
||||||
interface ProcessEnv {
|
interface ProcessEnv {
|
||||||
NODE_ENV: string;
|
NODE_ENV: string;
|
||||||
|
SITE_URL: string | undefined;
|
||||||
VUE_ROUTER_MODE: 'hash' | 'history' | 'abstract' | undefined;
|
VUE_ROUTER_MODE: 'hash' | 'history' | 'abstract' | undefined;
|
||||||
VUE_ROUTER_BASE: string | undefined;
|
VUE_ROUTER_BASE: string | undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,14 @@
|
|||||||
|
|
||||||
<q-drawer v-model="leftDrawerOpen" show-if-above bordered>
|
<q-drawer v-model="leftDrawerOpen" show-if-above bordered>
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item-label header> Essential Links </q-item-label>
|
<q-item-label header> List Items </q-item-label>
|
||||||
|
<q-item
|
||||||
<EssentialLink v-for="link in linksList" :key="link.title" v-bind="link" />
|
clickable
|
||||||
|
tag="a"
|
||||||
|
:href="privateLink"
|
||||||
|
>
|
||||||
|
<q-item-section>Private</q-item-section>
|
||||||
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
|
|
||||||
@@ -26,54 +31,10 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import EssentialLink, { type EssentialLinkProps } from 'components/EssentialLink.vue';
|
|
||||||
|
|
||||||
const linksList: EssentialLinkProps[] = [
|
|
||||||
{
|
|
||||||
title: 'Docs',
|
|
||||||
caption: 'quasar.dev',
|
|
||||||
icon: 'school',
|
|
||||||
link: 'https://quasar.dev',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Github',
|
|
||||||
caption: 'github.com/quasarframework',
|
|
||||||
icon: 'code',
|
|
||||||
link: 'https://github.com/quasarframework',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Discord Chat Channel',
|
|
||||||
caption: 'chat.quasar.dev',
|
|
||||||
icon: 'chat',
|
|
||||||
link: 'https://chat.quasar.dev',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Forum',
|
|
||||||
caption: 'forum.quasar.dev',
|
|
||||||
icon: 'record_voice_over',
|
|
||||||
link: 'https://forum.quasar.dev',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Twitter',
|
|
||||||
caption: '@quasarframework',
|
|
||||||
icon: 'rss_feed',
|
|
||||||
link: 'https://twitter.quasar.dev',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Facebook',
|
|
||||||
caption: '@QuasarFramework',
|
|
||||||
icon: 'public',
|
|
||||||
link: 'https://facebook.quasar.dev',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Quasar Awesome',
|
|
||||||
caption: 'Community Quasar projects',
|
|
||||||
icon: 'favorite',
|
|
||||||
link: 'https://awesome.quasar.dev',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const leftDrawerOpen = ref(false);
|
const leftDrawerOpen = ref(false);
|
||||||
|
const siteUrl = (process.env.SITE_URL || '').replace(/\/+$/, '');
|
||||||
|
const privateLink = `${siteUrl}/private`;
|
||||||
|
|
||||||
function toggleLeftDrawer() {
|
function toggleLeftDrawer() {
|
||||||
leftDrawerOpen.value = !leftDrawerOpen.value;
|
leftDrawerOpen.value = !leftDrawerOpen.value;
|
||||||
|
|||||||
@@ -1,43 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-page class="row items-center justify-evenly">
|
<q-page class="row items-center justify-evenly">
|
||||||
<example-component
|
admin page
|
||||||
title="Example component"
|
|
||||||
active
|
|
||||||
:todos="todos"
|
|
||||||
:meta="meta"
|
|
||||||
></example-component>
|
|
||||||
</q-page>
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
|
||||||
import type { Todo, Meta } from 'components/models';
|
|
||||||
import ExampleComponent from 'components/ExampleComponent.vue';
|
|
||||||
|
|
||||||
const todos = ref<Todo[]>([
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
content: 'ct1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
content: 'ct2',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
content: 'ct3',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
content: 'ct4',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
content: 'ct5',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const meta = ref<Meta>({
|
|
||||||
totalCount: 1200,
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
{
|
{
|
||||||
"extends": "./.quasar/tsconfig.json"
|
"extends": "./.quasar/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,29 +15,29 @@
|
|||||||
"postinstall": "quasar prepare"
|
"postinstall": "quasar prepare"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vue-i18n": "^11.0.0",
|
"@quasar/extras": "^1.17.0",
|
||||||
"pinia": "^3.0.1",
|
"pinia": "^3.0.4",
|
||||||
"@quasar/extras": "^1.16.4",
|
"quasar": "^2.18.6",
|
||||||
"quasar": "^2.16.0",
|
"vue": "^3.5.29",
|
||||||
"vue": "^3.5.22",
|
"vue-i18n": "^11.2.8",
|
||||||
"vue-router": "^5.0.0"
|
"vue-router": "^5.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.14.0",
|
"@eslint/js": "^9.39.3",
|
||||||
"eslint": "^9.14.0",
|
|
||||||
"eslint-plugin-vue": "^10.4.0",
|
|
||||||
"globals": "^16.4.0",
|
|
||||||
"vue-tsc": "^3.0.7",
|
|
||||||
"@vue/eslint-config-typescript": "^14.4.0",
|
|
||||||
"vite-plugin-checker": "^0.11.0",
|
|
||||||
"vue-eslint-parser": "^10.2.0",
|
|
||||||
"@vue/eslint-config-prettier": "^10.1.0",
|
|
||||||
"prettier": "^3.3.3",
|
|
||||||
"@types/node": "^20.5.9",
|
|
||||||
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
||||||
"@quasar/app-vite": "^2.1.0",
|
"@quasar/app-vite": "^2.4.1",
|
||||||
"autoprefixer": "^10.4.2",
|
"@types/node": "^20.19.35",
|
||||||
"typescript": "^5.9.2"
|
"@vue/eslint-config-prettier": "^10.2.0",
|
||||||
|
"@vue/eslint-config-typescript": "^14.7.0",
|
||||||
|
"autoprefixer": "^10.4.27",
|
||||||
|
"eslint": "^9.39.3",
|
||||||
|
"eslint-plugin-vue": "^10.8.0",
|
||||||
|
"globals": "^16.5.0",
|
||||||
|
"prettier": "^3.8.1",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
|
"vite-plugin-checker": "^0.11.0",
|
||||||
|
"vue-eslint-parser": "^10.4.0",
|
||||||
|
"vue-tsc": "^3.2.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^28 || ^26 || ^24 || ^22 || ^20",
|
"node": "^28 || ^26 || ^24 || ^22 || ^20",
|
||||||
@@ -9,68 +9,68 @@ importers:
|
|||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@quasar/extras':
|
'@quasar/extras':
|
||||||
specifier: ^1.16.4
|
specifier: ^1.17.0
|
||||||
version: 1.17.0
|
version: 1.17.0
|
||||||
pinia:
|
pinia:
|
||||||
specifier: ^3.0.1
|
specifier: ^3.0.4
|
||||||
version: 3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3))
|
version: 3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3))
|
||||||
quasar:
|
quasar:
|
||||||
specifier: ^2.16.0
|
specifier: ^2.18.6
|
||||||
version: 2.18.6
|
version: 2.18.6
|
||||||
vue:
|
vue:
|
||||||
specifier: ^3.5.22
|
specifier: ^3.5.29
|
||||||
version: 3.5.29(typescript@5.9.3)
|
version: 3.5.29(typescript@5.9.3)
|
||||||
vue-i18n:
|
vue-i18n:
|
||||||
specifier: ^11.0.0
|
specifier: ^11.2.8
|
||||||
version: 11.2.8(vue@3.5.29(typescript@5.9.3))
|
version: 11.2.8(vue@3.5.29(typescript@5.9.3))
|
||||||
vue-router:
|
vue-router:
|
||||||
specifier: ^5.0.0
|
specifier: ^5.0.3
|
||||||
version: 5.0.3(@vue/compiler-sfc@3.5.29)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))
|
version: 5.0.3(@vue/compiler-sfc@3.5.29)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@eslint/js':
|
'@eslint/js':
|
||||||
specifier: ^9.14.0
|
specifier: ^9.39.3
|
||||||
version: 9.39.3
|
version: 9.39.3
|
||||||
'@intlify/unplugin-vue-i18n':
|
'@intlify/unplugin-vue-i18n':
|
||||||
specifier: ^4.0.0
|
specifier: ^4.0.0
|
||||||
version: 4.0.0(rollup@4.59.0)(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))
|
version: 4.0.0(rollup@4.59.0)(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))
|
||||||
'@quasar/app-vite':
|
'@quasar/app-vite':
|
||||||
specifier: ^2.1.0
|
specifier: ^2.4.1
|
||||||
version: 2.4.1(@types/node@20.19.35)(eslint@9.39.3)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3)))(quasar@2.18.6)(rollup@4.59.0)(sass@1.97.3)(terser@5.46.0)(typescript@5.9.3)(vue-router@5.0.3(@vue/compiler-sfc@3.5.29)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))(yaml@2.8.2)
|
version: 2.4.1(@types/node@20.19.35)(eslint@9.39.3)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3)))(quasar@2.18.6)(rollup@4.59.0)(sass@1.97.3)(terser@5.46.0)(typescript@5.9.3)(vue-router@5.0.3(@vue/compiler-sfc@3.5.29)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))(yaml@2.8.2)
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20.5.9
|
specifier: ^20.19.35
|
||||||
version: 20.19.35
|
version: 20.19.35
|
||||||
'@vue/eslint-config-prettier':
|
'@vue/eslint-config-prettier':
|
||||||
specifier: ^10.1.0
|
specifier: ^10.2.0
|
||||||
version: 10.2.0(eslint@9.39.3)(prettier@3.8.1)
|
version: 10.2.0(eslint@9.39.3)(prettier@3.8.1)
|
||||||
'@vue/eslint-config-typescript':
|
'@vue/eslint-config-typescript':
|
||||||
specifier: ^14.4.0
|
specifier: ^14.7.0
|
||||||
version: 14.7.0(eslint-plugin-vue@10.8.0(@typescript-eslint/parser@8.56.1(eslint@9.39.3)(typescript@5.9.3))(eslint@9.39.3)(vue-eslint-parser@10.4.0(eslint@9.39.3)))(eslint@9.39.3)(typescript@5.9.3)
|
version: 14.7.0(eslint-plugin-vue@10.8.0(@typescript-eslint/parser@8.56.1(eslint@9.39.3)(typescript@5.9.3))(eslint@9.39.3)(vue-eslint-parser@10.4.0(eslint@9.39.3)))(eslint@9.39.3)(typescript@5.9.3)
|
||||||
autoprefixer:
|
autoprefixer:
|
||||||
specifier: ^10.4.2
|
specifier: ^10.4.27
|
||||||
version: 10.4.27(postcss@8.5.6)
|
version: 10.4.27(postcss@8.5.6)
|
||||||
eslint:
|
eslint:
|
||||||
specifier: ^9.14.0
|
specifier: ^9.39.3
|
||||||
version: 9.39.3
|
version: 9.39.3
|
||||||
eslint-plugin-vue:
|
eslint-plugin-vue:
|
||||||
specifier: ^10.4.0
|
specifier: ^10.8.0
|
||||||
version: 10.8.0(@typescript-eslint/parser@8.56.1(eslint@9.39.3)(typescript@5.9.3))(eslint@9.39.3)(vue-eslint-parser@10.4.0(eslint@9.39.3))
|
version: 10.8.0(@typescript-eslint/parser@8.56.1(eslint@9.39.3)(typescript@5.9.3))(eslint@9.39.3)(vue-eslint-parser@10.4.0(eslint@9.39.3))
|
||||||
globals:
|
globals:
|
||||||
specifier: ^16.4.0
|
specifier: ^16.5.0
|
||||||
version: 16.5.0
|
version: 16.5.0
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.3.3
|
specifier: ^3.8.1
|
||||||
version: 3.8.1
|
version: 3.8.1
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.9.2
|
specifier: ^5.9.3
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
vite-plugin-checker:
|
vite-plugin-checker:
|
||||||
specifier: ^0.11.0
|
specifier: ^0.11.0
|
||||||
version: 0.11.0(eslint@9.39.3)(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.35)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.5(typescript@5.9.3))
|
version: 0.11.0(eslint@9.39.3)(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.35)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.5(typescript@5.9.3))
|
||||||
vue-eslint-parser:
|
vue-eslint-parser:
|
||||||
specifier: ^10.2.0
|
specifier: ^10.4.0
|
||||||
version: 10.4.0(eslint@9.39.3)
|
version: 10.4.0(eslint@9.39.3)
|
||||||
vue-tsc:
|
vue-tsc:
|
||||||
specifier: ^3.0.7
|
specifier: ^3.2.5
|
||||||
version: 3.2.5(typescript@5.9.3)
|
version: 3.2.5(typescript@5.9.3)
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 859 B After Width: | Height: | Size: 859 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
@@ -3,6 +3,10 @@
|
|||||||
|
|
||||||
import { defineConfig } from '#q-app/wrappers';
|
import { defineConfig } from '#q-app/wrappers';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { resolve } from 'node:path';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
|
||||||
|
dotenv.config({ path: resolve(__dirname, '../../.env') });
|
||||||
|
|
||||||
export default defineConfig((ctx) => {
|
export default defineConfig((ctx) => {
|
||||||
return {
|
return {
|
||||||
@@ -33,6 +37,9 @@ export default defineConfig((ctx) => {
|
|||||||
|
|
||||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#build
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#build
|
||||||
build: {
|
build: {
|
||||||
|
env: {
|
||||||
|
SITE_URL: process.env.SITE_URL || '',
|
||||||
|
},
|
||||||
target: {
|
target: {
|
||||||
browser: ['es2022', 'firefox115', 'chrome115', 'safari14'],
|
browser: ['es2022', 'firefox115', 'chrome115', 'safari14'],
|
||||||
node: 'node20',
|
node: 'node20',
|
||||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
@@ -1,6 +1,7 @@
|
|||||||
declare namespace NodeJS {
|
declare namespace NodeJS {
|
||||||
interface ProcessEnv {
|
interface ProcessEnv {
|
||||||
NODE_ENV: string;
|
NODE_ENV: string;
|
||||||
|
SITE_URL: string | undefined;
|
||||||
VUE_ROUTER_MODE: 'hash' | 'history' | 'abstract' | undefined;
|
VUE_ROUTER_MODE: 'hash' | 'history' | 'abstract' | undefined;
|
||||||
VUE_ROUTER_BASE: string | undefined;
|
VUE_ROUTER_BASE: string | undefined;
|
||||||
}
|
}
|
||||||
60
quasar/private_section/src/layouts/MainLayout.vue
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<template>
|
||||||
|
<q-layout view="lHh Lpr lFf">
|
||||||
|
<q-header elevated>
|
||||||
|
<q-toolbar>
|
||||||
|
<q-btn flat dense round icon="menu" aria-label="Menu" @click="toggleLeftDrawer" />
|
||||||
|
|
||||||
|
<q-toolbar-title> Quasar App </q-toolbar-title>
|
||||||
|
|
||||||
|
<div>Quasar v{{ $q.version }}</div>
|
||||||
|
</q-toolbar>
|
||||||
|
</q-header>
|
||||||
|
|
||||||
|
<q-drawer v-model="leftDrawerOpen" show-if-above bordered>
|
||||||
|
<q-list>
|
||||||
|
<q-item-label header> items list </q-item-label>
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
clickable
|
||||||
|
tag="a"
|
||||||
|
:href="homeLink"
|
||||||
|
>
|
||||||
|
<q-item-section>Home</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
clickable
|
||||||
|
tag="a"
|
||||||
|
:href="privateLink"
|
||||||
|
>
|
||||||
|
<q-item-section>Private</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item
|
||||||
|
clickable
|
||||||
|
tag="a"
|
||||||
|
:href="adminLink"
|
||||||
|
>
|
||||||
|
<q-item-section>Admin</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-drawer>
|
||||||
|
|
||||||
|
<q-page-container>
|
||||||
|
<router-view />
|
||||||
|
</q-page-container>
|
||||||
|
</q-layout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const leftDrawerOpen = ref(false);
|
||||||
|
const siteUrl = (process.env.SITE_URL || '').replace(/\/+$/, '');
|
||||||
|
const privateLink = `${siteUrl}/private`;
|
||||||
|
const adminLink = `${siteUrl}/admin`;
|
||||||
|
const homeLink = `${siteUrl}/`;
|
||||||
|
|
||||||
|
function toggleLeftDrawer() {
|
||||||
|
leftDrawerOpen.value = !leftDrawerOpen.value;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
9
quasar/private_section/src/pages/IndexPage.vue
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<template>
|
||||||
|
<q-page class="row items-center justify-evenly">
|
||||||
|
private page
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
7
quasar/private_section/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"extends": "./.quasar/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<template>
|
|
||||||
<q-item clickable tag="a" target="_blank" :href="link">
|
|
||||||
<q-item-section v-if="icon" avatar>
|
|
||||||
<q-icon :name="icon" />
|
|
||||||
</q-item-section>
|
|
||||||
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>{{ title }}</q-item-label>
|
|
||||||
<q-item-label caption>{{ caption }}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
export interface EssentialLinkProps {
|
|
||||||
title: string;
|
|
||||||
caption?: string;
|
|
||||||
link?: string;
|
|
||||||
icon?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
withDefaults(defineProps<EssentialLinkProps>(), {
|
|
||||||
caption: '',
|
|
||||||
link: '#',
|
|
||||||
icon: '',
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<p>{{ title }}</p>
|
|
||||||
<ul>
|
|
||||||
<li v-for="todo in todos" :key="todo.id" @click="increment">
|
|
||||||
{{ todo.id }} - {{ todo.content }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p>Count: {{ todoCount }} / {{ meta.totalCount }}</p>
|
|
||||||
<p>Active: {{ active ? 'yes' : 'no' }}</p>
|
|
||||||
<p>Clicks on todos: {{ clickCount }}</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed, ref } from 'vue';
|
|
||||||
import type { Todo, Meta } from './models';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
title: string;
|
|
||||||
todos?: Todo[];
|
|
||||||
meta: Meta;
|
|
||||||
active: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
|
||||||
todos: () => [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const clickCount = ref(0);
|
|
||||||
function increment() {
|
|
||||||
clickCount.value += 1;
|
|
||||||
return clickCount.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const todoCount = computed(() => props.todos.length);
|
|
||||||
</script>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
export interface Todo {
|
|
||||||
id: number;
|
|
||||||
content: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Meta {
|
|
||||||
totalCount: number;
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
<template>
|
|
||||||
<q-layout view="lHh Lpr lFf">
|
|
||||||
<q-header elevated>
|
|
||||||
<q-toolbar>
|
|
||||||
<q-btn flat dense round icon="menu" aria-label="Menu" @click="toggleLeftDrawer" />
|
|
||||||
|
|
||||||
<q-toolbar-title> Quasar App </q-toolbar-title>
|
|
||||||
|
|
||||||
<div>Quasar v{{ $q.version }}</div>
|
|
||||||
</q-toolbar>
|
|
||||||
</q-header>
|
|
||||||
|
|
||||||
<q-drawer v-model="leftDrawerOpen" show-if-above bordered>
|
|
||||||
<q-list>
|
|
||||||
<q-item-label header> Essential Links </q-item-label>
|
|
||||||
|
|
||||||
<EssentialLink v-for="link in linksList" :key="link.title" v-bind="link" />
|
|
||||||
</q-list>
|
|
||||||
</q-drawer>
|
|
||||||
|
|
||||||
<q-page-container>
|
|
||||||
<router-view />
|
|
||||||
</q-page-container>
|
|
||||||
</q-layout>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import EssentialLink, { type EssentialLinkProps } from 'components/EssentialLink.vue';
|
|
||||||
|
|
||||||
const linksList: EssentialLinkProps[] = [
|
|
||||||
{
|
|
||||||
title: 'Docs',
|
|
||||||
caption: 'quasar.dev',
|
|
||||||
icon: 'school',
|
|
||||||
link: 'https://quasar.dev',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Github',
|
|
||||||
caption: 'github.com/quasarframework',
|
|
||||||
icon: 'code',
|
|
||||||
link: 'https://github.com/quasarframework',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Discord Chat Channel',
|
|
||||||
caption: 'chat.quasar.dev',
|
|
||||||
icon: 'chat',
|
|
||||||
link: 'https://chat.quasar.dev',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Forum',
|
|
||||||
caption: 'forum.quasar.dev',
|
|
||||||
icon: 'record_voice_over',
|
|
||||||
link: 'https://forum.quasar.dev',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Twitter',
|
|
||||||
caption: '@quasarframework',
|
|
||||||
icon: 'rss_feed',
|
|
||||||
link: 'https://twitter.quasar.dev',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Facebook',
|
|
||||||
caption: '@QuasarFramework',
|
|
||||||
icon: 'public',
|
|
||||||
link: 'https://facebook.quasar.dev',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Quasar Awesome',
|
|
||||||
caption: 'Community Quasar projects',
|
|
||||||
icon: 'favorite',
|
|
||||||
link: 'https://awesome.quasar.dev',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const leftDrawerOpen = ref(false);
|
|
||||||
|
|
||||||
function toggleLeftDrawer() {
|
|
||||||
leftDrawerOpen.value = !leftDrawerOpen.value;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
<template>
|
|
||||||
<q-page class="row items-center justify-evenly">
|
|
||||||
<example-component
|
|
||||||
title="Example component"
|
|
||||||
active
|
|
||||||
:todos="todos"
|
|
||||||
:meta="meta"
|
|
||||||
></example-component>
|
|
||||||
</q-page>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import type { Todo, Meta } from 'components/models';
|
|
||||||
import ExampleComponent from 'components/ExampleComponent.vue';
|
|
||||||
|
|
||||||
const todos = ref<Todo[]>([
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
content: 'ct1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
content: 'ct2',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
content: 'ct3',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
content: 'ct4',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
content: 'ct5',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const meta = ref<Meta>({
|
|
||||||
totalCount: 1200,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "./.quasar/tsconfig.json"
|
|
||||||
}
|
|
||||||
30
quasar/web_components/README.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# trustcontact web components
|
||||||
|
|
||||||
|
Progetto Vue 3 (Vite) per creare Web Components custom element.
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd quasar/web_components
|
||||||
|
npm i
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Apri il playground su `http://localhost:5173`.
|
||||||
|
|
||||||
|
## Build libreria
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Output in `dist/`:
|
||||||
|
- `trustcontact-web-components.es.js`
|
||||||
|
- `trustcontact-web-components.iife.js`
|
||||||
|
|
||||||
|
## Uso nel browser
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script type="module" src="/path/trustcontact-web-components.es.js"></script>
|
||||||
|
<trustcontact-greeting name="Fabio"></trustcontact-greeting>
|
||||||
|
```
|
||||||
13
quasar/web_components/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Web Components Playground</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Web Components Playground</h1>
|
||||||
|
<trustcontact-greeting name="Fabio"></trustcontact-greeting>
|
||||||
|
<script type="module" src="/src/playground.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
19
quasar/web_components/package.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "trustcontact-web-components",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"vue": "^3.5.13"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
|
"typescript": "^5.7.2",
|
||||||
|
"vite": "^6.0.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
829
quasar/web_components/pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,829 @@
|
|||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
dependencies:
|
||||||
|
vue:
|
||||||
|
specifier: ^3.5.13
|
||||||
|
version: 3.5.29(typescript@5.9.3)
|
||||||
|
devDependencies:
|
||||||
|
'@vitejs/plugin-vue':
|
||||||
|
specifier: ^5.2.1
|
||||||
|
version: 5.2.4(vite@6.4.1)(vue@3.5.29(typescript@5.9.3))
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.7.2
|
||||||
|
version: 5.9.3
|
||||||
|
vite:
|
||||||
|
specifier: ^6.0.7
|
||||||
|
version: 6.4.1
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
'@babel/helper-string-parser@7.27.1':
|
||||||
|
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@babel/helper-validator-identifier@7.28.5':
|
||||||
|
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@babel/parser@7.29.0':
|
||||||
|
resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==}
|
||||||
|
engines: {node: '>=6.0.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
'@babel/types@7.29.0':
|
||||||
|
resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [aix]
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.25.12':
|
||||||
|
resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.25.12':
|
||||||
|
resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.25.12':
|
||||||
|
resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [loong64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.25.12':
|
||||||
|
resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [mips64el]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.25.12':
|
||||||
|
resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [s390x]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/openharmony-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openharmony]
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [sunos]
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.25.12':
|
||||||
|
resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@jridgewell/sourcemap-codec@1.5.5':
|
||||||
|
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||||
|
|
||||||
|
'@rollup/rollup-android-arm-eabi@4.59.0':
|
||||||
|
resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@rollup/rollup-android-arm64@4.59.0':
|
||||||
|
resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@rollup/rollup-darwin-arm64@4.59.0':
|
||||||
|
resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@rollup/rollup-darwin-x64@4.59.0':
|
||||||
|
resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@rollup/rollup-freebsd-arm64@4.59.0':
|
||||||
|
resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@rollup/rollup-freebsd-x64@4.59.0':
|
||||||
|
resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm-gnueabihf@4.59.0':
|
||||||
|
resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm-musleabihf@4.59.0':
|
||||||
|
resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm64-gnu@4.59.0':
|
||||||
|
resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm64-musl@4.59.0':
|
||||||
|
resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-loong64-gnu@4.59.0':
|
||||||
|
resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==}
|
||||||
|
cpu: [loong64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-loong64-musl@4.59.0':
|
||||||
|
resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==}
|
||||||
|
cpu: [loong64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-ppc64-gnu@4.59.0':
|
||||||
|
resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-ppc64-musl@4.59.0':
|
||||||
|
resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-riscv64-gnu@4.59.0':
|
||||||
|
resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-riscv64-musl@4.59.0':
|
||||||
|
resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-s390x-gnu@4.59.0':
|
||||||
|
resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==}
|
||||||
|
cpu: [s390x]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-x64-gnu@4.59.0':
|
||||||
|
resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-x64-musl@4.59.0':
|
||||||
|
resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-openbsd-x64@4.59.0':
|
||||||
|
resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@rollup/rollup-openharmony-arm64@4.59.0':
|
||||||
|
resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openharmony]
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-arm64-msvc@4.59.0':
|
||||||
|
resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-ia32-msvc@4.59.0':
|
||||||
|
resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-x64-gnu@4.59.0':
|
||||||
|
resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-x64-msvc@4.59.0':
|
||||||
|
resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@types/estree@1.0.8':
|
||||||
|
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||||
|
|
||||||
|
'@vitejs/plugin-vue@5.2.4':
|
||||||
|
resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==}
|
||||||
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
|
peerDependencies:
|
||||||
|
vite: ^5.0.0 || ^6.0.0
|
||||||
|
vue: ^3.2.25
|
||||||
|
|
||||||
|
'@vue/compiler-core@3.5.29':
|
||||||
|
resolution: {integrity: sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw==}
|
||||||
|
|
||||||
|
'@vue/compiler-dom@3.5.29':
|
||||||
|
resolution: {integrity: sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg==}
|
||||||
|
|
||||||
|
'@vue/compiler-sfc@3.5.29':
|
||||||
|
resolution: {integrity: sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA==}
|
||||||
|
|
||||||
|
'@vue/compiler-ssr@3.5.29':
|
||||||
|
resolution: {integrity: sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw==}
|
||||||
|
|
||||||
|
'@vue/reactivity@3.5.29':
|
||||||
|
resolution: {integrity: sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA==}
|
||||||
|
|
||||||
|
'@vue/runtime-core@3.5.29':
|
||||||
|
resolution: {integrity: sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg==}
|
||||||
|
|
||||||
|
'@vue/runtime-dom@3.5.29':
|
||||||
|
resolution: {integrity: sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg==}
|
||||||
|
|
||||||
|
'@vue/server-renderer@3.5.29':
|
||||||
|
resolution: {integrity: sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g==}
|
||||||
|
peerDependencies:
|
||||||
|
vue: 3.5.29
|
||||||
|
|
||||||
|
'@vue/shared@3.5.29':
|
||||||
|
resolution: {integrity: sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg==}
|
||||||
|
|
||||||
|
csstype@3.2.3:
|
||||||
|
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
||||||
|
|
||||||
|
entities@7.0.1:
|
||||||
|
resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
|
||||||
|
engines: {node: '>=0.12'}
|
||||||
|
|
||||||
|
esbuild@0.25.12:
|
||||||
|
resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
estree-walker@2.0.2:
|
||||||
|
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||||
|
|
||||||
|
fdir@6.5.0:
|
||||||
|
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
picomatch: ^3 || ^4
|
||||||
|
peerDependenciesMeta:
|
||||||
|
picomatch:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
fsevents@2.3.3:
|
||||||
|
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||||
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
magic-string@0.30.21:
|
||||||
|
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||||
|
|
||||||
|
nanoid@3.3.11:
|
||||||
|
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||||
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
picocolors@1.1.1:
|
||||||
|
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||||
|
|
||||||
|
picomatch@4.0.3:
|
||||||
|
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
postcss@8.5.6:
|
||||||
|
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||||
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
|
|
||||||
|
rollup@4.59.0:
|
||||||
|
resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==}
|
||||||
|
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
source-map-js@1.2.1:
|
||||||
|
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
tinyglobby@0.2.15:
|
||||||
|
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
typescript@5.9.3:
|
||||||
|
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||||
|
engines: {node: '>=14.17'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
vite@6.4.1:
|
||||||
|
resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==}
|
||||||
|
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
|
||||||
|
jiti: '>=1.21.0'
|
||||||
|
less: '*'
|
||||||
|
lightningcss: ^1.21.0
|
||||||
|
sass: '*'
|
||||||
|
sass-embedded: '*'
|
||||||
|
stylus: '*'
|
||||||
|
sugarss: '*'
|
||||||
|
terser: ^5.16.0
|
||||||
|
tsx: ^4.8.1
|
||||||
|
yaml: ^2.4.2
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
jiti:
|
||||||
|
optional: true
|
||||||
|
less:
|
||||||
|
optional: true
|
||||||
|
lightningcss:
|
||||||
|
optional: true
|
||||||
|
sass:
|
||||||
|
optional: true
|
||||||
|
sass-embedded:
|
||||||
|
optional: true
|
||||||
|
stylus:
|
||||||
|
optional: true
|
||||||
|
sugarss:
|
||||||
|
optional: true
|
||||||
|
terser:
|
||||||
|
optional: true
|
||||||
|
tsx:
|
||||||
|
optional: true
|
||||||
|
yaml:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
vue@3.5.29:
|
||||||
|
resolution: {integrity: sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
typescript:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
'@babel/helper-string-parser@7.27.1': {}
|
||||||
|
|
||||||
|
'@babel/helper-validator-identifier@7.28.5': {}
|
||||||
|
|
||||||
|
'@babel/parser@7.29.0':
|
||||||
|
dependencies:
|
||||||
|
'@babel/types': 7.29.0
|
||||||
|
|
||||||
|
'@babel/types@7.29.0':
|
||||||
|
dependencies:
|
||||||
|
'@babel/helper-string-parser': 7.27.1
|
||||||
|
'@babel/helper-validator-identifier': 7.28.5
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openharmony-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||||
|
|
||||||
|
'@rollup/rollup-android-arm-eabi@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-android-arm64@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-darwin-arm64@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-darwin-x64@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-freebsd-arm64@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-freebsd-x64@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm-gnueabihf@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm-musleabihf@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm64-gnu@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm64-musl@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-loong64-gnu@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-loong64-musl@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-ppc64-gnu@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-ppc64-musl@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-riscv64-gnu@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-riscv64-musl@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-s390x-gnu@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-x64-gnu@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-x64-musl@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-openbsd-x64@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-openharmony-arm64@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-arm64-msvc@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-ia32-msvc@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-x64-gnu@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-x64-msvc@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@types/estree@1.0.8': {}
|
||||||
|
|
||||||
|
'@vitejs/plugin-vue@5.2.4(vite@6.4.1)(vue@3.5.29(typescript@5.9.3))':
|
||||||
|
dependencies:
|
||||||
|
vite: 6.4.1
|
||||||
|
vue: 3.5.29(typescript@5.9.3)
|
||||||
|
|
||||||
|
'@vue/compiler-core@3.5.29':
|
||||||
|
dependencies:
|
||||||
|
'@babel/parser': 7.29.0
|
||||||
|
'@vue/shared': 3.5.29
|
||||||
|
entities: 7.0.1
|
||||||
|
estree-walker: 2.0.2
|
||||||
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
|
'@vue/compiler-dom@3.5.29':
|
||||||
|
dependencies:
|
||||||
|
'@vue/compiler-core': 3.5.29
|
||||||
|
'@vue/shared': 3.5.29
|
||||||
|
|
||||||
|
'@vue/compiler-sfc@3.5.29':
|
||||||
|
dependencies:
|
||||||
|
'@babel/parser': 7.29.0
|
||||||
|
'@vue/compiler-core': 3.5.29
|
||||||
|
'@vue/compiler-dom': 3.5.29
|
||||||
|
'@vue/compiler-ssr': 3.5.29
|
||||||
|
'@vue/shared': 3.5.29
|
||||||
|
estree-walker: 2.0.2
|
||||||
|
magic-string: 0.30.21
|
||||||
|
postcss: 8.5.6
|
||||||
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
|
'@vue/compiler-ssr@3.5.29':
|
||||||
|
dependencies:
|
||||||
|
'@vue/compiler-dom': 3.5.29
|
||||||
|
'@vue/shared': 3.5.29
|
||||||
|
|
||||||
|
'@vue/reactivity@3.5.29':
|
||||||
|
dependencies:
|
||||||
|
'@vue/shared': 3.5.29
|
||||||
|
|
||||||
|
'@vue/runtime-core@3.5.29':
|
||||||
|
dependencies:
|
||||||
|
'@vue/reactivity': 3.5.29
|
||||||
|
'@vue/shared': 3.5.29
|
||||||
|
|
||||||
|
'@vue/runtime-dom@3.5.29':
|
||||||
|
dependencies:
|
||||||
|
'@vue/reactivity': 3.5.29
|
||||||
|
'@vue/runtime-core': 3.5.29
|
||||||
|
'@vue/shared': 3.5.29
|
||||||
|
csstype: 3.2.3
|
||||||
|
|
||||||
|
'@vue/server-renderer@3.5.29(vue@3.5.29(typescript@5.9.3))':
|
||||||
|
dependencies:
|
||||||
|
'@vue/compiler-ssr': 3.5.29
|
||||||
|
'@vue/shared': 3.5.29
|
||||||
|
vue: 3.5.29(typescript@5.9.3)
|
||||||
|
|
||||||
|
'@vue/shared@3.5.29': {}
|
||||||
|
|
||||||
|
csstype@3.2.3: {}
|
||||||
|
|
||||||
|
entities@7.0.1: {}
|
||||||
|
|
||||||
|
esbuild@0.25.12:
|
||||||
|
optionalDependencies:
|
||||||
|
'@esbuild/aix-ppc64': 0.25.12
|
||||||
|
'@esbuild/android-arm': 0.25.12
|
||||||
|
'@esbuild/android-arm64': 0.25.12
|
||||||
|
'@esbuild/android-x64': 0.25.12
|
||||||
|
'@esbuild/darwin-arm64': 0.25.12
|
||||||
|
'@esbuild/darwin-x64': 0.25.12
|
||||||
|
'@esbuild/freebsd-arm64': 0.25.12
|
||||||
|
'@esbuild/freebsd-x64': 0.25.12
|
||||||
|
'@esbuild/linux-arm': 0.25.12
|
||||||
|
'@esbuild/linux-arm64': 0.25.12
|
||||||
|
'@esbuild/linux-ia32': 0.25.12
|
||||||
|
'@esbuild/linux-loong64': 0.25.12
|
||||||
|
'@esbuild/linux-mips64el': 0.25.12
|
||||||
|
'@esbuild/linux-ppc64': 0.25.12
|
||||||
|
'@esbuild/linux-riscv64': 0.25.12
|
||||||
|
'@esbuild/linux-s390x': 0.25.12
|
||||||
|
'@esbuild/linux-x64': 0.25.12
|
||||||
|
'@esbuild/netbsd-arm64': 0.25.12
|
||||||
|
'@esbuild/netbsd-x64': 0.25.12
|
||||||
|
'@esbuild/openbsd-arm64': 0.25.12
|
||||||
|
'@esbuild/openbsd-x64': 0.25.12
|
||||||
|
'@esbuild/openharmony-arm64': 0.25.12
|
||||||
|
'@esbuild/sunos-x64': 0.25.12
|
||||||
|
'@esbuild/win32-arm64': 0.25.12
|
||||||
|
'@esbuild/win32-ia32': 0.25.12
|
||||||
|
'@esbuild/win32-x64': 0.25.12
|
||||||
|
|
||||||
|
estree-walker@2.0.2: {}
|
||||||
|
|
||||||
|
fdir@6.5.0(picomatch@4.0.3):
|
||||||
|
optionalDependencies:
|
||||||
|
picomatch: 4.0.3
|
||||||
|
|
||||||
|
fsevents@2.3.3:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
magic-string@0.30.21:
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
|
nanoid@3.3.11: {}
|
||||||
|
|
||||||
|
picocolors@1.1.1: {}
|
||||||
|
|
||||||
|
picomatch@4.0.3: {}
|
||||||
|
|
||||||
|
postcss@8.5.6:
|
||||||
|
dependencies:
|
||||||
|
nanoid: 3.3.11
|
||||||
|
picocolors: 1.1.1
|
||||||
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
|
rollup@4.59.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/estree': 1.0.8
|
||||||
|
optionalDependencies:
|
||||||
|
'@rollup/rollup-android-arm-eabi': 4.59.0
|
||||||
|
'@rollup/rollup-android-arm64': 4.59.0
|
||||||
|
'@rollup/rollup-darwin-arm64': 4.59.0
|
||||||
|
'@rollup/rollup-darwin-x64': 4.59.0
|
||||||
|
'@rollup/rollup-freebsd-arm64': 4.59.0
|
||||||
|
'@rollup/rollup-freebsd-x64': 4.59.0
|
||||||
|
'@rollup/rollup-linux-arm-gnueabihf': 4.59.0
|
||||||
|
'@rollup/rollup-linux-arm-musleabihf': 4.59.0
|
||||||
|
'@rollup/rollup-linux-arm64-gnu': 4.59.0
|
||||||
|
'@rollup/rollup-linux-arm64-musl': 4.59.0
|
||||||
|
'@rollup/rollup-linux-loong64-gnu': 4.59.0
|
||||||
|
'@rollup/rollup-linux-loong64-musl': 4.59.0
|
||||||
|
'@rollup/rollup-linux-ppc64-gnu': 4.59.0
|
||||||
|
'@rollup/rollup-linux-ppc64-musl': 4.59.0
|
||||||
|
'@rollup/rollup-linux-riscv64-gnu': 4.59.0
|
||||||
|
'@rollup/rollup-linux-riscv64-musl': 4.59.0
|
||||||
|
'@rollup/rollup-linux-s390x-gnu': 4.59.0
|
||||||
|
'@rollup/rollup-linux-x64-gnu': 4.59.0
|
||||||
|
'@rollup/rollup-linux-x64-musl': 4.59.0
|
||||||
|
'@rollup/rollup-openbsd-x64': 4.59.0
|
||||||
|
'@rollup/rollup-openharmony-arm64': 4.59.0
|
||||||
|
'@rollup/rollup-win32-arm64-msvc': 4.59.0
|
||||||
|
'@rollup/rollup-win32-ia32-msvc': 4.59.0
|
||||||
|
'@rollup/rollup-win32-x64-gnu': 4.59.0
|
||||||
|
'@rollup/rollup-win32-x64-msvc': 4.59.0
|
||||||
|
fsevents: 2.3.3
|
||||||
|
|
||||||
|
source-map-js@1.2.1: {}
|
||||||
|
|
||||||
|
tinyglobby@0.2.15:
|
||||||
|
dependencies:
|
||||||
|
fdir: 6.5.0(picomatch@4.0.3)
|
||||||
|
picomatch: 4.0.3
|
||||||
|
|
||||||
|
typescript@5.9.3: {}
|
||||||
|
|
||||||
|
vite@6.4.1:
|
||||||
|
dependencies:
|
||||||
|
esbuild: 0.25.12
|
||||||
|
fdir: 6.5.0(picomatch@4.0.3)
|
||||||
|
picomatch: 4.0.3
|
||||||
|
postcss: 8.5.6
|
||||||
|
rollup: 4.59.0
|
||||||
|
tinyglobby: 0.2.15
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents: 2.3.3
|
||||||
|
|
||||||
|
vue@3.5.29(typescript@5.9.3):
|
||||||
|
dependencies:
|
||||||
|
'@vue/compiler-dom': 3.5.29
|
||||||
|
'@vue/compiler-sfc': 3.5.29
|
||||||
|
'@vue/runtime-dom': 3.5.29
|
||||||
|
'@vue/server-renderer': 3.5.29(vue@3.5.29(typescript@5.9.3))
|
||||||
|
'@vue/shared': 3.5.29
|
||||||
|
optionalDependencies:
|
||||||
|
typescript: 5.9.3
|
||||||
39
quasar/web_components/src/components/Greeting.ce.vue
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<template>
|
||||||
|
<section class="card">
|
||||||
|
<p class="title">Hello {{ safeName }}</p>
|
||||||
|
<p class="subtitle">This is a Vue 3 custom element.</p>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps<{ name?: string }>();
|
||||||
|
|
||||||
|
const safeName = computed(() => (props.name && props.name.trim()) || 'there');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.card {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
font-family: Inter, system-ui, -apple-system, sans-serif;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
margin: 0;
|
||||||
|
color: #4b5563;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
3
quasar/web_components/src/playground.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { registerWebComponents } from './register';
|
||||||
|
|
||||||
|
registerWebComponents();
|
||||||
12
quasar/web_components/src/register.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { defineCustomElement } from 'vue';
|
||||||
|
import GreetingElement from './components/Greeting.ce.vue';
|
||||||
|
|
||||||
|
const TAG_NAME = 'trustcontact-greeting';
|
||||||
|
|
||||||
|
export function registerWebComponents(): void {
|
||||||
|
if (!customElements.get(TAG_NAME)) {
|
||||||
|
customElements.define(TAG_NAME, defineCustomElement(GreetingElement));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerWebComponents();
|
||||||
15
quasar/web_components/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"strict": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"types": ["vite/client"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.vue", "vite.config.ts"]
|
||||||
|
}
|
||||||
18
quasar/web_components/vite.config.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue({
|
||||||
|
customElement: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: 'src/register.ts',
|
||||||
|
name: 'TrustcontactWebComponents',
|
||||||
|
formats: ['es', 'iife'],
|
||||||
|
fileName: (format) => `trustcontact-web-components.${format}.js`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -2,9 +2,8 @@ module.exports = {
|
|||||||
darkMode: "class",
|
darkMode: "class",
|
||||||
content: [
|
content: [
|
||||||
"./web/templates/**/*.{html,gohtml}",
|
"./web/templates/**/*.{html,gohtml}",
|
||||||
"./web/static/**/*.js",
|
"./web/static/**/*.js"
|
||||||
"./node_modules/flowbite/**/*.js"
|
|
||||||
],
|
],
|
||||||
theme: { extend: {} },
|
theme: { extend: {} },
|
||||||
plugins: [require("flowbite/plugin")]
|
plugins: []
|
||||||
};
|
};
|
||||||
|
|||||||
2
web/static/css/app.css
Normal file
5
web/static/vendor/flags/ch.svg
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" role="img" aria-label="Swiss flag">
|
||||||
|
<rect width="32" height="32" fill="#d52b1e"/>
|
||||||
|
<rect x="13" y="6" width="6" height="20" fill="#ffffff"/>
|
||||||
|
<rect x="6" y="13" width="20" height="6" fill="#ffffff"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 271 B |
5
web/static/vendor/flags/de.svg
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 32" role="img" aria-label="German flag">
|
||||||
|
<rect width="48" height="10.67" x="0" y="0" fill="#000000"/>
|
||||||
|
<rect width="48" height="10.67" x="0" y="10.67" fill="#dd0000"/>
|
||||||
|
<rect width="48" height="10.66" x="0" y="21.34" fill="#ffce00"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 301 B |
9
web/static/vendor/flags/en.svg
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 32" role="img" aria-label="English flag">
|
||||||
|
<rect width="48" height="32" fill="#012169"/>
|
||||||
|
<path d="M0 0 48 32M48 0 0 32" stroke="#ffffff" stroke-width="6"/>
|
||||||
|
<path d="M0 0 48 32M48 0 0 32" stroke="#c8102e" stroke-width="3"/>
|
||||||
|
<rect x="20" y="0" width="8" height="32" fill="#ffffff"/>
|
||||||
|
<rect x="0" y="12" width="48" height="8" fill="#ffffff"/>
|
||||||
|
<rect x="22" y="0" width="4" height="32" fill="#c8102e"/>
|
||||||
|
<rect x="0" y="14" width="48" height="4" fill="#c8102e"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 531 B |
53
web/static/vendor/flags/en_us.svg
vendored
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 32" role="img" aria-label="United States flag">
|
||||||
|
<rect width="48" height="32" fill="#b22234"/>
|
||||||
|
<g fill="#ffffff">
|
||||||
|
<rect y="2.46" width="48" height="2.46"/>
|
||||||
|
<rect y="7.38" width="48" height="2.46"/>
|
||||||
|
<rect y="12.30" width="48" height="2.46"/>
|
||||||
|
<rect y="17.22" width="48" height="2.46"/>
|
||||||
|
<rect y="22.14" width="48" height="2.46"/>
|
||||||
|
<rect y="27.06" width="48" height="2.46"/>
|
||||||
|
</g>
|
||||||
|
<rect width="20" height="17.23" fill="#3c3b6e"/>
|
||||||
|
<g fill="#ffffff">
|
||||||
|
<circle cx="2.2" cy="2.2" r="0.7"/>
|
||||||
|
<circle cx="5.4" cy="2.2" r="0.7"/>
|
||||||
|
<circle cx="8.6" cy="2.2" r="0.7"/>
|
||||||
|
<circle cx="11.8" cy="2.2" r="0.7"/>
|
||||||
|
<circle cx="15.0" cy="2.2" r="0.7"/>
|
||||||
|
<circle cx="18.2" cy="2.2" r="0.7"/>
|
||||||
|
<circle cx="3.8" cy="4.4" r="0.7"/>
|
||||||
|
<circle cx="7.0" cy="4.4" r="0.7"/>
|
||||||
|
<circle cx="10.2" cy="4.4" r="0.7"/>
|
||||||
|
<circle cx="13.4" cy="4.4" r="0.7"/>
|
||||||
|
<circle cx="16.6" cy="4.4" r="0.7"/>
|
||||||
|
<circle cx="2.2" cy="6.6" r="0.7"/>
|
||||||
|
<circle cx="5.4" cy="6.6" r="0.7"/>
|
||||||
|
<circle cx="8.6" cy="6.6" r="0.7"/>
|
||||||
|
<circle cx="11.8" cy="6.6" r="0.7"/>
|
||||||
|
<circle cx="15.0" cy="6.6" r="0.7"/>
|
||||||
|
<circle cx="18.2" cy="6.6" r="0.7"/>
|
||||||
|
<circle cx="3.8" cy="8.8" r="0.7"/>
|
||||||
|
<circle cx="7.0" cy="8.8" r="0.7"/>
|
||||||
|
<circle cx="10.2" cy="8.8" r="0.7"/>
|
||||||
|
<circle cx="13.4" cy="8.8" r="0.7"/>
|
||||||
|
<circle cx="16.6" cy="8.8" r="0.7"/>
|
||||||
|
<circle cx="2.2" cy="11" r="0.7"/>
|
||||||
|
<circle cx="5.4" cy="11" r="0.7"/>
|
||||||
|
<circle cx="8.6" cy="11" r="0.7"/>
|
||||||
|
<circle cx="11.8" cy="11" r="0.7"/>
|
||||||
|
<circle cx="15.0" cy="11" r="0.7"/>
|
||||||
|
<circle cx="18.2" cy="11" r="0.7"/>
|
||||||
|
<circle cx="3.8" cy="13.2" r="0.7"/>
|
||||||
|
<circle cx="7.0" cy="13.2" r="0.7"/>
|
||||||
|
<circle cx="10.2" cy="13.2" r="0.7"/>
|
||||||
|
<circle cx="13.4" cy="13.2" r="0.7"/>
|
||||||
|
<circle cx="16.6" cy="13.2" r="0.7"/>
|
||||||
|
<circle cx="2.2" cy="15.4" r="0.7"/>
|
||||||
|
<circle cx="5.4" cy="15.4" r="0.7"/>
|
||||||
|
<circle cx="8.6" cy="15.4" r="0.7"/>
|
||||||
|
<circle cx="11.8" cy="15.4" r="0.7"/>
|
||||||
|
<circle cx="15.0" cy="15.4" r="0.7"/>
|
||||||
|
<circle cx="18.2" cy="15.4" r="0.7"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
5
web/static/vendor/flags/fr.svg
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 32" role="img" aria-label="French flag">
|
||||||
|
<rect width="16" height="32" x="0" y="0" fill="#0055a4"/>
|
||||||
|
<rect width="16" height="32" x="16" y="0" fill="#ffffff"/>
|
||||||
|
<rect width="16" height="32" x="32" y="0" fill="#ef4135"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 286 B |
5
web/static/vendor/flags/it.svg
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 32" role="img" aria-label="Italian flag">
|
||||||
|
<rect width="16" height="32" x="0" y="0" fill="#009246"/>
|
||||||
|
<rect width="16" height="32" x="16" y="0" fill="#ffffff"/>
|
||||||
|
<rect width="16" height="32" x="32" y="0" fill="#ce2b37"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 287 B |
1
web/static/vendor/htmx.min.js
vendored
Normal file
62
web/static/vendor/theme.js
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
(function () {
|
||||||
|
var STORAGE_KEY = 'theme';
|
||||||
|
var isAuthenticated = !!window.__TC_IS_AUTHENTICATED;
|
||||||
|
var serverTheme = (window.__TC_SERVER_THEME || '').toLowerCase();
|
||||||
|
var hasStoredTheme = false;
|
||||||
|
|
||||||
|
function getPreferredTheme() {
|
||||||
|
var stored = localStorage.getItem(STORAGE_KEY);
|
||||||
|
hasStoredTheme = stored === 'dark' || stored === 'light';
|
||||||
|
if (hasStoredTheme) return stored;
|
||||||
|
if (serverTheme === 'dark' || serverTheme === 'light') return serverTheme;
|
||||||
|
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTheme(theme) {
|
||||||
|
var isDark = theme === 'dark';
|
||||||
|
document.documentElement.classList.toggle('dark', isDark);
|
||||||
|
var button = document.getElementById('themeToggle');
|
||||||
|
if (button) {
|
||||||
|
button.setAttribute('aria-pressed', isDark ? 'true' : 'false');
|
||||||
|
button.textContent = isDark ? 'Light mode' : 'Dark mode';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function persistTheme(theme) {
|
||||||
|
localStorage.setItem(STORAGE_KEY, theme);
|
||||||
|
hasStoredTheme = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendThemeToServer(theme) {
|
||||||
|
if (!isAuthenticated) return;
|
||||||
|
fetch('/preferences/theme', {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||||
|
body: 'theme=' + encodeURIComponent(theme),
|
||||||
|
}).catch(function () {});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.toggleTheme = function toggleTheme() {
|
||||||
|
var currentIsDark = document.documentElement.classList.contains('dark');
|
||||||
|
var nextTheme = currentIsDark ? 'light' : 'dark';
|
||||||
|
applyTheme(nextTheme);
|
||||||
|
persistTheme(nextTheme);
|
||||||
|
sendThemeToServer(nextTheme);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.initThemeToggle = function initThemeToggle() {
|
||||||
|
applyTheme(document.documentElement.classList.contains('dark') ? 'dark' : 'light');
|
||||||
|
};
|
||||||
|
|
||||||
|
var initialTheme = getPreferredTheme();
|
||||||
|
applyTheme(initialTheme);
|
||||||
|
|
||||||
|
var mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||||
|
if (typeof mediaQuery.addEventListener === 'function') {
|
||||||
|
mediaQuery.addEventListener('change', function (event) {
|
||||||
|
if (hasStoredTheme) return;
|
||||||
|
applyTheme(event.matches ? 'dark' : 'light');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
<script src="/static/vendor/theme.js?v={{.BuildHash}}"></script>
|
<script src="/static/vendor/theme.js?v={{.BuildHash}}"></script>
|
||||||
<link rel="stylesheet" href="/static/css/app.css?v={{.BuildHash}}">
|
<link rel="stylesheet" href="/static/css/app.css?v={{.BuildHash}}">
|
||||||
<script src="/static/vendor/htmx.min.js"></script>
|
<script src="/static/vendor/htmx.min.js"></script>
|
||||||
<script src="/static/vendor/flowbite.js"></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body class="flex min-h-screen flex-col bg-white text-gray-900 antialiased dark:bg-gray-900 dark:text-gray-100">
|
<body class="flex min-h-screen flex-col bg-white text-gray-900 antialiased dark:bg-gray-900 dark:text-gray-100">
|
||||||
{{template "navbar" .}}
|
{{template "navbar" .}}
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
'forgot.title': 'Password dimenticata', 'forgot.subtitle': 'Inserisci la tua email per ricevere il link di reset.', 'forgot.submit': 'Invia link reset', 'forgot.back_login': 'Torna al login',
|
'forgot.title': 'Password dimenticata', 'forgot.subtitle': 'Inserisci la tua email per ricevere il link di reset.', 'forgot.submit': 'Invia link reset', 'forgot.back_login': 'Torna al login',
|
||||||
'reset.title': 'Reset password', 'reset.subtitle': 'Imposta una nuova password.', 'reset.new_password': 'Nuova password', 'reset.submit': 'Aggiorna password', 'reset.invalid_token': 'Token mancante o non valido.',
|
'reset.title': 'Reset password', 'reset.subtitle': 'Imposta una nuova password.', 'reset.new_password': 'Nuova password', 'reset.submit': 'Aggiorna password', 'reset.invalid_token': 'Token mancante o non valido.',
|
||||||
'verify.title': 'Verifica email', 'verify.p1': 'Controlla la casella di posta e apri il link di verifica ricevuto.', 'verify.p2': 'Se il link è scaduto, ripeti la registrazione o contatta supporto.', 'verify.go_login': 'Vai al login',
|
'verify.title': 'Verifica email', 'verify.p1': 'Controlla la casella di posta e apri il link di verifica ricevuto.', 'verify.p2': 'Se il link è scaduto, ripeti la registrazione o contatta supporto.', 'verify.go_login': 'Vai al login',
|
||||||
'welcome.title': 'Dashboard', 'welcome.back_prefix': 'Bentornato', 'welcome.generic': 'Benvenuto.', 'welcome.quick_links': 'Link rapidi',
|
'private.title': 'Dashboard', 'private.back_prefix': 'Bentornato', 'private.generic': 'Benvenuto.', 'private.quick_links': 'Link rapidi',
|
||||||
'admin.dashboard.title': 'Admin Dashboard', 'admin.dashboard.area': 'Area amministrazione.', 'admin.users_count': 'Utenti', 'admin.current_role': 'Ruolo corrente', 'admin.navigation': 'Navigazione', 'admin.manage_users': 'Gestione utenti',
|
'admin.dashboard.title': 'Admin Dashboard', 'admin.dashboard.area': 'Area amministrazione.', 'admin.users_count': 'Utenti', 'admin.current_role': 'Ruolo corrente', 'admin.navigation': 'Navigazione', 'admin.manage_users': 'Gestione utenti',
|
||||||
'users.title': 'Users', 'users.subtitle': 'Ricerca, ordinamento e paging server-side via HTMX.', 'users.new_user': 'Nuovo Utente', 'users.search': 'Search', 'users.search_placeholder': 'Cerca nome o email', 'users.page_size': 'Page size', 'users.search_button': 'Cerca', 'users.user_detail': 'Dettaglio utente', 'users.actions': 'Azioni', 'users.open': 'Apri', 'users.none': 'Nessun utente trovato.', 'users.total': 'Totale', 'users.users_label': 'utenti', 'users.page': 'Pagina', 'users.prev': 'Prev', 'users.next': 'Next', 'users.close': 'Chiudi',
|
'users.title': 'Users', 'users.subtitle': 'Ricerca, ordinamento e paging server-side via HTMX.', 'users.new_user': 'Nuovo Utente', 'users.search': 'Search', 'users.search_placeholder': 'Cerca nome o email', 'users.page_size': 'Page size', 'users.search_button': 'Cerca', 'users.user_detail': 'Dettaglio utente', 'users.actions': 'Azioni', 'users.open': 'Apri', 'users.none': 'Nessun utente trovato.', 'users.total': 'Totale', 'users.users_label': 'utenti', 'users.page': 'Pagina', 'users.prev': 'Prev', 'users.next': 'Next', 'users.close': 'Chiudi',
|
||||||
'users.new_user_modal_title': 'Nuovo utente', 'users.new_user_modal_placeholder': 'Placeholder UI Flowbite. La creazione utente può essere collegata a una route backend quando disponibile.',
|
'users.new_user_modal_title': 'Nuovo utente', 'users.new_user_modal_placeholder': 'Placeholder UI Flowbite. La creazione utente può essere collegata a una route backend quando disponibile.',
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
'forgot.title': 'Forgot Password', 'forgot.subtitle': 'Enter your email to receive a reset link.', 'forgot.submit': 'Send reset link', 'forgot.back_login': 'Back to login',
|
'forgot.title': 'Forgot Password', 'forgot.subtitle': 'Enter your email to receive a reset link.', 'forgot.submit': 'Send reset link', 'forgot.back_login': 'Back to login',
|
||||||
'reset.title': 'Reset Password', 'reset.subtitle': 'Set a new password.', 'reset.new_password': 'New password', 'reset.submit': 'Update password', 'reset.invalid_token': 'Missing or invalid token.',
|
'reset.title': 'Reset Password', 'reset.subtitle': 'Set a new password.', 'reset.new_password': 'New password', 'reset.submit': 'Update password', 'reset.invalid_token': 'Missing or invalid token.',
|
||||||
'verify.title': 'Verify email', 'verify.p1': 'Check your inbox and open the verification link.', 'verify.p2': 'If the link expired, sign up again or contact support.', 'verify.go_login': 'Go to login',
|
'verify.title': 'Verify email', 'verify.p1': 'Check your inbox and open the verification link.', 'verify.p2': 'If the link expired, sign up again or contact support.', 'verify.go_login': 'Go to login',
|
||||||
'welcome.title': 'Dashboard', 'welcome.back_prefix': 'Welcome back', 'welcome.generic': 'Welcome.', 'welcome.quick_links': 'Quick links',
|
'private.title': 'Dashboard', 'private.back_prefix': 'Signed in as', 'private.generic': 'Signed in.', 'private.quick_links': 'Quick links',
|
||||||
'admin.dashboard.title': 'Admin Dashboard', 'admin.dashboard.area': 'Administration area.', 'admin.users_count': 'Users', 'admin.current_role': 'Current role', 'admin.navigation': 'Navigation', 'admin.manage_users': 'Manage users',
|
'admin.dashboard.title': 'Admin Dashboard', 'admin.dashboard.area': 'Administration area.', 'admin.users_count': 'Users', 'admin.current_role': 'Current role', 'admin.navigation': 'Navigation', 'admin.manage_users': 'Manage users',
|
||||||
'users.title': 'Users', 'users.subtitle': 'Search, sorting and server-side paging via HTMX.', 'users.new_user': 'New user', 'users.search': 'Search', 'users.search_placeholder': 'Search by name or email', 'users.page_size': 'Page size', 'users.search_button': 'Search', 'users.user_detail': 'User details', 'users.actions': 'Actions', 'users.open': 'Open', 'users.none': 'No users found.', 'users.total': 'Total', 'users.users_label': 'users', 'users.page': 'Page', 'users.prev': 'Prev', 'users.next': 'Next', 'users.close': 'Close',
|
'users.title': 'Users', 'users.subtitle': 'Search, sorting and server-side paging via HTMX.', 'users.new_user': 'New user', 'users.search': 'Search', 'users.search_placeholder': 'Search by name or email', 'users.page_size': 'Page size', 'users.search_button': 'Search', 'users.user_detail': 'User details', 'users.actions': 'Actions', 'users.open': 'Open', 'users.none': 'No users found.', 'users.total': 'Total', 'users.users_label': 'users', 'users.page': 'Page', 'users.prev': 'Prev', 'users.next': 'Next', 'users.close': 'Close',
|
||||||
'users.new_user_modal_title': 'New user', 'users.new_user_modal_placeholder': 'Flowbite placeholder UI. Connect creation to backend route when available.',
|
'users.new_user_modal_title': 'New user', 'users.new_user_modal_placeholder': 'Flowbite placeholder UI. Connect creation to backend route when available.',
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
'forgot.title': 'Passwort vergessen', 'forgot.subtitle': 'Geben Sie Ihre E-Mail ein, um einen Reset-Link zu erhalten.', 'forgot.submit': 'Reset-Link senden', 'forgot.back_login': 'Zurück zum Login',
|
'forgot.title': 'Passwort vergessen', 'forgot.subtitle': 'Geben Sie Ihre E-Mail ein, um einen Reset-Link zu erhalten.', 'forgot.submit': 'Reset-Link senden', 'forgot.back_login': 'Zurück zum Login',
|
||||||
'reset.title': 'Passwort zurücksetzen', 'reset.subtitle': 'Legen Sie ein neues Passwort fest.', 'reset.new_password': 'Neues Passwort', 'reset.submit': 'Passwort aktualisieren', 'reset.invalid_token': 'Token fehlt oder ist ungültig.',
|
'reset.title': 'Passwort zurücksetzen', 'reset.subtitle': 'Legen Sie ein neues Passwort fest.', 'reset.new_password': 'Neues Passwort', 'reset.submit': 'Passwort aktualisieren', 'reset.invalid_token': 'Token fehlt oder ist ungültig.',
|
||||||
'verify.title': 'E-Mail verifizieren', 'verify.p1': 'Öffnen Sie die Verifizierungs-E-Mail in Ihrem Posteingang.', 'verify.p2': 'Wenn der Link abgelaufen ist, registrieren Sie sich erneut oder kontaktieren Sie den Support.', 'verify.go_login': 'Zum Login',
|
'verify.title': 'E-Mail verifizieren', 'verify.p1': 'Öffnen Sie die Verifizierungs-E-Mail in Ihrem Posteingang.', 'verify.p2': 'Wenn der Link abgelaufen ist, registrieren Sie sich erneut oder kontaktieren Sie den Support.', 'verify.go_login': 'Zum Login',
|
||||||
'welcome.title': 'Dashboard', 'welcome.back_prefix': 'Willkommen zurück', 'welcome.generic': 'Willkommen.', 'welcome.quick_links': 'Schnelllinks',
|
'private.title': 'Dashboard', 'private.back_prefix': 'Willkommen zurück', 'private.generic': 'Willkommen.', 'private.quick_links': 'Schnelllinks',
|
||||||
'admin.dashboard.title': 'Admin-Dashboard', 'admin.dashboard.area': 'Administrationsbereich.', 'admin.users_count': 'Benutzer', 'admin.current_role': 'Aktuelle Rolle', 'admin.navigation': 'Navigation', 'admin.manage_users': 'Benutzer verwalten',
|
'admin.dashboard.title': 'Admin-Dashboard', 'admin.dashboard.area': 'Administrationsbereich.', 'admin.users_count': 'Benutzer', 'admin.current_role': 'Aktuelle Rolle', 'admin.navigation': 'Navigation', 'admin.manage_users': 'Benutzer verwalten',
|
||||||
'users.title': 'Benutzer', 'users.subtitle': 'Suche, Sortierung und serverseitiges Paging via HTMX.', 'users.new_user': 'Neuer Benutzer', 'users.search': 'Suche', 'users.search_placeholder': 'Nach Name oder E-Mail suchen', 'users.page_size': 'Seitengröße', 'users.search_button': 'Suchen', 'users.user_detail': 'Benutzerdetails', 'users.actions': 'Aktionen', 'users.open': 'Öffnen', 'users.none': 'Keine Benutzer gefunden.', 'users.total': 'Gesamt', 'users.users_label': 'Benutzer', 'users.page': 'Seite', 'users.prev': 'Zurück', 'users.next': 'Weiter', 'users.close': 'Schließen',
|
'users.title': 'Benutzer', 'users.subtitle': 'Suche, Sortierung und serverseitiges Paging via HTMX.', 'users.new_user': 'Neuer Benutzer', 'users.search': 'Suche', 'users.search_placeholder': 'Nach Name oder E-Mail suchen', 'users.page_size': 'Seitengröße', 'users.search_button': 'Suchen', 'users.user_detail': 'Benutzerdetails', 'users.actions': 'Aktionen', 'users.open': 'Öffnen', 'users.none': 'Keine Benutzer gefunden.', 'users.total': 'Gesamt', 'users.users_label': 'Benutzer', 'users.page': 'Seite', 'users.prev': 'Zurück', 'users.next': 'Weiter', 'users.close': 'Schließen',
|
||||||
'users.new_user_modal_title': 'Neuer Benutzer', 'users.new_user_modal_placeholder': 'Flowbite-Placeholder-UI. Bei Bedarf mit Backend-Route verbinden.',
|
'users.new_user_modal_title': 'Neuer Benutzer', 'users.new_user_modal_placeholder': 'Flowbite-Placeholder-UI. Bei Bedarf mit Backend-Route verbinden.',
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
'forgot.title': 'Mot de passe oublié', 'forgot.subtitle': 'Entrez votre email pour recevoir un lien de réinitialisation.', 'forgot.submit': 'Envoyer le lien', 'forgot.back_login': 'Retour à la connexion',
|
'forgot.title': 'Mot de passe oublié', 'forgot.subtitle': 'Entrez votre email pour recevoir un lien de réinitialisation.', 'forgot.submit': 'Envoyer le lien', 'forgot.back_login': 'Retour à la connexion',
|
||||||
'reset.title': 'Réinitialiser le mot de passe', 'reset.subtitle': 'Définissez un nouveau mot de passe.', 'reset.new_password': 'Nouveau mot de passe', 'reset.submit': 'Mettre à jour', 'reset.invalid_token': 'Jeton manquant ou invalide.',
|
'reset.title': 'Réinitialiser le mot de passe', 'reset.subtitle': 'Définissez un nouveau mot de passe.', 'reset.new_password': 'Nouveau mot de passe', 'reset.submit': 'Mettre à jour', 'reset.invalid_token': 'Jeton manquant ou invalide.',
|
||||||
'verify.title': 'Vérifier l’email', 'verify.p1': 'Vérifiez votre boîte mail et ouvrez le lien de vérification.', 'verify.p2': 'Si le lien a expiré, réinscrivez-vous ou contactez le support.', 'verify.go_login': 'Aller à la connexion',
|
'verify.title': 'Vérifier l’email', 'verify.p1': 'Vérifiez votre boîte mail et ouvrez le lien de vérification.', 'verify.p2': 'Si le lien a expiré, réinscrivez-vous ou contactez le support.', 'verify.go_login': 'Aller à la connexion',
|
||||||
'welcome.title': 'Tableau de bord', 'welcome.back_prefix': 'Bon retour', 'welcome.generic': 'Bienvenue.', 'welcome.quick_links': 'Liens rapides',
|
'private.title': 'Tableau de bord', 'private.back_prefix': 'Bon retour', 'private.generic': 'Bienvenue.', 'private.quick_links': 'Liens rapides',
|
||||||
'admin.dashboard.title': 'Tableau de bord admin', 'admin.dashboard.area': 'Zone d’administration.', 'admin.users_count': 'Utilisateurs', 'admin.current_role': 'Rôle actuel', 'admin.navigation': 'Navigation', 'admin.manage_users': 'Gérer les utilisateurs',
|
'admin.dashboard.title': 'Tableau de bord admin', 'admin.dashboard.area': 'Zone d’administration.', 'admin.users_count': 'Utilisateurs', 'admin.current_role': 'Rôle actuel', 'admin.navigation': 'Navigation', 'admin.manage_users': 'Gérer les utilisateurs',
|
||||||
'users.title': 'Utilisateurs', 'users.subtitle': 'Recherche, tri et pagination côté serveur via HTMX.', 'users.new_user': 'Nouvel utilisateur', 'users.search': 'Recherche', 'users.search_placeholder': 'Rechercher par nom ou email', 'users.page_size': 'Taille de page', 'users.search_button': 'Rechercher', 'users.user_detail': 'Détails utilisateur', 'users.actions': 'Actions', 'users.open': 'Ouvrir', 'users.none': 'Aucun utilisateur trouvé.', 'users.total': 'Total', 'users.users_label': 'utilisateurs', 'users.page': 'Page', 'users.prev': 'Préc.', 'users.next': 'Suiv.', 'users.close': 'Fermer',
|
'users.title': 'Utilisateurs', 'users.subtitle': 'Recherche, tri et pagination côté serveur via HTMX.', 'users.new_user': 'Nouvel utilisateur', 'users.search': 'Recherche', 'users.search_placeholder': 'Rechercher par nom ou email', 'users.page_size': 'Taille de page', 'users.search_button': 'Rechercher', 'users.user_detail': 'Détails utilisateur', 'users.actions': 'Actions', 'users.open': 'Ouvrir', 'users.none': 'Aucun utilisateur trouvé.', 'users.total': 'Total', 'users.users_label': 'utilisateurs', 'users.page': 'Page', 'users.prev': 'Préc.', 'users.next': 'Suiv.', 'users.close': 'Fermer',
|
||||||
'users.new_user_modal_title': 'Nouvel utilisateur', 'users.new_user_modal_placeholder': 'UI Flowbite placeholder. Connecter à une route backend si nécessaire.',
|
'users.new_user_modal_title': 'Nouvel utilisateur', 'users.new_user_modal_placeholder': 'UI Flowbite placeholder. Connecter à une route backend si nécessaire.',
|
||||||
@@ -207,22 +207,60 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function reinitFlowbiteComponents(target) {
|
function initNavbarComponents(root) {
|
||||||
if (typeof window.initDropdowns === 'function') window.initDropdowns();
|
(root || document).querySelectorAll('[data-collapse-toggle]').forEach(function (button) {
|
||||||
if (typeof window.initModals === 'function') {
|
if (button.dataset.tcBound === '1') return;
|
||||||
if (!target || target.id === 'usersTableContainer') {
|
button.dataset.tcBound = '1';
|
||||||
window.initModals();
|
var targetId = button.getAttribute('data-collapse-toggle');
|
||||||
}
|
var target = document.getElementById(targetId);
|
||||||
|
if (!target) return;
|
||||||
|
button.addEventListener('click', function () {
|
||||||
|
var isHidden = target.classList.contains('hidden');
|
||||||
|
target.classList.toggle('hidden', !isHidden);
|
||||||
|
button.setAttribute('aria-expanded', isHidden ? 'true' : 'false');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var userButton = document.getElementById('user-menu-button');
|
||||||
|
var userDropdown = document.getElementById('user-dropdown');
|
||||||
|
if (userButton && userDropdown && userButton.dataset.tcBound !== '1') {
|
||||||
|
userButton.dataset.tcBound = '1';
|
||||||
|
userButton.addEventListener('click', function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
var isHidden = userDropdown.classList.contains('hidden');
|
||||||
|
userDropdown.classList.toggle('hidden', !isHidden);
|
||||||
|
userButton.setAttribute('aria-expanded', isHidden ? 'true' : 'false');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!window.__tcNavbarDocBound) {
|
||||||
|
window.__tcNavbarDocBound = true;
|
||||||
|
document.addEventListener('click', function (event) {
|
||||||
|
var btn = document.getElementById('user-menu-button');
|
||||||
|
var menu = document.getElementById('user-dropdown');
|
||||||
|
if (!btn || !menu || menu.classList.contains('hidden')) return;
|
||||||
|
if (btn.contains(event.target) || menu.contains(event.target)) return;
|
||||||
|
menu.classList.add('hidden');
|
||||||
|
btn.setAttribute('aria-expanded', 'false');
|
||||||
|
});
|
||||||
|
document.addEventListener('keydown', function (event) {
|
||||||
|
if (event.key !== 'Escape') return;
|
||||||
|
var btn = document.getElementById('user-menu-button');
|
||||||
|
var menu = document.getElementById('user-dropdown');
|
||||||
|
if (!btn || !menu) return;
|
||||||
|
menu.classList.add('hidden');
|
||||||
|
btn.setAttribute('aria-expanded', 'false');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reinitFlowbiteComponents();
|
initNavbarComponents(document);
|
||||||
applyTranslations(document);
|
applyTranslations(document);
|
||||||
if (typeof window.initThemeToggle === 'function') {
|
if (typeof window.initThemeToggle === 'function') {
|
||||||
window.initThemeToggle();
|
window.initThemeToggle();
|
||||||
}
|
}
|
||||||
document.body.addEventListener('htmx:afterSwap', function (evt) {
|
document.body.addEventListener('htmx:afterSwap', function (evt) {
|
||||||
reinitFlowbiteComponents(evt.target || null);
|
initNavbarComponents(evt.target || document);
|
||||||
applyTranslations(evt.target || document);
|
applyTranslations(evt.target || document);
|
||||||
if (typeof window.initThemeToggle === 'function') {
|
if (typeof window.initThemeToggle === 'function') {
|
||||||
window.initThemeToggle();
|
window.initThemeToggle();
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
<ul class="mt-4 flex flex-col gap-2 rounded-lg border border-gray-100 bg-gray-50 p-4 text-sm font-medium md:mt-0 md:flex-row md:items-center md:gap-1 md:border-0 md:bg-transparent md:p-0 dark:border-gray-700 dark:bg-gray-800 md:dark:bg-transparent">
|
<ul class="mt-4 flex flex-col gap-2 rounded-lg border border-gray-100 bg-gray-50 p-4 text-sm font-medium md:mt-0 md:flex-row md:items-center md:gap-1 md:border-0 md:bg-transparent md:p-0 dark:border-gray-700 dark:bg-gray-800 md:dark:bg-transparent">
|
||||||
{{if eq .NavSection "home"}}
|
{{if eq .NavSection "home"}}
|
||||||
{{if .CurrentUser}}
|
{{if .CurrentUser}}
|
||||||
<li><a href="/welcome" class="block rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700">Welcome</a></li>
|
|
||||||
{{else}}
|
{{else}}
|
||||||
<li><a href="/login" class="block rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700" data-i18n="nav.login">Login</a></li>
|
<li><a href="/login" class="block rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700" data-i18n="nav.login">Login</a></li>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -53,6 +52,10 @@
|
|||||||
<span class="block truncate text-sm text-gray-500 dark:text-gray-400">{{.CurrentUser.Email}}</span>
|
<span class="block truncate text-sm text-gray-500 dark:text-gray-400">{{.CurrentUser.Email}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="py-2">
|
<div class="py-2">
|
||||||
|
<a href="/private" class="block w-full rounded-lg px-2 py-2 text-left text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700">Private</a>
|
||||||
|
{{if eq .CurrentUser.Role "admin"}}
|
||||||
|
<a href="/admin" class="block w-full rounded-lg px-2 py-2 text-left text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700" data-i18n="nav.admin">Admin</a>
|
||||||
|
{{end}}
|
||||||
<form action="/logout" method="post" class="px-2">
|
<form action="/logout" method="post" class="px-2">
|
||||||
<button type="submit" class="block w-full rounded-lg px-2 py-2 text-left text-sm text-red-700 hover:bg-red-50 dark:text-red-300 dark:hover:bg-red-900/40" data-i18n="nav.logout">Logout</button>
|
<button type="submit" class="block w-full rounded-lg px-2 py-2 text-left text-sm text-red-700 hover:bg-red-50 dark:text-red-300 dark:hover:bg-red-900/40" data-i18n="nav.logout">Logout</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||