Skip to content

Swap Bulma for DaisyUI (Tailwind) #111

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jun 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .air.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ tmp_dir = "tmp"
[build]
args_bin = []
bin = "./tmp/main"
cmd = "go build -o ./tmp/main ./cmd/web"
cmd = "make build"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata", "uploads", "dbs"]
exclude_dir = ["assets", "tmp", "vendor", "testdata", "uploads", "dbs", "public"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_ext = ["go", "tpl", "tmpl", "html", "css"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
.idea
dbs
uploads
tmp
tmp
tailwindcss
daisyui*
21 changes: 21 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
# The Tailwind CSS CLI package to install (pick the one that matches your OS: https://github.com/tailwindlabs/tailwindcss/releases/latest)
TAILWIND_PACKAGE = tailwindcss-linux-x64

.PHONY: help
help: ## Print make targets
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

.PHONY: install
install: ent-install air-install tailwind-install ## Install all dependencies

.PHONY: tailwind-install
tailwind-install: ## Install the Tailwind CSS CLI
curl -sLo tailwindcss https://github.com/tailwindlabs/tailwindcss/releases/latest/download/$(TAILWIND_PACKAGE)
chmod +x tailwindcss
curl -sLO https://github.com/saadeghi/daisyui/releases/latest/download/daisyui.js
curl -sLO https://github.com/saadeghi/daisyui/releases/latest/download/daisyui-theme.js

.PHONY: ent-install
ent-install: ## Install Ent code-generation module
go get entgo.io/ent/cmd/ent
Expand Down Expand Up @@ -39,3 +52,11 @@ test: ## Run all tests
.PHONY: check-updates
check-updates: ## Check for direct dependency updates
go list -u -m -f '{{if not .Indirect}}{{.}}{{end}}' all | grep "\["

.PHONY: css
css: ## Build and minify Tailwind CSS
./tailwindcss -i tailwind.css -o public/static/main.css -m

.PHONY: build
build: css ## Build CSS and compile the application binary
go build -o ./tmp/main ./cmd/web
124 changes: 101 additions & 23 deletions README.md

Large diffs are not rendered by default.

14 changes: 3 additions & 11 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,6 @@ import (
"github.com/spf13/viper"
)

const (
// StaticDir stores the name of the directory that will serve static files.
StaticDir = "static"

// StaticPrefix stores the URL prefix used when serving static files.
StaticPrefix = "files"
)

type environment string

const (
Expand All @@ -25,8 +17,8 @@ const (
// EnvTest represents the test environment.
EnvTest environment = "test"

// EnvDevelop represents the development environment.
EnvDevelop environment = "dev"
// EnvDevelopment represents the development environment.
EnvDevelopment environment = "dev"

// EnvStaging represents the staging environment.
EnvStaging environment = "staging"
Expand Down Expand Up @@ -92,7 +84,7 @@ type (
CacheConfig struct {
Capacity int
Expiration struct {
StaticFile time.Duration
PublicFile time.Duration
}
}

Expand Down
2 changes: 1 addition & 1 deletion config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ app:
cache:
capacity: 100000
expiration:
staticFile: "4380h"
publicFile: "4380h"

database:
driver: "sqlite3"
Expand Down
6 changes: 3 additions & 3 deletions pkg/handlers/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func (h *Admin) EntityAddSubmit(n *gen.Type) echo.HandlerFunc {
return func(ctx echo.Context) error {
err := h.admin.Create(ctx, n.Name)
if err != nil {
msg.Danger(ctx, err.Error())
msg.Error(ctx, err.Error())
return h.EntityAdd(n)(ctx)
}

Expand All @@ -154,7 +154,7 @@ func (h *Admin) EntityEditSubmit(n *gen.Type) echo.HandlerFunc {
id := ctx.Get(context.AdminEntityIDKey).(int)
err := h.admin.Update(ctx, n.Name, id)
if err != nil {
msg.Danger(ctx, err.Error())
msg.Error(ctx, err.Error())
return h.EntityEdit(n)(ctx)
}

Expand All @@ -178,7 +178,7 @@ func (h *Admin) EntityDeleteSubmit(n *gen.Type) echo.HandlerFunc {
return func(ctx echo.Context) error {
id := ctx.Get(context.AdminEntityIDKey).(int)
if err := h.admin.Delete(ctx, n.Name, id); err != nil {
msg.Danger(ctx, err.Error())
msg.Error(ctx, err.Error())
return h.EntityDelete(n)(ctx)
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/handlers/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func (h *Auth) LoginSubmit(ctx echo.Context) error {
authFailed := func() error {
input.SetFieldError("Email", "")
input.SetFieldError("Password", "")
msg.Danger(ctx, "Invalid credentials. Please try again.")
msg.Error(ctx, "Invalid credentials. Please try again.")
return h.LoginPage(ctx)
}

Expand Down Expand Up @@ -185,7 +185,7 @@ func (h *Auth) Logout(ctx echo.Context) error {
if err := h.auth.Logout(ctx); err == nil {
msg.Success(ctx, "You have been logged out successfully.")
} else {
msg.Danger(ctx, "An error occurred. Please try again.")
msg.Error(ctx, "An error occurred. Please try again.")
}
return redirect.New(ctx).
Route(routenames.Home).
Expand Down
2 changes: 1 addition & 1 deletion pkg/handlers/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (h *Files) Page(ctx echo.Context) error {
func (h *Files) Submit(ctx echo.Context) error {
file, err := ctx.FormFile("file")
if err != nil {
msg.Danger(ctx, "A file is required.")
msg.Error(ctx, "A file is required.")
return h.Page(ctx)
}

Expand Down
1 change: 1 addition & 0 deletions pkg/handlers/pages.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func (h *Pages) fetchPosts(pager *pager.Pager) []models.Post {

for k := range posts {
posts[k] = models.Post{
ID: k + 1,
Title: fmt.Sprintf("Post example #%d", k+1),
Body: fmt.Sprintf("Lorem ipsum example #%d ddolor sit amet, consectetur adipiscing elit. Nam elementum vulputate tristique.", k+1),
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/handlers/pages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func TestPages__About(t *testing.T) {
toDoc()

// Goquery is an excellent package to use for testing HTML markup
h1 := doc.Find("h1.title")
h1 := doc.Find("h1")
assert.Len(t, h1.Nodes, 1)
assert.Equal(t, "About", h1.Text())
}
43 changes: 32 additions & 11 deletions pkg/handlers/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,52 @@ package handlers

import (
"net/http"
"strings"

"github.com/gorilla/sessions"
"github.com/labstack/echo/v4"
echomw "github.com/labstack/echo/v4/middleware"
"github.com/mikestefanello/pagoda/config"
"github.com/mikestefanello/pagoda/pkg/context"
"github.com/mikestefanello/pagoda/pkg/middleware"
"github.com/mikestefanello/pagoda/pkg/services"
files "github.com/mikestefanello/pagoda/public"
)

// BuildRouter builds the router.
func BuildRouter(c *services.Container) error {
// Static files with proper cache control.
// ui.File() should be used in ui components to append a cache key to the URL in order to break cache
// after each server restart.
c.Web.Group("", middleware.CacheControl(c.Config.Cache.Expiration.StaticFile)).
Static(config.StaticPrefix, config.StaticDir)

// Non-static file route group.
g := c.Web.Group("")

// Force HTTPS, if enabled.
if c.Config.HTTP.TLS.Enabled {
g.Use(echomw.HTTPSRedirect())
c.Web.Use(echomw.HTTPSRedirect())
}

// Serve public files with cache control.
c.Web.Group("", middleware.CacheControl(c.Config.Cache.Expiration.PublicFile)).
Static("files", "public/files")

// Serve static files.
// ui.StaticFile() should be used in ui components to append a cache key to the URL to break cache
// after each server reboot.
c.Web.Group(
"",
echomw.GzipWithConfig(echomw.GzipConfig{
Skipper: func(c echo.Context) bool {
for _, ext := range []string{
".js",
".css",
} {
if strings.HasSuffix(c.Request().URL.Path, ext) {
return false
}
}
return true
},
}),
middleware.CacheControl(c.Config.Cache.Expiration.PublicFile),
).StaticFS("static", echo.MustSubFS(files.Static, "static"))

// Non-static file route group.
g := c.Web.Group("")

// Create a cookie store for session data.
cookieStore := sessions.NewCookieStore([]byte(c.Config.App.EncryptionKey))
cookieStore.Options.HttpOnly = true
Expand Down
16 changes: 8 additions & 8 deletions pkg/msg/msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ const (
// TypeWarning represents a warning message type.
TypeWarning Type = "warning"

// TypeDanger represents a danger message type.
TypeDanger Type = "danger"
// TypeError represents an error message type.
TypeError Type = "error"
)

const (
Expand All @@ -44,9 +44,9 @@ func Warning(ctx echo.Context, message string) {
Set(ctx, TypeWarning, message)
}

// Danger sets a danger flash message.
func Danger(ctx echo.Context, message string) {
Set(ctx, TypeDanger, message)
// Error sets an error flash message.
func Error(ctx echo.Context, message string) {
Set(ctx, TypeError, message)
}

// Set adds a new flash message of a given type into the session storage.
Expand All @@ -61,19 +61,19 @@ func Set(ctx echo.Context, typ Type, message string) {
// Get gets flash messages of a given type from the session storage.
// Errors will be logged and not returned.
func Get(ctx echo.Context, typ Type) []string {
var msgs []string

if sess, err := getSession(ctx); err == nil {
if flash := sess.Flashes(string(typ)); len(flash) > 0 {
save(ctx, sess)

msgs := make([]string, 0, len(flash))
for _, m := range flash {
msgs = append(msgs, m.(string))
}
return msgs
}
}

return msgs
return nil
}

// getSession gets the flash message session.
Expand Down
4 changes: 2 additions & 2 deletions pkg/msg/msg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ func TestMsg(t *testing.T) {
assertMsg(TypeInfo, text)

text = "ccc"
Danger(ctx, text)
assertMsg(TypeDanger, text)
Error(ctx, text)
assertMsg(TypeError, text)

text = "ddd"
Warning(ctx, text)
Expand Down
2 changes: 1 addition & 1 deletion pkg/services/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ func (c *Container) initMail() {
// initTasks initializes the task client.
func (c *Container) initTasks() {
var err error
// You could use a separate database for tasks, if you'd like. but using one
// You could use a separate database for tasks, if you'd like, but using one
// makes transaction support easier.
c.Tasks, err = backlite.NewClient(backlite.ClientConfig{
DB: c.Database,
Expand Down
Loading
Loading