Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion apps/web/src/app/core/services/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { Injectable, inject, signal, WritableSignal } from '@angular/core';
import { Account } from '@zoneless/shared-types';
import { StorageService } from './storage.service';
import { ApiService } from './api.service';

export type DashboardType = 'express' | 'full' | 'none';

export interface ExchangeContext {
type: 'account_link' | 'login_link';
link_type?: 'account_onboarding' | 'account_update';
Expand All @@ -21,6 +24,17 @@ export interface ApiKeyLoginResponse {
account_id: string;
}

export function ResolveDashboardType(account: Account | null): DashboardType {
const explicit = account?.controller?.zoneless_dashboard?.type;
if (explicit) {
return explicit;
}
if (account && account.platform_account === account.id) {
return 'full';
}
return 'express';
}

@Injectable({
providedIn: 'root',
})
Expand All @@ -30,6 +44,7 @@ export class AuthService {

isAuthenticated: WritableSignal<boolean> = signal(false);
isPlatform: WritableSignal<boolean> = signal(false);
dashboardType: WritableSignal<DashboardType> = signal('express');

constructor() {
this.CheckAuth();
Expand All @@ -39,9 +54,29 @@ export class AuthService {
const token = this.storage.GetItemString('auth_token');
this.isAuthenticated.set(!!token);

// Check if user logged in as platform
const isPlatformStored = this.storage.GetItemString('is_platform');
this.isPlatform.set(isPlatformStored === 'true');

const storedType = this.storage.GetItemString('dashboard_type');
if (
storedType === 'express' ||
storedType === 'full' ||
storedType === 'none'
) {
this.dashboardType.set(storedType);
} else {
this.dashboardType.set(isPlatformStored === 'true' ? 'full' : 'express');
}
}

SyncFromAccount(account: Account | null): void {
const type = ResolveDashboardType(account);
const isPlatform = !!(account && account.platform_account === account.id);

this.dashboardType.set(type);
this.isPlatform.set(isPlatform);
this.storage.StoreItemString('dashboard_type', type);
this.storage.StoreItemString('is_platform', String(isPlatform));
}

GetToken(): string | null {
Expand All @@ -56,8 +91,10 @@ export class AuthService {
);
this.storage.StoreItemString('auth_token', response.token);
this.storage.StoreItemString('is_platform', 'false');
this.storage.StoreItemString('dashboard_type', 'express');
this.isAuthenticated.set(true);
this.isPlatform.set(false);
this.dashboardType.set('express');
return response;
}

Expand All @@ -78,8 +115,10 @@ export class AuthService {
}

this.storage.StoreItemString('is_platform', 'true');
this.storage.StoreItemString('dashboard_type', 'full');
this.isAuthenticated.set(true);
this.isPlatform.set(true);
this.dashboardType.set('full');
}

async LoginWithApiKey(apiKey: string): Promise<ApiKeyLoginResponse> {
Expand All @@ -90,8 +129,10 @@ export class AuthService {
);
this.storage.StoreItemString('auth_token', response.token);
this.storage.StoreItemString('is_platform', 'true');
this.storage.StoreItemString('dashboard_type', 'full');
this.isAuthenticated.set(true);
this.isPlatform.set(true);
this.dashboardType.set('full');
return response;
}

Expand All @@ -100,5 +141,6 @@ export class AuthService {
this.storage.ClearAll();
this.isAuthenticated.set(false);
this.isPlatform.set(false);
this.dashboardType.set('express');
}
}
37 changes: 16 additions & 21 deletions apps/web/src/app/features/account/account.component.html
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
<div class="site-wrapper-dashboard">
<div class="account-host">
<app-page-loader [loading]="loading()"></app-page-loader>

@if (configService.IsTestMode()) {
<div class="test-mode-bar">
You're using test data. No real transactions will be processed.
</div>
}

<div class="account-content-wrapper">
<div class="sidebar-wrapper">
<app-side-menu [sideMenu]="sideMenu()"></app-side-menu>
</div>

<div class="content-wrapper">
@if (accountService.account() && balanceService.balance() &&
personService.person()) {
<div class="view">
<router-outlet></router-outlet>
</div>
}
</div>
</div>
@if (ready()) { @if (dashboardType() === 'full') {
<app-full-shell
[sideMenu]="sideMenu()"
[showTestMode]="configService.IsTestMode()"
>
<router-outlet></router-outlet>
</app-full-shell>
} @else {
<app-express-shell
[sideMenu]="sideMenu()"
[showTestMode]="configService.IsTestMode()"
>
<router-outlet></router-outlet>
</app-express-shell>
} }
</div>
52 changes: 3 additions & 49 deletions apps/web/src/app/features/account/account.component.scss
Original file line number Diff line number Diff line change
@@ -1,54 +1,8 @@
@use '../../styles/base.scss' as *;
@use '../../styles/buttons.scss' as *;
@use '../../styles/account.scss' as *;

.test-mode-bar {
background-color: #0e3459;
color: white;
text-align: center;
padding: $spacing-snug;
font-size: $font-size;
flex-shrink: 0;
}

.site-wrapper-dashboard {
padding: 0;
max-width: none;
margin: 0;
flex-direction: column;
display: flex;
height: 100vh;
}

.account-content-wrapper {
display: flex;
flex: 1;
min-height: 300px;
}

.sidebar-wrapper {
:host {
display: block;
height: 100%;
}

.content-wrapper {
flex: 1;
padding: $spacing-large $spacing-extra-large;
.account-host {
height: 100%;
overflow-y: auto;
max-width: 1320px;
margin: auto;
}

@media only screen and (max-width: 1000px) {
.content-wrapper {
padding: $spacing;
}

.account-content-wrapper {
display: block;
}

.sidebar-wrapper {
height: auto;
}
}
92 changes: 24 additions & 68 deletions apps/web/src/app/features/account/account.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
WritableSignal,
computed,
} from '@angular/core';
import { Router, ActivatedRoute, RouterOutlet } from '@angular/router';
import { Router, RouterOutlet } from '@angular/router';

import { MetaService, AuthService } from '../../core';
import {
Expand All @@ -20,23 +20,27 @@ import {
ConfigService,
WebhookEndpointService,
} from '../../data';
import {
PageLoaderComponent,
SideMenuComponent,
SideMenuItem,
} from '../../shared';
import { PageLoaderComponent, SideMenuGroup } from '../../shared';
import { ExpressShellComponent } from './shells/express-shell/express-shell.component';
import { FullShellComponent } from './shells/full-shell/full-shell.component';
import { EXPRESS_NAV } from './nav/express-nav';
import { FULL_NAV } from './nav/full-nav';

@Component({
selector: 'app-account',
imports: [PageLoaderComponent, SideMenuComponent, RouterOutlet],
imports: [
PageLoaderComponent,
RouterOutlet,
ExpressShellComponent,
FullShellComponent,
],
templateUrl: './account.component.html',
styleUrl: './account.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AccountComponent implements OnInit {
private readonly meta = inject(MetaService);
private readonly router = inject(Router);
private readonly route = inject(ActivatedRoute);
private readonly authService = inject(AuthService);
readonly accountService = inject(AccountService);
readonly balanceService = inject(BalanceService);
Expand All @@ -49,65 +53,18 @@ export class AccountComponent implements OnInit {

loading: WritableSignal<boolean> = signal(true);

// Transaction detail panel state
transactionDetailPanelOpen: WritableSignal<boolean> = signal(false);

// Computed signal for side menu based on platform status
sideMenu = computed<SideMenuItem[][]>(() => {
const baseMenu: SideMenuItem[][] = [
[
{
title: 'Home',
icon: 'home.svg',
id: 'home',
},
{
title: 'Balance',
icon: 'account_balance.svg',
id: 'balance',
},
],
];
dashboardType = this.authService.dashboardType;

// Add platform-only tabs
if (this.authService.isPlatform()) {
baseMenu[0].push({
title: 'Connected',
icon: 'groups.svg',
id: 'connected-accounts',
});
baseMenu[0].push({
title: 'Transactions',
icon: 'autorenew.svg',
id: 'payments',
});
baseMenu[0].push({
title: 'Customers',
icon: 'person.svg',
id: 'customers',
});
baseMenu[0].push({
title: 'Products',
icon: 'package.svg',
id: 'products',
});
baseMenu[0].push({
title: 'Developers',
icon: 'code.svg',
id: 'developers',
});
}
sideMenu = computed<SideMenuGroup[]>(() =>
this.dashboardType() === 'full' ? FULL_NAV : EXPRESS_NAV
);

// Add Settings at the bottom
baseMenu[0].push({
title: 'Settings',
icon: 'person.svg',
id: 'settings',
bottom: true,
});

return baseMenu;
});
ready = computed(
() =>
!!this.accountService.account() &&
!!this.balanceService.balance() &&
!!this.personService.person()
);

async ngOnInit(): Promise<void> {
this.meta.SetMetaTitle('Home');
Expand All @@ -121,7 +78,6 @@ export class AccountComponent implements OnInit {
}

private async LoadAccountData(): Promise<void> {
// Load platform config for branding (parallel with account)
const [account] = await Promise.all([
this.accountService.GetAccount(),
this.configService.LoadConfig(),
Expand All @@ -132,6 +88,8 @@ export class AccountComponent implements OnInit {
return;
}

this.authService.SyncFromAccount(account);

if (!account.tos_acceptance) {
this.router.navigateByUrl('/onboard');
return;
Expand All @@ -142,10 +100,8 @@ export class AccountComponent implements OnInit {
}

await this.externalWalletService.GetExternalWallets(account.id);

await this.balanceService.GetBalance();

// Load webhook endpoints and API keys if platform
if (this.authService.isPlatform()) {
await Promise.all([
this.webhookEndpointService.ListWebhookEndpoints(),
Expand Down
Loading
Loading