@rolatech/angular-onboarding 20.3.0-beta.2 → 20.3.0-beta.4

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.
Files changed (40) hide show
  1. package/README.md +1 -1
  2. package/fesm2022/rolatech-angular-onboarding-admin-onboarding-detail-page-DREEMvoT.mjs +720 -0
  3. package/fesm2022/rolatech-angular-onboarding-admin-onboarding-detail-page-DREEMvoT.mjs.map +1 -0
  4. package/fesm2022/rolatech-angular-onboarding-admin-onboarding-index-page-DP7wffLd.mjs +313 -0
  5. package/fesm2022/rolatech-angular-onboarding-admin-onboarding-index-page-DP7wffLd.mjs.map +1 -0
  6. package/fesm2022/rolatech-angular-onboarding-agent-apply-form-page-y02hYlN_.mjs +446 -0
  7. package/fesm2022/rolatech-angular-onboarding-agent-apply-form-page-y02hYlN_.mjs.map +1 -0
  8. package/fesm2022/rolatech-angular-onboarding-agent-apply-result-page-CEL4nWb_.mjs +141 -0
  9. package/fesm2022/rolatech-angular-onboarding-agent-apply-result-page-CEL4nWb_.mjs.map +1 -0
  10. package/fesm2022/rolatech-angular-onboarding-agent-apply-review-page-DG_D03YW.mjs +453 -0
  11. package/fesm2022/rolatech-angular-onboarding-agent-apply-review-page-DG_D03YW.mjs.map +1 -0
  12. package/fesm2022/{rolatech-angular-onboarding-agent-apply-shell-page-DibWYeD1.mjs → rolatech-angular-onboarding-agent-apply-shell-page-CaTvnFzk.mjs} +27 -40
  13. package/fesm2022/rolatech-angular-onboarding-agent-apply-shell-page-CaTvnFzk.mjs.map +1 -0
  14. package/fesm2022/{rolatech-angular-onboarding-agent-apply-start-page-DC7gyOnS.mjs → rolatech-angular-onboarding-agent-apply-start-page-BfqO2YWB.mjs} +31 -29
  15. package/fesm2022/rolatech-angular-onboarding-agent-apply-start-page-BfqO2YWB.mjs.map +1 -0
  16. package/fesm2022/{rolatech-angular-onboarding-agent-onboarding-documents-page-DWBGTj5J.mjs → rolatech-angular-onboarding-agent-onboarding-documents-page-BKDYZE0e.mjs} +79 -6
  17. package/fesm2022/rolatech-angular-onboarding-agent-onboarding-documents-page-BKDYZE0e.mjs.map +1 -0
  18. package/fesm2022/rolatech-angular-onboarding.mjs +2251 -233
  19. package/fesm2022/rolatech-angular-onboarding.mjs.map +1 -1
  20. package/package.json +1 -1
  21. package/types/rolatech-angular-onboarding.d.ts +219 -12
  22. package/fesm2022/rolatech-angular-onboarding-admin-onboarding-detail-page-DKJQX3cs.mjs +0 -224
  23. package/fesm2022/rolatech-angular-onboarding-admin-onboarding-detail-page-DKJQX3cs.mjs.map +0 -1
  24. package/fesm2022/rolatech-angular-onboarding-admin-onboarding-index-page-BO4pC_NU.mjs +0 -206
  25. package/fesm2022/rolatech-angular-onboarding-admin-onboarding-index-page-BO4pC_NU.mjs.map +0 -1
  26. package/fesm2022/rolatech-angular-onboarding-agent-apply-banking-page-VYNfR4fy.mjs +0 -133
  27. package/fesm2022/rolatech-angular-onboarding-agent-apply-banking-page-VYNfR4fy.mjs.map +0 -1
  28. package/fesm2022/rolatech-angular-onboarding-agent-apply-financial-page-Ck3Rowke.mjs +0 -132
  29. package/fesm2022/rolatech-angular-onboarding-agent-apply-financial-page-Ck3Rowke.mjs.map +0 -1
  30. package/fesm2022/rolatech-angular-onboarding-agent-apply-profile-page-DNepDxHu.mjs +0 -122
  31. package/fesm2022/rolatech-angular-onboarding-agent-apply-profile-page-DNepDxHu.mjs.map +0 -1
  32. package/fesm2022/rolatech-angular-onboarding-agent-apply-qualification-page-CSwupuKt.mjs +0 -108
  33. package/fesm2022/rolatech-angular-onboarding-agent-apply-qualification-page-CSwupuKt.mjs.map +0 -1
  34. package/fesm2022/rolatech-angular-onboarding-agent-apply-review-page-DugCjfvK.mjs +0 -182
  35. package/fesm2022/rolatech-angular-onboarding-agent-apply-review-page-DugCjfvK.mjs.map +0 -1
  36. package/fesm2022/rolatech-angular-onboarding-agent-apply-shell-page-DibWYeD1.mjs.map +0 -1
  37. package/fesm2022/rolatech-angular-onboarding-agent-apply-start-page-DC7gyOnS.mjs.map +0 -1
  38. package/fesm2022/rolatech-angular-onboarding-agent-apply-submitted-page-BMkV2V8K.mjs +0 -55
  39. package/fesm2022/rolatech-angular-onboarding-agent-apply-submitted-page-BMkV2V8K.mjs.map +0 -1
  40. package/fesm2022/rolatech-angular-onboarding-agent-onboarding-documents-page-DWBGTj5J.mjs.map +0 -1
@@ -1,43 +1,121 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, signal, computed, Injectable, input, ViewEncapsulation, ChangeDetectionStrategy, Component, output } from '@angular/core';
2
+ import { inject, DestroyRef, signal, computed, effect, Injectable, input, ViewEncapsulation, ChangeDetectionStrategy, Component, output } from '@angular/core';
3
3
  import { Router, RouterLink } from '@angular/router';
4
- import { FormBuilder, Validators } from '@angular/forms';
5
- import { OnboardingApplicantService, OnboardingAdminService, onboardingStatusLabel, onboardingIssueTypeLabel, onboardingDocumentTypeLabel } from '@rolatech/angular-services';
6
- import { firstValueFrom } from 'rxjs';
7
- import { HttpClient } from '@angular/common/http';
4
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
5
+ import * as i1 from '@angular/forms';
6
+ import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
7
+ import { OnboardingApplicantService, OnboardingAdminService, onboardingStatusLabel, onboardingIssueTypeLabel, onboardingDocumentTypeLabel, onboardingVatModeLabel } from '@rolatech/angular-services';
8
+ import { firstValueFrom, debounceTime } from 'rxjs';
8
9
  import { NgClass, DatePipe } from '@angular/common';
10
+ import { DomSanitizer } from '@angular/platform-browser';
9
11
 
10
12
  const applicationIdGuard = (route) => Boolean(route.paramMap.get('applicationId'));
11
13
 
12
- const INDIVIDUAL_REQUIRED_DOCUMENTS = [
13
- { type: 'PASSPORT', label: 'Passport' },
14
- { type: 'PROOF_OF_ADDRESS', label: 'Proof of address' },
14
+ const INDIVIDUAL_REQUIRED_DOCUMENTS$1 = [
15
+ {
16
+ type: 'PASSPORT',
17
+ label: 'Applicant passport',
18
+ helperText: 'Upload a clear PDF, JPG, or PNG of the passport identity page.',
19
+ },
20
+ ];
21
+ const COMPANY_BASE_REQUIRED_DOCUMENTS$1 = [
22
+ {
23
+ type: 'PASSPORT',
24
+ label: 'Director passport',
25
+ helperText: 'Upload the passport for the company director handling the application.',
26
+ },
27
+ {
28
+ type: 'CERTIFICATE_OF_INCORPORATION',
29
+ label: 'Certificate of incorporation',
30
+ helperText: 'Use the latest registered incorporation certificate.',
31
+ },
32
+ {
33
+ type: 'INDUSTRY_CERTIFICATE',
34
+ label: 'Industry certificate',
35
+ helperText: 'Accepted certificates include RICS, ARLA, NAEA, or Propertymark.',
36
+ },
37
+ ];
38
+ const UK_COMPANY_REQUIRED_DOCUMENTS$1 = [
39
+ ...COMPANY_BASE_REQUIRED_DOCUMENTS$1,
40
+ {
41
+ type: 'OTHER',
42
+ label: 'Redress / AML evidence',
43
+ helperText: 'Upload the redress scheme certificate and HMRC AML evidence in one file if needed.',
44
+ },
15
45
  ];
16
- const COMPANY_REQUIRED_DOCUMENTS = [
17
- { type: 'PASSPORT', label: 'Passport' },
18
- { type: 'CERTIFICATE_OF_INCORPORATION', label: 'Certificate of incorporation' },
19
- { type: 'INDUSTRY_CERTIFICATE', label: 'Industry certificate' },
46
+ const CHINA_COMPANY_REQUIRED_DOCUMENTS$1 = [
47
+ ...COMPANY_BASE_REQUIRED_DOCUMENTS$1,
48
+ {
49
+ type: 'OTHER',
50
+ label: 'Stamped business license',
51
+ helperText: 'Upload the original scanned business license.',
52
+ warning: 'China company business licenses must include the official company stamp.',
53
+ },
20
54
  ];
55
+ const SORT_CODE_PATTERN = /^\d{6}$/;
56
+ const ACCOUNT_NUMBER_PATTERN = /^\d{8}$/;
21
57
  class AgentApplyFacade {
22
58
  onboardingService = inject(OnboardingApplicantService);
23
59
  fb = inject(FormBuilder);
24
- http = inject(HttpClient);
60
+ destroyRef = inject(DestroyRef);
25
61
  loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
26
62
  saving = signal(false, ...(ngDevMode ? [{ debugName: "saving" }] : []));
27
63
  submitting = signal(false, ...(ngDevMode ? [{ debugName: "submitting" }] : []));
28
64
  error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
65
+ lastLocalDraftSavedAt = signal(null, ...(ngDevMode ? [{ debugName: "lastLocalDraftSavedAt" }] : []));
29
66
  application = signal(null, ...(ngDevMode ? [{ debugName: "application" }] : []));
30
67
  detail = signal(null, ...(ngDevMode ? [{ debugName: "detail" }] : []));
68
+ uploadStates = signal({}, ...(ngDevMode ? [{ debugName: "uploadStates" }] : []));
31
69
  loadedApplicationId = signal(null, ...(ngDevMode ? [{ debugName: "loadedApplicationId" }] : []));
32
70
  applicationId = computed(() => this.detail()?.id ?? this.application()?.id ?? null, ...(ngDevMode ? [{ debugName: "applicationId" }] : []));
33
71
  status = computed(() => this.detail()?.status ?? this.application()?.status ?? null, ...(ngDevMode ? [{ debugName: "status" }] : []));
72
+ applicantType = computed(() => this.detail()?.applicantType ?? this.application()?.applicantType ?? 'INDIVIDUAL', ...(ngDevMode ? [{ debugName: "applicantType" }] : []));
73
+ companyCountry = computed(() => (this.detail()?.companyCountry ?? this.application()?.companyCountry ?? 'UK').toUpperCase(), ...(ngDevMode ? [{ debugName: "companyCountry" }] : []));
74
+ isCompany = computed(() => this.applicantType() === 'COMPANY', ...(ngDevMode ? [{ debugName: "isCompany" }] : []));
75
+ isIndividual = computed(() => this.applicantType() === 'INDIVIDUAL', ...(ngDevMode ? [{ debugName: "isIndividual" }] : []));
76
+ isUkCompany = computed(() => this.isCompany() && this.companyCountry() === 'UK', ...(ngDevMode ? [{ debugName: "isUkCompany" }] : []));
77
+ isChinaCompany = computed(() => this.isCompany() && this.companyCountry() === 'CN', ...(ngDevMode ? [{ debugName: "isChinaCompany" }] : []));
78
+ hasSubmittedState = computed(() => {
79
+ const status = this.status();
80
+ return status === 'SUBMITTED' || status === 'IN_REVIEW' || status === 'APPROVED' || status === 'FAILED';
81
+ }, ...(ngDevMode ? [{ debugName: "hasSubmittedState" }] : []));
82
+ hasGeneralIssue = computed(() => this.issues().some((issue) => !issue.resolved && issue.issueType === 'GENERAL'), ...(ngDevMode ? [{ debugName: "hasGeneralIssue" }] : []));
34
83
  documents = computed(() => this.detail()?.documents ?? [], ...(ngDevMode ? [{ debugName: "documents" }] : []));
35
84
  issues = computed(() => this.detail()?.issues ?? [], ...(ngDevMode ? [{ debugName: "issues" }] : []));
36
85
  timeline = computed(() => this.detail()?.timeline ?? [], ...(ngDevMode ? [{ debugName: "timeline" }] : []));
86
+ fieldIssueComments = computed(() => {
87
+ const comments = {};
88
+ for (const issue of this.issues()) {
89
+ if (issue.resolved || !issue.fieldKey || comments[issue.fieldKey]) {
90
+ continue;
91
+ }
92
+ comments[issue.fieldKey] = issue.comment;
93
+ }
94
+ return comments;
95
+ }, ...(ngDevMode ? [{ debugName: "fieldIssueComments" }] : []));
96
+ documentIssueComments = computed(() => {
97
+ const comments = {};
98
+ for (const issue of this.issues()) {
99
+ if (issue.resolved || !issue.documentType || comments[issue.documentType]) {
100
+ continue;
101
+ }
102
+ comments[issue.documentType] = issue.comment;
103
+ }
104
+ return comments;
105
+ }, ...(ngDevMode ? [{ debugName: "documentIssueComments" }] : []));
37
106
  requiredDocuments = computed(() => {
38
- const applicantType = this.detail()?.applicantType ?? this.application()?.applicantType;
39
- return applicantType === 'COMPANY' ? COMPANY_REQUIRED_DOCUMENTS : INDIVIDUAL_REQUIRED_DOCUMENTS;
107
+ if (!this.isCompany()) {
108
+ return INDIVIDUAL_REQUIRED_DOCUMENTS$1;
109
+ }
110
+ if (this.isChinaCompany()) {
111
+ return CHINA_COMPANY_REQUIRED_DOCUMENTS$1;
112
+ }
113
+ if (this.isUkCompany()) {
114
+ return UK_COMPANY_REQUIRED_DOCUMENTS$1;
115
+ }
116
+ return COMPANY_BASE_REQUIRED_DOCUMENTS$1;
40
117
  }, ...(ngDevMode ? [{ debugName: "requiredDocuments" }] : []));
118
+ hasActiveUploads = computed(() => Object.values(this.uploadStates()).some((state) => state?.status === 'uploading'), ...(ngDevMode ? [{ debugName: "hasActiveUploads" }] : []));
41
119
  completion = computed(() => {
42
120
  const detail = this.detail();
43
121
  if (!detail) {
@@ -46,25 +124,38 @@ class AgentApplyFacade {
46
124
  qualification: false,
47
125
  financial: false,
48
126
  banking: false,
127
+ form: false,
49
128
  };
50
129
  }
51
130
  if (detail.progress) {
131
+ const profile = detail.progress.profileCompleted;
132
+ const qualification = detail.progress.qualificationCompleted;
133
+ const financial = detail.progress.financialCompleted;
134
+ const banking = detail.progress.bankingCompleted;
52
135
  return {
53
- profile: detail.progress.profileCompleted,
54
- qualification: detail.progress.qualificationCompleted,
55
- financial: detail.progress.financialCompleted,
56
- banking: detail.progress.bankingCompleted,
136
+ profile,
137
+ qualification,
138
+ financial,
139
+ banking,
140
+ form: profile && qualification && financial && banking,
57
141
  };
58
142
  }
143
+ const profile = this.isProfileComplete(detail);
144
+ const qualification = this.isQualificationComplete(detail);
145
+ const financial = this.isFinancialComplete(detail);
146
+ const banking = this.isBankingComplete(detail);
59
147
  return {
60
- profile: this.isProfileComplete(detail),
61
- qualification: this.isQualificationComplete(detail),
62
- financial: this.isFinancialComplete(detail),
63
- banking: this.isBankingComplete(detail),
148
+ profile,
149
+ qualification,
150
+ financial,
151
+ banking,
152
+ form: profile && qualification && financial && banking,
64
153
  };
65
154
  }, ...(ngDevMode ? [{ debugName: "completion" }] : []));
155
+ readyForReview = computed(() => this.completion().form && !this.hasActiveUploads(), ...(ngDevMode ? [{ debugName: "readyForReview" }] : []));
156
+ canSubmit = computed(() => this.readyForReview() && !this.submitting() && !this.saving(), ...(ngDevMode ? [{ debugName: "canSubmit" }] : []));
66
157
  profileForm = this.fb.nonNullable.group({
67
- organizationName: ['', [Validators.required, Validators.maxLength(255)]],
158
+ organizationName: ['', [Validators.maxLength(255)]],
68
159
  contactName: ['', [Validators.required, Validators.maxLength(255)]],
69
160
  contactEmail: ['', [Validators.required, Validators.email]],
70
161
  contactPhone: ['', [Validators.required, Validators.maxLength(64)]],
@@ -72,14 +163,24 @@ class AgentApplyFacade {
72
163
  });
73
164
  financialForm = this.fb.nonNullable.group({
74
165
  vatMode: ['NON_VAT_REGISTERED', [Validators.required]],
75
- vatNumber: [''],
166
+ vatNumber: ['', [Validators.maxLength(64)]],
76
167
  });
77
168
  bankingForm = this.fb.nonNullable.group({
78
169
  bankName: ['', [Validators.required, Validators.maxLength(255)]],
79
- accountHolderName: ['', [Validators.required, Validators.maxLength(255)]],
80
- sortCode: ['', [Validators.required, Validators.maxLength(16)]],
81
- accountNumber: ['', [Validators.required, Validators.maxLength(32)]],
170
+ accountHolderName: ['', [Validators.required, Validators.maxLength(255), this.accountHolderNameValidator()]],
171
+ sortCode: ['', [Validators.required, Validators.pattern(SORT_CODE_PATTERN)]],
172
+ accountNumber: ['', [Validators.required, Validators.pattern(ACCOUNT_NUMBER_PATTERN)]],
82
173
  });
174
+ constructor() {
175
+ this.setupAutosave();
176
+ effect(() => {
177
+ this.applicantType();
178
+ this.companyCountry();
179
+ this.syncProfileValidators();
180
+ this.syncFinancialRules();
181
+ this.bankingForm.controls.accountHolderName.updateValueAndValidity({ emitEvent: false });
182
+ });
183
+ }
83
184
  async start(request) {
84
185
  this.loading.set(true);
85
186
  this.error.set(null);
@@ -112,6 +213,8 @@ class AgentApplyFacade {
112
213
  this.detail.set(response);
113
214
  this.loadedApplicationId.set(response.id);
114
215
  this.patchForms(response);
216
+ this.restoreLocalDraft(response.id);
217
+ this.syncFinancialRules();
115
218
  }
116
219
  catch (error) {
117
220
  this.error.set(this.toErrorMessage(error));
@@ -120,7 +223,7 @@ class AgentApplyFacade {
120
223
  this.loading.set(false);
121
224
  }
122
225
  }
123
- async saveProfile() {
226
+ async saveProfile(reload = true) {
124
227
  const applicationId = this.applicationId();
125
228
  if (!applicationId) {
126
229
  return false;
@@ -132,10 +235,19 @@ class AgentApplyFacade {
132
235
  this.saving.set(true);
133
236
  this.error.set(null);
134
237
  try {
135
- const payload = this.profileForm.getRawValue();
238
+ const rawValue = this.profileForm.getRawValue();
239
+ const payload = {
240
+ organizationName: this.isCompany() ? rawValue.organizationName.trim() : rawValue.contactName.trim(),
241
+ contactName: rawValue.contactName.trim(),
242
+ contactEmail: rawValue.contactEmail.trim(),
243
+ contactPhone: rawValue.contactPhone.trim(),
244
+ countryCode: rawValue.countryCode.trim().toUpperCase(),
245
+ };
136
246
  const response = await firstValueFrom(this.onboardingService.saveProfile(applicationId, payload));
137
247
  this.application.set(response);
138
- await this.ensureLoaded(applicationId, true);
248
+ if (reload) {
249
+ await this.ensureLoaded(applicationId, true);
250
+ }
139
251
  return true;
140
252
  }
141
253
  catch (error) {
@@ -146,11 +258,12 @@ class AgentApplyFacade {
146
258
  this.saving.set(false);
147
259
  }
148
260
  }
149
- async saveFinancial() {
261
+ async saveFinancial(reload = true) {
150
262
  const applicationId = this.applicationId();
151
263
  if (!applicationId) {
152
264
  return false;
153
265
  }
266
+ this.syncFinancialRules();
154
267
  if (this.financialForm.invalid) {
155
268
  this.financialForm.markAllAsTouched();
156
269
  return false;
@@ -159,13 +272,20 @@ class AgentApplyFacade {
159
272
  this.error.set(null);
160
273
  try {
161
274
  const rawValue = this.financialForm.getRawValue();
162
- const payload = {
163
- vatMode: rawValue.vatMode,
164
- vatNumber: rawValue.vatNumber || undefined,
165
- };
275
+ const payload = this.isIndividual()
276
+ ? {
277
+ vatMode: 'NON_VAT_REGISTERED',
278
+ vatNumber: undefined,
279
+ }
280
+ : {
281
+ vatMode: rawValue.vatMode,
282
+ vatNumber: rawValue.vatNumber.trim() || undefined,
283
+ };
166
284
  const response = await firstValueFrom(this.onboardingService.saveFinancial(applicationId, payload));
167
285
  this.application.set(response);
168
- await this.ensureLoaded(applicationId, true);
286
+ if (reload) {
287
+ await this.ensureLoaded(applicationId, true);
288
+ }
169
289
  return true;
170
290
  }
171
291
  catch (error) {
@@ -176,11 +296,13 @@ class AgentApplyFacade {
176
296
  this.saving.set(false);
177
297
  }
178
298
  }
179
- async saveBanking() {
299
+ async saveBanking(reload = true) {
180
300
  const applicationId = this.applicationId();
181
301
  if (!applicationId) {
182
302
  return false;
183
303
  }
304
+ this.normalizeBankingFields();
305
+ this.bankingForm.controls.accountHolderName.updateValueAndValidity({ emitEvent: false });
184
306
  if (this.bankingForm.invalid) {
185
307
  this.bankingForm.markAllAsTouched();
186
308
  return false;
@@ -188,10 +310,18 @@ class AgentApplyFacade {
188
310
  this.saving.set(true);
189
311
  this.error.set(null);
190
312
  try {
191
- const payload = this.bankingForm.getRawValue();
313
+ const rawValue = this.bankingForm.getRawValue();
314
+ const payload = {
315
+ bankName: rawValue.bankName.trim(),
316
+ accountHolderName: rawValue.accountHolderName.trim(),
317
+ sortCode: this.onlyDigits(rawValue.sortCode),
318
+ accountNumber: this.onlyDigits(rawValue.accountNumber),
319
+ };
192
320
  const response = await firstValueFrom(this.onboardingService.saveBanking(applicationId, payload));
193
321
  this.application.set(response);
194
- await this.ensureLoaded(applicationId, true);
322
+ if (reload) {
323
+ await this.ensureLoaded(applicationId, true);
324
+ }
195
325
  return true;
196
326
  }
197
327
  catch (error) {
@@ -202,6 +332,33 @@ class AgentApplyFacade {
202
332
  this.saving.set(false);
203
333
  }
204
334
  }
335
+ async saveFormProgress() {
336
+ if (this.hasActiveUploads()) {
337
+ this.error.set('Wait for all document uploads to finish before continuing.');
338
+ return false;
339
+ }
340
+ if (!this.completion().qualification) {
341
+ this.error.set('Upload every required qualification document before moving to review.');
342
+ return false;
343
+ }
344
+ const profileSaved = await this.saveProfile(false);
345
+ if (!profileSaved) {
346
+ return false;
347
+ }
348
+ const financialSaved = await this.saveFinancial(false);
349
+ if (!financialSaved) {
350
+ return false;
351
+ }
352
+ const bankingSaved = await this.saveBanking(false);
353
+ if (!bankingSaved) {
354
+ return false;
355
+ }
356
+ const applicationId = this.applicationId();
357
+ if (applicationId) {
358
+ await this.ensureLoaded(applicationId, true);
359
+ }
360
+ return true;
361
+ }
205
362
  async uploadDocument(documentType, file, replacedDocumentId) {
206
363
  const applicationId = this.applicationId();
207
364
  if (!applicationId) {
@@ -209,51 +366,59 @@ class AgentApplyFacade {
209
366
  }
210
367
  this.saving.set(true);
211
368
  this.error.set(null);
369
+ this.setUploadState(documentType, {
370
+ filename: file.name,
371
+ progress: 0,
372
+ status: 'uploading',
373
+ error: null,
374
+ });
212
375
  try {
213
376
  const uploadRequest = {
214
377
  documentType,
215
378
  filename: file.name,
216
- contentType: file.type,
379
+ contentType: file.type || 'application/octet-stream',
217
380
  fileSize: file.size,
218
381
  replacedDocumentId: replacedDocumentId ?? undefined,
219
382
  };
220
383
  const uploadUrl = await firstValueFrom(this.onboardingService.requestUploadUrl(applicationId, uploadRequest));
221
- // const uploadResponse1 = await fetch(uploadUrl.uploadUrl, {
222
- // method: 'PUT',
223
- // body: file,
224
- // headers: {
225
- // 'Content-Type': file.type || 'application/octet-stream',
226
- // },
227
- // mode: 'cors',
228
- // });
229
- // const uploadResponse = await firstValueFrom(
230
- // this.http.put(uploadUrl.uploadUrl, file, {
231
- // headers: {
232
- // 'Content-Type': file.type || 'application/octet-stream',
233
- // },
234
- // }),
235
- // );
236
- // console.log(uploadResponse);
237
- // if (!uploadResponse .ok) {
238
- // throw new Error(`Document upload failed with status ${uploadResponse.status}`);
239
- // }
384
+ await this.uploadToSignedUrl(uploadUrl, file, (progress) => {
385
+ this.setUploadState(documentType, {
386
+ filename: file.name,
387
+ progress,
388
+ status: 'uploading',
389
+ error: null,
390
+ });
391
+ });
240
392
  const confirmRequest = {
241
393
  documentType,
242
394
  storageType: uploadUrl.storageType,
243
395
  objectKey: uploadUrl.objectKey,
244
396
  objectUrl: uploadUrl.uploadUrl,
245
397
  originalFilename: file.name,
246
- contentType: file.type,
398
+ contentType: file.type || 'application/octet-stream',
247
399
  fileSize: file.size,
248
400
  replacedDocumentId: replacedDocumentId ?? undefined,
249
401
  };
250
402
  const detail = await firstValueFrom(this.onboardingService.confirmDocumentUpload(applicationId, confirmRequest));
251
403
  this.detail.set(detail);
252
404
  this.loadedApplicationId.set(detail.id);
405
+ this.setUploadState(documentType, {
406
+ filename: file.name,
407
+ progress: 100,
408
+ status: 'uploaded',
409
+ error: null,
410
+ });
253
411
  return true;
254
412
  }
255
413
  catch (error) {
256
- this.error.set(this.toErrorMessage(error));
414
+ const message = this.toErrorMessage(error);
415
+ this.error.set(message);
416
+ this.setUploadState(documentType, {
417
+ filename: file.name,
418
+ progress: 0,
419
+ status: 'failed',
420
+ error: message,
421
+ });
257
422
  return false;
258
423
  }
259
424
  finally {
@@ -265,11 +430,16 @@ class AgentApplyFacade {
265
430
  if (!applicationId) {
266
431
  return false;
267
432
  }
433
+ if (!this.readyForReview()) {
434
+ this.error.set('Complete the form and finish document uploads before submitting.');
435
+ return false;
436
+ }
268
437
  this.submitting.set(true);
269
438
  this.error.set(null);
270
439
  try {
271
440
  const response = await firstValueFrom(this.onboardingService.submit(applicationId));
272
441
  this.application.set(response);
442
+ this.clearLocalDraft(applicationId);
273
443
  await this.ensureLoaded(applicationId, true);
274
444
  return true;
275
445
  }
@@ -285,44 +455,122 @@ class AgentApplyFacade {
285
455
  return this.documents().find((item) => item.documentType === documentType) ?? null;
286
456
  }
287
457
  getIssueComment(documentType) {
288
- return this.issues().find((item) => item.documentType === documentType)?.comment ?? null;
458
+ return this.documentIssueComments()[documentType] ?? null;
459
+ }
460
+ getFieldIssueComment(fieldKey) {
461
+ return this.fieldIssueComments()[fieldKey] ?? null;
462
+ }
463
+ isFieldLocked(fieldKey) {
464
+ if (this.hasSubmittedState()) {
465
+ return true;
466
+ }
467
+ if (this.status() !== 'NEED_MORE_INFO') {
468
+ return false;
469
+ }
470
+ if (this.hasGeneralIssue()) {
471
+ return false;
472
+ }
473
+ const editableFields = Object.keys(this.fieldIssueComments());
474
+ if (editableFields.length === 0) {
475
+ return true;
476
+ }
477
+ return !editableFields.includes(fieldKey);
478
+ }
479
+ isDocumentLocked(documentType) {
480
+ if (this.hasSubmittedState()) {
481
+ return true;
482
+ }
483
+ if (this.status() !== 'NEED_MORE_INFO') {
484
+ return false;
485
+ }
486
+ if (this.hasGeneralIssue()) {
487
+ return false;
488
+ }
489
+ const editableDocuments = Object.keys(this.documentIssueComments());
490
+ if (editableDocuments.length === 0) {
491
+ return true;
492
+ }
493
+ return !editableDocuments.includes(documentType);
289
494
  }
290
495
  canAccessStep(step) {
291
- const completion = this.completion();
292
496
  switch (step) {
293
- case 'profile':
497
+ case 'form':
294
498
  return true;
295
- case 'qualification':
296
- return completion.profile;
297
- case 'financial':
298
- return completion.profile && completion.qualification;
299
- case 'banking':
300
- return completion.profile && completion.qualification && completion.financial;
301
499
  case 'review':
302
- return completion.profile && completion.qualification && completion.financial && completion.banking;
303
- case 'submitted': {
500
+ return this.completion().form;
501
+ case 'result': {
304
502
  const status = this.status();
305
- return status === 'SUBMITTED' || status === 'IN_REVIEW' || status === 'APPROVED' || status === 'FAILED';
503
+ return (status === 'SUBMITTED' ||
504
+ status === 'IN_REVIEW' ||
505
+ status === 'NEED_MORE_INFO' ||
506
+ status === 'APPROVED' ||
507
+ status === 'FAILED');
306
508
  }
307
509
  default:
308
510
  return false;
309
511
  }
310
512
  }
311
513
  firstIncompleteStep() {
312
- const completion = this.completion();
313
- if (!completion.profile) {
314
- return 'profile';
315
- }
316
- if (!completion.qualification) {
317
- return 'qualification';
514
+ return this.completion().form ? 'review' : 'form';
515
+ }
516
+ uploadState(documentType) {
517
+ return (this.uploadStates()[documentType] ?? {
518
+ filename: null,
519
+ progress: 0,
520
+ status: 'idle',
521
+ error: null,
522
+ });
523
+ }
524
+ expectedAccountHolderName() {
525
+ const rawProfile = this.profileForm.getRawValue();
526
+ return this.isCompany() ? rawProfile.organizationName.trim() : rawProfile.contactName.trim();
527
+ }
528
+ formatSortCode(value) {
529
+ const digits = this.onlyDigits(value);
530
+ if (!digits) {
531
+ return '';
318
532
  }
319
- if (!completion.financial) {
320
- return 'financial';
533
+ return digits.match(/.{1,2}/g)?.join('-') ?? digits;
534
+ }
535
+ setupAutosave() {
536
+ const autosave = () => this.persistLocalDraft();
537
+ this.profileForm.valueChanges.pipe(debounceTime(250), takeUntilDestroyed(this.destroyRef)).subscribe(() => autosave());
538
+ this.financialForm.valueChanges.pipe(debounceTime(250), takeUntilDestroyed(this.destroyRef)).subscribe(() => autosave());
539
+ this.bankingForm.valueChanges.pipe(debounceTime(250), takeUntilDestroyed(this.destroyRef)).subscribe(() => autosave());
540
+ this.profileForm.controls.contactName.valueChanges
541
+ .pipe(debounceTime(50), takeUntilDestroyed(this.destroyRef))
542
+ .subscribe(() => this.bankingForm.controls.accountHolderName.updateValueAndValidity({ emitEvent: false }));
543
+ this.profileForm.controls.organizationName.valueChanges
544
+ .pipe(debounceTime(50), takeUntilDestroyed(this.destroyRef))
545
+ .subscribe(() => this.bankingForm.controls.accountHolderName.updateValueAndValidity({ emitEvent: false }));
546
+ this.financialForm.controls.vatMode.valueChanges
547
+ .pipe(takeUntilDestroyed(this.destroyRef))
548
+ .subscribe(() => this.syncFinancialRules());
549
+ }
550
+ syncProfileValidators() {
551
+ const organizationName = this.profileForm.controls.organizationName;
552
+ const validators = this.isCompany() ? [Validators.required, Validators.maxLength(255)] : [Validators.maxLength(255)];
553
+ organizationName.setValidators(validators);
554
+ organizationName.updateValueAndValidity({ emitEvent: false });
555
+ }
556
+ syncFinancialRules() {
557
+ const vatMode = this.financialForm.controls.vatMode;
558
+ const vatNumber = this.financialForm.controls.vatNumber;
559
+ if (this.isIndividual()) {
560
+ if (vatMode.value !== 'NON_VAT_REGISTERED') {
561
+ vatMode.setValue('NON_VAT_REGISTERED', { emitEvent: false });
562
+ }
563
+ if (vatNumber.value) {
564
+ vatNumber.setValue('', { emitEvent: false });
565
+ }
321
566
  }
322
- if (!completion.banking) {
323
- return 'banking';
567
+ else if (vatMode.value === 'NON_VAT_REGISTERED') {
568
+ vatMode.setValue('VAT_REGISTERED', { emitEvent: false });
324
569
  }
325
- return 'review';
570
+ const requiresVatNumber = this.isCompany() && vatMode.value === 'VAT_REGISTERED';
571
+ const validators = requiresVatNumber ? [Validators.required, Validators.maxLength(64)] : [Validators.maxLength(64)];
572
+ vatNumber.setValidators(validators);
573
+ vatNumber.updateValueAndValidity({ emitEvent: false });
326
574
  }
327
575
  patchForms(detail) {
328
576
  this.profileForm.patchValue({
@@ -330,29 +578,39 @@ class AgentApplyFacade {
330
578
  contactName: detail.contactName ?? '',
331
579
  contactEmail: detail.contactEmail ?? '',
332
580
  contactPhone: detail.contactPhone ?? '',
333
- countryCode: detail.countryCode ?? 'GB',
334
- });
581
+ countryCode: detail.countryCode ?? this.defaultCountryCode(),
582
+ }, { emitEvent: false });
335
583
  this.financialForm.patchValue({
336
- vatMode: detail.vatMode ?? 'NON_VAT_REGISTERED',
584
+ vatMode: detail.vatMode ?? (detail.applicantType === 'COMPANY' ? 'VAT_REGISTERED' : 'NON_VAT_REGISTERED'),
337
585
  vatNumber: detail.vatNumber ?? '',
338
- });
586
+ }, { emitEvent: false });
339
587
  this.bankingForm.patchValue({
340
588
  bankName: detail.bankName ?? '',
341
589
  accountHolderName: detail.accountHolderName ?? '',
342
- sortCode: detail.sortCode ?? '',
343
- accountNumber: detail.accountNumber ?? '',
344
- });
590
+ sortCode: this.onlyDigits(detail.sortCode),
591
+ accountNumber: this.onlyDigits(detail.accountNumber),
592
+ }, { emitEvent: false });
345
593
  }
346
594
  isProfileComplete(detail) {
347
- return Boolean(detail.organizationName && detail.contactName && detail.contactEmail && detail.contactPhone && detail.countryCode);
595
+ const hasApplicantName = Boolean(detail.contactName && detail.contactEmail && detail.contactPhone);
596
+ const hasCountryCode = Boolean(detail.countryCode);
597
+ if (!hasApplicantName || !hasCountryCode) {
598
+ return false;
599
+ }
600
+ if (detail.applicantType === 'COMPANY') {
601
+ return Boolean(detail.organizationName);
602
+ }
603
+ return true;
348
604
  }
349
605
  isQualificationComplete(detail) {
350
606
  const uploadedTypes = new Set(detail.documents.map((item) => item.documentType));
351
- const required = detail.applicantType === 'COMPANY' ? COMPANY_REQUIRED_DOCUMENTS : INDIVIDUAL_REQUIRED_DOCUMENTS;
352
- return required.every((item) => uploadedTypes.has(item.type));
607
+ return this.requiredDocuments().every((item) => uploadedTypes.has(item.type));
353
608
  }
354
609
  isFinancialComplete(detail) {
355
- if (!detail.vatMode) {
610
+ if (detail.applicantType === 'INDIVIDUAL') {
611
+ return detail.vatMode === 'NON_VAT_REGISTERED';
612
+ }
613
+ if (!detail.vatMode || detail.vatMode === 'NON_VAT_REGISTERED') {
356
614
  return false;
357
615
  }
358
616
  if (detail.vatMode === 'VAT_REGISTERED') {
@@ -361,9 +619,121 @@ class AgentApplyFacade {
361
619
  return true;
362
620
  }
363
621
  isBankingComplete(detail) {
364
- return Boolean(detail.bankName && detail.accountHolderName && detail.sortCode && detail.accountNumber);
622
+ if (!detail.bankName || !detail.accountHolderName) {
623
+ return false;
624
+ }
625
+ return (SORT_CODE_PATTERN.test(this.onlyDigits(detail.sortCode)) &&
626
+ ACCOUNT_NUMBER_PATTERN.test(this.onlyDigits(detail.accountNumber)));
627
+ }
628
+ accountHolderNameValidator() {
629
+ return (control) => {
630
+ const expected = this.expectedAccountHolderName();
631
+ const value = typeof control.value === 'string' ? control.value.trim() : '';
632
+ if (!value || !expected) {
633
+ return null;
634
+ }
635
+ return value.toLowerCase() === expected.toLowerCase() ? null : { accountHolderMismatch: true };
636
+ };
637
+ }
638
+ normalizeBankingFields() {
639
+ this.bankingForm.patchValue({
640
+ sortCode: this.onlyDigits(this.bankingForm.controls.sortCode.value),
641
+ accountNumber: this.onlyDigits(this.bankingForm.controls.accountNumber.value),
642
+ }, { emitEvent: false });
643
+ }
644
+ persistLocalDraft() {
645
+ const applicationId = this.applicationId();
646
+ if (!applicationId || typeof localStorage === 'undefined') {
647
+ return;
648
+ }
649
+ const snapshot = {
650
+ profile: this.profileForm.getRawValue(),
651
+ financial: this.financialForm.getRawValue(),
652
+ banking: this.bankingForm.getRawValue(),
653
+ updatedAt: new Date().toISOString(),
654
+ };
655
+ localStorage.setItem(this.localDraftKey(applicationId), JSON.stringify(snapshot));
656
+ this.lastLocalDraftSavedAt.set(snapshot.updatedAt);
657
+ }
658
+ restoreLocalDraft(applicationId) {
659
+ if (typeof localStorage === 'undefined') {
660
+ return;
661
+ }
662
+ const rawSnapshot = localStorage.getItem(this.localDraftKey(applicationId));
663
+ if (!rawSnapshot) {
664
+ this.lastLocalDraftSavedAt.set(null);
665
+ return;
666
+ }
667
+ try {
668
+ const snapshot = JSON.parse(rawSnapshot);
669
+ this.profileForm.patchValue(snapshot.profile, { emitEvent: false });
670
+ this.financialForm.patchValue(snapshot.financial, { emitEvent: false });
671
+ this.bankingForm.patchValue({
672
+ ...snapshot.banking,
673
+ sortCode: this.onlyDigits(snapshot.banking.sortCode),
674
+ accountNumber: this.onlyDigits(snapshot.banking.accountNumber),
675
+ }, { emitEvent: false });
676
+ this.lastLocalDraftSavedAt.set(snapshot.updatedAt ?? null);
677
+ }
678
+ catch {
679
+ localStorage.removeItem(this.localDraftKey(applicationId));
680
+ this.lastLocalDraftSavedAt.set(null);
681
+ }
682
+ }
683
+ clearLocalDraft(applicationId) {
684
+ if (typeof localStorage === 'undefined') {
685
+ return;
686
+ }
687
+ localStorage.removeItem(this.localDraftKey(applicationId));
688
+ this.lastLocalDraftSavedAt.set(null);
689
+ }
690
+ localDraftKey(applicationId) {
691
+ return `rolatech.agent-apply.${applicationId}`;
692
+ }
693
+ setUploadState(documentType, state) {
694
+ this.uploadStates.update((current) => ({
695
+ ...current,
696
+ [documentType]: state,
697
+ }));
698
+ }
699
+ uploadToSignedUrl(upload, file, onProgress) {
700
+ return new Promise((resolve, reject) => {
701
+ const request = new XMLHttpRequest();
702
+ request.open('PUT', upload.uploadUrl, true);
703
+ const headers = upload.uploadHeaders ?? {};
704
+ for (const [key, value] of Object.entries(headers)) {
705
+ request.setRequestHeader(key, value);
706
+ }
707
+ request.upload.onprogress = (event) => {
708
+ if (!event.lengthComputable) {
709
+ return;
710
+ }
711
+ onProgress(Math.min(100, Math.round((event.loaded / event.total) * 100)));
712
+ };
713
+ request.onload = () => {
714
+ if (request.status >= 200 && request.status < 300) {
715
+ resolve();
716
+ return;
717
+ }
718
+ reject(new Error(`Document upload failed with status ${request.status}.`));
719
+ };
720
+ request.onerror = () => reject(new Error('Document upload failed.'));
721
+ request.send(file);
722
+ });
723
+ }
724
+ defaultCountryCode() {
725
+ return this.companyCountry() === 'CN' ? 'CN' : 'GB';
726
+ }
727
+ onlyDigits(value) {
728
+ return (value ?? '').replace(/\D/g, '');
365
729
  }
366
730
  toErrorMessage(error) {
731
+ if (error &&
732
+ typeof error === 'object' &&
733
+ 'message' in error &&
734
+ typeof error.message === 'string') {
735
+ return error.message;
736
+ }
367
737
  if (error instanceof Error && error.message) {
368
738
  return error.message;
369
739
  }
@@ -374,7 +744,7 @@ class AgentApplyFacade {
374
744
  }
375
745
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: AgentApplyFacade, decorators: [{
376
746
  type: Injectable
377
- }] });
747
+ }], ctorParameters: () => [] });
378
748
 
379
749
  const applyStepGuard = async (childRoute) => {
380
750
  const facade = inject(AgentApplyFacade);
@@ -407,61 +777,65 @@ function buildFallbackSegments(route, fallbackStep) {
407
777
  const AGENT_APPLY_ROUTES = [
408
778
  {
409
779
  path: '',
410
- loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-start-page-DC7gyOnS.mjs').then((m) => m.AgentApplyStartPage),
780
+ loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-start-page-BfqO2YWB.mjs').then((m) => m.AgentApplyStartPage),
411
781
  },
412
782
  {
413
783
  path: ':applicationId',
414
784
  canActivate: [applicationIdGuard],
415
785
  canActivateChild: [applyStepGuard],
416
786
  providers: [AgentApplyFacade],
417
- loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-shell-page-DibWYeD1.mjs').then((m) => m.AgentApplyShellPage),
787
+ loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-shell-page-CaTvnFzk.mjs').then((m) => m.AgentApplyShellPage),
418
788
  children: [
419
789
  {
420
790
  path: '',
421
791
  pathMatch: 'full',
422
- redirectTo: 'profile',
792
+ redirectTo: 'form',
423
793
  },
424
794
  {
425
- path: 'profile',
795
+ path: 'form',
426
796
  data: {
427
- requiredStep: 'profile',
797
+ requiredStep: 'form',
428
798
  },
429
- loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-profile-page-DNepDxHu.mjs').then((m) => m.AgentApplyProfilePage),
799
+ loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-form-page-y02hYlN_.mjs').then((m) => m.AgentApplyFormPage),
800
+ },
801
+ {
802
+ path: 'profile',
803
+ pathMatch: 'full',
804
+ redirectTo: 'form',
430
805
  },
431
806
  {
432
807
  path: 'qualification',
433
- data: {
434
- requiredStep: 'qualification',
435
- },
436
- loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-qualification-page-CSwupuKt.mjs').then((m) => m.AgentApplyQualificationPage),
808
+ pathMatch: 'full',
809
+ redirectTo: 'form',
437
810
  },
438
811
  {
439
812
  path: 'financial',
440
- data: {
441
- requiredStep: 'financial',
442
- },
443
- loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-financial-page-Ck3Rowke.mjs').then((m) => m.AgentApplyFinancialPage),
813
+ pathMatch: 'full',
814
+ redirectTo: 'form',
444
815
  },
445
816
  {
446
817
  path: 'banking',
447
- data: {
448
- requiredStep: 'banking',
449
- },
450
- loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-banking-page-VYNfR4fy.mjs').then((m) => m.AgentApplyBankingPage),
818
+ pathMatch: 'full',
819
+ redirectTo: 'form',
451
820
  },
452
821
  {
453
822
  path: 'review',
454
823
  data: {
455
824
  requiredStep: 'review',
456
825
  },
457
- loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-review-page-DugCjfvK.mjs').then((m) => m.AgentApplyReviewPage),
826
+ loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-review-page-DG_D03YW.mjs').then((m) => m.AgentApplyReviewPage),
458
827
  },
459
828
  {
460
829
  path: 'submitted',
830
+ pathMatch: 'full',
831
+ redirectTo: 'result',
832
+ },
833
+ {
834
+ path: 'result',
461
835
  data: {
462
- requiredStep: 'submitted',
836
+ requiredStep: 'result',
463
837
  },
464
- loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-submitted-page-BMkV2V8K.mjs').then((m) => m.AgentApplySubmittedPage),
838
+ loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-result-page-CEL4nWb_.mjs').then((m) => m.AgentApplyResultPage),
465
839
  },
466
840
  ],
467
841
  },
@@ -520,7 +894,7 @@ class AgentOnboardingFacade {
520
894
  method: 'PUT',
521
895
  body: file,
522
896
  headers: {
523
- 'Content-Type': file.type,
897
+ ...uploadUrl.uploadHeaders,
524
898
  },
525
899
  });
526
900
  if (!uploadResponse.ok) {
@@ -596,7 +970,7 @@ const AGENT_ONBOARDING_ROUTES = [
596
970
  },
597
971
  {
598
972
  path: 'documents',
599
- loadComponent: () => import('./rolatech-angular-onboarding-agent-onboarding-documents-page-DWBGTj5J.mjs').then((m) => m.AgentOnboardingDocumentsPage),
973
+ loadComponent: () => import('./rolatech-angular-onboarding-agent-onboarding-documents-page-BKDYZE0e.mjs').then((m) => m.AgentOnboardingDocumentsPage),
600
974
  },
601
975
  {
602
976
  path: 'issues',
@@ -609,17 +983,17 @@ const AGENT_ONBOARDING_ROUTES = [
609
983
  const ADMIN_ONBOARDING_ROUTES = [
610
984
  {
611
985
  path: '',
612
- loadComponent: () => import('./rolatech-angular-onboarding-admin-onboarding-index-page-BO4pC_NU.mjs').then((m) => m.AdminOnboardingIndexPage),
986
+ loadComponent: () => import('./rolatech-angular-onboarding-admin-onboarding-index-page-DP7wffLd.mjs').then((m) => m.AdminOnboardingIndexPage),
613
987
  },
614
988
  {
615
- path: ':applicationId',
989
+ path: ':applicationId/review',
616
990
  canActivate: [applicationIdGuard],
617
- loadComponent: () => import('./rolatech-angular-onboarding-admin-onboarding-detail-page-DKJQX3cs.mjs').then((m) => m.AdminOnboardingDetailPage),
991
+ loadComponent: () => import('./rolatech-angular-onboarding-admin-onboarding-review-page-BERcLBeQ.mjs').then((m) => m.AdminOnboardingReviewPage),
618
992
  },
619
993
  {
620
- path: ':applicationId/review',
994
+ path: ':applicationId',
621
995
  canActivate: [applicationIdGuard],
622
- loadComponent: () => import('./rolatech-angular-onboarding-admin-onboarding-review-page-BERcLBeQ.mjs').then((m) => m.AdminOnboardingReviewPage),
996
+ loadComponent: () => import('./rolatech-angular-onboarding-admin-onboarding-detail-page-DREEMvoT.mjs').then((m) => m.AdminOnboardingDetailPage),
623
997
  },
624
998
  ];
625
999
 
@@ -681,6 +1055,12 @@ class AdminOnboardingListFacade {
681
1055
  }));
682
1056
  }
683
1057
  toErrorMessage(error) {
1058
+ if (error &&
1059
+ typeof error === 'object' &&
1060
+ 'message' in error &&
1061
+ typeof error.message === 'string') {
1062
+ return error.message;
1063
+ }
684
1064
  if (error instanceof Error && error.message) {
685
1065
  return error.message;
686
1066
  }
@@ -728,6 +1108,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
728
1108
  type: Injectable
729
1109
  }] });
730
1110
 
1111
+ const INDIVIDUAL_REQUIRED_DOCUMENTS = [
1112
+ { type: 'PASSPORT', label: 'Applicant passport', helperText: 'Required identity document.' },
1113
+ ];
1114
+ const COMPANY_BASE_REQUIRED_DOCUMENTS = [
1115
+ { type: 'PASSPORT', label: 'Director passport', helperText: 'Required director identity document.' },
1116
+ { type: 'CERTIFICATE_OF_INCORPORATION', label: 'Certificate of incorporation', helperText: 'Required for company verification.' },
1117
+ { type: 'INDUSTRY_CERTIFICATE', label: 'Industry certificate', helperText: 'RICS, ARLA, NAEA, or Propertymark.' },
1118
+ ];
1119
+ const UK_COMPANY_REQUIRED_DOCUMENTS = [
1120
+ ...COMPANY_BASE_REQUIRED_DOCUMENTS,
1121
+ { type: 'OTHER', label: 'Redress / AML evidence', helperText: 'Evidence bundle for UK company review.' },
1122
+ ];
1123
+ const CHINA_COMPANY_REQUIRED_DOCUMENTS = [
1124
+ ...COMPANY_BASE_REQUIRED_DOCUMENTS,
1125
+ {
1126
+ type: 'OTHER',
1127
+ label: 'Stamped business license',
1128
+ helperText: 'The original scanned business license must include the official company stamp.',
1129
+ },
1130
+ ];
731
1131
  class AdminOnboardingReviewFacade {
732
1132
  onboardingAdminService = inject(OnboardingAdminService);
733
1133
  loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
@@ -735,7 +1135,49 @@ class AdminOnboardingReviewFacade {
735
1135
  error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
736
1136
  detail = signal(null, ...(ngDevMode ? [{ debugName: "detail" }] : []));
737
1137
  reviewComment = signal('', ...(ngDevMode ? [{ debugName: "reviewComment" }] : []));
738
- issueDrafts = signal([], ...(ngDevMode ? [{ debugName: "issueDrafts" }] : []));
1138
+ fieldIssueDrafts = signal({}, ...(ngDevMode ? [{ debugName: "fieldIssueDrafts" }] : []));
1139
+ documentIssueDrafts = signal({}, ...(ngDevMode ? [{ debugName: "documentIssueDrafts" }] : []));
1140
+ passedFields = signal({}, ...(ngDevMode ? [{ debugName: "passedFields" }] : []));
1141
+ passedDocuments = signal({}, ...(ngDevMode ? [{ debugName: "passedDocuments" }] : []));
1142
+ previewLoading = signal(false, ...(ngDevMode ? [{ debugName: "previewLoading" }] : []));
1143
+ previewError = signal(null, ...(ngDevMode ? [{ debugName: "previewError" }] : []));
1144
+ previewUrl = signal(null, ...(ngDevMode ? [{ debugName: "previewUrl" }] : []));
1145
+ previewDocumentId = signal(null, ...(ngDevMode ? [{ debugName: "previewDocumentId" }] : []));
1146
+ requiredDocuments = computed(() => {
1147
+ const detail = this.detail();
1148
+ if (!detail || detail.applicantType !== 'COMPANY') {
1149
+ return INDIVIDUAL_REQUIRED_DOCUMENTS;
1150
+ }
1151
+ if (detail.companyCountry === 'CN') {
1152
+ return CHINA_COMPANY_REQUIRED_DOCUMENTS;
1153
+ }
1154
+ if (detail.companyCountry === 'UK') {
1155
+ return UK_COMPANY_REQUIRED_DOCUMENTS;
1156
+ }
1157
+ return COMPANY_BASE_REQUIRED_DOCUMENTS;
1158
+ }, ...(ngDevMode ? [{ debugName: "requiredDocuments" }] : []));
1159
+ existingFieldIssues = computed(() => {
1160
+ const result = {};
1161
+ for (const issue of this.detail()?.issues ?? []) {
1162
+ if (issue.resolved || !issue.fieldKey || result[issue.fieldKey]) {
1163
+ continue;
1164
+ }
1165
+ result[issue.fieldKey] = issue.comment;
1166
+ }
1167
+ return result;
1168
+ }, ...(ngDevMode ? [{ debugName: "existingFieldIssues" }] : []));
1169
+ existingDocumentIssues = computed(() => {
1170
+ const result = {};
1171
+ for (const issue of this.detail()?.issues ?? []) {
1172
+ if (issue.resolved || !issue.documentType || result[issue.documentType]) {
1173
+ continue;
1174
+ }
1175
+ result[issue.documentType] = issue.comment;
1176
+ }
1177
+ return result;
1178
+ }, ...(ngDevMode ? [{ debugName: "existingDocumentIssues" }] : []));
1179
+ issueDrafts = computed(() => this.buildIssueDrafts(), ...(ngDevMode ? [{ debugName: "issueDrafts" }] : []));
1180
+ hasPendingIssues = computed(() => this.issueDrafts().length > 0, ...(ngDevMode ? [{ debugName: "hasPendingIssues" }] : []));
739
1181
  async load(applicationId) {
740
1182
  if (!applicationId) {
741
1183
  return;
@@ -745,6 +1187,8 @@ class AdminOnboardingReviewFacade {
745
1187
  try {
746
1188
  const detail = await firstValueFrom(this.onboardingAdminService.getApplication(applicationId));
747
1189
  this.detail.set(detail);
1190
+ this.resetDrafts();
1191
+ this.clearDocumentPreview();
748
1192
  }
749
1193
  catch (error) {
750
1194
  this.error.set(this.toErrorMessage(error));
@@ -754,108 +1198,296 @@ class AdminOnboardingReviewFacade {
754
1198
  }
755
1199
  }
756
1200
  addFieldIssue(fieldKey, comment) {
757
- this.issueDrafts.update((items) => [
758
- ...items,
759
- {
760
- issueType: 'FIELD',
761
- fieldKey,
762
- comment,
763
- },
764
- ]);
1201
+ this.setFieldIssueDraft(fieldKey, {
1202
+ optionKey: 'manual',
1203
+ comment,
1204
+ });
765
1205
  }
766
1206
  addDocumentIssue(documentType, comment) {
767
- this.issueDrafts.update((items) => [
768
- ...items,
769
- {
770
- issueType: 'DOCUMENT',
771
- documentType,
772
- comment,
773
- },
774
- ]);
1207
+ this.setDocumentIssueDraft(documentType, {
1208
+ optionKey: 'manual',
1209
+ comment,
1210
+ });
775
1211
  }
776
1212
  removeIssue(index) {
777
- this.issueDrafts.update((items) => items.filter((_, currentIndex) => currentIndex !== index));
1213
+ const target = this.issueDrafts()[index];
1214
+ if (!target) {
1215
+ return;
1216
+ }
1217
+ if (target.issueType === 'FIELD' && target.fieldKey) {
1218
+ this.clearFieldIssueDraft(target.fieldKey);
1219
+ return;
1220
+ }
1221
+ if (target.issueType === 'DOCUMENT' && target.documentType) {
1222
+ this.clearDocumentIssueDraft(target.documentType);
1223
+ }
778
1224
  }
779
1225
  setComment(value) {
780
1226
  this.reviewComment.set(value);
781
1227
  }
1228
+ setFieldIssueDraft(fieldKey, draft) {
1229
+ const normalized = this.normalizeDraft(draft);
1230
+ this.fieldIssueDrafts.update((current) => {
1231
+ const next = { ...current };
1232
+ if (!normalized) {
1233
+ delete next[fieldKey];
1234
+ }
1235
+ else {
1236
+ next[fieldKey] = normalized;
1237
+ }
1238
+ return next;
1239
+ });
1240
+ if (normalized) {
1241
+ this.passedFields.update((current) => ({
1242
+ ...current,
1243
+ [fieldKey]: false,
1244
+ }));
1245
+ }
1246
+ }
1247
+ setDocumentIssueDraft(documentType, draft) {
1248
+ const normalized = this.normalizeDraft(draft);
1249
+ this.documentIssueDrafts.update((current) => {
1250
+ const next = { ...current };
1251
+ if (!normalized) {
1252
+ delete next[documentType];
1253
+ }
1254
+ else {
1255
+ next[documentType] = normalized;
1256
+ }
1257
+ return next;
1258
+ });
1259
+ if (normalized) {
1260
+ this.passedDocuments.update((current) => ({
1261
+ ...current,
1262
+ [documentType]: false,
1263
+ }));
1264
+ }
1265
+ }
1266
+ toggleFieldPassed(fieldKey, passed) {
1267
+ this.passedFields.update((current) => ({
1268
+ ...current,
1269
+ [fieldKey]: passed,
1270
+ }));
1271
+ if (passed) {
1272
+ this.clearFieldIssueDraft(fieldKey);
1273
+ }
1274
+ }
1275
+ toggleDocumentPassed(documentType, passed) {
1276
+ this.passedDocuments.update((current) => ({
1277
+ ...current,
1278
+ [documentType]: passed,
1279
+ }));
1280
+ if (passed) {
1281
+ this.clearDocumentIssueDraft(documentType);
1282
+ }
1283
+ }
1284
+ fieldDraft(fieldKey) {
1285
+ return this.fieldIssueDrafts()[fieldKey] ?? null;
1286
+ }
1287
+ documentDraft(documentType) {
1288
+ return this.documentIssueDrafts()[documentType] ?? null;
1289
+ }
1290
+ fieldDraftComment(fieldKey) {
1291
+ return this.fieldDraft(fieldKey)?.comment ?? '';
1292
+ }
1293
+ documentDraftComment(documentType) {
1294
+ return this.documentDraft(documentType)?.comment ?? '';
1295
+ }
1296
+ fieldPassed(fieldKey) {
1297
+ return this.passedFields()[fieldKey] ?? false;
1298
+ }
1299
+ documentPassed(documentType) {
1300
+ return this.passedDocuments()[documentType] ?? false;
1301
+ }
782
1302
  async startReview(applicationId) {
783
1303
  return this.runAction(async () => {
784
- const detail = await firstValueFrom(this.onboardingAdminService.startReview(applicationId));
785
- this.detail.set(detail);
1304
+ await firstValueFrom(this.onboardingAdminService.startReview(applicationId));
1305
+ await this.load(applicationId);
786
1306
  });
787
1307
  }
788
1308
  async requestMoreInfo(applicationId) {
1309
+ const issues = this.issueDrafts();
1310
+ if (issues.length === 0) {
1311
+ this.error.set('Add at least one field or document issue before requesting more information.');
1312
+ return false;
1313
+ }
789
1314
  const payload = {
790
- comment: this.reviewComment(),
791
- issues: this.issueDrafts(),
1315
+ comment: this.reviewComment().trim(),
1316
+ issues,
792
1317
  };
793
1318
  return this.runAction(async () => {
794
- const detail = await firstValueFrom(this.onboardingAdminService.needMoreInfo(applicationId, payload));
795
- this.detail.set(detail);
1319
+ await firstValueFrom(this.onboardingAdminService.needMoreInfo(applicationId, payload));
1320
+ await this.load(applicationId);
796
1321
  this.reviewComment.set('');
797
- this.issueDrafts.set([]);
1322
+ this.resetDrafts();
798
1323
  });
799
1324
  }
800
1325
  async approve(applicationId) {
1326
+ if (this.hasPendingIssues()) {
1327
+ this.error.set('Clear draft issues before approving the application.');
1328
+ return false;
1329
+ }
801
1330
  const payload = {
802
- comment: this.reviewComment(),
1331
+ comment: this.reviewComment().trim(),
803
1332
  };
804
1333
  return this.runAction(async () => {
805
- const detail = await firstValueFrom(this.onboardingAdminService.approve(applicationId, payload));
806
- this.detail.set(detail);
1334
+ await firstValueFrom(this.onboardingAdminService.approve(applicationId, payload));
1335
+ await this.load(applicationId);
807
1336
  this.reviewComment.set('');
1337
+ this.resetDrafts();
808
1338
  });
809
1339
  }
810
1340
  async fail(applicationId) {
1341
+ if (this.hasPendingIssues()) {
1342
+ this.error.set('Clear draft issues before failing the application.');
1343
+ return false;
1344
+ }
811
1345
  const payload = {
812
- comment: this.reviewComment(),
1346
+ comment: this.reviewComment().trim(),
813
1347
  };
814
1348
  return this.runAction(async () => {
815
- const detail = await firstValueFrom(this.onboardingAdminService.fail(applicationId, payload));
816
- this.detail.set(detail);
1349
+ await firstValueFrom(this.onboardingAdminService.fail(applicationId, payload));
1350
+ await this.load(applicationId);
817
1351
  this.reviewComment.set('');
1352
+ this.resetDrafts();
818
1353
  });
819
1354
  }
820
- async runAction(action) {
821
- this.saving.set(true);
822
- this.error.set(null);
1355
+ async openDocumentPreview(documentId) {
1356
+ const applicationId = this.detail()?.id;
1357
+ if (!applicationId || !documentId) {
1358
+ return false;
1359
+ }
1360
+ this.previewLoading.set(true);
1361
+ this.previewError.set(null);
1362
+ this.previewUrl.set(null);
1363
+ this.previewDocumentId.set(documentId);
823
1364
  try {
824
- await action();
1365
+ const response = await firstValueFrom(this.onboardingAdminService.getDocumentPreviewUrl(applicationId, documentId));
1366
+ this.previewUrl.set(response.previewUrl);
1367
+ this.previewDocumentId.set(response.documentId);
825
1368
  return true;
826
1369
  }
827
1370
  catch (error) {
828
- this.error.set(this.toErrorMessage(error));
1371
+ this.previewError.set(this.toPreviewErrorMessage(error));
829
1372
  return false;
830
1373
  }
831
1374
  finally {
832
- this.saving.set(false);
1375
+ this.previewLoading.set(false);
833
1376
  }
834
1377
  }
835
- toErrorMessage(error) {
836
- if (error instanceof Error && error.message) {
837
- return error.message;
838
- }
839
- return 'Unable to complete the onboarding review action.';
1378
+ clearDocumentPreview() {
1379
+ this.previewLoading.set(false);
1380
+ this.previewError.set(null);
1381
+ this.previewUrl.set(null);
1382
+ this.previewDocumentId.set(null);
840
1383
  }
841
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: AdminOnboardingReviewFacade, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
842
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: AdminOnboardingReviewFacade });
843
- }
844
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: AdminOnboardingReviewFacade, decorators: [{
845
- type: Injectable
846
- }] });
847
-
848
- class OnboardingPageShell {
849
- title = input.required(...(ngDevMode ? [{ debugName: "title" }] : []));
850
- subtitle = input('', ...(ngDevMode ? [{ debugName: "subtitle" }] : []));
851
- kicker = input('', ...(ngDevMode ? [{ debugName: "kicker" }] : []));
852
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingPageShell, deps: [], target: i0.ɵɵFactoryTarget.Component });
853
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: OnboardingPageShell, isStandalone: true, selector: "rolatech-onboarding-page-shell", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, kicker: { classPropertyName: "kicker", publicName: "kicker", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
854
- <section class="rt-onboarding-theme mx-auto w-full max-w-6xl space-y-6 bg-(--rt-base-background) px-4 py-6 text-(--rt-text-primary) sm:px-6 lg:px-8">
855
- <header class="relative overflow-hidden rounded-3xl border border-(--rt-border-color) bg-(--rt-base-background) p-6 shadow-sm sm:p-8">
856
- <div class="pointer-events-none absolute -right-10 -top-10 h-36 w-36 rounded-full bg-(--rt-10-percent-layer) blur-2xl"></div>
857
- <div class="pointer-events-none absolute -bottom-16 left-12 h-36 w-36 rounded-full bg-(--rt-10-percent-layer) blur-2xl"></div>
1384
+ clearFieldIssueDraft(fieldKey) {
1385
+ this.fieldIssueDrafts.update((current) => {
1386
+ const next = { ...current };
1387
+ delete next[fieldKey];
1388
+ return next;
1389
+ });
1390
+ }
1391
+ clearDocumentIssueDraft(documentType) {
1392
+ this.documentIssueDrafts.update((current) => {
1393
+ const next = { ...current };
1394
+ delete next[documentType];
1395
+ return next;
1396
+ });
1397
+ }
1398
+ buildIssueDrafts() {
1399
+ const fieldIssues = Object.entries(this.fieldIssueDrafts()).flatMap(([fieldKey, draft]) => draft.comment.trim()
1400
+ ? [
1401
+ {
1402
+ issueType: 'FIELD',
1403
+ fieldKey,
1404
+ comment: draft.comment.trim(),
1405
+ },
1406
+ ]
1407
+ : []);
1408
+ const documentIssues = Object.entries(this.documentIssueDrafts()).flatMap(([documentType, draft]) => draft?.comment.trim()
1409
+ ? [
1410
+ {
1411
+ issueType: 'DOCUMENT',
1412
+ documentType: documentType,
1413
+ comment: draft.comment.trim(),
1414
+ },
1415
+ ]
1416
+ : []);
1417
+ return [...fieldIssues, ...documentIssues];
1418
+ }
1419
+ resetDrafts() {
1420
+ this.fieldIssueDrafts.set({});
1421
+ this.documentIssueDrafts.set({});
1422
+ this.passedFields.set({});
1423
+ this.passedDocuments.set({});
1424
+ }
1425
+ normalizeDraft(draft) {
1426
+ if (typeof draft === 'string') {
1427
+ return draft.trim()
1428
+ ? {
1429
+ optionKey: 'manual',
1430
+ comment: draft,
1431
+ }
1432
+ : null;
1433
+ }
1434
+ if (!draft) {
1435
+ return null;
1436
+ }
1437
+ if (!draft.optionKey?.trim()) {
1438
+ return null;
1439
+ }
1440
+ return {
1441
+ optionKey: draft.optionKey,
1442
+ comment: draft.comment ?? '',
1443
+ };
1444
+ }
1445
+ async runAction(action) {
1446
+ this.saving.set(true);
1447
+ this.error.set(null);
1448
+ try {
1449
+ await action();
1450
+ return true;
1451
+ }
1452
+ catch (error) {
1453
+ this.error.set(this.toErrorMessage(error));
1454
+ return false;
1455
+ }
1456
+ finally {
1457
+ this.saving.set(false);
1458
+ }
1459
+ }
1460
+ toErrorMessage(error) {
1461
+ if (error instanceof Error && error.message) {
1462
+ return error.message;
1463
+ }
1464
+ return 'Unable to complete the onboarding review action.';
1465
+ }
1466
+ toPreviewErrorMessage(error) {
1467
+ if (error instanceof Error && error.message) {
1468
+ return error.message;
1469
+ }
1470
+ return 'Unable to load the document preview.';
1471
+ }
1472
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: AdminOnboardingReviewFacade, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1473
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: AdminOnboardingReviewFacade });
1474
+ }
1475
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: AdminOnboardingReviewFacade, decorators: [{
1476
+ type: Injectable
1477
+ }] });
858
1478
 
1479
+ class OnboardingPageShell {
1480
+ title = input.required(...(ngDevMode ? [{ debugName: "title" }] : []));
1481
+ subtitle = input('', ...(ngDevMode ? [{ debugName: "subtitle" }] : []));
1482
+ kicker = input('', ...(ngDevMode ? [{ debugName: "kicker" }] : []));
1483
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingPageShell, deps: [], target: i0.ɵɵFactoryTarget.Component });
1484
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: OnboardingPageShell, isStandalone: true, selector: "rolatech-onboarding-page-shell", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, kicker: { classPropertyName: "kicker", publicName: "kicker", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
1485
+ <section
1486
+ class="rt-onboarding-theme mx-auto w-full max-w-6xl space-y-6 bg-(--rt-base-background) px-4 py-6 text-(--rt-text-primary) sm:px-6 lg:px-8"
1487
+ >
1488
+ <header
1489
+ class="relative overflow-hidden rounded-3xl border border-(--rt-border-color) bg-(--rt-base-background) p-6 shadow-sm sm:p-8"
1490
+ >
859
1491
  <div class="relative flex flex-wrap items-start justify-between gap-4">
860
1492
  <div class="flex min-w-0 items-start gap-3">
861
1493
  <div class="rt-onboarding-header-leading">
@@ -889,11 +1521,12 @@ class OnboardingPageShell {
889
1521
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingPageShell, decorators: [{
890
1522
  type: Component,
891
1523
  args: [{ selector: 'rolatech-onboarding-page-shell', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: { class: 'block' }, template: `
892
- <section class="rt-onboarding-theme mx-auto w-full max-w-6xl space-y-6 bg-(--rt-base-background) px-4 py-6 text-(--rt-text-primary) sm:px-6 lg:px-8">
893
- <header class="relative overflow-hidden rounded-3xl border border-(--rt-border-color) bg-(--rt-base-background) p-6 shadow-sm sm:p-8">
894
- <div class="pointer-events-none absolute -right-10 -top-10 h-36 w-36 rounded-full bg-(--rt-10-percent-layer) blur-2xl"></div>
895
- <div class="pointer-events-none absolute -bottom-16 left-12 h-36 w-36 rounded-full bg-(--rt-10-percent-layer) blur-2xl"></div>
896
-
1524
+ <section
1525
+ class="rt-onboarding-theme mx-auto w-full max-w-6xl space-y-6 bg-(--rt-base-background) px-4 py-6 text-(--rt-text-primary) sm:px-6 lg:px-8"
1526
+ >
1527
+ <header
1528
+ class="relative overflow-hidden rounded-3xl border border-(--rt-border-color) bg-(--rt-base-background) p-6 shadow-sm sm:p-8"
1529
+ >
897
1530
  <div class="relative flex flex-wrap items-start justify-between gap-4">
898
1531
  <div class="flex min-w-0 items-start gap-3">
899
1532
  <div class="rt-onboarding-header-leading">
@@ -933,14 +1566,14 @@ class OnboardingStatusBadge {
933
1566
  badgeClass = computed(() => {
934
1567
  switch (this.status()) {
935
1568
  case 'APPROVED':
936
- return 'border-emerald-200 bg-emerald-50 text-emerald-700';
1569
+ return 'border-(--rt-border-color) bg-(--rt-raised-background) text-emerald-700';
937
1570
  case 'FAILED':
938
- return 'border-rose-200 bg-rose-50 text-rose-700';
1571
+ return 'border-(--rt-border-color) bg-(--rt-raised-background) text-rose-700';
939
1572
  case 'NEED_MORE_INFO':
940
- return 'border-amber-200 bg-amber-50 text-amber-700';
1573
+ return 'border-(--rt-border-color) bg-(--rt-raised-background) text-amber-700';
941
1574
  case 'SUBMITTED':
942
1575
  case 'IN_REVIEW':
943
- return 'border-sky-200 bg-sky-50 text-sky-700';
1576
+ return 'border-(--rt-border-color) bg-(--rt-raised-background) text-sky-700';
944
1577
  case 'IN_PROGRESS':
945
1578
  case 'DRAFT':
946
1579
  return 'border-(--rt-border-color) bg-(--rt-raised-background) text-(--rt-text-secondary)';
@@ -982,7 +1615,7 @@ class OnboardingStepNav {
982
1615
  activeStep = input('', ...(ngDevMode ? [{ debugName: "activeStep" }] : []));
983
1616
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingStepNav, deps: [], target: i0.ɵɵFactoryTarget.Component });
984
1617
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: OnboardingStepNav, isStandalone: true, selector: "rolatech-onboarding-step-nav", inputs: { steps: { classPropertyName: "steps", publicName: "steps", isSignal: true, isRequired: false, transformFunction: null }, activeStep: { classPropertyName: "activeStep", publicName: "activeStep", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
985
- <nav class="grid gap-2 sm:grid-cols-2 lg:grid-cols-6" aria-label="Onboarding steps">
1618
+ <nav class="grid gap-2 sm:grid-cols-2 lg:grid-cols-3" aria-label="Onboarding steps">
986
1619
  @for (step of steps(); track step.step) {
987
1620
  <a
988
1621
  [routerLink]="step.accessible ? step.link : null"
@@ -1018,7 +1651,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
1018
1651
  encapsulation: ViewEncapsulation.None,
1019
1652
  host: { class: 'block' },
1020
1653
  template: `
1021
- <nav class="grid gap-2 sm:grid-cols-2 lg:grid-cols-6" aria-label="Onboarding steps">
1654
+ <nav class="grid gap-2 sm:grid-cols-2 lg:grid-cols-3" aria-label="Onboarding steps">
1022
1655
  @for (step of steps(); track step.step) {
1023
1656
  <a
1024
1657
  [routerLink]="step.accessible ? step.link : null"
@@ -1051,10 +1684,28 @@ class OnboardingDocumentUploader {
1051
1684
  helperText = input('Upload a clear and readable file.', ...(ngDevMode ? [{ debugName: "helperText" }] : []));
1052
1685
  documentType = input.required(...(ngDevMode ? [{ debugName: "documentType" }] : []));
1053
1686
  document = input(null, ...(ngDevMode ? [{ debugName: "document" }] : []));
1687
+ warning = input(null, ...(ngDevMode ? [{ debugName: "warning" }] : []));
1054
1688
  comment = input(null, ...(ngDevMode ? [{ debugName: "comment" }] : []));
1055
1689
  busy = input(false, ...(ngDevMode ? [{ debugName: "busy" }] : []));
1690
+ locked = input(false, ...(ngDevMode ? [{ debugName: "locked" }] : []));
1691
+ uploading = input(false, ...(ngDevMode ? [{ debugName: "uploading" }] : []));
1692
+ progress = input(0, ...(ngDevMode ? [{ debugName: "progress" }] : []));
1693
+ pendingFilename = input(null, ...(ngDevMode ? [{ debugName: "pendingFilename" }] : []));
1694
+ uploadError = input(null, ...(ngDevMode ? [{ debugName: "uploadError" }] : []));
1056
1695
  acceptedTypes = input('image/*,application/pdf', ...(ngDevMode ? [{ debugName: "acceptedTypes" }] : []));
1696
+ canPreview = input(false, ...(ngDevMode ? [{ debugName: "canPreview" }] : []));
1697
+ previewBusy = input(false, ...(ngDevMode ? [{ debugName: "previewBusy" }] : []));
1057
1698
  fileSelected = output();
1699
+ previewRequested = output();
1700
+ buttonLabel = computed(() => {
1701
+ if (this.locked()) {
1702
+ return 'Locked';
1703
+ }
1704
+ if (this.uploading()) {
1705
+ return 'Uploading...';
1706
+ }
1707
+ return this.document() ? 'Replace' : 'Upload';
1708
+ }, ...(ngDevMode ? [{ debugName: "buttonLabel" }] : []));
1058
1709
  onFileChange(event) {
1059
1710
  const target = event.target;
1060
1711
  const file = target.files?.[0];
@@ -1069,7 +1720,7 @@ class OnboardingDocumentUploader {
1069
1720
  target.value = '';
1070
1721
  }
1071
1722
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingDocumentUploader, deps: [], target: i0.ɵɵFactoryTarget.Component });
1072
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: OnboardingDocumentUploader, isStandalone: true, selector: "rolatech-onboarding-document-uploader", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, documentType: { classPropertyName: "documentType", publicName: "documentType", isSignal: true, isRequired: true, transformFunction: null }, document: { classPropertyName: "document", publicName: "document", isSignal: true, isRequired: false, transformFunction: null }, comment: { classPropertyName: "comment", publicName: "comment", isSignal: true, isRequired: false, transformFunction: null }, busy: { classPropertyName: "busy", publicName: "busy", isSignal: true, isRequired: false, transformFunction: null }, acceptedTypes: { classPropertyName: "acceptedTypes", publicName: "acceptedTypes", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fileSelected: "fileSelected" }, host: { classAttribute: "block" }, ngImport: i0, template: `
1723
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: OnboardingDocumentUploader, isStandalone: true, selector: "rolatech-onboarding-document-uploader", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, documentType: { classPropertyName: "documentType", publicName: "documentType", isSignal: true, isRequired: true, transformFunction: null }, document: { classPropertyName: "document", publicName: "document", isSignal: true, isRequired: false, transformFunction: null }, warning: { classPropertyName: "warning", publicName: "warning", isSignal: true, isRequired: false, transformFunction: null }, comment: { classPropertyName: "comment", publicName: "comment", isSignal: true, isRequired: false, transformFunction: null }, busy: { classPropertyName: "busy", publicName: "busy", isSignal: true, isRequired: false, transformFunction: null }, locked: { classPropertyName: "locked", publicName: "locked", isSignal: true, isRequired: false, transformFunction: null }, uploading: { classPropertyName: "uploading", publicName: "uploading", isSignal: true, isRequired: false, transformFunction: null }, progress: { classPropertyName: "progress", publicName: "progress", isSignal: true, isRequired: false, transformFunction: null }, pendingFilename: { classPropertyName: "pendingFilename", publicName: "pendingFilename", isSignal: true, isRequired: false, transformFunction: null }, uploadError: { classPropertyName: "uploadError", publicName: "uploadError", isSignal: true, isRequired: false, transformFunction: null }, acceptedTypes: { classPropertyName: "acceptedTypes", publicName: "acceptedTypes", isSignal: true, isRequired: false, transformFunction: null }, canPreview: { classPropertyName: "canPreview", publicName: "canPreview", isSignal: true, isRequired: false, transformFunction: null }, previewBusy: { classPropertyName: "previewBusy", publicName: "previewBusy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fileSelected: "fileSelected", previewRequested: "previewRequested" }, host: { classAttribute: "block" }, ngImport: i0, template: `
1073
1724
  <section class="rounded-2xl border border-(--rt-border-color) bg-(--rt-base-background) p-4 shadow-sm">
1074
1725
  <div class="flex flex-wrap items-start justify-between gap-4">
1075
1726
  <div>
@@ -1081,30 +1732,69 @@ class OnboardingDocumentUploader {
1081
1732
  {{ existing.originalFilename }}
1082
1733
  <span class="text-(--rt-text-secondary)">(v{{ existing.version }})</span>
1083
1734
  </p>
1735
+ } @else if (pendingFilename()) {
1736
+ <p class="mt-2 text-sm text-(--rt-text-primary)">{{ pendingFilename() }}</p>
1084
1737
  }
1085
1738
  </div>
1086
1739
 
1087
- <label
1088
- class="inline-flex cursor-pointer items-center rounded-xl border border-(--rt-border-color) px-3 py-2 text-sm font-medium text-(--rt-text-primary) transition hover:bg-(--rt-raised-background)"
1089
- [class.pointer-events-none]="busy()"
1090
- [class.opacity-60]="busy()"
1091
- >
1092
- {{ document() ? 'Replace' : 'Upload' }}
1093
- <input
1094
- type="file"
1095
- class="hidden"
1096
- [accept]="acceptedTypes()"
1097
- [disabled]="busy()"
1098
- (change)="onFileChange($event)"
1099
- />
1100
- </label>
1740
+ <div class="flex flex-wrap items-center gap-2">
1741
+ @if (document() && canPreview()) {
1742
+ <button
1743
+ type="button"
1744
+ class="rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm font-medium text-(--rt-text-primary) transition hover:bg-(--rt-base-background) disabled:cursor-not-allowed disabled:opacity-60"
1745
+ [disabled]="previewBusy()"
1746
+ (click)="previewRequested.emit()"
1747
+ >
1748
+ {{ previewBusy() ? 'Loading...' : 'Preview' }}
1749
+ </button>
1750
+ }
1751
+
1752
+ <label
1753
+ class="inline-flex cursor-pointer items-center rounded-xl border border-(--rt-border-color) px-3 py-2 text-sm font-medium text-(--rt-text-primary) transition hover:bg-(--rt-raised-background)"
1754
+ [class.pointer-events-none]="busy() || locked()"
1755
+ [class.opacity-60]="busy() || locked()"
1756
+ >
1757
+ {{ buttonLabel() }}
1758
+ <input
1759
+ type="file"
1760
+ class="hidden"
1761
+ [accept]="acceptedTypes()"
1762
+ [disabled]="busy() || locked()"
1763
+ (change)="onFileChange($event)"
1764
+ />
1765
+ </label>
1766
+ </div>
1101
1767
  </div>
1102
1768
 
1769
+ @if (uploading()) {
1770
+ <div class="mt-3 space-y-2">
1771
+ <div class="flex items-center justify-between gap-3 text-xs text-(--rt-text-secondary)">
1772
+ <span>Uploading</span>
1773
+ <span>{{ progress() }}%</span>
1774
+ </div>
1775
+ <div class="h-2 overflow-hidden rounded-full bg-(--rt-raised-background)">
1776
+ <div class="h-full rounded-full bg-(--rt-brand-color) transition-all duration-200" [style.width.%]="progress()"></div>
1777
+ </div>
1778
+ </div>
1779
+ }
1780
+
1781
+ @if (warning()) {
1782
+ <div class="mt-3 rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm text-amber-800">
1783
+ {{ warning() }}
1784
+ </div>
1785
+ }
1786
+
1103
1787
  @if (comment()) {
1104
- <div class="mt-3 rounded-xl border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-800">
1788
+ <div class="mt-3 rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm text-amber-800">
1105
1789
  {{ comment() }}
1106
1790
  </div>
1107
1791
  }
1792
+
1793
+ @if (uploadError()) {
1794
+ <div class="mt-3 rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm text-rose-700">
1795
+ {{ uploadError() }}
1796
+ </div>
1797
+ }
1108
1798
  </section>
1109
1799
  `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1110
1800
  }
@@ -1128,34 +1818,243 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
1128
1818
  {{ existing.originalFilename }}
1129
1819
  <span class="text-(--rt-text-secondary)">(v{{ existing.version }})</span>
1130
1820
  </p>
1821
+ } @else if (pendingFilename()) {
1822
+ <p class="mt-2 text-sm text-(--rt-text-primary)">{{ pendingFilename() }}</p>
1131
1823
  }
1132
1824
  </div>
1133
1825
 
1134
- <label
1135
- class="inline-flex cursor-pointer items-center rounded-xl border border-(--rt-border-color) px-3 py-2 text-sm font-medium text-(--rt-text-primary) transition hover:bg-(--rt-raised-background)"
1136
- [class.pointer-events-none]="busy()"
1137
- [class.opacity-60]="busy()"
1138
- >
1139
- {{ document() ? 'Replace' : 'Upload' }}
1140
- <input
1141
- type="file"
1142
- class="hidden"
1143
- [accept]="acceptedTypes()"
1144
- [disabled]="busy()"
1145
- (change)="onFileChange($event)"
1146
- />
1147
- </label>
1826
+ <div class="flex flex-wrap items-center gap-2">
1827
+ @if (document() && canPreview()) {
1828
+ <button
1829
+ type="button"
1830
+ class="rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm font-medium text-(--rt-text-primary) transition hover:bg-(--rt-base-background) disabled:cursor-not-allowed disabled:opacity-60"
1831
+ [disabled]="previewBusy()"
1832
+ (click)="previewRequested.emit()"
1833
+ >
1834
+ {{ previewBusy() ? 'Loading...' : 'Preview' }}
1835
+ </button>
1836
+ }
1837
+
1838
+ <label
1839
+ class="inline-flex cursor-pointer items-center rounded-xl border border-(--rt-border-color) px-3 py-2 text-sm font-medium text-(--rt-text-primary) transition hover:bg-(--rt-raised-background)"
1840
+ [class.pointer-events-none]="busy() || locked()"
1841
+ [class.opacity-60]="busy() || locked()"
1842
+ >
1843
+ {{ buttonLabel() }}
1844
+ <input
1845
+ type="file"
1846
+ class="hidden"
1847
+ [accept]="acceptedTypes()"
1848
+ [disabled]="busy() || locked()"
1849
+ (change)="onFileChange($event)"
1850
+ />
1851
+ </label>
1852
+ </div>
1148
1853
  </div>
1149
1854
 
1855
+ @if (uploading()) {
1856
+ <div class="mt-3 space-y-2">
1857
+ <div class="flex items-center justify-between gap-3 text-xs text-(--rt-text-secondary)">
1858
+ <span>Uploading</span>
1859
+ <span>{{ progress() }}%</span>
1860
+ </div>
1861
+ <div class="h-2 overflow-hidden rounded-full bg-(--rt-raised-background)">
1862
+ <div class="h-full rounded-full bg-(--rt-brand-color) transition-all duration-200" [style.width.%]="progress()"></div>
1863
+ </div>
1864
+ </div>
1865
+ }
1866
+
1867
+ @if (warning()) {
1868
+ <div class="mt-3 rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm text-amber-800">
1869
+ {{ warning() }}
1870
+ </div>
1871
+ }
1872
+
1150
1873
  @if (comment()) {
1151
- <div class="mt-3 rounded-xl border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-800">
1874
+ <div class="mt-3 rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm text-amber-800">
1152
1875
  {{ comment() }}
1153
1876
  </div>
1154
1877
  }
1878
+
1879
+ @if (uploadError()) {
1880
+ <div class="mt-3 rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm text-rose-700">
1881
+ {{ uploadError() }}
1882
+ </div>
1883
+ }
1155
1884
  </section>
1156
1885
  `,
1157
1886
  }]
1158
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], documentType: [{ type: i0.Input, args: [{ isSignal: true, alias: "documentType", required: true }] }], document: [{ type: i0.Input, args: [{ isSignal: true, alias: "document", required: false }] }], comment: [{ type: i0.Input, args: [{ isSignal: true, alias: "comment", required: false }] }], busy: [{ type: i0.Input, args: [{ isSignal: true, alias: "busy", required: false }] }], acceptedTypes: [{ type: i0.Input, args: [{ isSignal: true, alias: "acceptedTypes", required: false }] }], fileSelected: [{ type: i0.Output, args: ["fileSelected"] }] } });
1887
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], documentType: [{ type: i0.Input, args: [{ isSignal: true, alias: "documentType", required: true }] }], document: [{ type: i0.Input, args: [{ isSignal: true, alias: "document", required: false }] }], warning: [{ type: i0.Input, args: [{ isSignal: true, alias: "warning", required: false }] }], comment: [{ type: i0.Input, args: [{ isSignal: true, alias: "comment", required: false }] }], busy: [{ type: i0.Input, args: [{ isSignal: true, alias: "busy", required: false }] }], locked: [{ type: i0.Input, args: [{ isSignal: true, alias: "locked", required: false }] }], uploading: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploading", required: false }] }], progress: [{ type: i0.Input, args: [{ isSignal: true, alias: "progress", required: false }] }], pendingFilename: [{ type: i0.Input, args: [{ isSignal: true, alias: "pendingFilename", required: false }] }], uploadError: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadError", required: false }] }], acceptedTypes: [{ type: i0.Input, args: [{ isSignal: true, alias: "acceptedTypes", required: false }] }], canPreview: [{ type: i0.Input, args: [{ isSignal: true, alias: "canPreview", required: false }] }], previewBusy: [{ type: i0.Input, args: [{ isSignal: true, alias: "previewBusy", required: false }] }], fileSelected: [{ type: i0.Output, args: ["fileSelected"] }], previewRequested: [{ type: i0.Output, args: ["previewRequested"] }] } });
1888
+
1889
+ class OnboardingDocumentPreviewDialog {
1890
+ open = input(false, ...(ngDevMode ? [{ debugName: "open" }] : []));
1891
+ title = input(null, ...(ngDevMode ? [{ debugName: "title" }] : []));
1892
+ subtitle = input(null, ...(ngDevMode ? [{ debugName: "subtitle" }] : []));
1893
+ url = input(null, ...(ngDevMode ? [{ debugName: "url" }] : []));
1894
+ loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
1895
+ error = input(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
1896
+ closeRequested = output();
1897
+ sanitizer = inject(DomSanitizer);
1898
+ safeUrl = computed(() => {
1899
+ const url = this.url();
1900
+ return url ? this.sanitizer.bypassSecurityTrustResourceUrl(url) : null;
1901
+ }, ...(ngDevMode ? [{ debugName: "safeUrl" }] : []));
1902
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingDocumentPreviewDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
1903
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: OnboardingDocumentPreviewDialog, isStandalone: true, selector: "rolatech-onboarding-document-preview-dialog", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, url: { classPropertyName: "url", publicName: "url", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closeRequested: "closeRequested" }, host: { classAttribute: "block" }, ngImport: i0, template: `
1904
+ @if (open()) {
1905
+ <div
1906
+ class="fixed inset-0 z-9200 flex items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
1907
+ (click)="closeRequested.emit()"
1908
+ >
1909
+ <section
1910
+ class="flex max-h-[92vh] w-full max-w-6xl flex-col overflow-hidden rounded-xl border border-(--rt-border-color) bg-(--rt-base-background) shadow-2xl"
1911
+ (click)="$event.stopPropagation()"
1912
+ >
1913
+ <header
1914
+ class="flex flex-wrap items-start justify-between gap-4 border-b border-(--rt-border-color) bg-(--rt-raised-background) px-5 py-4"
1915
+ >
1916
+ <div class="min-w-0 space-y-1">
1917
+ <p class="text-[11px] font-semibold uppercase tracking-[0.24em] text-(--rt-text-secondary)">Document Preview</p>
1918
+ <h2 class="truncate text-lg font-semibold text-(--rt-text-primary)">{{ title() || 'Onboarding document' }}</h2>
1919
+ @if (subtitle()) {
1920
+ <p class="text-sm text-(--rt-text-secondary)">{{ subtitle() }}</p>
1921
+ }
1922
+ </div>
1923
+
1924
+ <div class="flex flex-wrap items-center gap-2">
1925
+ @if (url()) {
1926
+ <a
1927
+ [href]="url()!"
1928
+ target="_blank"
1929
+ rel="noreferrer"
1930
+ class="rounded-full border border-(--rt-border-color) bg-(--rt-base-background) px-3 py-1.5 text-xs font-semibold text-(--rt-text-primary) transition hover:bg-(--rt-raised-background)"
1931
+ >
1932
+ Open in new tab
1933
+ </a>
1934
+ }
1935
+
1936
+ <button
1937
+ type="button"
1938
+ class="rounded-full border border-(--rt-border-color) bg-(--rt-base-background) px-3 py-1.5 text-xs font-semibold text-(--rt-text-primary) transition hover:bg-(--rt-raised-background)"
1939
+ (click)="closeRequested.emit()"
1940
+ >
1941
+ Close
1942
+ </button>
1943
+ </div>
1944
+ </header>
1945
+
1946
+ <div class="min-h-[420px] flex-1 bg-(--rt-raised-background)">
1947
+ @if (loading()) {
1948
+ <div class="flex h-full items-center justify-center px-6 text-sm text-(--rt-text-secondary)">
1949
+ Loading preview...
1950
+ </div>
1951
+ } @else if (error()) {
1952
+ <div class="flex h-full items-center justify-center px-6">
1953
+ <div
1954
+ class="max-w-md rounded-3xl border border-(--rt-border-color) bg-(--rt-base-background) px-5 py-4 text-sm text-rose-700"
1955
+ >
1956
+ {{ error() }}
1957
+ </div>
1958
+ </div>
1959
+ } @else if (safeUrl()) {
1960
+ <iframe
1961
+ class="h-full min-h-[420px] w-full bg-(--rt-base-background)"
1962
+ [src]="safeUrl()"
1963
+ title="Onboarding document preview"
1964
+ ></iframe>
1965
+ } @else {
1966
+ <div class="flex h-full items-center justify-center px-6 text-sm text-(--rt-text-secondary)">
1967
+ Preview unavailable for this document.
1968
+ </div>
1969
+ }
1970
+ </div>
1971
+ </section>
1972
+ </div>
1973
+ }
1974
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1975
+ }
1976
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingDocumentPreviewDialog, decorators: [{
1977
+ type: Component,
1978
+ args: [{
1979
+ selector: 'rolatech-onboarding-document-preview-dialog',
1980
+ standalone: true,
1981
+ changeDetection: ChangeDetectionStrategy.OnPush,
1982
+ encapsulation: ViewEncapsulation.None,
1983
+ host: { class: 'block' },
1984
+ template: `
1985
+ @if (open()) {
1986
+ <div
1987
+ class="fixed inset-0 z-9200 flex items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
1988
+ (click)="closeRequested.emit()"
1989
+ >
1990
+ <section
1991
+ class="flex max-h-[92vh] w-full max-w-6xl flex-col overflow-hidden rounded-xl border border-(--rt-border-color) bg-(--rt-base-background) shadow-2xl"
1992
+ (click)="$event.stopPropagation()"
1993
+ >
1994
+ <header
1995
+ class="flex flex-wrap items-start justify-between gap-4 border-b border-(--rt-border-color) bg-(--rt-raised-background) px-5 py-4"
1996
+ >
1997
+ <div class="min-w-0 space-y-1">
1998
+ <p class="text-[11px] font-semibold uppercase tracking-[0.24em] text-(--rt-text-secondary)">Document Preview</p>
1999
+ <h2 class="truncate text-lg font-semibold text-(--rt-text-primary)">{{ title() || 'Onboarding document' }}</h2>
2000
+ @if (subtitle()) {
2001
+ <p class="text-sm text-(--rt-text-secondary)">{{ subtitle() }}</p>
2002
+ }
2003
+ </div>
2004
+
2005
+ <div class="flex flex-wrap items-center gap-2">
2006
+ @if (url()) {
2007
+ <a
2008
+ [href]="url()!"
2009
+ target="_blank"
2010
+ rel="noreferrer"
2011
+ class="rounded-full border border-(--rt-border-color) bg-(--rt-base-background) px-3 py-1.5 text-xs font-semibold text-(--rt-text-primary) transition hover:bg-(--rt-raised-background)"
2012
+ >
2013
+ Open in new tab
2014
+ </a>
2015
+ }
2016
+
2017
+ <button
2018
+ type="button"
2019
+ class="rounded-full border border-(--rt-border-color) bg-(--rt-base-background) px-3 py-1.5 text-xs font-semibold text-(--rt-text-primary) transition hover:bg-(--rt-raised-background)"
2020
+ (click)="closeRequested.emit()"
2021
+ >
2022
+ Close
2023
+ </button>
2024
+ </div>
2025
+ </header>
2026
+
2027
+ <div class="min-h-[420px] flex-1 bg-(--rt-raised-background)">
2028
+ @if (loading()) {
2029
+ <div class="flex h-full items-center justify-center px-6 text-sm text-(--rt-text-secondary)">
2030
+ Loading preview...
2031
+ </div>
2032
+ } @else if (error()) {
2033
+ <div class="flex h-full items-center justify-center px-6">
2034
+ <div
2035
+ class="max-w-md rounded-3xl border border-(--rt-border-color) bg-(--rt-base-background) px-5 py-4 text-sm text-rose-700"
2036
+ >
2037
+ {{ error() }}
2038
+ </div>
2039
+ </div>
2040
+ } @else if (safeUrl()) {
2041
+ <iframe
2042
+ class="h-full min-h-[420px] w-full bg-(--rt-base-background)"
2043
+ [src]="safeUrl()"
2044
+ title="Onboarding document preview"
2045
+ ></iframe>
2046
+ } @else {
2047
+ <div class="flex h-full items-center justify-center px-6 text-sm text-(--rt-text-secondary)">
2048
+ Preview unavailable for this document.
2049
+ </div>
2050
+ }
2051
+ </div>
2052
+ </section>
2053
+ </div>
2054
+ }
2055
+ `,
2056
+ }]
2057
+ }], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], url: [{ type: i0.Input, args: [{ isSignal: true, alias: "url", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], closeRequested: [{ type: i0.Output, args: ["closeRequested"] }] } });
1159
2058
 
1160
2059
  class OnboardingIssueList {
1161
2060
  issues = input([], ...(ngDevMode ? [{ debugName: "issues" }] : []));
@@ -1176,11 +2075,13 @@ class OnboardingIssueList {
1176
2075
  } @else {
1177
2076
  <div class="space-y-3">
1178
2077
  @for (issue of issues(); track issue.id) {
1179
- <article class="rounded-xl border border-amber-200 bg-amber-50 p-3">
2078
+ <article class="rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) p-3">
1180
2079
  <div class="flex flex-wrap items-center justify-between gap-2">
1181
2080
  <p class="text-xs font-semibold uppercase tracking-wide text-amber-800">{{ issueTypeLabel(issue.issueType) }}</p>
1182
2081
  @if (issue.resolved) {
1183
- <span class="rounded-full border border-emerald-200 bg-emerald-50 px-2 py-0.5 text-xs font-semibold text-emerald-700">
2082
+ <span
2083
+ class="rounded-full border border-(--rt-border-color) bg-(--rt-base-background) px-2 py-0.5 text-xs font-semibold text-emerald-700"
2084
+ >
1184
2085
  Resolved
1185
2086
  </span>
1186
2087
  }
@@ -1224,11 +2125,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
1224
2125
  } @else {
1225
2126
  <div class="space-y-3">
1226
2127
  @for (issue of issues(); track issue.id) {
1227
- <article class="rounded-xl border border-amber-200 bg-amber-50 p-3">
2128
+ <article class="rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) p-3">
1228
2129
  <div class="flex flex-wrap items-center justify-between gap-2">
1229
2130
  <p class="text-xs font-semibold uppercase tracking-wide text-amber-800">{{ issueTypeLabel(issue.issueType) }}</p>
1230
2131
  @if (issue.resolved) {
1231
- <span class="rounded-full border border-emerald-200 bg-emerald-50 px-2 py-0.5 text-xs font-semibold text-emerald-700">
2132
+ <span
2133
+ class="rounded-full border border-(--rt-border-color) bg-(--rt-base-background) px-2 py-0.5 text-xs font-semibold text-emerald-700"
2134
+ >
1232
2135
  Resolved
1233
2136
  </span>
1234
2137
  }
@@ -1487,9 +2390,1124 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
1487
2390
  }]
1488
2391
  }], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], zIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "zIndex", required: false }] }], closeRequested: [{ type: i0.Output, args: ["closeRequested"] }] } });
1489
2392
 
2393
+ class OnboardingReviewIssuesBanner {
2394
+ issues = input([], ...(ngDevMode ? [{ debugName: "issues" }] : []));
2395
+ status = input(null, ...(ngDevMode ? [{ debugName: "status" }] : []));
2396
+ unresolvedCount = computed(() => this.issues().filter((issue) => !issue.resolved).length, ...(ngDevMode ? [{ debugName: "unresolvedCount" }] : []));
2397
+ eyebrow = computed(() => (this.status() === 'NEED_MORE_INFO' ? 'More Information Required' : 'Review Status'), ...(ngDevMode ? [{ debugName: "eyebrow" }] : []));
2398
+ title = computed(() => this.status() === 'NEED_MORE_INFO'
2399
+ ? 'Review feedback is blocking resubmission.'
2400
+ : 'The application is now waiting on reviewer action.', ...(ngDevMode ? [{ debugName: "title" }] : []));
2401
+ description = computed(() => this.status() === 'NEED_MORE_INFO'
2402
+ ? 'Only the flagged fields and documents should be updated. Approved items stay read-only until review is complete.'
2403
+ : 'You can monitor status updates and any reviewer feedback from this flow.', ...(ngDevMode ? [{ debugName: "description" }] : []));
2404
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingReviewIssuesBanner, deps: [], target: i0.ɵɵFactoryTarget.Component });
2405
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.1", type: OnboardingReviewIssuesBanner, isStandalone: true, selector: "rolatech-onboarding-review-issues-banner", inputs: { issues: { classPropertyName: "issues", publicName: "issues", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
2406
+ <section
2407
+ class="rounded-2xl border border-(--rt-border-color) bg-(--rt-base-background) px-4 py-4 shadow-sm"
2408
+ [class]="status() === 'NEED_MORE_INFO' ? 'text-amber-950' : 'text-sky-950'"
2409
+ >
2410
+ <div class="flex flex-wrap items-start justify-between gap-3">
2411
+ <div class="space-y-1">
2412
+ <p class="text-xs font-semibold uppercase tracking-[0.2em]">{{ eyebrow() }}</p>
2413
+ <h3 class="text-base font-semibold">{{ title() }}</h3>
2414
+ <p class="text-sm opacity-80">{{ description() }}</p>
2415
+ </div>
2416
+
2417
+ <span class="rounded-full border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-1 text-xs font-semibold">
2418
+ {{ unresolvedCount() }} open
2419
+ </span>
2420
+ </div>
2421
+ </section>
2422
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
2423
+ }
2424
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingReviewIssuesBanner, decorators: [{
2425
+ type: Component,
2426
+ args: [{
2427
+ selector: 'rolatech-onboarding-review-issues-banner',
2428
+ standalone: true,
2429
+ changeDetection: ChangeDetectionStrategy.OnPush,
2430
+ encapsulation: ViewEncapsulation.None,
2431
+ host: { class: 'block' },
2432
+ template: `
2433
+ <section
2434
+ class="rounded-2xl border border-(--rt-border-color) bg-(--rt-base-background) px-4 py-4 shadow-sm"
2435
+ [class]="status() === 'NEED_MORE_INFO' ? 'text-amber-950' : 'text-sky-950'"
2436
+ >
2437
+ <div class="flex flex-wrap items-start justify-between gap-3">
2438
+ <div class="space-y-1">
2439
+ <p class="text-xs font-semibold uppercase tracking-[0.2em]">{{ eyebrow() }}</p>
2440
+ <h3 class="text-base font-semibold">{{ title() }}</h3>
2441
+ <p class="text-sm opacity-80">{{ description() }}</p>
2442
+ </div>
2443
+
2444
+ <span class="rounded-full border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-1 text-xs font-semibold">
2445
+ {{ unresolvedCount() }} open
2446
+ </span>
2447
+ </div>
2448
+ </section>
2449
+ `,
2450
+ }]
2451
+ }], propDecorators: { issues: [{ type: i0.Input, args: [{ isSignal: true, alias: "issues", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }] } });
2452
+
2453
+ class OnboardingQualificationUploadSection {
2454
+ documentSpecs = input([], ...(ngDevMode ? [{ debugName: "documentSpecs" }] : []));
2455
+ documents = input([], ...(ngDevMode ? [{ debugName: "documents" }] : []));
2456
+ issueComments = input({}, ...(ngDevMode ? [{ debugName: "issueComments" }] : []));
2457
+ lockedDocuments = input([], ...(ngDevMode ? [{ debugName: "lockedDocuments" }] : []));
2458
+ uploadStates = input({}, ...(ngDevMode ? [{ debugName: "uploadStates" }] : []));
2459
+ busy = input(false, ...(ngDevMode ? [{ debugName: "busy" }] : []));
2460
+ fileSelected = output();
2461
+ uploadedRequiredCount = computed(() => {
2462
+ const uploadedTypes = new Set(this.documents().map((item) => item.documentType));
2463
+ return this.documentSpecs().filter((spec) => uploadedTypes.has(spec.type)).length;
2464
+ }, ...(ngDevMode ? [{ debugName: "uploadedRequiredCount" }] : []));
2465
+ documentFor(documentType) {
2466
+ return this.documents().find((item) => item.documentType === documentType) ?? null;
2467
+ }
2468
+ issueCommentFor(documentType) {
2469
+ return this.issueComments()[documentType] ?? null;
2470
+ }
2471
+ isLocked(documentType) {
2472
+ return this.lockedDocuments().includes(documentType);
2473
+ }
2474
+ uploadStateFor(documentType) {
2475
+ return (this.uploadStates()[documentType] ?? {
2476
+ filename: null,
2477
+ progress: 0,
2478
+ status: 'idle',
2479
+ error: null,
2480
+ });
2481
+ }
2482
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingQualificationUploadSection, deps: [], target: i0.ɵɵFactoryTarget.Component });
2483
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: OnboardingQualificationUploadSection, isStandalone: true, selector: "rolatech-onboarding-qualification-upload-section", inputs: { documentSpecs: { classPropertyName: "documentSpecs", publicName: "documentSpecs", isSignal: true, isRequired: false, transformFunction: null }, documents: { classPropertyName: "documents", publicName: "documents", isSignal: true, isRequired: false, transformFunction: null }, issueComments: { classPropertyName: "issueComments", publicName: "issueComments", isSignal: true, isRequired: false, transformFunction: null }, lockedDocuments: { classPropertyName: "lockedDocuments", publicName: "lockedDocuments", isSignal: true, isRequired: false, transformFunction: null }, uploadStates: { classPropertyName: "uploadStates", publicName: "uploadStates", isSignal: true, isRequired: false, transformFunction: null }, busy: { classPropertyName: "busy", publicName: "busy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fileSelected: "fileSelected" }, host: { classAttribute: "block" }, ngImport: i0, template: `
2484
+ <section class="space-y-4 rounded-3xl border border-(--rt-border-color) bg-(--rt-base-background) p-6 shadow-sm">
2485
+ <div class="flex flex-wrap items-start justify-between gap-4">
2486
+ <div class="space-y-2">
2487
+ <h2 class="text-lg font-semibold text-(--rt-text-primary)">Qualification Documents</h2>
2488
+ <p class="max-w-2xl text-sm text-(--rt-text-secondary)">
2489
+ Upload the documents needed for review. File types are limited to PDF, JPG, and PNG.
2490
+ </p>
2491
+ </div>
2492
+
2493
+ <span class="rounded-full border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-1 text-xs font-semibold text-(--rt-text-secondary)">
2494
+ {{ uploadedRequiredCount() }}/{{ documentSpecs().length }} ready
2495
+ </span>
2496
+ </div>
2497
+
2498
+ @for (documentSpec of documentSpecs(); track documentSpec.type) {
2499
+ <rolatech-onboarding-document-uploader
2500
+ [label]="documentSpec.label"
2501
+ [helperText]="documentSpec.helperText ?? 'Upload a clear and readable file.'"
2502
+ [documentType]="documentSpec.type"
2503
+ [document]="documentFor(documentSpec.type)"
2504
+ [warning]="documentSpec.warning ?? null"
2505
+ [comment]="issueCommentFor(documentSpec.type)"
2506
+ [busy]="busy()"
2507
+ [locked]="isLocked(documentSpec.type)"
2508
+ [uploading]="uploadStateFor(documentSpec.type).status === 'uploading'"
2509
+ [progress]="uploadStateFor(documentSpec.type).progress"
2510
+ [pendingFilename]="uploadStateFor(documentSpec.type).filename"
2511
+ [uploadError]="uploadStateFor(documentSpec.type).error"
2512
+ (fileSelected)="fileSelected.emit($event)"
2513
+ />
2514
+ }
2515
+ </section>
2516
+ `, isInline: true, dependencies: [{ kind: "component", type: OnboardingDocumentUploader, selector: "rolatech-onboarding-document-uploader", inputs: ["label", "helperText", "documentType", "document", "warning", "comment", "busy", "locked", "uploading", "progress", "pendingFilename", "uploadError", "acceptedTypes", "canPreview", "previewBusy"], outputs: ["fileSelected", "previewRequested"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
2517
+ }
2518
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingQualificationUploadSection, decorators: [{
2519
+ type: Component,
2520
+ args: [{
2521
+ selector: 'rolatech-onboarding-qualification-upload-section',
2522
+ standalone: true,
2523
+ imports: [OnboardingDocumentUploader],
2524
+ changeDetection: ChangeDetectionStrategy.OnPush,
2525
+ encapsulation: ViewEncapsulation.None,
2526
+ host: { class: 'block' },
2527
+ template: `
2528
+ <section class="space-y-4 rounded-3xl border border-(--rt-border-color) bg-(--rt-base-background) p-6 shadow-sm">
2529
+ <div class="flex flex-wrap items-start justify-between gap-4">
2530
+ <div class="space-y-2">
2531
+ <h2 class="text-lg font-semibold text-(--rt-text-primary)">Qualification Documents</h2>
2532
+ <p class="max-w-2xl text-sm text-(--rt-text-secondary)">
2533
+ Upload the documents needed for review. File types are limited to PDF, JPG, and PNG.
2534
+ </p>
2535
+ </div>
2536
+
2537
+ <span class="rounded-full border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-1 text-xs font-semibold text-(--rt-text-secondary)">
2538
+ {{ uploadedRequiredCount() }}/{{ documentSpecs().length }} ready
2539
+ </span>
2540
+ </div>
2541
+
2542
+ @for (documentSpec of documentSpecs(); track documentSpec.type) {
2543
+ <rolatech-onboarding-document-uploader
2544
+ [label]="documentSpec.label"
2545
+ [helperText]="documentSpec.helperText ?? 'Upload a clear and readable file.'"
2546
+ [documentType]="documentSpec.type"
2547
+ [document]="documentFor(documentSpec.type)"
2548
+ [warning]="documentSpec.warning ?? null"
2549
+ [comment]="issueCommentFor(documentSpec.type)"
2550
+ [busy]="busy()"
2551
+ [locked]="isLocked(documentSpec.type)"
2552
+ [uploading]="uploadStateFor(documentSpec.type).status === 'uploading'"
2553
+ [progress]="uploadStateFor(documentSpec.type).progress"
2554
+ [pendingFilename]="uploadStateFor(documentSpec.type).filename"
2555
+ [uploadError]="uploadStateFor(documentSpec.type).error"
2556
+ (fileSelected)="fileSelected.emit($event)"
2557
+ />
2558
+ }
2559
+ </section>
2560
+ `,
2561
+ }]
2562
+ }], propDecorators: { documentSpecs: [{ type: i0.Input, args: [{ isSignal: true, alias: "documentSpecs", required: false }] }], documents: [{ type: i0.Input, args: [{ isSignal: true, alias: "documents", required: false }] }], issueComments: [{ type: i0.Input, args: [{ isSignal: true, alias: "issueComments", required: false }] }], lockedDocuments: [{ type: i0.Input, args: [{ isSignal: true, alias: "lockedDocuments", required: false }] }], uploadStates: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadStates", required: false }] }], busy: [{ type: i0.Input, args: [{ isSignal: true, alias: "busy", required: false }] }], fileSelected: [{ type: i0.Output, args: ["fileSelected"] }] } });
2563
+
2564
+ class OnboardingVatSection {
2565
+ form = input.required(...(ngDevMode ? [{ debugName: "form" }] : []));
2566
+ applicantType = input.required(...(ngDevMode ? [{ debugName: "applicantType" }] : []));
2567
+ companyCountry = input.required(...(ngDevMode ? [{ debugName: "companyCountry" }] : []));
2568
+ fieldLocks = input({}, ...(ngDevMode ? [{ debugName: "fieldLocks" }] : []));
2569
+ vatRateLabel = input('20%', ...(ngDevMode ? [{ debugName: "vatRateLabel" }] : []));
2570
+ issueComments = input({}, ...(ngDevMode ? [{ debugName: "issueComments" }] : []));
2571
+ isIndividual = computed(() => this.applicantType() === 'INDIVIDUAL', ...(ngDevMode ? [{ debugName: "isIndividual" }] : []));
2572
+ currentVatMode = computed(() => {
2573
+ const value = this.form().get('vatMode')?.value;
2574
+ return (typeof value === 'string' ? value : 'NON_VAT_REGISTERED');
2575
+ }, ...(ngDevMode ? [{ debugName: "currentVatMode" }] : []));
2576
+ vatNumberValue = computed(() => String(this.form().get('vatNumber')?.value ?? '').trim(), ...(ngDevMode ? [{ debugName: "vatNumberValue" }] : []));
2577
+ sectionReadOnly = computed(() => this.isFieldLocked('vatMode') && this.isFieldLocked('vatNumber'), ...(ngDevMode ? [{ debugName: "sectionReadOnly" }] : []));
2578
+ vatModeLabel = onboardingVatModeLabel;
2579
+ fieldComment(fieldKey) {
2580
+ return this.issueComments()[fieldKey] ?? null;
2581
+ }
2582
+ isFieldLocked(fieldKey) {
2583
+ return this.fieldLocks()[fieldKey] ?? false;
2584
+ }
2585
+ hasError(controlName, errorCode) {
2586
+ const control = this.form().get(controlName);
2587
+ return Boolean(control?.touched && control.hasError(errorCode));
2588
+ }
2589
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingVatSection, deps: [], target: i0.ɵɵFactoryTarget.Component });
2590
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: OnboardingVatSection, isStandalone: true, selector: "rolatech-onboarding-vat-section", inputs: { form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, applicantType: { classPropertyName: "applicantType", publicName: "applicantType", isSignal: true, isRequired: true, transformFunction: null }, companyCountry: { classPropertyName: "companyCountry", publicName: "companyCountry", isSignal: true, isRequired: true, transformFunction: null }, fieldLocks: { classPropertyName: "fieldLocks", publicName: "fieldLocks", isSignal: true, isRequired: false, transformFunction: null }, vatRateLabel: { classPropertyName: "vatRateLabel", publicName: "vatRateLabel", isSignal: true, isRequired: false, transformFunction: null }, issueComments: { classPropertyName: "issueComments", publicName: "issueComments", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
2591
+ <section class="space-y-4 rounded-3xl border border-(--rt-border-color) bg-(--rt-base-background) p-6 shadow-sm">
2592
+ <div class="space-y-2">
2593
+ <h2 class="text-lg font-semibold text-(--rt-text-primary)">VAT and Settlement Terms</h2>
2594
+ <p class="max-w-2xl text-sm text-(--rt-text-secondary)">
2595
+ We calculate final payment as commission base x (1 + VAT rate). The current VAT rate for this application is {{ vatRateLabel() }}.
2596
+ </p>
2597
+ </div>
2598
+
2599
+ @if (isIndividual()) {
2600
+ <div class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) px-4 py-4 text-sm text-emerald-900">
2601
+ Individual applicants are treated as non-VAT registered. VAT is fixed at 0% and no VAT number is required.
2602
+ </div>
2603
+ } @else {
2604
+ @if (sectionReadOnly()) {
2605
+ <div class="grid gap-4 md:grid-cols-2">
2606
+ <article class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) p-4">
2607
+ <p class="text-xs font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)">VAT mode</p>
2608
+ <p class="mt-2 text-sm font-medium text-(--rt-text-primary)">{{ vatModeLabel(currentVatMode()) }}</p>
2609
+ </article>
2610
+
2611
+ <article class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) p-4">
2612
+ <p class="text-xs font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)">VAT number</p>
2613
+ <p class="mt-2 text-sm font-medium text-(--rt-text-primary)">{{ vatNumberValue() || 'Pending manual verification' }}</p>
2614
+ </article>
2615
+ </div>
2616
+ } @else {
2617
+ <form [formGroup]="form()" class="grid gap-4 md:grid-cols-2">
2618
+ @if (isFieldLocked('vatMode')) {
2619
+ <article class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) p-4">
2620
+ <p class="text-xs font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)">VAT mode</p>
2621
+ <p class="mt-2 text-sm font-medium text-(--rt-text-primary)">{{ vatModeLabel(currentVatMode()) }}</p>
2622
+ </article>
2623
+ } @else {
2624
+ <label class="grid gap-2">
2625
+ <span class="text-sm font-medium text-(--rt-text-secondary)">VAT mode</span>
2626
+ <select
2627
+ formControlName="vatMode"
2628
+ class="h-11 rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 text-(--rt-text-primary)"
2629
+ >
2630
+ <option value="VAT_REGISTERED">VAT registered</option>
2631
+ <option value="MANUAL_REVIEW_REQUIRED">Manual review required</option>
2632
+ </select>
2633
+ </label>
2634
+ }
2635
+
2636
+ @if (currentVatMode() === 'VAT_REGISTERED') {
2637
+ <label class="grid gap-2">
2638
+ <span class="text-sm font-medium text-(--rt-text-secondary)">VAT number</span>
2639
+ <input formControlName="vatNumber" class="h-11 rounded-xl border border-(--rt-border-color) px-3" [readonly]="isFieldLocked('vatNumber')" />
2640
+ </label>
2641
+ }
2642
+ </form>
2643
+ }
2644
+
2645
+ @if (fieldComment('vatMode')) {
2646
+ <div class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) px-4 py-3 text-sm text-amber-800">
2647
+ {{ fieldComment('vatMode') }}
2648
+ </div>
2649
+ }
2650
+
2651
+ @if (fieldComment('vatNumber')) {
2652
+ <div class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) px-4 py-3 text-sm text-amber-800">
2653
+ {{ fieldComment('vatNumber') }}
2654
+ </div>
2655
+ }
2656
+
2657
+ @if (currentVatMode() === 'MANUAL_REVIEW_REQUIRED') {
2658
+ <div class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) px-4 py-3 text-sm text-amber-900">
2659
+ Manual verification is required because a VAT number is unavailable. This keeps the application inside the company review flow.
2660
+ </div>
2661
+ }
2662
+
2663
+ @if (companyCountry() === 'CN') {
2664
+ <p class="text-sm text-(--rt-text-secondary)">
2665
+ China companies still follow company VAT rules. If a VAT number is unavailable, choose manual review instead of leaving the section incomplete.
2666
+ </p>
2667
+ }
2668
+
2669
+ @if (hasError('vatNumber', 'required')) {
2670
+ <p class="text-sm text-rose-700">VAT number is required when VAT mode is set to VAT registered.</p>
2671
+ }
2672
+ }
2673
+ </section>
2674
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
2675
+ }
2676
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingVatSection, decorators: [{
2677
+ type: Component,
2678
+ args: [{
2679
+ selector: 'rolatech-onboarding-vat-section',
2680
+ standalone: true,
2681
+ imports: [ReactiveFormsModule],
2682
+ changeDetection: ChangeDetectionStrategy.OnPush,
2683
+ encapsulation: ViewEncapsulation.None,
2684
+ host: { class: 'block' },
2685
+ template: `
2686
+ <section class="space-y-4 rounded-3xl border border-(--rt-border-color) bg-(--rt-base-background) p-6 shadow-sm">
2687
+ <div class="space-y-2">
2688
+ <h2 class="text-lg font-semibold text-(--rt-text-primary)">VAT and Settlement Terms</h2>
2689
+ <p class="max-w-2xl text-sm text-(--rt-text-secondary)">
2690
+ We calculate final payment as commission base x (1 + VAT rate). The current VAT rate for this application is {{ vatRateLabel() }}.
2691
+ </p>
2692
+ </div>
2693
+
2694
+ @if (isIndividual()) {
2695
+ <div class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) px-4 py-4 text-sm text-emerald-900">
2696
+ Individual applicants are treated as non-VAT registered. VAT is fixed at 0% and no VAT number is required.
2697
+ </div>
2698
+ } @else {
2699
+ @if (sectionReadOnly()) {
2700
+ <div class="grid gap-4 md:grid-cols-2">
2701
+ <article class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) p-4">
2702
+ <p class="text-xs font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)">VAT mode</p>
2703
+ <p class="mt-2 text-sm font-medium text-(--rt-text-primary)">{{ vatModeLabel(currentVatMode()) }}</p>
2704
+ </article>
2705
+
2706
+ <article class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) p-4">
2707
+ <p class="text-xs font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)">VAT number</p>
2708
+ <p class="mt-2 text-sm font-medium text-(--rt-text-primary)">{{ vatNumberValue() || 'Pending manual verification' }}</p>
2709
+ </article>
2710
+ </div>
2711
+ } @else {
2712
+ <form [formGroup]="form()" class="grid gap-4 md:grid-cols-2">
2713
+ @if (isFieldLocked('vatMode')) {
2714
+ <article class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) p-4">
2715
+ <p class="text-xs font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)">VAT mode</p>
2716
+ <p class="mt-2 text-sm font-medium text-(--rt-text-primary)">{{ vatModeLabel(currentVatMode()) }}</p>
2717
+ </article>
2718
+ } @else {
2719
+ <label class="grid gap-2">
2720
+ <span class="text-sm font-medium text-(--rt-text-secondary)">VAT mode</span>
2721
+ <select
2722
+ formControlName="vatMode"
2723
+ class="h-11 rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 text-(--rt-text-primary)"
2724
+ >
2725
+ <option value="VAT_REGISTERED">VAT registered</option>
2726
+ <option value="MANUAL_REVIEW_REQUIRED">Manual review required</option>
2727
+ </select>
2728
+ </label>
2729
+ }
2730
+
2731
+ @if (currentVatMode() === 'VAT_REGISTERED') {
2732
+ <label class="grid gap-2">
2733
+ <span class="text-sm font-medium text-(--rt-text-secondary)">VAT number</span>
2734
+ <input formControlName="vatNumber" class="h-11 rounded-xl border border-(--rt-border-color) px-3" [readonly]="isFieldLocked('vatNumber')" />
2735
+ </label>
2736
+ }
2737
+ </form>
2738
+ }
2739
+
2740
+ @if (fieldComment('vatMode')) {
2741
+ <div class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) px-4 py-3 text-sm text-amber-800">
2742
+ {{ fieldComment('vatMode') }}
2743
+ </div>
2744
+ }
2745
+
2746
+ @if (fieldComment('vatNumber')) {
2747
+ <div class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) px-4 py-3 text-sm text-amber-800">
2748
+ {{ fieldComment('vatNumber') }}
2749
+ </div>
2750
+ }
2751
+
2752
+ @if (currentVatMode() === 'MANUAL_REVIEW_REQUIRED') {
2753
+ <div class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) px-4 py-3 text-sm text-amber-900">
2754
+ Manual verification is required because a VAT number is unavailable. This keeps the application inside the company review flow.
2755
+ </div>
2756
+ }
2757
+
2758
+ @if (companyCountry() === 'CN') {
2759
+ <p class="text-sm text-(--rt-text-secondary)">
2760
+ China companies still follow company VAT rules. If a VAT number is unavailable, choose manual review instead of leaving the section incomplete.
2761
+ </p>
2762
+ }
2763
+
2764
+ @if (hasError('vatNumber', 'required')) {
2765
+ <p class="text-sm text-rose-700">VAT number is required when VAT mode is set to VAT registered.</p>
2766
+ }
2767
+ }
2768
+ </section>
2769
+ `,
2770
+ }]
2771
+ }], propDecorators: { form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: true }] }], applicantType: [{ type: i0.Input, args: [{ isSignal: true, alias: "applicantType", required: true }] }], companyCountry: [{ type: i0.Input, args: [{ isSignal: true, alias: "companyCountry", required: true }] }], fieldLocks: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldLocks", required: false }] }], vatRateLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "vatRateLabel", required: false }] }], issueComments: [{ type: i0.Input, args: [{ isSignal: true, alias: "issueComments", required: false }] }] } });
2772
+
2773
+ class OnboardingBankingSection {
2774
+ form = input.required(...(ngDevMode ? [{ debugName: "form" }] : []));
2775
+ fieldLocks = input({}, ...(ngDevMode ? [{ debugName: "fieldLocks" }] : []));
2776
+ expectedAccountHolderName = input('', ...(ngDevMode ? [{ debugName: "expectedAccountHolderName" }] : []));
2777
+ issueComments = input({}, ...(ngDevMode ? [{ debugName: "issueComments" }] : []));
2778
+ fieldComment(fieldKey) {
2779
+ return this.issueComments()[fieldKey] ?? null;
2780
+ }
2781
+ isFieldLocked(fieldKey) {
2782
+ return this.fieldLocks()[fieldKey] ?? false;
2783
+ }
2784
+ hasError(controlName, errorCode) {
2785
+ const control = this.form().get(controlName);
2786
+ return Boolean(control?.touched && control.hasError(errorCode));
2787
+ }
2788
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingBankingSection, deps: [], target: i0.ɵɵFactoryTarget.Component });
2789
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: OnboardingBankingSection, isStandalone: true, selector: "rolatech-onboarding-banking-section", inputs: { form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, fieldLocks: { classPropertyName: "fieldLocks", publicName: "fieldLocks", isSignal: true, isRequired: false, transformFunction: null }, expectedAccountHolderName: { classPropertyName: "expectedAccountHolderName", publicName: "expectedAccountHolderName", isSignal: true, isRequired: false, transformFunction: null }, issueComments: { classPropertyName: "issueComments", publicName: "issueComments", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
2790
+ <section class="space-y-4 rounded-3xl border border-(--rt-border-color) bg-(--rt-base-background) p-6 shadow-sm">
2791
+ <div class="space-y-2">
2792
+ <h2 class="text-lg font-semibold text-(--rt-text-primary)">UK Settlement Bank Account</h2>
2793
+ <p class="max-w-2xl text-sm text-(--rt-text-secondary)">
2794
+ Settlement is restricted to a UK local bank account. The account holder name must match the legal applicant name.
2795
+ </p>
2796
+ </div>
2797
+
2798
+ <form [formGroup]="form()" class="grid gap-4 md:grid-cols-2">
2799
+ <label class="grid gap-2">
2800
+ <span class="text-sm font-medium text-(--rt-text-secondary)">Bank name</span>
2801
+ <input formControlName="bankName" class="h-11 rounded-xl border border-(--rt-border-color) px-3" [readonly]="isFieldLocked('bankName')" />
2802
+ @if (fieldComment('bankName')) {
2803
+ <span class="text-xs text-amber-800">{{ fieldComment('bankName') }}</span>
2804
+ }
2805
+ @if (hasError('bankName', 'required')) {
2806
+ <span class="text-xs text-rose-700">Bank name is required.</span>
2807
+ }
2808
+ </label>
2809
+
2810
+ <label class="grid gap-2">
2811
+ <span class="text-sm font-medium text-(--rt-text-secondary)">Account holder name</span>
2812
+ <input
2813
+ formControlName="accountHolderName"
2814
+ class="h-11 rounded-xl border border-(--rt-border-color) px-3"
2815
+ [readonly]="isFieldLocked('accountHolderName')"
2816
+ />
2817
+ <span class="text-xs text-(--rt-text-secondary)">Expected name: {{ expectedAccountHolderName() || 'Complete the profile section first' }}</span>
2818
+ @if (fieldComment('accountHolderName')) {
2819
+ <span class="text-xs text-amber-800">{{ fieldComment('accountHolderName') }}</span>
2820
+ }
2821
+ @if (hasError('accountHolderName', 'required')) {
2822
+ <span class="text-xs text-rose-700">Account holder name is required.</span>
2823
+ }
2824
+ @if (hasError('accountHolderName', 'accountHolderMismatch')) {
2825
+ <span class="text-xs text-rose-700">Account holder name must match the applicant or company legal name.</span>
2826
+ }
2827
+ </label>
2828
+
2829
+ <label class="grid gap-2">
2830
+ <span class="text-sm font-medium text-(--rt-text-secondary)">Sort code</span>
2831
+ <input formControlName="sortCode" class="h-11 rounded-xl border border-(--rt-border-color) px-3" [readonly]="isFieldLocked('sortCode')" />
2832
+ <span class="text-xs text-(--rt-text-secondary)">Use 6 digits. Example: 01-02-03.</span>
2833
+ @if (fieldComment('sortCode')) {
2834
+ <span class="text-xs text-amber-800">{{ fieldComment('sortCode') }}</span>
2835
+ }
2836
+ @if (hasError('sortCode', 'required')) {
2837
+ <span class="text-xs text-rose-700">Sort code is required.</span>
2838
+ }
2839
+ @if (hasError('sortCode', 'pattern')) {
2840
+ <span class="text-xs text-rose-700">Sort code must contain exactly 6 digits.</span>
2841
+ }
2842
+ </label>
2843
+
2844
+ <label class="grid gap-2">
2845
+ <span class="text-sm font-medium text-(--rt-text-secondary)">Account number</span>
2846
+ <input formControlName="accountNumber" class="h-11 rounded-xl border border-(--rt-border-color) px-3" [readonly]="isFieldLocked('accountNumber')" />
2847
+ <span class="text-xs text-(--rt-text-secondary)">Account number must contain exactly 8 digits.</span>
2848
+ @if (fieldComment('accountNumber')) {
2849
+ <span class="text-xs text-amber-800">{{ fieldComment('accountNumber') }}</span>
2850
+ }
2851
+ @if (hasError('accountNumber', 'required')) {
2852
+ <span class="text-xs text-rose-700">Account number is required.</span>
2853
+ }
2854
+ @if (hasError('accountNumber', 'pattern')) {
2855
+ <span class="text-xs text-rose-700">Account number must contain exactly 8 digits.</span>
2856
+ }
2857
+ </label>
2858
+ </form>
2859
+ </section>
2860
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
2861
+ }
2862
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingBankingSection, decorators: [{
2863
+ type: Component,
2864
+ args: [{
2865
+ selector: 'rolatech-onboarding-banking-section',
2866
+ standalone: true,
2867
+ imports: [ReactiveFormsModule],
2868
+ changeDetection: ChangeDetectionStrategy.OnPush,
2869
+ encapsulation: ViewEncapsulation.None,
2870
+ host: { class: 'block' },
2871
+ template: `
2872
+ <section class="space-y-4 rounded-3xl border border-(--rt-border-color) bg-(--rt-base-background) p-6 shadow-sm">
2873
+ <div class="space-y-2">
2874
+ <h2 class="text-lg font-semibold text-(--rt-text-primary)">UK Settlement Bank Account</h2>
2875
+ <p class="max-w-2xl text-sm text-(--rt-text-secondary)">
2876
+ Settlement is restricted to a UK local bank account. The account holder name must match the legal applicant name.
2877
+ </p>
2878
+ </div>
2879
+
2880
+ <form [formGroup]="form()" class="grid gap-4 md:grid-cols-2">
2881
+ <label class="grid gap-2">
2882
+ <span class="text-sm font-medium text-(--rt-text-secondary)">Bank name</span>
2883
+ <input formControlName="bankName" class="h-11 rounded-xl border border-(--rt-border-color) px-3" [readonly]="isFieldLocked('bankName')" />
2884
+ @if (fieldComment('bankName')) {
2885
+ <span class="text-xs text-amber-800">{{ fieldComment('bankName') }}</span>
2886
+ }
2887
+ @if (hasError('bankName', 'required')) {
2888
+ <span class="text-xs text-rose-700">Bank name is required.</span>
2889
+ }
2890
+ </label>
2891
+
2892
+ <label class="grid gap-2">
2893
+ <span class="text-sm font-medium text-(--rt-text-secondary)">Account holder name</span>
2894
+ <input
2895
+ formControlName="accountHolderName"
2896
+ class="h-11 rounded-xl border border-(--rt-border-color) px-3"
2897
+ [readonly]="isFieldLocked('accountHolderName')"
2898
+ />
2899
+ <span class="text-xs text-(--rt-text-secondary)">Expected name: {{ expectedAccountHolderName() || 'Complete the profile section first' }}</span>
2900
+ @if (fieldComment('accountHolderName')) {
2901
+ <span class="text-xs text-amber-800">{{ fieldComment('accountHolderName') }}</span>
2902
+ }
2903
+ @if (hasError('accountHolderName', 'required')) {
2904
+ <span class="text-xs text-rose-700">Account holder name is required.</span>
2905
+ }
2906
+ @if (hasError('accountHolderName', 'accountHolderMismatch')) {
2907
+ <span class="text-xs text-rose-700">Account holder name must match the applicant or company legal name.</span>
2908
+ }
2909
+ </label>
2910
+
2911
+ <label class="grid gap-2">
2912
+ <span class="text-sm font-medium text-(--rt-text-secondary)">Sort code</span>
2913
+ <input formControlName="sortCode" class="h-11 rounded-xl border border-(--rt-border-color) px-3" [readonly]="isFieldLocked('sortCode')" />
2914
+ <span class="text-xs text-(--rt-text-secondary)">Use 6 digits. Example: 01-02-03.</span>
2915
+ @if (fieldComment('sortCode')) {
2916
+ <span class="text-xs text-amber-800">{{ fieldComment('sortCode') }}</span>
2917
+ }
2918
+ @if (hasError('sortCode', 'required')) {
2919
+ <span class="text-xs text-rose-700">Sort code is required.</span>
2920
+ }
2921
+ @if (hasError('sortCode', 'pattern')) {
2922
+ <span class="text-xs text-rose-700">Sort code must contain exactly 6 digits.</span>
2923
+ }
2924
+ </label>
2925
+
2926
+ <label class="grid gap-2">
2927
+ <span class="text-sm font-medium text-(--rt-text-secondary)">Account number</span>
2928
+ <input formControlName="accountNumber" class="h-11 rounded-xl border border-(--rt-border-color) px-3" [readonly]="isFieldLocked('accountNumber')" />
2929
+ <span class="text-xs text-(--rt-text-secondary)">Account number must contain exactly 8 digits.</span>
2930
+ @if (fieldComment('accountNumber')) {
2931
+ <span class="text-xs text-amber-800">{{ fieldComment('accountNumber') }}</span>
2932
+ }
2933
+ @if (hasError('accountNumber', 'required')) {
2934
+ <span class="text-xs text-rose-700">Account number is required.</span>
2935
+ }
2936
+ @if (hasError('accountNumber', 'pattern')) {
2937
+ <span class="text-xs text-rose-700">Account number must contain exactly 8 digits.</span>
2938
+ }
2939
+ </label>
2940
+ </form>
2941
+ </section>
2942
+ `,
2943
+ }]
2944
+ }], propDecorators: { form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: true }] }], fieldLocks: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldLocks", required: false }] }], expectedAccountHolderName: [{ type: i0.Input, args: [{ isSignal: true, alias: "expectedAccountHolderName", required: false }] }], issueComments: [{ type: i0.Input, args: [{ isSignal: true, alias: "issueComments", required: false }] }] } });
2945
+
2946
+ class OnboardingReviewRow {
2947
+ label = input.required(...(ngDevMode ? [{ debugName: "label" }] : []));
2948
+ value = input(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
2949
+ helper = input('', ...(ngDevMode ? [{ debugName: "helper" }] : []));
2950
+ existingIssue = input(null, ...(ngDevMode ? [{ debugName: "existingIssue" }] : []));
2951
+ issueOptions = input([], ...(ngDevMode ? [{ debugName: "issueOptions" }] : []));
2952
+ draftIssue = input(null, ...(ngDevMode ? [{ debugName: "draftIssue" }] : []));
2953
+ passed = input(false, ...(ngDevMode ? [{ debugName: "passed" }] : []));
2954
+ busy = input(false, ...(ngDevMode ? [{ debugName: "busy" }] : []));
2955
+ actionLabel = input(null, ...(ngDevMode ? [{ debugName: "actionLabel" }] : []));
2956
+ actionDisabled = input(false, ...(ngDevMode ? [{ debugName: "actionDisabled" }] : []));
2957
+ selectedIssueKey = computed(() => this.draftIssue()?.optionKey ?? '', ...(ngDevMode ? [{ debugName: "selectedIssueKey" }] : []));
2958
+ activeIssueOption = computed(() => this.issueOptions().find((option) => option.key === this.selectedIssueKey()) ?? null, ...(ngDevMode ? [{ debugName: "activeIssueOption" }] : []));
2959
+ showCustomComment = computed(() => this.activeIssueOption()?.requiresComment ?? false, ...(ngDevMode ? [{ debugName: "showCustomComment" }] : []));
2960
+ selectedPresetComment = computed(() => {
2961
+ const option = this.activeIssueOption();
2962
+ if (!option || option.requiresComment) {
2963
+ return '';
2964
+ }
2965
+ return option.comment;
2966
+ }, ...(ngDevMode ? [{ debugName: "selectedPresetComment" }] : []));
2967
+ customCommentPlaceholder = computed(() => this.activeIssueOption()?.placeholder ?? 'Add a short reviewer note for this issue.', ...(ngDevMode ? [{ debugName: "customCommentPlaceholder" }] : []));
2968
+ passButtonClass = computed(() => this.passed()
2969
+ ? 'border-(--rt-border-color) bg-(--rt-raised-background) text-emerald-800'
2970
+ : 'border-(--rt-border-color) bg-(--rt-base-background) text-(--rt-text-secondary)', ...(ngDevMode ? [{ debugName: "passButtonClass" }] : []));
2971
+ passedChange = output();
2972
+ draftIssueChange = output();
2973
+ actionTriggered = output();
2974
+ togglePassed() {
2975
+ this.passedChange.emit(!this.passed());
2976
+ }
2977
+ onIssueSelectionChange(event) {
2978
+ const target = event.target;
2979
+ const optionKey = target.value;
2980
+ if (!optionKey) {
2981
+ this.draftIssueChange.emit(null);
2982
+ return;
2983
+ }
2984
+ const option = this.issueOptions().find((item) => item.key === optionKey);
2985
+ if (!option) {
2986
+ this.draftIssueChange.emit(null);
2987
+ return;
2988
+ }
2989
+ this.draftIssueChange.emit({
2990
+ optionKey,
2991
+ comment: option.requiresComment && this.draftIssue()?.optionKey === optionKey
2992
+ ? (this.draftIssue()?.comment ?? '')
2993
+ : option.comment,
2994
+ });
2995
+ }
2996
+ onCustomCommentChange(event) {
2997
+ const option = this.activeIssueOption();
2998
+ if (!option) {
2999
+ this.draftIssueChange.emit(null);
3000
+ return;
3001
+ }
3002
+ const target = event.target;
3003
+ this.draftIssueChange.emit({
3004
+ optionKey: option.key,
3005
+ comment: target.value,
3006
+ });
3007
+ }
3008
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingReviewRow, deps: [], target: i0.ɵɵFactoryTarget.Component });
3009
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: OnboardingReviewRow, isStandalone: true, selector: "rolatech-onboarding-review-row", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, helper: { classPropertyName: "helper", publicName: "helper", isSignal: true, isRequired: false, transformFunction: null }, existingIssue: { classPropertyName: "existingIssue", publicName: "existingIssue", isSignal: true, isRequired: false, transformFunction: null }, issueOptions: { classPropertyName: "issueOptions", publicName: "issueOptions", isSignal: true, isRequired: false, transformFunction: null }, draftIssue: { classPropertyName: "draftIssue", publicName: "draftIssue", isSignal: true, isRequired: false, transformFunction: null }, passed: { classPropertyName: "passed", publicName: "passed", isSignal: true, isRequired: false, transformFunction: null }, busy: { classPropertyName: "busy", publicName: "busy", isSignal: true, isRequired: false, transformFunction: null }, actionLabel: { classPropertyName: "actionLabel", publicName: "actionLabel", isSignal: true, isRequired: false, transformFunction: null }, actionDisabled: { classPropertyName: "actionDisabled", publicName: "actionDisabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { passedChange: "passedChange", draftIssueChange: "draftIssueChange", actionTriggered: "actionTriggered" }, host: { classAttribute: "block" }, ngImport: i0, template: `
3010
+ <article
3011
+ class="overflow-hidden rounded-[1.75rem] border border-(--rt-border-color) bg-(--rt-base-background) p-4 shadow-sm"
3012
+ >
3013
+ <div class="flex flex-wrap items-start justify-between gap-3">
3014
+ <div class="min-w-0 space-y-1">
3015
+ <div class="flex flex-wrap items-center gap-2">
3016
+ <p class="text-[11px] font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)">{{ label() }}</p>
3017
+ @if (existingIssue()) {
3018
+ <span
3019
+ class="rounded-full border border-(--rt-border-color) bg-(--rt-raised-background) px-2.5 py-0.5 text-[10px] font-semibold uppercase tracking-[0.16em] text-amber-800"
3020
+ >
3021
+ Open issue
3022
+ </span>
3023
+ }
3024
+ </div>
3025
+
3026
+ <p class="wrap-break-words text-sm font-semibold text-(--rt-text-primary)">{{ value() || 'Missing' }}</p>
3027
+ @if (helper()) {
3028
+ <p class="text-xs text-(--rt-text-secondary)">{{ helper() }}</p>
3029
+ }
3030
+ </div>
3031
+
3032
+ <div class="flex flex-wrap items-center gap-2">
3033
+ @if (actionLabel()) {
3034
+ <button
3035
+ type="button"
3036
+ class="cursor-pointer rounded-full border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-1.5 text-xs font-semibold text-(--rt-text-primary) transition hover:bg-(--rt-base-background) disabled:cursor-not-allowed disabled:opacity-60"
3037
+ [disabled]="busy() || actionDisabled()"
3038
+ (click)="actionTriggered.emit()"
3039
+ >
3040
+ {{ actionLabel() }}
3041
+ </button>
3042
+ }
3043
+
3044
+ <button
3045
+ type="button"
3046
+ class="cursor-pointer rounded-full border px-3 py-1.5 text-xs font-semibold transition disabled:cursor-not-allowed disabled:opacity-60"
3047
+ [class]="passButtonClass()"
3048
+ [disabled]="busy()"
3049
+ (click)="togglePassed()"
3050
+ >
3051
+ {{ passed() ? 'Passed' : 'Mark pass' }}
3052
+ </button>
3053
+ </div>
3054
+ </div>
3055
+
3056
+ @if (existingIssue()) {
3057
+ <div
3058
+ class="mt-3 rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm text-amber-900"
3059
+ >
3060
+ Current issue: {{ existingIssue() }}
3061
+ </div>
3062
+ }
3063
+
3064
+ <div class="mt-3 grid gap-2">
3065
+ <label class="grid gap-2">
3066
+ <span class="text-[11px] font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)">Issue to raise</span>
3067
+ <select
3068
+ class="rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm text-(--rt-text-primary) disabled:cursor-not-allowed disabled:bg-(--rt-raised-background)"
3069
+ [value]="selectedIssueKey()"
3070
+ [disabled]="busy() || passed()"
3071
+ (change)="onIssueSelectionChange($event)"
3072
+ >
3073
+ <option value="">No issue</option>
3074
+
3075
+ @for (option of issueOptions(); track option.key) {
3076
+ <option [value]="option.key">{{ option.label }}</option>
3077
+ }
3078
+ </select>
3079
+ </label>
3080
+
3081
+ @if (selectedPresetComment()) {
3082
+ <p class="text-xs text-(--rt-text-secondary)">Review note: {{ selectedPresetComment() }}</p>
3083
+ }
3084
+
3085
+ @if (showCustomComment()) {
3086
+ <label class="grid gap-2">
3087
+ <span class="text-[11px] font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)"
3088
+ >Custom review note</span
3089
+ >
3090
+ <textarea
3091
+ class="min-h-24 rounded-xl border border-(--rt-border-color) px-3 py-2 text-sm"
3092
+ [value]="draftIssue()?.comment ?? ''"
3093
+ [disabled]="busy() || passed()"
3094
+ (input)="onCustomCommentChange($event)"
3095
+ [placeholder]="customCommentPlaceholder()"
3096
+ ></textarea>
3097
+ </label>
3098
+ }
3099
+ </div>
3100
+ </article>
3101
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
3102
+ }
3103
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingReviewRow, decorators: [{
3104
+ type: Component,
3105
+ args: [{
3106
+ selector: 'rolatech-onboarding-review-row',
3107
+ standalone: true,
3108
+ changeDetection: ChangeDetectionStrategy.OnPush,
3109
+ encapsulation: ViewEncapsulation.None,
3110
+ host: { class: 'block' },
3111
+ template: `
3112
+ <article
3113
+ class="overflow-hidden rounded-[1.75rem] border border-(--rt-border-color) bg-(--rt-base-background) p-4 shadow-sm"
3114
+ >
3115
+ <div class="flex flex-wrap items-start justify-between gap-3">
3116
+ <div class="min-w-0 space-y-1">
3117
+ <div class="flex flex-wrap items-center gap-2">
3118
+ <p class="text-[11px] font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)">{{ label() }}</p>
3119
+ @if (existingIssue()) {
3120
+ <span
3121
+ class="rounded-full border border-(--rt-border-color) bg-(--rt-raised-background) px-2.5 py-0.5 text-[10px] font-semibold uppercase tracking-[0.16em] text-amber-800"
3122
+ >
3123
+ Open issue
3124
+ </span>
3125
+ }
3126
+ </div>
3127
+
3128
+ <p class="wrap-break-words text-sm font-semibold text-(--rt-text-primary)">{{ value() || 'Missing' }}</p>
3129
+ @if (helper()) {
3130
+ <p class="text-xs text-(--rt-text-secondary)">{{ helper() }}</p>
3131
+ }
3132
+ </div>
3133
+
3134
+ <div class="flex flex-wrap items-center gap-2">
3135
+ @if (actionLabel()) {
3136
+ <button
3137
+ type="button"
3138
+ class="cursor-pointer rounded-full border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-1.5 text-xs font-semibold text-(--rt-text-primary) transition hover:bg-(--rt-base-background) disabled:cursor-not-allowed disabled:opacity-60"
3139
+ [disabled]="busy() || actionDisabled()"
3140
+ (click)="actionTriggered.emit()"
3141
+ >
3142
+ {{ actionLabel() }}
3143
+ </button>
3144
+ }
3145
+
3146
+ <button
3147
+ type="button"
3148
+ class="cursor-pointer rounded-full border px-3 py-1.5 text-xs font-semibold transition disabled:cursor-not-allowed disabled:opacity-60"
3149
+ [class]="passButtonClass()"
3150
+ [disabled]="busy()"
3151
+ (click)="togglePassed()"
3152
+ >
3153
+ {{ passed() ? 'Passed' : 'Mark pass' }}
3154
+ </button>
3155
+ </div>
3156
+ </div>
3157
+
3158
+ @if (existingIssue()) {
3159
+ <div
3160
+ class="mt-3 rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm text-amber-900"
3161
+ >
3162
+ Current issue: {{ existingIssue() }}
3163
+ </div>
3164
+ }
3165
+
3166
+ <div class="mt-3 grid gap-2">
3167
+ <label class="grid gap-2">
3168
+ <span class="text-[11px] font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)">Issue to raise</span>
3169
+ <select
3170
+ class="rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm text-(--rt-text-primary) disabled:cursor-not-allowed disabled:bg-(--rt-raised-background)"
3171
+ [value]="selectedIssueKey()"
3172
+ [disabled]="busy() || passed()"
3173
+ (change)="onIssueSelectionChange($event)"
3174
+ >
3175
+ <option value="">No issue</option>
3176
+
3177
+ @for (option of issueOptions(); track option.key) {
3178
+ <option [value]="option.key">{{ option.label }}</option>
3179
+ }
3180
+ </select>
3181
+ </label>
3182
+
3183
+ @if (selectedPresetComment()) {
3184
+ <p class="text-xs text-(--rt-text-secondary)">Review note: {{ selectedPresetComment() }}</p>
3185
+ }
3186
+
3187
+ @if (showCustomComment()) {
3188
+ <label class="grid gap-2">
3189
+ <span class="text-[11px] font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)"
3190
+ >Custom review note</span
3191
+ >
3192
+ <textarea
3193
+ class="min-h-24 rounded-xl border border-(--rt-border-color) px-3 py-2 text-sm"
3194
+ [value]="draftIssue()?.comment ?? ''"
3195
+ [disabled]="busy() || passed()"
3196
+ (input)="onCustomCommentChange($event)"
3197
+ [placeholder]="customCommentPlaceholder()"
3198
+ ></textarea>
3199
+ </label>
3200
+ }
3201
+ </div>
3202
+ </article>
3203
+ `,
3204
+ }]
3205
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], helper: [{ type: i0.Input, args: [{ isSignal: true, alias: "helper", required: false }] }], existingIssue: [{ type: i0.Input, args: [{ isSignal: true, alias: "existingIssue", required: false }] }], issueOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "issueOptions", required: false }] }], draftIssue: [{ type: i0.Input, args: [{ isSignal: true, alias: "draftIssue", required: false }] }], passed: [{ type: i0.Input, args: [{ isSignal: true, alias: "passed", required: false }] }], busy: [{ type: i0.Input, args: [{ isSignal: true, alias: "busy", required: false }] }], actionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionLabel", required: false }] }], actionDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionDisabled", required: false }] }], passedChange: [{ type: i0.Output, args: ["passedChange"] }], draftIssueChange: [{ type: i0.Output, args: ["draftIssueChange"] }], actionTriggered: [{ type: i0.Output, args: ["actionTriggered"] }] } });
3206
+
3207
+ const OTHER_ISSUE_OPTION = {
3208
+ key: 'OTHER',
3209
+ label: 'Other issue',
3210
+ comment: '',
3211
+ requiresComment: true,
3212
+ placeholder: 'Add a short reviewer note for this issue.',
3213
+ };
3214
+ const DEFAULT_FIELD_ISSUE_OPTIONS = [
3215
+ {
3216
+ key: 'missing',
3217
+ label: 'Missing or incomplete',
3218
+ comment: 'This field is missing or incomplete and needs correction.',
3219
+ },
3220
+ {
3221
+ key: 'format',
3222
+ label: 'Incorrect format',
3223
+ comment: 'This field format is incorrect and needs correction.',
3224
+ },
3225
+ {
3226
+ key: 'mismatch',
3227
+ label: 'Does not match documents',
3228
+ comment: 'This field does not match the supporting documents.',
3229
+ },
3230
+ {
3231
+ key: 'clarify',
3232
+ label: 'Needs clarification',
3233
+ comment: 'Please clarify or correct this field before review can continue.',
3234
+ },
3235
+ OTHER_ISSUE_OPTION,
3236
+ ];
3237
+ const NAME_FIELD_ISSUE_OPTIONS = [
3238
+ {
3239
+ key: 'missing',
3240
+ label: 'Missing or incomplete',
3241
+ comment: 'The name is missing or incomplete and needs correction.',
3242
+ },
3243
+ {
3244
+ key: 'mismatch',
3245
+ label: 'Does not match documents',
3246
+ comment: 'The name does not match the supporting documents.',
3247
+ },
3248
+ {
3249
+ key: 'legal',
3250
+ label: 'Legal name unclear',
3251
+ comment: 'Please provide the full legal name exactly as shown on the supporting documents.',
3252
+ },
3253
+ OTHER_ISSUE_OPTION,
3254
+ ];
3255
+ const EMAIL_FIELD_ISSUE_OPTIONS = [
3256
+ {
3257
+ key: 'missing',
3258
+ label: 'Missing email',
3259
+ comment: 'The primary email address is missing.',
3260
+ },
3261
+ {
3262
+ key: 'invalid',
3263
+ label: 'Invalid format',
3264
+ comment: 'The primary email address format is invalid.',
3265
+ },
3266
+ {
3267
+ key: 'verify',
3268
+ label: 'Needs verification',
3269
+ comment: 'Please confirm the primary email address before review can continue.',
3270
+ },
3271
+ OTHER_ISSUE_OPTION,
3272
+ ];
3273
+ const PHONE_FIELD_ISSUE_OPTIONS = [
3274
+ {
3275
+ key: 'missing',
3276
+ label: 'Missing number',
3277
+ comment: 'The phone number is missing or incomplete.',
3278
+ },
3279
+ {
3280
+ key: 'invalid',
3281
+ label: 'Invalid format',
3282
+ comment: 'The phone number format is invalid.',
3283
+ },
3284
+ {
3285
+ key: 'clarify',
3286
+ label: 'Needs clarification',
3287
+ comment: 'Please confirm the correct phone number before review can continue.',
3288
+ },
3289
+ OTHER_ISSUE_OPTION,
3290
+ ];
3291
+ const COUNTRY_FIELD_ISSUE_OPTIONS = [
3292
+ {
3293
+ key: 'missing',
3294
+ label: 'Missing selection',
3295
+ comment: 'The country selection is missing.',
3296
+ },
3297
+ {
3298
+ key: 'mismatch',
3299
+ label: 'Does not match documents',
3300
+ comment: 'The selected country does not match the supporting documents.',
3301
+ },
3302
+ {
3303
+ key: 'clarify',
3304
+ label: 'Needs clarification',
3305
+ comment: 'Please confirm the correct country selection before review can continue.',
3306
+ },
3307
+ OTHER_ISSUE_OPTION,
3308
+ ];
3309
+ const VAT_MODE_ISSUE_OPTIONS = [
3310
+ {
3311
+ key: 'mismatch',
3312
+ label: 'Does not match documents',
3313
+ comment: 'The VAT setup does not match the supporting documents.',
3314
+ },
3315
+ {
3316
+ key: 'clarify',
3317
+ label: 'Needs clarification',
3318
+ comment: 'Please clarify the VAT registration status before review can continue.',
3319
+ },
3320
+ OTHER_ISSUE_OPTION,
3321
+ ];
3322
+ const VAT_NUMBER_ISSUE_OPTIONS = [
3323
+ {
3324
+ key: 'missing',
3325
+ label: 'Missing VAT number',
3326
+ comment: 'The VAT number is missing.',
3327
+ },
3328
+ {
3329
+ key: 'invalid',
3330
+ label: 'Invalid format',
3331
+ comment: 'The VAT number format appears invalid.',
3332
+ },
3333
+ {
3334
+ key: 'mismatch',
3335
+ label: 'Does not match documents',
3336
+ comment: 'The VAT number does not match the supporting documents.',
3337
+ },
3338
+ OTHER_ISSUE_OPTION,
3339
+ ];
3340
+ const BANK_DETAIL_ISSUE_OPTIONS = [
3341
+ {
3342
+ key: 'missing',
3343
+ label: 'Missing or incomplete',
3344
+ comment: 'The banking detail is missing or incomplete.',
3345
+ },
3346
+ {
3347
+ key: 'mismatch',
3348
+ label: 'Does not match evidence',
3349
+ comment: 'The banking detail does not match the supporting bank evidence.',
3350
+ },
3351
+ {
3352
+ key: 'clarify',
3353
+ label: 'Needs clarification',
3354
+ comment: 'Please confirm the banking detail before review can continue.',
3355
+ },
3356
+ OTHER_ISSUE_OPTION,
3357
+ ];
3358
+ const BANK_ACCOUNT_ISSUE_OPTIONS = [
3359
+ {
3360
+ key: 'missing',
3361
+ label: 'Missing or incomplete',
3362
+ comment: 'The account detail is missing or incomplete.',
3363
+ },
3364
+ {
3365
+ key: 'invalid',
3366
+ label: 'Invalid format',
3367
+ comment: 'The account detail format appears invalid.',
3368
+ },
3369
+ {
3370
+ key: 'mismatch',
3371
+ label: 'Does not match evidence',
3372
+ comment: 'The account detail does not match the supporting bank evidence.',
3373
+ },
3374
+ OTHER_ISSUE_OPTION,
3375
+ ];
3376
+ const DEFAULT_DOCUMENT_ISSUE_OPTIONS = [
3377
+ {
3378
+ key: 'missing',
3379
+ label: 'Missing document',
3380
+ comment: 'The required document is missing.',
3381
+ },
3382
+ {
3383
+ key: 'unreadable',
3384
+ label: 'Unreadable file',
3385
+ comment: 'The uploaded document is unreadable or incomplete.',
3386
+ },
3387
+ {
3388
+ key: 'wrong-document',
3389
+ label: 'Wrong document',
3390
+ comment: 'The uploaded file does not match the requested document type.',
3391
+ },
3392
+ {
3393
+ key: 'mismatch',
3394
+ label: 'Details do not match',
3395
+ comment: 'The uploaded document details do not match the application information.',
3396
+ },
3397
+ OTHER_ISSUE_OPTION,
3398
+ ];
3399
+ const PASSPORT_DOCUMENT_ISSUE_OPTIONS = [
3400
+ {
3401
+ key: 'missing',
3402
+ label: 'Missing passport',
3403
+ comment: 'The passport document is missing.',
3404
+ },
3405
+ {
3406
+ key: 'unreadable',
3407
+ label: 'Unreadable photo page',
3408
+ comment: 'The passport photo page is unreadable or incomplete.',
3409
+ },
3410
+ {
3411
+ key: 'expired',
3412
+ label: 'Expired or invalid',
3413
+ comment: 'The passport appears expired or otherwise invalid for verification.',
3414
+ },
3415
+ {
3416
+ key: 'mismatch',
3417
+ label: 'Details do not match',
3418
+ comment: 'The passport details do not match the application information.',
3419
+ },
3420
+ OTHER_ISSUE_OPTION,
3421
+ ];
3422
+ const CERTIFICATE_DOCUMENT_ISSUE_OPTIONS = [
3423
+ {
3424
+ key: 'missing',
3425
+ label: 'Missing certificate',
3426
+ comment: 'The certificate is missing.',
3427
+ },
3428
+ {
3429
+ key: 'unreadable',
3430
+ label: 'Unreadable file',
3431
+ comment: 'The certificate is unreadable or incomplete.',
3432
+ },
3433
+ {
3434
+ key: 'invalid',
3435
+ label: 'Invalid or outdated',
3436
+ comment: 'The certificate appears invalid or outdated for review.',
3437
+ },
3438
+ {
3439
+ key: 'mismatch',
3440
+ label: 'Details do not match',
3441
+ comment: 'The certificate details do not match the application information.',
3442
+ },
3443
+ OTHER_ISSUE_OPTION,
3444
+ ];
3445
+ const OTHER_DOCUMENT_ISSUE_OPTIONS = [
3446
+ {
3447
+ key: 'missing',
3448
+ label: 'Missing supporting file',
3449
+ comment: 'The requested supporting evidence is missing.',
3450
+ },
3451
+ {
3452
+ key: 'unreadable',
3453
+ label: 'Unreadable file',
3454
+ comment: 'The supporting evidence is unreadable or incomplete.',
3455
+ },
3456
+ {
3457
+ key: 'insufficient',
3458
+ label: 'Insufficient evidence',
3459
+ comment: 'The supporting evidence is insufficient for review.',
3460
+ },
3461
+ {
3462
+ key: 'wrong-document',
3463
+ label: 'Unrelated document',
3464
+ comment: 'The uploaded file does not provide the requested supporting evidence.',
3465
+ },
3466
+ OTHER_ISSUE_OPTION,
3467
+ ];
3468
+ function onboardingFieldIssueOptions(fieldKey) {
3469
+ switch (fieldKey) {
3470
+ case 'organizationName':
3471
+ case 'contactName':
3472
+ return NAME_FIELD_ISSUE_OPTIONS;
3473
+ case 'contactEmail':
3474
+ return EMAIL_FIELD_ISSUE_OPTIONS;
3475
+ case 'contactPhone':
3476
+ return PHONE_FIELD_ISSUE_OPTIONS;
3477
+ case 'countryCode':
3478
+ case 'companyCountry':
3479
+ return COUNTRY_FIELD_ISSUE_OPTIONS;
3480
+ case 'vatMode':
3481
+ return VAT_MODE_ISSUE_OPTIONS;
3482
+ case 'vatNumber':
3483
+ return VAT_NUMBER_ISSUE_OPTIONS;
3484
+ case 'bankName':
3485
+ case 'accountHolderName':
3486
+ return BANK_DETAIL_ISSUE_OPTIONS;
3487
+ case 'sortCode':
3488
+ case 'accountNumber':
3489
+ return BANK_ACCOUNT_ISSUE_OPTIONS;
3490
+ default:
3491
+ return DEFAULT_FIELD_ISSUE_OPTIONS;
3492
+ }
3493
+ }
3494
+ function onboardingDocumentIssueOptions(documentType) {
3495
+ switch (documentType) {
3496
+ case 'PASSPORT':
3497
+ return PASSPORT_DOCUMENT_ISSUE_OPTIONS;
3498
+ case 'CERTIFICATE_OF_INCORPORATION':
3499
+ case 'INDUSTRY_CERTIFICATE':
3500
+ return CERTIFICATE_DOCUMENT_ISSUE_OPTIONS;
3501
+ case 'OTHER':
3502
+ return OTHER_DOCUMENT_ISSUE_OPTIONS;
3503
+ default:
3504
+ return DEFAULT_DOCUMENT_ISSUE_OPTIONS;
3505
+ }
3506
+ }
3507
+
1490
3508
  /**
1491
3509
  * Generated bundle index. Do not edit.
1492
3510
  */
1493
3511
 
1494
- export { ADMIN_ONBOARDING_ROUTES, AGENT_APPLY_ROUTES, AGENT_ONBOARDING_ROUTES, AdminOnboardingDetailFacade, AdminOnboardingListFacade, AdminOnboardingReviewFacade, AgentApplyFacade, AgentOnboardingFacade, OnboardingDocumentUploader, OnboardingIssueList, OnboardingPageShell, OnboardingStatusBadge, OnboardingStepNav, OnboardingSummaryCard, OnboardingTimeline, OnboardingTimelineDrawer, applicationIdGuard, applyStepGuard };
3512
+ export { ADMIN_ONBOARDING_ROUTES, AGENT_APPLY_ROUTES, AGENT_ONBOARDING_ROUTES, AdminOnboardingDetailFacade, AdminOnboardingListFacade, AdminOnboardingReviewFacade, AgentApplyFacade, AgentOnboardingFacade, OnboardingBankingSection, OnboardingDocumentPreviewDialog, OnboardingDocumentUploader, OnboardingIssueList, OnboardingPageShell, OnboardingQualificationUploadSection, OnboardingReviewIssuesBanner, OnboardingReviewRow, OnboardingStatusBadge, OnboardingStepNav, OnboardingSummaryCard, OnboardingTimeline, OnboardingTimelineDrawer, OnboardingVatSection, applicationIdGuard, applyStepGuard, onboardingDocumentIssueOptions, onboardingFieldIssueOptions };
1495
3513
  //# sourceMappingURL=rolatech-angular-onboarding.mjs.map