add more style

This commit is contained in:
Aleksandr Trushkin
2024-01-05 23:03:15 +03:00
parent fbe9927ac3
commit 48f5d80f7a
10 changed files with 415 additions and 134 deletions

View File

@ -83,7 +83,8 @@ type subcategoryInfo struct {
}
type listCoursesTemplateParams struct {
Categories []categoryInfo
Categories []categoryInfo
NextPageToken string
}
func mapDomainCourseToTemplate(in ...domain.Course) listCoursesTemplateParams {
@ -146,8 +147,9 @@ func (c courseServer) List(w http.ResponseWriter, r *http.Request) {
courses := result.Courses
templateCourses := mapDomainCourseToTemplate(courses...)
templateCourses.NextPageToken = result.NextPageToken
err = listTemplateParsed.ExecuteTemplate(w, "courses", templateCourses)
err = getCoreTemplate(ctx, c.log).ExecuteTemplate(w, "courses", templateCourses)
if handleError(ctx, err, w, c.log, "unable to execute template") {
return
}
@ -177,6 +179,76 @@ func (c courseServer) Get(w http.ResponseWriter, r *http.Request) {
}
}
func (c courseServer) GetShort(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id := mux.Vars(r)["course_id"]
course, err := c.app.Queries.GetCourse.Handle(ctx, query.GetCourse{
ID: id,
})
if handleError(ctx, err, w, c.log, "unable to get course") {
return
}
err = getCoreTemplate(ctx, c.log).ExecuteTemplate(w, "course_info", course)
if handleError(ctx, err, w, c.log, "unable to execute template") {
return
}
}
func (c courseServer) RenderEditDescription(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id := mux.Vars(r)["course_id"]
course, err := c.app.Queries.GetCourse.Handle(ctx, query.GetCourse{
ID: id,
})
if handleError(ctx, err, w, c.log, "unable to get course") {
return
}
err = getCoreTemplate(ctx, c.log).ExecuteTemplate(w, "edit_description", course)
if handleError(ctx, err, w, c.log, "unable to execute template") {
return
}
}
func (c courseServer) UpdateCourseDescription(w http.ResponseWriter, r *http.Request) {
type requestModel struct {
ID string `json:"-"`
Text string `json:"text"`
}
ctx := r.Context()
var req requestModel
req.ID = mux.Vars(r)["course_id"]
err := json.NewDecoder(r.Body).Decode(&req)
if handleError(ctx, err, w, c.log, "unable to read body") {
return
}
err = c.app.Commands.UpdateCourseDescription.Handle(ctx, command.UpdateCourseDescription{
ID: req.ID,
Description: req.Text,
})
if handleError(ctx, err, w, c.log, "unable to update course description") {
return
}
course, err := c.app.Queries.GetCourse.Handle(ctx, query.GetCourse{
ID: req.ID,
})
if handleError(ctx, err, w, c.log, "unable to get course") {
return
}
err = getCoreTemplate(ctx, c.log).ExecuteTemplate(w, "course_info", course)
if handleError(ctx, err, w, c.log, "unable to execute template") {
return
}
}
func (c courseServer) UdpateDescription(w http.ResponseWriter, r *http.Request) {
type requestModel struct {
ID string `json:"id"`

View File

@ -1,6 +1,48 @@
package http
import "html/template"
import (
"context"
"html/template"
"io/fs"
"log/slog"
"os"
"path"
"git.loyso.art/frx/kurious/internal/common/xcontext"
"git.loyso.art/frx/kurious/internal/common/xslice"
)
const baseTemplatePath = "./internal/kurious/ports/http/templates/"
func must[T any](t T, err error) T {
if err != nil {
panic(err.Error())
}
return t
}
func scanFiles() []string {
entries := xslice.Map(
must(os.ReadDir(baseTemplatePath)),
func(v fs.DirEntry) string {
return path.Join(baseTemplatePath, v.Name())
},
)
return entries
}
func getCoreTemplate(ctx context.Context, log *slog.Logger) *template.Template {
filenames := scanFiles()
out, err := template.New("courses").ParseFiles(filenames...)
if err != nil {
xcontext.LogWithWarnError(ctx, log, err, "unable to parse template")
return listTemplateParsed
}
return out
}
var listTemplateParsed = template.Must(
template.New("courses").
@ -111,6 +153,7 @@ const listTemplate = `{{define "courses"}}
{{end}}
{{end}}
{{end}}
<button onclick="window.location.href='/courses/?next={{.NextPageToken}}'">Next Page</button>
<script>
const editableTexts = document.querySelectorAll('.editable-text');

View File

@ -0,0 +1,68 @@
{{ define "html_head" }}
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Courses Aggregator</title>
<script src="https://unpkg.com/htmx.org@1.8.0"></script>
<script src="https://unpkg.com/htmx.org/dist/ext/json-enc.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
</head>
{{ end }}
{{ define "header" }}
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
Courses
</div>
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
<div id="navbarBasicExample" class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item">
Home
</a>
<a class="navbar-item">
Find
</a>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">
More
</a>
<div class="navbar-dropdown">
<a class="navbar-item">
About
</a>
<a class="navbar-item">
Contact
</a>
<hr class="navbar-divider">
<a class="navbar-item">
Report an issue
</a>
</div>
</div>
</div>
</div>
</nav>
{{ end }}
{{ define "footer" }}
<footer>
Here will be footer
</footer>
{{ end }}

View File

@ -0,0 +1,76 @@
{{ define "course_info" }}
<article class="column is-one-quarter" hx-target="this" hx-swap="outerHTML">
<div class="card">
<div class="card-image">
<figure class="image">
<img src="{{ .ImageLink }}">
</figure>
</div>
<div class="card-content">
<div class="media-content">
<p class="title is-5">{{ .Name }}</p>
<p class="subtitle is-8">{{ .Description }}</p>
</div>
<div class="content">
<p>{{ .FullPrice }} rub.</p>
</div>
<button class="button" hx-get="/courses/{{ .ID }}/editdesc">
Edit description
</button>
<!-- <button class="button" hx-get="/courses/{{ .ID }}/" hx-target="#course-info" hx-swap="innerHTML">
View course
</button> -->
</div>
</div>
</article>
{{ end }}
{{ define "edit_description" }}
<form
hx-ext="json-enc"
hx-put="/courses/{{ .ID }}/description"
hx-target="this"
hx-swap="outerHTML"
>
<fieldset disabled>
<div class="field">
<label class="label">Name</label>
<div class="control">
<input class="input" type="text" placeholder="Text input" value="{{ .Name }}">
</div>
</fieldset>
<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label">Description</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<textarea class="textarea" placeholder="Description">{{ .Description }}</textarea>
</div>
</div>
</div>
</div>
<div>
<p>Full price: {{ .FullPrice }}</p>
</div>
<div class="field is-grouped">
<p class="control">
<button class="button is-primary is-link" hx-include="closest .control">
Submit
</button>
</p>
<p class="control">
<button class="button is-light" hx-get="/courses/{{ .ID }}/short">
Cancel
</button>
</p>
</div>
<!-- <button class="btn">Submit</button>
<button class="btn" hx-get="/courses/{{ .ID }}/short">Cancel</button> -->
</form>
{{ end }}

View File

@ -1,86 +1,69 @@
{{define "courses"}}
<!DOCTYPE html>
<html>
<head>
<title>Courses</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
header {
background-color: #333;
color: white;
padding: 10px;
}
header h1 {
margin: 0;
}
nav ul {
list-style-type: none;
margin: 0;
padding: 0;
}
nav li {
display: inline;
margin-right: 10px;
}
nav a {
color: white;
text-decoration: none;
}
h1, h2, h3 {
margin-top: 0;
}
p {
margin-bottom: 10px;
}
.course-plate {
background-color: #f2f2f2;
border: 1px solid #ddd;
border-radius: 5px;
padding: 10px;
margin-bottom: 10px;
text-align: center;
}
.course-plate a {
color: #333;
text-decoration: none;
}
.course-plate a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<header>
<h1>My Product</h1>
<nav>
<ul>
<li><a href="/">Main page</a></li>
<li><a href="/about">About us</a></li>
<li><a href="/help">Help</a></li>
</ul>
</nav>
</header>
<h1>Courses</h1>
{{range $category, $courses := .Courses}}
<h2>{{$category}}</h2>
<p>{{$category.Description}}</p>
{{range $course := $courses}}
<div class="course-plate">
<h3><a href="/courses/{{$course.ID}}">{{$course.Name}}</a></h3>
<p>{{$course.Description}}</p>
<p>Full price: {{$course.FullPrice}}</p>
<p>Discount: {{$course.Discount}}</p>
<p>Thematic: {{$course.Thematic}}</p>
<p>Learning type: {{$course.LearningType}}</p>
<p>Duration: {{$course.Duration}}</p>
<p>Starts at: {{$course.StartsAt}}</p>
</div>
{{end}}
{{end}}
</body>
</html>
<html>
{{ template "html_head" . }}
<body>
{{ template "header" . }}
<nav class="level">
<div class="level-item has-text-centered">
<div>
<p class="heading">Courses</p>
<p class="title">10k</p>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<p class="heading">Clients</p>
<p class="title">1m</p>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<p class="heading">Categories</p>
<p class="title">1,024</p>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<p class="heading">Likes</p>
<p class="title">Over 9m</p>
</div>
</div>
</nav>
<div class="section">
<div class="container">
<h1>Welcome to the Course Aggregator</h1>
<div id="category-course-list">
{{ range $category := .Categories }}
<div class="title">{{ $category.Name }}</div>
<p>{{ $category.Description }}</p>
{{ range $subcategory := $category.Subcategories }}
<div class="subtitle">{{ $subcategory.Name }}</div>
<p>{{ $subcategory.Description }}</p>
<div class="columns is-multiline">
{{ range $course := $subcategory.Courses }}
{{ template "course_info" $course }}
{{ end }}
</div>
{{ end }}
{{ end }}
</div>
<div id="course-info"></div>
</div>
</div>
<nav class="pagination is-centered" role="navigation" aria-label="pagination">
<a class="pagination-previous">Previous</a>
<a class="pagination-next" href="/courses/?next={{ .NextPageToken }}&per_page=50">Next page</a>
</nav>
{{ template "footer" . }}
</body>
</html>
{{end}}