diff --git a/starter/routes/routing.go b/starter/routes/routing.go index 18ba1ad..c06665e 100644 --- a/starter/routes/routing.go +++ b/starter/routes/routing.go @@ -12,6 +12,7 @@ import ( "ersteller-lib/starter/index" "ersteller-lib/starter/login" "ersteller-lib/starter/page" + "ersteller-lib/starter/todos" "net/http" "github.com/gorilla/sessions" @@ -62,8 +63,17 @@ func CreateApi(environment env.Environment, db *ent.Client) http.Handler { De: contact.ContactPathDe, }) + // Todos navigation item + todosActivePath := NewActivePath(map[Language]string{ + En: "Todos", + De: "Todos", + }, LanguagePaths{ + En: todos.TodosPath, + De: todos.TodosPathDe, + }) + // Main navigation items - activePaths := []ActivePath{indexActivePath, aboutActivePath, contactActivePath} + activePaths := []ActivePath{indexActivePath, aboutActivePath, contactActivePath, todosActivePath} // Footer navigation items (placeholder - can be customized) footerPaths := []ActivePath{} @@ -75,6 +85,8 @@ func CreateApi(environment env.Environment, db *ent.Client) http.Handler { // Create About and Contact pages using the new packages _ = about.NewPage(createPageFunc, server, &aboutActivePath) _ = contact.NewPage(createPageFunc, server, &contactActivePath) + // Create Todos page + _ = todos.NewPage(createPageFunc, server, &todosActivePath, db) // Create Login page loginPaths := LanguagePaths{ diff --git a/starter/todos/todos.go b/starter/todos/todos.go new file mode 100644 index 0000000..6c068ce --- /dev/null +++ b/starter/todos/todos.go @@ -0,0 +1,168 @@ +package todos + +import ( + "context" + "fmt" + "strconv" + "strings" + + . "ersteller-lib" + "ersteller-lib/starter/ent" + "ersteller-lib/starter/ent/todo" + + . "maragu.dev/gomponents" + . "maragu.dev/gomponents/html" +) + +const TodosPath = "/todos" +const TodosPathDe = "/todos" + +// i18n texts +var texts *Texts + +type Texts struct { + PageTitle I18nText + PageDescription I18nText + HeroTitle I18nText + AddPlaceholder I18nText + AddButton I18nText + DeleteButton I18nText + SaveButton I18nText +} + +type Page struct { + createPage CreateHtmxPageFunc + db *ent.Client + + ViewRoute HtmxRoute + AddRoute HtmxRoute + UpdateRoute HtmxRoute + DeleteRoute HtmxRoute +} + +func NewPage(createPage CreateHtmxPageFunc, server HtmxServer, path *ActivePath, db *ent.Client) *Page { + if texts == nil { + createTexts() + } + p := &Page{createPage: createPage, db: db} + p.ViewRoute = NewHtmxGetRoute(p.View, LanguagePaths{En: TodosPath, De: TodosPathDe}).SetActivePath(path) + p.ViewRoute.Add(server) + + p.AddRoute = NewHtmxPostRoute(p.Add, LanguagePaths{En: TodosPath + "/add", De: TodosPathDe + "/add"}) + p.AddRoute.Add(server) + p.UpdateRoute = NewHtmxPostRoute(p.Update, LanguagePaths{En: TodosPath + "/update", De: TodosPathDe + "/update"}) + p.UpdateRoute.Add(server) + p.DeleteRoute = NewHtmxPostRoute(p.Delete, LanguagePaths{En: TodosPath + "/delete", De: TodosPathDe + "/delete"}) + p.DeleteRoute.Add(server) + return p +} + +func createTexts() { + texts = &Texts{ + PageTitle: NewI18nText(map[Language]string{En: "Todos", De: "Todos"}), + PageDescription: NewI18nText(map[Language]string{En: "Manage your todos", De: "Verwalte deine Todos"}), + HeroTitle: NewI18nText(map[Language]string{En: "Todos", De: "Todos"}), + AddPlaceholder: NewI18nText(map[Language]string{En: "New todo title", De: "Neuer Todo-Titel"}), + AddButton: NewI18nText(map[Language]string{En: "Add", De: "Hinzufügen"}), + DeleteButton: NewI18nText(map[Language]string{En: "Delete", De: "Löschen"}), + SaveButton: NewI18nText(map[Language]string{En: "Save", De: "Speichern"}), + } +} + +func (p *Page) getMetaData() PageWebsiteMetaData { + return PageWebsiteMetaData{ + Title: texts.PageTitle, + Lang: En, + Description: texts.PageDescription, + } +} + +func (p *Page) View(c HtmxContext) { + language := c.GetLanguage() + todos, _ := p.db.Todo.Query().Order(ent.Asc(todo.FieldCreatedAt)).All(context.Background()) + content := Group{ + Div(Class("hero-section"), + H1(Class("hero-title"), Text(texts.HeroTitle.FromLang(language))), + ), + Div(Class("content-section"), + p.addForm(c), + Div(ID("todos-list"), p.todosList(c, todos)), + ), + } + p.createPage(c, p.getMetaData(), content) +} + +func (p *Page) addForm(c HtmxContext) Node { + lang := c.GetLanguage() + return Form(Action(p.AddRoute.ToUrlFromContext(c, lang)), Method("post"), + Div(Class("form-row"), + Input(Type("text"), Name("title"), Placeholder(texts.AddPlaceholder.FromLang(lang))), + Button(Type("submit"), Text(texts.AddButton.FromLang(lang))), + ), + ) +} + +func (p *Page) todosList(c HtmxContext, list []*ent.Todo) Node { + lang := c.GetLanguage() + items := make([]Node, 0, len(list)) + for _, t := range list { + items = append(items, p.todoItem(c, lang, t)) + } + return Ul(Class("todo-list"), Group(items)) +} + +func (p *Page) todoItem(c HtmxContext, lang Language, t *ent.Todo) Node { + // Simple inline edit form per item + return Li(ID(p.itemDomID(t.ID)), + Form(Action(p.UpdateRoute.ToUrlFromContext(c, lang)), Method("post"), + Input(Type("hidden"), Name("id"), Value(fmt.Sprintf("%d", t.ID))), + Input(Type("checkbox"), Name("completed"), If(t.Completed, Attr("checked", "checked"))), + Input(Type("text"), Name("title"), Value(t.Title)), + Button(Type("submit"), Text(texts.SaveButton.FromLang(lang))), + ), + Form(Action(p.DeleteRoute.ToUrlFromContext(c, lang)), Method("post"), + Input(Type("hidden"), Name("id"), Value(fmt.Sprintf("%d", t.ID))), + Button(Type("submit"), Class("danger"), Text(texts.DeleteButton.FromLang(lang))), + ), + ) +} + +func (p *Page) itemDomID(id int) string { return fmt.Sprintf("todo-%d", id) } + +// Handlers +func (p *Page) Add(c HtmxContext) { + title := strings.TrimSpace(c.GetFormValue("title")) + if title == "" { + p.renderList(c) + return + } + _, _ = p.db.Todo.Create().SetTitle(title).Save(context.Background()) + p.renderList(c) +} + +func (p *Page) Update(c HtmxContext) { + idStr := c.GetFormValue("id") + id, _ := strconv.Atoi(idStr) + if id == 0 { + p.renderList(c) + return + } + title := strings.TrimSpace(c.GetFormValue("title")) + completed := c.GetFormValue("completed") == "on" + _ = p.db.Todo.UpdateOneID(id).SetTitle(title).SetCompleted(completed).Exec(context.Background()) + p.renderList(c) +} + +func (p *Page) Delete(c HtmxContext) { + idStr := c.GetFormValue("id") + id, _ := strconv.Atoi(idStr) + if id != 0 { + _ = p.db.Todo.DeleteOneID(id).Exec(context.Background()) + } + p.renderList(c) +} + +func (p *Page) renderList(c HtmxContext) { + todos, _ := p.db.Todo.Query().Order(ent.Asc(todo.FieldCreatedAt)).All(context.Background()) + c.Render(Div(ID("todos-list"), p.todosList(c, todos))) +}