package front import ( "context" "fmt" "io" "net/http" "net/url" "time" hm "gitlab.codemonkeysoftware.net/b/hatmill" a "gitlab.codemonkeysoftware.net/b/hatmill/attribute" e "gitlab.codemonkeysoftware.net/b/hatmill/element" "gitlab.codemonkeysoftware.net/b/henwen/back" ) const ( pathRoot = "/" pathCreate = "/create" pathDoCreate = "/create/do" pathAdmin = "/admin" pathVote = "/vote" pathDoVote = "/vote/do" ) type handler struct { mux *http.ServeMux store *back.Store title string baseURL string } type HandlerParams struct { Title string Store *back.Store BaseURL string } func NewHandler(params HandlerParams) http.Handler { h := &handler{ store: params.Store, title: params.Title, baseURL: params.BaseURL, mux: http.NewServeMux(), } h.mux.HandleFunc(pathRoot, h.handleRoot) h.mux.HandleFunc(pathCreate, h.handleCreate) h.mux.HandleFunc(pathDoCreate, h.handleDoCreate) h.mux.HandleFunc(pathAdmin, h.handleAdmin) h.mux.HandleFunc(pathVote, h.handleVote) h.mux.HandleFunc(pathDoVote, h.handleDoVote) return h } func (h *handler) handleRoot(w http.ResponseWriter, r *http.Request) { body := hm.Terms{ e.H2()(hm.Text("Welcome!")), e.A(a.Href(pathCreate))( hm.Text("Create event"), ), } _ = h.writePage(w, body) } var timeLabels = []string{ "12 AM", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12 PM", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", } func disableIf(cond bool, input hm.Term) hm.Term { if !cond { return input } switch e := input.(type) { case hm.ParentElement: e.Attribs = append(e.Attribs, a.Disabled()) return e case hm.VoidElement: e.Attribs = append(e.Attribs, a.Disabled()) return e default: return e } } type voteState struct { name string earliestDate, latestDate time.Time } func voteForm(disabled bool, st voteState) hm.Term { var dates []time.Time for curr := st.earliestDate; curr.Before(st.latestDate.Add(time.Hour)); curr = curr.AddDate(0, 0, 1) { dates = append(dates, curr) } var ths = hm.Terms{e.Th()()} for _, date := range dates { ths = append(ths, e.Th()(hm.Text(date.Format("Jan 2")))) } var rows = hm.Terms{e.Thead()(ths)} for hour := 0; hour < 24; hour++ { var row = hm.Terms{ e.Td()(hm.Text(timeLabels[hour])), } for day := 0; day < len(dates); day++ { row = append(row, e.Td()(disableIf(disabled, e.Input(a.Type("checkbox"))))) } rows = append(rows, e.Tr()(row)) } return e.Form(a.Method(http.MethodPost), a.Action(pathDoVote))( e.Label()(hm.Text("What's your name?")), e.Br(), disableIf(disabled, e.Input(a.Size(40), a.Value(st.name))), e.Fieldset()( e.Legend()(hm.Text("When are you available?")), e.Table()(rows), ), e.Input(a.Type("submit"), a.Value("Submit")), ) } func (h *handler) handleVote(w http.ResponseWriter, r *http.Request) { // TODO use actual data state := voteState{ name: "Suzie Q", earliestDate: time.Date(2006, time.May, 3, 0, 0, 0, 0, time.UTC), latestDate: time.Date(2006, time.May, 8, 0, 0, 0, 0, time.UTC), } body := hm.Terms{ e.H2()(hm.Text("Billy's birthday party")), e.P()(hm.Text("At Billy's house. Bring presents. Eat cake.")), voteForm(false, state), } _ = h.writePage(w, body) } func (h *handler) handleDoVote(w http.ResponseWriter, r *http.Request) { // TODO use actual data state := voteState{ name: "Suzie Q", earliestDate: time.Date(2006, time.May, 3, 0, 0, 0, 0, time.UTC), latestDate: time.Date(2006, time.May, 8, 0, 0, 0, 0, time.UTC), } body := hm.Terms{ e.H2()(hm.Text("Billy's birthday party")), e.P()(hm.Text("At Billy's house. Bring presents. Eat cake.")), e.H3()(hm.Text("Thanks for voting!")), e.P()( hm.Text("You can edit your response anytime at "), e.A(a.Href("#"))(hm.Text("this link")), hm.Text("."), ), voteForm(true, state), } _ = h.writePage(w, body) } func (h *handler) handleCreate(w http.ResponseWriter, r *http.Request) { body := hm.Terms{ e.H2()(hm.Text("Create an event")), e.Form(a.Action(pathDoCreate), a.Method(http.MethodPost))( e.Label(a.For(fieldNameEventName))(hm.Text("Event name")), e.Input(a.Name(fieldNameEventName)), e.Label(a.For(fieldNameDescription))(hm.Text("Description")), e.Textarea(a.Name(fieldNameDescription), a.Placeholder("What's going on?"))(), e.Label(a.For(fieldNameEarliest))(hm.Text("Earliest date")), e.Input(a.Name(fieldNameEarliest), a.Type("date")), e.Label(a.For(fieldNameLatest))(hm.Text("Latest date")), e.Input(a.Name(fieldNameLatest), a.Type("date")), e.Input(a.Type("submit")), ), } _ = h.writePage(w, body) } func (h *handler) handleDoCreate(w http.ResponseWriter, r *http.Request) { earliest, err := time.Parse(formDateLayout, r.FormValue(fieldNameEarliest)) if err != nil { fmt.Fprint(w, "bad earliest date") return } latest, err := time.Parse(formDateLayout, r.FormValue(fieldNameLatest)) if err != nil { fmt.Fprint(w, "bad latest date") return } eventName := r.FormValue(fieldNameEventName) if eventName == "" { fmt.Fprint(w, "event name is required") return } description := r.FormValue(fieldNameDescription) event, err := h.store.CreateEvent(context.Background(), back.CreateEventCommand{ Name: eventName, Description: description, EarliestDate: earliest, LatestDate: latest, }) if err != nil { fmt.Fprint(w, err) return } var adminQuery = make(url.Values) adminQuery.Add(keyEventID, event.AlphaID) adminQuery.Add(keyAdminCode, event.AdminCode) adminURL := h.baseURL + pathAdmin + "?" + adminQuery.Encode() const dateDisplayFmt = "Monday, January 2, 2006" body := hm.Terms{ e.H2()(hm.Text("Created event!")), e.P()( hm.Text("You can find it again at "), e.A(a.Href(adminURL))(hm.Text(adminURL)), hm.Text("."), ), e.H3()(hm.Text("Name")), hm.Text(eventName), e.H3()(hm.Text("Description")), hm.Text(description), e.H3()(hm.Text("Earliest date")), hm.Text(earliest.Format(dateDisplayFmt)), e.H3()(hm.Text("Latest date")), hm.Text(latest.Format(dateDisplayFmt)), } _ = h.writePage(w, body) } func (h *handler) handleAdmin(w http.ResponseWriter, r *http.Request) { query := r.URL.Query() event, err := h.store.GetEvent(context.Background(), back.GetEventQuery{ AlphaID: query.Get(keyEventID), AdminCode: query.Get(keyAdminCode), }) if err != nil { fmt.Fprint(w, err) return } body := hm.Terms{ e.H2()(hm.Text("Edit your event")), e.Form()( e.Label(a.For(fieldNameEventName))(hm.Text("Event name")), e.Input( a.Name(fieldNameEventName), a.Value(event.Name), ), e.Br(), e.Label(a.For(fieldNameDescription))(hm.Text("Description")), e.Textarea(a.Name(fieldNameDescription))(hm.Text(event.Description)), e.Br(), e.Label(a.For(fieldNameEarliest))(hm.Text("Earliest date")), e.Input( a.Name(fieldNameEarliest), a.Type("date"), a.Value(event.EarliestDate.Format(formDateLayout)), ), e.Br(), e.Label(a.For(fieldNameLatest))(hm.Text("Latest date")), e.Input( a.Name(fieldNameLatest), a.Type("date"), a.Value(event.LatestDate.Format(formDateLayout)), ), e.Br(), e.Input(a.Type("submit")), ), } _ = h.writePage(w, body) } func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.mux.ServeHTTP(w, r) } func (h *handler) writePage(w io.Writer, contents hm.Term) error { page := e.Html()( e.Head()( e.Title()(hm.Text(h.title)), ), e.Body()( e.H1()(hm.Text(h.title)), e.Div()(contents), ), ) _, err := hm.WriteDocument(w, page) return err } const ( fieldNameEarliest = "earliestDate" fieldNameLatest = "latestDate" fieldNameEventName = "eventName" fieldNameDescription = "eventDescription" ) const keyEventID = "event_id" const keyAdminCode = "admin_code" const formDateLayout = "2006-01-02"