feat(15-03): wire AppLayout into all authenticated handlers and templates
- TablosListHandler: derives sidebarTablos from cardViews, calls TablosDashboard with activePath="/" - TabloDetailHandler: fetches ListTablosByUser for sidebar, calls TabloDetailPage with activePath="" - TabloUpdateHandler: fetches ListTablosByUser for non-HTMX error path - renderTabloCreateError: derives errorSidebarTablos from errorCardViews - TabloDiscussionTabHandler, TabloEventsTabHandler, TabloFilesTabHandler, TabloTasksTabHandler: fetch ListTablosByUser for non-HTMX full-page renders - PlanningPageHandler: fetches ListTablosByUser, calls PlanningPage with activePath="/planning" - AccountProvidersHandler: fetches ListTablosByUser, calls AccountProvidersPage with activePath="/" - planning.templ: updated signature + switched to @AppLayout - account_providers.templ: updated signature + switched to @AppLayout
This commit is contained in:
parent
9c7b080f67
commit
c7a16dbcae
8 changed files with 101 additions and 15 deletions
|
|
@ -1,9 +1,11 @@
|
||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"backend/internal/auth"
|
"backend/internal/auth"
|
||||||
|
"backend/internal/db/sqlc"
|
||||||
"backend/templates"
|
"backend/templates"
|
||||||
|
|
||||||
"github.com/gorilla/csrf"
|
"github.com/gorilla/csrf"
|
||||||
|
|
@ -34,7 +36,16 @@ func AccountProvidersHandler(deps AuthDeps) http.HandlerFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sidebarTablos, err := deps.Queries.ListTablosByUser(r.Context(), user.ID)
|
||||||
|
if err != nil {
|
||||||
|
slog.Default().Error("account providers: ListTablosByUser failed", "user_id", user.ID, "err", err)
|
||||||
|
sidebarTablos = []sqlc.Tablo{}
|
||||||
|
}
|
||||||
|
if sidebarTablos == nil {
|
||||||
|
sidebarTablos = []sqlc.Tablo{}
|
||||||
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
_ = templates.AccountProvidersPage(user, statuses, csrf.Token(r)).Render(r.Context(), w)
|
_ = templates.AccountProvidersPage(user, statuses, csrf.Token(r), "/", sidebarTablos).Render(r.Context(), w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,15 @@ func TabloDiscussionTabHandler(deps DiscussionDeps) http.HandlerFunc {
|
||||||
_ = templates.DiscussionTabFragment(tablo, data, templates.DiscussionForm{}, templates.DiscussionErrors{}, csrf.Token(r)).Render(r.Context(), w)
|
_ = templates.DiscussionTabFragment(tablo, data, templates.DiscussionForm{}, templates.DiscussionErrors{}, csrf.Token(r)).Render(r.Context(), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = templates.TabloDetailPage(user, csrf.Token(r), tablo, nil, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, nil, templates.EventsCalendar{}, data, "discussion").Render(r.Context(), w)
|
discussionSidebarTablos, sidebarErr := deps.Queries.ListTablosByUser(r.Context(), user.ID)
|
||||||
|
if sidebarErr != nil {
|
||||||
|
slog.Default().Error("discussion: ListTablosByUser failed", "user_id", user.ID, "err", sidebarErr)
|
||||||
|
discussionSidebarTablos = []sqlc.Tablo{}
|
||||||
|
}
|
||||||
|
if discussionSidebarTablos == nil {
|
||||||
|
discussionSidebarTablos = []sqlc.Tablo{}
|
||||||
|
}
|
||||||
|
_ = templates.TabloDetailPage(user, csrf.Token(r), "", discussionSidebarTablos, tablo, nil, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, nil, templates.EventsCalendar{}, data, "discussion").Render(r.Context(), w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,15 @@ func TabloEventsTabHandler(deps EventsDeps) http.HandlerFunc {
|
||||||
_ = templates.EventsTabFragment(tablo, calendar, csrf.Token(r)).Render(r.Context(), w)
|
_ = templates.EventsTabFragment(tablo, calendar, csrf.Token(r)).Render(r.Context(), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = templates.TabloDetailPage(user, csrf.Token(r), tablo, nil, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, nil, calendar, templates.DiscussionTabData{}, "events").Render(r.Context(), w)
|
eventsSidebarTablos, sidebarErr := deps.Queries.ListTablosByUser(r.Context(), user.ID)
|
||||||
|
if sidebarErr != nil {
|
||||||
|
slog.Default().Error("events: ListTablosByUser failed", "user_id", user.ID, "err", sidebarErr)
|
||||||
|
eventsSidebarTablos = []sqlc.Tablo{}
|
||||||
|
}
|
||||||
|
if eventsSidebarTablos == nil {
|
||||||
|
eventsSidebarTablos = []sqlc.Tablo{}
|
||||||
|
}
|
||||||
|
_ = templates.TabloDetailPage(user, csrf.Token(r), "", eventsSidebarTablos, tablo, nil, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, nil, calendar, templates.DiscussionTabData{}, "events").Render(r.Context(), w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,15 @@ func TabloFilesTabHandler(deps FilesDeps) http.HandlerFunc {
|
||||||
_ = templates.FilesTabFragment(tablo, fileList, csrf.Token(r)).Render(r.Context(), w)
|
_ = templates.FilesTabFragment(tablo, fileList, csrf.Token(r)).Render(r.Context(), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = templates.TabloDetailPage(user, csrf.Token(r), tablo, nil, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, fileList, templates.EventsCalendar{}, templates.DiscussionTabData{}, "files").Render(r.Context(), w)
|
filesSidebarTablos, sidebarErr := deps.Queries.ListTablosByUser(r.Context(), user.ID)
|
||||||
|
if sidebarErr != nil {
|
||||||
|
slog.Default().Error("files tab: ListTablosByUser failed", "user_id", user.ID, "err", sidebarErr)
|
||||||
|
filesSidebarTablos = []sqlc.Tablo{}
|
||||||
|
}
|
||||||
|
if filesSidebarTablos == nil {
|
||||||
|
filesSidebarTablos = []sqlc.Tablo{}
|
||||||
|
}
|
||||||
|
_ = templates.TabloDetailPage(user, csrf.Token(r), "", filesSidebarTablos, tablo, nil, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, fileList, templates.EventsCalendar{}, templates.DiscussionTabData{}, "files").Render(r.Context(), w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,7 +127,15 @@ func TabloTasksTabHandler(deps FilesDeps) http.HandlerFunc {
|
||||||
_ = templates.TasksTabFragment(tablo, tasks, etapes, counts, filter, csrf.Token(r)).Render(r.Context(), w)
|
_ = templates.TasksTabFragment(tablo, tasks, etapes, counts, filter, csrf.Token(r)).Render(r.Context(), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = templates.TabloDetailPage(user, csrf.Token(r), tablo, tasks, etapes, counts, filter, nil, templates.EventsCalendar{}, templates.DiscussionTabData{}, "tasks").Render(r.Context(), w)
|
tasksSidebarTablos, sidebarErr := deps.Queries.ListTablosByUser(r.Context(), user.ID)
|
||||||
|
if sidebarErr != nil {
|
||||||
|
slog.Default().Error("tasks tab: ListTablosByUser failed", "user_id", user.ID, "err", sidebarErr)
|
||||||
|
tasksSidebarTablos = []sqlc.Tablo{}
|
||||||
|
}
|
||||||
|
if tasksSidebarTablos == nil {
|
||||||
|
tasksSidebarTablos = []sqlc.Tablo{}
|
||||||
|
}
|
||||||
|
_ = templates.TabloDetailPage(user, csrf.Token(r), "", tasksSidebarTablos, tablo, tasks, etapes, counts, filter, nil, templates.EventsCalendar{}, templates.DiscussionTabData{}, "tasks").Render(r.Context(), w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,18 @@ func PlanningPageHandler(deps PlanningDeps) http.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
agenda := templates.NewPlanningAgenda(start, end, parsePlanningStart("", now()), rows)
|
agenda := templates.NewPlanningAgenda(start, end, parsePlanningStart("", now()), rows)
|
||||||
|
|
||||||
|
sidebarTablos, err := deps.Queries.ListTablosByUser(r.Context(), user.ID)
|
||||||
|
if err != nil {
|
||||||
|
slog.Default().Error("planning: ListTablosByUser failed", "user_id", user.ID, "err", err)
|
||||||
|
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if sidebarTablos == nil {
|
||||||
|
sidebarTablos = []sqlc.Tablo{}
|
||||||
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
_ = templates.PlanningPage(user, csrf.Token(r), agenda).Render(r.Context(), w)
|
_ = templates.PlanningPage(user, csrf.Token(r), "/planning", sidebarTablos, agenda).Render(r.Context(), w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,14 @@ func TablosListHandler(deps TablosDeps) http.HandlerFunc {
|
||||||
tabloRows = []sqlc.ListTablosByUserWithDiscussionUnreadRow{}
|
tabloRows = []sqlc.ListTablosByUserWithDiscussionUnreadRow{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cardViews := templates.TabloCardsFromUnreadRows(tabloRows)
|
||||||
|
sidebarTablos := make([]sqlc.Tablo, 0, len(cardViews))
|
||||||
|
for _, cv := range cardViews {
|
||||||
|
sidebarTablos = append(sidebarTablos, cv.Tablo)
|
||||||
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
_ = templates.TablosDashboard(user, csrf.Token(r), templates.TabloCardsFromUnreadRows(tabloRows)).Render(r.Context(), w)
|
_ = templates.TablosDashboard(user, csrf.Token(r), "/", sidebarTablos, cardViews).Render(r.Context(), w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -201,8 +207,18 @@ func TabloDetailHandler(deps TablosDeps) http.HandlerFunc {
|
||||||
if tasks == nil {
|
if tasks == nil {
|
||||||
tasks = []sqlc.Task{}
|
tasks = []sqlc.Task{}
|
||||||
}
|
}
|
||||||
|
// Fetch sidebar tablos for AppLayout. On error, log and use empty slice —
|
||||||
|
// the page still renders, just without a populated sidebar projects list.
|
||||||
|
sidebarTablos, err := deps.Queries.ListTablosByUser(r.Context(), user.ID)
|
||||||
|
if err != nil {
|
||||||
|
slog.Default().Error("tablos detail: ListTablosByUser failed", "user_id", user.ID, "err", err)
|
||||||
|
sidebarTablos = []sqlc.Tablo{}
|
||||||
|
}
|
||||||
|
if sidebarTablos == nil {
|
||||||
|
sidebarTablos = []sqlc.Tablo{}
|
||||||
|
}
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
_ = templates.TabloDetailPage(user, csrf.Token(r), tablo, tasks, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, nil, templates.EventsCalendar{}, templates.DiscussionTabData{}, "overview").Render(r.Context(), w)
|
_ = templates.TabloDetailPage(user, csrf.Token(r), "", sidebarTablos, tablo, tasks, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, nil, templates.EventsCalendar{}, templates.DiscussionTabData{}, "overview").Render(r.Context(), w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -308,7 +324,14 @@ func TabloUpdateHandler(deps TablosDeps) http.HandlerFunc {
|
||||||
if tasks == nil {
|
if tasks == nil {
|
||||||
tasks = []sqlc.Task{}
|
tasks = []sqlc.Task{}
|
||||||
}
|
}
|
||||||
_ = templates.TabloDetailPage(user, csrf.Token(r), tablo, tasks, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, nil, templates.EventsCalendar{}, templates.DiscussionTabData{}, "overview").Render(ctx, w)
|
updateSidebarTablos, sidebarErr := deps.Queries.ListTablosByUser(ctx, user.ID)
|
||||||
|
if sidebarErr != nil {
|
||||||
|
updateSidebarTablos = []sqlc.Tablo{}
|
||||||
|
}
|
||||||
|
if updateSidebarTablos == nil {
|
||||||
|
updateSidebarTablos = []sqlc.Tablo{}
|
||||||
|
}
|
||||||
|
_ = templates.TabloDetailPage(user, csrf.Token(r), "", updateSidebarTablos, tablo, tasks, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, nil, templates.EventsCalendar{}, templates.DiscussionTabData{}, "overview").Render(ctx, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -430,5 +453,10 @@ func renderTabloCreateError(w http.ResponseWriter, r *http.Request, form templat
|
||||||
// Render full page — form fragment is not embedded in the full page by default;
|
// Render full page — form fragment is not embedded in the full page by default;
|
||||||
// for the non-HTMX error case we redirect so the user sees their list intact
|
// for the non-HTMX error case we redirect so the user sees their list intact
|
||||||
// and can try again (simpler than threading form state through the full page).
|
// and can try again (simpler than threading form state through the full page).
|
||||||
_ = templates.TablosDashboard(user, csrf.Token(r), templates.TabloCardsFromUnreadRows(tabloRows)).Render(r.Context(), w)
|
errorCardViews := templates.TabloCardsFromUnreadRows(tabloRows)
|
||||||
|
errorSidebarTablos := make([]sqlc.Tablo, 0, len(errorCardViews))
|
||||||
|
for _, cv := range errorCardViews {
|
||||||
|
errorSidebarTablos = append(errorSidebarTablos, cv.Tablo)
|
||||||
|
}
|
||||||
|
_ = templates.TablosDashboard(user, csrf.Token(r), "/", errorSidebarTablos, errorCardViews).Render(r.Context(), w)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
package templates
|
package templates
|
||||||
|
|
||||||
import "backend/internal/auth"
|
import (
|
||||||
|
"backend/internal/auth"
|
||||||
|
"backend/internal/db/sqlc"
|
||||||
|
)
|
||||||
|
|
||||||
templ AccountProvidersPage(user *auth.User, providers []LinkedProviderStatus, csrfToken string) {
|
templ AccountProvidersPage(user *auth.User, providers []LinkedProviderStatus, csrfToken string, activePath string, tablos []sqlc.Tablo) {
|
||||||
@Layout("Linked providers", user, csrfToken) {
|
@AppLayout("Linked providers", user, csrfToken, activePath, tablos) {
|
||||||
<section class="mx-auto max-w-xl">
|
<section class="mx-auto max-w-xl">
|
||||||
<h1 class="mb-6 text-xl font-semibold">Linked providers</h1>
|
<h1 class="mb-6 text-xl font-semibold">Linked providers</h1>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@ package templates
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"backend/internal/auth"
|
"backend/internal/auth"
|
||||||
|
"backend/internal/db/sqlc"
|
||||||
)
|
)
|
||||||
|
|
||||||
templ PlanningPage(user *auth.User, csrfToken string, agenda PlanningAgenda) {
|
templ PlanningPage(user *auth.User, csrfToken string, activePath string, tablos []sqlc.Tablo, agenda PlanningAgenda) {
|
||||||
@Layout("Planning - Xtablo", user, csrfToken) {
|
@AppLayout("Planning - Xtablo", user, csrfToken, activePath, tablos) {
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div class="flex flex-wrap items-start justify-between gap-4">
|
<div class="flex flex-wrap items-start justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue