@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.4",
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
- <span>Menu</span>
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
  /**