@sneat/components 0.27.30 → 0.27.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/index.js +2 -0
- package/esm2022/index.js.map +1 -1
- package/esm2022/lib/country-selector/countries-loader.service.js +9 -7
- package/esm2022/lib/country-selector/countries-loader.service.js.map +1 -1
- package/esm2022/lib/country-selector/country-selector.component.js +4 -3
- package/esm2022/lib/country-selector/country-selector.component.js.map +1 -1
- package/esm2022/lib/subscription-recovery/subscription-recovery.component.js +232 -0
- package/esm2022/lib/subscription-recovery/subscription-recovery.component.js.map +1 -0
- package/esm2022/lib/subscription-settings/subscription-settings.component.js +653 -0
- package/esm2022/lib/subscription-settings/subscription-settings.component.js.map +1 -0
- package/index.d.ts +2 -0
- package/lib/country-selector/countries-loader.service.d.ts +1 -0
- package/lib/subscription-recovery/subscription-recovery.component.d.ts +41 -0
- package/lib/subscription-settings/subscription-settings.component.d.ts +96 -0
- package/package.json +1 -1
- package/tsconfig.lib.prod.tsbuildinfo +1 -1
package/esm2022/index.js
CHANGED
|
@@ -12,4 +12,6 @@ export * from './lib/save-event';
|
|
|
12
12
|
export * from './lib/date-input/date-input.component';
|
|
13
13
|
export * from './lib/user-country/user-country.component';
|
|
14
14
|
export * from './lib/card-list/sneat-card-list.component';
|
|
15
|
+
export * from './lib/subscription-recovery/subscription-recovery.component';
|
|
16
|
+
export * from './lib/subscription-settings/subscription-settings.component';
|
|
15
17
|
//# sourceMappingURL=index.js.map
|
package/esm2022/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/components/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,qCAAqC,CAAC;AACpD,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uCAAuC,CAAC;AACtD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,2CAA2C,CAAC","sourcesContent":["export * from './lib/app-version';\nexport * from './lib/pipes';\nexport * from './lib/virtual-slider/virtual-slider';\nexport * from './lib/card-list';\nexport * from './lib/error-card';\nexport * from './lib/country-selector';\nexport * from './lib/filter-item';\nexport * from './lib/copyright';\nexport * from './lib/dialog-header';\nexport * from './lib/country-input';\nexport * from './lib/save-event';\nexport * from './lib/date-input/date-input.component';\nexport * from './lib/user-country/user-country.component';\nexport * from './lib/card-list/sneat-card-list.component';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/components/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,qCAAqC,CAAC;AACpD,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uCAAuC,CAAC;AACtD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,6DAA6D,CAAC;AAC5E,cAAc,6DAA6D,CAAC","sourcesContent":["export * from './lib/app-version';\nexport * from './lib/pipes';\nexport * from './lib/virtual-slider/virtual-slider';\nexport * from './lib/card-list';\nexport * from './lib/error-card';\nexport * from './lib/country-selector';\nexport * from './lib/filter-item';\nexport * from './lib/copyright';\nexport * from './lib/dialog-header';\nexport * from './lib/country-input';\nexport * from './lib/save-event';\nexport * from './lib/date-input/date-input.component';\nexport * from './lib/user-country/user-country.component';\nexport * from './lib/card-list/sneat-card-list.component';\nexport * from './lib/subscription-recovery/subscription-recovery.component';\nexport * from './lib/subscription-settings/subscription-settings.component';\n"]}
|
|
@@ -64,32 +64,34 @@ export class CountriesLoaderService {
|
|
|
64
64
|
* Automatically loads country data if not yet loaded.
|
|
65
65
|
*/
|
|
66
66
|
async getCountries() {
|
|
67
|
-
await this.
|
|
68
|
-
return Object.values(this.countriesData.countriesByID);
|
|
67
|
+
return Object.values((await this.loadedCountries()).countriesByID);
|
|
69
68
|
}
|
|
70
69
|
/**
|
|
71
70
|
* Gets countries by ID lookup.
|
|
72
71
|
* Automatically loads country data if not yet loaded.
|
|
73
72
|
*/
|
|
74
73
|
async getCountriesByID() {
|
|
75
|
-
await this.
|
|
76
|
-
return this.countriesData.countriesByID;
|
|
74
|
+
return (await this.loadedCountries()).countriesByID;
|
|
77
75
|
}
|
|
78
76
|
/**
|
|
79
77
|
* Gets a specific country by ID.
|
|
80
78
|
* Automatically loads country data if not yet loaded.
|
|
81
79
|
*/
|
|
82
80
|
async getCountryByID(id) {
|
|
83
|
-
await this.
|
|
84
|
-
return this.countriesData.countriesByID[id];
|
|
81
|
+
return (await this.loadedCountries()).countriesByID[id];
|
|
85
82
|
}
|
|
86
83
|
/**
|
|
87
84
|
* Gets the unknown country constant.
|
|
88
85
|
* Automatically loads country data if not yet loaded.
|
|
89
86
|
*/
|
|
90
87
|
async getUnknownCountry() {
|
|
88
|
+
return (await this.loadedCountries()).unknownCountry;
|
|
89
|
+
}
|
|
90
|
+
async loadedCountries() {
|
|
91
91
|
await this.loadCountries();
|
|
92
|
-
|
|
92
|
+
if (!this.countriesData)
|
|
93
|
+
throw new Error('Country data was cleared during loading');
|
|
94
|
+
return this.countriesData;
|
|
93
95
|
}
|
|
94
96
|
/**
|
|
95
97
|
* Gets countries filtered by geo region.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"countries-loader.service.js","sourceRoot":"","sources":["../../../../../../libs/components/src/lib/country-selector/countries-loader.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;;AAQtC;;;GAGG;AAEH,MAAM,OAAO,sBAAsB;IADnC;QAEmB,SAAI,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;QAI3C;;;;WAIG;QACM,eAAU,GAAG,MAAM,CAAC,KAAK;uFAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"countries-loader.service.js","sourceRoot":"","sources":["../../../../../../libs/components/src/lib/country-selector/countries-loader.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { inject, Injectable, signal } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { firstValueFrom } from 'rxjs';\nimport { GeoRegion, ICountry } from './countries';\n\ninterface CountriesData {\n countriesByID: Record<string, ICountry>;\n unknownCountry: ICountry;\n}\n\n/**\n * Service to lazy load country data.\n * This reduces the initial bundle size by loading country data on-demand.\n */\n@Injectable({ providedIn: 'root' })\nexport class CountriesLoaderService {\n private readonly http = inject(HttpClient);\n private countriesData?: CountriesData;\n private loadPromise?: Promise<void>;\n\n /**\n * True when the last load attempt failed (countries.json couldn't be\n * fetched/parsed). Consumers can read this to show an error in the UI. Reset\n * to false on a successful (re)load. See `reload()` to retry.\n */\n readonly loadFailed = signal(false);\n\n /**\n * Loads country data from JSON file.\n * Subsequent calls return the same promise (singleton pattern).\n */\n private async loadCountries(): Promise<void> {\n if (this.countriesData) {\n return; // Already loaded\n }\n\n if (!this.loadPromise) {\n this.loadPromise = (async () => {\n try {\n this.countriesData = await firstValueFrom(\n this.http.get<CountriesData>('assets/data/countries.json'),\n );\n this.loadFailed.set(false);\n } catch (error) {\n console.error('Failed to load countries:', error);\n this.loadFailed.set(true);\n // Set to empty to avoid repeated failures\n this.countriesData = {\n countriesByID: {},\n unknownCountry: {\n id: '--',\n id3: '---',\n title: 'Unknown',\n geoRegions: [],\n emoji: '🏳️',\n },\n };\n }\n })();\n }\n\n return this.loadPromise;\n }\n\n /**\n * Discards the cached data and re-attempts the load — used to retry after a\n * failure (`loadFailed()` is true). Resolves once the reload settles.\n */\n async reload(): Promise<void> {\n this.countriesData = undefined;\n this.loadPromise = undefined;\n await this.loadCountries();\n }\n\n /**\n * Gets all countries as an array.\n * Automatically loads country data if not yet loaded.\n */\n async getCountries(): Promise<readonly ICountry[]> {\n return Object.values((await this.loadedCountries()).countriesByID);\n }\n\n /**\n * Gets countries by ID lookup.\n * Automatically loads country data if not yet loaded.\n */\n async getCountriesByID(): Promise<Record<string, ICountry>> {\n return (await this.loadedCountries()).countriesByID;\n }\n\n /**\n * Gets a specific country by ID.\n * Automatically loads country data if not yet loaded.\n */\n async getCountryByID(id: string): Promise<ICountry | undefined> {\n return (await this.loadedCountries()).countriesByID[id];\n }\n\n /**\n * Gets the unknown country constant.\n * Automatically loads country data if not yet loaded.\n */\n async getUnknownCountry(): Promise<ICountry> {\n return (await this.loadedCountries()).unknownCountry;\n }\n\n private async loadedCountries(): Promise<CountriesData> {\n await this.loadCountries();\n if (!this.countriesData)\n throw new Error('Country data was cleared during loading');\n return this.countriesData;\n }\n\n /**\n * Gets countries filtered by geo region.\n * Automatically loads country data if not yet loaded.\n */\n async getCountriesByRegion(region: GeoRegion): Promise<readonly ICountry[]> {\n const countries = await this.getCountries();\n return countries.filter((c) => c.geoRegions.includes(region));\n }\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Component, computed, EventEmitter, inject, Input, Output, signal, ChangeDetectionStrategy, input } from '@angular/core';
|
|
1
|
+
import { Component, computed, EventEmitter, inject, Input, Output, signal, ChangeDetectionStrategy, input, } from '@angular/core';
|
|
2
2
|
import { FormsModule } from '@angular/forms';
|
|
3
3
|
import { IonButton, IonItem, IonLabel, IonSegment, IonSegmentButton, } from '@ionic/angular';
|
|
4
4
|
import { SelectFromListComponent } from '@sneat/ui';
|
|
@@ -48,8 +48,9 @@ export class CountrySelectorComponent {
|
|
|
48
48
|
(c.geoRegions.includes('North America') ||
|
|
49
49
|
c.geoRegions.includes('South America'))) ||
|
|
50
50
|
c.geoRegions.includes(this.geoRegion()));
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
const unknownCountry = this.unknownCountry();
|
|
52
|
+
if (this.canBeUnknown() && unknownCountry) {
|
|
53
|
+
countriesToShow = [...countriesToShow, unknownCountry];
|
|
53
54
|
}
|
|
54
55
|
return countriesToShow;
|
|
55
56
|
}, /* @ts-ignore */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"country-selector.component.js","sourceRoot":"","sources":["../../../../../../libs/components/src/lib/country-selector/country-selector.component.ts","../../../../../../libs/components/src/lib/country-selector/country-selector.component.html"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,MAAM,EACN,KAAK,EAGL,MAAM,EACN,MAAM,EAEN,uBAAuB,EACvB,KAAK,
|
|
1
|
+
{"version":3,"file":"country-selector.component.js","sourceRoot":"","sources":["../../../../../../libs/components/src/lib/country-selector/country-selector.component.ts","../../../../../../libs/components/src/lib/country-selector/country-selector.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n computed,\n EventEmitter,\n inject,\n Input,\n OnChanges,\n OnInit,\n Output,\n signal,\n SimpleChanges,\n ChangeDetectionStrategy,\n input,\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport {\n IonButton,\n IonItem,\n IonLabel,\n IonSegment,\n IonSegmentButton,\n} from '@ionic/angular';\nimport { ISelectItem, SelectFromListComponent } from '@sneat/ui';\nimport { GeoRegion, ICountry } from './countries';\nimport { CountriesLoaderService } from './countries-loader.service';\n\n@Component({\n selector: 'sneat-country-selector',\n templateUrl: './country-selector.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [\n FormsModule,\n SelectFromListComponent,\n IonSegment,\n IonSegmentButton,\n IonItem,\n IonLabel,\n IonButton,\n ],\n})\nexport class CountrySelectorComponent implements OnInit, OnChanges {\n private readonly countriesLoader = inject(CountriesLoaderService);\n\n protected readonly geoRegions: readonly {\n readonly id: string;\n title?: string;\n }[] = [\n { id: 'Europe' },\n { id: 'Asia' },\n { id: 'Americas' },\n { id: 'Africa' },\n ];\n\n // TODO: Skipped for migration because:\n // Your application code writes to the input. This prevents migration.\n @Input({ required: true }) countryID?: string;\n\n readonly defaultCountryID = input<string>();\n\n readonly readonly = input(false);\n readonly disabled = input(false);\n readonly label = input('search');\n readonly canBeUnknown = input(false);\n\n @Output() readonly countryIDChange = new EventEmitter<string>();\n\n protected readonly filter = signal<string>('');\n protected readonly geoRegion = signal<GeoRegion | 'All' | 'Americas'>('All');\n protected readonly allCountries = signal<readonly ICountry[]>([]);\n protected readonly unknownCountry = signal<ICountry | undefined>(undefined);\n\n // True when the country list failed to load — surfaced in the UI with a Retry.\n protected readonly loadFailed = this.countriesLoader.loadFailed;\n\n protected readonly countries = computed<readonly ISelectItem[]>(() => {\n const allCountries = this.allCountries();\n if (allCountries.length === 0) {\n return []; // Data not loaded yet\n }\n\n const filter = this.filter();\n let countriesToShow: readonly ISelectItem[] = filter\n ? allCountries\n : allCountries.filter(\n (c) =>\n this.geoRegion() === 'All' ||\n (this.geoRegion() === 'Americas' &&\n (c.geoRegions.includes('North America') ||\n c.geoRegions.includes('South America'))) ||\n c.geoRegions.includes(this.geoRegion() as GeoRegion),\n );\n const unknownCountry = this.unknownCountry();\n if (this.canBeUnknown() && unknownCountry) {\n countriesToShow = [...countriesToShow, unknownCountry];\n }\n return countriesToShow;\n });\n\n ngOnInit(): void {\n this.load();\n }\n\n private load(): void {\n this.countriesLoader.getCountries().then((countries) => {\n this.allCountries.set(countries);\n });\n this.countriesLoader.getUnknownCountry().then((unknown) => {\n this.unknownCountry.set(unknown);\n });\n }\n\n // Retry loading the country list after a failure (loadFailed()).\n protected retry(): void {\n this.countriesLoader.reload().then(() => this.load());\n }\n\n // protected geoRegion: GeoRegion | 'All' | 'Americas' = 'All';\n\n protected onChanged(): void {\n this.countryIDChange.emit(this.countryID);\n }\n\n protected onFilterChanged(filter: string): void {\n this.filter.set(filter);\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes['country'] && this.countryID === '--' && !this.canBeUnknown()) {\n this.countryID = undefined;\n }\n const defaultCountryID = this.defaultCountryID();\n if (\n changes['defaultCountryID'] &&\n !this.countryID &&\n defaultCountryID &&\n defaultCountryID !== '--'\n ) {\n this.countryID = defaultCountryID;\n this.countryIDChange.emit(this.countryID);\n }\n }\n\n protected onRegionChanged(event: Event): void {\n this.geoRegion.set(\n (event as CustomEvent).detail.value as GeoRegion | 'All' | 'Americas',\n );\n }\n\n protected readonly filterCountryByCode = (\n item: ISelectItem,\n filter: string,\n ) => {\n const f = filter.trim().toUpperCase();\n const c = item as ICountry;\n return c.id === f || c.id3.startsWith(f);\n };\n}\n","@if (loadFailed()) {\n <ion-item color=\"danger\" lines=\"none\">\n <ion-label class=\"ion-text-wrap\">\n Couldn't load the list of countries. Check your connection and try again.\n </ion-label>\n <ion-button slot=\"end\" fill=\"clear\" (click)=\"retry()\">Retry</ion-button>\n </ion-item>\n}\n\n<sneat-select-from-list\n [value]=\"countryID || ''\"\n [isFilterable]=\"true\"\n [filterLabel]=\"label()\"\n [label]=\"label()\"\n [items]=\"countries()\"\n [(ngModel)]=\"countryID\"\n (ngModelChange)=\"onChanged()\"\n [isReadonly]=\"readonly()\"\n [filterItem]=\"filterCountryByCode\"\n (filterChanged)=\"onFilterChanged($event)\"\n labelPlacement=\"start\"\n>\n @if (!countryID && !filter()) {\n <ion-segment [(ngModel)]=\"geoRegion\" (ionChange)=\"onRegionChanged($event)\">\n <ion-segment-button value=\"All\">All</ion-segment-button>\n @for (region of geoRegions; track region.id) {\n <ion-segment-button [value]=\"region.id\">\n <small>{{ region.id }}</small>\n </ion-segment-button>\n }\n </ion-segment>\n }\n</sneat-select-from-list>\n"]}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, InjectionToken, inject, input, signal, } from '@angular/core';
|
|
2
|
+
import { ActivatedRoute, RouterLink } from '@angular/router';
|
|
3
|
+
import { IonButton } from '@ionic/angular/ion-button';
|
|
4
|
+
import { IonCard } from '@ionic/angular/ion-card';
|
|
5
|
+
import { IonCardContent } from '@ionic/angular/ion-card-content';
|
|
6
|
+
import { IonCardHeader } from '@ionic/angular/ion-card-header';
|
|
7
|
+
import { IonCardTitle } from '@ionic/angular/ion-card-title';
|
|
8
|
+
import { IonItem } from '@ionic/angular/ion-item';
|
|
9
|
+
import { IonLabel } from '@ionic/angular/ion-label';
|
|
10
|
+
import { IonList } from '@ionic/angular/ion-list';
|
|
11
|
+
import { IonNote } from '@ionic/angular/ion-note';
|
|
12
|
+
import * as i0 from "@angular/core";
|
|
13
|
+
/** The host supplies authenticated read-only billing infrastructure. */
|
|
14
|
+
export const SUBSCRIPTION_RECOVERY = new InjectionToken('SubscriptionRecovery');
|
|
15
|
+
export class SubscriptionRecoveryComponent {
|
|
16
|
+
constructor() {
|
|
17
|
+
this.port = inject(SUBSCRIPTION_RECOVERY, { optional: true });
|
|
18
|
+
this.route = inject(ActivatedRoute);
|
|
19
|
+
this.title = input('Finish subscription setup', /* @ts-ignore */
|
|
20
|
+
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
21
|
+
this.items = signal([], /* @ts-ignore */
|
|
22
|
+
...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
23
|
+
this.mode = signal('live', /* @ts-ignore */
|
|
24
|
+
...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
25
|
+
this.signedIn = signal(false, /* @ts-ignore */
|
|
26
|
+
...(ngDevMode ? [{ debugName: "signedIn" }] : /* istanbul ignore next */ []));
|
|
27
|
+
this.busy = signal(false, /* @ts-ignore */
|
|
28
|
+
...(ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []));
|
|
29
|
+
this.error = signal('', /* @ts-ignore */
|
|
30
|
+
...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
31
|
+
this.next = signal('', /* @ts-ignore */
|
|
32
|
+
...(ngDevMode ? [{ debugName: "next" }] : /* istanbul ignore next */ []));
|
|
33
|
+
this.generation = 0;
|
|
34
|
+
this.observerGeneration = 0;
|
|
35
|
+
this.identity = null;
|
|
36
|
+
this.routeSubscription = this.route.queryParamMap.subscribe((params) => {
|
|
37
|
+
this.generation++;
|
|
38
|
+
const observerGeneration = ++this.observerGeneration;
|
|
39
|
+
this.stopIdentity?.();
|
|
40
|
+
this.items.set([]);
|
|
41
|
+
this.next.set('');
|
|
42
|
+
this.error.set('');
|
|
43
|
+
this.busy.set(false);
|
|
44
|
+
this.signedIn.set(false);
|
|
45
|
+
const values = params.getAll('checkout');
|
|
46
|
+
if (values.length > 1 || (values.length === 1 && values[0] !== 'test'))
|
|
47
|
+
return;
|
|
48
|
+
this.mode.set(values[0] === 'test' ? 'test' : 'live');
|
|
49
|
+
this.stopIdentity = this.port?.observeIdentity((id) => {
|
|
50
|
+
if (observerGeneration !== this.observerGeneration)
|
|
51
|
+
return;
|
|
52
|
+
this.generation++;
|
|
53
|
+
this.identity = id;
|
|
54
|
+
this.signedIn.set(Boolean(id));
|
|
55
|
+
this.items.set([]);
|
|
56
|
+
this.next.set('');
|
|
57
|
+
this.busy.set(false);
|
|
58
|
+
this.error.set('');
|
|
59
|
+
if (id)
|
|
60
|
+
void this.load(false);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async load(more) {
|
|
65
|
+
if (!this.port || !this.identity || this.busy())
|
|
66
|
+
return;
|
|
67
|
+
const generation = this.generation;
|
|
68
|
+
const after = more ? this.next() : '';
|
|
69
|
+
this.busy.set(true);
|
|
70
|
+
this.error.set('');
|
|
71
|
+
try {
|
|
72
|
+
const page = await this.port.load(this.mode(), after);
|
|
73
|
+
if (generation !== this.generation)
|
|
74
|
+
return;
|
|
75
|
+
const items = more ? [...this.items(), ...page.items] : page.items;
|
|
76
|
+
this.items.set([
|
|
77
|
+
...new Map(items.map((item) => [item.id, item])).values(),
|
|
78
|
+
]);
|
|
79
|
+
this.next.set(page.next ?? '');
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
if (generation === this.generation)
|
|
83
|
+
this.error.set('Your purchases could not be checked. Please retry.');
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
if (generation === this.generation)
|
|
87
|
+
this.busy.set(false);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
refresh() {
|
|
91
|
+
if (this.busy())
|
|
92
|
+
return;
|
|
93
|
+
this.generation++;
|
|
94
|
+
this.next.set('');
|
|
95
|
+
void this.load(false);
|
|
96
|
+
}
|
|
97
|
+
ngOnDestroy() {
|
|
98
|
+
this.generation++;
|
|
99
|
+
this.observerGeneration++;
|
|
100
|
+
this.stopIdentity?.();
|
|
101
|
+
this.routeSubscription.unsubscribe();
|
|
102
|
+
}
|
|
103
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SubscriptionRecoveryComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
104
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: SubscriptionRecoveryComponent, isStandalone: true, selector: "sneat-subscription-recovery", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
105
|
+
@if (
|
|
106
|
+
port && signedIn() && (busy() || error() || items().length || next())
|
|
107
|
+
) {
|
|
108
|
+
<ion-card aria-label="Unfinished subscriptions">
|
|
109
|
+
<ion-card-header
|
|
110
|
+
><ion-card-title
|
|
111
|
+
>{{ title() }}
|
|
112
|
+
@if (mode() === 'test') {
|
|
113
|
+
· Test mode
|
|
114
|
+
}
|
|
115
|
+
</ion-card-title></ion-card-header
|
|
116
|
+
>
|
|
117
|
+
<ion-card-content>
|
|
118
|
+
@if (items().length) {
|
|
119
|
+
<ion-list>
|
|
120
|
+
@for (item of items(); track item.id) {
|
|
121
|
+
<ion-item
|
|
122
|
+
[routerLink]="item.path"
|
|
123
|
+
[queryParams]="item.query"
|
|
124
|
+
button
|
|
125
|
+
detail
|
|
126
|
+
>
|
|
127
|
+
<ion-label class="ion-text-wrap"
|
|
128
|
+
><h2>{{ item.label }}</h2>
|
|
129
|
+
@if (item.description) {
|
|
130
|
+
<p>{{ item.description }}</p>
|
|
131
|
+
}
|
|
132
|
+
<p>{{ item.action }}</p></ion-label
|
|
133
|
+
>
|
|
134
|
+
</ion-item>
|
|
135
|
+
}
|
|
136
|
+
</ion-list>
|
|
137
|
+
}
|
|
138
|
+
@if (busy()) {
|
|
139
|
+
<p role="status">Checking your purchases…</p>
|
|
140
|
+
}
|
|
141
|
+
@if (error()) {
|
|
142
|
+
<p role="alert">{{ error() }}</p>
|
|
143
|
+
<ion-button fill="outline" (click)="load(false)">Retry</ion-button>
|
|
144
|
+
}
|
|
145
|
+
@if (next() && !busy() && !error()) {
|
|
146
|
+
<ion-button fill="clear" (click)="load(true)"
|
|
147
|
+
>Show more purchases</ion-button
|
|
148
|
+
>
|
|
149
|
+
}
|
|
150
|
+
<ion-note
|
|
151
|
+
>Opening a purchase checks its current status. It does not charge
|
|
152
|
+
you again.</ion-note
|
|
153
|
+
>
|
|
154
|
+
</ion-card-content>
|
|
155
|
+
</ion-card>
|
|
156
|
+
}
|
|
157
|
+
`, 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: IonNote, selector: "ion-note", inputs: ["color", "mode"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
158
|
+
}
|
|
159
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SubscriptionRecoveryComponent, decorators: [{
|
|
160
|
+
type: Component,
|
|
161
|
+
args: [{
|
|
162
|
+
selector: 'sneat-subscription-recovery',
|
|
163
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
164
|
+
imports: [
|
|
165
|
+
RouterLink,
|
|
166
|
+
IonButton,
|
|
167
|
+
IonCard,
|
|
168
|
+
IonCardContent,
|
|
169
|
+
IonCardHeader,
|
|
170
|
+
IonCardTitle,
|
|
171
|
+
IonItem,
|
|
172
|
+
IonLabel,
|
|
173
|
+
IonList,
|
|
174
|
+
IonNote,
|
|
175
|
+
],
|
|
176
|
+
template: `
|
|
177
|
+
@if (
|
|
178
|
+
port && signedIn() && (busy() || error() || items().length || next())
|
|
179
|
+
) {
|
|
180
|
+
<ion-card aria-label="Unfinished subscriptions">
|
|
181
|
+
<ion-card-header
|
|
182
|
+
><ion-card-title
|
|
183
|
+
>{{ title() }}
|
|
184
|
+
@if (mode() === 'test') {
|
|
185
|
+
· Test mode
|
|
186
|
+
}
|
|
187
|
+
</ion-card-title></ion-card-header
|
|
188
|
+
>
|
|
189
|
+
<ion-card-content>
|
|
190
|
+
@if (items().length) {
|
|
191
|
+
<ion-list>
|
|
192
|
+
@for (item of items(); track item.id) {
|
|
193
|
+
<ion-item
|
|
194
|
+
[routerLink]="item.path"
|
|
195
|
+
[queryParams]="item.query"
|
|
196
|
+
button
|
|
197
|
+
detail
|
|
198
|
+
>
|
|
199
|
+
<ion-label class="ion-text-wrap"
|
|
200
|
+
><h2>{{ item.label }}</h2>
|
|
201
|
+
@if (item.description) {
|
|
202
|
+
<p>{{ item.description }}</p>
|
|
203
|
+
}
|
|
204
|
+
<p>{{ item.action }}</p></ion-label
|
|
205
|
+
>
|
|
206
|
+
</ion-item>
|
|
207
|
+
}
|
|
208
|
+
</ion-list>
|
|
209
|
+
}
|
|
210
|
+
@if (busy()) {
|
|
211
|
+
<p role="status">Checking your purchases…</p>
|
|
212
|
+
}
|
|
213
|
+
@if (error()) {
|
|
214
|
+
<p role="alert">{{ error() }}</p>
|
|
215
|
+
<ion-button fill="outline" (click)="load(false)">Retry</ion-button>
|
|
216
|
+
}
|
|
217
|
+
@if (next() && !busy() && !error()) {
|
|
218
|
+
<ion-button fill="clear" (click)="load(true)"
|
|
219
|
+
>Show more purchases</ion-button
|
|
220
|
+
>
|
|
221
|
+
}
|
|
222
|
+
<ion-note
|
|
223
|
+
>Opening a purchase checks its current status. It does not charge
|
|
224
|
+
you again.</ion-note
|
|
225
|
+
>
|
|
226
|
+
</ion-card-content>
|
|
227
|
+
</ion-card>
|
|
228
|
+
}
|
|
229
|
+
`,
|
|
230
|
+
}]
|
|
231
|
+
}], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }] } });
|
|
232
|
+
//# sourceMappingURL=subscription-recovery.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscription-recovery.component.js","sourceRoot":"","sources":["../../../../../../libs/components/src/lib/subscription-recovery/subscription-recovery.component.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n InjectionToken,\n OnDestroy,\n inject,\n input,\n signal,\n} from '@angular/core';\nimport { ActivatedRoute, RouterLink } from '@angular/router';\nimport { IonButton } from '@ionic/angular/ion-button';\nimport { IonCard } from '@ionic/angular/ion-card';\nimport { IonCardContent } from '@ionic/angular/ion-card-content';\nimport { IonCardHeader } from '@ionic/angular/ion-card-header';\nimport { IonCardTitle } from '@ionic/angular/ion-card-title';\nimport { IonItem } from '@ionic/angular/ion-item';\nimport { IonLabel } from '@ionic/angular/ion-label';\nimport { IonList } from '@ionic/angular/ion-list';\nimport { IonNote } from '@ionic/angular/ion-note';\n\nexport type SubscriptionRecoveryMode = 'live' | 'test';\nexport interface SubscriptionRecoveryItem {\n id: string;\n label: string;\n description?: string;\n action: string;\n path: string;\n query: Record<string, string>;\n}\nexport interface SubscriptionRecoveryPort {\n observeIdentity(callback: (id: string | null) => void): () => void;\n load(\n mode: SubscriptionRecoveryMode,\n after: string,\n ): Promise<{ items: SubscriptionRecoveryItem[]; next?: string }>;\n}\n/** The host supplies authenticated read-only billing infrastructure. */\nexport const SUBSCRIPTION_RECOVERY =\n new InjectionToken<SubscriptionRecoveryPort>('SubscriptionRecovery');\n\n@Component({\n selector: 'sneat-subscription-recovery',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [\n RouterLink,\n IonButton,\n IonCard,\n IonCardContent,\n IonCardHeader,\n IonCardTitle,\n IonItem,\n IonLabel,\n IonList,\n IonNote,\n ],\n template: `\n @if (\n port && signedIn() && (busy() || error() || items().length || next())\n ) {\n <ion-card aria-label=\"Unfinished subscriptions\">\n <ion-card-header\n ><ion-card-title\n >{{ title() }}\n @if (mode() === 'test') {\n · Test mode\n }\n </ion-card-title></ion-card-header\n >\n <ion-card-content>\n @if (items().length) {\n <ion-list>\n @for (item of items(); track item.id) {\n <ion-item\n [routerLink]=\"item.path\"\n [queryParams]=\"item.query\"\n button\n detail\n >\n <ion-label class=\"ion-text-wrap\"\n ><h2>{{ item.label }}</h2>\n @if (item.description) {\n <p>{{ item.description }}</p>\n }\n <p>{{ item.action }}</p></ion-label\n >\n </ion-item>\n }\n </ion-list>\n }\n @if (busy()) {\n <p role=\"status\">Checking your purchases…</p>\n }\n @if (error()) {\n <p role=\"alert\">{{ error() }}</p>\n <ion-button fill=\"outline\" (click)=\"load(false)\">Retry</ion-button>\n }\n @if (next() && !busy() && !error()) {\n <ion-button fill=\"clear\" (click)=\"load(true)\"\n >Show more purchases</ion-button\n >\n }\n <ion-note\n >Opening a purchase checks its current status. It does not charge\n you again.</ion-note\n >\n </ion-card-content>\n </ion-card>\n }\n `,\n})\nexport class SubscriptionRecoveryComponent implements OnDestroy {\n protected readonly port = inject(SUBSCRIPTION_RECOVERY, { optional: true });\n private readonly route = inject(ActivatedRoute);\n readonly title = input('Finish subscription setup');\n protected readonly items = signal<SubscriptionRecoveryItem[]>([]);\n protected readonly mode = signal<SubscriptionRecoveryMode>('live');\n protected readonly signedIn = signal(false);\n protected readonly busy = signal(false);\n protected readonly error = signal('');\n protected readonly next = signal('');\n private generation = 0;\n private observerGeneration = 0;\n private identity: string | null = null;\n private stopIdentity?: () => void;\n private readonly routeSubscription = this.route.queryParamMap.subscribe(\n (params) => {\n this.generation++;\n const observerGeneration = ++this.observerGeneration;\n this.stopIdentity?.();\n this.items.set([]);\n this.next.set('');\n this.error.set('');\n this.busy.set(false);\n this.signedIn.set(false);\n const values = params.getAll('checkout');\n if (values.length > 1 || (values.length === 1 && values[0] !== 'test'))\n return;\n this.mode.set(values[0] === 'test' ? 'test' : 'live');\n this.stopIdentity = this.port?.observeIdentity((id) => {\n if (observerGeneration !== this.observerGeneration) return;\n this.generation++;\n this.identity = id;\n this.signedIn.set(Boolean(id));\n this.items.set([]);\n this.next.set('');\n this.busy.set(false);\n this.error.set('');\n if (id) void this.load(false);\n });\n },\n );\n\n protected async load(more: boolean): Promise<void> {\n if (!this.port || !this.identity || this.busy()) return;\n const generation = this.generation;\n const after = more ? this.next() : '';\n this.busy.set(true);\n this.error.set('');\n try {\n const page = await this.port.load(this.mode(), after);\n if (generation !== this.generation) return;\n const items = more ? [...this.items(), ...page.items] : page.items;\n this.items.set([\n ...new Map(items.map((item) => [item.id, item])).values(),\n ]);\n this.next.set(page.next ?? '');\n } catch {\n if (generation === this.generation)\n this.error.set('Your purchases could not be checked. Please retry.');\n } finally {\n if (generation === this.generation) this.busy.set(false);\n }\n }\n refresh(): void {\n if (this.busy()) return;\n this.generation++;\n this.next.set('');\n void this.load(false);\n }\n ngOnDestroy(): void {\n this.generation++;\n this.observerGeneration++;\n this.stopIdentity?.();\n this.routeSubscription.unsubscribe();\n }\n}\n"]}
|