├── .gitignore
├── main.db
├── postcss.config.js
├── cmd
└── main.go
├── Makefile
├── views
├── components
│ ├── Hero_templ.txt
│ ├── Hero.templ
│ ├── Header_templ.txt
│ ├── Header.templ
│ ├── Hero_templ.go
│ └── Header_templ.go
├── layouts
│ ├── base_templ.txt
│ ├── base.templ
│ └── base_templ.go
├── pages
│ ├── login_templ.txt
│ ├── index_templ.txt
│ ├── login.templ
│ ├── index.templ
│ ├── login_templ.go
│ └── index_templ.go
└── css
│ └── main.css
├── tailwind.config.js
├── package.json
├── go.mod
├── internal
├── entities
│ └── todo.go
├── storage
│ └── sqlite
│ │ ├── user.go
│ │ ├── new.go
│ │ └── todo.go
├── transport
│ └── web
│ │ ├── new.go
│ │ ├── utils.go
│ │ └── handlers.go
└── services
│ └── new.go
├── .air.toml
├── go.sum
└── public
└── style.css
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | tmp/
--------------------------------------------------------------------------------
/main.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ethanhamilthon/gohtmx_boiler/main/main.db
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "templtest/internal/transport/web"
4 |
5 | func main() {
6 | server := web.New()
7 | server.Serve()
8 | }
9 |
--------------------------------------------------------------------------------
/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 --proxy="http://localhost:4000" --open-browser=false
9 |
--------------------------------------------------------------------------------
/views/components/Hero_templ.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/views/components/Hero.templ:
--------------------------------------------------------------------------------
1 | package components
2 |
3 | templ Hero(heading string){
4 |
5 | {heading}
6 |
7 |
8 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/views/components/Header_templ.txt:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
12 | github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
13 | github.com/mattn/go-sqlite3 v1.14.22 // indirect
14 | )
15 |
--------------------------------------------------------------------------------
/views/layouts/base_templ.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/internal/entities/todo.go:
--------------------------------------------------------------------------------
1 | package entities
2 |
3 | type Todo struct {
4 | ID string `json:"id" db:"id"`
5 | Title string `json:"title" db:"title"`
6 | IsCompleted string `json:"is_completed" db:"is_completed"`
7 | UserID string `json:"user_id" db:"user_id"`
8 | CreatedAt string `json:"created_at" db:"created_at"`
9 | }
10 |
11 | type User struct {
12 | ID string `json:"id" db:"id"`
13 | Name string `json:"name" db:"name"`
14 | Email string `json:"email" db:"email"`
15 | Password string `json:"password" db:"password"`
16 | CreatedAt string `json:"created_at" db:"created_at"`
17 | }
18 |
--------------------------------------------------------------------------------
/views/components/Header.templ:
--------------------------------------------------------------------------------
1 | package components
2 | import (
3 | "templtest/internal/entities"
4 | )
5 |
6 |
7 | templ Header(user entities.User){
8 |
14 | }
15 |
16 | templ Profile(userName, userEmail string){
17 |
18 | {userName}
19 | {userEmail}
20 |
21 | }
--------------------------------------------------------------------------------
/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 | {children...}
16 |
17 |
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/views/pages/login_templ.txt:
--------------------------------------------------------------------------------
1 | Welcome to system
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/internal/storage/sqlite/user.go:
--------------------------------------------------------------------------------
1 | package sqlite
2 |
3 | import (
4 | "templtest/internal/entities"
5 |
6 | sq "github.com/Masterminds/squirrel"
7 | )
8 |
9 | func (repo *Repository) GetUser(Email string) (entities.User, error) {
10 | var user entities.User
11 |
12 | query, args, err := sq.Select("*").
13 | From("users").
14 | Where(sq.Eq{"email": Email}).
15 | ToSql()
16 | if err != nil {
17 | return user, err
18 | }
19 |
20 | err = repo.db.Get(&user, query, args...)
21 | if err != nil {
22 | return user, err
23 | }
24 |
25 | return user, nil
26 | }
27 |
28 | func (repo *Repository) CreateUser(user entities.User) error {
29 |
30 | query, args, err := sq.Insert("users").
31 | Columns("id", "email", "name", "password").
32 | Values(user.ID, user.Email, user.Name, user.Password).
33 | ToSql()
34 | if err != nil {
35 | return err
36 | }
37 |
38 | _, err = repo.db.Exec(query, args...)
39 | if err != nil {
40 | return err
41 | }
42 |
43 | return nil
44 | }
45 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/views/pages/index_templ.txt:
--------------------------------------------------------------------------------
1 |
5 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/views/pages/login.templ:
--------------------------------------------------------------------------------
1 | package pages
2 |
3 | import "templtest/views/layouts"
4 |
5 | templ LoginPage(err string){
6 | @layouts.BaseLayout("Go app"){
7 |
8 |
9 |
Welcome to system
10 | if err != "" {
11 | {err}
12 | }
13 |
14 |
23 |
24 | }
25 |
26 | }
--------------------------------------------------------------------------------
/internal/storage/sqlite/new.go:
--------------------------------------------------------------------------------
1 | package sqlite
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/jmoiron/sqlx"
7 | _ "github.com/mattn/go-sqlite3"
8 | )
9 |
10 | type Repository struct {
11 | db *sqlx.DB
12 | }
13 |
14 | func New() (*Repository, error) {
15 | db, err := sqlx.Open("sqlite3", "./main.db")
16 | if err != nil {
17 | return &Repository{}, err
18 | }
19 |
20 | err = db.Ping()
21 | if err != nil {
22 | return &Repository{}, err
23 | }
24 |
25 | newDB := &Repository{
26 | db: db,
27 | }
28 | err = newDB.runMigration()
29 | if err != nil {
30 | return &Repository{}, err
31 | }
32 | fmt.Println("db connected")
33 | return newDB, nil
34 | }
35 |
36 | func (repo *Repository) Close() {
37 | repo.db.Close()
38 | }
39 |
40 | func (repo *Repository) runMigration() error {
41 | _, err := repo.db.Exec(`
42 | CREATE TABLE IF NOT EXISTS todos (
43 | id TEXT PRIMARY KEY,
44 | title TEXT,
45 | is_completed TEXT,
46 | user_id TEXT,
47 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
48 | FOREIGN KEY(user_id) REFERENCES users(id)
49 | )
50 | `)
51 | if err != nil {
52 | return err
53 | }
54 |
55 | // Создание таблицы languages
56 | _, err = repo.db.Exec(`
57 | CREATE TABLE IF NOT EXISTS users (
58 | id TEXT PRIMARY KEY,
59 | email TEXT UNIQUE,
60 | name TEXT,
61 | password TEXT,
62 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
63 | )
64 | `)
65 | if err != nil {
66 | return err
67 | }
68 |
69 | return nil
70 | }
71 |
--------------------------------------------------------------------------------
/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 |
11 | "time"
12 | )
13 |
14 | type Server struct {
15 | mux *http.ServeMux
16 | }
17 |
18 | func New() *Server {
19 | main := http.NewServeMux()
20 | handlers := NewHandlers()
21 | fsHandler := http.FileServer(http.Dir("./public"))
22 | main.Handle("/public/", http.StripPrefix("/public/", fsHandler))
23 | main.HandleFunc("/", handlers.Index)
24 | main.HandleFunc("/login", handlers.Login)
25 | main.HandleFunc("POST /todos", handlers.CreateTodo)
26 | main.HandleFunc("DELETE /todos/{id}", handlers.DeleteTodo)
27 | return &Server{mux: main}
28 | }
29 |
30 | func (s *Server) Serve() {
31 | ctx, cancel := context.WithCancel(context.Background())
32 | defer cancel()
33 |
34 | // Signal to close app
35 | osSignal := make(chan os.Signal, 1)
36 | signal.Notify(osSignal, os.Interrupt, syscall.SIGTERM)
37 |
38 | server := &http.Server{
39 | Addr: ":4000",
40 | Handler: s.mux,
41 | }
42 |
43 | go func() {
44 | log.Printf("Starting server on port 4000")
45 | if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
46 | log.Fatalf("Server failed: %v", err)
47 | }
48 | }()
49 |
50 | select {
51 | case <-osSignal:
52 | log.Println("Shutdown signal received. Shutting down...")
53 |
54 | // Timeout to close handlers
55 | shutdownCtx, shutdownCancel := context.WithTimeout(ctx, 30*time.Second)
56 | defer shutdownCancel()
57 |
58 | // Stop the server
59 | if err := server.Shutdown(shutdownCtx); err != nil {
60 | log.Fatalf("Server shutdown failed: %v", err)
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/internal/services/new.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "fmt"
5 | "templtest/internal/entities"
6 |
7 | "github.com/google/uuid"
8 | )
9 |
10 | type DB interface {
11 | CreateTodo(todo entities.Todo) error
12 | GetTodos(userID string) ([]entities.Todo, error)
13 | UpdateTodo(todo entities.Todo) error
14 | DeleteTodo(string, string) error
15 | GetUser(Email string) (entities.User, error)
16 | CreateUser(user entities.User) error
17 | }
18 |
19 | type Service struct {
20 | db DB
21 | }
22 |
23 | func New(db DB) *Service {
24 | return &Service{db}
25 | }
26 |
27 | func (s *Service) UserLogin(Email, Name, Password string) (entities.User, error) {
28 | usergetted, err := s.db.GetUser(Email)
29 | if err != nil {
30 | fmt.Println(err.Error())
31 | user := entities.User{
32 | ID: uuid.New().String(),
33 | Email: Email,
34 | Name: Name,
35 | Password: Password,
36 | }
37 | err = s.db.CreateUser(user)
38 | if err != nil {
39 | fmt.Println(err.Error())
40 | return entities.User{}, err
41 | }
42 | return user, nil
43 | }
44 | return usergetted, nil
45 | }
46 |
47 | func (s *Service) User(Email string) (entities.User, error) {
48 | return s.db.GetUser(Email)
49 | }
50 |
51 | func (s *Service) Todos(UserID string) ([]entities.Todo, error) {
52 | return s.db.GetTodos(UserID)
53 | }
54 |
55 | func (s *Service) CreateTodos(Title, UserID string) (entities.Todo, error) {
56 | newTodo := entities.Todo{
57 | ID: uuid.New().String(),
58 | Title: Title,
59 | IsCompleted: "false",
60 | UserID: UserID,
61 | }
62 |
63 | return newTodo, s.db.CreateTodo(newTodo)
64 | }
65 |
66 | func (s *Service) DeleteTodo(ID string, UserID string) error {
67 | return s.db.DeleteTodo(ID, UserID)
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 |
--------------------------------------------------------------------------------
/views/components/Hero_templ.go:
--------------------------------------------------------------------------------
1 | // Code generated by templ - DO NOT EDIT.
2 |
3 | // templ: version: v0.2.747
4 | package components
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 Hero(heading 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.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
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(heading)
35 | if templ_7745c5c3_Err != nil {
36 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/components/Hero.templ`, Line: 5, Col: 43}
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.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
43 | if templ_7745c5c3_Err != nil {
44 | return templ_7745c5c3_Err
45 | }
46 | return templ_7745c5c3_Err
47 | })
48 | }
49 |
--------------------------------------------------------------------------------
/views/pages/index.templ:
--------------------------------------------------------------------------------
1 | package pages
2 |
3 | import (
4 | "templtest/views/components"
5 | "templtest/views/layouts"
6 | "templtest/internal/entities"
7 | "fmt"
8 | )
9 |
10 | templ Index(user entities.User, todos []entities.Todo){
11 | @layouts.BaseLayout("Go app"){
12 | @components.Header(user)
13 |
14 |
15 |
16 | @TodoList(todos)
17 |
18 | Пока нету Todos
19 |
20 |
21 | @TodoCreate()
22 |
23 |
24 | }
25 | }
26 |
27 | templ TodoComponent(todo entities.Todo){
28 |
29 | {todo.Title}
30 |
32 |
33 | }
34 |
35 | templ TodoList(todos []entities.Todo){
36 | if len(todos) > 0 {
37 | for _, todo := range todos{
38 | @TodoComponent(todo)
39 | }
40 | }
41 | }
42 |
43 | templ TodoCreate(){
44 |
49 | }
--------------------------------------------------------------------------------
/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/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
15 | github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
16 | github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
17 | github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
18 | github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
19 | github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
20 | github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
21 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
22 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
23 |
--------------------------------------------------------------------------------
/internal/storage/sqlite/todo.go:
--------------------------------------------------------------------------------
1 | package sqlite
2 |
3 | import (
4 | "templtest/internal/entities"
5 |
6 | "github.com/Masterminds/squirrel"
7 | sq "github.com/Masterminds/squirrel"
8 | )
9 |
10 | func (repo *Repository) CreateTodo(todo entities.Todo) error {
11 | // Создание запроса с использованием Squirrel
12 | query, args, err := sq.Insert("todos").
13 | Columns("id", "title", "is_completed", "user_id").
14 | Values(todo.ID, todo.Title, todo.IsCompleted, todo.UserID).
15 | ToSql()
16 | if err != nil {
17 | return err
18 | }
19 |
20 | // Выполнение запроса
21 | _, err = repo.db.Exec(query, args...)
22 | if err != nil {
23 | return err
24 | }
25 |
26 | return nil
27 | }
28 |
29 | // Функция для получения всех задач пользователя с использованием Squirrel
30 | func (repo *Repository) GetTodos(userID string) ([]entities.Todo, error) {
31 | var todos []entities.Todo
32 |
33 | // Создание запроса с использованием Squirrel
34 | query, args, err := squirrel.Select("*").
35 | From("todos").
36 | Where(squirrel.Eq{"user_id": userID}).
37 | OrderBy("created_at DESC").
38 | ToSql()
39 | if err != nil {
40 | return nil, err
41 | }
42 |
43 | // Выполнение запроса и сканирование результата в срез Todo
44 | err = repo.db.Select(&todos, query, args...)
45 | if err != nil {
46 | return nil, err
47 | }
48 |
49 | return todos, nil
50 | }
51 |
52 | // Функция для обновления задачи (todo) с использованием Squirrel
53 | func (repo *Repository) UpdateTodo(todo entities.Todo) error {
54 | // Создание запроса с использованием Squirrel
55 | query, args, err := squirrel.Update("todos").
56 | Set("title", todo.Title).
57 | Set("is_completed", todo.IsCompleted).
58 | Where(squirrel.Eq{"id": todo.ID}).
59 | ToSql()
60 | if err != nil {
61 | return err
62 | }
63 |
64 | // Выполнение запроса
65 | _, err = repo.db.Exec(query, args...)
66 | if err != nil {
67 | return err
68 | }
69 |
70 | return nil
71 | }
72 |
73 | func (repo *Repository) DeleteTodo(ID string, UserID string) error {
74 | query, args, err := sq.Delete("todos").
75 | Where(squirrel.Eq{"id": ID, "user_id": UserID}).ToSql()
76 | if err != nil {
77 | return err
78 | }
79 | _, err = repo.db.Exec(query, args...)
80 | if err != nil {
81 | return err
82 | }
83 | return nil
84 | }
85 |
--------------------------------------------------------------------------------
/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.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
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.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
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.WriteWatchModeString(templ_7745c5c3_Buffer, 3)
51 | if templ_7745c5c3_Err != nil {
52 | return templ_7745c5c3_Err
53 | }
54 | return templ_7745c5c3_Err
55 | })
56 | }
57 |
--------------------------------------------------------------------------------
/internal/transport/web/handlers.go:
--------------------------------------------------------------------------------
1 | package web
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "net/http"
7 | "templtest/internal/services"
8 | "templtest/internal/storage/sqlite"
9 | "templtest/views/pages"
10 | "time"
11 | )
12 |
13 | type Handler struct {
14 | s *services.Service
15 | }
16 |
17 | func NewHandlers() *Handler {
18 | repo, err := sqlite.New()
19 | if err != nil {
20 | log.Fatal(err.Error())
21 | }
22 | s := services.New(repo)
23 | return &Handler{s}
24 | }
25 |
26 | func (h *Handler) Index(w http.ResponseWriter, r *http.Request) {
27 | token, err := getAuthCookie(r)
28 | if err != nil {
29 | http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
30 | return
31 | }
32 | claims, err := VerifyJWT(token)
33 | user, err := h.s.User(claims.UserEmail)
34 | if err != nil {
35 | fmt.Println(err.Error())
36 | http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
37 | return
38 | }
39 | todos, err := h.s.Todos(user.ID)
40 | if err != nil {
41 | fmt.Println(err.Error())
42 | }
43 | pages.Index(user, todos).Render(r.Context(), w)
44 | }
45 |
46 | func (h *Handler) Login(w http.ResponseWriter, r *http.Request) {
47 | email := r.FormValue("email")
48 | password := r.FormValue("pass")
49 | name := r.FormValue("name")
50 | fmt.Println(email, password)
51 | if email != "" && password != "" {
52 | user, err := h.s.UserLogin(email, name, password)
53 | if err == nil {
54 | if user.Password != password {
55 | pages.LoginPage("Wrong password").Render(r.Context(), w)
56 | return
57 | }
58 | token, _ := CreateJWT(user.ID, user.Email)
59 | cookie := createCookie(time.Now().Add(24*60*time.Hour), "Authorization", token)
60 | http.SetCookie(w, &cookie)
61 | http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
62 | }
63 | }
64 | pages.LoginPage("").Render(r.Context(), w)
65 | }
66 |
67 | func (h *Handler) LoginForm(w http.ResponseWriter, r *http.Request) {
68 | w.Write([]byte("some"))
69 | }
70 |
71 | func (h *Handler) CreateTodo(w http.ResponseWriter, r *http.Request) {
72 | token, err := getAuthCookie(r)
73 | if err != nil {
74 | http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
75 | return
76 | }
77 | title := r.FormValue("title")
78 | claims, err := VerifyJWT(token)
79 | todo, err := h.s.CreateTodos(title, claims.UserID)
80 | if err != nil {
81 | fmt.Println(err.Error())
82 | }
83 | pages.TodoComponent(todo).Render(r.Context(), w)
84 | }
85 |
86 | func (h *Handler) DeleteTodo(w http.ResponseWriter, r *http.Request) {
87 | token, err := getAuthCookie(r)
88 | if err != nil {
89 | http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
90 | return
91 | }
92 | claims, err := VerifyJWT(token)
93 | id := r.PathValue("id")
94 | err = h.s.DeleteTodo(id, claims.UserID)
95 |
96 | w.WriteHeader(http.StatusAccepted)
97 | todos, err := h.s.Todos(claims.UserID)
98 | if err != nil {
99 | fmt.Println(err.Error())
100 | }
101 | pages.TodoList(todos).Render(r.Context(), w)
102 | }
103 |
--------------------------------------------------------------------------------
/views/pages/login_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 "templtest/views/layouts"
12 |
13 | func LoginPage(err string) templ.Component {
14 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
15 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
16 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
17 | if !templ_7745c5c3_IsBuffer {
18 | defer func() {
19 | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
20 | if templ_7745c5c3_Err == nil {
21 | templ_7745c5c3_Err = templ_7745c5c3_BufErr
22 | }
23 | }()
24 | }
25 | ctx = templ.InitializeContext(ctx)
26 | templ_7745c5c3_Var1 := templ.GetChildren(ctx)
27 | if templ_7745c5c3_Var1 == nil {
28 | templ_7745c5c3_Var1 = templ.NopComponent
29 | }
30 | ctx = templ.ClearChildren(ctx)
31 | templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
32 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
33 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
34 | if !templ_7745c5c3_IsBuffer {
35 | defer func() {
36 | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
37 | if templ_7745c5c3_Err == nil {
38 | templ_7745c5c3_Err = templ_7745c5c3_BufErr
39 | }
40 | }()
41 | }
42 | ctx = templ.InitializeContext(ctx)
43 | templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
44 | if templ_7745c5c3_Err != nil {
45 | return templ_7745c5c3_Err
46 | }
47 | if err != "" {
48 | templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
49 | if templ_7745c5c3_Err != nil {
50 | return templ_7745c5c3_Err
51 | }
52 | var templ_7745c5c3_Var3 string
53 | templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(err)
54 | if templ_7745c5c3_Err != nil {
55 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/pages/login.templ`, Line: 11, Col: 83}
56 | }
57 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
58 | if templ_7745c5c3_Err != nil {
59 | return templ_7745c5c3_Err
60 | }
61 | templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3)
62 | if templ_7745c5c3_Err != nil {
63 | return templ_7745c5c3_Err
64 | }
65 | }
66 | templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 4)
67 | if templ_7745c5c3_Err != nil {
68 | return templ_7745c5c3_Err
69 | }
70 | return templ_7745c5c3_Err
71 | })
72 | templ_7745c5c3_Err = layouts.BaseLayout("Go app").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
73 | if templ_7745c5c3_Err != nil {
74 | return templ_7745c5c3_Err
75 | }
76 | return templ_7745c5c3_Err
77 | })
78 | }
79 |
--------------------------------------------------------------------------------
/views/components/Header_templ.go:
--------------------------------------------------------------------------------
1 | // Code generated by templ - DO NOT EDIT.
2 |
3 | // templ: version: v0.2.747
4 | package components
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 | )
14 |
15 | func Header(user entities.User) templ.Component {
16 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
17 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
18 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
19 | if !templ_7745c5c3_IsBuffer {
20 | defer func() {
21 | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
22 | if templ_7745c5c3_Err == nil {
23 | templ_7745c5c3_Err = templ_7745c5c3_BufErr
24 | }
25 | }()
26 | }
27 | ctx = templ.InitializeContext(ctx)
28 | templ_7745c5c3_Var1 := templ.GetChildren(ctx)
29 | if templ_7745c5c3_Var1 == nil {
30 | templ_7745c5c3_Var1 = templ.NopComponent
31 | }
32 | ctx = templ.ClearChildren(ctx)
33 | templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
34 | if templ_7745c5c3_Err != nil {
35 | return templ_7745c5c3_Err
36 | }
37 | templ_7745c5c3_Err = Profile(user.Name, user.Email).Render(ctx, templ_7745c5c3_Buffer)
38 | if templ_7745c5c3_Err != nil {
39 | return templ_7745c5c3_Err
40 | }
41 | templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
42 | if templ_7745c5c3_Err != nil {
43 | return templ_7745c5c3_Err
44 | }
45 | return templ_7745c5c3_Err
46 | })
47 | }
48 |
49 | func Profile(userName, userEmail string) templ.Component {
50 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
51 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
52 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
53 | if !templ_7745c5c3_IsBuffer {
54 | defer func() {
55 | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
56 | if templ_7745c5c3_Err == nil {
57 | templ_7745c5c3_Err = templ_7745c5c3_BufErr
58 | }
59 | }()
60 | }
61 | ctx = templ.InitializeContext(ctx)
62 | templ_7745c5c3_Var2 := templ.GetChildren(ctx)
63 | if templ_7745c5c3_Var2 == nil {
64 | templ_7745c5c3_Var2 = templ.NopComponent
65 | }
66 | ctx = templ.ClearChildren(ctx)
67 | templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3)
68 | if templ_7745c5c3_Err != nil {
69 | return templ_7745c5c3_Err
70 | }
71 | var templ_7745c5c3_Var3 string
72 | templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(userName)
73 | if templ_7745c5c3_Err != nil {
74 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/components/Header.templ`, Line: 18, Col: 51}
75 | }
76 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
77 | if templ_7745c5c3_Err != nil {
78 | return templ_7745c5c3_Err
79 | }
80 | templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 4)
81 | if templ_7745c5c3_Err != nil {
82 | return templ_7745c5c3_Err
83 | }
84 | var templ_7745c5c3_Var4 string
85 | templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(userEmail)
86 | if templ_7745c5c3_Err != nil {
87 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/components/Header.templ`, Line: 19, Col: 61}
88 | }
89 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
90 | if templ_7745c5c3_Err != nil {
91 | return templ_7745c5c3_Err
92 | }
93 | templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 5)
94 | if templ_7745c5c3_Err != nil {
95 | return templ_7745c5c3_Err
96 | }
97 | return templ_7745c5c3_Err
98 | })
99 | }
100 |
--------------------------------------------------------------------------------
/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 | "fmt"
13 | "templtest/internal/entities"
14 | "templtest/views/components"
15 | "templtest/views/layouts"
16 | )
17 |
18 | func Index(user entities.User, todos []entities.Todo) templ.Component {
19 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
20 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
21 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
22 | if !templ_7745c5c3_IsBuffer {
23 | defer func() {
24 | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
25 | if templ_7745c5c3_Err == nil {
26 | templ_7745c5c3_Err = templ_7745c5c3_BufErr
27 | }
28 | }()
29 | }
30 | ctx = templ.InitializeContext(ctx)
31 | templ_7745c5c3_Var1 := templ.GetChildren(ctx)
32 | if templ_7745c5c3_Var1 == nil {
33 | templ_7745c5c3_Var1 = templ.NopComponent
34 | }
35 | ctx = templ.ClearChildren(ctx)
36 | templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
37 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
38 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
39 | if !templ_7745c5c3_IsBuffer {
40 | defer func() {
41 | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
42 | if templ_7745c5c3_Err == nil {
43 | templ_7745c5c3_Err = templ_7745c5c3_BufErr
44 | }
45 | }()
46 | }
47 | ctx = templ.InitializeContext(ctx)
48 | templ_7745c5c3_Err = components.Header(user).Render(ctx, templ_7745c5c3_Buffer)
49 | if templ_7745c5c3_Err != nil {
50 | return templ_7745c5c3_Err
51 | }
52 | templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
53 | if templ_7745c5c3_Err != nil {
54 | return templ_7745c5c3_Err
55 | }
56 | var templ_7745c5c3_Var3 string
57 | templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("{ todosCount: %v }", len(todos)))
58 | if templ_7745c5c3_Err != nil {
59 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/pages/index.templ`, Line: 13, Col: 98}
60 | }
61 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
62 | if templ_7745c5c3_Err != nil {
63 | return templ_7745c5c3_Err
64 | }
65 | templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
66 | if templ_7745c5c3_Err != nil {
67 | return templ_7745c5c3_Err
68 | }
69 | templ_7745c5c3_Err = TodoList(todos).Render(ctx, templ_7745c5c3_Buffer)
70 | if templ_7745c5c3_Err != nil {
71 | return templ_7745c5c3_Err
72 | }
73 | templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3)
74 | if templ_7745c5c3_Err != nil {
75 | return templ_7745c5c3_Err
76 | }
77 | templ_7745c5c3_Err = TodoCreate().Render(ctx, templ_7745c5c3_Buffer)
78 | if templ_7745c5c3_Err != nil {
79 | return templ_7745c5c3_Err
80 | }
81 | templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 4)
82 | if templ_7745c5c3_Err != nil {
83 | return templ_7745c5c3_Err
84 | }
85 | return templ_7745c5c3_Err
86 | })
87 | templ_7745c5c3_Err = layouts.BaseLayout("Go app").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
88 | if templ_7745c5c3_Err != nil {
89 | return templ_7745c5c3_Err
90 | }
91 | return templ_7745c5c3_Err
92 | })
93 | }
94 |
95 | func TodoComponent(todo entities.Todo) templ.Component {
96 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
97 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
98 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
99 | if !templ_7745c5c3_IsBuffer {
100 | defer func() {
101 | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
102 | if templ_7745c5c3_Err == nil {
103 | templ_7745c5c3_Err = templ_7745c5c3_BufErr
104 | }
105 | }()
106 | }
107 | ctx = templ.InitializeContext(ctx)
108 | templ_7745c5c3_Var4 := templ.GetChildren(ctx)
109 | if templ_7745c5c3_Var4 == nil {
110 | templ_7745c5c3_Var4 = templ.NopComponent
111 | }
112 | ctx = templ.ClearChildren(ctx)
113 | templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 5)
114 | if templ_7745c5c3_Err != nil {
115 | return templ_7745c5c3_Err
116 | }
117 | var templ_7745c5c3_Var5 string
118 | templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(todo.Title)
119 | if templ_7745c5c3_Err != nil {
120 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/pages/index.templ`, Line: 29, Col: 61}
121 | }
122 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
123 | if templ_7745c5c3_Err != nil {
124 | return templ_7745c5c3_Err
125 | }
126 | templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 6)
127 | if templ_7745c5c3_Err != nil {
128 | return templ_7745c5c3_Err
129 | }
130 | var templ_7745c5c3_Var6 string
131 | templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(todo.ID)
132 | if templ_7745c5c3_Err != nil {
133 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/pages/index.templ`, Line: 30, Col: 23}
134 | }
135 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
136 | if templ_7745c5c3_Err != nil {
137 | return templ_7745c5c3_Err
138 | }
139 | templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 7)
140 | if templ_7745c5c3_Err != nil {
141 | return templ_7745c5c3_Err
142 | }
143 | var templ_7745c5c3_Var7 string
144 | templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs("/todos/" + todo.ID)
145 | if templ_7745c5c3_Err != nil {
146 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/pages/index.templ`, Line: 31, Col: 36}
147 | }
148 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
149 | if templ_7745c5c3_Err != nil {
150 | return templ_7745c5c3_Err
151 | }
152 | templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 8)
153 | if templ_7745c5c3_Err != nil {
154 | return templ_7745c5c3_Err
155 | }
156 | return templ_7745c5c3_Err
157 | })
158 | }
159 |
160 | func TodoList(todos []entities.Todo) templ.Component {
161 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
162 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
163 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
164 | if !templ_7745c5c3_IsBuffer {
165 | defer func() {
166 | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
167 | if templ_7745c5c3_Err == nil {
168 | templ_7745c5c3_Err = templ_7745c5c3_BufErr
169 | }
170 | }()
171 | }
172 | ctx = templ.InitializeContext(ctx)
173 | templ_7745c5c3_Var8 := templ.GetChildren(ctx)
174 | if templ_7745c5c3_Var8 == nil {
175 | templ_7745c5c3_Var8 = templ.NopComponent
176 | }
177 | ctx = templ.ClearChildren(ctx)
178 | if len(todos) > 0 {
179 | for _, todo := range todos {
180 | templ_7745c5c3_Err = TodoComponent(todo).Render(ctx, templ_7745c5c3_Buffer)
181 | if templ_7745c5c3_Err != nil {
182 | return templ_7745c5c3_Err
183 | }
184 | }
185 | }
186 | return templ_7745c5c3_Err
187 | })
188 | }
189 |
190 | func TodoCreate() templ.Component {
191 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
192 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
193 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
194 | if !templ_7745c5c3_IsBuffer {
195 | defer func() {
196 | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
197 | if templ_7745c5c3_Err == nil {
198 | templ_7745c5c3_Err = templ_7745c5c3_BufErr
199 | }
200 | }()
201 | }
202 | ctx = templ.InitializeContext(ctx)
203 | templ_7745c5c3_Var9 := templ.GetChildren(ctx)
204 | if templ_7745c5c3_Var9 == nil {
205 | templ_7745c5c3_Var9 = templ.NopComponent
206 | }
207 | ctx = templ.ClearChildren(ctx)
208 | templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 9)
209 | if templ_7745c5c3_Err != nil {
210 | return templ_7745c5c3_Err
211 | }
212 | return templ_7745c5c3_Err
213 | })
214 | }
215 |
--------------------------------------------------------------------------------
/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 | .container {
558 | width: 100%;
559 | margin-right: auto;
560 | margin-left: auto;
561 | padding-right: 36px;
562 | padding-left: 36px;
563 | }
564 |
565 | @media (min-width: 1200px) {
566 | .container {
567 | max-width: 1200px;
568 | }
569 | }
570 |
571 | .visible {
572 | visibility: visible;
573 | }
574 |
575 | .mt-12 {
576 | margin-top: 3rem;
577 | }
578 |
579 | .flex {
580 | display: flex;
581 | }
582 |
583 | .h-96 {
584 | height: 24rem;
585 | }
586 |
587 | .h-dvh {
588 | height: 100dvh;
589 | }
590 |
591 | .w-64 {
592 | width: 16rem;
593 | }
594 |
595 | .w-72 {
596 | width: 18rem;
597 | }
598 |
599 | .w-full {
600 | width: 100%;
601 | }
602 |
603 | .flex-col {
604 | flex-direction: column;
605 | }
606 |
607 | .items-center {
608 | align-items: center;
609 | }
610 |
611 | .justify-center {
612 | justify-content: center;
613 | }
614 |
615 | .justify-between {
616 | justify-content: space-between;
617 | }
618 |
619 | .gap-1 {
620 | gap: 0.25rem;
621 | }
622 |
623 | .gap-2 {
624 | gap: 0.5rem;
625 | }
626 |
627 | .gap-3 {
628 | gap: 0.75rem;
629 | }
630 |
631 | .gap-6 {
632 | gap: 1.5rem;
633 | }
634 |
635 | .rounded {
636 | border-radius: 0.25rem;
637 | }
638 |
639 | .rounded-lg {
640 | border-radius: 0.5rem;
641 | }
642 |
643 | .rounded-xl {
644 | border-radius: 0.75rem;
645 | }
646 |
647 | .border {
648 | border-width: 1px;
649 | }
650 |
651 | .border-red-500 {
652 | --tw-border-opacity: 1;
653 | border-color: rgb(239 68 68 / var(--tw-border-opacity));
654 | }
655 |
656 | .border-zinc-200 {
657 | --tw-border-opacity: 1;
658 | border-color: rgb(228 228 231 / var(--tw-border-opacity));
659 | }
660 |
661 | .border-zinc-300 {
662 | --tw-border-opacity: 1;
663 | border-color: rgb(212 212 216 / var(--tw-border-opacity));
664 | }
665 |
666 | .border-zinc-400 {
667 | --tw-border-opacity: 1;
668 | border-color: rgb(161 161 170 / var(--tw-border-opacity));
669 | }
670 |
671 | .bg-blue-600 {
672 | --tw-bg-opacity: 1;
673 | background-color: rgb(37 99 235 / var(--tw-bg-opacity));
674 | }
675 |
676 | .bg-blue-900 {
677 | --tw-bg-opacity: 1;
678 | background-color: rgb(30 58 138 / var(--tw-bg-opacity));
679 | }
680 |
681 | .bg-gray-800 {
682 | --tw-bg-opacity: 1;
683 | background-color: rgb(31 41 55 / var(--tw-bg-opacity));
684 | }
685 |
686 | .bg-red-100 {
687 | --tw-bg-opacity: 1;
688 | background-color: rgb(254 226 226 / var(--tw-bg-opacity));
689 | }
690 |
691 | .bg-red-500 {
692 | --tw-bg-opacity: 1;
693 | background-color: rgb(239 68 68 / var(--tw-bg-opacity));
694 | }
695 |
696 | .bg-zinc-50 {
697 | --tw-bg-opacity: 1;
698 | background-color: rgb(250 250 250 / var(--tw-bg-opacity));
699 | }
700 |
701 | .p-2 {
702 | padding: 0.5rem;
703 | }
704 |
705 | .p-4 {
706 | padding: 1rem;
707 | }
708 |
709 | .p-6 {
710 | padding: 1.5rem;
711 | }
712 |
713 | .py-3 {
714 | padding-top: 0.75rem;
715 | padding-bottom: 0.75rem;
716 | }
717 |
718 | .py-4 {
719 | padding-top: 1rem;
720 | padding-bottom: 1rem;
721 | }
722 |
723 | .text-2xl {
724 | font-size: 1.5rem;
725 | line-height: 2rem;
726 | }
727 |
728 | .text-4xl {
729 | font-size: 2.25rem;
730 | line-height: 2.5rem;
731 | }
732 |
733 | .text-lg {
734 | font-size: 1.125rem;
735 | line-height: 1.75rem;
736 | }
737 |
738 | .text-xs {
739 | font-size: 0.75rem;
740 | line-height: 1rem;
741 | }
742 |
743 | .font-bold {
744 | font-weight: 700;
745 | }
746 |
747 | .font-light {
748 | font-weight: 300;
749 | }
750 |
751 | .text-red-800 {
752 | --tw-text-opacity: 1;
753 | color: rgb(153 27 27 / var(--tw-text-opacity));
754 | }
755 |
756 | .text-white {
757 | --tw-text-opacity: 1;
758 | color: rgb(255 255 255 / var(--tw-text-opacity));
759 | }
760 |
761 | .text-zinc-500 {
762 | --tw-text-opacity: 1;
763 | color: rgb(113 113 122 / var(--tw-text-opacity));
764 | }
765 |
766 | .text-zinc-600 {
767 | --tw-text-opacity: 1;
768 | color: rgb(82 82 91 / var(--tw-text-opacity));
769 | }
770 |
771 | .text-zinc-800 {
772 | --tw-text-opacity: 1;
773 | color: rgb(39 39 42 / var(--tw-text-opacity));
774 | }
775 |
776 | @keyframes fade-in {
777 | from {
778 | opacity: 0;
779 | }
780 | }
781 |
782 | @keyframes fade-out {
783 | to {
784 | opacity: 0;
785 | }
786 | }
787 |
788 | @keyframes slide-from-right {
789 | from {
790 | transform: translateX(90px);
791 | }
792 | }
793 |
794 | @keyframes slide-to-left {
795 | to {
796 | transform: translateX(-90px);
797 | }
798 | }
799 |
800 | /* define animations for the old and new content */
801 |
802 | ::view-transition-old(slide-it) {
803 | animation: 180ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
804 | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
805 | }
806 |
807 | ::view-transition-new(slide-it) {
808 | animation: 420ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
809 | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
810 | }
811 |
812 | /* tie the view transition to a given CSS class */
813 |
814 | .sample-transition {
815 | view-transition-name: slide-it;
816 | }
817 |
818 | @media (min-width: 768px) {
819 | .md\:w-1\/2 {
820 | width: 50%;
821 | }
822 |
823 | .md\:flex-row {
824 | flex-direction: row;
825 | }
826 | }
827 |
--------------------------------------------------------------------------------