Compare commits
1 commit
master
...
ui-compone
Author | SHA1 | Date | |
---|---|---|---|
|
4cf5175548 |
39 changed files with 1224 additions and 351 deletions
21
LICENSE
21
LICENSE
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 @anthdm and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
2
Makefile
2
Makefile
|
@ -1,2 +0,0 @@
|
|||
test:
|
||||
@go test -v ./...
|
|
@ -6,23 +6,21 @@ endif
|
|||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
MAIN_PATH = /tmp/bin/main.exe
|
||||
SYNC_ASSETS_COMMAND = @go run github.com/makiuchi-d/arelo@v1.13.1 \
|
||||
SYNC_ASSETS_COMMAND = go run github.com/makiuchi-d/arelo@v1.13.1 \
|
||||
--target "./public" \
|
||||
--pattern "**/*.js" \
|
||||
--pattern "**/*.css" \
|
||||
--delay "100ms" \
|
||||
--templ generate --notify-proxy
|
||||
-- templ generate --notify-proxy
|
||||
else
|
||||
MAIN_PATH = tmp/bin/main
|
||||
SYNC_ASSETS_COMMAND = @go run github.com/cosmtrek/air@v1.51.0 \
|
||||
SYNC_ASSETS_COMMAND = go run github.com/cosmtrek/air@v1.51.0 \
|
||||
--build.cmd "templ generate --notify-proxy" \
|
||||
--build.bin "true" \
|
||||
--build.delay "100" \
|
||||
--build.exclude_dir "" \
|
||||
--build.include_dir "public" \
|
||||
--build.include_ext "js,css" \
|
||||
--screen.clear_on_rebuild true \
|
||||
--log.main_only true
|
||||
--build.include_ext "js,css"
|
||||
endif
|
||||
|
||||
# run templ generation in watch mode to detect all .templ files and
|
||||
|
@ -38,17 +36,15 @@ server:
|
|||
--build.exclude_dir "node_modules" \
|
||||
--build.include_ext "go" \
|
||||
--build.stop_on_error "false" \
|
||||
--misc.clean_on_exit true \
|
||||
--screen.clear_on_rebuild true \
|
||||
--log.main_only true
|
||||
--misc.clean_on_exit true
|
||||
|
||||
# run tailwindcss to generate the styles.css bundle in watch mode.
|
||||
watch-assets:
|
||||
@npx tailwindcss -i app/assets/app.css -o ./public/assets/styles.css --watch
|
||||
npx tailwindcss -i app/assets/app.css -o ./public/assets/styles.css --watch
|
||||
|
||||
# run esbuild to generate the index.js bundle in watch mode.
|
||||
watch-esbuild:
|
||||
@npx esbuild app/assets/index.js --bundle --outdir=public/assets --watch
|
||||
npx esbuild app/assets/index.js --bundle --outdir=public/assets --watch
|
||||
|
||||
# watch for any js or css change in the assets/ folder, then reload the browser via templ proxy.
|
||||
sync_assets:
|
||||
|
@ -67,7 +63,7 @@ build:
|
|||
@echo "compiled you application with all its assets to a single binary => bin/app_prod"
|
||||
|
||||
db-status:
|
||||
@GOOSE_DRIVER=$(DB_DRIVER) GOOSE_DBSTRING=$(DB_NAME) go run github.com/pressly/goose/v3/cmd/goose@latest -dir=$(MIGRATION_DIR) status
|
||||
@GOOSE_DRIVER=$(DB_DRIVER) GOOSE_DBSTRING=$(DB_NAME) go run github.com/pressly/goose/v3/cmd/goose@latest status
|
||||
|
||||
db-reset:
|
||||
@GOOSE_DRIVER=$(DB_DRIVER) GOOSE_DBSTRING=$(DB_NAME) go run github.com/pressly/goose/v3/cmd/goose@latest -dir=$(MIGRATION_DIR) reset
|
||||
|
@ -82,4 +78,4 @@ db-mig-create:
|
|||
@GOOSE_DRIVER=$(DB_DRIVER) GOOSE_DBSTRING=$(DB_NAME) go run github.com/pressly/goose/v3/cmd/goose@latest -dir=$(MIGRATION_DIR) create $(filter-out $@,$(MAKECMDGOALS)) sql
|
||||
|
||||
db-seed:
|
||||
@go run cmd/scripts/seed/main.go
|
||||
@go run cmd/scripts/seed/main.go
|
|
@ -1,3 +1,3 @@
|
|||
package conf
|
||||
|
||||
// Here goes your application config
|
||||
// Application config
|
||||
|
|
|
@ -5,25 +5,21 @@ import (
|
|||
"os"
|
||||
|
||||
"github.com/anthdm/superkit/db"
|
||||
"github.com/anthdm/superkit/kit"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect/sqlitedialect"
|
||||
"github.com/uptrace/bun/extra/bundebug"
|
||||
)
|
||||
|
||||
// By default this is a pre-configured Gorm DB instance.
|
||||
// Change this type based on the database package of your likings.
|
||||
var dbInstance *gorm.DB
|
||||
|
||||
// Get returns the instantiated DB instance.
|
||||
func Get() *gorm.DB {
|
||||
return dbInstance
|
||||
}
|
||||
// I could not came up with a better naming for this.
|
||||
// Ideally, app should export a global variable called "DB"
|
||||
// but this will cause imports cycles for plugins.
|
||||
var Query *bun.DB
|
||||
|
||||
func init() {
|
||||
// Create a default *sql.DB exposed by the superkit/db package
|
||||
// based on the given configuration.
|
||||
config := db.Config{
|
||||
Driver: os.Getenv("DB_DRIVER"),
|
||||
Name: os.Getenv("DB_NAME"),
|
||||
|
@ -31,30 +27,12 @@ func init() {
|
|||
User: os.Getenv("DB_USER"),
|
||||
Host: os.Getenv("DB_HOST"),
|
||||
}
|
||||
dbinst, err := db.NewSQL(config)
|
||||
db, err := db.New(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Based on the driver create the corresponding DB instance.
|
||||
// By default, the SuperKit boilerplate comes with a pre-configured
|
||||
// ORM called Gorm. https://gorm.io.
|
||||
//
|
||||
// You can change this to any other DB interaction tool
|
||||
// of your liking. EG:
|
||||
// - uptrace bun -> https://bun.uptrace.dev
|
||||
// - SQLC -> https://github.com/sqlc-dev/sqlc
|
||||
// - gojet -> https://github.com/go-jet/jet
|
||||
switch config.Driver {
|
||||
case db.DriverSqlite3:
|
||||
dbInstance, err = gorm.Open(sqlite.New(sqlite.Config{
|
||||
Conn: dbinst,
|
||||
}))
|
||||
case db.DriverMysql:
|
||||
// ...
|
||||
default:
|
||||
log.Fatal("invalid driver:", config.Driver)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
Query = bun.NewDB(db, sqlitedialect.New())
|
||||
if kit.IsDevelopment() {
|
||||
Query.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true)))
|
||||
}
|
||||
}
|
||||
|
|
0
bootstrap/app/db/migrations/.keep
Normal file
0
bootstrap/app/db/migrations/.keep
Normal file
|
@ -5,10 +5,9 @@ create table if not exists users(
|
|||
password_hash text not null,
|
||||
first_name text not null,
|
||||
last_name text not null,
|
||||
email_verified_at datetime,
|
||||
created_at datetime not null,
|
||||
updated_at datetime not null,
|
||||
deleted_at datetime
|
||||
email_verified_at timestamp with time zone,
|
||||
created_at timestamp with time zone not null,
|
||||
updated_at timestamp with time zone not null
|
||||
);
|
||||
|
||||
-- +goose Down
|
||||
|
|
|
@ -5,10 +5,9 @@ create table if not exists sessions(
|
|||
user_id integer not null references users,
|
||||
ip_address text,
|
||||
user_agent text,
|
||||
expires_at datetime not null,
|
||||
created_at datetime not null,
|
||||
updated_at datetime not null,
|
||||
deleted_at datetime
|
||||
expires_at timestamp with time zone not null,
|
||||
last_login_at timestamp with time zone,
|
||||
created_at timestamp with time zone not null
|
||||
);
|
||||
|
||||
-- +goose Down
|
||||
|
|
11
bootstrap/app/handlers/showcase.go
Normal file
11
bootstrap/app/handlers/showcase.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"AABBCCDD/app/views/showcase"
|
||||
|
||||
"github.com/anthdm/superkit/kit"
|
||||
)
|
||||
|
||||
func HandleShowcaseIndex(kit *kit.Kit) error {
|
||||
return kit.Render(showcase.Index())
|
||||
}
|
|
@ -17,14 +17,14 @@ import (
|
|||
func InitializeMiddleware(router *chi.Mux) {
|
||||
router.Use(chimiddleware.Logger)
|
||||
router.Use(chimiddleware.Recoverer)
|
||||
router.Use(middleware.WithRequest)
|
||||
router.Use(middleware.WithRequestURL)
|
||||
}
|
||||
|
||||
// Define your routes in here
|
||||
func InitializeRoutes(router *chi.Mux) {
|
||||
// Authentication plugin
|
||||
//
|
||||
// By default the auth plugin is active, to disable the auth plugin
|
||||
// By default the auth plugin is active. To disable the auth plugin
|
||||
// you will need to pass your own handler in the `AuthFunc`` field
|
||||
// of the `kit.AuthenticationConfig`.
|
||||
// authConfig := kit.AuthenticationConfig{
|
||||
|
|
|
@ -2,7 +2,7 @@ package types
|
|||
|
||||
// AuthUser represents an user that might be authenticated.
|
||||
type AuthUser struct {
|
||||
ID uint
|
||||
ID int
|
||||
Email string
|
||||
LoggedIn bool
|
||||
}
|
||||
|
|
157
bootstrap/app/views/showcase/index.templ
Normal file
157
bootstrap/app/views/showcase/index.templ
Normal file
|
@ -0,0 +1,157 @@
|
|||
package showcase
|
||||
|
||||
import (
|
||||
"AABBCCDD/app/views/layouts"
|
||||
"AABBCCDD/ui/button"
|
||||
"AABBCCDD/ui/input"
|
||||
"AABBCCDD/ui/modal"
|
||||
"AABBCCDD/ui/card"
|
||||
"AABBCCDD/ui"
|
||||
"AABBCCDD/ui/table"
|
||||
)
|
||||
|
||||
templ Index() {
|
||||
@layouts.App() {
|
||||
<div class="flex flex-col gap-10 mt-10 py-8">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold">Buttons</h1>
|
||||
<div class="border-b pt-2 mb-6"></div>
|
||||
<div class="flex gap-4">
|
||||
<button { button.Primary()... }>primary</button>
|
||||
<button { button.Outline()... }>outline</button>
|
||||
<button { button.Secondary()... }>secondary</button>
|
||||
<button { button.Destructive()... }>destructive</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold">Forms</h1>
|
||||
<div class="border-b pt-2 mb-6"></div>
|
||||
<form class="flex flex-col gap-4 w-full max-w-xs">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-sm">Email</label>
|
||||
<input name="email" type="email" placeholder="enter your email" { input.Input()... }/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-sm">First Name</label>
|
||||
<input name="firstName" type="text" placeholder="first name" { input.Input()... }/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-sm">Last Name</label>
|
||||
<input name="lastName" type="text" placeholder="last name" { input.Input()... }/>
|
||||
</div>
|
||||
<button type="button" { button.Primary(ui.Class("w-fit"))... }>submit</button>
|
||||
</form>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold">Card</h1>
|
||||
<div class="border-b pt-2 mb-6"></div>
|
||||
@card.Card(ui.Class("max-w-md")) {
|
||||
@card.Header() {
|
||||
<h1 class="text-xl">User registration</h1>
|
||||
<h2 class="text-muted-foreground">Please register to get the latest updates</h2>
|
||||
}
|
||||
@card.Content() {
|
||||
<form class="flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="text-sm">Username</label>
|
||||
<input { input.Input()... }/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="text-sm">Email</label>
|
||||
<input { input.Input()... }/>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
@card.Footer() {
|
||||
<button { button.Primary()... }>Submit</button>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold">Table</h1>
|
||||
<div class="border-b pt-2 mb-6"></div>
|
||||
<div class="max-w-4xl">
|
||||
@table.Table() {
|
||||
@table.Header(ui.Class("text-left")) {
|
||||
<th { table.Th()... }>
|
||||
<div class="flex items-center">
|
||||
<input id="checkbox-id" type="checkbox" class="w-4 h-4 bg-transparent border-input rounded"/>
|
||||
</div>
|
||||
</th>
|
||||
<th { table.Th()... }>id</th>
|
||||
<th { table.Th()... }>email</th>
|
||||
<th { table.Th()... }>first name</th>
|
||||
<th { table.Th()... }>last name</th>
|
||||
<th { table.Th(ui.Class("text-right"))... }>action</th>
|
||||
}
|
||||
@table.Body() {
|
||||
<tr>
|
||||
<td { table.Td()... }>
|
||||
<div class="flex items-center">
|
||||
<input id="checkbox-id" type="checkbox" class="w-4 h-4 bg-transparent border-input rounded"/>
|
||||
</div>
|
||||
</td>
|
||||
<td { table.Td()... }>1</td>
|
||||
<td { table.Td()... }>foo@foo.com</td>
|
||||
<td { table.Td()... }>Anthony</td>
|
||||
<td { table.Td()... }>GG</td>
|
||||
<td { table.Td(ui.Class("text-right text-blue-500"))... }>view</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td { table.Td()... }>
|
||||
<div class="flex items-center">
|
||||
<input id="checkbox-id" type="checkbox" class="w-4 h-4 bg-transparent border-input rounded"/>
|
||||
</div>
|
||||
</td>
|
||||
<td { table.Td()... }>1</td>
|
||||
<td { table.Td()... }>foo@foo.com</td>
|
||||
<td { table.Td()... }>Anthony</td>
|
||||
<td { table.Td()... }>GG</td>
|
||||
<td { table.Td(ui.Class("text-right text-blue-500"))... }>view</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td { table.Td()... }>
|
||||
<div class="flex items-center">
|
||||
<input id="checkbox-id" type="checkbox" class="w-4 h-4 bg-transparent border-input rounded"/>
|
||||
</div>
|
||||
</td>
|
||||
<td { table.Td()... }>1</td>
|
||||
<td { table.Td()... }>foo@foo.com</td>
|
||||
<td { table.Td()... }>Anthony</td>
|
||||
<td { table.Td()... }>GG</td>
|
||||
<td { table.Td(ui.Class("text-right text-blue-500"))... }>view</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td { table.Td()... }>
|
||||
<div class="flex items-center">
|
||||
<input id="checkbox-id" type="checkbox" class="w-4 h-4 bg-transparent border-input rounded"/>
|
||||
</div>
|
||||
</td>
|
||||
<td { table.Td()... }>1</td>
|
||||
<td { table.Td()... }>foo@foo.com</td>
|
||||
<td { table.Td()... }>Anthony</td>
|
||||
<td { table.Td()... }>GG</td>
|
||||
<td { table.Td(ui.Class("text-right text-blue-500"))... }>view</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold">Modal</h1>
|
||||
<div class="border-b pt-2 mb-6"></div>
|
||||
@modal.Modal() {
|
||||
@modal.Header() {
|
||||
the header
|
||||
}
|
||||
@modal.Content() {
|
||||
the content
|
||||
}
|
||||
@modal.Trigger() {
|
||||
<div class="text-white">trigger me</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
|
@ -14,7 +14,10 @@ import (
|
|||
)
|
||||
|
||||
func main() {
|
||||
kit.Setup()
|
||||
if err := godotenv.Load(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
router := chi.NewMux()
|
||||
|
||||
app.InitializeMiddleware(router)
|
||||
|
|
|
@ -6,21 +6,28 @@ go 1.22.4
|
|||
// replace github.com/anthdm/superkit => ../
|
||||
|
||||
require (
|
||||
github.com/a-h/templ v0.2.731
|
||||
github.com/anthdm/superkit v0.0.0-20240622052611-30be5bb82e0d
|
||||
github.com/go-chi/chi/v5 v5.0.14
|
||||
github.com/a-h/templ v0.2.707
|
||||
github.com/anthdm/superkit v0.0.0-20240616155928-19996932bf4f
|
||||
github.com/go-chi/chi/v5 v5.0.12
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
github.com/uptrace/bun v1.2.1
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.2.1
|
||||
github.com/uptrace/bun/extra/bundebug v1.2.1
|
||||
golang.org/x/crypto v0.24.0
|
||||
gorm.io/driver/sqlite v1.5.6
|
||||
gorm.io/gorm v1.25.10
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/gorilla/sessions v1.3.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
)
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
github.com/a-h/templ v0.2.731 h1:yiv4C7whSUsa36y65O06DPr/U/j3+WGB0RmvLOoVFXc=
|
||||
github.com/a-h/templ v0.2.731/go.mod h1:IejA/ecDD0ul0dCvgCwp9t7bUZXVpGClEAdsqZQigi8=
|
||||
github.com/a-h/templ v0.2.707 h1:T1Gkd2ugbRglZ9rYw/VBchWOSZVKmetDbBkm4YubM7U=
|
||||
github.com/a-h/templ v0.2.707/go.mod h1:5cqsugkq9IerRNucNsI4DEamdHPsoGMQy99DzydLhM8=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-chi/chi/v5 v5.0.14 h1:PyEwo2Vudraa0x/Wl6eDRRW2NXBvekgfxyydcM0WGE0=
|
||||
github.com/go-chi/chi/v5 v5.0.14/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
||||
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
|
@ -18,21 +20,36 @@ github.com/gorilla/sessions v1.3.0 h1:XYlkq7KcpOB2ZhHBPv5WpjMIxrQosiZanfoy1HLZFz
|
|||
github.com/gorilla/sessions v1.3.0/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
github.com/uptrace/bun v1.2.1 h1:2ENAcfeCfaY5+2e7z5pXrzFKy3vS8VXvkCag6N2Yzfk=
|
||||
github.com/uptrace/bun v1.2.1/go.mod h1:cNg+pWBUMmJ8rHnETgf65CEvn3aIKErrwOD6IA8e+Ec=
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.2.1 h1:IprvkIKUjEjvt4VKpcmLpbMIucjrsmUPJOSlg19+a0Q=
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.2.1/go.mod h1:mMQf4NUpgY8bnOanxGmxNiHCdALOggS4cZ3v63a9D/o=
|
||||
github.com/uptrace/bun/extra/bundebug v1.2.1 h1:85MYpX3QESYI02YerKxUi1CD9mHuLrc2BXs1eOCtQus=
|
||||
github.com/uptrace/bun/extra/bundebug v1.2.1/go.mod h1:sfGKIi0HSGxsTC/sgIHGwpnYduHHYhdMeOIwurgSY+Y=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
|
||||
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
|
||||
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Add your packages aka custom libraries here.
|
|
@ -13,7 +13,6 @@ import (
|
|||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -41,9 +40,12 @@ func HandleLoginCreate(kit *kit.Kit) error {
|
|||
}
|
||||
|
||||
var user User
|
||||
err := db.Get().Find(&user, "email = ?", values.Email).Error
|
||||
err := db.Query.NewSelect().
|
||||
Model(&user).
|
||||
Where("user.email = ?", values.Email).
|
||||
Scan(kit.Request.Context())
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
if err == sql.ErrNoRows {
|
||||
errors.Add("credentials", "invalid credentials")
|
||||
return kit.Render(LoginForm(values, errors))
|
||||
}
|
||||
|
@ -57,7 +59,7 @@ func HandleLoginCreate(kit *kit.Kit) error {
|
|||
|
||||
skipVerify := kit.Getenv("SUPERKIT_AUTH_SKIP_VERIFY", "false")
|
||||
if skipVerify != "true" {
|
||||
if !user.EmailVerifiedAt.Valid {
|
||||
if user.EmailVerifiedAt.Equal(time.Time{}) {
|
||||
errors.Add("verified", "please verify your email")
|
||||
return kit.Render(LoginForm(values, errors))
|
||||
}
|
||||
|
@ -69,17 +71,24 @@ func HandleLoginCreate(kit *kit.Kit) error {
|
|||
sessionExpiry = 48
|
||||
}
|
||||
session := Session{
|
||||
UserID: user.ID,
|
||||
Token: uuid.New().String(),
|
||||
ExpiresAt: time.Now().Add(time.Hour * time.Duration(sessionExpiry)),
|
||||
UserID: user.ID,
|
||||
Token: uuid.New().String(),
|
||||
CreatedAt: time.Now(),
|
||||
LastLoginAt: time.Now(),
|
||||
ExpiresAt: time.Now().Add(time.Hour * time.Duration(sessionExpiry)),
|
||||
}
|
||||
if err = db.Get().Create(&session).Error; err != nil {
|
||||
_, err = db.Query.NewInsert().
|
||||
Model(&session).
|
||||
Exec(kit.Request.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO change this with kit.Getenv
|
||||
sess := kit.GetSession(userSessionName)
|
||||
sess.Values["sessionToken"] = session.Token
|
||||
sess.Save(kit.Request, kit.Response)
|
||||
|
||||
redirectURL := kit.Getenv("SUPERKIT_AUTH_REDIRECT_AFTER_LOGIN", "/profile")
|
||||
|
||||
return kit.Redirect(http.StatusSeeOther, redirectURL)
|
||||
|
@ -91,7 +100,10 @@ func HandleLoginDelete(kit *kit.Kit) error {
|
|||
sess.Values = map[any]any{}
|
||||
sess.Save(kit.Request, kit.Response)
|
||||
}()
|
||||
err := db.Get().Delete(&Session{}, "token = ?", sess.Values["sessionToken"]).Error
|
||||
_, err := db.Query.NewDelete().
|
||||
Model((*Session)(nil)).
|
||||
Where("token = ?", sess.Values["sessionToken"]).
|
||||
Exec(kit.Request.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -109,7 +121,7 @@ func HandleEmailVerify(kit *kit.Kit) error {
|
|||
return []byte(os.Getenv("SUPERKIT_SECRET")), nil
|
||||
}, jwt.WithLeeway(5*time.Second))
|
||||
if err != nil {
|
||||
return kit.Render(EmailVerificationError("invalid verification token"))
|
||||
return err
|
||||
}
|
||||
if !token.Valid {
|
||||
return kit.Render(EmailVerificationError("invalid verification token"))
|
||||
|
@ -129,18 +141,23 @@ func HandleEmailVerify(kit *kit.Kit) error {
|
|||
}
|
||||
|
||||
var user User
|
||||
err = db.Get().First(&user, userID).Error
|
||||
err = db.Query.NewSelect().
|
||||
Model(&user).
|
||||
Where("id = ?", userID).
|
||||
Scan(kit.Request.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if user.EmailVerifiedAt.Time.After(time.Time{}) {
|
||||
if user.EmailVerifiedAt.After(time.Time{}) {
|
||||
return kit.Render(EmailVerificationError("Email already verified"))
|
||||
}
|
||||
|
||||
now := sql.NullTime{Time: time.Now(), Valid: true}
|
||||
user.EmailVerifiedAt = now
|
||||
err = db.Get().Save(&user).Error
|
||||
user.EmailVerifiedAt = time.Now()
|
||||
_, err = db.Query.NewUpdate().
|
||||
Model(&user).
|
||||
WherePK().
|
||||
Exec(kit.Request.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -157,14 +174,16 @@ func AuthenticateUser(kit *kit.Kit) (kit.Auth, error) {
|
|||
}
|
||||
|
||||
var session Session
|
||||
err := db.Get().
|
||||
Preload("User").
|
||||
Find(&session, "token = ? AND expires_at > ?", token, time.Now()).Error
|
||||
if err != nil || session.ID == 0 {
|
||||
err := db.Query.NewSelect().
|
||||
Model(&session).
|
||||
Relation("User").
|
||||
Where("session.token = ? AND session.expires_at > ?", token, time.Now()).
|
||||
Scan(kit.Request.Context())
|
||||
if err != nil {
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
return Auth{
|
||||
|
||||
LoggedIn: true,
|
||||
UserID: session.User.ID,
|
||||
Email: session.User.Email,
|
||||
|
|
|
@ -14,7 +14,7 @@ var profileSchema = v.Schema{
|
|||
}
|
||||
|
||||
type ProfileFormValues struct {
|
||||
ID uint `form:"id"`
|
||||
ID int `form:"id"`
|
||||
FirstName string `form:"firstName"`
|
||||
LastName string `form:"lastName"`
|
||||
Email string
|
||||
|
@ -25,7 +25,11 @@ func HandleProfileShow(kit *kit.Kit) error {
|
|||
auth := kit.Auth().(Auth)
|
||||
|
||||
var user User
|
||||
if err := db.Get().First(&user, auth.UserID).Error; err != nil {
|
||||
err := db.Query.NewSelect().
|
||||
Model(&user).
|
||||
Where("id = ?", auth.UserID).
|
||||
Scan(kit.Request.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -50,12 +54,12 @@ func HandleProfileUpdate(kit *kit.Kit) error {
|
|||
if auth.UserID != values.ID {
|
||||
return fmt.Errorf("unauthorized request for profile %d", values.ID)
|
||||
}
|
||||
err := db.Get().Model(&User{}).
|
||||
_, err := db.Query.NewUpdate().
|
||||
Model((*User)(nil)).
|
||||
Set("first_name = ?", values.FirstName).
|
||||
Set("last_name = ?", values.LastName).
|
||||
Where("id = ?", auth.UserID).
|
||||
Updates(&User{
|
||||
FirstName: values.FirstName,
|
||||
LastName: values.LastName,
|
||||
}).Error
|
||||
Exec(kit.Request.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -63,15 +63,19 @@ func HandleResendVerificationCode(kit *kit.Kit) error {
|
|||
}
|
||||
|
||||
var user User
|
||||
if err = db.Get().First(&user, id).Error; err != nil {
|
||||
err = db.Query.NewSelect().
|
||||
Model(&user).
|
||||
Where("id = ?", id).
|
||||
Scan(kit.Request.Context())
|
||||
if err != nil {
|
||||
return kit.Text(http.StatusOK, "An unexpected error occured")
|
||||
}
|
||||
|
||||
if user.EmailVerifiedAt.Time.After(time.Time{}) {
|
||||
if user.EmailVerifiedAt.After(time.Time{}) {
|
||||
return kit.Text(http.StatusOK, "Email already verified!")
|
||||
}
|
||||
|
||||
token, err := createVerificationToken(uint(id))
|
||||
token, err := createVerificationToken(id)
|
||||
if err != nil {
|
||||
return kit.Text(http.StatusOK, "An unexpected error occured")
|
||||
}
|
||||
|
@ -86,7 +90,7 @@ func HandleResendVerificationCode(kit *kit.Kit) error {
|
|||
return kit.Text(http.StatusOK, msg)
|
||||
}
|
||||
|
||||
func createVerificationToken(userID uint) (string, error) {
|
||||
func createVerificationToken(userID int) (string, error) {
|
||||
expiryStr := kit.Getenv("SUPERKIT_AUTH_EMAIL_VERIFICATION_EXPIRY_IN_HOURS", "1")
|
||||
expiry, err := strconv.Atoi(expiryStr)
|
||||
if err != nil {
|
||||
|
|
|
@ -2,11 +2,10 @@ package auth
|
|||
|
||||
import (
|
||||
"AABBCCDD/app/db"
|
||||
"database/sql"
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Event name constants
|
||||
|
@ -23,7 +22,7 @@ type UserWithVerificationToken struct {
|
|||
}
|
||||
|
||||
type Auth struct {
|
||||
UserID uint
|
||||
UserID int
|
||||
Email string
|
||||
LoggedIn bool
|
||||
}
|
||||
|
@ -33,13 +32,12 @@ func (auth Auth) Check() bool {
|
|||
}
|
||||
|
||||
type User struct {
|
||||
gorm.Model
|
||||
|
||||
ID int `bun:"id,pk,autoincrement"`
|
||||
Email string
|
||||
FirstName string
|
||||
LastName string
|
||||
PasswordHash string
|
||||
EmailVerifiedAt sql.NullTime
|
||||
EmailVerifiedAt time.Time
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
@ -54,19 +52,22 @@ func createUserFromFormValues(values SignupFormValues) (User, error) {
|
|||
FirstName: values.FirstName,
|
||||
LastName: values.LastName,
|
||||
PasswordHash: string(hash),
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
result := db.Get().Create(&user)
|
||||
return user, result.Error
|
||||
_, err = db.Query.NewInsert().Model(&user).Exec(context.Background())
|
||||
return user, err
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
gorm.Model
|
||||
ID int `bun:"id,pk,autoincrement"`
|
||||
UserID int
|
||||
Token string
|
||||
IPAddress string
|
||||
UserAgent string
|
||||
ExpiresAt time.Time
|
||||
LastLoginAt time.Time
|
||||
CreatedAt time.Time
|
||||
|
||||
UserID uint
|
||||
Token string
|
||||
IPAddress string
|
||||
UserAgent string
|
||||
ExpiresAt time.Time
|
||||
CreatedAt time.Time
|
||||
User User
|
||||
User User `bun:"rel:belongs-to,join:user_id=id"`
|
||||
}
|
||||
|
|
|
@ -629,23 +629,63 @@ body {
|
|||
position: fixed;
|
||||
}
|
||||
|
||||
.absolute {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.inset-0 {
|
||||
inset: 0px;
|
||||
}
|
||||
|
||||
.left-0 {
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.right-0 {
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.right-6 {
|
||||
right: 1.5rem;
|
||||
}
|
||||
|
||||
.top-0 {
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.top-6 {
|
||||
top: 1.5rem;
|
||||
}
|
||||
|
||||
.z-50 {
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.z-\[99\] {
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.mx-auto {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.mb-6 {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.ml-4 {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.mr-5 {
|
||||
margin-right: 1.25rem;
|
||||
}
|
||||
|
||||
.mt-10 {
|
||||
margin-top: 2.5rem;
|
||||
}
|
||||
|
@ -654,6 +694,14 @@ body {
|
|||
margin-top: 8rem;
|
||||
}
|
||||
|
||||
.mt-5 {
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
|
||||
.mt-8 {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
|
@ -666,14 +714,58 @@ body {
|
|||
display: inline-flex;
|
||||
}
|
||||
|
||||
.table {
|
||||
display: table;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.h-10 {
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
.h-4 {
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.h-5 {
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
.h-8 {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.h-auto {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.h-screen {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.w-4 {
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
.w-5 {
|
||||
width: 1.25rem;
|
||||
}
|
||||
|
||||
.w-8 {
|
||||
width: 2rem;
|
||||
}
|
||||
|
||||
.w-auto {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.w-fit {
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
|
@ -683,10 +775,22 @@ body {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.w-screen {
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.min-w-full {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.max-w-2xl {
|
||||
max-width: 42rem;
|
||||
}
|
||||
|
||||
.max-w-4xl {
|
||||
max-width: 56rem;
|
||||
}
|
||||
|
||||
.max-w-7xl {
|
||||
max-width: 80rem;
|
||||
}
|
||||
|
@ -699,6 +803,20 @@ body {
|
|||
max-width: 24rem;
|
||||
}
|
||||
|
||||
.max-w-xs {
|
||||
max-width: 20rem;
|
||||
}
|
||||
|
||||
.translate-y-0 {
|
||||
--tw-translate-y: 0px;
|
||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||
}
|
||||
|
||||
.translate-y-4 {
|
||||
--tw-translate-y: 1rem;
|
||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||
}
|
||||
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -755,6 +873,36 @@ body {
|
|||
gap: 2rem;
|
||||
}
|
||||
|
||||
.divide-y > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-y-reverse: 0;
|
||||
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
|
||||
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
|
||||
}
|
||||
|
||||
.overflow-hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.overflow-x-auto {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.whitespace-nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.rounded {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.rounded-full {
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.rounded-lg {
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.rounded-md {
|
||||
border-radius: calc(var(--radius) - 2px);
|
||||
}
|
||||
|
@ -772,20 +920,49 @@ body {
|
|||
border-color: hsl(var(--input) / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.border-primary {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: hsl(var(--primary) / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.border-red-500 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(239 68 68 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.bg-black {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-card {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: hsl(var(--card) / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-destructive {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: hsl(var(--destructive) / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-primary {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: hsl(var(--primary) / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-secondary {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: hsl(var(--secondary) / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-transparent {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.bg-opacity-40 {
|
||||
--tw-bg-opacity: 0.4;
|
||||
}
|
||||
|
||||
.bg-gradient-to-r {
|
||||
background-image: linear-gradient(to right, var(--tw-gradient-stops));
|
||||
}
|
||||
|
@ -810,6 +987,10 @@ body {
|
|||
background-clip: text;
|
||||
}
|
||||
|
||||
.p-6 {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.px-3 {
|
||||
padding-left: 0.75rem;
|
||||
padding-right: 0.75rem;
|
||||
|
@ -820,16 +1001,31 @@ body {
|
|||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.px-5 {
|
||||
padding-left: 1.25rem;
|
||||
padding-right: 1.25rem;
|
||||
}
|
||||
|
||||
.px-6 {
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
|
||||
.px-7 {
|
||||
padding-left: 1.75rem;
|
||||
padding-right: 1.75rem;
|
||||
}
|
||||
|
||||
.px-8 {
|
||||
padding-left: 2rem;
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
.py-1 {
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.py-12 {
|
||||
padding-top: 3rem;
|
||||
padding-bottom: 3rem;
|
||||
|
@ -845,10 +1041,45 @@ body {
|
|||
padding-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.py-4 {
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.py-6 {
|
||||
padding-top: 1.5rem;
|
||||
padding-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.py-8 {
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
.pb-2 {
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.pt-0 {
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
.pt-2 {
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.align-middle {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
@ -908,16 +1139,35 @@ body {
|
|||
letter-spacing: 0.025em;
|
||||
}
|
||||
|
||||
.text-blue-500 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(59 130 246 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-card-foreground {
|
||||
--tw-text-opacity: 1;
|
||||
color: hsl(var(--card-foreground) / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-foreground {
|
||||
--tw-text-opacity: 1;
|
||||
color: hsl(var(--foreground) / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-foreground\/60 {
|
||||
color: hsl(var(--foreground) / 0.6);
|
||||
}
|
||||
|
||||
.text-muted-foreground {
|
||||
--tw-text-opacity: 1;
|
||||
color: hsl(var(--muted-foreground) / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
--tw-text-opacity: 1;
|
||||
color: hsl(var(--primary) / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-primary-foreground {
|
||||
--tw-text-opacity: 1;
|
||||
color: hsl(var(--primary-foreground) / var(--tw-text-opacity));
|
||||
|
@ -932,16 +1182,33 @@ body {
|
|||
color: transparent;
|
||||
}
|
||||
|
||||
.text-white {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.opacity-0 {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.opacity-100 {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.shadow-sm {
|
||||
--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
|
||||
.outline {
|
||||
outline-style: solid;
|
||||
}
|
||||
|
||||
.ring-offset-background {
|
||||
--tw-ring-offset-color: hsl(var(--background) / 1);
|
||||
}
|
||||
|
@ -956,12 +1223,51 @@ body {
|
|||
transition-duration: 200ms;
|
||||
}
|
||||
|
||||
.duration-300 {
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
.ease-in {
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
|
||||
}
|
||||
|
||||
.ease-out {
|
||||
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* fix for Alpine users. */
|
||||
|
||||
[x-cloak] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.file\:border-0::file-selector-button {
|
||||
border-width: 0px;
|
||||
}
|
||||
|
||||
.file\:bg-transparent::file-selector-button {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.file\:text-sm::file-selector-button {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.file\:font-medium::file-selector-button {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.placeholder\:text-muted-foreground::-moz-placeholder {
|
||||
--tw-text-opacity: 1;
|
||||
color: hsl(var(--muted-foreground) / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.placeholder\:text-muted-foreground::placeholder {
|
||||
--tw-text-opacity: 1;
|
||||
color: hsl(var(--muted-foreground) / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.placeholder\:text-neutral-500::-moz-placeholder {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(115 115 115 / var(--tw-text-opacity));
|
||||
|
@ -972,10 +1278,27 @@ body {
|
|||
color: rgb(115 115 115 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-destructive\/80:hover {
|
||||
background-color: hsl(var(--destructive) / 0.8);
|
||||
}
|
||||
|
||||
.hover\:bg-primary\/90:hover {
|
||||
background-color: hsl(var(--primary) / 0.9);
|
||||
}
|
||||
|
||||
.hover\:bg-secondary:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: hsl(var(--secondary) / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-secondary\/80:hover {
|
||||
background-color: hsl(var(--secondary) / 0.8);
|
||||
}
|
||||
|
||||
.hover\:text-foreground\/80:hover {
|
||||
color: hsl(var(--foreground) / 0.8);
|
||||
}
|
||||
|
||||
.focus\:border-neutral-300:focus {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(212 212 212 / var(--tw-border-opacity));
|
||||
|
@ -997,6 +1320,22 @@ body {
|
|||
--tw-ring-color: hsl(var(--primary) / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.focus-visible\:outline-none:focus-visible {
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.focus-visible\:ring-1:focus-visible {
|
||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
||||
}
|
||||
|
||||
.focus-visible\:ring-ring:focus-visible {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: hsl(var(--ring) / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.disabled\:cursor-not-allowed:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
@ -1005,6 +1344,33 @@ body {
|
|||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.sm\:max-w-lg {
|
||||
max-width: 32rem;
|
||||
}
|
||||
|
||||
.sm\:translate-y-0 {
|
||||
--tw-translate-y: 0px;
|
||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||
}
|
||||
|
||||
.sm\:scale-100 {
|
||||
--tw-scale-x: 1;
|
||||
--tw-scale-y: 1;
|
||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||
}
|
||||
|
||||
.sm\:scale-95 {
|
||||
--tw-scale-x: .95;
|
||||
--tw-scale-y: .95;
|
||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||
}
|
||||
|
||||
.sm\:rounded-lg {
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:mt-20 {
|
||||
margin-top: 5rem;
|
||||
|
|
3
db/db.go
3
db/db.go
|
@ -7,7 +7,6 @@ import (
|
|||
|
||||
const (
|
||||
DriverSqlite3 = "sqlite3"
|
||||
DriverMysql = "mysql"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
|
@ -18,7 +17,7 @@ type Config struct {
|
|||
Password string
|
||||
}
|
||||
|
||||
func NewSQL(cfg Config) (*sql.DB, error) {
|
||||
func New(cfg Config) (*sql.DB, error) {
|
||||
switch cfg.Driver {
|
||||
case DriverSqlite3:
|
||||
name := cfg.Name
|
||||
|
|
|
@ -3,14 +3,17 @@ package event
|
|||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEventSubscribeEmit(t *testing.T) {
|
||||
expect := 1
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
Subscribe("foo.a", func(_ context.Context, event any) {
|
||||
defer cancel()
|
||||
var (
|
||||
expect = 1
|
||||
wg = sync.WaitGroup{}
|
||||
)
|
||||
wg.Add(1)
|
||||
Subscribe("foo.bar", func(_ context.Context, event any) {
|
||||
value, ok := event.(int)
|
||||
if !ok {
|
||||
t.Errorf("expected int got %v", reflect.TypeOf(event))
|
||||
|
@ -18,15 +21,16 @@ func TestEventSubscribeEmit(t *testing.T) {
|
|||
if value != 1 {
|
||||
t.Errorf("expected %d got %d", expect, value)
|
||||
}
|
||||
wg.Done()
|
||||
})
|
||||
Emit("foo.a", expect)
|
||||
<-ctx.Done()
|
||||
Emit("foo.bar", expect)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestUnsubscribe(t *testing.T) {
|
||||
sub := Subscribe("foo.b", func(_ context.Context, _ any) {})
|
||||
sub := Subscribe("foo.bar", func(_ context.Context, _ any) {})
|
||||
Unsubscribe(sub)
|
||||
if _, ok := stream.subs["foo.b"]; ok {
|
||||
if _, ok := stream.subs["foo.bar"]; ok {
|
||||
t.Errorf("expected topic foo.bar to be deleted")
|
||||
}
|
||||
}
|
||||
|
|
2
go.mod
2
go.mod
|
@ -3,7 +3,7 @@ module github.com/anthdm/superkit
|
|||
go 1.22.0
|
||||
|
||||
require (
|
||||
github.com/a-h/templ v0.2.731
|
||||
github.com/a-h/templ v0.2.707
|
||||
github.com/gorilla/sessions v1.3.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
)
|
||||
|
|
2
go.sum
2
go.sum
|
@ -1,7 +1,5 @@
|
|||
github.com/a-h/templ v0.2.707 h1:T1Gkd2ugbRglZ9rYw/VBchWOSZVKmetDbBkm4YubM7U=
|
||||
github.com/a-h/templ v0.2.707/go.mod h1:5cqsugkq9IerRNucNsI4DEamdHPsoGMQy99DzydLhM8=
|
||||
github.com/a-h/templ v0.2.731 h1:yiv4C7whSUsa36y65O06DPr/U/j3+WGB0RmvLOoVFXc=
|
||||
github.com/a-h/templ v0.2.731/go.mod h1:IejA/ecDD0ul0dCvgCwp9t7bUZXVpGClEAdsqZQigi8=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
|
|
|
@ -4,14 +4,12 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/a-h/templ"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
var store *sessions.CookieStore
|
||||
|
@ -168,10 +166,7 @@ func Env() string {
|
|||
// initialize the store here so the environment variables are
|
||||
// already initialized. Calling NewCookieStore() from outside of
|
||||
// a function scope won't work.
|
||||
func Setup() {
|
||||
if err := godotenv.Load(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
func init() {
|
||||
appSecret := os.Getenv("SUPERKIT_SECRET")
|
||||
if len(appSecret) < 32 {
|
||||
// For security reasons we are calling os.Exit(1) here so Go's panic recover won't
|
||||
|
|
|
@ -5,14 +5,11 @@ import (
|
|||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
RequestKey struct{}
|
||||
ResponseHeadersKey struct{}
|
||||
)
|
||||
type RequestURLKey struct{}
|
||||
|
||||
func WithRequest(next http.Handler) http.Handler {
|
||||
func WithRequestURL(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.WithValue(r.Context(), RequestKey{}, r)
|
||||
ctx := context.WithValue(r.Context(), RequestURLKey{}, r.URL)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package button
|
||||
|
||||
import (
|
||||
"github.com/anthdm/superkit/ui"
|
||||
"AABBCCDD/ui"
|
||||
|
||||
"github.com/a-h/templ"
|
||||
)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package card
|
||||
|
||||
import "github.com/anthdm/superkit/ui"
|
||||
import "AABBCCDD/ui"
|
||||
|
||||
const cardBaseClass = "rounded-lg border bg-card text-card-foreground shadow-sm"
|
||||
|
||||
|
|
175
ui/card/card_templ.go
Normal file
175
ui/card/card_templ.go
Normal file
|
@ -0,0 +1,175 @@
|
|||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.707
|
||||
package card
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import "context"
|
||||
import "io"
|
||||
import "bytes"
|
||||
|
||||
import "AABBCCDD/ui"
|
||||
|
||||
const cardBaseClass = "rounded-lg border bg-card text-card-foreground shadow-sm"
|
||||
|
||||
func Card(opts ...func(*templ.Attributes)) templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.RenderAttributes(ctx, templ_7745c5c3_Buffer, ui.CreateAttrs(cardBaseClass, "", opts...))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
func Header(opts ...func(*templ.Attributes)) templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var2 == nil {
|
||||
templ_7745c5c3_Var2 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.RenderAttributes(ctx, templ_7745c5c3_Buffer, ui.CreateAttrs("p-6", "", opts...))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var2.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
func Content(opts ...func(*templ.Attributes)) templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var3 == nil {
|
||||
templ_7745c5c3_Var3 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.RenderAttributes(ctx, templ_7745c5c3_Buffer, ui.CreateAttrs("p-6 pt-0", "", opts...))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var3.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
func Footer(opts ...func(*templ.Attributes)) templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var4 == nil {
|
||||
templ_7745c5c3_Var4 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.RenderAttributes(ctx, templ_7745c5c3_Buffer, ui.CreateAttrs("p-6 pt-0", "", opts...))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var4.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
|
@ -3,7 +3,7 @@ package input
|
|||
import (
|
||||
"github.com/a-h/templ"
|
||||
|
||||
"github.com/anthdm/superkit/ui"
|
||||
"AABBCCDD/ui"
|
||||
)
|
||||
|
||||
const defaultInputClass = "flex h-10 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
|
|
159
ui/modal/modal_templ.go
Normal file
159
ui/modal/modal_templ.go
Normal file
|
@ -0,0 +1,159 @@
|
|||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.707
|
||||
package modal
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import "context"
|
||||
import "io"
|
||||
import "bytes"
|
||||
|
||||
func Modal() templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div x-data=\"{ modalOpen: false }\" @keydown.escape.window=\"modalOpen = false\" class=\"relative z-50 w-auto h-auto\"><div @click=\"modalOpen=true\">foo</div><template x-teleport=\"body\"><div x-show=\"modalOpen\" class=\"fixed top-0 left-0 z-[99] flex items-center justify-center w-screen h-screen\" x-cloak><div x-show=\"modalOpen\" x-transition:enter=\"ease-out duration-300\" x-transition:enter-start=\"opacity-0\" x-transition:enter-end=\"opacity-100\" x-transition:leave=\"ease-in duration-300\" x-transition:leave-start=\"opacity-100\" x-transition:leave-end=\"opacity-0\" @click=\"modalOpen=false\" class=\"absolute inset-0 w-full h-full bg-black bg-opacity-40\"></div><div x-show=\"modalOpen\" x-trap.inert.noscroll=\"modalOpen\" x-transition:enter=\"ease-out duration-300\" x-transition:enter-start=\"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95\" x-transition:enter-end=\"opacity-100 translate-y-0 sm:scale-100\" x-transition:leave=\"ease-in duration-200\" x-transition:leave-start=\"opacity-100 translate-y-0 sm:scale-100\" x-transition:leave-end=\"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95\" class=\"relative w-full py-6 px-7 sm:max-w-lg sm:rounded-lg\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div @click=\"modalOpen=true\">foo</div></div></div></template></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
func Header() templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var2 == nil {
|
||||
templ_7745c5c3_Var2 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"flex items-center justify-between pb-2\"><h3 class=\"text-lg font-semibold\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var2.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h3><button @click=\"modalOpen=false\" class=\"absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 rounded-full\"><svg class=\"w-5 h-5\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\"></path></svg></button></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
func Content() templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var3 == nil {
|
||||
templ_7745c5c3_Var3 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"relative w-auto\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var3.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
func Trigger() templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var4 == nil {
|
||||
templ_7745c5c3_Var4 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div @click=\"modalOpen=true\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var4.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
func Footer() templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var5 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var5 == nil {
|
||||
templ_7745c5c3_Var5 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package table
|
||||
|
||||
import (
|
||||
"github.com/anthdm/superkit/ui"
|
||||
"AABBCCDD/ui"
|
||||
)
|
||||
|
||||
templ Table() {
|
||||
|
|
172
ui/table/table_templ.go
Normal file
172
ui/table/table_templ.go
Normal file
|
@ -0,0 +1,172 @@
|
|||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.707
|
||||
package table
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import "context"
|
||||
import "io"
|
||||
import "bytes"
|
||||
|
||||
import (
|
||||
"AABBCCDD/ui"
|
||||
)
|
||||
|
||||
func Table() templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"flex flex-col border rounded-md\"><div class=\"overflow-x-auto\"><div class=\"inline-block min-w-full\"><div class=\"overflow-hidden\"><table class=\"min-w-full divide-y\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</table></div></div></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
func Header(opts ...func(*templ.Attributes)) templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var2 == nil {
|
||||
templ_7745c5c3_Var2 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<thead")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.RenderAttributes(ctx, templ_7745c5c3_Buffer, ui.CreateAttrs("", "", opts...))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("><tr>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var2.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</tr></thead>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
func Body(opts ...func(*templ.Attributes)) templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var3 == nil {
|
||||
templ_7745c5c3_Var3 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<tbody")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.RenderAttributes(ctx, templ_7745c5c3_Buffer, ui.CreateAttrs("divide-y", "", opts...))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var3.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</tbody>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
func Footer(opts ...func(*templ.Attributes)) templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var4 == nil {
|
||||
templ_7745c5c3_Var4 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<tfoot><tr>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var4.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</tr></tfoot>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
const (
|
||||
thBaseClass = "px-5 py-3 text-xs font-medium uppercase"
|
||||
tdBaseClass = "px-5 py-4 text-sm whitespace-nowrap"
|
||||
)
|
||||
|
||||
func Td(opts ...func(*templ.Attributes)) templ.Attributes {
|
||||
return ui.CreateAttrs(tdBaseClass, "", opts...)
|
||||
}
|
||||
|
||||
func Th(opts ...func(*templ.Attributes)) templ.Attributes {
|
||||
return ui.CreateAttrs(thBaseClass, "", opts...)
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
# Validate
|
||||
Schema based validation with superpowers for Golang.
|
||||
|
||||
# TODO
|
|
@ -10,7 +10,7 @@ import (
|
|||
|
||||
var (
|
||||
emailRegex = regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
|
||||
urlRegex = regexp.MustCompile(`^(https?:\/\/)?(www\.)?([a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,}(\/[a-zA-Z0-9\-._~:\/?#\[\]@!$&'()*+,;=]*)?$`)
|
||||
urlRegex = regexp.MustCompile(`^(http(s)?://)?([\da-z\.-]+)\.([a-z\.]{2,6})([/\w \.-]*)*/?$`)
|
||||
)
|
||||
|
||||
// RuleSet holds the state of a single rule.
|
||||
|
|
|
@ -39,7 +39,7 @@ func (e Errors) Has(field string) bool {
|
|||
// Schema represents a validation schema.
|
||||
type Schema map[string][]RuleSet
|
||||
|
||||
// Merge merges the two given schemas, returning a new Schema.
|
||||
// Merge merges the two given schemas returning a new Schema.
|
||||
func Merge(schema, other Schema) Schema {
|
||||
newSchema := Schema{}
|
||||
maps.Copy(newSchema, schema)
|
||||
|
@ -75,14 +75,9 @@ func Request(r *http.Request, data any, schema Schema) (Errors, bool) {
|
|||
func validate(data any, schema Schema, errors Errors) (Errors, bool) {
|
||||
ok := true
|
||||
for fieldName, ruleSets := range schema {
|
||||
// Uppercase the field name so we never check un-exported fields.
|
||||
// But we need to watch out for member fields that are uppercased by
|
||||
// the user. For example (URL, ID, ...)
|
||||
if !isUppercase(fieldName) {
|
||||
fieldName = string(unicode.ToUpper(rune(fieldName[0]))) + fieldName[1:]
|
||||
}
|
||||
|
||||
fieldValue := getFieldAndTagByName(data, fieldName)
|
||||
// Uppercase the field name so we never check un-exported fields
|
||||
fieldName = string(unicode.ToUpper(rune(fieldName[0]))) + fieldName[1:]
|
||||
fieldValue := getFieldValueByName(data, fieldName)
|
||||
for _, set := range ruleSets {
|
||||
set.FieldValue = fieldValue
|
||||
set.FieldName = fieldName
|
||||
|
@ -103,7 +98,7 @@ func validate(data any, schema Schema, errors Errors) (Errors, bool) {
|
|||
return errors, ok
|
||||
}
|
||||
|
||||
func getFieldAndTagByName(v any, name string) any {
|
||||
func getFieldValueByName(v any, name string) any {
|
||||
val := reflect.ValueOf(v)
|
||||
if val.Kind() == reflect.Ptr {
|
||||
val = val.Elem()
|
||||
|
@ -153,18 +148,12 @@ func parseRequest(r *http.Request, v any) error {
|
|||
}
|
||||
case reflect.String:
|
||||
fieldVal.SetString(formValue)
|
||||
case reflect.Int, reflect.Int32, reflect.Int64:
|
||||
case reflect.Int:
|
||||
intVal, err := strconv.Atoi(formValue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse int: %v", err)
|
||||
}
|
||||
fieldVal.SetInt(int64(intVal))
|
||||
case reflect.Uint, reflect.Uint32, reflect.Uint64:
|
||||
intVal, err := strconv.Atoi(formValue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse int: %v", err)
|
||||
}
|
||||
fieldVal.SetUint(uint64(intVal))
|
||||
case reflect.Float64:
|
||||
floatVal, err := strconv.ParseFloat(formValue, 64)
|
||||
if err != nil {
|
||||
|
@ -179,12 +168,3 @@ func parseRequest(r *http.Request, v any) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isUppercase(s string) bool {
|
||||
for _, ch := range s {
|
||||
if !unicode.IsUpper(rune(ch)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -2,9 +2,6 @@ package validate
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -31,65 +28,6 @@ var testSchema = Schema{
|
|||
"username": Rules(Required),
|
||||
}
|
||||
|
||||
func TestValidateRequest(t *testing.T) {
|
||||
var (
|
||||
email = "foo@bar.com"
|
||||
password = "superHunter123@"
|
||||
firstName = "Anthony"
|
||||
website = "http://foo.com"
|
||||
randomNumber = 123
|
||||
randomFloat = 9.999
|
||||
)
|
||||
formValues := url.Values{}
|
||||
formValues.Set("email", email)
|
||||
formValues.Set("password", password)
|
||||
formValues.Set("firstName", firstName)
|
||||
formValues.Set("url", website)
|
||||
formValues.Set("brandom", fmt.Sprint(randomNumber))
|
||||
formValues.Set("arandom", fmt.Sprint(randomFloat))
|
||||
encodedValues := formValues.Encode()
|
||||
|
||||
req, err := http.NewRequest("POST", "http://foo.com", strings.NewReader(encodedValues))
|
||||
assert.Nil(t, err)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
type SignupData struct {
|
||||
Email string `form:"email"`
|
||||
Password string `form:"password"`
|
||||
FirstName string `form:"firstName"`
|
||||
URL string `form:"url"`
|
||||
ARandomRenamedNumber int `form:"brandom"`
|
||||
ARandomRenamedFloat float64 `form:"arandom"`
|
||||
}
|
||||
|
||||
schema := Schema{
|
||||
"Email": Rules(Email),
|
||||
"Password": Rules(
|
||||
Required,
|
||||
ContainsDigit,
|
||||
ContainsUpper,
|
||||
ContainsSpecial,
|
||||
Min(7),
|
||||
),
|
||||
"FirstName": Rules(Min(3), Max(50)),
|
||||
"URL": Rules(URL),
|
||||
"ARandomRenamedNumber": Rules(GT(100), LT(124)),
|
||||
"ARandomRenamedFloat": Rules(GT(9.0), LT(10.1)),
|
||||
}
|
||||
|
||||
var data SignupData
|
||||
errors, ok := Request(req, &data, schema)
|
||||
assert.True(t, ok)
|
||||
assert.Empty(t, errors)
|
||||
|
||||
assert.Equal(t, data.Email, email)
|
||||
assert.Equal(t, data.Password, password)
|
||||
assert.Equal(t, data.FirstName, firstName)
|
||||
assert.Equal(t, data.URL, website)
|
||||
assert.Equal(t, data.ARandomRenamedNumber, randomNumber)
|
||||
assert.Equal(t, data.ARandomRenamedFloat, randomFloat)
|
||||
}
|
||||
|
||||
func TestTime(t *testing.T) {
|
||||
type Foo struct {
|
||||
CreatedAt time.Time
|
||||
|
@ -110,89 +48,21 @@ func TestTime(t *testing.T) {
|
|||
|
||||
func TestURL(t *testing.T) {
|
||||
type Foo struct {
|
||||
URL string `v:"URL"`
|
||||
URL string
|
||||
}
|
||||
foo := Foo{
|
||||
URL: "not an url",
|
||||
}
|
||||
schema := Schema{
|
||||
"URL": Rules(URL),
|
||||
"url": Rules(URL),
|
||||
}
|
||||
errors, ok := Validate(foo, schema)
|
||||
assert.False(t, ok)
|
||||
assert.NotEmpty(t, errors)
|
||||
|
||||
validURLS := []string{
|
||||
"http://google.com",
|
||||
"http://www.google.com",
|
||||
"https://www.google.com",
|
||||
"https://www.google.com",
|
||||
"www.google.com",
|
||||
"https://book.com/sales",
|
||||
"app.book.com",
|
||||
"app.book.com/signup",
|
||||
}
|
||||
|
||||
for _, url := range validURLS {
|
||||
foo.URL = url
|
||||
errors, ok = Validate(foo, schema)
|
||||
assert.True(t, ok)
|
||||
assert.Empty(t, errors)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsUpper(t *testing.T) {
|
||||
type Foo struct {
|
||||
Password string
|
||||
}
|
||||
foo := Foo{"hunter"}
|
||||
schema := Schema{
|
||||
"Password": Rules(ContainsUpper),
|
||||
}
|
||||
errors, ok := Validate(foo, schema)
|
||||
assert.False(t, ok)
|
||||
assert.NotEmpty(t, errors)
|
||||
|
||||
foo.Password = "Hunter"
|
||||
foo.URL = "www.user.com"
|
||||
errors, ok = Validate(foo, schema)
|
||||
assert.True(t, ok)
|
||||
assert.Empty(t, errors)
|
||||
}
|
||||
|
||||
func TestContainsDigit(t *testing.T) {
|
||||
type Foo struct {
|
||||
Password string
|
||||
}
|
||||
foo := Foo{"hunter"}
|
||||
schema := Schema{
|
||||
"Password": Rules(ContainsDigit),
|
||||
}
|
||||
errors, ok := Validate(foo, schema)
|
||||
assert.False(t, ok)
|
||||
assert.NotEmpty(t, errors)
|
||||
|
||||
foo.Password = "Hunter1"
|
||||
errors, ok = Validate(foo, schema)
|
||||
assert.True(t, ok)
|
||||
assert.Empty(t, errors)
|
||||
}
|
||||
|
||||
func TestContainsSpecial(t *testing.T) {
|
||||
type Foo struct {
|
||||
Password string
|
||||
}
|
||||
foo := Foo{"hunter"}
|
||||
schema := Schema{
|
||||
"Password": Rules(ContainsSpecial),
|
||||
}
|
||||
errors, ok := Validate(foo, schema)
|
||||
assert.False(t, ok)
|
||||
assert.NotEmpty(t, errors)
|
||||
|
||||
foo.Password = "Hunter@"
|
||||
errors, ok = Validate(foo, schema)
|
||||
assert.True(t, ok)
|
||||
assert.Empty(t, errors)
|
||||
fmt.Println(errors)
|
||||
}
|
||||
|
||||
func TestRuleIn(t *testing.T) {
|
||||
|
|
11
view/view.go
11
view/view.go
|
@ -3,7 +3,6 @@ package view
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/anthdm/superkit/kit"
|
||||
|
@ -41,13 +40,5 @@ func Auth(ctx context.Context) kit.Auth {
|
|||
//
|
||||
// view.URL(ctx).Path // => ex. /login
|
||||
func URL(ctx context.Context) *url.URL {
|
||||
return getContextValue(ctx, middleware.RequestKey{}, &http.Request{}).URL
|
||||
}
|
||||
|
||||
// Request is a view helper that returns the current http request.
|
||||
// The request can be accessed with:
|
||||
//
|
||||
// view.Request(ctx)
|
||||
func Request(ctx context.Context) *http.Request {
|
||||
return getContextValue(ctx, middleware.RequestKey{}, &http.Request{})
|
||||
return getContextValue(ctx, middleware.RequestURLKey{}, &url.URL{})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue