Add dashboard page with login header

A lot of things here don't work yet, but this is the basic layout.
This commit is contained in:
Eli Ribble 2025-11-05 17:49:19 +00:00
parent 4d55a391c9
commit ac736cced1
No known key found for this signature in database
6 changed files with 207 additions and 8 deletions

4
go.mod
View file

@ -9,11 +9,14 @@ require (
github.com/go-chi/chi/v5 v5.2.3 github.com/go-chi/chi/v5 v5.2.3
github.com/go-webauthn/webauthn v0.14.0 github.com/go-webauthn/webauthn v0.14.0
github.com/golang-jwt/jwt/v5 v5.3.0 github.com/golang-jwt/jwt/v5 v5.3.0
github.com/google/go-cmp v0.7.0
github.com/jackc/pgx/v5 v5.7.6 github.com/jackc/pgx/v5 v5.7.6
github.com/jaswdr/faker/v2 v2.8.1 github.com/jaswdr/faker/v2 v2.8.1
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/pressly/goose/v3 v3.26.0 github.com/pressly/goose/v3 v3.26.0
github.com/riverqueue/river/rivershared v0.26.0
github.com/stephenafamo/bob v0.41.1 github.com/stephenafamo/bob v0.41.1
github.com/stephenafamo/scan v0.7.0
github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07
golang.org/x/crypto v0.42.0 golang.org/x/crypto v0.42.0
) )
@ -31,7 +34,6 @@ require (
github.com/pganalyze/pg_query_go/v6 v6.1.0 // indirect github.com/pganalyze/pg_query_go/v6 v6.1.0 // indirect
github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 // indirect github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 // indirect
github.com/sethvargo/go-retry v0.3.0 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect
github.com/stephenafamo/scan v0.7.0 // indirect
github.com/tetratelabs/wazero v1.9.0 // indirect github.com/tetratelabs/wazero v1.9.0 // indirect
github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 // indirect github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect

2
go.sum
View file

@ -135,6 +135,8 @@ github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 h1:wSmWgpuccqS2IOfmY
github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494/go.mod h1:yipyliwI08eQ6XwDm1fEwKPdF/xdbkiHtrU+1Hg+vc4= github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494/go.mod h1:yipyliwI08eQ6XwDm1fEwKPdF/xdbkiHtrU+1Hg+vc4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/riverqueue/river/rivershared v0.26.0 h1:tsMvxTIdG58GoYXd3788DwjNq87Y7CcfRlV7TAzeuhw=
github.com/riverqueue/river/rivershared v0.26.0/go.mod h1:/BEdbdGEqfcFP9FtChwK81e2AWF8e82RC6z5mwQ3y1g=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=

37
html.go
View file

@ -5,16 +5,20 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"io" "io"
"log/slog"
"os" "os"
"strings"
"github.com/riverqueue/river/rivershared/util/slogutil"
"github.com/Gleipnir-Technology/nidus-sync/models" "github.com/Gleipnir-Technology/nidus-sync/models"
) )
var ( var (
dashboard = newBuiltTemplate("dashboard", "base") dashboard = newBuiltTemplate("dashboard", "authenticated")
signin = newBuiltTemplate("signin", "base") signin = newBuiltTemplate("signin", "base")
signup = newBuiltTemplate("signup", "base") signup = newBuiltTemplate("signup", "base")
) )
var components = [ ... ]string{"header"}
type BuiltTemplate struct { type BuiltTemplate struct {
files []string files []string
@ -26,13 +30,17 @@ type Link struct {
Title string Title string
} }
type ContentDashboard struct { type ContentDashboard struct {
BabbleLinks []Link User User
Username string
} }
type ContentSignin struct { type ContentSignin struct {
InvalidCredentials bool InvalidCredentials bool
} }
type ContentSignup struct { } type ContentSignup struct { }
type User struct {
DisplayName string
Initials string
Username string
}
func (bt *BuiltTemplate) ExecuteTemplate(w io.Writer, data any) error { func (bt *BuiltTemplate) ExecuteTemplate(w io.Writer, data any) error {
name := bt.files[0] + ".html" name := bt.files[0] + ".html"
@ -51,9 +59,26 @@ func (bt *BuiltTemplate) ExecuteTemplate(w io.Writer, data any) error {
} }
} }
func extractInitials(name string) string {
parts := strings.Fields(name)
var initials strings.Builder
for _, part := range parts {
if len(part) > 0 {
initials.WriteString(strings.ToUpper(string(part[0])))
}
}
return initials.String()
}
func htmlDashboard(w io.Writer, user *models.User) error { func htmlDashboard(w io.Writer, user *models.User) error {
data := ContentDashboard{ data := ContentDashboard{
Username: user.Username, User: User{
DisplayName: user.DisplayName,
Initials: extractInitials(user.DisplayName),
Username: user.Username,
},
} }
return dashboard.ExecuteTemplate(w, data) return dashboard.ExecuteTemplate(w, data)
} }
@ -108,6 +133,10 @@ func parseFromDisk(files []string) (*template.Template, error) {
paths = append(paths, "templates/"+f+".html") paths = append(paths, "templates/"+f+".html")
} }
name := files[0] + ".html" name := files[0] + ".html"
for _, f := range components {
paths = append(paths, "templates/components/"+f+".html")
}
slog.Info("Rendering templates from disk", slog.Any("paths", slogutil.SliceString(paths)))
templ, err := template.New(name).Funcs(funcMap).ParseFiles(paths...) templ, err := template.New(name).Funcs(funcMap).ParseFiles(paths...)
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to parse %s: %v", paths, err) return nil, fmt.Errorf("Failed to parse %s: %v", paths, err)

View file

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{template "title" .}} - Nidus Sync</title>
<link href="/static/vendor/css/bootstrap.min.css" rel="stylesheet">
<style>
{{template "style" .}}
</style>
</head>
<body>
{{if .User}}
{{template "header" .User}}
{{end}}
{{template "content" .}}
<script src="/static/vendor/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View file

@ -0,0 +1,50 @@
{{define "header"}}
<header class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
<div class="container">
<!-- Logo -->
<a class="navbar-brand" href="dashboard.html">
<div class="logo-placeholder" style="width: 100px; height: 40px; background-color: #e9ecef; display: flex; align-items: center; justify-content: center; border-radius: 4px;">
<span class="text-muted small">Your Logo</span>
</div>
</a>
<!-- Toggle Button for Mobile -->
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<!-- Nav Items -->
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="dashboard.html">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="projects.html">Projects</a>
</li>
<li class="nav-item">
<a class="nav-link" href="maps.html">Maps</a>
</li>
</ul>
<!-- User Info & Logout -->
<div class="d-flex align-items-center">
<div class="dropdown">
<a class="text-decoration-none dropdown-toggle d-flex align-items-center" href="#" role="button" id="userDropdown" data-bs-toggle="dropdown" aria-expanded="false">
<div class="avatar me-2 bg-primary rounded-circle d-flex align-items-center justify-content-center" style="width: 36px; height: 36px;">
<span class="text-white">{{ .Initials }}</span>
</div>
<span class="me-2">{{ .DisplayName }}</span>
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown">
<li><a class="dropdown-item" href="profile.html">Profile</a></li>
<li><a class="dropdown-item" href="settings.html">Settings</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="login.html">Logout</a></li>
</ul>
</div>
</div>
</div>
</div>
</header>
{{end}}

View file

@ -1,9 +1,106 @@
{{template "base.html" .}} {{template "authenticated.html" .}}
{{define "title"}}Dash{{end}} {{define "title"}}Dash{{end}}
{{define "style"}} {{define "style"}}
.connect-container {
max-width: 800px;
margin: 0 auto;
}
.connect-box {
box-shadow: 0 0 15px rgba(0,0,0,0.1);
border-radius: 10px;
padding: 40px;
background-color: #fff;
}
.connect-header {
margin-bottom: 25px;
text-align: center;
}
.logo-area {
text-align: center;
margin-bottom: 30px;
}
.logo-placeholder {
width: 120px;
height: 60px;
background-color: #e9ecef;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
}
.steps-container {
margin: 30px 0;
}
.step {
margin-bottom: 20px;
padding: 15px;
border-left: 3px solid #0d6efd;
background-color: #f8f9fa;
}
.connect-btn {
margin-top: 30px;
}
{{end}} {{end}}
{{define "content"}} {{define "content"}}
<h1>Hey {{ .Username }}</h1> <div class="container min-vh-100 d-flex align-items-center justify-content-center py-5">
<p>At this point, pretend I'm showing you the result of some ArcGIS data.</p> <div class="connect-container">
<!-- Logo Area -->
<div class="logo-area">
<div class="logo-placeholder">
<span class="text-muted">Your Logo</span>
</div>
</div>
<div class="connect-box">
<div class="connect-header">
<h1>Connect Your ArcGIS Account</h1>
<p class="text-muted">Link your data to get started</p>
</div>
<div class="connect-content">
<p>To provide you with the best experience, we need to connect to your ArcGIS account. This allows us to securely access and visualize your spatial data within our platform.</p>
<div class="steps-container">
<h4>What to expect:</h4>
<div class="step">
<h5>1. Secure Authentication</h5>
<p>When you click the "Connect to ArcGIS" button below, you'll be redirected to the official ArcGIS login page. This connection is secure and uses OAuth 2.0 protocol.</p>
</div>
<div class="step">
<h5>2. Grant Permissions</h5>
<p>After logging in with your ArcGIS credentials, you'll be asked to approve permissions for our application to access your data. We only request access to what's needed for the platform to function.</p>
</div>
<div class="step">
<h5>3. Return to Platform</h5>
<p>Once authentication is complete, you'll be automatically redirected back to our platform where your data will be available to work with.</p>
</div>
</div>
<div class="alert alert-info">
<strong>Note:</strong> You'll need an active ArcGIS Online account or ArcGIS Enterprise account to proceed. If you don't have one, you can <a href="https://www.arcgis.com/home/signin.html" target="_blank">create an ArcGIS account here</a>.
</div>
<p>By connecting your ArcGIS account, you'll be able to:</p>
<ul>
<li>Access and visualize your spatial data</li>
<li>Perform advanced analysis using our integrated tools</li>
<li>Share results with team members securely</li>
<li>Keep your data synchronized across platforms</li>
</ul>
<div class="text-center connect-btn">
<button type="button" class="btn btn-primary btn-lg">
Connect to ArcGIS
</button>
<p class="mt-2 text-muted"><small>You can disconnect your account at any time in settings</small></p>
</div>
</div>
</div>
</div>
</div>
{{end}} {{end}}