device: added add page
This commit is contained in:
parent
b8fcb37db8
commit
19ffbdf98c
6
Makefile
6
Makefile
|
@ -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
|
||||
|
|
19
api/devices_validate_slug.go
Normal file
19
api/devices_validate_slug.go
Normal 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
|
||||
}
|
49
server/routes/device_validate_slug.go
Normal file
49
server/routes/device_validate_slug.go
Normal 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")
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
8
views/components/alpine.templ
Normal file
8
views/components/alpine.templ
Normal 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>
|
||||
}
|
|
@ -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>
|
||||
}
|
||||
|
|
39
views/devicesview/adddevice/name_input.templ
Normal file
39
views/devicesview/adddevice/name_input.templ
Normal 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>
|
||||
}
|
58
views/devicesview/adddevice/slug_input.templ
Normal file
58
views/devicesview/adddevice/slug_input.templ
Normal 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>
|
||||
}
|
|
@ -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>
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue