Skip to content

Commit

Permalink
Move the imports into a new page and create the sidenav (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
Peltoche authored Jun 25, 2024
1 parent 6c23673 commit f261bde
Show file tree
Hide file tree
Showing 11 changed files with 235 additions and 28 deletions.
2 changes: 1 addition & 1 deletion assets/public/js/setup.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { Sidenav, Datatable, Dropdown, Collapse, Select } from "/assets/js/libs/mdb.es.min.js";

export function SetupSideNav() {
const sidenav = document.getElementById("main-sidenav");
const sidenav = document.getElementById("sidenav");

let innerWidth = null;

Expand Down
2 changes: 2 additions & 0 deletions internal/server/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/Peltoche/gnocchi/internal/tools/sqlstorage"
contactsweb "github.com/Peltoche/gnocchi/internal/web/contacts"
"github.com/Peltoche/gnocchi/internal/web/html"
importsweb "github.com/Peltoche/gnocchi/internal/web/imports"
"github.com/spf13/afero"
"go.uber.org/fx"
"go.uber.org/fx/fxevent"
Expand Down Expand Up @@ -91,6 +92,7 @@ func start(ctx context.Context, cfg Config, invoke fx.Option) *fx.App {
// Web Pages
AsRoute(contactsweb.NewListPage),
AsRoute(contactsweb.NewDetailsPage),
AsRoute(importsweb.NewImportsPage),

// HTTP Router / HTTP Server
router.InitMiddlewares,
Expand Down
37 changes: 37 additions & 0 deletions internal/web/html/templates/components/sidenav.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{{define "sidenav-btn"}}
<button
data-mdb-ripple-init
data-mdb-toggle="sidenav"
data-mdb-target="#sidenav"
class="btn shadow-0 p-0 me-3 d-block d-xxl-none"
aria-controls="#sidenav"
aria-haspopup="true"
>
<i class="fas fa-bars fa-lg"></i>
</button>
{{end}}

{{define "sidenav"}}
<!-- Sidenav -->
<nav
data-mdb-sidenav-init
id="sidenav"
class="sidenav"
data-mdb-hidden="true"
>
<ul class="sidenav-menu">
<li class="sidenav-item">
<a class="sidenav-link" href="/web/contacts" hx-boost="true">
<i class="fas fa-address-book me-2"></i>Contacts
</a>
</li>
<li class="sidenav-item">
<a class="sidenav-link" href="/web/imports" hx-boost="true">
<i class="fas fa-arrow-up-from-bracket me-2"></i>Import
</a>
</li>
</ul>
</nav>
<!-- Sidenav -->
{{end}}

9 changes: 6 additions & 3 deletions internal/web/html/templates/pages/contacts/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,25 @@
</div>
</div>


{{ yield }}
{{template "sidenav"}}
</main>


<script src="/assets/js/libs/htmx.min.js"></script>
<script src="/assets/js/libs/response-targets.js"></script>
</body>

<script type="module">
<script type="module">

import {SetupBootstrapElems} from "/assets/js/setup.mjs"
import {SetupBootstrapElems, SetupSideNav} from "/assets/js/setup.mjs"
import {Modal, Button, Ripple, Dropdown, initMDB} from "/assets/js/libs/mdb.es.min.js";

initMDB({Modal, Button, Ripple, Dropdown});
SetupBootstrapElems()
SetupSideNav()

</script>
</script>

</html>
24 changes: 2 additions & 22 deletions internal/web/html/templates/pages/contacts/page_list.html
Original file line number Diff line number Diff line change
@@ -1,30 +1,10 @@
<header>
<nav class="navbar sticky-top bg-primary">
<div class="container-fluid justify-content-between">
<div class="container-fluid justify-content-start">
{{template "sidenav-btn"}}
<div class="d-flex flex-row align-items-center">
<a class="navbar-brand ps-4">Contacts</a>
</div>
<div>
<div class="dropdown">
<a class="btn btn-white btn-rounded shadow-0" role="button" data-mdb-dropdown-init
aria-expanded="false"><i class="fas fa-ellipsis-vertical fa-2x text-muted"></i></a>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<li><a
class="dropdown-item"
data-mdb-target="#modal-target"
data-mdb-modal-init
data-mdb-toggle="modal"
hx-target="#modal-target"
hx-get="/web/contacts/imports"
hx-trigger="click"
hx-swap="innerHTML"
data-mdb-ripple-init
>
<i class="fas fa-arrow-up-from-bracket me-2"></i>Import</a>
</li>
</ul>
</div>
</div>
</div>
</nav>

Expand Down
37 changes: 37 additions & 0 deletions internal/web/html/templates/pages/imports/layout.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!doctype html >
{{ template "header"}}

<body hx-ext="response-targets" hx-target-5*="this" hx-target-4*="this">
<main>
<!-- Modal -->
<div id="modal-target" class="modal modal-blur fade" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered" role="document">
<div class="modal-content"></div>
</div>
</div>


<div id="content">
{{ yield }}
</div>

{{template "sidenav"}}
</main>


<script src="/assets/js/libs/htmx.min.js"></script>
<script src="/assets/js/libs/response-targets.js"></script>
</body>

<script type="module">

import {SetupBootstrapElems, SetupSideNav} from "/assets/js/setup.mjs"
import {Modal, Button, Ripple, Dropdown, initMDB} from "/assets/js/libs/mdb.es.min.js";

initMDB({Modal, Button, Ripple, Dropdown});
SetupBootstrapElems()
SetupSideNav()

</script>

</html>
24 changes: 24 additions & 0 deletions internal/web/html/templates/pages/imports/page_imports.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<header>
<nav class="navbar sticky-top bg-primary">
<div class="container-fluid justify-content-start">
<a class="navbar-nav" href="/web/contacts" hx-boost="true"><i class="fas fa-arrow-left fa-lg"></i></a>
<a class="navbar-brand ps-4">Imports</a>
</div>
</nav>
</header>

<div class="container-fluid py-2">
<h3 class="my-3">Import a csv file</h3>

<form target="_top" hx-post="/web/imports/vcs"
hx-encoding='multipart/form-data'
hx-swap="outerHTML" hx-target="body">
<label for="fileInput" class="visually-hidden">Import a CSV file</label>
<input class="form-control" name="file" id="fileInput" type="file" />
<button type="submit" class="btn btn-primary my-3">Submit</button>
</form>

{{if .ErrorMsg}}
<div id="validation-alert" class="alert alert-danger role=">{{.ErrorMsg}}</div>
{{end}}
</div>
9 changes: 9 additions & 0 deletions internal/web/html/templates/pages/imports/templates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package imports

type ImportsPageTmpl struct {
ErrorMsg string
}

func (t *ImportsPageTmpl) Template() string {
return "pages/imports/page_imports"
}
52 changes: 52 additions & 0 deletions internal/web/html/templates/pages/imports/templates_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package imports

import (
"io"
"net/http"
"net/http/httptest"
"testing"

"github.com/Peltoche/gnocchi/internal/web/html"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Test_Templates(t *testing.T) {
renderer := html.NewRenderer(html.Config{
PrettyRender: false,
HotReload: false,
})

tests := []struct {
Template html.Templater
Name string
Layout bool
}{
{
Name: "ImportsPageTmpl",
Layout: true,
Template: &ImportsPageTmpl{},
},
}

for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/foo", nil)

if !test.Layout {
r.Header.Add("HX-Boosted", "true")
}

renderer.WriteHTMLTemplate(w, r, http.StatusOK, test.Template)

if !assert.Equal(t, http.StatusOK, w.Code) {
res := w.Result()
res.Body.Close()
body, err := io.ReadAll(res.Body)
require.NoError(t, err)
t.Log(string(body))
}
})
}
}
4 changes: 2 additions & 2 deletions internal/web/html/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,12 @@ func (t *Renderer) WriteHTMLErrorPage(w http.ResponseWriter, r *http.Request, er
reqID := r.Context().Value(middleware.RequestIDKey).(string)

if r.Header.Get("HX-Boosted") == "" && r.Header.Get("HX-Request") == "" {
layout = path.Join("home/layout")
layout = path.Join("page/home/layout")
}

logger.LogEntrySetError(r.Context(), err)

if err := t.render.HTML(w, http.StatusInternalServerError, "home/500", map[string]any{
if err := t.render.HTML(w, http.StatusInternalServerError, "pages/home/500", map[string]any{
"requestID": reqID,
}, render.HTMLOptions{Layout: layout}); err != nil {
logger.LogEntrySetAttrs(r.Context(), slog.String("render-error", err.Error()))
Expand Down
63 changes: 63 additions & 0 deletions internal/web/imports/page_imports.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package imports

import (
"errors"
"fmt"
"net/http"

"github.com/Peltoche/gnocchi/internal/service/vcard"
"github.com/Peltoche/gnocchi/internal/tools/router"
"github.com/Peltoche/gnocchi/internal/web/html"
importsmpl "github.com/Peltoche/gnocchi/internal/web/html/templates/pages/imports"
"github.com/go-chi/chi/v5"
)

type ImportsPage struct {
html html.Writer
vcard vcard.Service
}

func NewImportsPage(html html.Writer, vcard vcard.Service) *ImportsPage {
return &ImportsPage{
html: html,
vcard: vcard,
}
}

func (h *ImportsPage) Register(r chi.Router, mids *router.Middlewares) {
if mids != nil {
r = r.With(mids.Defaults()...)
}

r.Get("/web/imports", h.getImportsPage)
r.Post("/web/imports/vcs", h.importFile)
}

func (h *ImportsPage) getImportsPage(w http.ResponseWriter, r *http.Request) {
h.html.WriteHTMLTemplate(w, r, http.StatusOK, &importsmpl.ImportsPageTmpl{})
}

func (h *ImportsPage) importFile(w http.ResponseWriter, r *http.Request) {
file, _, err := r.FormFile("file")
if err != nil {
h.html.WriteHTMLTemplate(w, r, http.StatusUnprocessableEntity, &importsmpl.ImportsPageTmpl{
ErrorMsg: "missing file",
})
return
}
defer file.Close()

err = h.vcard.ImportVCardFile(r.Context(), file)
switch {
case err == nil:
http.Redirect(w, r, "/web/contacts", http.StatusFound)
return
case errors.Is(err, vcard.ErrUnsupportedVCardVersion) ||
errors.Is(err, vcard.ErrInvalidVCard):
h.html.WriteHTMLTemplate(w, r, http.StatusUnprocessableEntity, &importsmpl.ImportsPageTmpl{
ErrorMsg: err.Error(),
})
default:
h.html.WriteHTMLErrorPage(w, r, fmt.Errorf("failed to import the vcard file: %w", err))
}
}

0 comments on commit f261bde

Please sign in to comment.