diff --git a/WIP.md b/WIP.md index aabe8b8..2b0526d 100644 --- a/WIP.md +++ b/WIP.md @@ -4,24 +4,22 @@ for simplicity, I keep todo list and progress here for now **Before release**: - [ ] Review "TODO!"s -- [ ] Homepage +- [x] Homepage - [ ] Licence -- [ ] RELEASE +- [x] RELEASE --- -AFTER RELEASE CHECKLIST: -- [x] Setup & test email -- [x] Setup & test auth & registration with google -- [x] Setup & test auth & registration with github -- [x] Setup & test tracing -- [ ] Enable linting and testing on CI/CD -- [ ] Disable push to main without MR +Tweaks & fixes: +- [ ] Add description to upload book cover input +- [ ] When book cover upload ends with error, another cannot be added -NEXT: + +TODO: - [ ] Resend verification link (Sometimes email is lost in somewhere between the woods (Not mailman to blame)) - [ ] Create new topic when creating/editing entity - [ ] Pages - [ ] Add meta to page (Who created, last edit by who and list of activities on page (api call)) + - [ ] Content chips - [ ] Books - [ ] Add subtitle - [ ] Sort @@ -53,3 +51,4 @@ NEXT: - [ ] Let user continue work on reject entity and proposed changes - [ ] Review "delete account" test. Right now, it passes even if models belonging to the user still exist. - [ ] Order of props when reviewing changes and public diffs should be constant and predefined +- [ ] Sitemap diff --git a/app/repo/page_repo.go b/app/repo/page_repo.go index 81e49ff..fa936c8 100644 --- a/app/repo/page_repo.go +++ b/app/repo/page_repo.go @@ -30,6 +30,22 @@ func (r *Repo) GetPageByPublicID(ctx context.Context, publicID string) (*ds.Page return page, err } +// GetPagesByPublicID retrieves pages by given public ID. +func (r *Repo) GetPagesByPublicID(ctx context.Context, publicIDs ...string) (pages []ds.Page, err error) { + _, span := r.tracer.Start(ctx, "GetPageByPublicID") + defer span.End() + + if len(publicIDs) == 0 { + return + } + + pages = make([]ds.Page, 0, len(publicIDs)) + const query = `SELECT * FROM entities e JOIN pages p USING (id) WHERE e.public_id = ANY($1) AND e.type = $2 AND e.deleted_at IS NULL` + + err = pgxscan.Select(ctx, r.getDB(ctx), &pages, query, publicIDs, ds.EntityTypePage) + return +} + // GetPageByID retrieves a page by its ID. func (r *Repo) GetPageByID(ctx context.Context, id ds.ID) (*ds.Page, error) { _, span := r.tracer.Start(ctx, "GetPageByID") diff --git a/app/service/page_service.go b/app/service/page_service.go index 5705952..31440c1 100644 --- a/app/service/page_service.go +++ b/app/service/page_service.go @@ -11,7 +11,7 @@ import ( "github.com/gopl-dev/server/email" ) -// GetPageByPublicID retrieves a page by its public identifier. +// GetPageByPublicID retrieves a page by its public ID. func (s *Service) GetPageByPublicID(ctx context.Context, id string) (*ds.Page, error) { ctx, span := s.tracer.Start(ctx, "GetPageByPublicID") defer span.End() @@ -19,6 +19,14 @@ func (s *Service) GetPageByPublicID(ctx context.Context, id string) (*ds.Page, e return s.db.GetPageByPublicID(ctx, id) } +// GetPagesByPublicID retrieves pages by given public IDs. +func (s *Service) GetPagesByPublicID(ctx context.Context, id ...string) ([]ds.Page, error) { + ctx, span := s.tracer.Start(ctx, "GetPagesByPublicID") + defer span.End() + + return s.db.GetPagesByPublicID(ctx, id...) +} + // GetPageByID retrieves a page by its ID from the database. func (s *Service) GetPageByID(ctx context.Context, id ds.ID) (*ds.Page, error) { ctx, span := s.tracer.Start(ctx, "GetBookByID") diff --git a/frontend/assets/output.css b/frontend/assets/output.css index e311cd3..38cd685 100644 --- a/frontend/assets/output.css +++ b/frontend/assets/output.css @@ -3590,9 +3590,6 @@ .left-0 { left: calc(var(--spacing) * 0); } - .left-1 { - left: calc(var(--spacing) * 1); - } .left-1\/2 { left: calc(1/2 * 100%); } @@ -5151,6 +5148,9 @@ .mb-4 { margin-bottom: calc(var(--spacing) * 4); } + .mb-5 { + margin-bottom: calc(var(--spacing) * 5); + } .mb-10 { margin-bottom: calc(var(--spacing) * 10); } @@ -5920,15 +5920,9 @@ .w-6 { width: calc(var(--spacing) * 6); } - .w-11 { - width: calc(var(--spacing) * 11); - } .w-11\/12 { width: calc(11/12 * 100%); } - .w-32 { - width: calc(var(--spacing) * 32); - } .w-40 { width: calc(var(--spacing) * 40); } @@ -5983,10 +5977,6 @@ .border-collapse { border-collapse: collapse; } - .-translate-x-1 { - --tw-translate-x: calc(var(--spacing) * -1); - translate: var(--tw-translate-x) var(--tw-translate-y); - } .-translate-x-1\/2 { --tw-translate-x: calc(calc(1/2 * 100%) * -1); translate: var(--tw-translate-x) var(--tw-translate-y); @@ -6195,9 +6185,6 @@ .gap-8 { gap: calc(var(--spacing) * 8); } - .self-start { - align-self: flex-start; - } .overflow-x-auto { overflow-x: auto; } @@ -6628,9 +6615,6 @@ .border-info { border-color: var(--color-info); } - .border-neutral-content { - border-color: var(--color-neutral-content); - } .border-neutral-content\/30 { border-color: var(--color-neutral-content); @supports (color: color-mix(in lab, red, red)) { @@ -7043,6 +7027,9 @@ .py-2 { padding-block: calc(var(--spacing) * 2); } + .py-4 { + padding-block: calc(var(--spacing) * 4); + } .py-8 { padding-block: calc(var(--spacing) * 8); } @@ -7055,9 +7042,6 @@ font-size: 1.375rem; } } - .pt-1 { - padding-top: calc(var(--spacing) * 1); - } .pt-6 { padding-top: calc(var(--spacing) * 6); } @@ -8381,6 +8365,11 @@ width: auto; } } + .lg\:grid-cols-2 { + @media (width >= 64rem) { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + } .lg\:grid-cols-4 { @media (width >= 64rem) { grid-template-columns: repeat(4, minmax(0, 1fr)); diff --git a/frontend/component/form.templ b/frontend/component/form.templ index 31aa157..6bd1feb 100644 --- a/frontend/component/form.templ +++ b/frontend/component/form.templ @@ -7,7 +7,7 @@ const ( const ( LabelClass = "text-gray-900" InputClassDefault = "input input-xl w-full" - TextareaClassDefault = "textarea textarea-xl w-full" + TextareaClassDefault = "textarea w-full" InputClassError = "input-error" ) diff --git a/frontend/component/form_templ.go b/frontend/component/form_templ.go index 9c6d301..0ff1b20 100644 --- a/frontend/component/form_templ.go +++ b/frontend/component/form_templ.go @@ -15,7 +15,7 @@ const ( const ( LabelClass = "text-gray-900" InputClassDefault = "input input-xl w-full" - TextareaClassDefault = "textarea textarea-xl w-full" + TextareaClassDefault = "textarea w-full" InputClassError = "input-error" ) diff --git a/frontend/page/edit_page.templ b/frontend/page/edit_page.templ index 55aeb98..c643a23 100644 --- a/frontend/page/edit_page.templ +++ b/frontend/page/edit_page.templ @@ -189,7 +189,7 @@ templ EditPageForm(pageID string) { Model: "form.content", ErrorModel: "errors.content", Type: "textarea", - Rows: 15, + Rows: 25, Description: "You can use Markdown.", })
diff --git a/frontend/page/edit_page_templ.go b/frontend/page/edit_page_templ.go index 145d478..bb3a571 100644 --- a/frontend/page/edit_page_templ.go +++ b/frontend/page/edit_page_templ.go @@ -87,7 +87,7 @@ func EditPageForm(pageID string) templ.Component { Model: "form.content", ErrorModel: "errors.content", Type: "textarea", - Rows: 15, + Rows: 25, Description: "You can use Markdown.", }).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { diff --git a/frontend/page/home.templ b/frontend/page/home.templ index 1e0bbec..5ef4bb2 100644 --- a/frontend/page/home.templ +++ b/frontend/page/home.templ @@ -1,13 +1,74 @@ package page -templ Home() { - -
hello world
-
- - - +import "github.com/gopl-dev/server/frontend/component/icon" + +type HomeData struct { + Title string + Data map[string]string +} + +templ Home(d HomeData) { +
+
+

+ { d.Title } +

+ + @icon.Pencil("w-4") + +
+
+ @templ.Raw(d.Data["intro"]) +
+
+
+
+

From zero to hero

+ + @icon.Pencil("w-4") + +
+
+ @templ.Raw(d.Data["getting-started"]) +
+
+
+ +
+ @templ.Raw(d.Data["tools"]) +
+
+
+ +
+ @templ.Raw(d.Data["community"]) +
} diff --git a/frontend/page/home_templ.go b/frontend/page/home_templ.go index cd89111..74b0da0 100644 --- a/frontend/page/home_templ.go +++ b/frontend/page/home_templ.go @@ -8,7 +8,14 @@ package page import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" -func Home() templ.Component { +import "github.com/gopl-dev/server/frontend/component/icon" + +type HomeData struct { + Title string + Data map[string]string +} + +func Home(d HomeData) 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 { @@ -29,7 +36,84 @@ func Home() templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
hello world
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(d.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `frontend/page/home.templ`, Line: 14, Col: 21} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = icon.Pencil("w-4").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(d.Data["intro"]).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 + } + templ_7745c5c3_Err = templ.Raw(d.Data["getting-started"]).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(d.Data["tools"]).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(d.Data["community"]).Render(ctx, templ_7745c5c3_Buffer) + 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 } diff --git a/frontend/page/view_page.templ b/frontend/page/view_page.templ index 14c69f3..073f32f 100644 --- a/frontend/page/view_page.templ +++ b/frontend/page/view_page.templ @@ -3,13 +3,15 @@ package page import "github.com/gopl-dev/server/frontend/component/icon" templ ViewPage(id, title, text string) { -
+ } diff --git a/frontend/page/view_page_templ.go b/frontend/page/view_page_templ.go index 01c9ee8..18b4803 100644 --- a/frontend/page/view_page_templ.go +++ b/frontend/page/view_page_templ.go @@ -31,14 +31,14 @@ func ViewPage(id, title, text string) templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `frontend/page/view_page.templ`, Line: 8, Col: 19} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `frontend/page/view_page.templ`, Line: 8, Col: 42} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -65,7 +65,7 @@ func ViewPage(id, title, text string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "Edit

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "Edit

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -73,7 +73,7 @@ func ViewPage(id, title, text string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - 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 } diff --git a/server/handler/dashboard_handler.go b/server/handler/dashboard_handler.go index 7bbe733..89c5ee4 100644 --- a/server/handler/dashboard_handler.go +++ b/server/handler/dashboard_handler.go @@ -23,7 +23,7 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) { renderTempl(ctx, w, layout.Dashboard(layout.Data{ Title: "Dashboard", - Body: page.Home(), + Body: page.Home(page.HomeData{}), User: frontend.NewUser(ds.UserFromContext(r.Context())), })) } diff --git a/server/handler/home_handler.go b/server/handler/home_handler.go index e2c4ad8..ff4bb5b 100644 --- a/server/handler/home_handler.go +++ b/server/handler/home_handler.go @@ -7,6 +7,7 @@ import ( "github.com/gopl-dev/server/frontend" "github.com/gopl-dev/server/frontend/layout" "github.com/gopl-dev/server/frontend/page" + "github.com/gopl-dev/server/test/factory/random" ) // Home sweet home. @@ -14,9 +15,45 @@ func (h *Handler) Home(w http.ResponseWriter, r *http.Request) { ctx, span := h.tracer.Start(r.Context(), "Home") defer span.End() + pages, err := h.service.GetPagesByPublicID(ctx, + "intro", "getting-started", "tools", "community", + ) + if err != nil { + Abort(w, r, err) + return + } + + data := page.HomeData{ + Title: "Welcome", + Data: map[string]string{}, + } + + for _, p := range pages { + data.Data[p.PublicID] = p.Content + } + + title := random.Element([]string{ + // Now that you have found a list of silly titles, please read through it. + "Green Olives Party Late", + "Goats Organizing Pajama Launches", + "Grandma’s Overpowered Pancake Love", + "Galactic Octopus Pizza League", + "Giraffes On Purple Ladders", + "Goblin Operations & Potion Logistics", + "Grumpy Owls Playing Lute", + "Gravity Occasionally Pauses Lunch", + "Global Organization of Professional Llamas", + "Gigantic Overengineered Paperclip Lab", + // Thanks. Now that you have committed and if you want to continue, do this three steps: + // 1. remove one string from this list that you dislike + // 2. add two of your silly titles of your liking (that explain the acronym GOPL). + // 3. move this list elsewhere (within this repo, ofc) + }) + + data.Title = title renderTempl(ctx, w, layout.Default(layout.Data{ - Title: "Welcome!", - Body: page.Home(), + Title: "Welcome", + Body: page.Home(data), User: frontend.NewUser(ds.UserFromContext(r.Context())), })) }