device: added add page

This commit is contained in:
Tigor Hutasuhut 2024-05-07 16:48:15 +07:00
parent b8fcb37db8
commit 19ffbdf98c
9 changed files with 186 additions and 0 deletions

View file

@ -9,6 +9,7 @@ export GOOSE_MIGRATION_DIR=db/migrations
export REDMAGE_WEB_DEPENDENCIES_HTMX_VERSION=$(shell echo "$${REDMAGE_WEB_DEPENDENCIES_HTMX_VERSION:-1.9.12}")
export REDMAGE_WEB_DEPENDENCIES_DAYJS_VERSION=$(shell echo "$${REDMAGE_WEB_DEPENDENCIES_DAYJS_VERSION:-1.11.10}")
export REDMAGE_WEB_DEPENDENCIES_ALPINEJS_VERSION=$(shell echo "$${REDMAGE_WEB_DEPENDENCIES_ALPINEJS_VERSION:-3.13.10}")
start: dev-dependencies
@tailwindcss -i views/style.css -o public/style.css --watch &
@ -62,6 +63,11 @@ build-dependencies:
echo "Dayjs Timezone ${REDMAGE_WEB_DEPENDENCIES_DAYJS_VERSION} plugin not found, installing it"
curl -o public/dayjs-timezone-${REDMAGE_WEB_DEPENDENCIES_DAYJS_VERSION}.min.js https://cdnjs.cloudflare.com/ajax/libs/dayjs/${REDMAGE_WEB_DEPENDENCIES_DAYJS_VERSION}/plugin/timezone.min.js
fi
@if [ ! -f "public/alpinejs-${REDMAGE_WEB_DEPENDENCIES_ALPINEJS_VERSION}.min.js" ]; then
mkdir -p public
echo "Alpinejs ${REDMAGE_WEB_DEPENDENCIES_ALPINEJS_VERSION} not found, installing it"
curl -o public/alpinejs-${REDMAGE_WEB_DEPENDENCIES_ALPINEJS_VERSION}.min.js https://cdn.jsdelivr.net/npm/alpinejs@${REDMAGE_WEB_DEPENDENCIES_ALPINEJS_VERSION}/dist/cdn.min.js
fi
build: build-dependencies prepare
go build -o redmage

View file

@ -0,0 +1,19 @@
package api
import (
"context"
"github.com/tigorlazuardi/redmage/models"
"github.com/tigorlazuardi/redmage/pkg/errs"
)
func (api *API) DevicesValidateSlug(ctx context.Context, slug string) (exist bool, err error) {
ctx, span := tracer.Start(ctx, "*API.DevicesValidateSlug")
defer span.End()
exist, err = models.Devices.Query(ctx, api.db, models.SelectWhere.Devices.Slug.EQ(slug)).Exists()
if err != nil {
return exist, errs.Wrapw(err, "failed to check device slug existence", "slug", slug)
}
return exist, err
}

View file

@ -0,0 +1,49 @@
package routes
import (
"net/http"
"github.com/tigorlazuardi/redmage/pkg/errs"
"github.com/tigorlazuardi/redmage/pkg/log"
"github.com/tigorlazuardi/redmage/views/devicesview/adddevice"
)
func (routes *Routes) DevicesValidateSlugHTMX(rw http.ResponseWriter, req *http.Request) {
ctx, span := tracer.Start(req.Context(), "*Routes.ValidateSlugHTMX")
defer span.End()
var data adddevice.SlugInputData
data.Value = req.FormValue("slug")
if data.Value == "" {
if err := adddevice.SlugInput(data).Render(ctx, rw); err != nil {
log.New(ctx).Err(err).Error("failed to render slug input")
}
return
}
exist, err := routes.API.DevicesValidateSlug(ctx, data.Value)
if err != nil {
log.New(ctx).Err(err).Error("failed to validate slug")
code, message := errs.HTTPMessage(err)
rw.WriteHeader(code)
data.Error = message
if err := adddevice.SlugInput(data).Render(ctx, rw); err != nil {
log.New(ctx).Err(err).Error("failed to render slug input")
}
return
}
if exist {
data.Error = "Device with this identifier already exist"
rw.WriteHeader(http.StatusConflict)
if err := adddevice.SlugInput(data).Render(ctx, rw); err != nil {
log.New(ctx).Err(err).Error("failed to render slug input")
}
return
}
data.Valid = "Identifier is available"
if err := adddevice.SlugInput(data).Render(ctx, rw); err != nil {
log.New(ctx).Err(err).Error("failed to render slug input")
}
}

View file

@ -62,6 +62,8 @@ func (routes *Routes) registerHTMXRoutes(router chi.Router) {
router.Post("/subreddits/start", routes.SubredditStartDownloadHTMX)
router.Post("/subreddits/check", routes.SubredditCheckHTMX)
router.Get("/subreddits/validate/schedule", routes.SubredditValidateScheduleHTMX)
router.Post("/devices/add/validate/slug", routes.DevicesValidateSlugHTMX)
}
func (routes *Routes) registerWWWRoutes(router chi.Router) {

View file

@ -0,0 +1,8 @@
package components
import "github.com/tigorlazuardi/redmage/views"
import "fmt"
templ AlpineJS(c *views.Context) {
<script defer src={ fmt.Sprintf("/public/alpinejs-%s.min.js", c.Config.String("web.dependencies.alpinejs.version")) }></script>
}

View file

@ -13,6 +13,7 @@ templ Head(vc *views.Context, extras ...templ.Component) {
<link rel="icon" href="/public/favicon.svg"/>
@Dayjs(vc)
@HTMX(vc)
@AlpineJS(vc)
if vc.Config.Bool("http.hotreload") {
<script src="/public/hot_reload.js"></script>
}

View file

@ -0,0 +1,39 @@
package adddevice
import "github.com/tigorlazuardi/redmage/views/utils"
import "fmt"
type NameInputData struct {
Error string
Value string
}
templ NameInput(data NameInputData) {
<label class="form-control">
<div class="label">
<span
class={ utils.CXX("label-text", true, "text-error", data.Error != "") }
>Name</span>
</div>
<input
id="name-input-field"
x-data={ fmt.Sprintf(`{ init() { $el.setCustomValidity(%q) } }`, data.Error) }
hx-post="/htmx/devices/add/validate/name"
hx-include="[name='slug']"
hx-trigger="change"
name="name"
type="text"
class={ utils.CXX("input input-bordered", true, "input-error", data.Error != "") }
value={ data.Value }
placeholder="My Awesome Device"
required
/>
<div class="label">
if data.Error != "" {
<span class="label-text text-error">{ data.Error }</span>
} else {
<span class="label-text">The display name for the device. Use distinguishable value for easy lookup.</span>
}
</div>
</label>
}

View file

@ -0,0 +1,58 @@
package adddevice
import "github.com/tigorlazuardi/redmage/views/utils"
import "fmt"
type SlugInputData struct {
Error string
Value string
Valid string
HXSwapOOB bool
}
templ SlugInput(data SlugInputData) {
<label id="slug-input-form" class="form-control">
<div class="label">
<span
class={ utils.CXX(
"label-text", true,
"text-error", data.Error != "",
"text-success", data.Valid != "",
) }
>Slug</span>
</div>
<input
id="slug-input-field"
x-data={ fmt.Sprintf(`{ init() { $el.setCustomValidity(%q) } }`, data.Error) }
name="slug"
type="text"
@change="$el.setCustomValidity('')"
class={ utils.CXX(
"input input-bordered", true,
"text-error", data.Error != "",
"text-success", data.Valid != "",
"input-error", data.Error != "",
"input-success", data.Valid != "",
) }
hx-post="/htmx/devices/add/validate/slug"
hx-trigger="change, input delay:200ms"
hx-target="#slug-input-form"
hx-target-409="#slug-input-form"
hx-swap="outerHTML"
value={ data.Value }
placeholder="my-awesome-device"
/>
<div class="label">
<span class={ utils.CXX("label-text", true, "text-error", data.Error != "", "text-success", data.Valid != "") }>
if data.Valid != "" {
{ data.Valid }
} else if data.Error != "" {
{ data.Error }
} else {
Unique identifier for the device.
Value must be lowercase english alphabet and supported separator is only 'dash' (-).
}
</span>
</div>
</label>
}

View file

@ -17,6 +17,10 @@ templ Content(c *views.Context) {
@components.Container() {
<h1>Add Device</h1>
<div class="divider"></div>
<form class="grid sm:grid-cols-2 gap-4">
@NameInput(NameInputData{})
@SlugInput(SlugInputData{})
</form>
}
</main>
}