@sneat/components 0.27.31 → 0.27.33

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.
@@ -0,0 +1,653 @@
1
+ import { ChangeDetectionStrategy, Component, InjectionToken, computed, effect, inject, input, signal, untracked, } from '@angular/core';
2
+ import { CurrencyPipe, DatePipe } from '@angular/common';
3
+ import { Router, RouterLink } from '@angular/router';
4
+ import { IonButton } from '@ionic/angular/ion-button';
5
+ import { IonCard } from '@ionic/angular/ion-card';
6
+ import { IonCardContent } from '@ionic/angular/ion-card-content';
7
+ import { IonCardHeader } from '@ionic/angular/ion-card-header';
8
+ import { IonCardTitle } from '@ionic/angular/ion-card-title';
9
+ import { IonItem } from '@ionic/angular/ion-item';
10
+ import { IonLabel } from '@ionic/angular/ion-label';
11
+ import { IonList } from '@ionic/angular/ion-list';
12
+ import { IonSelect } from '@ionic/angular/ion-select';
13
+ import { IonSelectOption } from '@ionic/angular/ion-select-option';
14
+ import * as i0 from "@angular/core";
15
+ export const SUBSCRIPTION_SETTINGS = new InjectionToken('SubscriptionSettings');
16
+ export class SubscriptionSettingsComponent {
17
+ constructor() {
18
+ this.port = inject(SUBSCRIPTION_SETTINGS, { optional: true });
19
+ this.router = inject(Router);
20
+ this.accountId = input('', /* @ts-ignore */
21
+ ...(ngDevMode ? [{ debugName: "accountId" }] : /* istanbul ignore next */ []));
22
+ this.mode = input('live', /* @ts-ignore */
23
+ ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
24
+ this.transferView = input(false, /* @ts-ignore */
25
+ ...(ngDevMode ? [{ debugName: "transferView" }] : /* istanbul ignore next */ []));
26
+ this.requestId = input('', /* @ts-ignore */
27
+ ...(ngDevMode ? [{ debugName: "requestId" }] : /* istanbul ignore next */ []));
28
+ this.basePath = input('/subscriptions', /* @ts-ignore */
29
+ ...(ngDevMode ? [{ debugName: "basePath" }] : /* istanbul ignore next */ []));
30
+ this.identity = signal(null, /* @ts-ignore */
31
+ ...(ngDevMode ? [{ debugName: "identity" }] : /* istanbul ignore next */ []));
32
+ this.items = signal([], /* @ts-ignore */
33
+ ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
34
+ this.selected = signal(null, /* @ts-ignore */
35
+ ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
36
+ this.busy = signal(false, /* @ts-ignore */
37
+ ...(ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []));
38
+ this.error = signal('', /* @ts-ignore */
39
+ ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
40
+ this.next = signal('', /* @ts-ignore */
41
+ ...(ngDevMode ? [{ debugName: "next" }] : /* istanbul ignore next */ []));
42
+ this.target = signal('', /* @ts-ignore */
43
+ ...(ngDevMode ? [{ debugName: "target" }] : /* istanbul ignore next */ []));
44
+ this.confirmCancel = signal(false, /* @ts-ignore */
45
+ ...(ngDevMode ? [{ debugName: "confirmCancel" }] : /* istanbul ignore next */ []));
46
+ this.query = computed(() => this.mode() === 'test' ? { checkout: 'test' } : {}, /* @ts-ignore */
47
+ ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
48
+ this.transferQuery = computed(() => ({
49
+ ...this.query(),
50
+ requestId: `${this.selected()?.bindingRevision ?? 0}-${crypto.randomUUID()}`,
51
+ }), /* @ts-ignore */
52
+ ...(ngDevMode ? [{ debugName: "transferQuery" }] : /* istanbul ignore next */ []));
53
+ this.validRequest = computed(() => /^[A-Za-z0-9_-]{1,128}$/.test(this.requestId()), /* @ts-ignore */
54
+ ...(ngDevMode ? [{ debugName: "validRequest" }] : /* istanbul ignore next */ []));
55
+ this.eligibleSpaces = computed(() => (this.port?.spaces() ?? []).filter((s) => s.id !== this.selected()?.spaceID), /* @ts-ignore */
56
+ ...(ngDevMode ? [{ debugName: "eligibleSpaces" }] : /* istanbul ignore next */ []));
57
+ this.generation = 0;
58
+ this.key = '';
59
+ this.stop = this.port?.observeIdentity((id) => this.identity.set(id));
60
+ effect(() => {
61
+ const key = [
62
+ this.identity(),
63
+ this.mode(),
64
+ this.accountId(),
65
+ this.transferView(),
66
+ this.requestId(),
67
+ ].join('|');
68
+ if (key === this.key)
69
+ return;
70
+ this.key = key;
71
+ this.generation++;
72
+ this.items.set([]);
73
+ this.selected.set(null);
74
+ this.next.set('');
75
+ this.error.set('');
76
+ this.busy.set(false);
77
+ this.target.set('');
78
+ this.confirmCancel.set(false);
79
+ if (this.identity())
80
+ untracked(() => {
81
+ void this.read(false);
82
+ });
83
+ });
84
+ }
85
+ status(item) {
86
+ return ({
87
+ active: 'Active',
88
+ ended: 'Ended',
89
+ refunded: 'Refunded',
90
+ cancellation_scheduled: 'Cancellation scheduled',
91
+ setup_required: 'Payment received · Finish setup',
92
+ check_status: 'Payment status check required',
93
+ }[item.status] ?? 'Status unavailable');
94
+ }
95
+ resume(item) {
96
+ return this.port?.resume(this.mode(), item);
97
+ }
98
+ async read(more) {
99
+ if (!this.port || !this.identity() || this.busy())
100
+ return;
101
+ const generation = this.generation;
102
+ this.busy.set(true);
103
+ this.error.set('');
104
+ try {
105
+ if (this.accountId()) {
106
+ const item = await this.port.read(this.mode(), this.accountId());
107
+ if (generation === this.generation)
108
+ this.selected.set(item);
109
+ }
110
+ else {
111
+ const page = await this.port.load(this.mode(), more ? this.next() : '');
112
+ if (generation === this.generation) {
113
+ this.items.set([
114
+ ...new Map([...(more ? this.items() : []), ...page.items].map((i) => [
115
+ i.accountId,
116
+ i,
117
+ ])).values(),
118
+ ]);
119
+ this.next.set(page.next ?? '');
120
+ }
121
+ }
122
+ }
123
+ catch (error) {
124
+ if (generation === this.generation)
125
+ this.error.set(this.port.message(error));
126
+ }
127
+ finally {
128
+ if (generation === this.generation)
129
+ this.busy.set(false);
130
+ }
131
+ }
132
+ refresh() {
133
+ if (!this.busy())
134
+ void this.read(false);
135
+ }
136
+ loadMore() {
137
+ void this.read(true);
138
+ }
139
+ async cancel() {
140
+ const item = this.selected();
141
+ if (!item || !this.port || this.busy() || !this.confirmCancel())
142
+ return;
143
+ const generation = this.generation;
144
+ this.busy.set(true);
145
+ this.error.set('');
146
+ try {
147
+ const result = await this.port.cancel(this.mode(), item.accountId);
148
+ if (generation === this.generation) {
149
+ this.selected.set(result);
150
+ this.confirmCancel.set(false);
151
+ }
152
+ }
153
+ catch (error) {
154
+ if (generation === this.generation)
155
+ this.error.set(this.port.message(error));
156
+ }
157
+ finally {
158
+ if (generation === this.generation)
159
+ this.busy.set(false);
160
+ }
161
+ }
162
+ async transfer() {
163
+ const item = this.selected();
164
+ if (!item ||
165
+ !this.port ||
166
+ this.busy() ||
167
+ !this.validRequest() ||
168
+ !this.eligibleSpaces().some((s) => s.id === this.target()) ||
169
+ !item.canTransfer)
170
+ return;
171
+ const generation = this.generation;
172
+ this.busy.set(true);
173
+ this.error.set('');
174
+ try {
175
+ await this.port.transfer(this.mode(), item, this.target(), this.requestId());
176
+ if (generation === this.generation)
177
+ await this.router.navigate([this.basePath(), item.accountId], {
178
+ queryParams: this.query(),
179
+ replaceUrl: true,
180
+ });
181
+ }
182
+ catch (error) {
183
+ if (generation === this.generation)
184
+ this.error.set(this.port.message(error));
185
+ }
186
+ finally {
187
+ if (generation === this.generation)
188
+ this.busy.set(false);
189
+ }
190
+ }
191
+ ngOnDestroy() {
192
+ this.generation++;
193
+ this.stop?.();
194
+ }
195
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SubscriptionSettingsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
196
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: SubscriptionSettingsComponent, isStandalone: true, selector: "sneat-subscription-settings", inputs: { accountId: { classPropertyName: "accountId", publicName: "accountId", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, transferView: { classPropertyName: "transferView", publicName: "transferView", isSignal: true, isRequired: false, transformFunction: null }, requestId: { classPropertyName: "requestId", publicName: "requestId", isSignal: true, isRequired: false, transformFunction: null }, basePath: { classPropertyName: "basePath", publicName: "basePath", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
197
+ @if (!identity()) {
198
+ <ion-card
199
+ ><ion-card-content
200
+ >Sign in to manage your subscriptions.</ion-card-content
201
+ ></ion-card
202
+ >
203
+ } @else {
204
+ @if (busy()) {
205
+ <p role="status">Checking your subscriptions…</p>
206
+ }
207
+ @if (error()) {
208
+ <ion-card
209
+ ><ion-card-content
210
+ ><p role="alert">{{ error() }}</p>
211
+ <ion-button fill="outline" [disabled]="busy()" (click)="refresh()"
212
+ >Retry</ion-button
213
+ >
214
+ </ion-card-content></ion-card
215
+ >
216
+ }
217
+ @if (!accountId()) {
218
+ @if (!busy() && !error() && !items().length) {
219
+ <p>No subscriptions found.</p>
220
+ }
221
+ <ion-list aria-label="Your subscriptions">
222
+ @for (item of items(); track item.accountId) {
223
+ <ion-item
224
+ button
225
+ detail
226
+ [routerLink]="[basePath(), item.accountId]"
227
+ [queryParams]="query()"
228
+ >
229
+ <ion-label class="ion-text-wrap"
230
+ ><h2>{{ item.label }} · {{ item.spaceTitle }}</h2>
231
+ <p>{{ status(item) }}</p>
232
+ @if (item.priceConfirmed) {
233
+ <p>
234
+ {{ item.renewalMinor / 100 | currency: item.currency }} /
235
+ {{ item.period }}
236
+ </p>
237
+ }
238
+ <p>Reference {{ item.accountId.slice(-8) }}</p>
239
+ </ion-label>
240
+ </ion-item>
241
+ }
242
+ </ion-list>
243
+ @if (next()) {
244
+ <ion-button fill="clear" [disabled]="busy()" (click)="loadMore()"
245
+ >Show more subscriptions</ion-button
246
+ >
247
+ }
248
+ } @else if (selected(); as item) {
249
+ <ion-card>
250
+ <ion-card-header
251
+ ><ion-card-title>{{ item.label }}</ion-card-title></ion-card-header
252
+ >
253
+ <ion-card-content>
254
+ <p>
255
+ Receiving space: <strong>{{ item.spaceTitle }}</strong>
256
+ </p>
257
+ <p>{{ status(item) }}</p>
258
+ @if (item.priceConfirmed) {
259
+ <p>
260
+ Regular price:
261
+ {{ item.regularMinor / 100 | currency: item.currency }} /
262
+ {{ item.period }}
263
+ </p>
264
+ @if (item.discountPercent) {
265
+ <p>
266
+ {{
267
+ item.launchDiscount
268
+ ? 'Launch discount'
269
+ : 'Renewal discount'
270
+ }}: {{ item.discountPercent }}%
271
+ </p>
272
+ }
273
+ <p>
274
+ <strong
275
+ >{{
276
+ item.scheduledEndUTC.startsWith('0001-')
277
+ ? 'Renewal'
278
+ : 'Price per period'
279
+ }}: {{ item.renewalMinor / 100 | currency: item.currency }} /
280
+ {{ item.period }}</strong
281
+ >
282
+ </p>
283
+ } @else {
284
+ <p>Price requires a payment status check.</p>
285
+ }
286
+ @if (!item.paidThroughUTC.startsWith('0001-')) {
287
+ <p>Paid through {{ item.paidThroughUTC | date: 'mediumDate' }}</p>
288
+ }
289
+ @if (!item.scheduledEndUTC.startsWith('0001-')) {
290
+ <p>
291
+ Renewal cancelled. Access ends
292
+ {{ item.scheduledEndUTC | date: 'mediumDate' }}.
293
+ </p>
294
+ }
295
+ @if (resume(item); as continuation) {
296
+ <ion-button
297
+ [routerLink]="continuation.path"
298
+ [queryParams]="continuation.query"
299
+ >{{
300
+ item.status === 'setup_required'
301
+ ? 'Finish subscription setup'
302
+ : 'Check payment status'
303
+ }}</ion-button
304
+ >
305
+ }
306
+ @if (!transferView() && item.canTransfer) {
307
+ <ion-button
308
+ fill="outline"
309
+ [routerLink]="[basePath(), item.accountId, 'transfer']"
310
+ [queryParams]="transferQuery()"
311
+ [disabled]="busy()"
312
+ >Transfer paid access</ion-button
313
+ >
314
+ }
315
+ @if (!transferView() && item.canCancel && !confirmCancel()) {
316
+ <ion-button
317
+ fill="clear"
318
+ color="danger"
319
+ [disabled]="busy()"
320
+ (click)="confirmCancel.set(true)"
321
+ >Cancel renewal</ion-button
322
+ >
323
+ }
324
+ </ion-card-content>
325
+ </ion-card>
326
+ @if (confirmCancel()) {
327
+ <ion-card aria-label="Confirm cancellation"
328
+ ><ion-card-content>
329
+ <p>
330
+ Cancel renewal for {{ item.label }} in {{ item.spaceTitle }}?
331
+ </p>
332
+ <p>
333
+ Paid access remains until
334
+ {{ item.paidThroughUTC | date: 'mediumDate' }}. Your other
335
+ subscriptions stay active.
336
+ </p>
337
+ <ion-button color="danger" [disabled]="busy()" (click)="cancel()"
338
+ >Confirm cancellation at period end</ion-button
339
+ >
340
+ <ion-button
341
+ fill="clear"
342
+ [disabled]="busy()"
343
+ (click)="confirmCancel.set(false)"
344
+ >Keep subscription</ion-button
345
+ >
346
+ </ion-card-content></ion-card
347
+ >
348
+ }
349
+ @if (transferView()) {
350
+ <ion-card aria-label="Transfer paid access"
351
+ ><ion-card-header
352
+ ><ion-card-title
353
+ >Transfer paid access</ion-card-title
354
+ ></ion-card-header
355
+ >
356
+ <ion-card-content>
357
+ <p>
358
+ Projects stay in {{ item.spaceTitle }}. That space loses this
359
+ subscription's paid access after transfer; normal expiry
360
+ restrictions apply.
361
+ </p>
362
+ <p>
363
+ Billing dates, discount and the original introductory period
364
+ stay unchanged. Separate prepaid capacity and SSO add-ons stay
365
+ in their current space.
366
+ </p>
367
+ <p>
368
+ Transfers with consumed current-period allowance require
369
+ settlement first; transferring never grants a fresh allowance.
370
+ </p>
371
+ @if (!item.canTransfer) {
372
+ <p role="alert">
373
+ This subscription cannot currently be transferred. Check its
374
+ payment status first.
375
+ </p>
376
+ }
377
+ <ion-select
378
+ label="Receiving business space"
379
+ interface="popover"
380
+ [value]="target()"
381
+ [disabled]="busy() || !item.canTransfer"
382
+ (ionChange)="target.set($event.detail.value)"
383
+ >
384
+ @for (space of eligibleSpaces(); track space.id) {
385
+ <ion-select-option [value]="space.id">{{
386
+ space.title
387
+ }}</ion-select-option>
388
+ }
389
+ </ion-select>
390
+ @if (!eligibleSpaces().length) {
391
+ <p>No other eligible business space is available.</p>
392
+ }
393
+ <ion-button
394
+ [disabled]="
395
+ busy() || !target() || !item.canTransfer || !validRequest()
396
+ "
397
+ (click)="transfer()"
398
+ >Confirm transfer of paid access</ion-button
399
+ >
400
+ <ion-button
401
+ fill="clear"
402
+ [routerLink]="[basePath(), item.accountId]"
403
+ [queryParams]="query()"
404
+ [disabled]="busy()"
405
+ >Cancel</ion-button
406
+ >
407
+ </ion-card-content>
408
+ </ion-card>
409
+ }
410
+ }
411
+ }
412
+ `, isInline: true, dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"], outputs: ["ionFocus", "ionBlur"] }, { kind: "component", type: IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: IonSelect, selector: "ion-select", inputs: ["cancelText", "color", "compareWith", "disabled", "errorText", "expandedIcon", "fill", "helperText", "interface", "interfaceOptions", "justify", "label", "labelPlacement", "mode", "multiple", "name", "okText", "placeholder", "selectedText", "shape", "toggleIcon", "value"] }, { kind: "component", type: IonSelectOption, selector: "ion-select-option", inputs: ["description", "disabled", "justify", "labelPlacement", "mode", "value"] }, { kind: "pipe", type: CurrencyPipe, name: "currency" }, { kind: "pipe", type: DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
413
+ }
414
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SubscriptionSettingsComponent, decorators: [{
415
+ type: Component,
416
+ args: [{
417
+ selector: 'sneat-subscription-settings',
418
+ changeDetection: ChangeDetectionStrategy.OnPush,
419
+ imports: [
420
+ RouterLink,
421
+ CurrencyPipe,
422
+ DatePipe,
423
+ IonButton,
424
+ IonCard,
425
+ IonCardContent,
426
+ IonCardHeader,
427
+ IonCardTitle,
428
+ IonItem,
429
+ IonLabel,
430
+ IonList,
431
+ IonSelect,
432
+ IonSelectOption,
433
+ ],
434
+ template: `
435
+ @if (!identity()) {
436
+ <ion-card
437
+ ><ion-card-content
438
+ >Sign in to manage your subscriptions.</ion-card-content
439
+ ></ion-card
440
+ >
441
+ } @else {
442
+ @if (busy()) {
443
+ <p role="status">Checking your subscriptions…</p>
444
+ }
445
+ @if (error()) {
446
+ <ion-card
447
+ ><ion-card-content
448
+ ><p role="alert">{{ error() }}</p>
449
+ <ion-button fill="outline" [disabled]="busy()" (click)="refresh()"
450
+ >Retry</ion-button
451
+ >
452
+ </ion-card-content></ion-card
453
+ >
454
+ }
455
+ @if (!accountId()) {
456
+ @if (!busy() && !error() && !items().length) {
457
+ <p>No subscriptions found.</p>
458
+ }
459
+ <ion-list aria-label="Your subscriptions">
460
+ @for (item of items(); track item.accountId) {
461
+ <ion-item
462
+ button
463
+ detail
464
+ [routerLink]="[basePath(), item.accountId]"
465
+ [queryParams]="query()"
466
+ >
467
+ <ion-label class="ion-text-wrap"
468
+ ><h2>{{ item.label }} · {{ item.spaceTitle }}</h2>
469
+ <p>{{ status(item) }}</p>
470
+ @if (item.priceConfirmed) {
471
+ <p>
472
+ {{ item.renewalMinor / 100 | currency: item.currency }} /
473
+ {{ item.period }}
474
+ </p>
475
+ }
476
+ <p>Reference {{ item.accountId.slice(-8) }}</p>
477
+ </ion-label>
478
+ </ion-item>
479
+ }
480
+ </ion-list>
481
+ @if (next()) {
482
+ <ion-button fill="clear" [disabled]="busy()" (click)="loadMore()"
483
+ >Show more subscriptions</ion-button
484
+ >
485
+ }
486
+ } @else if (selected(); as item) {
487
+ <ion-card>
488
+ <ion-card-header
489
+ ><ion-card-title>{{ item.label }}</ion-card-title></ion-card-header
490
+ >
491
+ <ion-card-content>
492
+ <p>
493
+ Receiving space: <strong>{{ item.spaceTitle }}</strong>
494
+ </p>
495
+ <p>{{ status(item) }}</p>
496
+ @if (item.priceConfirmed) {
497
+ <p>
498
+ Regular price:
499
+ {{ item.regularMinor / 100 | currency: item.currency }} /
500
+ {{ item.period }}
501
+ </p>
502
+ @if (item.discountPercent) {
503
+ <p>
504
+ {{
505
+ item.launchDiscount
506
+ ? 'Launch discount'
507
+ : 'Renewal discount'
508
+ }}: {{ item.discountPercent }}%
509
+ </p>
510
+ }
511
+ <p>
512
+ <strong
513
+ >{{
514
+ item.scheduledEndUTC.startsWith('0001-')
515
+ ? 'Renewal'
516
+ : 'Price per period'
517
+ }}: {{ item.renewalMinor / 100 | currency: item.currency }} /
518
+ {{ item.period }}</strong
519
+ >
520
+ </p>
521
+ } @else {
522
+ <p>Price requires a payment status check.</p>
523
+ }
524
+ @if (!item.paidThroughUTC.startsWith('0001-')) {
525
+ <p>Paid through {{ item.paidThroughUTC | date: 'mediumDate' }}</p>
526
+ }
527
+ @if (!item.scheduledEndUTC.startsWith('0001-')) {
528
+ <p>
529
+ Renewal cancelled. Access ends
530
+ {{ item.scheduledEndUTC | date: 'mediumDate' }}.
531
+ </p>
532
+ }
533
+ @if (resume(item); as continuation) {
534
+ <ion-button
535
+ [routerLink]="continuation.path"
536
+ [queryParams]="continuation.query"
537
+ >{{
538
+ item.status === 'setup_required'
539
+ ? 'Finish subscription setup'
540
+ : 'Check payment status'
541
+ }}</ion-button
542
+ >
543
+ }
544
+ @if (!transferView() && item.canTransfer) {
545
+ <ion-button
546
+ fill="outline"
547
+ [routerLink]="[basePath(), item.accountId, 'transfer']"
548
+ [queryParams]="transferQuery()"
549
+ [disabled]="busy()"
550
+ >Transfer paid access</ion-button
551
+ >
552
+ }
553
+ @if (!transferView() && item.canCancel && !confirmCancel()) {
554
+ <ion-button
555
+ fill="clear"
556
+ color="danger"
557
+ [disabled]="busy()"
558
+ (click)="confirmCancel.set(true)"
559
+ >Cancel renewal</ion-button
560
+ >
561
+ }
562
+ </ion-card-content>
563
+ </ion-card>
564
+ @if (confirmCancel()) {
565
+ <ion-card aria-label="Confirm cancellation"
566
+ ><ion-card-content>
567
+ <p>
568
+ Cancel renewal for {{ item.label }} in {{ item.spaceTitle }}?
569
+ </p>
570
+ <p>
571
+ Paid access remains until
572
+ {{ item.paidThroughUTC | date: 'mediumDate' }}. Your other
573
+ subscriptions stay active.
574
+ </p>
575
+ <ion-button color="danger" [disabled]="busy()" (click)="cancel()"
576
+ >Confirm cancellation at period end</ion-button
577
+ >
578
+ <ion-button
579
+ fill="clear"
580
+ [disabled]="busy()"
581
+ (click)="confirmCancel.set(false)"
582
+ >Keep subscription</ion-button
583
+ >
584
+ </ion-card-content></ion-card
585
+ >
586
+ }
587
+ @if (transferView()) {
588
+ <ion-card aria-label="Transfer paid access"
589
+ ><ion-card-header
590
+ ><ion-card-title
591
+ >Transfer paid access</ion-card-title
592
+ ></ion-card-header
593
+ >
594
+ <ion-card-content>
595
+ <p>
596
+ Projects stay in {{ item.spaceTitle }}. That space loses this
597
+ subscription's paid access after transfer; normal expiry
598
+ restrictions apply.
599
+ </p>
600
+ <p>
601
+ Billing dates, discount and the original introductory period
602
+ stay unchanged. Separate prepaid capacity and SSO add-ons stay
603
+ in their current space.
604
+ </p>
605
+ <p>
606
+ Transfers with consumed current-period allowance require
607
+ settlement first; transferring never grants a fresh allowance.
608
+ </p>
609
+ @if (!item.canTransfer) {
610
+ <p role="alert">
611
+ This subscription cannot currently be transferred. Check its
612
+ payment status first.
613
+ </p>
614
+ }
615
+ <ion-select
616
+ label="Receiving business space"
617
+ interface="popover"
618
+ [value]="target()"
619
+ [disabled]="busy() || !item.canTransfer"
620
+ (ionChange)="target.set($event.detail.value)"
621
+ >
622
+ @for (space of eligibleSpaces(); track space.id) {
623
+ <ion-select-option [value]="space.id">{{
624
+ space.title
625
+ }}</ion-select-option>
626
+ }
627
+ </ion-select>
628
+ @if (!eligibleSpaces().length) {
629
+ <p>No other eligible business space is available.</p>
630
+ }
631
+ <ion-button
632
+ [disabled]="
633
+ busy() || !target() || !item.canTransfer || !validRequest()
634
+ "
635
+ (click)="transfer()"
636
+ >Confirm transfer of paid access</ion-button
637
+ >
638
+ <ion-button
639
+ fill="clear"
640
+ [routerLink]="[basePath(), item.accountId]"
641
+ [queryParams]="query()"
642
+ [disabled]="busy()"
643
+ >Cancel</ion-button
644
+ >
645
+ </ion-card-content>
646
+ </ion-card>
647
+ }
648
+ }
649
+ }
650
+ `,
651
+ }]
652
+ }], ctorParameters: () => [], propDecorators: { accountId: [{ type: i0.Input, args: [{ isSignal: true, alias: "accountId", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], transferView: [{ type: i0.Input, args: [{ isSignal: true, alias: "transferView", required: false }] }], requestId: [{ type: i0.Input, args: [{ isSignal: true, alias: "requestId", required: false }] }], basePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "basePath", required: false }] }] } });
653
+ //# sourceMappingURL=subscription-settings.component.js.map