@sneat/components 0.27.30 → 0.27.31

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 CHANGED
@@ -12,4 +12,5 @@ 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';
15
16
  //# sourceMappingURL=index.js.map
@@ -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","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';\n"]}
@@ -0,0 +1,226 @@
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 (port && signedIn()) {
106
+ <ion-card aria-label="Unfinished subscriptions">
107
+ <ion-card-header
108
+ ><ion-card-title
109
+ >{{ title() }}
110
+ @if (mode() === 'test') {
111
+ · Test mode
112
+ }
113
+ </ion-card-title></ion-card-header
114
+ >
115
+ <ion-card-content>
116
+ @if (items().length) {
117
+ <ion-list>
118
+ @for (item of items(); track item.id) {
119
+ <ion-item
120
+ [routerLink]="item.path"
121
+ [queryParams]="item.query"
122
+ button
123
+ detail
124
+ >
125
+ <ion-label class="ion-text-wrap"
126
+ ><h2>{{ item.label }}</h2>
127
+ <p>{{ item.action }}</p></ion-label
128
+ >
129
+ </ion-item>
130
+ }
131
+ </ion-list>
132
+ } @else if (!busy() && !error()) {
133
+ <p>No subscriptions waiting for setup.</p>
134
+ }
135
+ @if (busy()) {
136
+ <p role="status">Checking your purchases…</p>
137
+ }
138
+ @if (error()) {
139
+ <p role="alert">{{ error() }}</p>
140
+ <ion-button fill="outline" (click)="load(false)">Retry</ion-button>
141
+ }
142
+ @if (next() && !busy() && !error()) {
143
+ <ion-button fill="clear" (click)="load(true)"
144
+ >Show more purchases</ion-button
145
+ >
146
+ }
147
+ <ion-note
148
+ >Opening a purchase checks its current status. It does not charge
149
+ you again.</ion-note
150
+ >
151
+ </ion-card-content>
152
+ </ion-card>
153
+ }
154
+ `, 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 }); }
155
+ }
156
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SubscriptionRecoveryComponent, decorators: [{
157
+ type: Component,
158
+ args: [{
159
+ selector: 'sneat-subscription-recovery',
160
+ changeDetection: ChangeDetectionStrategy.OnPush,
161
+ imports: [
162
+ RouterLink,
163
+ IonButton,
164
+ IonCard,
165
+ IonCardContent,
166
+ IonCardHeader,
167
+ IonCardTitle,
168
+ IonItem,
169
+ IonLabel,
170
+ IonList,
171
+ IonNote,
172
+ ],
173
+ template: `
174
+ @if (port && signedIn()) {
175
+ <ion-card aria-label="Unfinished subscriptions">
176
+ <ion-card-header
177
+ ><ion-card-title
178
+ >{{ title() }}
179
+ @if (mode() === 'test') {
180
+ · Test mode
181
+ }
182
+ </ion-card-title></ion-card-header
183
+ >
184
+ <ion-card-content>
185
+ @if (items().length) {
186
+ <ion-list>
187
+ @for (item of items(); track item.id) {
188
+ <ion-item
189
+ [routerLink]="item.path"
190
+ [queryParams]="item.query"
191
+ button
192
+ detail
193
+ >
194
+ <ion-label class="ion-text-wrap"
195
+ ><h2>{{ item.label }}</h2>
196
+ <p>{{ item.action }}</p></ion-label
197
+ >
198
+ </ion-item>
199
+ }
200
+ </ion-list>
201
+ } @else if (!busy() && !error()) {
202
+ <p>No subscriptions waiting for setup.</p>
203
+ }
204
+ @if (busy()) {
205
+ <p role="status">Checking your purchases…</p>
206
+ }
207
+ @if (error()) {
208
+ <p role="alert">{{ error() }}</p>
209
+ <ion-button fill="outline" (click)="load(false)">Retry</ion-button>
210
+ }
211
+ @if (next() && !busy() && !error()) {
212
+ <ion-button fill="clear" (click)="load(true)"
213
+ >Show more purchases</ion-button
214
+ >
215
+ }
216
+ <ion-note
217
+ >Opening a purchase checks its current status. It does not charge
218
+ you again.</ion-note
219
+ >
220
+ </ion-card-content>
221
+ </ion-card>
222
+ }
223
+ `,
224
+ }]
225
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }] } });
226
+ //# 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 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 (port && signedIn()) {\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 <p>{{ item.action }}</p></ion-label\n >\n </ion-item>\n }\n </ion-list>\n } @else if (!busy() && !error()) {\n <p>No subscriptions waiting for setup.</p>\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"]}
package/index.d.ts CHANGED
@@ -12,3 +12,4 @@ 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';
@@ -0,0 +1,40 @@
1
+ import { InjectionToken, OnDestroy } from '@angular/core';
2
+ import * as i0 from "@angular/core";
3
+ export type SubscriptionRecoveryMode = 'live' | 'test';
4
+ export interface SubscriptionRecoveryItem {
5
+ id: string;
6
+ label: string;
7
+ action: string;
8
+ path: string;
9
+ query: Record<string, string>;
10
+ }
11
+ export interface SubscriptionRecoveryPort {
12
+ observeIdentity(callback: (id: string | null) => void): () => void;
13
+ load(mode: SubscriptionRecoveryMode, after: string): Promise<{
14
+ items: SubscriptionRecoveryItem[];
15
+ next?: string;
16
+ }>;
17
+ }
18
+ /** The host supplies authenticated read-only billing infrastructure. */
19
+ export declare const SUBSCRIPTION_RECOVERY: InjectionToken<SubscriptionRecoveryPort>;
20
+ export declare class SubscriptionRecoveryComponent implements OnDestroy {
21
+ protected readonly port: SubscriptionRecoveryPort | null;
22
+ private readonly route;
23
+ readonly title: import("@angular/core").InputSignal<string>;
24
+ protected readonly items: import("@angular/core").WritableSignal<SubscriptionRecoveryItem[]>;
25
+ protected readonly mode: import("@angular/core").WritableSignal<SubscriptionRecoveryMode>;
26
+ protected readonly signedIn: import("@angular/core").WritableSignal<boolean>;
27
+ protected readonly busy: import("@angular/core").WritableSignal<boolean>;
28
+ protected readonly error: import("@angular/core").WritableSignal<string>;
29
+ protected readonly next: import("@angular/core").WritableSignal<string>;
30
+ private generation;
31
+ private observerGeneration;
32
+ private identity;
33
+ private stopIdentity?;
34
+ private readonly routeSubscription;
35
+ protected load(more: boolean): Promise<void>;
36
+ refresh(): void;
37
+ ngOnDestroy(): void;
38
+ static ɵfac: i0.ɵɵFactoryDeclaration<SubscriptionRecoveryComponent, never>;
39
+ static ɵcmp: i0.ɵɵComponentDeclaration<SubscriptionRecoveryComponent, "sneat-subscription-recovery", never, { "title": { "alias": "title"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
40
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sneat/components",
3
- "version": "0.27.30",
3
+ "version": "0.27.31",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },