diff --git a/src/core/auth/session.go b/src/core/auth/session.go index fedf88a..b8de9bb 100644 --- a/src/core/auth/session.go +++ b/src/core/auth/session.go @@ -15,18 +15,19 @@ import ( type contextKey string const ( - // ContextUserID is the key for the authenticated user's ID. ContextUserID contextKey = "user_id" + ContextRole contextKey = "role" ) // SessionStore manages user sessions backed by SQLite. type SessionStore struct { - db *sql.DB + db *sql.DB + roleDB *sql.DB // Optional: same DB, used for role lookups } // NewSessionStore creates a session store. func NewSessionStore(db *sql.DB) *SessionStore { - return &SessionStore{db: db} + return &SessionStore{db: db, roleDB: db} } // Session represents an authenticated user session. @@ -120,7 +121,15 @@ func (s *SessionStore) SessionMiddleware(next http.Handler) http.Handler { } // Set user_id in context + // Set user_id and role in context ctx := context.WithValue(r.Context(), ContextUserID, session.UserID) + // Look up role from database + var role string + s.roleDB.QueryRow("SELECT role FROM users WHERE username = ?", session.UserID).Scan(&role) + if role == "" { + role = "user" + } + ctx = context.WithValue(ctx, ContextRole, role) next.ServeHTTP(w, r.WithContext(ctx)) }) } diff --git a/src/core/ui/app-grid.templ b/src/core/ui/app-grid.templ index 656aa34..0a1a1dd 100644 --- a/src/core/ui/app-grid.templ +++ b/src/core/ui/app-grid.templ @@ -1,107 +1,107 @@ package ui -import "fmt" - -// AppTile represents an application card on the workspace launcher. +// AppTile represents an app on the workspace launcher. type AppTile struct { Name string Description string URL string - Icon string // Emoji or SVG - Color string // Background color for icon - Status string // "ready", "coming-soon", "beta" + Icon string // SVG inline + Color string // Background color + Status string // "ready" or "coming-soon" + AdminOnly bool // Only visible to admin role } -// DefaultApps returns the default set of workspace apps. -// These are placeholder tiles until supervisor modules are built. -func DefaultApps() []AppTile { - return []AppTile{ +// DefaultApps returns apps filtered by user role. +func DefaultApps(role string) []AppTile { + all := []AppTile{ { - Name: "Admin Panel", - Description: "Manage users, groups, and workspace settings", - URL: "/admin", - Icon: "⚙️", - Color: "#1e293b", - Status: "ready", + Name: "Admin Panel", Description: "Users, groups & settings", + URL: "/admin", Color: "#9333ea", + Icon: ``, + Status: "ready", AdminOnly: true, }, { - Name: "Files", - Description: "Coming soon — File storage and sharing", - URL: "#", - Icon: "📁", - Color: "#1e293b", - Status: "coming-soon", + Name: "Files", Description: "Storage & sharing", + URL: "#", Color: "#2563eb", + Icon: ``, + Status: "coming-soon", }, { - Name: "Calendar", - Description: "Coming soon — Schedule and events", - URL: "#", - Icon: "📅", - Color: "#1e293b", - Status: "coming-soon", + Name: "Mail", Description: "Email client", + URL: "#", Color: "#dc2626", + Icon: ``, + Status: "coming-soon", }, { - Name: "Mail", - Description: "Coming soon — Email integration", - URL: "#", - Icon: "✉️", - Color: "#1e293b", - Status: "coming-soon", + Name: "Calendar", Description: "Schedule & events", + URL: "#", Color: "#059669", + Icon: ``, + Status: "coming-soon", }, { - Name: "Office", - Description: "Coming soon — Documents and spreadsheets", - URL: "#", - Icon: "📝", - Color: "#1e293b", - Status: "coming-soon", + Name: "Office", Description: "Documents & sheets", + URL: "#", Color: "#0891b2", + Icon: ``, + Status: "coming-soon", }, { - Name: "Settings", - Description: "Coming soon — Workspace preferences", - URL: "#", - Icon: "🔧", - Color: "#1e293b", - Status: "coming-soon", + Name: "Contacts", Description: "People & directory", + URL: "#", Color: "#d97706", + Icon: ``, + Status: "coming-soon", + }, + { + Name: "Tasks", Description: "Project management", + URL: "#", Color: "#7c3aed", + Icon: ``, + Status: "coming-soon", + }, + { + Name: "Chat", Description: "Team messaging", + URL: "#", Color: "#db2777", + Icon: ``, + Status: "coming-soon", }, } + + // Filter by role + filtered := make([]AppTile, 0, len(all)) + for _, a := range all { + if a.AdminOnly && role != "admin" { + continue + } + filtered = append(filtered, a) + } + return filtered } templ AppGrid(apps []AppTile) { -
-

- Applications -

-
- for _, app := range apps { - @appCard(app) - } -
-
+
+ for _, a := range apps { + @appTile(a) + } +
} -templ appCard(app AppTile) { - if app.Status == "coming-soon" { - -
- { app.Icon } -
-
{ app.Name }
-
{ app.Description }
-
- ● Coming Soon -
-
+templ appTile(a AppTile) { + if a.Status == "coming-soon" { +
+
@templ.Raw(a.Icon)
+
{ a.Name }
+
Coming Soon
+
} else { - -
- { app.Icon } -
-
{ app.Name }
-
{ app.Description }
-
- ● Available -
-
+
+
@templ.Raw(a.Icon)
+
{ a.Name }
+
Available
+
} } diff --git a/src/core/ui/app-grid_templ.go b/src/core/ui/app-grid_templ.go index 8d9d779..6e0e751 100644 --- a/src/core/ui/app-grid_templ.go +++ b/src/core/ui/app-grid_templ.go @@ -8,71 +8,79 @@ package ui import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" -import "fmt" - -// AppTile represents an application card on the workspace launcher. +// AppTile represents an app on the workspace launcher. type AppTile struct { Name string Description string URL string - Icon string // Emoji or SVG - Color string // Background color for icon - Status string // "ready", "coming-soon", "beta" + Icon string // SVG inline + Color string // Background color + Status string // "ready" or "coming-soon" + AdminOnly bool // Only visible to admin role } -// DefaultApps returns the default set of workspace apps. -// These are placeholder tiles until supervisor modules are built. -func DefaultApps() []AppTile { - return []AppTile{ +// DefaultApps returns apps filtered by user role. +func DefaultApps(role string) []AppTile { + all := []AppTile{ { - Name: "Admin Panel", - Description: "Manage users, groups, and workspace settings", - URL: "/admin", - Icon: "⚙️", - Color: "#1e293b", - Status: "ready", + Name: "Admin Panel", Description: "Users, groups & settings", + URL: "/admin", Color: "#9333ea", + Icon: ``, + Status: "ready", AdminOnly: true, }, { - Name: "Files", - Description: "Coming soon — File storage and sharing", - URL: "#", - Icon: "📁", - Color: "#1e293b", - Status: "coming-soon", + Name: "Files", Description: "Storage & sharing", + URL: "#", Color: "#2563eb", + Icon: ``, + Status: "coming-soon", }, { - Name: "Calendar", - Description: "Coming soon — Schedule and events", - URL: "#", - Icon: "📅", - Color: "#1e293b", - Status: "coming-soon", + Name: "Mail", Description: "Email client", + URL: "#", Color: "#dc2626", + Icon: ``, + Status: "coming-soon", }, { - Name: "Mail", - Description: "Coming soon — Email integration", - URL: "#", - Icon: "✉️", - Color: "#1e293b", - Status: "coming-soon", + Name: "Calendar", Description: "Schedule & events", + URL: "#", Color: "#059669", + Icon: ``, + Status: "coming-soon", }, { - Name: "Office", - Description: "Coming soon — Documents and spreadsheets", - URL: "#", - Icon: "📝", - Color: "#1e293b", - Status: "coming-soon", + Name: "Office", Description: "Documents & sheets", + URL: "#", Color: "#0891b2", + Icon: ``, + Status: "coming-soon", }, { - Name: "Settings", - Description: "Coming soon — Workspace preferences", - URL: "#", - Icon: "🔧", - Color: "#1e293b", - Status: "coming-soon", + Name: "Contacts", Description: "People & directory", + URL: "#", Color: "#d97706", + Icon: ``, + Status: "coming-soon", + }, + { + Name: "Tasks", Description: "Project management", + URL: "#", Color: "#7c3aed", + Icon: ``, + Status: "coming-soon", + }, + { + Name: "Chat", Description: "Team messaging", + URL: "#", Color: "#db2777", + Icon: ``, + Status: "coming-soon", }, } + + // Filter by role + filtered := make([]AppTile, 0, len(all)) + for _, a := range all { + if a.AdminOnly && role != "admin" { + continue + } + filtered = append(filtered, a) + } + return filtered } func AppGrid(apps []AppTile) templ.Component { @@ -96,17 +104,17 @@ func AppGrid(apps []AppTile) templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Applications

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - for _, app := range apps { - templ_7745c5c3_Err = appCard(app).Render(ctx, templ_7745c5c3_Buffer) + for _, a := range apps { + templ_7745c5c3_Err = appTile(a).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -114,7 +122,7 @@ func AppGrid(apps []AppTile) templ.Component { }) } -func appCard(app AppTile) templ.Component { +func appTile(a AppTile) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -135,15 +143,15 @@ func appCard(app AppTile) templ.Component { templ_7745c5c3_Var2 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - if app.Status == "coming-soon" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
Coming Soon
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(a.Icon).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var6 string - templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(app.Description) + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(a.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/app-grid.templ`, Line: 90, Col: 42} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/app-grid.templ`, Line: 103, Col: 33} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
● Coming Soon
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var9 string - templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(app.Icon) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/app-grid.templ`, Line: 98, Col: 14} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var10 string - templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(app.Name) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/app-grid.templ`, Line: 100, Col: 35} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var11 string - templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(app.Description) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/app-grid.templ`, Line: 101, Col: 42} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
● Available
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
Available
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/src/core/ui/handler.go b/src/core/ui/handler.go index 579ffc5..063bec8 100644 --- a/src/core/ui/handler.go +++ b/src/core/ui/handler.go @@ -3,6 +3,8 @@ package ui import ( "net/http" "path/filepath" + + "git.lohmar.co.uk/lexton-it/NextWks/core/auth" ) // Handler serves the workspace launcher UI. @@ -12,35 +14,34 @@ type Handler struct { // NewHandler creates a UI handler that serves the launcher and static assets. func NewHandler(appDir string) *Handler { - return &Handler{ - appDir: appDir, - } + return &Handler{appDir: appDir} } // RegisterRoutes mounts the public UI routes on the given mux. func (h *Handler) RegisterRoutes(mux *http.ServeMux, authGate func(http.Handler) http.Handler) { - // Static assets (manifest.json, sw.js, icons) staticDir := filepath.Join(h.appDir, "static") - staticHandler := http.FileServer(http.Dir(staticDir)) - mux.Handle("GET /static/", http.StripPrefix("/static", staticHandler)) - - // Launcher page — protected by OIDC auth gate + mux.Handle("GET /static/", http.StripPrefix("/static", http.FileServer(http.Dir(staticDir)))) mux.Handle("GET /", authGate(http.HandlerFunc(h.launcherPage))) } -// launcherPage renders the main workspace landing page. func (h *Handler) launcherPage(w http.ResponseWriter, r *http.Request) { - // Only handle root path, not all paths if r.URL.Path != "/" { http.NotFound(w, r) return } - // Get user info from session (set by auth middleware) - // For now, render without user name (OIDC provides this later) - userName := "" + userID, _ := auth.GetUserID(r) + role := getUserRole(r) + apps := DefaultApps(role) - apps := DefaultApps() - component := LauncherPage(userName, apps) + component := LauncherPage(userID, role, apps) component.Render(r.Context(), w) } + +// getUserRole extracts the user's role from the request context or session. +func getUserRole(r *http.Request) string { + if role, ok := r.Context().Value(auth.ContextRole).(string); ok { + return role + } + return "user" +} diff --git a/src/core/ui/launcher.templ b/src/core/ui/launcher.templ index bfbe2a8..ec73ef9 100644 --- a/src/core/ui/launcher.templ +++ b/src/core/ui/launcher.templ @@ -1,245 +1,269 @@ package ui -import "fmt" - -templ LauncherPage(userName string, apps []AppTile) { +templ LauncherPage(userName string, role string, apps []AppTile) { - + Next Workspace - + - + -
- @headerBar(userName) -
-
- @greetingHeading(userName) -

Your workspace is ready

-
+
+ @topBar(userName, role) +
+ @greetingSection(userName) @AppGrid(apps) - @PWAInstallPrompt()
-
") + templ_7745c5c3_Err = pwaInstructions().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -67,7 +65,7 @@ func LauncherPage(userName string, apps []AppTile) templ.Component { }) } -func greetingHeading(userName string) templ.Component { +func greetingSection(userName string) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -88,32 +86,46 @@ func greetingHeading(userName string) templ.Component { templ_7745c5c3_Var2 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - if userName != "" { - var templ_7745c5c3_Var3 string - templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("Welcome, %s", userName)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/launcher.templ`, Line: 73, Col: 41} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { - var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs("Welcome") - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/launcher.templ`, Line: 75, Col: 14} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(initials(userName)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/launcher.templ`, Line: 46, Col: 42} } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

Good ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(timeOfDay()) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/launcher.templ`, Line: 48, Col: 25} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, ", ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(userName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/launcher.templ`, Line: 48, Col: 39} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "

Your workspace is ready

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -121,46 +133,7 @@ func greetingHeading(userName string) templ.Component { }) } -func headerBar(userName string) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var5 := templ.GetChildren(ctx) - if templ_7745c5c3_Var5 == nil { - templ_7745c5c3_Var5 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
NextWks
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if userName != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func workspaceStyles() templ.Component { +func topBar(userName string, role string) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -181,7 +154,126 @@ func workspaceStyles() templ.Component { templ_7745c5c3_Var6 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
N
NextWks
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(initials(userName)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/launcher.templ`, Line: 65, Col: 47} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func workspaceCSS() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var8 := templ.GetChildren(ctx) + if templ_7745c5c3_Var8 == nil { + templ_7745c5c3_Var8 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// --- Helpers --- + +func initials(name string) string { + if name == "" { + return "?" + } + if len(name) == 1 { + return name + } + return string(name[0]) +} + +func timeOfDay() string { + // Simple: always show "morning" for now + return "morning" +} + +// --- PWA Instructions --- +func pwaInstructions() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var9 := templ.GetChildren(ctx) + if templ_7745c5c3_Var9 == nil { + templ_7745c5c3_Var9 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "

Desktop Chrome/Edge

  1. Click the icon in the address bar
  2. Click Install

iOS Safari

  1. Tap Share
  2. Tap Add to Home Screen

Android Chrome

  1. Tap menu
  2. Tap Install app
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// --- JavaScript --- +func launcherJS() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var10 := templ.GetChildren(ctx) + if templ_7745c5c3_Var10 == nil { + templ_7745c5c3_Var10 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/src/main.go b/src/main.go index b992ce3..8a8836a 100644 --- a/src/main.go +++ b/src/main.go @@ -149,6 +149,12 @@ func main() { isAdmin, _ := roleChecker.IsAdmin(session.UserID) if isAdmin { ctx := context.WithValue(r.Context(), auth.ContextUserID, session.UserID) + ctx = context.WithValue(ctx, auth.ContextRole, "admin") + next.ServeHTTP(w, r.WithContext(ctx)) + return + } else { + ctx := context.WithValue(r.Context(), auth.ContextUserID, session.UserID) + ctx = context.WithValue(ctx, auth.ContextRole, "user") next.ServeHTTP(w, r.WithContext(ctx)) return }