diff --git a/.task/checksum/generate b/.task/checksum/generate
new file mode 100644
index 0000000..f654668
--- /dev/null
+++ b/.task/checksum/generate
@@ -0,0 +1 @@
+cf55887b91f81f789d59205c41f8368
diff --git a/Taskfile.yml b/Taskfile.yml
index 6e2f54e..33040ce 100644
--- a/Taskfile.yml
+++ b/Taskfile.yml
@@ -18,12 +18,24 @@ tasks:
install_tools:
cmds:
- go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2
+ - go install github.com/a-h/templ/cmd/templ@v0.2.513
+ generate:
+ cmds:
+ - "$GOBIN/templ generate"
+ sources:
+ - "internal/kurious/ports/http/templ/*.templ"
+ generates:
+ - "internal/kurious/ports/http/templ/*.go"
check:
cmds:
- "$GOBIN/golangci-lint run ./..."
+ deps:
+ - generate
test:
cmds:
- go test ./internal/...
+ deps:
+ - generate
build_web:
cmds:
- go build -o $GOBIN/kuriousweb -v -ldflags '{{.LDFLAGS}}' cmd/kuriweb/*.go
diff --git a/cmd/background/main.go b/cmd/background/main.go
index 009da69..e3ea585 100644
--- a/cmd/background/main.go
+++ b/cmd/background/main.go
@@ -93,13 +93,16 @@ func app(ctx context.Context) error {
defer xcontext.LogInfo(ctx, log, "finished bprocess")
bgProcess.Run()
+
return nil
})
eg.Go(func() error {
xcontext.LogInfo(ctx, log, "running cancelation waiter")
defer xcontext.LogInfo(ctx, log, "finished cancelation waiter")
+
<-egctx.Done()
+
sdctx, sdcancel := context.WithTimeout(context.Background(), time.Second*15)
defer sdcancel()
diff --git a/cmd/kuriweb/http.go b/cmd/kuriweb/http.go
index f3a017f..edb22c7 100644
--- a/cmd/kuriweb/http.go
+++ b/cmd/kuriweb/http.go
@@ -17,6 +17,8 @@ import (
func makePathTemplate(params ...string) string {
var sb strings.Builder
for _, param := range params {
+ sb.Grow(len(param) + 3)
+
sb.WriteRune('/')
sb.WriteRune('{')
sb.WriteString(param)
@@ -26,14 +28,21 @@ func makePathTemplate(params ...string) string {
return sb.String()
}
-func setupHTTP(cfg config.HTTP, srv xhttp.Server, log *slog.Logger) *http.Server {
- router := mux.NewRouter()
+func setupHTTPWithTempl(srv xhttp.Server, router *mux.Router, log *slog.Logger) {
+ coursesRouter := router.PathPrefix("/courses").Subrouter().StrictSlash(true)
+ coursesAPI := srv.CoursesByTempl()
+
+ coursesRouter.HandleFunc("/", coursesAPI.List).Methods(http.MethodGet)
+ coursesListLearningOnlyPath := makePathTemplate(xhttp.LearningTypePathParam)
+ coursesRouter.HandleFunc(coursesListLearningOnlyPath, coursesAPI.List).Methods(http.MethodGet)
+ coursesListFullPath := makePathTemplate(xhttp.LearningTypePathParam, xhttp.ThematicTypePathParam)
+ coursesRouter.HandleFunc(coursesListFullPath, coursesAPI.List).Methods(http.MethodGet)
+}
+
+func setupHTTPWithGoTemplates(srv xhttp.Server, router *mux.Router, log *slog.Logger) {
coursesAPI := srv.Courses()
- router.Use(mux.CORSMethodMiddleware(router))
- router.Use(middlewareLogger(log))
- // router.HandleFunc("/updatedesc", coursesAPI.UdpateDescription).Methods(http.MethodPost)
coursesRouter := router.PathPrefix("/courses").Subrouter().StrictSlash(true)
coursesRouter.HandleFunc("/", coursesAPI.List).Methods(http.MethodGet)
coursesListLearningOnlyPath := makePathTemplate(xhttp.LearningTypePathParam)
@@ -46,6 +55,23 @@ func setupHTTP(cfg config.HTTP, srv xhttp.Server, log *slog.Logger) *http.Server
courseRouter.HandleFunc("/short", coursesAPI.GetShort).Methods(http.MethodGet)
courseRouter.HandleFunc("/editdesc", coursesAPI.RenderEditDescription).Methods(http.MethodGet)
courseRouter.HandleFunc("/description", coursesAPI.UpdateCourseDescription).Methods(http.MethodPut)
+}
+
+func setupHTTP(cfg config.HTTP, srv xhttp.Server, log *slog.Logger) *http.Server {
+ router := mux.NewRouter()
+
+ router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ })
+
+ router.Use(mux.CORSMethodMiddleware(router))
+ router.Use(middlewareLogger(log, cfg.Engine))
+
+ if cfg.Engine == "templ" {
+ setupHTTPWithTempl(srv, router, log)
+ } else {
+ setupHTTPWithGoTemplates(srv, router, log)
+ }
if cfg.MountLive {
fs := http.FileServer(http.Dir("./assets/kurious/static/"))
@@ -85,7 +111,7 @@ func setupHTTP(cfg config.HTTP, srv xhttp.Server, log *slog.Logger) *http.Server
}
}
-func middlewareLogger(log *slog.Logger) mux.MiddlewareFunc {
+func middlewareLogger(log *slog.Logger, engine string) mux.MiddlewareFunc {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
@@ -93,7 +119,11 @@ func middlewareLogger(log *slog.Logger) mux.MiddlewareFunc {
if requestID == "" {
requestID = generator.RandomInt64ID()
}
- ctx = xcontext.WithLogFields(ctx, slog.String("request_id", requestID))
+ ctx = xcontext.WithLogFields(
+ ctx,
+ slog.String("request_id", requestID),
+ slog.String("engine", engine),
+ )
xcontext.LogInfo(
ctx, log, "incoming request",
diff --git a/go.mod b/go.mod
index 9617ea6..8d6aec8 100644
--- a/go.mod
+++ b/go.mod
@@ -15,6 +15,7 @@ require (
)
require (
+ github.com/a-h/templ v0.2.513 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/uuid v1.4.0 // indirect
diff --git a/go.sum b/go.sum
index 2eae5a8..0b17c3c 100644
--- a/go.sum
+++ b/go.sum
@@ -515,6 +515,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/a-h/templ v0.2.513 h1:ZmwGAOx4NYllnHy+FTpusc4+c5msoMpPIYX0Oy3dNqw=
+github.com/a-h/templ v0.2.513/go.mod h1:9gZxTLtRzM3gQxO8jr09Na0v8/jfliS97S9W5SScanM=
github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=
github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
diff --git a/internal/common/config/http.go b/internal/common/config/http.go
index ad36b8e..fff7915 100644
--- a/internal/common/config/http.go
+++ b/internal/common/config/http.go
@@ -3,4 +3,5 @@ package config
type HTTP struct {
ListenAddr string `json:"listen_addr"`
MountLive bool `json:"mount_live"`
+ Engine string `json:"engine"`
}
diff --git a/internal/kurious/ports/http/coursev2.go b/internal/kurious/ports/http/coursev2.go
new file mode 100644
index 0000000..32db513
--- /dev/null
+++ b/internal/kurious/ports/http/coursev2.go
@@ -0,0 +1,139 @@
+package http
+
+import (
+ "log/slog"
+ "net/http"
+
+ "git.loyso.art/frx/kurious/internal/common/xslices"
+ "git.loyso.art/frx/kurious/internal/kurious/app/query"
+ "git.loyso.art/frx/kurious/internal/kurious/domain"
+ xtempl "git.loyso.art/frx/kurious/internal/kurious/ports/http/templ"
+ "git.loyso.art/frx/kurious/internal/kurious/service"
+)
+
+type courseTemplServer struct {
+ app service.Application
+ log *slog.Logger
+}
+
+func makeTemplListCoursesParams(in ...domain.Course) xtempl.ListCoursesParams {
+ coursesBySubcategory := make(map[string][]xtempl.CourseInfo, len(in))
+ subcategoriesByCategories := make(map[string]map[string]struct{}, len(in))
+ categoryByID := make(map[string]xtempl.CategoryBaseInfo, len(in))
+
+ xslices.ForEach(in, func(c domain.Course) {
+ courseInfo := xtempl.CourseInfo{
+ ID: c.ID,
+ Name: c.Name,
+ FullPrice: int(c.FullPrice),
+ ImageLink: c.ImageLink,
+ OriginLink: c.OriginLink,
+ }
+
+ coursesBySubcategory[c.ThematicID] = append(coursesBySubcategory[c.ThematicID], courseInfo)
+
+ if _, ok := subcategoriesByCategories[c.LearningTypeID]; !ok {
+ subcategoriesByCategories[c.LearningTypeID] = map[string]struct{}{}
+ }
+ subcategoriesByCategories[c.LearningTypeID][c.ThematicID] = struct{}{}
+
+ if _, ok := categoryByID[c.LearningTypeID]; !ok {
+ categoryByID[c.LearningTypeID] = xtempl.CategoryBaseInfo{
+ ID: c.LearningTypeID,
+ Name: c.LearningType,
+ }
+ }
+ if _, ok := categoryByID[c.ThematicID]; !ok {
+ categoryByID[c.ThematicID] = xtempl.CategoryBaseInfo{
+ ID: c.ThematicID,
+ Name: c.Thematic,
+ }
+ }
+ })
+
+ var out xtempl.ListCoursesParams
+ for categoryID, subcategoriesID := range subcategoriesByCategories {
+ outCategory := xtempl.CategoryContainer{
+ CategoryBaseInfo: categoryByID[categoryID],
+ }
+
+ for subcategoryID := range subcategoriesID {
+ outSubcategory := xtempl.SubcategoryContainer{
+ CategoryBaseInfo: categoryByID[subcategoryID],
+ Courses: coursesBySubcategory[subcategoryID],
+ }
+
+ outCategory.Subcategories = append(outCategory.Subcategories, outSubcategory)
+ }
+
+ out.Categories = append(out.Categories, outCategory)
+ }
+
+ return out
+}
+
+func (c courseTemplServer) List(w http.ResponseWriter, r *http.Request) {
+ ctx := r.Context()
+
+ stats := xtempl.MakeNewStats(10_240, 2_560_000, 1800)
+
+ pathParams, err := parseListCoursesParams(r)
+ if handleError(ctx, err, w, c.log, "unable to parse list courses params") {
+ return
+ }
+
+ listCoursesResult, err := c.app.Queries.ListCourses.Handle(ctx, query.ListCourse{
+ CourseThematic: pathParams.courseThematic,
+ LearningType: pathParams.learningType,
+ Limit: pathParams.perPage,
+ NextPageToken: pathParams.nextPageToken,
+ })
+ if handleError(ctx, err, w, c.log, "unable to list courses") {
+ return
+ }
+
+ params := makeTemplListCoursesParams(listCoursesResult.Courses...)
+
+ learningTypeResult, err := c.app.Queries.ListLearningTypes.Handle(ctx, query.ListLearningTypes{})
+ if handleError(ctx, err, w, c.log, "unable to list learning types") {
+ return
+ }
+
+ params.FilterForm.AvailableLearningTypes = xslices.Map(learningTypeResult.LearningTypes, func(in query.LearningType) xtempl.Category {
+ outcategory := xtempl.Category{
+ ID: in.ID,
+ Name: in.Name,
+ }
+ if in.ID == pathParams.learningType {
+ params.FilterForm.BreadcrumbsParams.ActiveLearningType = outcategory
+ }
+
+ return outcategory
+ })
+
+ if pathParams.learningType != "" {
+ courseThematicsResult, err := c.app.Queries.ListCourseThematics.Handle(ctx, query.ListCourseThematics{
+ LearningTypeID: pathParams.learningType,
+ })
+ if handleError(ctx, err, w, c.log, "unab;e to list course thematics") {
+ return
+ }
+
+ params.FilterForm.AvailableCourseThematics = xslices.Map(courseThematicsResult.CourseThematics, func(in query.CourseThematic) xtempl.Category {
+ outcategory := xtempl.Category{
+ ID: in.ID,
+ Name: in.Name,
+ }
+ if pathParams.courseThematic == in.ID {
+ params.FilterForm.BreadcrumbsParams.ActiveCourseThematic = outcategory
+ }
+
+ return outcategory
+ })
+ }
+
+ err = xtempl.ListCourses(stats, params).Render(ctx, w)
+ if handleError(ctx, err, w, c.log, "unable to render list courses") {
+ return
+ }
+}
diff --git a/internal/kurious/ports/http/server.go b/internal/kurious/ports/http/server.go
index 01e6123..9b16138 100644
--- a/internal/kurious/ports/http/server.go
+++ b/internal/kurious/ports/http/server.go
@@ -27,6 +27,10 @@ func (s Server) Courses() courseServer {
return courseServer(s)
}
+func (s Server) CoursesByTempl() courseTemplServer {
+ return courseTemplServer(s)
+}
+
func handleError(ctx context.Context, err error, w http.ResponseWriter, log *slog.Logger, msg string) bool {
if err == nil {
return false
diff --git a/internal/kurious/ports/http/templ/common.templ b/internal/kurious/ports/http/templ/common.templ
new file mode 100644
index 0000000..3936045
--- /dev/null
+++ b/internal/kurious/ports/http/templ/common.templ
@@ -0,0 +1,22 @@
+package templ
+
+templ button(title string, attributes templ.Attributes) {
+
+}
+
+templ buttonRedirect(id, title string, linkTo string) {
+
+
+ @onclickRedirect("origin-link-" + id, linkTo)
+}
+
+script onclickRedirect(id, to string) {
+ document.getElementById(id).onclick = () => {
+ location.href = to
+ }
+}
diff --git a/internal/kurious/ports/http/templ/common_templ.go b/internal/kurious/ports/http/templ/common_templ.go
new file mode 100644
index 0000000..b76bd0f
--- /dev/null
+++ b/internal/kurious/ports/http/templ/common_templ.go
@@ -0,0 +1,116 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.513
+package templ
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import "context"
+import "io"
+import "bytes"
+
+func button(title string, attributes templ.Attributes) templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
+ if !templ_7745c5c3_IsBuffer {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func buttonRedirect(id, title string, linkTo string) templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
+ if !templ_7745c5c3_IsBuffer {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var3 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var3 == nil {
+ templ_7745c5c3_Var3 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = onclickRedirect("origin-link-"+id, linkTo).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func onclickRedirect(id, to string) templ.ComponentScript {
+ return templ.ComponentScript{
+ Name: `__templ_onclickRedirect_47ae`,
+ Function: `function __templ_onclickRedirect_47ae(id, to){document.getElementById(id).onclick = () => {
+ location.href = to
+ }}`,
+ Call: templ.SafeScript(`__templ_onclickRedirect_47ae`, id, to),
+ CallInline: templ.SafeScriptInline(`__templ_onclickRedirect_47ae`, id, to),
+ }
+}
diff --git a/internal/kurious/ports/http/templ/header.templ b/internal/kurious/ports/http/templ/header.templ
new file mode 100644
index 0000000..8b75f19
--- /dev/null
+++ b/internal/kurious/ports/http/templ/header.templ
@@ -0,0 +1,62 @@
+package templ
+
+templ head() {
+
+
+
+ Courses Aggregator
+
+
+
+
+
+
+
+
+}
+
+templ navigation() {
+
+}
+
+templ footer() {
+
+}
diff --git a/internal/kurious/ports/http/templ/header_templ.go b/internal/kurious/ports/http/templ/header_templ.go
new file mode 100644
index 0000000..fe6eac2
--- /dev/null
+++ b/internal/kurious/ports/http/templ/header_templ.go
@@ -0,0 +1,182 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.513
+package templ
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import "context"
+import "io"
+import "bytes"
+
+func head() templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
+ if !templ_7745c5c3_IsBuffer {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var2 := `Courses Aggregator`
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func navigation() templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
+ if !templ_7745c5c3_IsBuffer {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ 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 = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func footer() templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
+ if !templ_7745c5c3_IsBuffer {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var13 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var13 == nil {
+ templ_7745c5c3_Var13 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
diff --git a/internal/kurious/ports/http/templ/list.templ b/internal/kurious/ports/http/templ/list.templ
new file mode 100644
index 0000000..a698f2a
--- /dev/null
+++ b/internal/kurious/ports/http/templ/list.templ
@@ -0,0 +1,209 @@
+package templ
+
+import "strconv"
+import "fmt"
+
+script breadcrumbsLoad() {
+ const formFilterOnSubmit = event => {
+ event.preventDefault();
+
+ const lt = document.getElementById('learning-type-filter');
+ const ct = document.getElementById('course-thematic-filter');
+
+ const prefix = (lt !== null && lt.value !== '') ? `/courses/${lt.value}` : `/courses`;
+ const out = (ct !== null && ct.value !== '') ? `${prefix}/${ct.value}` : prefix;
+
+ document.location.assign(out);
+ return false;
+ };
+
+ document.addEventListener('DOMContentLoaded', () => {
+ const ff = document.getElementById('filter-form');
+ if (ff === null) return;
+ ff.addEventListener('submit', formFilterOnSubmit);
+ });
+}
+
+templ breadcrumbItem(enabled bool, link string, isLink bool, title string) {
+ if enabled {
+
+
+
+ { title }
+
+
+
+ }
+}
+
+templ listCourseHeader(params FilterFormParams) {
+
+ @breadcrumb(params.BreadcrumbsParams)
+ @filterForm(params)
+
+}
+
+templ breadcrumb(params BreadcrumbsParams) {
+
+}
+
+templ filterForm(params FilterFormParams) {
+
+}
+
+templ listCoursesContainer(categories []CategoryContainer) {
+
+ for _, category := range categories {
+
+
+
+ This category contains a lot of interesing courses. Check them out!
+
+ for _, subcategory := range category.Subcategories {
+
+ }
+
+ }
+
+}
+
+templ ListCourses(s stats, params ListCoursesParams) {
+ @root(s) {
+ @listCourseHeader(params.FilterForm)
+ @listCoursesContainer(params.Categories)
+
+
+ }
+}
+
+templ root(s stats) {
+
+
+ @head()
+
+ @navigation()
+
+
+ { children... }
+
+ @footer()
+ @breadcrumbsLoad()
+
+
+}
+
+templ courseInfoElement(params CourseInfo) {
+
+
+
+
+
+
+
+
+
+
{ params.Name }
+
oh well
+
+
+ if params.FullPrice > 0 {
+
{ strconv.Itoa(params.FullPrice) } руб.
+ } else {
+
Бесплатно
+ }
+
+
+ @buttonRedirect(params.ID, "Show course", params.OriginLink)
+
+
+
+}
diff --git a/internal/kurious/ports/http/templ/list_templ.go b/internal/kurious/ports/http/templ/list_templ.go
new file mode 100644
index 0000000..39a90df
--- /dev/null
+++ b/internal/kurious/ports/http/templ/list_templ.go
@@ -0,0 +1,714 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.513
+package templ
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import "context"
+import "io"
+import "bytes"
+
+import "strconv"
+import "fmt"
+
+func breadcrumbsLoad() templ.ComponentScript {
+ return templ.ComponentScript{
+ Name: `__templ_breadcrumbsLoad_9a1d`,
+ Function: `function __templ_breadcrumbsLoad_9a1d(){const formFilterOnSubmit = event => {
+ event.preventDefault();
+
+ const lt = document.getElementById('learning-type-filter');
+ const ct = document.getElementById('course-thematic-filter');
+
+ const prefix = (lt !== null && lt.value !== '') ? ` + "`" + `/courses/${lt.value}` + "`" + ` : ` + "`" + `/courses` + "`" + `;
+ const out = (ct !== null && ct.value !== '') ? ` + "`" + `${prefix}/${ct.value}` + "`" + ` : prefix;
+
+ document.location.assign(out);
+ return false;
+ };
+
+ document.addEventListener('DOMContentLoaded', () => {
+ const ff = document.getElementById('filter-form');
+ if (ff === null) return;
+ ff.addEventListener('submit', formFilterOnSubmit);
+ });}`,
+ Call: templ.SafeScript(`__templ_breadcrumbsLoad_9a1d`),
+ CallInline: templ.SafeScriptInline(`__templ_breadcrumbsLoad_9a1d`),
+ }
+}
+
+func breadcrumbItem(enabled bool, link string, isLink bool, title string) templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
+ if !templ_7745c5c3_IsBuffer {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ if enabled {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var3 string
+ templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(title)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/templ/list.templ`, Line: 41, Col: 12}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func listCourseHeader(params FilterFormParams) templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
+ if !templ_7745c5c3_IsBuffer {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var4 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var4 == nil {
+ templ_7745c5c3_Var4 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = breadcrumb(params.BreadcrumbsParams).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = filterForm(params).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func breadcrumb(params BreadcrumbsParams) templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
+ if !templ_7745c5c3_IsBuffer {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ 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 = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func filterForm(params FilterFormParams) templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
+ if !templ_7745c5c3_IsBuffer {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var6 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var6 == nil {
+ templ_7745c5c3_Var6 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func listCoursesContainer(categories []CategoryContainer) templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
+ if !templ_7745c5c3_IsBuffer {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var11 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var11 == nil {
+ templ_7745c5c3_Var11 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for _, category := range categories {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var14 := `This category contains a lot of interesing courses. Check them out!`
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for _, subcategory := range category.Subcategories {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func ListCourses(s stats, params ListCoursesParams) templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
+ if !templ_7745c5c3_IsBuffer {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var17 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var17 == nil {
+ templ_7745c5c3_Var17 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Var18 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
+ if !templ_7745c5c3_IsBuffer {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ templ_7745c5c3_Err = listCourseHeader(params.FilterForm).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = listCoursesContainer(params.Categories).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
+ }
+ return templ_7745c5c3_Err
+ })
+ templ_7745c5c3_Err = root(s).Render(templ.WithChildren(ctx, templ_7745c5c3_Var18), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func root(s stats) templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
+ if !templ_7745c5c3_IsBuffer {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var19 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var19 == nil {
+ templ_7745c5c3_Var19 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = head().Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = navigation().Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ_7745c5c3_Var19.Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = footer().Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = breadcrumbsLoad().Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func courseInfoElement(params CourseInfo) templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
+ if !templ_7745c5c3_IsBuffer {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var26 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var26 == nil {
+ templ_7745c5c3_Var26 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(")
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var27 string
+ templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(params.Name)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/templ/list.templ`, Line: 193, Col: 40}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var28 := `oh well`
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var28)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if params.FullPrice > 0 {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var29 string
+ templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(params.FullPrice))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/templ/list.templ`, Line: 198, Col: 41}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var30 := `руб.`
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var30)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ } else {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var31 := `Бесплатно`
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var31)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = buttonRedirect(params.ID, "Show course", params.OriginLink).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
diff --git a/internal/kurious/ports/http/templ/vars.go b/internal/kurious/ports/http/templ/vars.go
new file mode 100644
index 0000000..ff7b698
--- /dev/null
+++ b/internal/kurious/ports/http/templ/vars.go
@@ -0,0 +1,95 @@
+package templ
+
+import (
+ "strconv"
+ "strings"
+)
+
+func getCompactedValue(value int) string {
+ var (
+ myValue float64
+ dim string
+ )
+ switch {
+ case value/1e6 > 0:
+ cutted := value / 1e3
+ myValue, dim = float64(cutted)/1e3, "m"
+ case value/1e3 > 0:
+ myValue, dim = float64(value/1e3), "k"
+ default:
+ myValue, dim = float64(value), ""
+ }
+
+ return strings.TrimSuffix(strconv.FormatFloat(myValue, 'f', 3, 32), ".000") + dim
+}
+
+func MakeNewStats(courses, clients, categories int) stats {
+ return stats{
+ CoursesCount: getCompactedValue(courses),
+ ClientsCount: getCompactedValue(clients),
+ CategoriesCount: getCompactedValue(categories),
+ }
+}
+
+type stats struct {
+ CoursesCount string
+ ClientsCount string
+ CategoriesCount string
+}
+
+type Category struct {
+ ID string
+ Name string
+}
+
+func (c Category) Empty() bool {
+ return c == (Category{})
+}
+
+type BreadcrumbsParams struct {
+ ActiveLearningType Category
+ ActiveCourseThematic Category
+}
+
+type FilterFormParams struct {
+ BreadcrumbsParams
+
+ AvailableLearningTypes []Category
+ AvailableCourseThematics []Category
+}
+
+func isEmpty(s string) bool {
+ return s == ""
+}
+
+type CourseInfo struct {
+ ID string
+ Name string
+ FullPrice int
+ ImageLink string
+ OriginLink string
+}
+
+type CategoryBaseInfo struct {
+ ID string
+ Name string
+ Description string
+}
+
+type CategoryContainer struct {
+ CategoryBaseInfo
+
+ Subcategories []SubcategoryContainer
+}
+
+type SubcategoryContainer struct {
+ CategoryBaseInfo
+
+ Courses []CourseInfo
+}
+
+type ListCoursesParams struct {
+ FilterForm FilterFormParams
+
+ Categories []CategoryContainer
+}
diff --git a/internal/kurious/ports/http/templ/vars_test.go b/internal/kurious/ports/http/templ/vars_test.go
new file mode 100644
index 0000000..6de60bb
--- /dev/null
+++ b/internal/kurious/ports/http/templ/vars_test.go
@@ -0,0 +1,57 @@
+package templ
+
+import "testing"
+
+func TestGetCompactedValue(t *testing.T) {
+ var tt = []struct {
+ name string
+ in int
+ exp string
+ }{
+ {
+ name: "less than 1k",
+ in: 666,
+ exp: "666",
+ },
+ {
+ name: "exactly 1k",
+ in: 1000,
+ exp: "1k",
+ },
+ {
+ name: "some thousands",
+ in: 12345,
+ exp: "12k",
+ },
+ {
+ name: "more thousands",
+ in: 123456,
+ exp: "123k",
+ },
+ {
+ name: "million",
+ in: 1e6,
+ exp: "1m",
+ },
+ {
+ name: "some millions",
+ in: 2e6,
+ exp: "2m",
+ },
+ {
+ name: "more complex value",
+ in: 1.2346e6,
+ exp: "1.234m",
+ },
+ }
+
+ for _, tc := range tt {
+ tc := tc
+ t.Run(tc.name, func(t *testing.T) {
+ got := getCompactedValue(tc.in)
+ if tc.exp != got {
+ t.Errorf("exp=%s got=%s", tc.exp, got)
+ }
+ })
+ }
+}
diff --git a/internal/kurious/ports/http/templates/list.tmpl b/internal/kurious/ports/http/templates/list.tmpl
index 2f637d0..61a8bfc 100644
--- a/internal/kurious/ports/http/templates/list.tmpl
+++ b/internal/kurious/ports/http/templates/list.tmpl
@@ -47,7 +47,7 @@
{{ else }}
-
+
Курсы
@@ -64,7 +64,7 @@
{{ else }}
-
+
{{ .LearningTypeName }}