tailwind c ss
This commit is contained in:
parent
81245535b3
commit
0cd6ce05cd
10
Makefile
10
Makefile
|
|
@ -1,14 +1,20 @@
|
||||||
.PHONY: dev ui-build ui-dev test db-reset fmt
|
.PHONY: dev ui-build ui-dev css-build css-dev test db-reset fmt
|
||||||
|
|
||||||
dev:
|
dev:
|
||||||
go run ./cmd/server
|
go run ./cmd/server
|
||||||
|
|
||||||
ui-build:
|
ui-build:
|
||||||
cd ui-kit && npm i && npm run build
|
cd ui-kit && npm i && npm run build && npm run css:build
|
||||||
|
|
||||||
ui-dev:
|
ui-dev:
|
||||||
cd ui-kit && npm i && npm run dev
|
cd ui-kit && npm i && npm run dev
|
||||||
|
|
||||||
|
css-build:
|
||||||
|
cd ui-kit && npm i && npm run css:build
|
||||||
|
|
||||||
|
css-dev:
|
||||||
|
cd ui-kit && npm i && npm run css:dev
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test ./...
|
go test ./...
|
||||||
|
|
||||||
|
|
|
||||||
32
README.md
32
README.md
|
|
@ -6,6 +6,8 @@ Boilerplate GoFiber MVC + HTMX + Svelte Custom Elements + GORM, con auth server-
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
|
make css-build
|
||||||
|
make ui-build
|
||||||
make dev
|
make dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -35,22 +37,26 @@ DB_PG_DSN=postgres://trustcontact:trustcontact@localhost:5432/trustcontact?sslmo
|
||||||
|
|
||||||
`DB_POSTGRES_DSN` è comunque supportato.
|
`DB_POSTGRES_DSN` è comunque supportato.
|
||||||
|
|
||||||
## UI Kit Build
|
## Tailwind + UI Kit
|
||||||
|
|
||||||
|
Tailwind (template server-rendered) compila in `web/static/css/app.css`.
|
||||||
|
|
||||||
|
UI kit (Svelte custom elements) compila in `web/static/ui`.
|
||||||
|
|
||||||
|
Comandi:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make ui-build
|
make css-build # build tailwind
|
||||||
|
make css-dev # watch tailwind
|
||||||
|
make ui-build # build ui-kit + css tailwind
|
||||||
|
make ui-dev # vite dev server ui-kit
|
||||||
```
|
```
|
||||||
|
|
||||||
Per sviluppo UI:
|
Layout include:
|
||||||
|
|
||||||
```bash
|
- `/static/css/app.css?v={{.BuildHash}}`
|
||||||
make ui-dev
|
- `/static/ui/ui.css?v={{.BuildHash}}`
|
||||||
```
|
- `/static/ui/ui.esm.js?v={{.BuildHash}}`
|
||||||
|
|
||||||
Output build in `web/static/ui`:
|
|
||||||
|
|
||||||
- `ui.esm.js`
|
|
||||||
- `ui.css`
|
|
||||||
|
|
||||||
## Template Directories
|
## Template Directories
|
||||||
|
|
||||||
|
|
@ -65,8 +71,10 @@ In `develop`, le email vengono salvate in `./data/emails`.
|
||||||
## Make Targets
|
## Make Targets
|
||||||
|
|
||||||
- `make dev` -> `go run ./cmd/server`
|
- `make dev` -> `go run ./cmd/server`
|
||||||
- `make ui-build` -> install + build ui-kit
|
- `make ui-build` -> install + build ui-kit + build css tailwind
|
||||||
- `make ui-dev` -> watch UI con Vite
|
- `make ui-dev` -> watch UI con Vite
|
||||||
|
- `make css-build` -> build Tailwind CSS
|
||||||
|
- `make css-dev` -> watch Tailwind CSS
|
||||||
- `make test` -> `go test ./...`
|
- `make test` -> `go test ./...`
|
||||||
- `make db-reset` -> reset DB sqlite locale (`./data/app.db` / `./data/app.sqlite3`)
|
- `make db-reset` -> reset DB sqlite locale (`./data/app.db` / `./data/app.sqlite3`)
|
||||||
- `make fmt` -> `gofmt` su `cmd/` e `internal/`
|
- `make fmt` -> `gofmt` su `cmd/` e `internal/`
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -6,11 +6,16 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"css:build": "tailwindcss -c tailwind.config.cjs -i ./styles/tailwind.css -o ../web/static/css/app.css --minify",
|
||||||
|
"css:dev": "tailwindcss -c tailwind.config.cjs -i ./styles/tailwind.css -o ../web/static/css/app.css --watch"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"postcss": "^8.4.47",
|
||||||
"svelte": "^5.0.0",
|
"svelte": "^5.0.0",
|
||||||
|
"tailwindcss": "^3.4.13",
|
||||||
"typescript": "^5.6.0",
|
"typescript": "^5.6.0",
|
||||||
"vite": "^5.4.0"
|
"vite": "^5.4.0"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
body {
|
||||||
|
@apply m-0 bg-slate-100 text-slate-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
@apply text-slate-800;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.row {
|
||||||
|
@apply flex flex-wrap gap-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.muted {
|
||||||
|
@apply text-sm text-slate-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
@apply rounded-lg bg-slate-900 px-4 py-2 text-white hover:bg-slate-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-base {
|
||||||
|
@apply rounded-lg border border-slate-300 px-3 py-2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
'./src/**/*.{svelte,ts,js}',
|
||||||
|
'../web/templates/**/*.html'
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {}
|
||||||
|
},
|
||||||
|
plugins: []
|
||||||
|
};
|
||||||
|
|
@ -16,8 +16,17 @@ export default defineConfig({
|
||||||
lib: {
|
lib: {
|
||||||
entry: 'src/index.ts',
|
entry: 'src/index.ts',
|
||||||
formats: ['es'],
|
formats: ['es'],
|
||||||
fileName: () => 'ui.esm.js',
|
fileName: () => 'ui.esm.js'
|
||||||
cssFileName: 'ui'
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
assetFileNames: (assetInfo) => {
|
||||||
|
if (assetInfo.name === 'style.css') {
|
||||||
|
return 'ui.css';
|
||||||
|
}
|
||||||
|
return '[name][extname]';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
/* Generated by: cd ui-kit && npm run css:build */
|
||||||
|
/* Placeholder committed for first run. */
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
:root{--ui-bg: #ffffff;--ui-fg: #111827;--ui-muted: #6b7280;--ui-border: #d1d5db;--ui-overlay: rgba(17, 24, 39, .56);--ui-panel: #ffffff;--ui-radius: 10px;--ui-shadow: 0 10px 35px rgba(15, 23, 42, .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)}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,8 +1,10 @@
|
||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
<h1>Admin Dashboard</h1>
|
<div class="space-y-3">
|
||||||
<p class="muted">Area amministrazione.</p>
|
<h1 class="text-2xl font-semibold">Admin Dashboard</h1>
|
||||||
<div class="row">
|
<p class="muted">Area amministrazione.</p>
|
||||||
<a href="/admin/users">Gestione utenti</a>
|
<div class="row">
|
||||||
<a href="/users">Vista utenti (private)</a>
|
<a href="/admin/users" class="rounded-lg border border-slate-300 px-4 py-2 hover:bg-slate-50">Gestione utenti</a>
|
||||||
|
<a href="/users" class="rounded-lg border border-slate-300 px-4 py-2 hover:bg-slate-50">Vista utenti (private)</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
||||||
|
|
@ -1,53 +1,59 @@
|
||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
<h1>Admin - Users</h1>
|
<div class="space-y-4">
|
||||||
<p class="muted">Elenco utenti server-rendered.</p>
|
<div>
|
||||||
|
<h1 class="text-2xl font-semibold">Admin - Users</h1>
|
||||||
|
<p class="muted">Elenco utenti server-rendered.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form class="row" method="get" action="/admin/users">
|
<form class="row items-center" method="get" action="/admin/users">
|
||||||
<input type="text" name="q" placeholder="Cerca nome o email" value="{{.PageData.Q}}">
|
<input class="input-base" type="text" name="q" placeholder="Cerca nome o email" value="{{.PageData.Q}}">
|
||||||
<select name="sort" style="padding:10px;border:1px solid #d1d5db;border-radius:8px;">
|
<select class="input-base" name="sort">
|
||||||
<option value="id" {{if eq .PageData.Sort "id"}}selected{{end}}>ID</option>
|
<option value="id" {{if eq .PageData.Sort "id"}}selected{{end}}>ID</option>
|
||||||
<option value="name" {{if eq .PageData.Sort "name"}}selected{{end}}>Name</option>
|
<option value="name" {{if eq .PageData.Sort "name"}}selected{{end}}>Name</option>
|
||||||
<option value="email" {{if eq .PageData.Sort "email"}}selected{{end}}>Email</option>
|
<option value="email" {{if eq .PageData.Sort "email"}}selected{{end}}>Email</option>
|
||||||
</select>
|
</select>
|
||||||
<select name="dir" style="padding:10px;border:1px solid #d1d5db;border-radius:8px;">
|
<select class="input-base" name="dir">
|
||||||
<option value="asc" {{if eq .PageData.Dir "asc"}}selected{{end}}>ASC</option>
|
<option value="asc" {{if eq .PageData.Dir "asc"}}selected{{end}}>ASC</option>
|
||||||
<option value="desc" {{if eq .PageData.Dir "desc"}}selected{{end}}>DESC</option>
|
<option value="desc" {{if eq .PageData.Dir "desc"}}selected{{end}}>DESC</option>
|
||||||
</select>
|
</select>
|
||||||
<input type="number" name="pageSize" min="1" max="100" value="{{.PageData.PageSize}}" style="max-width:120px;">
|
<input class="input-base w-28" type="number" name="pageSize" min="1" max="100" value="{{.PageData.PageSize}}">
|
||||||
<input type="hidden" name="page" value="1">
|
<input type="hidden" name="page" value="1">
|
||||||
<button type="submit">Filtra</button>
|
<button class="btn-primary" type="submit">Filtra</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<table style="width:100%;border-collapse:collapse;margin-top:16px;">
|
<div class="overflow-x-auto">
|
||||||
<thead>
|
<table class="w-full border-collapse">
|
||||||
<tr>
|
<thead>
|
||||||
<th style="text-align:left;border-bottom:1px solid #e5e7eb;padding:8px;">ID</th>
|
<tr class="border-b border-slate-200 text-left">
|
||||||
<th style="text-align:left;border-bottom:1px solid #e5e7eb;padding:8px;">Name</th>
|
<th class="px-2 py-2">ID</th>
|
||||||
<th style="text-align:left;border-bottom:1px solid #e5e7eb;padding:8px;">Email</th>
|
<th class="px-2 py-2">Name</th>
|
||||||
<th style="text-align:left;border-bottom:1px solid #e5e7eb;padding:8px;">Role</th>
|
<th class="px-2 py-2">Email</th>
|
||||||
<th style="text-align:left;border-bottom:1px solid #e5e7eb;padding:8px;">Verified</th>
|
<th class="px-2 py-2">Role</th>
|
||||||
</tr>
|
<th class="px-2 py-2">Verified</th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody>
|
</thead>
|
||||||
{{range .PageData.Users}}
|
<tbody>
|
||||||
<tr>
|
{{range .PageData.Users}}
|
||||||
<td style="border-bottom:1px solid #f1f5f9;padding:8px;">{{.ID}}</td>
|
<tr class="border-b border-slate-100">
|
||||||
<td style="border-bottom:1px solid #f1f5f9;padding:8px;">{{if .Name}}{{.Name}}{{else}}-{{end}}</td>
|
<td class="px-2 py-2">{{.ID}}</td>
|
||||||
<td style="border-bottom:1px solid #f1f5f9;padding:8px;">{{.Email}}</td>
|
<td class="px-2 py-2">{{if .Name}}{{.Name}}{{else}}-{{end}}</td>
|
||||||
<td style="border-bottom:1px solid #f1f5f9;padding:8px;">{{.Role}}</td>
|
<td class="px-2 py-2">{{.Email}}</td>
|
||||||
<td style="border-bottom:1px solid #f1f5f9;padding:8px;">{{if .EmailVerified}}yes{{else}}no{{end}}</td>
|
<td class="px-2 py-2">{{.Role}}</td>
|
||||||
</tr>
|
<td class="px-2 py-2">{{if .EmailVerified}}yes{{else}}no{{end}}</td>
|
||||||
{{else}}
|
</tr>
|
||||||
<tr><td colspan="5" style="padding:12px;">Nessun utente trovato.</td></tr>
|
{{else}}
|
||||||
{{end}}
|
<tr><td colspan="5" class="px-2 py-3">Nessun utente trovato.</td></tr>
|
||||||
</tbody>
|
{{end}}
|
||||||
</table>
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row" style="margin-top:12px;align-items:center;justify-content:space-between;">
|
<div class="row items-center justify-between">
|
||||||
<div class="muted">Totale: {{.PageData.Total}} utenti. Pagina {{.PageData.Page}}{{if gt .PageData.TotalPages 0}} / {{.PageData.TotalPages}}{{end}}</div>
|
<div class="muted">Totale: {{.PageData.Total}} utenti. Pagina {{.PageData.Page}}{{if gt .PageData.TotalPages 0}} / {{.PageData.TotalPages}}{{end}}</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<a {{if not .PageData.HasPrev}}style="pointer-events:none;opacity:.5;"{{end}} href="/admin/users?q={{.PageData.Q}}&sort={{.PageData.Sort}}&dir={{.PageData.Dir}}&page={{.PageData.PrevPage}}&pageSize={{.PageData.PageSize}}">Prev</a>
|
<a class="rounded border border-slate-300 px-3 py-1.5 hover:bg-slate-50 {{if not .PageData.HasPrev}}pointer-events-none opacity-50{{end}}" href="/admin/users?q={{.PageData.Q}}&sort={{.PageData.Sort}}&dir={{.PageData.Dir}}&page={{.PageData.PrevPage}}&pageSize={{.PageData.PageSize}}">Prev</a>
|
||||||
<a {{if not .PageData.HasNext}}style="pointer-events:none;opacity:.5;"{{end}} href="/admin/users?q={{.PageData.Q}}&sort={{.PageData.Sort}}&dir={{.PageData.Dir}}&page={{.PageData.NextPage}}&pageSize={{.PageData.PageSize}}">Next</a>
|
<a class="rounded border border-slate-300 px-3 py-1.5 hover:bg-slate-50 {{if not .PageData.HasNext}}pointer-events-none opacity-50{{end}}" href="/admin/users?q={{.PageData.Q}}&sort={{.PageData.Sort}}&dir={{.PageData.Dir}}&page={{.PageData.NextPage}}&pageSize={{.PageData.PageSize}}">Next</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
||||||
|
|
@ -4,40 +4,28 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{{.Title}}</title>
|
<title>{{.Title}}</title>
|
||||||
<style>
|
<link rel="stylesheet" href="/static/css/app.css?v={{.BuildHash}}">
|
||||||
body { font-family: system-ui, sans-serif; margin: 0; background: #f5f7fb; color: #1f2937; }
|
|
||||||
nav { background: #111827; color: #fff; padding: 12px 16px; display: flex; gap: 12px; }
|
|
||||||
nav a { color: #e5e7eb; text-decoration: none; }
|
|
||||||
nav a.active { color: #fff; font-weight: 600; }
|
|
||||||
.container { max-width: 920px; margin: 20px auto; padding: 0 16px; }
|
|
||||||
.card { background: #fff; border-radius: 10px; padding: 20px; }
|
|
||||||
form { display: grid; gap: 10px; max-width: 420px; }
|
|
||||||
input { padding: 10px; border: 1px solid #d1d5db; border-radius: 8px; }
|
|
||||||
button { padding: 10px 14px; border: 0; border-radius: 8px; background: #111827; color: #fff; cursor: pointer; }
|
|
||||||
.muted { color: #6b7280; font-size: 0.95rem; }
|
|
||||||
.row { display: flex; gap: 10px; flex-wrap: wrap; }
|
|
||||||
</style>
|
|
||||||
<link rel="stylesheet" href="/static/ui/ui.css?v={{.BuildHash}}">
|
<link rel="stylesheet" href="/static/ui/ui.css?v={{.BuildHash}}">
|
||||||
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
|
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
|
||||||
<script type="module" src="/static/ui/ui.esm.js?v={{.BuildHash}}"></script>
|
<script type="module" src="/static/ui/ui.esm.js?v={{.BuildHash}}"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav>
|
<nav class="flex gap-3 bg-slate-900 px-4 py-3 text-white">
|
||||||
<a href="/" class="{{if eq .NavSection "public"}}active{{end}}">Public</a>
|
<a href="/" class="text-slate-200 hover:text-white {{if eq .NavSection "public"}}font-semibold text-white{{end}}">Public</a>
|
||||||
<a href="/private" class="{{if eq .NavSection "private"}}active{{end}}">Private</a>
|
<a href="/private" class="text-slate-200 hover:text-white {{if eq .NavSection "private"}}font-semibold text-white{{end}}">Private</a>
|
||||||
{{if and .CurrentUser (eq .CurrentUser.Role "admin")}}
|
{{if and .CurrentUser (eq .CurrentUser.Role "admin")}}
|
||||||
<a href="/admin" class="{{if eq .NavSection "admin"}}active{{end}}">Admin</a>
|
<a href="/admin" class="text-slate-200 hover:text-white {{if eq .NavSection "admin"}}font-semibold text-white{{end}}">Admin</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if .CurrentUser}}
|
{{if .CurrentUser}}
|
||||||
<form action="/logout" method="post" style="margin-left:auto;">
|
<form action="/logout" method="post" class="ml-auto">
|
||||||
<button type="submit">Logout</button>
|
<button type="submit" class="btn-primary">Logout</button>
|
||||||
</form>
|
</form>
|
||||||
{{end}}
|
{{end}}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="container">
|
<div class="mx-auto my-5 max-w-5xl px-4">
|
||||||
{{template "_flash.html" .}}
|
{{template "_flash.html" .}}
|
||||||
<div class="card">
|
<div class="rounded-xl bg-white p-5 shadow-sm">
|
||||||
{{template "content" .}}
|
{{template "content" .}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue