@nauticana/sail 1.1.4 → 1.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -111,6 +111,7 @@ bootstrapApplication(App, {
|
|
|
111
111
|
opField: 'op_code',
|
|
112
112
|
hiddenFields: ['op_code', 'PartnerId'],
|
|
113
113
|
appTitle: 'My App',
|
|
114
|
+
appLogoUrl: 'logo.png',
|
|
114
115
|
dashboardComponent: DashUser,
|
|
115
116
|
publicRoutes: [
|
|
116
117
|
{ path: 'login/local', loadComponent: () => import('@nauticana/sail').then(m => m.LoginComponent) },
|
|
@@ -160,6 +161,17 @@ import { Navigation } from '@nauticana/sail';
|
|
|
160
161
|
export class App {}
|
|
161
162
|
```
|
|
162
163
|
|
|
164
|
+
Place the application logo in the consumer's `public/` directory (for example,
|
|
165
|
+
`public/logo.png`) and set the same public URL in `appLogoUrl`. Sail displays it
|
|
166
|
+
in the sidenav toolbar as a link to `/`. When `appLogoUrl` is omitted, the link
|
|
167
|
+
falls back to `appTitle`, then to a home icon, so home navigation remains
|
|
168
|
+
available without requiring a library-owned brand asset. The consuming app may
|
|
169
|
+
also reuse that asset as its favicon:
|
|
170
|
+
|
|
171
|
+
```html
|
|
172
|
+
<link rel="icon" type="image/png" href="logo.png">
|
|
173
|
+
```
|
|
174
|
+
|
|
163
175
|
### 6. Provide global styles
|
|
164
176
|
|
|
165
177
|
All sail components use `ViewEncapsulation.None` — they ship no CSS. Your project must provide a global stylesheet covering sail selectors.
|
|
@@ -177,7 +189,8 @@ Key CSS classes used by components:
|
|
|
177
189
|
/* Layout */
|
|
178
190
|
.auth-container, .auth-card, .auth-header, .auth-title, .auth-form,
|
|
179
191
|
.auth-actions, .auth-footer, .auth-app-title, .auth-checkbox,
|
|
180
|
-
.auth-section-label, .auth-instructions, .form-row, .register-card
|
|
192
|
+
.auth-section-label, .auth-instructions, .form-row, .register-card,
|
|
193
|
+
.sail-navigation-home, .sail-navigation-logo, .sail-navigation-home-title
|
|
181
194
|
|
|
182
195
|
/* Feedback */
|
|
183
196
|
.auth-error, .auth-success, .geocode-status, .geocode-loading,
|
|
@@ -240,6 +253,7 @@ interface SailGuiConfig {
|
|
|
240
253
|
opField: string; // Operation field name (default: 'op_code')
|
|
241
254
|
hiddenFields: string[]; // Fields hidden from forms (default: ['op_code', 'PartnerId'])
|
|
242
255
|
appTitle?: string; // Shown on login pages
|
|
256
|
+
appLogoUrl?: string; // Public logo URL used by the navigation home link
|
|
243
257
|
googleMapsApiKey?: string; // For RegisterComponent geocoding
|
|
244
258
|
dashboardComponent?: Type<any>; // Component for /dashboard route
|
|
245
259
|
publicRoutes?: Routes; // Routes available before login
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nauticana/sail",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "Metadata-driven Angular component library for CRUD admin frontends backed by the keel Go backend. Tables, forms, navigation, OTP/2FA/social auth, billing, payout onboarding.",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -9,6 +9,7 @@ import { SAIL_GUI_CONFIG, SailGuiConfig, DEFAULT_CONFIG } from '../../config';
|
|
|
9
9
|
import {
|
|
10
10
|
ConsentState, LoginResponseSocial, SignupConsent, SocialProvider,
|
|
11
11
|
} from '../../model/auth';
|
|
12
|
+
import { firstValueFrom } from 'rxjs';
|
|
12
13
|
|
|
13
14
|
declare const google: any;
|
|
14
15
|
declare const AppleID: any;
|
|
@@ -66,9 +67,11 @@ export class SocialLoginComponent extends BaseAsync implements OnInit {
|
|
|
66
67
|
|
|
67
68
|
private async initGoogle() {
|
|
68
69
|
await loadScript(GSI_URL);
|
|
70
|
+
const nonce = await firstValueFrom(this.auth.getSocialNonce());
|
|
69
71
|
google.accounts.id.initialize({
|
|
70
72
|
client_id: this.guiConfig.googleClientId,
|
|
71
73
|
callback: (res: { credential: string }) => this.onIdToken('google', res.credential),
|
|
74
|
+
...(nonce ? { nonce } : {}),
|
|
72
75
|
});
|
|
73
76
|
// Render into the template anchor once the view is ready.
|
|
74
77
|
queueMicrotask(() => {
|
|
@@ -80,11 +83,13 @@ export class SocialLoginComponent extends BaseAsync implements OnInit {
|
|
|
80
83
|
|
|
81
84
|
private async initApple() {
|
|
82
85
|
await loadScript(APPLE_URL);
|
|
86
|
+
const nonce = await firstValueFrom(this.auth.getSocialNonce());
|
|
83
87
|
AppleID.auth.init({
|
|
84
88
|
clientId: this.guiConfig.appleServiceId,
|
|
85
89
|
scope: 'name email',
|
|
86
90
|
redirectURI: this.guiConfig.appleRedirectUri ?? window.location.origin,
|
|
87
91
|
usePopup: true,
|
|
92
|
+
...(nonce ? { nonce } : {}),
|
|
88
93
|
});
|
|
89
94
|
}
|
|
90
95
|
|
|
@@ -4,7 +4,21 @@
|
|
|
4
4
|
[mode]="isHandset() ? 'over' : 'side'"
|
|
5
5
|
[opened]="isLoggedIn() && !isHandset()">
|
|
6
6
|
<mat-toolbar class="primary">
|
|
7
|
-
<
|
|
7
|
+
<a class="sail-navigation-home"
|
|
8
|
+
routerLink="/"
|
|
9
|
+
[attr.aria-label]="guiConfig.appTitle ? guiConfig.appTitle + ' home' : 'Home'">
|
|
10
|
+
@if (guiConfig.appLogoUrl) {
|
|
11
|
+
<img class="sail-navigation-logo"
|
|
12
|
+
[src]="guiConfig.appLogoUrl"
|
|
13
|
+
width="40"
|
|
14
|
+
height="40"
|
|
15
|
+
alt="">
|
|
16
|
+
} @else if (guiConfig.appTitle) {
|
|
17
|
+
<span class="sail-navigation-home-title">{{ guiConfig.appTitle }}</span>
|
|
18
|
+
} @else {
|
|
19
|
+
<mat-icon aria-hidden="true">home</mat-icon>
|
|
20
|
+
}
|
|
21
|
+
</a>
|
|
8
22
|
</mat-toolbar>
|
|
9
23
|
@if (isLoggedIn()) {
|
|
10
24
|
<mat-accordion>
|
package/src/config.ts
CHANGED
|
@@ -33,6 +33,8 @@ export interface SailGuiConfig {
|
|
|
33
33
|
extraRoutes?: (data: ApplicationData) => Routes;
|
|
34
34
|
dashboardComponent?: Type<any>;
|
|
35
35
|
appTitle?: string;
|
|
36
|
+
/** Public asset URL for the application logo shown in the navigation home link, e.g. 'logo.png'. */
|
|
37
|
+
appLogoUrl?: string;
|
|
36
38
|
googleMapsApiKey?: string;
|
|
37
39
|
loginFooterLinks?: RouteLink[];
|
|
38
40
|
/** Links shown in navigation toolbar when not logged in (e.g., Login, Register) */
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
UserProfile,
|
|
12
12
|
} from '../model/auth';
|
|
13
13
|
import { RestURL } from './rest_url';
|
|
14
|
-
import { Observable, ReplaySubject } from 'rxjs';
|
|
14
|
+
import { Observable, ReplaySubject, catchError, of } from 'rxjs';
|
|
15
15
|
import { ApplicationMenu, ConstantValue } from '../model/common';
|
|
16
16
|
import { CanActivateFn, CanDeactivateFn, Router, Routes } from '@angular/router';
|
|
17
17
|
import { SAIL_GUI_CONFIG, SailGuiConfig, DEFAULT_CONFIG } from '../config';
|
|
@@ -381,6 +381,16 @@ export abstract class BaseAuthService extends BaseRestService {
|
|
|
381
381
|
);
|
|
382
382
|
}
|
|
383
383
|
|
|
384
|
+
// Single-use nonce for the social id_token flow (replay protection): GET on
|
|
385
|
+
// the same social-login route. Empty when the backend hasn't enabled it —
|
|
386
|
+
// callers then sign in without one.
|
|
387
|
+
getSocialNonce(): Observable<string> {
|
|
388
|
+
return this.http.get<{ nonce: string }>(this.loginSocialUrl).pipe(
|
|
389
|
+
map((res) => res?.nonce ?? ''),
|
|
390
|
+
catchError(() => of('')),
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
384
394
|
// ── Account lifecycle ──
|
|
385
395
|
|
|
386
396
|
/**
|