From 8ec81532c0c8d474416ddcec707e53778f334bcb Mon Sep 17 00:00:00 2001 From: Alec Schitzkat Date: Wed, 3 Dec 2025 22:00:17 +0100 Subject: [PATCH 1/2] Enhances settings and backend API configuration Updates the settings page to include language and theme options, allowing users to customize the application's appearance and localization. Simplifies the backend API configuration by removing the "/api" suffix from the API_URL, ensuring consistent URL handling for uploads and other API endpoints. The commit also introduces a fallback slug generation mechanism for forms, using the first 8 characters of the form ID when the slug is cleared. --- .env.example | 2 +- README.md | 16 ++-- backend/cmd/server/main.go | 6 +- backend/internal/config/config.go | 4 +- backend/internal/handlers/form.go | 3 +- backend/internal/handlers/setup.go | 12 +++ backend/internal/models/form.go | 5 + backend/internal/models/settings.go | 9 +- frontend/Dockerfile | 10 +- frontend/app/app.vue | 11 --- .../app/components/Builder/FormSettings.vue | 3 +- frontend/app/components/ThemeToggle.vue | 33 ------- frontend/app/composables/useApi.ts | 13 +-- frontend/app/composables/useAutoSave.ts | 13 ++- frontend/app/layouts/default.vue | 2 - frontend/app/pages/forms/[id]/edit.vue | 14 ++- frontend/app/pages/settings.vue | 96 ++++++++++++++++++- frontend/app/plugins/00.pina.client.ts | 14 ++- frontend/app/plugins/settings-head.server.ts | 28 +++++- frontend/app/store/setup.ts | 26 +++++ frontend/app/store/theme.ts | 49 +++++++--- frontend/docker-entrypoint.sh | 7 ++ frontend/i18n/locales/de.json | 11 +++ frontend/i18n/locales/en.json | 11 +++ frontend/nuxt.config.ts | 2 +- frontend/shared/types/index.ts | 4 + 26 files changed, 299 insertions(+), 105 deletions(-) delete mode 100644 frontend/app/components/ThemeToggle.vue create mode 100644 frontend/docker-entrypoint.sh diff --git a/.env.example b/.env.example index 2c3ef81..6fba3c7 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,7 @@ # FRONTEND CONFIGURATION # ============================================================================= BASE_URL=http://localhost:3000 -API_URL=http://localhost:8080/api +API_URL=http://localhost:8080 # ============================================================================= # BACKEND CONFIGURATION diff --git a/README.md b/README.md index b0d464b..4ecff88 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,8 @@ services: ports: - "3000:3000" environment: - - NUXT_PUBLIC_BASE_URL=http://localhost:3000 - - NUXT_PUBLIC_API_URL=http://localhost:8080/api + - BASE_URL=http://localhost:3000 + - API_URL=http://localhost:8080 depends_on: - backend @@ -78,8 +78,8 @@ docker run -d \ ```bash docker run -d \ -p 3000:3000 \ - -e NUXT_PUBLIC_BASE_URL=http://localhost:3000 \ - -e NUXT_PUBLIC_API_URL=http://localhost:8080/api \ + -e BASE_URL=http://localhost:3000 \ + -e API_URL=http://localhost:8080 \ ghcr.io/formeraapp/formera-frontend:latest ``` @@ -99,8 +99,8 @@ cd frontend && yarn install && yarn dev | Variable | Description | Default | |----------|-------------|---------| -| `NUXT_PUBLIC_BASE_URL` | Public URL of the frontend | `http://localhost:3000` | -| `NUXT_PUBLIC_API_URL` | Backend API URL | `http://localhost:8080/api` | +| `BASE_URL` | Public URL of the frontend | `http://localhost:3000` | +| `API_URL` | Backend base URL | `http://localhost:8080` | ### Backend @@ -176,8 +176,8 @@ services: image: ghcr.io/formeraapp/formera-frontend:latest restart: unless-stopped environment: - - NUXT_PUBLIC_BASE_URL=https://forms.example.com - - NUXT_PUBLIC_API_URL=https://forms.example.com/api + - BASE_URL=https://forms.example.com + - API_URL=https://forms.example.com labels: - "traefik.enable=true" - "traefik.http.routers.formera.rule=Host(`forms.example.com`)" diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index 3be996e..261bb15 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -179,9 +179,9 @@ func initStorage(cfg *config.Config) (storage.Storage, error) { return s3Store, nil default: - // Build full URL for local storage (ApiURL + LocalURL path) - apiURL := cfg.ApiURL + cfg.Storage.LocalURL - return storage.NewLocalStorage(cfg.Storage.LocalPath, apiURL) + // ApiURL is the backend base URL (e.g., http://localhost:8080) + uploadsURL := cfg.ApiURL + cfg.Storage.LocalURL + return storage.NewLocalStorage(cfg.Storage.LocalPath, uploadsURL) } } diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index ce18cbe..ecebd58 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -32,7 +32,7 @@ func init() { type Config struct { Port string BaseURL string // Frontend URL (e.g., http://localhost:3000) - ApiURL string // Backend API URL (e.g., http://localhost:8080/api) + ApiURL string // Backend base URL (e.g., http://localhost:8080) DBPath string JWTSecret string CorsOrigin string @@ -100,7 +100,7 @@ func Load() *Config { port := getEnv("PORT", "8080") baseURL := getEnv("BASE_URL", "http://localhost:3000") - apiURL := getEnv("API_URL", "http://localhost:"+port+"/api") + apiURL := getEnv("API_URL", "http://localhost:"+port) // CORS_ORIGIN defaults to BASE_URL if not set (same-origin deployment) corsOrigin := getEnv("CORS_ORIGIN", "") diff --git a/backend/internal/handlers/form.go b/backend/internal/handlers/form.go index 8a80d77..d96a542 100644 --- a/backend/internal/handlers/form.go +++ b/backend/internal/handlers/form.go @@ -243,7 +243,8 @@ func (h *FormHandler) Update(c *gin.Context) { if req.Slug != nil { slug := *req.Slug if slug == "" { - form.Slug = "" + // When slug is cleared, use first 8 chars of ID (form accessible via ID) + form.Slug = form.ID[:8] } else { slug = normalizeSlug(slug) if !isValidSlug(slug) { diff --git a/backend/internal/handlers/setup.go b/backend/internal/handlers/setup.go index 8823400..b8d6956 100644 --- a/backend/internal/handlers/setup.go +++ b/backend/internal/handlers/setup.go @@ -27,6 +27,8 @@ type SetupStatusResponse struct { LogoShowText bool `json:"logo_show_text"` FaviconURL string `json:"favicon_url"` LoginBackgroundURL string `json:"login_background_url"` + Language string `json:"language"` + Theme string `json:"theme"` } type SetupRequest struct { @@ -56,6 +58,8 @@ func (h *SetupHandler) GetStatus(c *gin.Context) { LogoShowText: settings.LogoShowText, FaviconURL: settings.FaviconURL, LoginBackgroundURL: settings.LoginBackgroundURL, + Language: settings.Language, + Theme: settings.Theme, }) } @@ -130,6 +134,8 @@ type UpdateSettingsRequest struct { LogoShowText *bool `json:"logo_show_text"` FaviconURL *string `json:"favicon_url"` LoginBackgroundURL *string `json:"login_background_url"` + Language string `json:"language"` + Theme string `json:"theme"` } func (h *SetupHandler) UpdateSettings(c *gin.Context) { @@ -166,6 +172,12 @@ func (h *SetupHandler) UpdateSettings(c *gin.Context) { if req.LoginBackgroundURL != nil { settings.LoginBackgroundURL = *req.LoginBackgroundURL } + if req.Language != "" { + settings.Language = req.Language + } + if req.Theme != "" { + settings.Theme = req.Theme + } database.DB.Save(&settings) diff --git a/backend/internal/models/form.go b/backend/internal/models/form.go index fb86192..85047d1 100644 --- a/backend/internal/models/form.go +++ b/backend/internal/models/form.go @@ -157,6 +157,11 @@ type Form struct { func (f *Form) BeforeCreate(tx *gorm.DB) error { f.ID = uuid.New().String() + // Auto-generate unique slug from ID if not set + if f.Slug == "" { + // Use first 8 characters of UUID as slug (unique enough) + f.Slug = f.ID[:8] + } if f.Status == "" { f.Status = FormStatusDraft } diff --git a/backend/internal/models/settings.go b/backend/internal/models/settings.go index f9cf981..e27d606 100644 --- a/backend/internal/models/settings.go +++ b/backend/internal/models/settings.go @@ -49,8 +49,11 @@ type Settings struct { LogoShowText bool `json:"logo_show_text" gorm:"default:true"` FaviconURL string `json:"favicon_url"` LoginBackgroundURL string `json:"login_background_url"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + // Language and Theme + Language string `json:"language" gorm:"default:en"` + Theme string `json:"theme" gorm:"default:system"` // "light", "dark", or "system" + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } func GetDefaultSettings() *Settings { @@ -65,5 +68,7 @@ func GetDefaultSettings() *Settings { LogoShowText: true, FaviconURL: "", LoginBackgroundURL: "", + Language: "en", + Theme: "system", } } diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 11c6a1a..8963788 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -20,10 +20,13 @@ RUN apk add --no-cache ca-certificates tzdata WORKDIR /app COPY --from=builder /app/.output ./.output +COPY docker-entrypoint.sh /docker-entrypoint.sh -# Environment variables (override with NUXT_PUBLIC_BASE_URL and NUXT_PUBLIC_API_URL) -ENV NUXT_PUBLIC_BASE_URL=http://localhost:3000 -ENV NUXT_PUBLIC_API_URL=http://localhost:8080/api +RUN chmod +x /docker-entrypoint.sh + +# Environment variables - use simple BASE_URL and API_URL +ENV BASE_URL=http://localhost:3000 +ENV API_URL=http://localhost:8080 ENV NITRO_PORT=3000 EXPOSE 3000 @@ -31,4 +34,5 @@ EXPOSE 3000 RUN addgroup -S app && adduser -S app -G app \ && chown -R app:app /app USER app +ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["node", ".output/server/index.mjs"] diff --git a/frontend/app/app.vue b/frontend/app/app.vue index 355a7e3..2ab3e21 100644 --- a/frontend/app/app.vue +++ b/frontend/app/app.vue @@ -1,14 +1,3 @@ - -