feat(account): Refactor account UI styles into reusable design tokens, apply updated styles across views and fix login submit behavior

This commit is contained in:
2025-12-01 04:08:17 +00:00
parent 9d012cd59f
commit 401d35186f
14 changed files with 457 additions and 203 deletions
+4 -5
View File
@@ -12,6 +12,7 @@ import {
} from '@design.estate/dees-element';
import { LeleAccountNavigation } from './navigation.js';
import { accountDesignTokens } from './sharedstyles.js';
import * as views from './views/index.js';
import * as accountstate from '../../states/accountstate.js';
@@ -36,15 +37,13 @@ export class IdpAccountContent extends DeesElement {
public static styles = [
cssManager.defaultStyles,
accountDesignTokens,
css`
:host {
display: block;
color: #fff;
padding-top: 10px;
padding-bottom: 10px;
height: 100%;
width: 100%;
background: ${cssManager.bdTheme('#eeeeeb', '#000000')}
background: var(--background);
}
:host([hidden]) {
display: none;
@@ -72,7 +71,7 @@ export class IdpAccountContent extends DeesElement {
height: 100vh;
overflow-y: scroll;
overscroll-behavior: contain;
transition: all 0.3s;
transition: all 0.3s ease;
opacity: 1;
}
+173 -117
View File
@@ -12,6 +12,7 @@ import {
import * as plugins from '../../plugins.js';
import * as states from '../../states/accountstate.js';
import { IdpState } from '../../states/idp.state.js';
import { accountDesignTokens } from './sharedstyles.js';
import { commitinfo } from '../../../dist_ts/00_commitinfo_data.js';
@@ -23,108 +24,122 @@ declare global {
@customElement('lele-accountnavigation')
export class LeleAccountNavigation extends DeesElement {
@property()
accessor options: { text: string; id: string }[] = [
{
id: '1',
text: 'Apps',
},
{
id: '2',
text: 'Users',
},
{
id: '3',
text: 'Activity',
},
{
id: '4',
text: 'Billing & Subscription',
},
];
constructor() {
super();
}
public static styles = [
cssManager.defaultStyles,
accountDesignTokens,
css`
:host {
display: block;
color: ${cssManager.bdTheme('#333', '#fff')};
padding: 10px;
padding-left: 0px;
background: ${cssManager.bdTheme('#eeeeeb', '#000')};
border-right: ${cssManager.bdTheme('1px solid #ccc', '1px solid #111')};
display: flex;
flex-direction: column;
background: var(--card);
border-right: 1px solid var(--border);
height: 100%;
}
:host([hidden]) {
display: none;
}
.logoArea {
padding: 20px 16px;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.logo {
font-family: 'Cal Sans';
letter-spacing: 0.0125em;
font-size: 16px;
text-align: center;
padding: 16px 0px 16px 0px;
margin: -8px -8px -16px 0px;
border-bottom: 1px solid #111111;
cursor: default;
position: relative;
z-index: 10;
font-family: 'Cal Sans', 'Geist Sans', sans-serif;
letter-spacing: -0.02em;
font-size: 20px;
font-weight: 600;
color: var(--foreground);
cursor: pointer;
transition: opacity 0.15s ease;
display: flex;
align-items: center;
gap: 8px;
}
.logo:hover {
background: ${unsafeCSS(plugins.deesCatalog.colors.dark.blue)};
opacity: 0.8;
}
.logo dees-icon {
font-size: 24px;
opacity: 0.9;
}
.navContent {
flex: 1;
overflow-y: auto;
padding-bottom: 16px;
}
.commitinfo {
flex-shrink: 0;
text-align: center;
position: absolute;
bottom: 0px;
left: 0px;
font-family: 'Intel One Mono';
width: 100%;
font-size: 12px;
padding: 8px;
border-top: ${cssManager.bdTheme('1px solid #ccc', '1px solid #333')};
color: ${cssManager.bdTheme('#666', '#ccc')};
font-family: 'Geist Mono', monospace;
font-size: 10px;
padding: 12px 16px;
border-top: 1px solid var(--border);
color: var(--muted-foreground);
opacity: 0.6;
background: var(--card);
}
.navigationGroupLabel {
width: min-content;
white-space: nowrap;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
font-size: 12px;
font-weight: 300;
border-bottom: 1px solid;
border-image: linear-gradient(to right, orange, #44444400) 1;
color: ${cssManager.bdTheme('#666', '#ccc')};
margin-bottom: 5px;
padding-top: 32px;
padding-left: 10px;
padding-bottom: 5px;
letter-spacing: 0.08em;
color: var(--muted-foreground);
padding: 20px 16px 8px;
opacity: 0.7;
}
.navigationGroupLabel:first-of-type {
padding-top: 16px;
}
.navigationOption {
border-top-right-radius: 30px;
border-bottom-right-radius: 30px;
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
margin: 2px 8px;
border-radius: 8px;
font-size: 13px;
font-weight: 500;
padding: 8px;
padding-left: 10px;
margin-bottom: 5px;
font-size: 14px;
color: var(--muted-foreground);
transition: all 0.15s ease;
cursor: pointer;
}
.navigationOption:hover {
cursor: default;
background: ${cssManager.bdTheme('#bbb', plugins.deesCatalog.colors.dark.blue)};
background: var(--muted);
color: var(--foreground);
}
.navigationOption dees-icon {
font-size: 16px;
opacity: 0.7;
flex-shrink: 0;
}
.navigationOption:hover dees-icon {
opacity: 1;
}
.divider {
height: 1px;
background: var(--border);
margin: 8px 16px;
}
dees-input-dropdown {
margin-top: 16px;
margin-bottom: 16px;
margin-left: 8px;
margin: 8px;
}
`,
];
@@ -136,57 +151,98 @@ export class LeleAccountNavigation extends DeesElement {
public render(): TemplateResult {
return html`
<style></style>
<div class="commitinfo">idp.global v${commitinfo.version}</div>
<div class="logo">idp.global</div>
<div class="navigationGroupLabel">Account Settings</div>
<div
class="navigationOption"
@click=${async () => {
const subrouter = await this.getAccountRouter();
subrouter.pushUrl('');
}}
>
overview
<div class="logoArea">
<div class="logo">
<dees-icon .icon=${'lucide:fingerprint'}></dees-icon>
idp.global
</div>
</div>
<div
class="navigationOption"
@click=${async () => {
const idpState = await IdpState.getSingletonInstance();
idpState.domtools.router.pushUrl('/logout');
}}
>
logout
<div class="navContent">
<div class="navigationGroupLabel">Account</div>
<div
class="navigationOption"
@click=${async () => {
const subrouter = await this.getAccountRouter();
subrouter.pushUrl('');
}}
>
<dees-icon .icon=${'lucide:home'}></dees-icon>
Overview
</div>
<div
class="navigationOption"
@click=${async () => {
}}
>
<dees-icon .icon=${'lucide:shield'}></dees-icon>
Manage Roles
</div>
<div
class="navigationOption"
@click=${async () => {
}}
>
<dees-icon .icon=${'lucide:plus'}></dees-icon>
Create Organization
</div>
<div
class="navigationOption"
@click=${async () => {
const idpState = await IdpState.getSingletonInstance();
idpState.domtools.router.pushUrl('/logout');
}}
>
<dees-icon .icon=${'lucide:power'}></dees-icon>
Log Out
</div>
<div class="divider"></div>
<div class="navigationGroupLabel">Organization</div>
<dees-input-dropdown
.label=${'Select organization'}
@selectedOption=${(eventArg: CustomEvent) => {
const currentState = states.accountState.getState();
states.accountState.dispatchAction(
states.setSelectedOrg,
currentState.organizations.find((org) => org.data.slug === eventArg.detail.payload)
);
}}
></dees-input-dropdown>
<div
class="navigationOption"
@click=${async () => {}}
>
<dees-icon .icon=${'lucide:box'}></dees-icon>
Apps
</div>
<div
class="navigationOption"
@click=${async () => {}}
>
<dees-icon .icon=${'lucide:users'}></dees-icon>
Users
</div>
<div
class="navigationOption"
@click=${async () => {}}
>
<dees-icon .icon=${'lucide:activity'}></dees-icon>
Activity
</div>
<div
class="navigationOption"
@click=${async () => {}}
>
<dees-icon .icon=${'lucide:wallet'}></dees-icon>
Billing
</div>
</div>
<div
class="navigationOption"
@click=${async () => {
}}
>
manage roles
</div>
<div
class="navigationOption"
@click=${async () => {
}}
>
create an org
</div>
<div class="navigationGroupLabel">Organization Settings</div>
<dees-input-dropdown
.label=${'choose org:'}
@selectedOption=${(eventArg: CustomEvent) => {
const currentState = states.accountState.getState();
states.accountState.dispatchAction(
states.setSelectedOrg,
currentState.organizations.find((org) => org.data.slug === eventArg.detail.payload)
);
}}
></dees-input-dropdown>
${this.options.map((option) => {
return html` <div class="navigationOption">${option.text}</div> `;
})}
<div class="commitinfo">v${commitinfo.version}</div>
`;
}
+99 -11
View File
@@ -1,29 +1,117 @@
import { css } from '@design.estate/dees-element';
export default css`
/**
* Design tokens matching the login page aesthetic (idp-centercontainer.ts)
*/
export const accountDesignTokens = css`
:host {
--background: hsl(240 10% 3.9%);
--foreground: hsl(0 0% 98%);
--muted: hsl(240 3.7% 15.9%);
--muted-foreground: hsl(240 5% 64.9%);
--border: hsl(240 3.7% 15.9%);
--card: hsl(240 6% 6%);
font-family: 'Geist Sans', -apple-system, BlinkMacSystemFont, sans-serif;
color: var(--foreground);
}
`;
/**
* Card container styles
*/
export const cardStyles = css`
.card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 32px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
}
`;
/**
* Typography styles for consistent text hierarchy
*/
export const typographyStyles = css`
h1 {
margin-top: 50px;
border-bottom: 1px solid;
border-image: radial-gradient(rgba(136, 136, 136, 0.44), rgba(136, 136, 136, 0)) 1 / 1 / 0 stretch;
padding-bottom: 10px;
font-weight: 500;
font-size: 24px;
font-weight: 600;
color: var(--foreground);
margin: 0 0 8px 0;
letter-spacing: -0.02em;
}
h2 {
border-top: 1px dotted #666;
padding-top: 16px;
font-size: 18px;
font-weight: 600;
color: var(--foreground);
margin: 24px 0 8px 0;
letter-spacing: -0.01em;
}
p {
line-height: 1.5em;
font-size: 14px;
color: var(--muted-foreground);
margin: 0 0 16px 0;
line-height: 1.5;
}
.description {
font-size: 14px;
color: var(--muted-foreground);
margin: 0;
line-height: 1.5;
}
dees-button {
margin-top: 16px;
width: 200px;
}
dees-input-text {
max-width: 400px;
max-width: 100%;
}
`;
/**
* Navigation styles for the sidebar
*/
export const navigationStyles = css`
.nav-item {
padding: 10px 16px;
margin: 2px 8px;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
color: var(--muted-foreground);
transition: all 0.15s ease;
cursor: pointer;
}
.nav-item:hover {
background: var(--muted);
color: var(--foreground);
}
.nav-item.active {
background: var(--muted);
color: var(--foreground);
}
.nav-group-label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--muted-foreground);
padding: 24px 16px 8px;
}
`;
/**
* Legacy export for backwards compatibility
*/
export default css`
${accountDesignTokens}
${typographyStyles}
`;
+76 -32
View File
@@ -11,7 +11,7 @@ import {
directives,
} from '@design.estate/dees-element';
import sharedStyles from '../sharedstyles.js';
import sharedStyles, { accountDesignTokens, cardStyles, typographyStyles } from '../sharedstyles.js';
declare global {
interface HTMLElementTagNameMap {
@@ -52,34 +52,75 @@ export class BaseView extends DeesElement {
public static styles = [
cssManager.defaultStyles,
sharedStyles,
accountDesignTokens,
cardStyles,
typographyStyles,
css`
:host {
display: block;
max-width: 900px;
margin: auto;
color: ${cssManager.bdTheme('#333', '#fff')};
padding: 48px;
}
.viewHost {
max-width: 600px;
margin: 0 auto;
}
.card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 32px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
}
.slug {
color: orange;
color: var(--foreground);
font-weight: 600;
font-family: 'Geist Mono', monospace;
}
.hint {
display: block;
font-size: 13px;
color: var(--muted-foreground);
margin: 16px 0;
padding: 12px 16px;
background: var(--muted);
border-radius: 8px;
}
dees-form {
display: flex;
flex-direction: column;
gap: 16px;
margin-top: 24px;
}
.orgGrid {
display: grid;
grid-gap: 16px;
grid-template-columns: ${cssManager.cssGridColumns(2, 16)};
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
margin-top: 24px;
}
.org {
padding: 16px;
border-top: 1px solid #444;
background: ${cssManager.bdTheme('#ccc', '#222')};
border-radius: 16px;
padding: 20px;
background: var(--card);
border: 1px solid var(--border);
border-radius: 12px;
color: var(--foreground);
transition: all 0.15s ease;
cursor: pointer;
}
.org:hover {
cursor: default;
background: ${cssManager.bdTheme('#CCC', '#333')};
background: var(--muted);
border-color: var(--muted-foreground);
}
.org dees-icon {
opacity: 0.7;
}
`,
];
@@ -100,24 +141,26 @@ export class BaseView extends DeesElement {
if (state.accountState.getState().organizations.length === 0) {
render(
html`
<h1>Setup Your Account</h1>
<p>
There are no organizations for your account. Please create one now. Alternatively you
can ask an admin of an existing organization to invite you.
</p>
<dees-form>
<dees-input-text .label=${'Organization Name'} .key=${'orgName'}></dees-input-text>
</dees-form>
<p>
The organization slug corresponds to the organization name:<br />
<span class="slug"
>${directives.subscribe(
state.accountState.select((stateArg) => stateArg.newOrg.chosenSlug)
)}</span
>
</p>
<span class="hint"></span>
<dees-button .disabled=${true}>Create the Organization</dees-button>
<div class="card">
<h1>Setup Your Account</h1>
<p>
There are no organizations for your account. Please create one now. Alternatively you
can ask an admin of an existing organization to invite you.
</p>
<dees-form>
<dees-input-text .label=${'Organization Name'} .key=${'orgName'}></dees-input-text>
</dees-form>
<p>
The organization slug will be:<br />
<span class="slug"
>${directives.subscribe(
state.accountState.select((stateArg) => stateArg.newOrg.chosenSlug)
)}</span
>
</p>
<span class="hint"></span>
<dees-button .disabled=${true}>Create the Organization</dees-button>
</div>
`,
viewHost
);
@@ -169,6 +212,7 @@ export class BaseView extends DeesElement {
render(
html`
<h1>Select An Organization</h1>
<p>Choose an organization to manage its settings and billing.</p>
<div class="orgGrid">
${state.accountState.getState().organizations.map((orgArg) => {
return html`
@@ -180,7 +224,7 @@ export class BaseView extends DeesElement {
parentElement.subrouter.pushUrl(`/org/${orgArg.data.slug}/billing`);
}}
>
<dees-icon .iconFA=${"wallet"} style="display: inline-block; transform: translateY(3px); padding-right: 4px;"></dees-icon> ${orgArg.data.name}
<dees-icon .icon=${'lucide:building2'} style="display: inline-block; transform: translateY(3px); padding-right: 8px;"></dees-icon> ${orgArg.data.name}
</div>
`;
})}
+91 -33
View File
@@ -8,7 +8,7 @@ import {
css,
} from '@design.estate/dees-element';
import sharedStyles from '../sharedstyles.js';
import sharedStyles, { accountDesignTokens, cardStyles, typographyStyles } from '../sharedstyles.js';
import * as state from '../../../states/accountstate.js';
@@ -46,48 +46,106 @@ export class SubscriptionView extends DeesElement {
public static styles = [
cssManager.defaultStyles,
sharedStyles,
accountDesignTokens,
cardStyles,
typographyStyles,
css`
:host {
display: block;
padding: 48px;
max-width: 900px;
margin: auto;
color: ${cssManager.bdTheme('#333', '#fff')};
margin: 0 auto;
}
.section {
margin-bottom: 48px;
}
.card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 24px;
margin-top: 16px;
}
h3 {
font-size: 16px;
font-weight: 600;
color: var(--foreground);
margin: 24px 0 8px 0;
}
dees-table {
margin-top: 16px;
}
dees-button {
margin-top: 16px;
}
`
]
public render() {
return html`
<h1>-> Billing & Subscription</h1>
This page allows you to setup how you are billed for any workspace.global charges.
<h2>PaymentMethod</h2>
<p>Our customer-side billing is handled by paddle.com. You subscribe to a free plan there,
and we will bill any occurring charges as an extra on the monthly date of your choosing.
Paddle.com will take care of proper VAT invoices that will allow for VAT reduction according to the law.</p>
<h3>Paddle</h3>
<dees-button @click=${async () => {
await this.domtoolsPromise;
this.domtools.router.pushUrl(`/org/${state.accountState.getState().selectedOrg.data.slug}/paddlesetup`)
}}>set up paddle.com</dees-button>
<h3>Enterprise billing</h3>
Once you have 100 or more Pro Plan users, you can request custom Enterprise billing for your organization here. Note: You are currently not eligible.
<h2>Subscriptions</h2>
<p>
The total price of a subscription already includes all taxes. If you are a VAT registered business,
the actual price might be cheaper in case you can claim VAT exemption from the purchase.
</p>
<p>
Note: Subscriptions are tied to prganizations. You are only seeing subcriptions regarding ${'org1'} right now.
To see other organization, select the respective organization at the top left of this page.
</p>
<dees-table .heading1=${'Subscriptions'} .heading2=${`for organization ${'org1'}`} .data=${this.subscriptions}></dees-table>
<dees-button>Add subscription</dees-button>
<h2>Accrued IaaS Usage</h2>
<p>Note: The accrued IaaS Usage will be charged by adjusting the workspsace.gobal IaaS Postpaid Access price prior the renewal date.</p>
<dees-table .heading1=${'Subscriptions'} .heading2=${`for organization ${'org1'}`} .data=${this.subscriptions}></dees-table>
<h2>Upcoming Billable Items</h2>
<h2>Past Invoices</h2>
<h1>Billing & Subscription</h1>
<p>Manage your billing settings and subscriptions for your organization.</p>
<div class="section">
<h2>Payment Method</h2>
<div class="card">
<p>Our customer-side billing is handled by paddle.com. You subscribe to a free plan there,
and we will bill any occurring charges as an extra on the monthly date of your choosing.
Paddle.com will take care of proper VAT invoices that will allow for VAT reduction according to the law.</p>
<h3>Paddle</h3>
<dees-button @click=${async () => {
await this.domtoolsPromise;
this.domtools.router.pushUrl(`/org/${state.accountState.getState().selectedOrg.data.slug}/paddlesetup`)
}}>Set up Paddle.com</dees-button>
<h3>Enterprise Billing</h3>
<p>Once you have 100 or more Pro Plan users, you can request custom Enterprise billing for your organization here.</p>
<p><em>Note: You are currently not eligible.</em></p>
</div>
</div>
<div class="section">
<h2>Subscriptions</h2>
<div class="card">
<p>
The total price of a subscription already includes all taxes. If you are a VAT registered business,
the actual price might be cheaper in case you can claim VAT exemption from the purchase.
</p>
<p>
<em>Note: Subscriptions are tied to organizations. Select the respective organization from the sidebar to view its subscriptions.</em>
</p>
<dees-table .heading1=${'Subscriptions'} .heading2=${`for organization`} .data=${this.subscriptions}></dees-table>
<dees-button>Add Subscription</dees-button>
</div>
</div>
<div class="section">
<h2>Accrued IaaS Usage</h2>
<div class="card">
<p>The accrued IaaS Usage will be charged by adjusting the workspace.global IaaS Postpaid Access price prior to the renewal date.</p>
<dees-table .heading1=${'Usage'} .heading2=${`for organization`} .data=${this.subscriptions}></dees-table>
</div>
</div>
<div class="section">
<h2>Upcoming Billable Items</h2>
<div class="card">
<p>No upcoming billable items.</p>
</div>
</div>
<div class="section">
<h2>Past Invoices</h2>
<div class="card">
<p>No past invoices available.</p>
</div>
</div>
`;
}
}
+3 -3
View File
@@ -167,9 +167,9 @@ export class IdpLoginPrompt extends DeesElement {
}
private login = async (valueArg: { emailAddress: string; passwordArg: string }) => {
// lets disable the register button
const registerButton: plugins.deesCatalog.DeesButton = this.shadowRoot.querySelector('.registerButton');
registerButton.disabled = true;
// lets disable the submit button
const loginSubmitButton: plugins.deesCatalog.DeesFormSubmit = this.shadowRoot.querySelector('#loginSubmitButton');
loginSubmitButton.disabled = true;
// lets define the needed requests
const idpState = await IdpState.getSingletonInstance();
const loginForm: DeesForm = this.shadowRoot.querySelector('#loginForm');