Skip to content

Commit f3520b4

Browse files
authored
Merge pull request #135 from CommonsEngine/feat/account-settings
Add Account Settings Flow
2 parents 6fbbbbe + 6809859 commit f3520b4

File tree

16 files changed

+938
-4
lines changed

16 files changed

+938
-4
lines changed

.prettierignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ prisma/reset.sh
66
prisma/schema.prisma
77
# Ignore Handlebars templates that use conditional attrs
88
platform/src/views/layouts/spa/index.html
9+
platform/src/views/_partials/header.html
910
platform/src/views/_partials/layout/head.html
1011
platform/src/views/_partials/sidebar.html
12+
platform/src/views/_partials/modal/account-settings.html
1113
platform/src/views/register.html
1214
plugins/**
1315
tools/plugin-templates/

platform/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"private": true,
33
"name": "@sovereign/platform",
4-
"version": "0.12.1",
4+
"version": "0.12.2",
55
"description": "Core runtime and service framework for the Sovereign platform",
66
"license": "AGPL-3.0",
77
"main": "./index.cjs",

platform/src/config/env.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ const baseTemplate = Object.freeze({
8989
TOTP_RECOVERY_CODES: Number(process.env.TOTP_RECOVERY_CODES ?? 8),
9090
TOTP_RECOVERY_LENGTH: Number(process.env.TOTP_RECOVERY_LENGTH ?? 10),
9191

92+
PROFILE_PICTURE_MAX_BYTES: Number(process.env.PROFILE_PICTURE_MAX_BYTES ?? 2 * 1024 * 1024),
93+
9294
DATABASE_URL: process.env.DATABASE_URL || `file:${defaultDbPath}`,
9395

9496
DEFAULT_USER_ROLE: process.env.DEFAULT_USER_ROLE || "platform:user",

platform/src/handlers/auth/login.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,18 @@ export default async function login(req, res) {
4949
select: {
5050
id: true,
5151
name: true,
52+
firstName: true,
53+
lastName: true,
54+
pictureUrl: true,
5255
status: true,
5356
passwordHash: true,
5457
primaryEmailId: true,
58+
profile: {
59+
select: {
60+
locale: true,
61+
timezone: true,
62+
},
63+
},
5564
primaryEmail: {
5665
select: { id: true, email: true, isVerified: true },
5766
},

platform/src/middlewares/auth.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ export async function requireAuth(req, res, next) {
3535
id: session.userId,
3636
name: session.user.name,
3737
email: primaryEmail,
38+
firstName: session.user.firstName ?? null,
39+
lastName: session.user.lastName ?? null,
40+
pictureUrl: session.user.pictureUrl ?? null,
41+
locale: session.user.locale ?? null,
42+
timezone: session.user.timezone ?? null,
3843
primaryEmailId: session.user.primaryEmail?.id || session.user.primaryEmailId || null,
3944
roles,
4045
role: roles[0] || null,
@@ -69,6 +74,11 @@ export async function requireAuth(req, res, next) {
6974
req.user = {
7075
id: guest.id,
7176
name: guest.name,
77+
firstName: guest.firstName ?? null,
78+
lastName: guest.lastName ?? null,
79+
pictureUrl: guest.pictureUrl ?? null,
80+
locale: guest.locale ?? null,
81+
timezone: guest.timezone ?? null,
7282
email: guestEmail || null,
7383
primaryEmailId: guestEmailId || guest.primaryEmailId || null,
7484
roles: [],
@@ -91,6 +101,11 @@ export async function requireAuth(req, res, next) {
91101
req.user = {
92102
id: session.userId,
93103
name: session.user.name,
104+
firstName: session.user.firstName ?? null,
105+
lastName: session.user.lastName ?? null,
106+
pictureUrl: session.user.pictureUrl ?? null,
107+
locale: session.user.locale ?? null,
108+
timezone: session.user.timezone ?? null,
94109
email: primaryEmail,
95110
primaryEmailId: session.user.primaryEmail?.id || session.user.primaryEmailId || null,
96111
roles,

platform/src/middlewares/exposeGlobals.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import path from "node:path";
22

33
import * as fs from "$/utils/fs.js";
4+
import env from "$/config/env.js";
45

56
// TODO: Utilize config/head.js for these
67

@@ -67,6 +68,8 @@ const pluginAccessible = (module, pluginAccess) => {
6768
};
6869

6970
export default function exposeGlobals(req, res, next) {
71+
const config = env();
72+
7073
if (req.path.startsWith("/api/") || req.path.startsWith("/auth/")) {
7174
return next();
7275
}
@@ -111,6 +114,12 @@ export default function exposeGlobals(req, res, next) {
111114

112115
res.locals.user = {
113116
name: req.user?.name || "guest",
117+
firstName: req.user?.firstName ?? null,
118+
lastName: req.user?.lastName ?? null,
119+
email: req.user?.email ?? null,
120+
pictureUrl: req.user?.pictureUrl ?? null,
121+
locale: req.user?.locale ?? null,
122+
timezone: req.user?.timezone ?? null,
114123
primaryRole: req.user?.role || null,
115124
roles: Array.isArray(req.user?.roles) ? req.user.roles : [],
116125
};
@@ -152,5 +161,12 @@ export default function exposeGlobals(req, res, next) {
152161
res.locals.showSidebar = activeModule ? activeModule?.ui?.layout?.sidebar !== false : true;
153162
}
154163

164+
res.locals.accountSettings = {
165+
passwordMinLength: config.AUTH_PASSWORD_MIN_LENGTH,
166+
supportedLocales: Array.isArray(config.LOCALES_SUPPORTED) ? config.LOCALES_SUPPORTED : [],
167+
timezoneDefault: config.TIMEZONE_DEFAULT,
168+
profilePictureMaxBytes: Number(config.PROFILE_PICTURE_MAX_BYTES ?? 0),
169+
};
170+
155171
next();
156172
}

platform/src/public/css/sv_styled.css

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,18 @@
284284
color var(--transition-base) var(--transition-easing-standard),
285285
box-shadow var(--transition-base) var(--transition-easing-standard);
286286
}
287+
.sv-header__user-avatar[data-has-avatar="true"] svg {
288+
display: none;
289+
}
290+
.sv-header__user-avatar[data-has-avatar="false"] .sv-header__user-avatar-img {
291+
display: none;
292+
}
293+
.sv-header__user-avatar-img {
294+
width: 100%;
295+
height: 100%;
296+
object-fit: cover;
297+
display: block;
298+
}
287299
.sv-header__user-avatar > img {
288300
width: 100%;
289301
height: 100%;
@@ -832,6 +844,52 @@
832844
/* uses your .button styles; this adds spacing consistency */
833845
--no-op: 0; /* workaround for empty rule */
834846
}
847+
.account-settings__section {
848+
display: grid;
849+
gap: var(--space-s, 12px);
850+
}
851+
.account-settings__picture {
852+
display: flex;
853+
align-items: center;
854+
gap: var(--space-m, 16px);
855+
flex-wrap: wrap;
856+
}
857+
.account-settings__picture-preview {
858+
width: 80px;
859+
height: 80px;
860+
border-radius: var(--radius-round);
861+
border: 1px solid var(--color-border-primary);
862+
background: var(--color-bg-muted);
863+
display: grid;
864+
place-items: center;
865+
overflow: hidden;
866+
position: relative;
867+
}
868+
.account-settings__picture-preview[data-picture-loaded="true"]
869+
.account-settings__picture-placeholder {
870+
display: none;
871+
}
872+
.account-settings__picture-preview[data-picture-loaded="true"] img {
873+
display: block;
874+
}
875+
.account-settings__picture-preview img {
876+
width: 100%;
877+
height: 100%;
878+
object-fit: cover;
879+
display: none;
880+
}
881+
.account-settings__picture-placeholder {
882+
display: grid;
883+
place-items: center;
884+
width: 100%;
885+
height: 100%;
886+
color: var(--color-text-secondary);
887+
}
888+
.account-settings__picture-actions {
889+
display: flex;
890+
flex-direction: column;
891+
gap: var(--space-2xs, 6px);
892+
}
835893
/* Responsive */
836894
@media (max-width: 640px) {
837895
.sv-modal__dialog {

0 commit comments

Comments
 (0)