diff --git a/admin/next.config.ts b/admin/next.config.ts
index 97ad710..f731faa 100644
--- a/admin/next.config.ts
+++ b/admin/next.config.ts
@@ -2,7 +2,6 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "standalone",
- basePath: "/admin",
trailingSlash: true,
images: {
unoptimized: true,
diff --git a/admin/src/app/(dashboard)/apple-social-auth/page.tsx b/admin/src/app/(dashboard)/apple-social-auth/page.tsx
index ed7cfbd..a022965 100644
--- a/admin/src/app/(dashboard)/apple-social-auth/page.tsx
+++ b/admin/src/app/(dashboard)/apple-social-auth/page.tsx
@@ -206,7 +206,7 @@ export default function AppleSocialAuthPage() {
{entry.id}
{entry.username}
diff --git a/admin/src/app/(dashboard)/auth-tokens/page.tsx b/admin/src/app/(dashboard)/auth-tokens/page.tsx
index c2b8820..c141780 100644
--- a/admin/src/app/(dashboard)/auth-tokens/page.tsx
+++ b/admin/src/app/(dashboard)/auth-tokens/page.tsx
@@ -205,7 +205,7 @@ export default function AuthTokensPage() {
{token.user_id}
{token.username}
diff --git a/admin/src/app/(dashboard)/completion-images/page.tsx b/admin/src/app/(dashboard)/completion-images/page.tsx
index 1b9c9e0..8d04a8f 100644
--- a/admin/src/app/(dashboard)/completion-images/page.tsx
+++ b/admin/src/app/(dashboard)/completion-images/page.tsx
@@ -221,7 +221,7 @@ export default function CompletionImagesPage() {
{img.task_title || `Task #${img.task_id}`}
@@ -234,7 +234,7 @@ export default function CompletionImagesPage() {
#{img.completion_id}
diff --git a/admin/src/app/(dashboard)/confirmation-codes/page.tsx b/admin/src/app/(dashboard)/confirmation-codes/page.tsx
index 0ff0117..2315a16 100644
--- a/admin/src/app/(dashboard)/confirmation-codes/page.tsx
+++ b/admin/src/app/(dashboard)/confirmation-codes/page.tsx
@@ -212,7 +212,7 @@ export default function ConfirmationCodesPage() {
{code.id}
{code.username}
diff --git a/admin/src/app/(dashboard)/devices/page.tsx b/admin/src/app/(dashboard)/devices/page.tsx
index d8b2c04..94fd786 100644
--- a/admin/src/app/(dashboard)/devices/page.tsx
+++ b/admin/src/app/(dashboard)/devices/page.tsx
@@ -312,7 +312,7 @@ export default function DevicesPage() {
{device.user_id ? (
{device.username || `User #${device.user_id}`}
diff --git a/admin/src/app/(dashboard)/document-images/page.tsx b/admin/src/app/(dashboard)/document-images/page.tsx
index 63e2f07..e5d6369 100644
--- a/admin/src/app/(dashboard)/document-images/page.tsx
+++ b/admin/src/app/(dashboard)/document-images/page.tsx
@@ -221,7 +221,7 @@ export default function DocumentImagesPage() {
{img.document_title || `Document #${img.document_id}`}
@@ -229,7 +229,7 @@ export default function DocumentImagesPage() {
{img.residence_name || `#${img.residence_id}`}
diff --git a/admin/src/app/(dashboard)/notification-prefs/page.tsx b/admin/src/app/(dashboard)/notification-prefs/page.tsx
index 92fb22b..5714f11 100644
--- a/admin/src/app/(dashboard)/notification-prefs/page.tsx
+++ b/admin/src/app/(dashboard)/notification-prefs/page.tsx
@@ -102,7 +102,7 @@ export default function NotificationPrefsPage() {
header: 'User',
cell: ({ row }) => (
{row.original.username}
@@ -265,7 +265,7 @@ export default function NotificationPrefsPage() {
Actions
- View User
+ View User
[] = [
Actions
- View user
+ View user
diff --git a/admin/src/app/(dashboard)/password-reset-codes/page.tsx b/admin/src/app/(dashboard)/password-reset-codes/page.tsx
index 5c683c0..2363d6d 100644
--- a/admin/src/app/(dashboard)/password-reset-codes/page.tsx
+++ b/admin/src/app/(dashboard)/password-reset-codes/page.tsx
@@ -245,7 +245,7 @@ export default function PasswordResetCodesPage() {
{code.id}
{code.username}
diff --git a/admin/src/app/(dashboard)/share-codes/page.tsx b/admin/src/app/(dashboard)/share-codes/page.tsx
index 3f742d9..b98c1df 100644
--- a/admin/src/app/(dashboard)/share-codes/page.tsx
+++ b/admin/src/app/(dashboard)/share-codes/page.tsx
@@ -232,7 +232,7 @@ export default function ShareCodesPage() {
{code.residence_name}
@@ -240,7 +240,7 @@ export default function ShareCodesPage() {
{code.created_by}
diff --git a/admin/src/app/(dashboard)/user-profiles/page.tsx b/admin/src/app/(dashboard)/user-profiles/page.tsx
index 051720f..1cb3881 100644
--- a/admin/src/app/(dashboard)/user-profiles/page.tsx
+++ b/admin/src/app/(dashboard)/user-profiles/page.tsx
@@ -206,7 +206,7 @@ export default function UserProfilesPage() {
{profile.id}
{profile.username}
diff --git a/admin/src/components/app-sidebar.tsx b/admin/src/components/app-sidebar.tsx
index b3cadd8..1c144e2 100644
--- a/admin/src/components/app-sidebar.tsx
+++ b/admin/src/components/app-sidebar.tsx
@@ -49,40 +49,40 @@ import {
import { Button } from '@/components/ui/button';
const menuItems = [
- { title: 'Dashboard', url: '/admin/', icon: Home },
- { title: 'Users', url: '/admin/users', icon: Users },
- { title: 'User Profiles', url: '/admin/user-profiles', icon: UserCircle },
- { title: 'Apple Sign In', url: '/admin/apple-social-auth', icon: Apple },
- { title: 'Auth Tokens', url: '/admin/auth-tokens', icon: Key },
- { title: 'Confirmation Codes', url: '/admin/confirmation-codes', icon: Mail },
- { title: 'Password Resets', url: '/admin/password-reset-codes', icon: KeyRound },
- { title: 'Residences', url: '/admin/residences', icon: Building2 },
- { title: 'Share Codes', url: '/admin/share-codes', icon: Share2 },
- { title: 'Tasks', url: '/admin/tasks', icon: ClipboardList },
- { title: 'Completions', url: '/admin/completions', icon: CheckCircle },
- { title: 'Completion Images', url: '/admin/completion-images', icon: Image },
- { title: 'Contractors', url: '/admin/contractors', icon: Wrench },
- { title: 'Documents', url: '/admin/documents', icon: FileText },
- { title: 'Document Images', url: '/admin/document-images', icon: ImagePlus },
- { title: 'Notifications', url: '/admin/notifications', icon: Bell },
- { title: 'Notification Prefs', url: '/admin/notification-prefs', icon: BellRing },
- { title: 'Onboarding Emails', url: '/admin/onboarding-emails', icon: MailCheck },
- { title: 'Devices', url: '/admin/devices', icon: Smartphone },
- { title: 'Subscriptions', url: '/admin/subscriptions', icon: CreditCard },
+ { title: 'Dashboard', url: '/', icon: Home },
+ { title: 'Users', url: '/users', icon: Users },
+ { title: 'User Profiles', url: '/user-profiles', icon: UserCircle },
+ { title: 'Apple Sign In', url: '/apple-social-auth', icon: Apple },
+ { title: 'Auth Tokens', url: '/auth-tokens', icon: Key },
+ { title: 'Confirmation Codes', url: '/confirmation-codes', icon: Mail },
+ { title: 'Password Resets', url: '/password-reset-codes', icon: KeyRound },
+ { title: 'Residences', url: '/residences', icon: Building2 },
+ { title: 'Share Codes', url: '/share-codes', icon: Share2 },
+ { title: 'Tasks', url: '/tasks', icon: ClipboardList },
+ { title: 'Completions', url: '/completions', icon: CheckCircle },
+ { title: 'Completion Images', url: '/completion-images', icon: Image },
+ { title: 'Contractors', url: '/contractors', icon: Wrench },
+ { title: 'Documents', url: '/documents', icon: FileText },
+ { title: 'Document Images', url: '/document-images', icon: ImagePlus },
+ { title: 'Notifications', url: '/notifications', icon: Bell },
+ { title: 'Notification Prefs', url: '/notification-prefs', icon: BellRing },
+ { title: 'Onboarding Emails', url: '/onboarding-emails', icon: MailCheck },
+ { title: 'Devices', url: '/devices', icon: Smartphone },
+ { title: 'Subscriptions', url: '/subscriptions', icon: CreditCard },
];
const limitationsItems = [
- { title: 'Tier Limits', url: '/admin/limitations', icon: Layers },
- { title: 'Upgrade Triggers', url: '/admin/limitations/triggers', icon: Sparkles },
+ { title: 'Tier Limits', url: '/limitations', icon: Layers },
+ { title: 'Upgrade Triggers', url: '/limitations/triggers', icon: Sparkles },
];
const settingsItems = [
- { title: 'Monitoring', url: '/admin/monitoring', icon: Activity },
- { title: 'Automation Reference', url: '/admin/automation-reference', icon: Cog },
- { title: 'Lookup Tables', url: '/admin/lookups', icon: BookOpen },
- { title: 'Task Templates', url: '/admin/task-templates', icon: LayoutTemplate },
- { title: 'Admin Users', url: '/admin/admin-users', icon: UserCog },
- { title: 'Settings', url: '/admin/settings', icon: Settings },
+ { title: 'Monitoring', url: '/monitoring', icon: Activity },
+ { title: 'Automation Reference', url: '/automation-reference', icon: Cog },
+ { title: 'Lookup Tables', url: '/lookups', icon: BookOpen },
+ { title: 'Task Templates', url: '/task-templates', icon: LayoutTemplate },
+ { title: 'Admin Users', url: '/admin-users', icon: UserCog },
+ { title: 'Settings', url: '/settings', icon: Settings },
];
export function AppSidebar() {
@@ -134,7 +134,7 @@ export function AppSidebar() {
diff --git a/admin/src/lib/api.ts b/admin/src/lib/api.ts
index b272371..434d9f6 100644
--- a/admin/src/lib/api.ts
+++ b/admin/src/lib/api.ts
@@ -41,7 +41,7 @@ api.interceptors.response.use(
if (error.response?.status === 401) {
if (typeof window !== 'undefined') {
localStorage.removeItem('admin_token');
- window.location.href = '/admin/login/';
+ window.location.href = '/login/';
}
}
return Promise.reject(error);
diff --git a/internal/admin/routes.go b/internal/admin/routes.go
index b7f5c06..86baa5f 100644
--- a/internal/admin/routes.go
+++ b/internal/admin/routes.go
@@ -5,6 +5,7 @@ import (
"net/http/httputil"
"net/url"
"os"
+ "strings"
"github.com/labstack/echo/v4"
"gorm.io/gorm"
@@ -447,7 +448,10 @@ func SetupRoutes(router *echo.Echo, db *gorm.DB, cfg *config.Config, deps *Depen
setupAdminProxy(router)
}
-// setupAdminProxy configures reverse proxy to the Next.js admin panel
+// setupAdminProxy configures reverse proxy to the Next.js admin panel.
+// When ADMIN_HOST is set (e.g. admin.myhoneydue.com), requests to that
+// subdomain are proxied to Next.js at the root path. Requests to /admin/*
+// on the admin subdomain redirect to the main web app.
func setupAdminProxy(router *echo.Echo) {
// Get admin panel URL from env, default to localhost:3001
// Note: In production (Dokku), Next.js runs on internal port 3001
@@ -456,19 +460,6 @@ func setupAdminProxy(router *echo.Echo) {
adminURL = "http://127.0.0.1:3001"
}
- // Admin subdomain (e.g. admin.myhoneydue.com) — redirect root to /admin/
- adminHost := os.Getenv("ADMIN_HOST")
- if adminHost != "" {
- router.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
- return func(c echo.Context) error {
- if c.Request().Host == adminHost && c.Request().URL.Path == "/" {
- return c.Redirect(http.StatusMovedPermanently, "/admin/")
- }
- return next(c)
- }
- })
- }
-
target, err := url.Parse(adminURL)
if err != nil {
return
@@ -476,18 +467,40 @@ func setupAdminProxy(router *echo.Echo) {
proxy := httputil.NewSingleHostReverseProxy(target)
- // Handle all /admin/* requests
- router.Any("/admin/*", func(c echo.Context) error {
- proxy.ServeHTTP(c.Response(), c.Request())
- return nil
- })
+ // Admin subdomain: proxy all non-API requests to Next.js
+ adminHost := os.Getenv("ADMIN_HOST")
+ webAppURL := os.Getenv("WEB_APP_URL")
+ if webAppURL == "" {
+ webAppURL = "https://myhoneydue.com"
+ }
- // Also handle /admin without trailing path
- router.Any("/admin", func(c echo.Context) error {
- return c.Redirect(http.StatusMovedPermanently, "/admin/")
- })
+ if adminHost != "" {
+ router.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ if c.Request().Host != adminHost {
+ return next(c)
+ }
- // Proxy Next.js static assets
+ path := c.Request().URL.Path
+
+ // Redirect /admin/* to the main web app
+ if strings.HasPrefix(path, "/admin") {
+ return c.Redirect(http.StatusMovedPermanently, webAppURL)
+ }
+
+ // Let /api/* routes pass through to the Go API
+ if strings.HasPrefix(path, "/api/") {
+ return next(c)
+ }
+
+ // Proxy everything else to Next.js admin
+ proxy.ServeHTTP(c.Response(), c.Request())
+ return nil
+ }
+ })
+ }
+
+ // Proxy Next.js static assets (served from /_next/ regardless of host)
router.Any("/_next/*", func(c echo.Context) error {
proxy.ServeHTTP(c.Response(), c.Request())
return nil
diff --git a/internal/router/router.go b/internal/router/router.go
index 98093ac..822925c 100644
--- a/internal/router/router.go
+++ b/internal/router/router.go
@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"net/http"
+ "os"
"strings"
"time"
@@ -73,7 +74,9 @@ func SetupRouter(deps *Dependencies) *echo.Echo {
// timeout middleware wraps the response writer in *http.timeoutWriter
// which does not implement http.Flusher, causing a panic when
// httputil.ReverseProxy or WebSocket upgraders try to flush.
- return strings.HasPrefix(path, "/admin") ||
+ // Also skip for admin subdomain (all requests proxied to Next.js).
+ adminHost := os.Getenv("ADMIN_HOST")
+ return (adminHost != "" && c.Request().Host == adminHost) ||
strings.HasPrefix(path, "/_next") ||
strings.HasSuffix(path, "/ws")
},