├── .gitignore
├── postcss.config.js
├── Makefile
├── tailwind.config.js
├── config
└── init.go
├── internal
├── transport
│ └── web
│ │ ├── admin.go
│ │ ├── new.go
│ │ └── utils.go
├── services
│ ├── register.go
│ └── sqlite-admin
│ │ └── new.go
├── storage
│ └── sqlite
│ │ ├── users.go
│ │ └── new.go
└── entities
│ └── main.go
├── package.json
├── public
├── js
│ └── animation.js
└── style.css
├── cmd
└── main.go
├── views
├── pages
│ ├── index.templ
│ └── index_templ.go
├── layouts
│ ├── base.templ
│ └── base_templ.go
└── css
│ └── main.css
├── go.mod
├── .air.toml
└── go.sum
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | tmp/
3 | .env
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | run:
2 | ./runner.sh
3 |
4 | css:
5 | npx tailwindcss build -i views/css/main.css -o public/style.css --watch
6 |
7 | templ:
8 | templ generate --watch
9 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: ["./views/**/*.{html,js,go,templ}"],
4 | theme: {
5 | container: {
6 | center: true,
7 | padding: "36px",
8 | screens: {
9 | "2xl": "1200px",
10 | },
11 | },
12 | },
13 | plugins: [],
14 | };
15 |
--------------------------------------------------------------------------------
/config/init.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "log"
5 | "os"
6 |
7 | "github.com/joho/godotenv"
8 | )
9 |
10 | var (
11 | MainSQLitePath string
12 | )
13 |
14 | func Initial() {
15 | err := godotenv.Load()
16 | if err != nil {
17 | log.Fatal("Error loading .env file")
18 | }
19 | MainSQLitePath = os.Getenv("MAINSQLITEPATH")
20 | }
21 |
--------------------------------------------------------------------------------
/internal/transport/web/admin.go:
--------------------------------------------------------------------------------
1 | package web
2 |
3 | import (
4 | "log"
5 | "net/http"
6 | "templtest/views/pages"
7 | )
8 |
9 | func (s *Server) index(w http.ResponseWriter, r *http.Request) {
10 | users, err := s.service.AllUsers()
11 | if err != nil {
12 | log.Println(err.Error())
13 | }
14 | pages.Index(users).Render(r.Context(), w)
15 | }
16 |
--------------------------------------------------------------------------------
/internal/services/register.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import sqliteadmin "templtest/internal/services/sqlite-admin"
4 |
5 | type DB interface {
6 | sqliteadmin.DB
7 | }
8 |
9 | type RegisterService struct {
10 | *sqliteadmin.SQLiteAdminService
11 | }
12 |
13 | func New(db DB) *RegisterService {
14 | sqliteAdminS := sqliteadmin.New(db)
15 | return &RegisterService{SQLiteAdminService: sqliteAdminS}
16 | }
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "templ_test",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "autoprefixer": "^10.4.19",
14 | "postcss": "^8.4.39",
15 | "tailwindcss": "^3.4.4"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/public/js/animation.js:
--------------------------------------------------------------------------------
1 | function OnLoadAnimation() {
2 | gsap.from("#create-todo", { opacity: 0, y: 100, duration: 0.5 });
3 | }
4 |
5 | const buttons = document.querySelectorAll(".deleteanimated");
6 | buttons.forEach((element, i) => {
7 | console.log("element " + i);
8 | element.addEventListener("click", () => {
9 | console.log(element.id);
10 | });
11 | });
12 |
13 | window.addEventListener("load", OnLoadAnimation);
14 |
--------------------------------------------------------------------------------
/internal/services/sqlite-admin/new.go:
--------------------------------------------------------------------------------
1 | package sqliteadmin
2 |
3 | import "templtest/internal/entities"
4 |
5 | type DB interface {
6 | Users() ([]entities.User, error)
7 | }
8 |
9 | type SQLiteAdminService struct {
10 | db DB
11 | }
12 |
13 | func New(db DB) *SQLiteAdminService {
14 | return &SQLiteAdminService{db}
15 | }
16 |
17 | func (s *SQLiteAdminService) AllUsers() ([]entities.User, error) {
18 | return s.db.Users()
19 | }
20 |
--------------------------------------------------------------------------------
/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "templtest/config"
6 | "templtest/internal/services"
7 | "templtest/internal/storage/sqlite"
8 | "templtest/internal/transport/web"
9 | )
10 |
11 | func main() {
12 | config.Initial()
13 | repo, err := sqlite.New()
14 | if err != nil {
15 | log.Fatal("db connection failed")
16 | }
17 |
18 | service := services.New(repo)
19 |
20 | server := web.New(service)
21 | server.Serve()
22 | }
23 |
--------------------------------------------------------------------------------
/views/pages/index.templ:
--------------------------------------------------------------------------------
1 | package pages
2 |
3 | import (
4 | "templtest/views/layouts"
5 | "templtest/internal/entities"
6 | )
7 |
8 | templ Index(users []entities.User){
9 | @layouts.BaseLayout("Admin | Syncword"){
10 |
11 | for _, user := range users {
12 |
13 | {user.Name}
14 | {user.Email}
15 |
16 | }
17 |
18 | }
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module templtest
2 |
3 | go 1.22.1
4 |
5 | require (
6 | github.com/Masterminds/squirrel v1.5.4 // indirect
7 | github.com/a-h/templ v0.2.747 // indirect
8 | github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
9 | github.com/google/uuid v1.6.0 // indirect
10 | github.com/jmoiron/sqlx v1.4.0 // indirect
11 | github.com/joho/godotenv v1.5.1 // indirect
12 | github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
13 | github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
14 | github.com/mattn/go-sqlite3 v1.14.22 // indirect
15 | )
16 |
--------------------------------------------------------------------------------
/internal/storage/sqlite/users.go:
--------------------------------------------------------------------------------
1 | package sqlite
2 |
3 | import (
4 | "templtest/internal/entities"
5 |
6 | sq "github.com/Masterminds/squirrel"
7 | )
8 |
9 | func (repo *Repository) Users() ([]entities.User, error) {
10 | sql, arg, err := sq.Select("id", "name", "full_name", "email", "avatar", "language", "created_at").From("users").ToSql()
11 | if err != nil {
12 | return []entities.User{}, err
13 | }
14 | result := make([]entities.User, 0)
15 | err = repo.db.Select(&result, sql, arg...)
16 | if err != nil {
17 | return []entities.User{}, err
18 | }
19 | return result, nil
20 | }
21 |
--------------------------------------------------------------------------------
/internal/storage/sqlite/new.go:
--------------------------------------------------------------------------------
1 | package sqlite
2 |
3 | import (
4 | "fmt"
5 | "templtest/config"
6 |
7 | "github.com/jmoiron/sqlx"
8 | _ "github.com/mattn/go-sqlite3"
9 | )
10 |
11 | type Repository struct {
12 | db *sqlx.DB
13 | }
14 |
15 | func New() (*Repository, error) {
16 | db, err := sqlx.Open("sqlite3", config.MainSQLitePath)
17 | if err != nil {
18 | return &Repository{}, err
19 | }
20 |
21 | err = db.Ping()
22 | if err != nil {
23 | return &Repository{}, err
24 | }
25 |
26 | newDB := &Repository{
27 | db: db,
28 | }
29 | fmt.Println("db connected")
30 | fmt.Println(config.MainSQLitePath)
31 | return newDB, nil
32 | }
33 |
34 | func (repo *Repository) Close() {
35 | repo.db.Close()
36 | }
37 |
--------------------------------------------------------------------------------
/views/layouts/base.templ:
--------------------------------------------------------------------------------
1 | package layouts
2 |
3 | templ BaseLayout(title string) {
4 |
5 |
6 |
7 |
8 |
9 | {title}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | {children...}
18 |
19 |
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/views/css/main.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @keyframes fade-in {
6 | from {
7 | opacity: 0;
8 | }
9 | }
10 |
11 | @keyframes fade-out {
12 | to {
13 | opacity: 0;
14 | }
15 | }
16 |
17 | @keyframes slide-from-right {
18 | from {
19 | transform: translateX(90px);
20 | }
21 | }
22 |
23 | @keyframes slide-to-left {
24 | to {
25 | transform: translateX(-90px);
26 | }
27 | }
28 |
29 | /* define animations for the old and new content */
30 | ::view-transition-old(slide-it) {
31 | animation: 180ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
32 | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
33 | }
34 | ::view-transition-new(slide-it) {
35 | animation: 420ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
36 | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
37 | }
38 |
39 | /* tie the view transition to a given CSS class */
40 | .sample-transition {
41 | view-transition-name: slide-it;
42 | }
43 |
--------------------------------------------------------------------------------
/.air.toml:
--------------------------------------------------------------------------------
1 | root = "."
2 | testdata_dir = "testdata"
3 | tmp_dir = "tmp"
4 |
5 | [build]
6 | args_bin = []
7 | bin = "./tmp/main"
8 | cmd = "go build -o ./tmp/main cmd/main.go"
9 | delay = 1000
10 | exclude_dir = ["assets", "tmp", "vendor", "testdata"]
11 | exclude_file = []
12 | exclude_regex = ["_test.go"]
13 | exclude_unchanged = false
14 | follow_symlink = false
15 | full_bin = ""
16 | include_dir = []
17 | include_ext = ["go", "tpl", "tmpl", "html"]
18 | include_file = []
19 | kill_delay = "0s"
20 | log = "build-errors.log"
21 | poll = false
22 | poll_interval = 0
23 | post_cmd = []
24 | pre_cmd = []
25 | rerun = false
26 | rerun_delay = 500
27 | send_interrupt = false
28 | stop_on_error = false
29 |
30 | [color]
31 | app = ""
32 | build = "yellow"
33 | main = "magenta"
34 | runner = "green"
35 | watcher = "cyan"
36 |
37 | [log]
38 | main_only = false
39 | time = false
40 |
41 | [misc]
42 | clean_on_exit = false
43 |
44 | [screen]
45 | clear_on_rebuild = false
46 | keep_scroll = true
47 |
--------------------------------------------------------------------------------
/internal/entities/main.go:
--------------------------------------------------------------------------------
1 | package entities
2 |
3 | import "time"
4 |
5 | type User struct {
6 | ID string `db:"id" json:"id"`
7 | Name string `db:"name" json:"name"`
8 | FullName string `db:"full_name" json:"full_name"`
9 | Email string `db:"email" json:"email"`
10 | Avatar string `db:"avatar" json:"avatar"`
11 | Language string `db:"language" json:"language"`
12 | CreatedAt time.Time `db:"created_at" json:"created_at"`
13 | }
14 |
15 | type WordBasic struct {
16 | ID string `db:"id" json:"id"`
17 | Title string `db:"title" json:"title"`
18 | Description string `db:"description" json:"description"`
19 | FromLanguage string `db:"from_language" json:"from_language"`
20 | ToLanguage string `db:"to_language" json:"to_language"`
21 | Type string `db:"type" json:"type"`
22 | }
23 |
24 | type Word struct {
25 | WordBasic
26 | CreatedAt time.Time `db:"created_at" json:"created_at"`
27 | UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
28 | UserID string `db:"user_id" json:"user_id"`
29 | }
30 |
--------------------------------------------------------------------------------
/internal/transport/web/new.go:
--------------------------------------------------------------------------------
1 | package web
2 |
3 | import (
4 | "context"
5 | "log"
6 | "net/http"
7 | "os"
8 | "os/signal"
9 | "syscall"
10 | "templtest/internal/services"
11 |
12 | "time"
13 | )
14 |
15 | type Server struct {
16 | mux *http.ServeMux
17 | service *services.RegisterService
18 | }
19 |
20 | func New(service *services.RegisterService) *Server {
21 | s := &Server{service: service}
22 | s.register()
23 | return s
24 | }
25 |
26 | func (s *Server) register() {
27 | handlers := http.NewServeMux()
28 | main := http.NewServeMux()
29 | fsHandler := http.FileServer(http.Dir("./public"))
30 | main.Handle("/public/", http.StripPrefix("/public/", fsHandler))
31 | main.HandleFunc("/", s.index)
32 | handlers.Handle("/metrics/", http.StripPrefix("/metrics", main))
33 | s.mux = handlers
34 | }
35 |
36 | func (s *Server) Serve() {
37 | ctx, cancel := context.WithCancel(context.Background())
38 | defer cancel()
39 |
40 | // Signal to close app
41 | osSignal := make(chan os.Signal, 1)
42 | signal.Notify(osSignal, os.Interrupt, syscall.SIGTERM)
43 |
44 | server := &http.Server{
45 | Addr: ":4000",
46 | Handler: s.mux,
47 | }
48 |
49 | go func() {
50 | log.Printf("Starting server on port 4000")
51 | if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
52 | log.Fatalf("Server failed: %v", err)
53 | }
54 | }()
55 |
56 | <-osSignal
57 | log.Println("Shutdown signal received. Shutting down...")
58 |
59 | // Timeout to close handlers
60 | shutdownCtx, shutdownCancel := context.WithTimeout(ctx, 30*time.Second)
61 | defer shutdownCancel()
62 |
63 | // Stop the server
64 | if err := server.Shutdown(shutdownCtx); err != nil {
65 | log.Fatalf("Server shutdown failed: %v", err)
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/internal/transport/web/utils.go:
--------------------------------------------------------------------------------
1 | package web
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "net/http"
7 | "time"
8 |
9 | "github.com/golang-jwt/jwt/v5"
10 | )
11 |
12 | func getAuthCookie(r *http.Request) (string, error) {
13 | cookies := r.Cookies()
14 | for _, cookie := range cookies {
15 | if cookie.Name == "Authorization" {
16 | return cookie.Value, nil
17 | }
18 | }
19 |
20 | return "", errors.New("Cookies not found")
21 | }
22 |
23 | type MyClaims struct {
24 | UserID string `json:"user_id"`
25 | UserEmail string `json:"user_email"`
26 | jwt.RegisteredClaims
27 | }
28 |
29 | func CreateJWT(userID string, userEmail string) (string, error) {
30 | jwtKey := []byte("some-key")
31 | expirationTime := time.Now().Add(24 * 60 * time.Hour)
32 |
33 | claims := &MyClaims{
34 | UserID: userID,
35 | UserEmail: userEmail,
36 | RegisteredClaims: jwt.RegisteredClaims{
37 | ExpiresAt: jwt.NewNumericDate(expirationTime),
38 | },
39 | }
40 |
41 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
42 |
43 | tokenString, err := token.SignedString(jwtKey)
44 | if err != nil {
45 | return "", err
46 | }
47 |
48 | return tokenString, nil
49 | }
50 |
51 | func VerifyJWT(tokenString string) (*MyClaims, error) {
52 | jwtKey := []byte("some-key")
53 | claims := &MyClaims{}
54 |
55 | token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
56 | return jwtKey, nil
57 | })
58 | if err != nil {
59 | return nil, err
60 | }
61 |
62 | if !token.Valid {
63 | return nil, fmt.Errorf("invalid token")
64 | }
65 |
66 | return claims, nil
67 | }
68 |
69 | func createCookie(exp time.Time, key string, value string) http.Cookie {
70 | return http.Cookie{
71 | Name: key,
72 | Value: value,
73 | Expires: exp,
74 | HttpOnly: false,
75 | Path: "/",
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
2 | github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
3 | github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
4 | github.com/a-h/templ v0.2.747 h1:D0dQ2lxC3W7Dxl6fxQ/1zZHBQslSkTSvl5FxP/CfdKg=
5 | github.com/a-h/templ v0.2.747/go.mod h1:69ObQIbrcuwPCU32ohNaWce3Cb7qM5GMiqN1K+2yop4=
6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7 | github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
8 | github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
9 | github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
10 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
11 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
12 | github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
13 | github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
14 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
15 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
16 | github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
17 | github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
18 | github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
19 | github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
20 | github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
21 | github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
22 | github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
23 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
24 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
25 |
--------------------------------------------------------------------------------
/views/layouts/base_templ.go:
--------------------------------------------------------------------------------
1 | // Code generated by templ - DO NOT EDIT.
2 |
3 | // templ: version: v0.2.747
4 | package layouts
5 |
6 | //lint:file-ignore SA4006 This context is only used if a nested component is present.
7 |
8 | import "github.com/a-h/templ"
9 | import templruntime "github.com/a-h/templ/runtime"
10 |
11 | func BaseLayout(title string) templ.Component {
12 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
13 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
14 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
15 | if !templ_7745c5c3_IsBuffer {
16 | defer func() {
17 | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
18 | if templ_7745c5c3_Err == nil {
19 | templ_7745c5c3_Err = templ_7745c5c3_BufErr
20 | }
21 | }()
22 | }
23 | ctx = templ.InitializeContext(ctx)
24 | templ_7745c5c3_Var1 := templ.GetChildren(ctx)
25 | if templ_7745c5c3_Var1 == nil {
26 | templ_7745c5c3_Var1 = templ.NopComponent
27 | }
28 | ctx = templ.ClearChildren(ctx)
29 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
30 | if templ_7745c5c3_Err != nil {
31 | return templ_7745c5c3_Err
32 | }
33 | var templ_7745c5c3_Var2 string
34 | templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title)
35 | if templ_7745c5c3_Err != nil {
36 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/layouts/base.templ`, Line: 9, Col: 17}
37 | }
38 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
39 | if templ_7745c5c3_Err != nil {
40 | return templ_7745c5c3_Err
41 | }
42 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
43 | if templ_7745c5c3_Err != nil {
44 | return templ_7745c5c3_Err
45 | }
46 | templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
47 | if templ_7745c5c3_Err != nil {
48 | return templ_7745c5c3_Err
49 | }
50 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
51 | if templ_7745c5c3_Err != nil {
52 | return templ_7745c5c3_Err
53 | }
54 | return templ_7745c5c3_Err
55 | })
56 | }
57 |
--------------------------------------------------------------------------------
/views/pages/index_templ.go:
--------------------------------------------------------------------------------
1 | // Code generated by templ - DO NOT EDIT.
2 |
3 | // templ: version: v0.2.747
4 | package pages
5 |
6 | //lint:file-ignore SA4006 This context is only used if a nested component is present.
7 |
8 | import "github.com/a-h/templ"
9 | import templruntime "github.com/a-h/templ/runtime"
10 |
11 | import (
12 | "templtest/internal/entities"
13 | "templtest/views/layouts"
14 | )
15 |
16 | func Index(users []entities.User) templ.Component {
17 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
18 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
19 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
20 | if !templ_7745c5c3_IsBuffer {
21 | defer func() {
22 | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
23 | if templ_7745c5c3_Err == nil {
24 | templ_7745c5c3_Err = templ_7745c5c3_BufErr
25 | }
26 | }()
27 | }
28 | ctx = templ.InitializeContext(ctx)
29 | templ_7745c5c3_Var1 := templ.GetChildren(ctx)
30 | if templ_7745c5c3_Var1 == nil {
31 | templ_7745c5c3_Var1 = templ.NopComponent
32 | }
33 | ctx = templ.ClearChildren(ctx)
34 | templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
35 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
36 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
37 | if !templ_7745c5c3_IsBuffer {
38 | defer func() {
39 | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
40 | if templ_7745c5c3_Err == nil {
41 | templ_7745c5c3_Err = templ_7745c5c3_BufErr
42 | }
43 | }()
44 | }
45 | ctx = templ.InitializeContext(ctx)
46 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
47 | if templ_7745c5c3_Err != nil {
48 | return templ_7745c5c3_Err
49 | }
50 | for _, user := range users {
51 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
52 | if templ_7745c5c3_Err != nil {
53 | return templ_7745c5c3_Err
54 | }
55 | var templ_7745c5c3_Var3 string
56 | templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(user.Name)
57 | if templ_7745c5c3_Err != nil {
58 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/pages/index.templ`, Line: 13, Col: 26}
59 | }
60 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
61 | if templ_7745c5c3_Err != nil {
62 | return templ_7745c5c3_Err
63 | }
64 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
65 | if templ_7745c5c3_Err != nil {
66 | return templ_7745c5c3_Err
67 | }
68 | var templ_7745c5c3_Var4 string
69 | templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email)
70 | if templ_7745c5c3_Err != nil {
71 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/pages/index.templ`, Line: 14, Col: 27}
72 | }
73 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
74 | if templ_7745c5c3_Err != nil {
75 | return templ_7745c5c3_Err
76 | }
77 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
78 | if templ_7745c5c3_Err != nil {
79 | return templ_7745c5c3_Err
80 | }
81 | }
82 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
83 | if templ_7745c5c3_Err != nil {
84 | return templ_7745c5c3_Err
85 | }
86 | return templ_7745c5c3_Err
87 | })
88 | templ_7745c5c3_Err = layouts.BaseLayout("Admin | Syncword").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
89 | if templ_7745c5c3_Err != nil {
90 | return templ_7745c5c3_Err
91 | }
92 | return templ_7745c5c3_Err
93 | })
94 | }
95 |
--------------------------------------------------------------------------------
/public/style.css:
--------------------------------------------------------------------------------
1 | /*
2 | ! tailwindcss v3.4.4 | MIT License | https://tailwindcss.com
3 | */
4 |
5 | /*
6 | 1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
7 | 2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
8 | */
9 |
10 | *,
11 | ::before,
12 | ::after {
13 | box-sizing: border-box;
14 | /* 1 */
15 | border-width: 0;
16 | /* 2 */
17 | border-style: solid;
18 | /* 2 */
19 | border-color: #e5e7eb;
20 | /* 2 */
21 | }
22 |
23 | ::before,
24 | ::after {
25 | --tw-content: '';
26 | }
27 |
28 | /*
29 | 1. Use a consistent sensible line-height in all browsers.
30 | 2. Prevent adjustments of font size after orientation changes in iOS.
31 | 3. Use a more readable tab size.
32 | 4. Use the user's configured `sans` font-family by default.
33 | 5. Use the user's configured `sans` font-feature-settings by default.
34 | 6. Use the user's configured `sans` font-variation-settings by default.
35 | 7. Disable tap highlights on iOS
36 | */
37 |
38 | html,
39 | :host {
40 | line-height: 1.5;
41 | /* 1 */
42 | -webkit-text-size-adjust: 100%;
43 | /* 2 */
44 | -moz-tab-size: 4;
45 | /* 3 */
46 | -o-tab-size: 4;
47 | tab-size: 4;
48 | /* 3 */
49 | font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
50 | /* 4 */
51 | font-feature-settings: normal;
52 | /* 5 */
53 | font-variation-settings: normal;
54 | /* 6 */
55 | -webkit-tap-highlight-color: transparent;
56 | /* 7 */
57 | }
58 |
59 | /*
60 | 1. Remove the margin in all browsers.
61 | 2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
62 | */
63 |
64 | body {
65 | margin: 0;
66 | /* 1 */
67 | line-height: inherit;
68 | /* 2 */
69 | }
70 |
71 | /*
72 | 1. Add the correct height in Firefox.
73 | 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
74 | 3. Ensure horizontal rules are visible by default.
75 | */
76 |
77 | hr {
78 | height: 0;
79 | /* 1 */
80 | color: inherit;
81 | /* 2 */
82 | border-top-width: 1px;
83 | /* 3 */
84 | }
85 |
86 | /*
87 | Add the correct text decoration in Chrome, Edge, and Safari.
88 | */
89 |
90 | abbr:where([title]) {
91 | -webkit-text-decoration: underline dotted;
92 | text-decoration: underline dotted;
93 | }
94 |
95 | /*
96 | Remove the default font size and weight for headings.
97 | */
98 |
99 | h1,
100 | h2,
101 | h3,
102 | h4,
103 | h5,
104 | h6 {
105 | font-size: inherit;
106 | font-weight: inherit;
107 | }
108 |
109 | /*
110 | Reset links to optimize for opt-in styling instead of opt-out.
111 | */
112 |
113 | a {
114 | color: inherit;
115 | text-decoration: inherit;
116 | }
117 |
118 | /*
119 | Add the correct font weight in Edge and Safari.
120 | */
121 |
122 | b,
123 | strong {
124 | font-weight: bolder;
125 | }
126 |
127 | /*
128 | 1. Use the user's configured `mono` font-family by default.
129 | 2. Use the user's configured `mono` font-feature-settings by default.
130 | 3. Use the user's configured `mono` font-variation-settings by default.
131 | 4. Correct the odd `em` font sizing in all browsers.
132 | */
133 |
134 | code,
135 | kbd,
136 | samp,
137 | pre {
138 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
139 | /* 1 */
140 | font-feature-settings: normal;
141 | /* 2 */
142 | font-variation-settings: normal;
143 | /* 3 */
144 | font-size: 1em;
145 | /* 4 */
146 | }
147 |
148 | /*
149 | Add the correct font size in all browsers.
150 | */
151 |
152 | small {
153 | font-size: 80%;
154 | }
155 |
156 | /*
157 | Prevent `sub` and `sup` elements from affecting the line height in all browsers.
158 | */
159 |
160 | sub,
161 | sup {
162 | font-size: 75%;
163 | line-height: 0;
164 | position: relative;
165 | vertical-align: baseline;
166 | }
167 |
168 | sub {
169 | bottom: -0.25em;
170 | }
171 |
172 | sup {
173 | top: -0.5em;
174 | }
175 |
176 | /*
177 | 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
178 | 2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
179 | 3. Remove gaps between table borders by default.
180 | */
181 |
182 | table {
183 | text-indent: 0;
184 | /* 1 */
185 | border-color: inherit;
186 | /* 2 */
187 | border-collapse: collapse;
188 | /* 3 */
189 | }
190 |
191 | /*
192 | 1. Change the font styles in all browsers.
193 | 2. Remove the margin in Firefox and Safari.
194 | 3. Remove default padding in all browsers.
195 | */
196 |
197 | button,
198 | input,
199 | optgroup,
200 | select,
201 | textarea {
202 | font-family: inherit;
203 | /* 1 */
204 | font-feature-settings: inherit;
205 | /* 1 */
206 | font-variation-settings: inherit;
207 | /* 1 */
208 | font-size: 100%;
209 | /* 1 */
210 | font-weight: inherit;
211 | /* 1 */
212 | line-height: inherit;
213 | /* 1 */
214 | letter-spacing: inherit;
215 | /* 1 */
216 | color: inherit;
217 | /* 1 */
218 | margin: 0;
219 | /* 2 */
220 | padding: 0;
221 | /* 3 */
222 | }
223 |
224 | /*
225 | Remove the inheritance of text transform in Edge and Firefox.
226 | */
227 |
228 | button,
229 | select {
230 | text-transform: none;
231 | }
232 |
233 | /*
234 | 1. Correct the inability to style clickable types in iOS and Safari.
235 | 2. Remove default button styles.
236 | */
237 |
238 | button,
239 | input:where([type='button']),
240 | input:where([type='reset']),
241 | input:where([type='submit']) {
242 | -webkit-appearance: button;
243 | /* 1 */
244 | background-color: transparent;
245 | /* 2 */
246 | background-image: none;
247 | /* 2 */
248 | }
249 |
250 | /*
251 | Use the modern Firefox focus style for all focusable elements.
252 | */
253 |
254 | :-moz-focusring {
255 | outline: auto;
256 | }
257 |
258 | /*
259 | Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
260 | */
261 |
262 | :-moz-ui-invalid {
263 | box-shadow: none;
264 | }
265 |
266 | /*
267 | Add the correct vertical alignment in Chrome and Firefox.
268 | */
269 |
270 | progress {
271 | vertical-align: baseline;
272 | }
273 |
274 | /*
275 | Correct the cursor style of increment and decrement buttons in Safari.
276 | */
277 |
278 | ::-webkit-inner-spin-button,
279 | ::-webkit-outer-spin-button {
280 | height: auto;
281 | }
282 |
283 | /*
284 | 1. Correct the odd appearance in Chrome and Safari.
285 | 2. Correct the outline style in Safari.
286 | */
287 |
288 | [type='search'] {
289 | -webkit-appearance: textfield;
290 | /* 1 */
291 | outline-offset: -2px;
292 | /* 2 */
293 | }
294 |
295 | /*
296 | Remove the inner padding in Chrome and Safari on macOS.
297 | */
298 |
299 | ::-webkit-search-decoration {
300 | -webkit-appearance: none;
301 | }
302 |
303 | /*
304 | 1. Correct the inability to style clickable types in iOS and Safari.
305 | 2. Change font properties to `inherit` in Safari.
306 | */
307 |
308 | ::-webkit-file-upload-button {
309 | -webkit-appearance: button;
310 | /* 1 */
311 | font: inherit;
312 | /* 2 */
313 | }
314 |
315 | /*
316 | Add the correct display in Chrome and Safari.
317 | */
318 |
319 | summary {
320 | display: list-item;
321 | }
322 |
323 | /*
324 | Removes the default spacing and border for appropriate elements.
325 | */
326 |
327 | blockquote,
328 | dl,
329 | dd,
330 | h1,
331 | h2,
332 | h3,
333 | h4,
334 | h5,
335 | h6,
336 | hr,
337 | figure,
338 | p,
339 | pre {
340 | margin: 0;
341 | }
342 |
343 | fieldset {
344 | margin: 0;
345 | padding: 0;
346 | }
347 |
348 | legend {
349 | padding: 0;
350 | }
351 |
352 | ol,
353 | ul,
354 | menu {
355 | list-style: none;
356 | margin: 0;
357 | padding: 0;
358 | }
359 |
360 | /*
361 | Reset default styling for dialogs.
362 | */
363 |
364 | dialog {
365 | padding: 0;
366 | }
367 |
368 | /*
369 | Prevent resizing textareas horizontally by default.
370 | */
371 |
372 | textarea {
373 | resize: vertical;
374 | }
375 |
376 | /*
377 | 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
378 | 2. Set the default placeholder color to the user's configured gray 400 color.
379 | */
380 |
381 | input::-moz-placeholder, textarea::-moz-placeholder {
382 | opacity: 1;
383 | /* 1 */
384 | color: #9ca3af;
385 | /* 2 */
386 | }
387 |
388 | input::placeholder,
389 | textarea::placeholder {
390 | opacity: 1;
391 | /* 1 */
392 | color: #9ca3af;
393 | /* 2 */
394 | }
395 |
396 | /*
397 | Set the default cursor for buttons.
398 | */
399 |
400 | button,
401 | [role="button"] {
402 | cursor: pointer;
403 | }
404 |
405 | /*
406 | Make sure disabled buttons don't get the pointer cursor.
407 | */
408 |
409 | :disabled {
410 | cursor: default;
411 | }
412 |
413 | /*
414 | 1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
415 | 2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
416 | This can trigger a poorly considered lint error in some tools but is included by design.
417 | */
418 |
419 | img,
420 | svg,
421 | video,
422 | canvas,
423 | audio,
424 | iframe,
425 | embed,
426 | object {
427 | display: block;
428 | /* 1 */
429 | vertical-align: middle;
430 | /* 2 */
431 | }
432 |
433 | /*
434 | Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
435 | */
436 |
437 | img,
438 | video {
439 | max-width: 100%;
440 | height: auto;
441 | }
442 |
443 | /* Make elements with the HTML hidden attribute stay hidden by default */
444 |
445 | [hidden] {
446 | display: none;
447 | }
448 |
449 | *, ::before, ::after {
450 | --tw-border-spacing-x: 0;
451 | --tw-border-spacing-y: 0;
452 | --tw-translate-x: 0;
453 | --tw-translate-y: 0;
454 | --tw-rotate: 0;
455 | --tw-skew-x: 0;
456 | --tw-skew-y: 0;
457 | --tw-scale-x: 1;
458 | --tw-scale-y: 1;
459 | --tw-pan-x: ;
460 | --tw-pan-y: ;
461 | --tw-pinch-zoom: ;
462 | --tw-scroll-snap-strictness: proximity;
463 | --tw-gradient-from-position: ;
464 | --tw-gradient-via-position: ;
465 | --tw-gradient-to-position: ;
466 | --tw-ordinal: ;
467 | --tw-slashed-zero: ;
468 | --tw-numeric-figure: ;
469 | --tw-numeric-spacing: ;
470 | --tw-numeric-fraction: ;
471 | --tw-ring-inset: ;
472 | --tw-ring-offset-width: 0px;
473 | --tw-ring-offset-color: #fff;
474 | --tw-ring-color: rgb(59 130 246 / 0.5);
475 | --tw-ring-offset-shadow: 0 0 #0000;
476 | --tw-ring-shadow: 0 0 #0000;
477 | --tw-shadow: 0 0 #0000;
478 | --tw-shadow-colored: 0 0 #0000;
479 | --tw-blur: ;
480 | --tw-brightness: ;
481 | --tw-contrast: ;
482 | --tw-grayscale: ;
483 | --tw-hue-rotate: ;
484 | --tw-invert: ;
485 | --tw-saturate: ;
486 | --tw-sepia: ;
487 | --tw-drop-shadow: ;
488 | --tw-backdrop-blur: ;
489 | --tw-backdrop-brightness: ;
490 | --tw-backdrop-contrast: ;
491 | --tw-backdrop-grayscale: ;
492 | --tw-backdrop-hue-rotate: ;
493 | --tw-backdrop-invert: ;
494 | --tw-backdrop-opacity: ;
495 | --tw-backdrop-saturate: ;
496 | --tw-backdrop-sepia: ;
497 | --tw-contain-size: ;
498 | --tw-contain-layout: ;
499 | --tw-contain-paint: ;
500 | --tw-contain-style: ;
501 | }
502 |
503 | ::backdrop {
504 | --tw-border-spacing-x: 0;
505 | --tw-border-spacing-y: 0;
506 | --tw-translate-x: 0;
507 | --tw-translate-y: 0;
508 | --tw-rotate: 0;
509 | --tw-skew-x: 0;
510 | --tw-skew-y: 0;
511 | --tw-scale-x: 1;
512 | --tw-scale-y: 1;
513 | --tw-pan-x: ;
514 | --tw-pan-y: ;
515 | --tw-pinch-zoom: ;
516 | --tw-scroll-snap-strictness: proximity;
517 | --tw-gradient-from-position: ;
518 | --tw-gradient-via-position: ;
519 | --tw-gradient-to-position: ;
520 | --tw-ordinal: ;
521 | --tw-slashed-zero: ;
522 | --tw-numeric-figure: ;
523 | --tw-numeric-spacing: ;
524 | --tw-numeric-fraction: ;
525 | --tw-ring-inset: ;
526 | --tw-ring-offset-width: 0px;
527 | --tw-ring-offset-color: #fff;
528 | --tw-ring-color: rgb(59 130 246 / 0.5);
529 | --tw-ring-offset-shadow: 0 0 #0000;
530 | --tw-ring-shadow: 0 0 #0000;
531 | --tw-shadow: 0 0 #0000;
532 | --tw-shadow-colored: 0 0 #0000;
533 | --tw-blur: ;
534 | --tw-brightness: ;
535 | --tw-contrast: ;
536 | --tw-grayscale: ;
537 | --tw-hue-rotate: ;
538 | --tw-invert: ;
539 | --tw-saturate: ;
540 | --tw-sepia: ;
541 | --tw-drop-shadow: ;
542 | --tw-backdrop-blur: ;
543 | --tw-backdrop-brightness: ;
544 | --tw-backdrop-contrast: ;
545 | --tw-backdrop-grayscale: ;
546 | --tw-backdrop-hue-rotate: ;
547 | --tw-backdrop-invert: ;
548 | --tw-backdrop-opacity: ;
549 | --tw-backdrop-saturate: ;
550 | --tw-backdrop-sepia: ;
551 | --tw-contain-size: ;
552 | --tw-contain-layout: ;
553 | --tw-contain-paint: ;
554 | --tw-contain-style: ;
555 | }
556 |
557 | .flex {
558 | display: flex;
559 | }
560 |
561 | .w-full {
562 | width: 100%;
563 | }
564 |
565 | .flex-col {
566 | flex-direction: column;
567 | }
568 |
569 | .justify-center {
570 | justify-content: center;
571 | }
572 |
573 | .gap-2 {
574 | gap: 0.5rem;
575 | }
576 |
577 | .py-16 {
578 | padding-top: 4rem;
579 | padding-bottom: 4rem;
580 | }
581 |
582 | @keyframes fade-in {
583 | from {
584 | opacity: 0;
585 | }
586 | }
587 |
588 | @keyframes fade-out {
589 | to {
590 | opacity: 0;
591 | }
592 | }
593 |
594 | @keyframes slide-from-right {
595 | from {
596 | transform: translateX(90px);
597 | }
598 | }
599 |
600 | @keyframes slide-to-left {
601 | to {
602 | transform: translateX(-90px);
603 | }
604 | }
605 |
606 | /* define animations for the old and new content */
607 |
608 | ::view-transition-old(slide-it) {
609 | animation: 180ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
610 | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
611 | }
612 |
613 | ::view-transition-new(slide-it) {
614 | animation: 420ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
615 | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
616 | }
617 |
618 | /* tie the view transition to a given CSS class */
619 |
620 | .sample-transition {
621 | view-transition-name: slide-it;
622 | }
623 |
--------------------------------------------------------------------------------