mirror of
https://github.com/dnote/dnote
synced 2026-03-14 22:45:50 +01:00
Implement basic settings layout
This commit is contained in:
parent
348bf8398c
commit
7af65c1eee
13 changed files with 431 additions and 49 deletions
139
pkg/server/assets/styles/src/_settings.scss
Normal file
139
pkg/server/assets/styles/src/_settings.scss
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
@import './theme';
|
||||
@import './font';
|
||||
|
||||
.settings-page {
|
||||
.sidebar {
|
||||
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);
|
||||
background: white;
|
||||
margin-bottom: rem(20px);
|
||||
margin-top: rem(20px);
|
||||
|
||||
@include breakpoint(lg) {
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-item {
|
||||
display: block;
|
||||
padding: rem(12px) rem(16px);
|
||||
border-left: 4px solid transparent;
|
||||
@include font-size('regular');
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
background: $light;
|
||||
}
|
||||
|
||||
&.active {
|
||||
font-weight: 600;
|
||||
border-left-color: $first;
|
||||
}
|
||||
}
|
||||
|
||||
.setting-section-wrapper {
|
||||
.header {
|
||||
@include breakpoint(lg) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.setting-section {
|
||||
margin-top: rem(24px);
|
||||
background: white;
|
||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.14);
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
@include font-size('regular');
|
||||
font-weight: 600;
|
||||
padding-bottom: rem(4px);
|
||||
background: $light;
|
||||
padding: rem(16px) rem(20px);
|
||||
}
|
||||
.section-content {
|
||||
margin-top: rem(20px);
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: rem(18px);
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.setting-row {
|
||||
padding: rem(16px) rem(20px);
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid $border-color;
|
||||
}
|
||||
|
||||
.setting-row-summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
@include breakpoint(md) {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.setting-row-main {
|
||||
padding-top: rem(24px);
|
||||
}
|
||||
|
||||
.setting-name {
|
||||
font-weight: 400;
|
||||
@include font-size('regular');
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.setting-desc {
|
||||
margin-bottom: 0;
|
||||
@include font-size('small');
|
||||
color: $gray;
|
||||
}
|
||||
.setting-action {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@include breakpoint(md) {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.setting-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
word-break: break-all;
|
||||
|
||||
@include breakpoint(md) {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.setting-edit {
|
||||
color: $link;
|
||||
padding: 0;
|
||||
|
||||
&:hover {
|
||||
color: $link-hover;
|
||||
}
|
||||
@include breakpoint(md) {
|
||||
margin-left: rem(16px);
|
||||
}
|
||||
}
|
||||
|
||||
.input-row {
|
||||
& ~ .input-row,
|
||||
.input-row {
|
||||
margin-top: rem(12px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,7 @@
|
|||
@import './home';
|
||||
@import './note';
|
||||
@import './books';
|
||||
@import './settings';
|
||||
@import './header';
|
||||
@import './global';
|
||||
|
||||
|
|
|
|||
|
|
@ -9,4 +9,7 @@ var (
|
|||
JSFiles = ""
|
||||
// RootURL is the root url
|
||||
RootURL = "/"
|
||||
// Standalone reprsents whether the build is for on-premises. It is a string
|
||||
// rather than a boolean, so that it can be overridden during compile time.
|
||||
Standalone = "false"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -30,12 +30,15 @@ func NewWebRoutes(a *app.App, c *Controllers) []Route {
|
|||
|
||||
ret := []Route{
|
||||
{"GET", "/", mw.Auth(a, c.Users.Settings, redirectGuest), true},
|
||||
{"GET", "/about", mw.Auth(a, c.Users.About, redirectGuest), true},
|
||||
{"GET", "/login", mw.GuestOnly(a, c.Users.NewLogin), true},
|
||||
{"POST", "/login", mw.GuestOnly(a, c.Users.Login), true},
|
||||
{"POST", "/logout", c.Users.Logout, true},
|
||||
|
||||
{"GET", "/password-reset", c.Users.PasswordResetView.ServeHTTP, true},
|
||||
{"PATCH", "/password-reset", c.Users.PasswordReset, true},
|
||||
{"PATCH", "/account/profile", c.Users.PasswordReset, true},
|
||||
{"PATCH", "/account/password", c.Users.PasswordReset, true},
|
||||
{"GET", "/password-reset/{token}", c.Users.PasswordResetConfirm, true},
|
||||
{"POST", "/reset-token", c.Users.CreateResetToken, true},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/dnote/dnote/pkg/server/app"
|
||||
"github.com/dnote/dnote/pkg/server/buildinfo"
|
||||
"github.com/dnote/dnote/pkg/server/database"
|
||||
"github.com/dnote/dnote/pkg/server/helpers"
|
||||
"github.com/dnote/dnote/pkg/server/log"
|
||||
|
|
@ -50,9 +51,13 @@ func NewUsers(app *app.App) *Users {
|
|||
"users/password_reset_confirm",
|
||||
),
|
||||
SettingView: views.NewView(app,
|
||||
views.Config{Title: "Settings", Layout: "base", HelperFuncs: commonHelpers, AlertInBody: true, HeaderTemplate: "navbar"},
|
||||
views.Config{Layout: "base", HelperFuncs: commonHelpers, AlertInBody: true, HeaderTemplate: "navbar"},
|
||||
"users/settings",
|
||||
),
|
||||
AboutView: views.NewView(app,
|
||||
views.Config{Title: "About", Layout: "base", HelperFuncs: commonHelpers, AlertInBody: true, HeaderTemplate: "navbar"},
|
||||
"users/settings_about",
|
||||
),
|
||||
app: app,
|
||||
}
|
||||
}
|
||||
|
|
@ -62,6 +67,7 @@ type Users struct {
|
|||
NewView *views.View
|
||||
LoginView *views.View
|
||||
SettingView *views.View
|
||||
AboutView *views.View
|
||||
PasswordResetView *views.View
|
||||
PasswordResetConfirmView *views.View
|
||||
app *app.App
|
||||
|
|
@ -431,3 +437,13 @@ func (u *Users) Settings(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
u.SettingView.Render(w, r, &vd, http.StatusOK)
|
||||
}
|
||||
|
||||
func (u *Users) About(w http.ResponseWriter, r *http.Request) {
|
||||
vd := views.Data{}
|
||||
|
||||
vd.Yield = map[string]interface{}{
|
||||
"Version": buildinfo.Version,
|
||||
}
|
||||
|
||||
u.AboutView.Render(w, r, &vd, http.StatusOK)
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -50,7 +50,7 @@
|
|||
|
||||
<ul class="list-unstyled" role="menu">
|
||||
<li role="none">
|
||||
<a class="dropdown-link" href="/settings" role="menuitem">Settings</a>
|
||||
<a class="dropdown-link" href="/" role="menuitem">Settings</a>
|
||||
</li>
|
||||
<li role="none">
|
||||
{{template "logoutForm"}}
|
||||
|
|
|
|||
23
pkg/server/views/partials/settings_sidebar.gohtml
Normal file
23
pkg/server/views/partials/settings_sidebar.gohtml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{{define "settingsSidebar"}}
|
||||
<nav class="sidebar">
|
||||
<ul class="list-unstyled">
|
||||
<li>
|
||||
<a class="sidebar-item {{if eq .CurrentPath "/"}}active{{end}}" href="/">Account</a>
|
||||
</li>
|
||||
|
||||
{{if ne .Standalone "true"}}
|
||||
<li>
|
||||
<a class="sidebar-item" href="/subscriptions/manage">
|
||||
Billing
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
|
||||
<li>
|
||||
<a class="sidebar-item {{if eq .CurrentPath "/about"}}active{{end}}" href="/about">
|
||||
About
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{{end}}
|
||||
|
|
@ -1,50 +1,180 @@
|
|||
{{define "yield"}}
|
||||
<div class="container page page-mobile-full">
|
||||
<div class="page-header">
|
||||
<h1 class="page-heading">Settings</h1>
|
||||
</div>
|
||||
<div class="page page-mobile-full settings-page">
|
||||
<div class="container mobile-fw">
|
||||
<div class="page-header">
|
||||
<h1 class="page-heading">Settings</h1>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-12 col-lg-3">
|
||||
{{template "settingsSidebar" .}}
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-12 col-lg-9">
|
||||
<div class="setting-section-wrapper">
|
||||
<section class="setting-section">
|
||||
<h2 class="section-heading">Email</h2>
|
||||
|
||||
<div class="setting-row">
|
||||
<div class="setting-row-summary">
|
||||
<div>
|
||||
<h3 class="setting-name">Email</h3>
|
||||
</div>
|
||||
|
||||
<div class="setting-right">
|
||||
{{.Account.Email.String}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-row">
|
||||
<div class="setting-row-summary">
|
||||
<div>
|
||||
<h3 class="setting-name">Email Verified</h3>
|
||||
</div>
|
||||
|
||||
<div class="setting-right">
|
||||
{{if .Account.EmailVerified}}
|
||||
Yes
|
||||
{{else}}
|
||||
No
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-row">
|
||||
<div class="setting-row-summary">
|
||||
<div>
|
||||
<h3 class="setting-name">Change Email</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-row-main">
|
||||
{{template "emailForm" .}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="setting-section">
|
||||
<h2 class="section-heading">Password</h2>
|
||||
|
||||
<div class="setting-row">
|
||||
<div class="setting-row-summary">
|
||||
<div>
|
||||
<h3 class="setting-name">Change Password</h3>
|
||||
<p class="setting-desc">
|
||||
Set a unique password to protect your data.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-row-main">
|
||||
{{template "passwordChangeForm" .}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "settingsSidebar"}}
|
||||
<nav class="wrapper">
|
||||
<ul class="list-unstyled">
|
||||
<li>
|
||||
<a href="/settings"></a>
|
||||
<NavLink
|
||||
class={styles.item}
|
||||
activeClassName={styles.active}
|
||||
to={getSettingsPath(SettingSections.account)}
|
||||
>
|
||||
Account
|
||||
</NavLink>
|
||||
</li>
|
||||
{__STANDALONE__ ? null : (
|
||||
<li>
|
||||
<a class={styles.item} href="/subscriptions/manage">
|
||||
Billing
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
<li>
|
||||
<NavLink
|
||||
class={styles.item}
|
||||
activeClassName={styles.active}
|
||||
to={getSettingsPath(SettingSections.notifications)}
|
||||
>
|
||||
Notifications
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink
|
||||
class={styles.item}
|
||||
activeClassName={styles.active}
|
||||
to={getSettingsPath(SettingSections.about)}
|
||||
>
|
||||
About
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{{define "emailForm"}}
|
||||
<form action="/account/profile">
|
||||
<input type="password" style="display: none;" readonly />
|
||||
|
||||
<div class="input-row">
|
||||
<label class="input-label" for="email-form-email-input">
|
||||
New email
|
||||
</label>
|
||||
|
||||
<input
|
||||
id="email-form-email-input"
|
||||
type="email"
|
||||
placeholder="your@email.com"
|
||||
class="form-control"
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-row">
|
||||
<label class="input-label" for="email-form-password-input">
|
||||
Current password
|
||||
</label>
|
||||
|
||||
<input
|
||||
id="email-form-password-input"
|
||||
type="password"
|
||||
placeholder="********"
|
||||
class="form-control"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button
|
||||
class="button button-first button-normal"
|
||||
type="submit"
|
||||
>
|
||||
Update email
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{{end}}
|
||||
|
||||
{{define "passwordChangeForm"}}
|
||||
<form action="/account/password">
|
||||
<input type="password" style="display: none;" readOnly />
|
||||
|
||||
<div class="input-row">
|
||||
<label class="input-label" for="old-password-input">
|
||||
Current password
|
||||
</label>
|
||||
<input
|
||||
id="old-password-input"
|
||||
type="password"
|
||||
placeholder="********"
|
||||
class="form-control"
|
||||
autocomplete="false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-row">
|
||||
<label class="input-label" for="new-password-input">
|
||||
New Password
|
||||
</label>
|
||||
<input
|
||||
id="new-password-input"
|
||||
type="password"
|
||||
placeholder="********"
|
||||
class="form-control"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-row">
|
||||
<label
|
||||
class="input-label"
|
||||
for="new-password-confirmation-input"
|
||||
>
|
||||
New Password Confirmation
|
||||
</label>
|
||||
<input
|
||||
id="new-password-confirmation-input"
|
||||
type="password"
|
||||
placeholder="********"
|
||||
class="form-control"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button
|
||||
class="button button-first button-normal"
|
||||
type="submit"
|
||||
>
|
||||
Update password
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{{end}}
|
||||
|
|
|
|||
57
pkg/server/views/users/settings_about.gohtml
Normal file
57
pkg/server/views/users/settings_about.gohtml
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{{define "yield"}}
|
||||
<div class="page page-mobile-full settings-page">
|
||||
<div class="container mobile-fw">
|
||||
<div class="page-header">
|
||||
<h1 class="page-heading">Settings</h1>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-12 col-lg-3">
|
||||
{{template "settingsSidebar" .}}
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-12 col-lg-9">
|
||||
<div class="setting-section-wrapper">
|
||||
<section class="setting-section">
|
||||
<h2 class="section-heading">Software</h2>
|
||||
|
||||
<div class="setting-row">
|
||||
<div class="setting-row-summary">
|
||||
<div>
|
||||
<h3 class="setting-name">Version</h3>
|
||||
</div>
|
||||
|
||||
<div class="setting-right">
|
||||
{{.Version}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if ne .Standalone "true"}}
|
||||
<div class="setting-row">
|
||||
<div class="setting-row-summary">
|
||||
<div>
|
||||
<h3 class="setting-name">Support</h3>
|
||||
</div>
|
||||
|
||||
<div class="setting-right">
|
||||
{{if .User.Cloud}}
|
||||
<a href="mailto:support@getdnote.com">
|
||||
support@getdnote.com
|
||||
</a>
|
||||
{{else}}
|
||||
Not eligible
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
|
||||
{{end}}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/dnote/dnote/pkg/clock"
|
||||
"github.com/dnote/dnote/pkg/server/app"
|
||||
"github.com/dnote/dnote/pkg/server/buildinfo"
|
||||
"github.com/dnote/dnote/pkg/server/context"
|
||||
"github.com/dnote/dnote/pkg/server/log"
|
||||
"github.com/gorilla/csrf"
|
||||
|
|
@ -123,6 +124,15 @@ func (v *View) Render(w http.ResponseWriter, r *http.Request, data *Data, status
|
|||
vd.User = context.User(r.Context())
|
||||
vd.Account = context.Account(r.Context())
|
||||
|
||||
// Put user data in Yield
|
||||
if vd.Yield == nil {
|
||||
vd.Yield = map[string]interface{}{}
|
||||
}
|
||||
vd.Yield["Account"] = &vd.Account
|
||||
vd.Yield["User"] = &vd.User
|
||||
vd.Yield["CurrentPath"] = r.URL.Path
|
||||
vd.Yield["Standalone"] = buildinfo.Standalone
|
||||
|
||||
var buf bytes.Buffer
|
||||
csrfField := csrf.TemplateField(r)
|
||||
tpl := v.Template.Funcs(template.FuncMap{
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ cp "$basePath"/pkg/server/assets/static/* "$basePath/pkg/server/static"
|
|||
|
||||
# run server
|
||||
moduleName="github.com/dnote/dnote"
|
||||
ldflags="-X '$moduleName/pkg/server/buildinfo.CSSFiles=main.css' -X '$moduleName/pkg/server/buildinfo.JSFiles=main.js' -X '$moduleName/pkg/server/buildinfo.Version=dev'"
|
||||
ldflags="-X '$moduleName/pkg/server/buildinfo.CSSFiles=main.css' -X '$moduleName/pkg/server/buildinfo.JSFiles=main.js' -X '$moduleName/pkg/server/buildinfo.Version=dev' -X '$moduleName/pkg/server/buildinfo.Standalone=true'"
|
||||
task="go run -ldflags \"$ldflags\" main.go start -port 3000"
|
||||
|
||||
(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue