@nauticana/sail 0.5.5 → 0.5.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nauticana/sail",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
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.",
5
5
  "main": "src/index.ts",
6
6
  "files": [
@@ -92,17 +92,26 @@ export class ConsentGateComponent {
92
92
  }
93
93
  this.formValue.set(this.form.getRawValue());
94
94
  this.formValid.set(this.form.valid);
95
+ this.consentStateChange.emit(this.state());
95
96
  });
96
97
 
98
+ // Emit on every form change. We emit explicitly here rather than via a
99
+ // standalone `effect(() => emit(state()))` because output emission
100
+ // from inside an effect is unreliable in production AOT builds: the
101
+ // effect's scheduler runs in a microtask outside the user-event CD
102
+ // tick, and parent OnPush views can miss the propagated signal write.
103
+ // Emitting inside the RxJS subscription puts the emission on the
104
+ // synchronous click-handler path, where Angular's zone-aware CD
105
+ // picks up the parent's `consents` signal write deterministically.
97
106
  this.form.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
98
107
  this.formValue.set(this.form.getRawValue());
99
108
  this.formValid.set(this.form.valid);
109
+ this.consentStateChange.emit(this.state());
100
110
  });
101
111
  this.form.statusChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
102
112
  this.formValid.set(this.form.valid);
113
+ this.consentStateChange.emit(this.state());
103
114
  });
104
-
105
- effect(() => this.consentStateChange.emit(this.state()));
106
115
  }
107
116
 
108
117
  protected readonly ConsentType = ConsentType;
package/src/model/auth.ts CHANGED
@@ -43,19 +43,26 @@ export interface SignupConsent {
43
43
  }
44
44
 
45
45
  // ── Phone / email OTP ──
46
- // `contactType` is a frontend-only convenience field used to drive UI
47
- // navigation (phone-confirm vs email-confirm). It is NOT sent to the
48
- // backend — keel detects phone-vs-email from the `contact` value itself.
49
- // `BaseAuthService.sendOtp` strips this field before posting, since keel
50
- // rejects unknown JSON fields with 400.
46
+ // `contactType` selects the OTP delivery channel. As of keel v0.5.11
47
+ // the backend honors this field directly: 'phone' (default — `contact`
48
+ // is normalized to E.164 and OTP goes via SMS) or 'email' (`contact`
49
+ // is lowercased + trimmed and OTP goes via the keel MailClient).
50
+ // Older keel deployments ignore anything other than phone — pin the
51
+ // consumer's go.mod when this matters.
51
52
  export type OtpContactType = 'phone' | 'email';
52
53
  export type OtpPurpose = 'login' | 'register' | 'verify';
53
54
 
54
55
  export interface OtpRequest {
55
56
  contact: string;
56
- contactType?: OtpContactType; // frontend-only, stripped before POST
57
+ contactType?: OtpContactType; // 'phone' (default) | 'email'
57
58
  purpose?: OtpPurpose;
58
59
  defaultRegion?: string; // ISO country hint for phone normalization
60
+ // The OTHER contact the user supplied at signup, persisted on the
61
+ // new user_account row even though it isn't being OTP-verified at
62
+ // this step. When contactType='email', this is the user's phone;
63
+ // when contactType='phone', this is the user's email. Optional —
64
+ // omit for login flows (only registration creates the row).
65
+ secondaryContact?: string;
59
66
  policyType?: string;
60
67
  policyVersion?: string;
61
68
  policyRegion?: string;
@@ -263,8 +263,12 @@ export abstract class BaseAuthService {
263
263
  * numbers by comparing 200 vs 404.
264
264
  */
265
265
  sendOtp(req: OtpRequest): Observable<OtpResponse> {
266
- const { contactType: _contactType, ...payload } = req;
267
- return this.http.post<OtpResponse>(this.otpSendUrl, payload);
266
+ // Send the request as-is — `contactType` is now a real backend
267
+ // field (keel v0.5.11+) that selects the SMS-vs-email dispatch
268
+ // channel. The pre-v0.5.11 strip-before-post workaround was
269
+ // removed when email-OTP support landed in keel/handler/
270
+ // otp_handler.go.
271
+ return this.http.post<OtpResponse>(this.otpSendUrl, req);
268
272
  }
269
273
 
270
274
  resendOtp(otpToken: string, purpose?: OtpRequest['purpose']): Observable<{ status: string }> {