~inferiormartin/shishutsu

835d0f4704b120372da49c14bf1c92d04d466c59 — Maarten Vos a month ago be98894 master
WIP
23 files changed, 249 insertions(+), 156 deletions(-)

A alert/alert.go
M cmd/shi-import/main.go
M cmd/shi-web/main.go
M go.mod
M go.sum
A nav/nav.go
A pages/cards/handler.go
R services/dashboard/handler.go => pages/dashboard/handler.go
R services/signin/handler.go => pages/signin/handler.go
D services/notification/notification.go
M static/account.svg
M static/admin.svg
A static/card.svg
A static/cards.css
M static/dashboard.svg
M static/main.css
R templates/{notification.html => alert.html}
R templates/{transactions.html => card-transactions.html}
R templates/{index.html => cards.html}
A templates/dashboard.html
A templates/nav.html
D templates/sidebar.html
M templates/sign-in.html
A alert/alert.go => alert/alert.go +15 -0
@@ 0,0 1,15 @@
package alert

type AlertType string

const (
	Notice  AlertType = "notice"
	Warning           = "warning"
	Error             = "error"
	OK                = "ok"
)

type Alert struct {
	Text string
	Type AlertType
}

M cmd/shi-import/main.go => cmd/shi-import/main.go +6 -3
@@ 55,7 55,10 @@ func parseFloat(value string) float64 {

func read() []*database.Transaction {
	args := os.Args[1:]
	data, err := os.ReadFile(args[0])
	path := args[0]
	account := args[1]
	iban := args[2]
	data, err := os.ReadFile(path)
	if err != nil {
		log.Fatal(err)
	}


@@ 70,8 73,8 @@ func read() []*database.Transaction {
	for _, result := range results {
		item := &database.Transaction{
			-1,
			"d8230e7e-ddcd-4455-b215-b72ca4a9638b", //TODO: allow import for any user
			"TODO",                                 //TODO: my bank's specific csv does not contain an IBAN because the statement exports are specific for an account. allow manual override using flag?
			account,
			iban,
			int64(parseFloat(result[6]) * 100),
			parseDate(result[4]),
			parseDate(result[5]),

M cmd/shi-web/main.go => cmd/shi-web/main.go +5 -3
@@ 7,8 7,9 @@ import (

	"git.sr.ht/~inferiormartin/shishutsu/app"
	"git.sr.ht/~inferiormartin/shishutsu/config"
	"git.sr.ht/~inferiormartin/shishutsu/services/dashboard"
	"git.sr.ht/~inferiormartin/shishutsu/services/signin"
	"git.sr.ht/~inferiormartin/shishutsu/pages/cards"
	"git.sr.ht/~inferiormartin/shishutsu/pages/dashboard"
	"git.sr.ht/~inferiormartin/shishutsu/pages/signin"
)

func main() {


@@ 24,8 25,9 @@ func main() {
	fs := http.FileServer(http.Dir("./static"))
	http.Handle("/static/", http.StripPrefix("/static/", fs))

	http.HandleFunc("/", dashboard.Handler(app))
	http.HandleFunc("/dashboard", dashboard.Handler(app))
	http.HandleFunc("/sign-in", signin.Handler(app))
	http.HandleFunc("/cards", cards.Handler(app))

	bind, ok := cfg.Get("shishutsu::web", "bind")


M go.mod => go.mod +1 -0
@@ 3,6 3,7 @@ module git.sr.ht/~inferiormartin/shishutsu
go 1.18

require (
	git.sr.ht/~sircmpwn/getopt v1.0.0 // indirect
	github.com/google/uuid v1.3.0 // indirect
	github.com/lib/pq v1.10.6 // indirect
	github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec // indirect

M go.sum => go.sum +6 -0
@@ 1,7 1,13 @@
git.sr.ht/~sircmpwn/getopt v1.0.0 h1:/pRHjO6/OCbBF4puqD98n6xtPEgE//oq5U8NXjP7ROc=
git.sr.ht/~sircmpwn/getopt v1.0.0/go.mod h1:wMEGFFFNuPos7vHmWXfszqImLppbc0wEhh6JBfJIUgw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs=
github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec h1:DGmKwyZwEB8dI7tbLt/I/gQuP559o/0FrAkHKlQM/Ks=
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=

A nav/nav.go => nav/nav.go +32 -0
@@ 0,0 1,32 @@
package nav

type Nav struct {
	Items map[string]*NavItem
}

type NavItem struct {
	Label  string
	Path   string
	Svg    string
	Active bool
}

func New(active string) *Nav {
	nav := &Nav{
		map[string]*NavItem{
			"dashboard": &NavItem{"Dashboard", "/dashboard", "dashboard.svg", false},
			"account":   &NavItem{"Account", "#", "account.svg", false},
			"admin":     &NavItem{"Admin", "#", "admin.svg", false},
			"cards":     &NavItem{"Cards", "/cards", "card.svg", false},
		},
	}

	for name, item := range nav.Items {
		if name == active {
			item.Active = true
			break
		}
	}

	return nav
}

A pages/cards/handler.go => pages/cards/handler.go +24 -0
@@ 0,0 1,24 @@
package cards

import (
	"context"
	"log"
	"net/http"

	"git.sr.ht/~inferiormartin/shishutsu/app"
	"git.sr.ht/~inferiormartin/shishutsu/auth"
	"git.sr.ht/~inferiormartin/shishutsu/nav"
)

type Cards struct {
	Nav *nav.Nav
}

func Handler(app *app.Application) http.HandlerFunc {
	return auth.Middleware(app, func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		err := app.Template.ExecuteTemplate(w, "cards.html", Cards{nav.New("cards")})
		if err != nil {
			log.Fatal(err)
		}
	})
}

R services/dashboard/handler.go => pages/dashboard/handler.go +6 -1
@@ 9,10 9,12 @@ import (
	"git.sr.ht/~inferiormartin/shishutsu/app"
	"git.sr.ht/~inferiormartin/shishutsu/auth"
	"git.sr.ht/~inferiormartin/shishutsu/database"
	"git.sr.ht/~inferiormartin/shishutsu/nav"
)

type Dashboard struct {
	Transactions []database.Transaction
	Nav          *nav.Nav
}

func Handler(app *app.Application) http.HandlerFunc {


@@ 22,6 24,7 @@ func Handler(app *app.Application) http.HandlerFunc {
		defer db.Close()
		rows, err := db.Query("SELECT id, account_id, iban, amount, transaction_date, interest_date, description FROM transaction WHERE account_id = $1", id)
		if err != nil {
			//TODO: no Fatal
			log.Fatal(err)
		}
		defer rows.Close()


@@ 34,8 37,10 @@ func Handler(app *app.Application) http.HandlerFunc {
			transactions = append(transactions, t)
		}
		if err = rows.Err(); err != nil {
			//TODO: no Fatal
			log.Fatal(err)
		}
		err = app.Template.ExecuteTemplate(w, "index.html", &Dashboard{transactions})

		err = app.Template.ExecuteTemplate(w, "dashboard.html", Dashboard{transactions, nav.New("dashboard")})
	})
}

R services/signin/handler.go => pages/signin/handler.go +11 -11
@@ 4,16 4,16 @@ import (
	"log"
	"net/http"

	"git.sr.ht/~inferiormartin/shishutsu/alert"
	"git.sr.ht/~inferiormartin/shishutsu/app"
	"git.sr.ht/~inferiormartin/shishutsu/services/notification"
	"github.com/google/uuid"
	"golang.org/x/crypto/bcrypt"
)

type SignIn struct {
	Email        string
	Success      bool
	Notification *notification.Notification
	Email   string
	Success bool
	Alert   *alert.Alert
}

func Handler(app *app.Application) http.HandlerFunc {


@@ 39,16 39,16 @@ func Handler(app *app.Application) http.HandlerFunc {
			var hash string
			row := db.QueryRow(`SELECT id, password FROM account WHERE email = $1`, email)
			if err := row.Scan(&id, &hash); err != nil {
				err = app.Template.ExecuteTemplate(w, "sign-in.html", SignIn{email, false, &notification.Notification{
				err = app.Template.ExecuteTemplate(w, "sign-in.html", SignIn{email, false, &alert.Alert{
					"Incorrect email or password.",
					notification.NotificationError,
					alert.Error,
				}})
				return
			}
			if err = bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)); err != nil {
				err = app.Template.ExecuteTemplate(w, "sign-in.html", SignIn{email, false, &notification.Notification{
				err = app.Template.ExecuteTemplate(w, "sign-in.html", SignIn{email, false, &alert.Alert{
					"Incorrect email or password.",
					notification.NotificationError,
					alert.Error,
				}})
				return
			}


@@ 59,9 59,9 @@ func Handler(app *app.Application) http.HandlerFunc {

			if err != nil {
				log.Println(err)
				err = app.Template.ExecuteTemplate(w, "sign-in.html", SignIn{email, false, &notification.Notification{
				err = app.Template.ExecuteTemplate(w, "sign-in.html", SignIn{email, false, &alert.Alert{
					"An error occured while attempting to sign you in.",
					notification.NotificationError,
					alert.Error,
				}})
				return
			}


@@ 71,7 71,7 @@ func Handler(app *app.Application) http.HandlerFunc {
				Value: session,
			})

			http.Redirect(w, r, "/", http.StatusFound)
			http.Redirect(w, r, "/dashboard", http.StatusFound)
		}
	}
}

D services/notification/notification.go => services/notification/notification.go +0 -15
@@ 1,15 0,0 @@
package notification

type NotificationType string

const (
	NotificationNotice  NotificationType = "notice"
	NotificationWarning                  = "warning"
	NotificationError                    = "error"
	NotificationOK                       = "ok"
)

type Notification struct {
	Text string
	Type NotificationType
}

M static/account.svg => static/account.svg +1 -1
@@ 1,1 1,1 @@
<svg width="100%" height="100%" viewBox="0 0 24 24" role="img" xmlns="http://www.w3.org/2000/svg" aria-labelledby="personIconTitle" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none" color="#000000"> <title id="personIconTitle">Person</title> <path d="M4,20 C4,17 8,17 10,15 C11,14 8,14 8,9 C8,5.667 9.333,4 12,4 C14.667,4 16,5.667 16,9 C16,14 13,14 14,15 C16,17 20,17 20,20"/> </svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-user"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
\ No newline at end of file

M static/admin.svg => static/admin.svg +1 -4
@@ 1,4 1,1 @@
<svg width="100%" height="100%" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
  <path fill-rule="evenodd" clip-rule="evenodd" d="M9.253 2.782l-1.11-.273a4.021 4.021 0 00-.593 1.025l.79.825a3.005 3.005 0 000 1.282l-.79.825c.147.372.348.717.593 1.025l1.11-.273c.315.288.693.509 1.109.641l.318 1.097a3.999 3.999 0 001.185 0l.318-1.097a2.985 2.985 0 001.11-.641l1.11.273c.245-.308.446-.653.593-1.025l-.792-.825a3.005 3.005 0 000-1.282l.792-.825a4.021 4.021 0 00-.593-1.025l-1.11.273a2.985 2.985 0 00-1.11-.641l-.318-1.097a3.985 3.985 0 00-1.185 0l-.318 1.097a2.982 2.982 0 00-1.11.641zM12.273 5a1 1 0 11-2 0 1 1 0 012 0z" fill="#000"/>
  <path fill-rule="evenodd" clip-rule="evenodd" d="M6.915 6.264A4.54 4.54 0 005.7 6.04l-.445.918a3.588 3.588 0 00-1.364.37l-.85-.564c-.345.226-.66.499-.932.81l.44.92c-.27.391-.462.833-.56 1.298l-.971.312a4.51 4.51 0 00.051 1.233l.995.23c.136.456.363.88.665 1.248l-.362.954c.298.287.632.532.996.728l.8-.633a3.58 3.58 0 001.39.257l.521.877c.409-.053.81-.162 1.19-.324l.004-1.02a3.58 3.58 0 001.067-.927l1.01.14c.215-.353.38-.735.49-1.133l-.796-.639a3.61 3.61 0 00-.058-1.413l.739-.701a4.508 4.508 0 00-.581-1.09l-.995.224a3.588 3.588 0 00-1.141-.834l-.088-1.017zm-1.15 2.82a1.477 1.477 0 11-.538 2.904 1.477 1.477 0 01.539-2.905z" fill="#000"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-settings"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
\ No newline at end of file

A static/card.svg => static/card.svg +1 -0
@@ 0,0 1,1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-credit-card"><rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect><line x1="1" y1="10" x2="23" y2="10"></line></svg>
\ No newline at end of file

A static/cards.css => static/cards.css +7 -0
@@ 0,0 1,7 @@
.card {
    height: 100px;
    width: 200px;
    border-radius: 5px;
    background-color: #FF8C00;
    padding: 5px;
}

M static/dashboard.svg => static/dashboard.svg +1 -58
@@ 1,58 1,1 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
	 viewBox="0 0 480.822 480.822" style="enable-background:new 0 0 480.822 480.822;" xml:space="preserve">
<path d="M477.929,387.31l-69.874-95.563c-15.397-21.059-38.438-35.084-63.388-39.082c16.204-24.051,24.898-52.333,24.898-82.14
	c0-81.387-66.213-147.6-147.6-147.6c-81.387,0-147.6,66.213-147.6,147.6c0,28.511,8.148,56.186,23.564,80.032
	c13.469,20.835,31.868,37.851,53.524,49.637c4.003,16.208,18.601,28.373,35.779,28.755l88.164,1.959
	c2.29,0.051,3.729,1.29,4.383,2.024s1.72,2.307,1.507,4.588c-0.289,3.095-2.926,5.489-6.025,5.489c-0.031,0-0.063-0.001-0.094-0.001
	l-101.287-1.572c-5.584-0.087-11.035-2.089-15.348-5.637l-89.688-73.776c-17.121-14.085-42.383-12.424-57.514,3.779
	c-7.698,8.244-11.712,18.991-11.303,30.263s5.193,21.7,13.471,29.363l114.018,105.562c14.104,13.057,32.449,20.601,51.658,21.242
	l169.337,5.653c0.171,0.006,0.34,0.009,0.51,0.009c8.052,0,14.711-6.391,14.982-14.499c0.276-8.28-6.211-15.216-14.491-15.492
	l-169.336-5.653c-12.002-0.401-23.466-5.115-32.278-13.273L33.881,303.416c-2.379-2.202-3.754-5.199-3.872-8.439
	c-0.118-3.239,1.036-6.328,3.249-8.697c4.348-4.656,11.609-5.133,16.529-1.087l89.688,73.777
	c9.539,7.846,21.593,12.272,33.94,12.464l101.287,1.572c0.187,0.003,0.371,0.005,0.558,0.005
	c18.464-0.002,34.179-14.265,35.898-32.707c0.924-9.925-2.348-19.885-8.979-27.327s-16.15-11.838-26.116-12.06l-88.164-1.959
	c-3.98-0.089-7.286-3.271-7.524-7.245c-0.172-2.864,1.208-4.805,2.045-5.707s2.67-2.423,5.538-2.465l140.835-2.047
	c0.72-0.01,1.437-0.008,2.149,0.001c20.547,0.309,40.322,10.761,52.897,27.96l69.874,95.563c4.889,6.687,14.275,8.146,20.962,3.255
	C481.362,403.382,482.819,393.998,477.929,387.31z M187.521,253.545c-10.246,0.148-20.12,4.542-27.09,12.052
	c-1.001,1.079-1.925,2.217-2.786,3.398c-13.745-8.987-25.513-20.787-34.522-34.722c-12.272-18.983-18.758-41.026-18.758-63.746
	c0-64.845,52.755-117.6,117.6-117.6s117.6,52.755,117.6,117.6c0,30.667-11.543,59.312-32.622,81.283l-74.981,1.09v-5.448
	c19.482-4.542,34.043-22.039,34.043-42.884c0-20.846-14.561-38.343-34.043-42.885v-47.058c8.278,3.803,14.043,12.167,14.043,21.856
	c0,5.522,4.477,10,10,10s10-4.478,10-10c0-20.845-14.561-38.342-34.043-42.884v-6.932c0-5.522-4.477-10-10-10s-10,4.478-10,10v6.932
	c-19.482,4.542-34.043,22.039-34.043,42.884s14.561,38.341,34.043,42.884v47.059c-8.278-3.802-14.043-12.166-14.043-21.856
	c0-5.522-4.477-10-10-10s-10,4.478-10,10c0,20.845,14.561,38.341,34.043,42.884v5.738L187.521,253.545z M211.962,158.336
	c-8.278-3.802-14.043-12.166-14.043-21.856c0-9.689,5.765-18.053,14.043-21.856V158.336z M231.962,182.71
	c8.278,3.803,14.043,12.167,14.043,21.857s-5.765,18.054-14.043,21.856V182.71z"/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
\ No newline at end of file

M static/main.css => static/main.css +77 -47
@@ 2,6 2,10 @@
    font-family: sans-serif;
}

body {
    background-color: #F5F5F5;
}

.styled-table {
    border-collapse: collapse;
    font-size: 0.9em;


@@ 11,7 15,7 @@
}

.styled-table thead tr {
    background-color: #009879;
    background-color: #1F75FE;
    color: #ffffff;
    text-align: left;
}


@@ 30,68 34,94 @@
}

.styled-table tbody tr:last-of-type {
    border-bottom: 2px solid #009879;
    border-bottom: 2px solid #1F75FE;
}

.dashboard-svg {
    height: 50px;
    width: 50px;
    background-color: #111;
    mask: url(/static/dashboard.svg) no-repeat center;
    margin: auto;
.nav {
    height: 100%;
    width: auto;

    position: fixed;
    top: 0;
    left: 0;

    background-color: #FFFFFF;
    box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);


    display: flex;
    flex-direction: column;
}

.account-svg {
    height: 50px;
    width: 50px;
    background-color: #111;
    mask: url(/static/account.svg) no-repeat center;
    margin: auto;
.nav-item {
    padding: 15px 40px;
    margin: 5px;
}

.admin-svg {
    height: 50px;
    width: 50px;
    background-color: #111;
    mask: url(/static/admin.svg) no-repeat center;
    margin: auto;
.nav-item.active {
    color: #ffffff;
    background-color: #1F75FE;
    border-radius: 5px;
    box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
}

.nav-url {
    display: flex;
    flex-direction: row;
    align-items: center;

.sidenav {
  height: 100%;
  width: 130px;
  position: fixed;
  z-index: 1;
  top: 0;
  left: 0;
  background-color: #F5F5F5;
  overflow-x: hidden;
  padding: 0px;
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
  display: flex;
  flex-direction: column;
  justify-content: center;
    font-weight: bold;
    color: #808080;
    text-decoration: none;
}

.sidenav a {
    padding: 15%;
    margin-top: 30%;
    margin-bottom: 30%;
.nav-url.active {
    color: #ffffff;
}

.sidenav a:hover {
    border-left: 4px solid #009879;
    border-right: 4px solid #009879;
.svg {
    width: 24px;
    height: 24px;
    background-color: #808080;
    margin-right: 10px;
} 

.svg.active {
    background-color: #ffffff;
}

.sidenav a:hover .admin-svg,
.sidenav a:hover .dashboard-svg,
.sidenav a:hover .account-svg {
  background-color: #009879;

.center {
    text-align: center
}

#title {
    margin-bottom: 0;
}

.dashboard-item h4 {
    margin: 10px;
    color: #808080;
}

.dashboard-item h2 {
    color: #FFE314;
    text-align: center;
}

.main {
  margin-left: 130px;
  padding: 0px 10px;
    margin-left: 200px;
    padding: 0px 10px;
    display: flex;
    flex-direction: row;
}

.dashboard-item {
    background-color: white;
    width: 300px;
    height: 150px;

    margin: 5px;

    box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
}

R templates/notification.html => templates/alert.html +0 -0
R templates/transactions.html => templates/card-transactions.html +5 -5
@@ 1,20 1,20 @@
<table class="styled-table">
    <thead>                                   
    <thead>
        <tr>
            <th>IBAN</th>   
            <th>IBAN</th>
            <th>Amount</th>
            <th>Date</th>
            <th>Description</th>
        </tr>
    </thead>                        
    </thead>
    <tbody>
        {{ range .Transactions }}
        <tr> 
        <tr>
            <td>{{ .IBAN }}</td>
            <td>€ {{ .ToMoney }}</td>
            <td>{{ .TransactionDate.Format "02/01/2006" }}</td>
            <td>{{ .Description }}</td>
        </tr>
        {{ end }}                          
        {{ end }}
    </tbody>
</table>

R templates/index.html => templates/cards.html +7 -2
@@ 2,11 2,16 @@
<html>
    <head>
        <link rel="stylesheet" href="/static/main.css" />
        <link rel="stylesheet" href="/static/cards.css" />
    </head>
    <body>
        {{ template "sidebar.html" . }}
        {{ template "nav.html" .Nav }}
        <div class="main">
            {{ template "transactions.html" . }}
            <div class="card">
                <span>John Doe</span>
                <br>
                <span>BEXX XXXX XXXX XXXX</span>
            </div>
        </div>
    </body>
</html> 

A templates/dashboard.html => templates/dashboard.html +21 -0
@@ 0,0 1,21 @@
<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" href="/static/main.css" />
        <title>shishutsu dashboard</title>
    </head>
    <body>
        {{ template "nav.html" .Nav }}
        <div class="main">
            <!--{{ template "card-transactions.html" . }}-->
            <div class="dashboard-item">
                <h4>Spending | Food</h4>
                <h2>€ 184.00</h2>
            </div>
            <div class="dashboard-item"></div>
            
            <div class="dashboard-item"></div>
        </div>
    </body>
</html> 


A templates/nav.html => templates/nav.html +21 -0
@@ 0,0 1,21 @@
<nav class="nav">
    <h1 id="title" class="center">支出</h1>
    <small class="center">shishutsu</small>
    {{ range $name, $item := .Items }}
    {{ if $item.Active }}    
    <div class="nav-item active">
        <a class="nav-url active" href="{{ $item.Path }}"> 
            <div class="svg active" style="mask: url(/static/{{$item.Svg}}) no-repeat center;"></div>
            {{ $item.Label }}
        </a>
    </div>
    {{ else }}
    <div class="nav-item">
        <a class="nav-url" href="{{ $item.Path }}">
            <div class="svg" style="mask: url(/static/{{$item.Svg}}) no-repeat center;"></div>
            {{ $item.Label }}
        </a>
    </div>
    {{ end }}
    {{ end }}
</nav>

D templates/sidebar.html => templates/sidebar.html +0 -5
@@ 1,5 0,0 @@
<div class="sidenav">
    <a href="/"><div class="dashboard-svg"></div></a>
    <a href="#"><div class="account-svg"></div></a>
    <a href="#"><div class="admin-svg"></div></a>
</div>

M templates/sign-in.html => templates/sign-in.html +1 -1
@@ 8,7 8,7 @@
            <h1>shishutsu</h1>
            {{ if . }}
                {{ if not .Success }}
                    {{ template "notification.html" .Notification }}
                    {{ template "alert.html" .Alert }}
                {{ end }}
                <div style="margin-bottom: 15px;"></div>
            {{ end }}