diff --git a/.gitignore b/.gitignore index e5d4875..7099806 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,9 @@ tmp/ .idea/ .vscode/ +# JS deps +ui-kit/node_modules/ + # Dev data and local state /data/ *.sqlite diff --git a/README.md b/README.md index 8ff548c..d850813 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,22 @@ Boilerplate riusabile per: In ambiente `develop`, le email vengono salvate in `./data/emails` (sink locale). +## UI Kit (Vite + Svelte CE) + +Comandi: + +```bash +cd ui-kit +npm install +npm run dev +npm run build +``` + +La build scrive direttamente in `web/static/ui`: + +- `ui.esm.js` +- `ui.css` + ## Struttura iniziale ```text @@ -32,6 +48,7 @@ In ambiente `develop`, le email vengono salvate in `./data/emails` (sink locale) │ ├── models/ │ ├── repo/ │ └── services/ +├── ui-kit/ ├── web/ │ ├── emails/ │ │ └── templates/ @@ -43,29 +60,5 @@ In ambiente `develop`, le email vengono salvate in `./data/emails` (sink locale) │ ├── admin/ │ ├── private/ │ └── public/ -├── ui-kit/ └── data/ # solo sviluppo locale ``` - -## TODO Checklist - -- [ ] Definire bootstrap server in `cmd/server` (entrypoint + lifecycle). -- [ ] Configurare loader config (`env`, `flags`) in `internal/config`. -- [ ] Impostare `internal/db` con supporto SQLite (dev) e Postgres (prod). -- [ ] Definire modelli base GORM in `internal/models` (User, Role, Session, ecc.). -- [ ] Implementare repository layer in `internal/repo`. -- [ ] Implementare service layer in `internal/services`. -- [ ] Implementare controller layer in `internal/controllers`. -- [ ] Configurare router HTTP in `internal/http` (gruppi public/private/admin). -- [ ] Aggiungere middleware comuni in `internal/middleware` (logging, recovery, auth, cors). -- [ ] Implementare auth in `internal/auth` (login/logout/session o token). -- [ ] Implementare RBAC con ruolo `admin`. -- [ ] Configurare mailer + email sink in `internal/mailer`. -- [ ] Definire template rendering per `web/templates/public`, `web/templates/private`, `web/templates/admin`. -- [ ] Preparare template email in `web/emails/templates`. -- [ ] Definire static assets pipeline e convenzioni in `web/static`. -- [ ] Impostare `ui-kit` con Svelte Custom Elements e output in `web/static/ui`. -- [ ] Definire integrazione HTMX lato template/partials. -- [ ] Aggiungere migrazioni DB iniziali e seed minimo. -- [ ] Aggiungere test base (unit + integrazione) per router/auth/repo. -- [ ] Aggiungere script Makefile/task runner per setup e run locale. diff --git a/codex-prompt/prompt-9.txt b/codex-prompt/prompt-9.txt new file mode 100644 index 0000000..b3a2ee2 --- /dev/null +++ b/codex-prompt/prompt-9.txt @@ -0,0 +1,33 @@ +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: + - + - 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 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. \ No newline at end of file diff --git a/internal/http/router.go b/internal/http/router.go index badc0ec..5c22079 100644 --- a/internal/http/router.go +++ b/internal/http/router.go @@ -16,6 +16,10 @@ func RegisterRoutes(app *fiber.App, store *session.Store, database *gorm.DB, cfg app.Use(httpmw.SessionStoreMiddleware(store)) app.Use(httpmw.CurrentUserMiddleware(store, database)) app.Use(httpmw.ConsumeFlash()) + app.Use(func(c *fiber.Ctx) error { + httpmw.SetTemplateData(c, "BuildHash", cfg.BuildHash) + return c.Next() + }) authService, err := services.NewAuthService(database, cfg) if err != nil { diff --git a/ui-kit/index.html b/ui-kit/index.html new file mode 100644 index 0000000..b227c90 --- /dev/null +++ b/ui-kit/index.html @@ -0,0 +1,15 @@ + + + + + + UI Kit Dev + + + UI Kit Dev + + + + + + diff --git a/ui-kit/package.json b/ui-kit/package.json new file mode 100644 index 0000000..70f0b85 --- /dev/null +++ b/ui-kit/package.json @@ -0,0 +1,17 @@ +{ + "name": "trustcontact-ui-kit", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^4.0.0", + "svelte": "^5.0.0", + "typescript": "^5.6.0", + "vite": "^5.4.0" + } +} diff --git a/ui-kit/src/base.css b/ui-kit/src/base.css new file mode 100644 index 0000000..ef95d00 --- /dev/null +++ b/ui-kit/src/base.css @@ -0,0 +1,19 @@ +:root { + --ui-bg: #ffffff; + --ui-fg: #111827; + --ui-muted: #6b7280; + --ui-border: #d1d5db; + --ui-overlay: rgba(17, 24, 39, 0.56); + --ui-panel: #ffffff; + --ui-radius: 10px; + --ui-shadow: 0 10px 35px rgba(15, 23, 42, 0.2); + --ui-primary: #111827; + --ui-primary-contrast: #ffffff; +} + +ui-modal, +ui-drop-down, +ui-data-table-shell { + font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; + color: var(--ui-fg); +} diff --git a/ui-kit/src/components/UiDataTableShell.svelte b/ui-kit/src/components/UiDataTableShell.svelte new file mode 100644 index 0000000..5837ec8 --- /dev/null +++ b/ui-kit/src/components/UiDataTableShell.svelte @@ -0,0 +1,74 @@ + + + + + submit(1)}> + e.key === 'Enter' && submit(1)} + /> + Search + + + diff --git a/ui-kit/src/components/UiDropDown.svelte b/ui-kit/src/components/UiDropDown.svelte new file mode 100644 index 0000000..2d722f2 --- /dev/null +++ b/ui-kit/src/components/UiDropDown.svelte @@ -0,0 +1,153 @@ + + + + + + (open = !open)}> + {selectedLabel()} + ▾ + + + {#if open} + + {#if options.length === 0} + No options + {:else} + {#each options as opt} + selectOption(opt.value)}> + {opt.label} + + {/each} + {/if} + + {/if} + + + diff --git a/ui-kit/src/components/UiModal.svelte b/ui-kit/src/components/UiModal.svelte new file mode 100644 index 0000000..f2b2f0f --- /dev/null +++ b/ui-kit/src/components/UiModal.svelte @@ -0,0 +1,142 @@ + + + + +{#if open} + + + + {title} + × + + + + + + +{/if} + + + + diff --git a/ui-kit/src/index.ts b/ui-kit/src/index.ts new file mode 100644 index 0000000..8385eec --- /dev/null +++ b/ui-kit/src/index.ts @@ -0,0 +1,6 @@ +import './base.css'; +import './components/UiModal.svelte'; +import './components/UiDropDown.svelte'; +import './components/UiDataTableShell.svelte'; + +export {}; diff --git a/ui-kit/tsconfig.json b/ui-kit/tsconfig.json new file mode 100644 index 0000000..d7bb49f --- /dev/null +++ b/ui-kit/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "skipLibCheck": true, + "types": ["vite/client"] + }, + "include": ["src/**/*", "vite.config.ts"] +} diff --git a/ui-kit/vite.config.ts b/ui-kit/vite.config.ts new file mode 100644 index 0000000..56f567f --- /dev/null +++ b/ui-kit/vite.config.ts @@ -0,0 +1,23 @@ +import { defineConfig } from 'vite'; +import { svelte } from '@sveltejs/vite-plugin-svelte'; + +export default defineConfig({ + plugins: [ + svelte({ + compilerOptions: { + customElement: true + } + }) + ], + build: { + outDir: '../web/static/ui', + emptyOutDir: true, + cssCodeSplit: false, + lib: { + entry: 'src/index.ts', + formats: ['es'], + fileName: () => 'ui.esm.js', + cssFileName: 'ui' + } + } +}); diff --git a/web/templates/layout.html b/web/templates/layout.html index bb75d0b..294c76d 100644 --- a/web/templates/layout.html +++ b/web/templates/layout.html @@ -17,7 +17,9 @@ .muted { color: #6b7280; font-size: 0.95rem; } .row { display: flex; gap: 10px; flex-wrap: wrap; } + +