diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54f1ed6..7e91724 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,6 +59,8 @@ jobs: with: go-version-file: go.mod check-latest: true + - name: Setup Bun + uses: oven-sh/setup-bun@v2.2.0 - name: Run generate templ files run: make generate-web - name: Run tests @@ -78,5 +80,7 @@ jobs: with: go-version-file: go.mod check-latest: true + - name: Setup Bun + uses: oven-sh/setup-bun@v2.2.0 - name: Build run: make build diff --git a/.gitignore b/.gitignore index 90c59f8..ba1b47f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,14 @@ bin tmp node_modules -internal/web/static/css/ +internal/web/static/css/* +!internal/web/static/css/input.css internal/web/static/js/ .DS_Store +.deps-stamp + # Ignore compiled templ files internal/web/**/*_templ.go internal/web/**/*_templ.txt diff --git a/Dockerfile b/Dockerfile index 9e62439..2ddea1a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.25 AS builder +FROM golang:1.26 AS builder WORKDIR /app RUN apt update && apt install -y --no-install-recommends ca-certificates tzdata make curl upx COPY --from=oven/bun:1.3 /usr/local/bin/bun /usr/local/bin/bun diff --git a/Makefile b/Makefile index ea3423f..d2f278e 100644 --- a/Makefile +++ b/Makefile @@ -8,14 +8,18 @@ all: help ## build: Compile templ files and build application .PHONY: build -build: get-deps get-js-deps generate-web +build: prepare-data CGO_ENABLED=0 go build -ldflags="-s -w -extldflags '-static'" -trimpath -o 'bin/app' ./cmd/app ## start: Build and start application .PHONY: start -start: get-deps get-js-deps generate-web +start: prepare-data go run ./cmd/app +## dev: Build and start application in live reload mode +.PHONY: dev +dev: air + ## build-docker: Build Docker container image with this app .PHONY: build-docker build-docker: @@ -26,6 +30,14 @@ build-docker: run-docker: docker run --rm -it -p 8089:8089 $(shell basename $(PWD)):latest +## prepare-data: Prepare data for the application +.PHONY: prepare-data +prepare-data: .deps-stamp get-js-deps generate-web + +.deps-stamp: go.mod go.sum + go mod download + @touch .deps-stamp + ## get-js-deps: Install frontend dependencies using bun (locally if available and otherwise via Docker) .PHONY: get-js-deps get-js-deps: @@ -33,7 +45,7 @@ get-js-deps: @mkdir -p internal/web/static/js internal/web/static/css @cp node_modules/htmx.org/dist/htmx.min.js internal/web/static/js/ @cp node_modules/htmx-ext-response-targets/dist/response-targets.min.js internal/web/static/js/ - @cp node_modules/@picocss/pico/css/pico.min.css internal/web/static/css/ + @bun tailwindcss -i internal/web/static/css/input.css -o internal/web/static/css/style.css --minify @cp -r node_modules/ionicons/dist/ionicons internal/web/static/js/ # ------------------------------------------------------------------------------------------------- @@ -56,7 +68,7 @@ generate-web: check-go ## air: Build and start application in live reload mode via air .PHONY: air -air: get-deps generate-web +air: prepare-data go tool air ## lint: Run golangci-lint to lint Go files @@ -69,9 +81,9 @@ lint: lint-fix: go run $(GOLANGCI_LINT_PACKAGE) run --fix -## lint-fmt: Run golangci-lint fmt to show code format issues -.PHONY: lint-fmt -lint-fmt: +## format: Run golangci-lint fmt to show code format issues +.PHONY: format +format: go run $(GOLANGCI_LINT_PACKAGE) fmt ## audit: Quality checks diff --git a/README.md b/README.md index 4203918..5eadd24 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,39 @@ -# go-templ-htmx-picocss-example +# go-full-stack-example -Example of a Web CRUD app based on [Go](https://github.com/golang/go) + ([sqlite](https://gitlab.com/cznic/sqlite) + [gorm](https://github.com/go-gorm/gorm)) + [templ](https://github.com/a-h/templ) + [htmx](https://github.com/bigskysoftware/htmx) + [PicoCSS](https://github.com/picocss/pico) + [Ionicons](https://github.com/ionic-team/ionicons) +Example of full-stack Go based Web app +[![GO](https://img.shields.io/badge/go-%233366CC.svg?logo=go&logoColor=white)](https://go.dev) [![templ](https://img.shields.io/badge/templ-%233366CC.svg?logo=htmx&logoColor=white)](https://github.com/a-h/templ) [![HTMX](https://img.shields.io/badge/htmx-%233366CC.svg?logo=htmx&logoColor=white)](https://github.com/bigskysoftware/htmx) [![SQLite](https://img.shields.io/badge/sqlite-%233366CC.svg?logo=sqlite&logoColor=white)](https://gitlab.com/cznic/sqlite) [![GORM](https://img.shields.io/badge/gorm-%233366CC.svg?logo=sqlite&logoColor=white)](https://github.com/go-gorm/gorm) [![tailwindcss](https://img.shields.io/badge/tailwindcss-%233366CC.svg?logo=tailwindcss&logoColor=white)](https://github.com/tailwindlabs/tailwindcss) [![daisyui](https://img.shields.io/badge/daisyui-%233366CC.svg?logo=daisyui&logoColor=white)](https://daisyui.com) [![ionicons](https://img.shields.io/badge/ionicons-%233366CC.svg?logo=ionic&logoColor=white)](https://github.com/ionic-team/ionicons) -![demo](demo.gif) Features: - Comfortable and flexible component based templates via [templ](https://github.com/a-h/templ) -- CRUD functionality +- CRUD functionality (Create, Read, Update, and Delete entries) - Persistent storage via [SQLite](https://gitlab.com/cznic/sqlite) + ORM ([gorm](https://github.com/go-gorm/gorm)) -- Modal windows -- Error handling on user interface side -- Infinite scroll (Lazy Loading) -- User frendly interface +- Error handling on server and user interface side +- Infinite Scrolling via lazy loading +- User friendly interface +- Interactive Modals for better UX +- Native light and dark mode support - Preserve static files -## Develop - -Available makefile actions: -```sh -% make -Usage: make COMMAND - -Commands: - build Compile templ files and build application - start Build and start application - build-docker Build Docker container image with this app - run-docker Run Docker container image with this app - get-js-deps Install frontend dependencies using bun (locally if available and otherwise via Docker) - test Run unit tests - generate-web Compile templ files via github.com/a-h/templ/cmd/templ - air Build and start application in live reload mode via air - lint Run golangci-lint to lint Go files - lint-fix Run golangci-lint to lint Go files and fix issues - lint-fmt Run golangci-lint fmt to show code format issues - audit Quality checks - tidy Removes unused dependencies and adds missing ones - update-deps Update Go dependencies - get-deps Download Go dependencies - check-go Check that Go is installed - help Display help -``` +## Quick start + +```bash +# 1. Clone this repository +git clone https://github.com/sonjek/go-full-stack-example && cd go-full-stack-example -## Local Development Setup +# 2. Run (with hot-reload) +make dev -To get started, follow these steps: +# Or run (without hot-reload) +make start -1) Run `make start` to download go dependencies, compile templ files, build application and finally start application. -```sh -% make start -go mod download -go tool templ generate -(✓) Complete [ updates=7 duration=13.893847ms ] -go run ./cmd/app -Starting web interface on port: 8089 +# Or build a binary and run +make build && bin/app ``` -You should now be able to access application in your web browser at http://localhost:8089 +The server starts on `:8089`. + +The SQLite database is created automatically and migrations are applied on startup. + +--- diff --git a/bun.lock b/bun.lock index 7fa3e2b..8ce3da1 100644 --- a/bun.lock +++ b/bun.lock @@ -3,17 +3,57 @@ "configVersion": 1, "workspaces": { "": { - "name": "go-templ-gorm-htmx-picocss-example", + "name": "go-full-stack-example", "dependencies": { - "@picocss/pico": "^2.1.1", "htmx-ext-response-targets": "^2.0.4", "htmx.org": "^2.0.7", "ionicons": "^8.0.13", }, + "devDependencies": { + "@tailwindcss/cli": "^4.2.2", + "daisyui": "5.5.19", + "tailwindcss": "^4.2.2", + }, }, }, "packages": { - "@picocss/pico": ["@picocss/pico@2.1.1", "", {}, "sha512-kIDugA7Ps4U+2BHxiNHmvgPIQDWPDU4IeU6TNRdvXQM1uZX+FibqDQT2xUOnnO2yq/LUHcwnGlu1hvf4KfXnMg=="], + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@parcel/watcher": ["@parcel/watcher@2.5.6", "", { "dependencies": { "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "node-addon-api": "^7.0.0", "picomatch": "^4.0.3" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.6", "@parcel/watcher-darwin-arm64": "2.5.6", "@parcel/watcher-darwin-x64": "2.5.6", "@parcel/watcher-freebsd-x64": "2.5.6", "@parcel/watcher-linux-arm-glibc": "2.5.6", "@parcel/watcher-linux-arm-musl": "2.5.6", "@parcel/watcher-linux-arm64-glibc": "2.5.6", "@parcel/watcher-linux-arm64-musl": "2.5.6", "@parcel/watcher-linux-x64-glibc": "2.5.6", "@parcel/watcher-linux-x64-musl": "2.5.6", "@parcel/watcher-win32-arm64": "2.5.6", "@parcel/watcher-win32-ia32": "2.5.6", "@parcel/watcher-win32-x64": "2.5.6" } }, "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ=="], + + "@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.6", "", { "os": "android", "cpu": "arm64" }, "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A=="], + + "@parcel/watcher-darwin-arm64": ["@parcel/watcher-darwin-arm64@2.5.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA=="], + + "@parcel/watcher-darwin-x64": ["@parcel/watcher-darwin-x64@2.5.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg=="], + + "@parcel/watcher-freebsd-x64": ["@parcel/watcher-freebsd-x64@2.5.6", "", { "os": "freebsd", "cpu": "x64" }, "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng=="], + + "@parcel/watcher-linux-arm-glibc": ["@parcel/watcher-linux-arm-glibc@2.5.6", "", { "os": "linux", "cpu": "arm" }, "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ=="], + + "@parcel/watcher-linux-arm-musl": ["@parcel/watcher-linux-arm-musl@2.5.6", "", { "os": "linux", "cpu": "arm" }, "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg=="], + + "@parcel/watcher-linux-arm64-glibc": ["@parcel/watcher-linux-arm64-glibc@2.5.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA=="], + + "@parcel/watcher-linux-arm64-musl": ["@parcel/watcher-linux-arm64-musl@2.5.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA=="], + + "@parcel/watcher-linux-x64-glibc": ["@parcel/watcher-linux-x64-glibc@2.5.6", "", { "os": "linux", "cpu": "x64" }, "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ=="], + + "@parcel/watcher-linux-x64-musl": ["@parcel/watcher-linux-x64-musl@2.5.6", "", { "os": "linux", "cpu": "x64" }, "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg=="], + + "@parcel/watcher-win32-arm64": ["@parcel/watcher-win32-arm64@2.5.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q=="], + + "@parcel/watcher-win32-ia32": ["@parcel/watcher-win32-ia32@2.5.6", "", { "os": "win32", "cpu": "ia32" }, "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g=="], + + "@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.6", "", { "os": "win32", "cpu": "x64" }, "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw=="], "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.34.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ=="], @@ -33,10 +73,106 @@ "@stencil/core": ["@stencil/core@4.38.3", "", { "optionalDependencies": { "@rollup/rollup-darwin-arm64": "4.34.9", "@rollup/rollup-darwin-x64": "4.34.9", "@rollup/rollup-linux-arm64-gnu": "4.34.9", "@rollup/rollup-linux-arm64-musl": "4.34.9", "@rollup/rollup-linux-x64-gnu": "4.34.9", "@rollup/rollup-linux-x64-musl": "4.34.9", "@rollup/rollup-win32-arm64-msvc": "4.34.9", "@rollup/rollup-win32-x64-msvc": "4.34.9" }, "bin": { "stencil": "bin/stencil" } }, "sha512-rSDzGUfi58X8K79ySjlM6KlY+mq7D+ittzgNAdYHcsXHc70sBpdatFhnbOg25uVDiMf7xRAH9slP38pPdXnZOQ=="], + "@tailwindcss/cli": ["@tailwindcss/cli@4.2.2", "", { "dependencies": { "@parcel/watcher": "^2.5.1", "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "enhanced-resolve": "^5.19.0", "mri": "^1.2.0", "picocolors": "^1.1.1", "tailwindcss": "4.2.2" }, "bin": { "tailwindcss": "dist/index.mjs" } }, "sha512-iJS+8kAFZ8HPqnh0O5DHCLjo4L6dD97DBQEkrhfSO4V96xeefUus2jqsBs1dUMt3OU9Ks4qIkiY0mpL5UW+4LQ=="], + + "@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.2", "", { "os": "android", "cpu": "arm64" }, "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.2", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA=="], + + "daisyui": ["daisyui@5.5.19", "", {}, "sha512-pbFAkl1VCEh/MPCeclKL61I/MqRIFFhNU7yiXoDDRapXN4/qNCoMxeCCswyxEEhqL5eiTTfwHvucFtOE71C9sA=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + "htmx-ext-response-targets": ["htmx-ext-response-targets@2.0.4", "", { "dependencies": { "htmx.org": "^2.0.2" } }, "sha512-Q/yfH0N2A40j903mr6ldGV3qLWMQeufROylYIbYQLBGrCpmydflxXmQwDOEhEWdPnBTgiHofkAo0gQ3S1BmyxQ=="], "htmx.org": ["htmx.org@2.0.8", "", {}, "sha512-fm297iru0iWsNJlBrjvtN7V9zjaxd+69Oqjh4F/Vq9Wwi2kFisLcrLCiv5oBX0KLfOX/zG8AUo9ROMU5XUB44Q=="], "ionicons": ["ionicons@8.0.13", "", { "dependencies": { "@stencil/core": "^4.35.3" } }, "sha512-2QQVyG2P4wszne79jemMjWYLp0DBbDhr4/yFroPCxvPP1wtMxgdIV3l5n+XZ5E9mgoXU79w7yTWpm2XzJsISxQ=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], + + "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="], + + "tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], } } diff --git a/cmd/app/main.go b/cmd/app/main.go index fa19163..a4ee4e6 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -1,10 +1,10 @@ package main import ( - "github.com/sonjek/go-templ-htmx-picocss-example/internal/service" - "github.com/sonjek/go-templ-htmx-picocss-example/internal/storage" - "github.com/sonjek/go-templ-htmx-picocss-example/internal/web" - "github.com/sonjek/go-templ-htmx-picocss-example/internal/web/handlers" + "github.com/sonjek/go-full-stack-example/internal/service" + "github.com/sonjek/go-full-stack-example/internal/storage" + "github.com/sonjek/go-full-stack-example/internal/web" + "github.com/sonjek/go-full-stack-example/internal/web/handlers" ) func main() { diff --git a/demo.gif b/demo.gif deleted file mode 100644 index 81e0a1d..0000000 Binary files a/demo.gif and /dev/null differ diff --git a/go.mod b/go.mod index 94c78e2..9ee71ef 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/sonjek/go-templ-htmx-picocss-example +module github.com/sonjek/go-full-stack-example go 1.26.1 @@ -30,7 +30,7 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-sqlite3 v1.14.37 // indirect + github.com/mattn/go-sqlite3 v1.14.38 // indirect github.com/natefinch/atomic v1.0.1 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect diff --git a/go.sum b/go.sum index 82111ff..67243ea 100644 --- a/go.sum +++ b/go.sum @@ -139,8 +139,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.37 h1:3DOZp4cXis1cUIpCfXLtmlGolNLp2VEqhiB/PARNBIg= -github.com/mattn/go-sqlite3 v1.14.37/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.38 h1:tDUzL85kMvOrvpCt8P64SbGgVFtJB11GPi2AdmITgb4= +github.com/mattn/go-sqlite3 v1.14.38/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= diff --git a/internal/service/notes.go b/internal/service/notes.go index ae7b5b7..b6a492d 100644 --- a/internal/service/notes.go +++ b/internal/service/notes.go @@ -1,7 +1,7 @@ package service import ( - database "github.com/sonjek/go-templ-htmx-picocss-example/internal/storage" + database "github.com/sonjek/go-full-stack-example/internal/storage" "gorm.io/gorm" ) diff --git a/internal/storage/note.go b/internal/storage/note.go index 79029e3..78c540d 100644 --- a/internal/storage/note.go +++ b/internal/storage/note.go @@ -22,6 +22,11 @@ var NotesSeed = []Note{ Body: "Package sqlite is a CGo-free port of SQLite/SQLite3.", CreatedAt: time.Date(2017, 4, 20, 23, 17, 29, 0, time.UTC), }, + { + Title: "Tailwind CSS", + Body: "A utility-first CSS framework for rapid UI development.", + CreatedAt: time.Date(2017, 11, 1, 21, 11, 0, 0, time.UTC), + }, { Title: "GoLang", Body: "The Go programming language.", @@ -42,6 +47,11 @@ var NotesSeed = []Note{ Body: " htmx - high power tools for HTML.", CreatedAt: time.Date(2022, 8, 10, 21, 21, 0, 0, time.UTC), }, + { + Title: "daisyUI", + Body: "The most popular, free and open-source Tailwind CSS component library.", + CreatedAt: time.Date(2023, 11, 13, 12, 49, 0, 0, time.UTC), + }, } type Note struct { diff --git a/internal/web/handlers/base.go b/internal/web/handlers/base.go index af99280..380fe7d 100644 --- a/internal/web/handlers/base.go +++ b/internal/web/handlers/base.go @@ -5,10 +5,10 @@ import ( "net/http" "github.com/a-h/templ" - "github.com/sonjek/go-templ-htmx-picocss-example/internal/service" - "github.com/sonjek/go-templ-htmx-picocss-example/internal/web/templ/components" - "github.com/sonjek/go-templ-htmx-picocss-example/internal/web/templ/page" - "github.com/sonjek/go-templ-htmx-picocss-example/internal/web/templ/view" + "github.com/sonjek/go-full-stack-example/internal/service" + "github.com/sonjek/go-full-stack-example/internal/web/templ/components" + "github.com/sonjek/go-full-stack-example/internal/web/templ/page" + "github.com/sonjek/go-full-stack-example/internal/web/templ/view" "gorm.io/gorm" ) diff --git a/internal/web/handlers/base_test.go b/internal/web/handlers/base_test.go index e9aba98..cf8fb6a 100644 --- a/internal/web/handlers/base_test.go +++ b/internal/web/handlers/base_test.go @@ -15,14 +15,12 @@ func Test_sendErrorMsg(t *testing.T) { errorMsg string body io.Reader expectedStatus int - expectedBody string }{ { name: "invalid request", body: nil, errorMsg: "MSG", expectedStatus: http.StatusBadRequest, - expectedBody: ``, }, } @@ -37,7 +35,9 @@ func Test_sendErrorMsg(t *testing.T) { "unexpected status code. Expected :%d, got: %d", tc.expectedStatus, w.Result().StatusCode) body, _ := io.ReadAll(w.Result().Body) - assert.Equal(t, tc.expectedBody, string(body), "expected response body") + bodyStr := string(body) + assert.Contains(t, bodyStr, tc.errorMsg, "error message should be in response") + assert.Contains(t, bodyStr, "Error:", "should contain Error label") }) } } diff --git a/internal/web/handlers/notes.go b/internal/web/handlers/notes.go index 20e95d0..0e68529 100644 --- a/internal/web/handlers/notes.go +++ b/internal/web/handlers/notes.go @@ -6,9 +6,9 @@ import ( "strconv" "time" - "github.com/sonjek/go-templ-htmx-picocss-example/internal/web/templ/components" - "github.com/sonjek/go-templ-htmx-picocss-example/internal/web/templ/page" - "github.com/sonjek/go-templ-htmx-picocss-example/internal/web/templ/view" + "github.com/sonjek/go-full-stack-example/internal/web/templ/components" + "github.com/sonjek/go-full-stack-example/internal/web/templ/page" + "github.com/sonjek/go-full-stack-example/internal/web/templ/view" ) const ( diff --git a/internal/web/middleware/slowdown.go b/internal/web/middleware/slowdown.go index 5e23055..1896069 100644 --- a/internal/web/middleware/slowdown.go +++ b/internal/web/middleware/slowdown.go @@ -7,8 +7,8 @@ import ( func SlowdownMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Slow down request by 10 milliseconds for lazy loading demonstration - time.Sleep(10 * time.Millisecond) + // Slow down request by 2 milliseconds for lazy loading demonstration + time.Sleep(2 * time.Millisecond) next.ServeHTTP(w, r) }) } diff --git a/internal/web/static/css/input.css b/internal/web/static/css/input.css new file mode 100644 index 0000000..74adbcb --- /dev/null +++ b/internal/web/static/css/input.css @@ -0,0 +1,19 @@ +@import "tailwindcss"; +@source "../../templ/**/*.templ"; +@plugin "daisyui" { + themes: light --default, dark --prefersdark; +} + +[data-theme="light"] { + --color-base-100: #ffffff; + --color-base-200: #f4f4f5; + --color-base-300: #fafafa; + --color-neutral: #0000001a; +} + +[data-theme="dark"] { + --color-base-100: #121212; + --color-base-200: #27272a; + --color-base-300: #09090b; + --color-neutral: #ffffff1a; +} diff --git a/internal/web/static/custom.css b/internal/web/static/custom.css deleted file mode 100644 index 5f6dac0..0000000 --- a/internal/web/static/custom.css +++ /dev/null @@ -1,48 +0,0 @@ -:root { - --pico-nav-element-spacing-vertical: 0.5rem; - --pico-nav-element-spacing-horizontal:0.5rem; - --pico-form-element-spacing-vertical: 0.5rem; - --pico-nav-link-spacing-vertical: 0rem; - --pico-border-radius: 0.3rem; - --pico-spacing: 0.9rem; - --pico-font-size: 110%; -} - -fieldset { - padding: 0 10px; -} - -button { - padding: 0.3rem; -} - -article > header { - margin-bottom: 0.25rem; -} - -.page-content { - max-width: 800px; -} - -.align-center { - text-align: center; -} - -.align-right { - display: block; - text-align: right; -} - -.error-msg { - padding: 1em; - color: red; -} - -.htmx-indicator { - display: none; - text-align: center; -} - -.htmx-request { - display: block; -} diff --git a/internal/web/templ/components/error.templ b/internal/web/templ/components/error.templ index 30374c1..f03fb17 100644 --- a/internal/web/templ/components/error.templ +++ b/internal/web/templ/components/error.templ @@ -1,8 +1,19 @@ package components templ ErrorMsg(msg string) { - +
+
+ + Error: + { msg } +
+ + +
} diff --git a/internal/web/templ/components/modal.templ b/internal/web/templ/components/modal.templ new file mode 100644 index 0000000..8d9cf20 --- /dev/null +++ b/internal/web/templ/components/modal.templ @@ -0,0 +1,26 @@ +package components + +templ Modal(method, url, target, swap string, content templ.Component) { + + + + +} diff --git a/internal/web/templ/components/modal_add_note.templ b/internal/web/templ/components/modal_add_note.templ index 0abd80b..d1a84ea 100644 --- a/internal/web/templ/components/modal_add_note.templ +++ b/internal/web/templ/components/modal_add_note.templ @@ -1,17 +1,11 @@ package components templ ModalAddNote() { - -
- @ModalForm("Add note", "Creating Note...", "Create", "", "") -
-
+ @Modal( + "post", + "/notes", + "#notes", + "afterbegin", + ModalForm("New Note", "Saving...", "Create", "", ""), + ) } diff --git a/internal/web/templ/components/modal_edit_note.templ b/internal/web/templ/components/modal_edit_note.templ index 94cb235..a41bb61 100644 --- a/internal/web/templ/components/modal_edit_note.templ +++ b/internal/web/templ/components/modal_edit_note.templ @@ -2,21 +2,15 @@ package components import ( "strconv" - "github.com/sonjek/go-templ-htmx-picocss-example/internal/storage" + "github.com/sonjek/go-full-stack-example/internal/storage" ) templ ModalEditNote(note storage.Note) { - -
- @ModalForm("Edit note", "Updating Note...", "Update", note.Title, note.Body) -
-
+ @Modal( + "put", + "/note/" + strconv.Itoa(note.ID), + "#note-" + strconv.Itoa(note.ID), + "outerHTML", + ModalForm("Edit Note", "Updating Note...", "Update Note", note.Title, note.Body), + ) } diff --git a/internal/web/templ/components/modal_form.templ b/internal/web/templ/components/modal_form.templ index 29f6add..0f8b06e 100644 --- a/internal/web/templ/components/modal_form.templ +++ b/internal/web/templ/components/modal_form.templ @@ -1,38 +1,63 @@ package components templ ModalForm(title, loaderMsg, action, noteTitle, noteBody string) { -
-
-

{ title }

+
+
+

+ { title } +

+
-
-
- - -
- { loaderMsg } +
+ -
- - +
+ + +
+ +
+ + +
+ +
+ + { loaderMsg } +
+
+ + -
- + } diff --git a/internal/web/templ/components/navbar.templ b/internal/web/templ/components/navbar.templ new file mode 100644 index 0000000..162b415 --- /dev/null +++ b/internal/web/templ/components/navbar.templ @@ -0,0 +1,43 @@ +package components + +templ Navbar() { +
+
+
+
+ +
+ Notebook +
+ +
+ + +
+ + + + +
+
+
+ + +} diff --git a/internal/web/templ/components/note.templ b/internal/web/templ/components/note.templ index 493ba8c..338c54f 100644 --- a/internal/web/templ/components/note.templ +++ b/internal/web/templ/components/note.templ @@ -2,65 +2,71 @@ package components import ( "strconv" - "github.com/sonjek/go-templ-htmx-picocss-example/pkg/datetime" - "github.com/sonjek/go-templ-htmx-picocss-example/internal/storage" + "github.com/sonjek/go-full-stack-example/pkg/datetime" + "github.com/sonjek/go-full-stack-example/internal/storage" ) -templ noteData(note storage.Note) { -
- -
-

- { note.Body } -

- -} +templ NoteItem(note storage.Note, loadMoreURL ...string) { +
0 && loadMoreURL[0] != "" { + hx-get={ loadMoreURL[0] } + hx-trigger="intersect once" + hx-target="this" + hx-swap="afterend" + hx-push-url="false" + hx-indicator=".htmx-indicator" + } + > +
+

+ { note.Title } +

+ +
+ +
+

+ { note.Body } +

+
-templ NoteItem(note storage.Note) { -
- @noteData(note) -
+
+
+ + { datetime.FormatToDateTime(note.CreatedAt) } ({ datetime.FormatToAgo(note.CreatedAt) }) +
+
+ { note.ID } +
+
+
} templ LastNote(note storage.Note) { -
- @noteData(note) -
+ @NoteItem(note, "/notes/load-more?cursor=" + strconv.Itoa(note.ID)) } - diff --git a/internal/web/templ/components/notesList.templ b/internal/web/templ/components/notesList.templ index 48f72e2..19e13ab 100644 --- a/internal/web/templ/components/notesList.templ +++ b/internal/web/templ/components/notesList.templ @@ -1,6 +1,6 @@ package components -import "github.com/sonjek/go-templ-htmx-picocss-example/internal/storage" +import "github.com/sonjek/go-full-stack-example/internal/storage" templ NotesList(notes []storage.Note) { if len(notes) != 0 { diff --git a/internal/web/templ/page/index.templ b/internal/web/templ/page/index.templ index 5b5ddb8..aab89e9 100644 --- a/internal/web/templ/page/index.templ +++ b/internal/web/templ/page/index.templ @@ -1,5 +1,9 @@ package page +import ( + "github.com/sonjek/go-full-stack-example/internal/web/templ/components" +) + templ Index(body templ.Component) { @@ -7,30 +11,38 @@ templ Index(body templ.Component) { + - + - - Notes list + Notes App - -
- -
-
+ + @components.Navbar() + +
@body
-