@sneat/extension-splitus-ui 0.2.0 → 0.2.2
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/fesm2022/{sneat-extension-splitus-ui-new-split-page.component-DkP-7W5H.mjs → sneat-extension-splitus-ui-new-split-page.component-DqIE_0pp.mjs} +8 -8
- package/fesm2022/sneat-extension-splitus-ui-new-split-page.component-DqIE_0pp.mjs.map +1 -0
- package/fesm2022/sneat-extension-splitus-ui-sneat-extension-splitus-ui-CX1eiAAR.mjs +558 -0
- package/fesm2022/sneat-extension-splitus-ui-sneat-extension-splitus-ui-CX1eiAAR.mjs.map +1 -0
- package/fesm2022/sneat-extension-splitus-ui-split-details-page.component-fkoVkmXD.mjs +105 -0
- package/fesm2022/sneat-extension-splitus-ui-split-details-page.component-fkoVkmXD.mjs.map +1 -0
- package/fesm2022/sneat-extension-splitus-ui-split-status-view-CRt2FKg1.mjs +7 -0
- package/fesm2022/sneat-extension-splitus-ui-split-status-view-CRt2FKg1.mjs.map +1 -0
- package/fesm2022/sneat-extension-splitus-ui-splitus-home-page.component-tUSn27LG.mjs +100 -0
- package/fesm2022/sneat-extension-splitus-ui-splitus-home-page.component-tUSn27LG.mjs.map +1 -0
- package/fesm2022/sneat-extension-splitus-ui-start-split-page.component-Br8thaKs.mjs +74 -0
- package/fesm2022/sneat-extension-splitus-ui-start-split-page.component-Br8thaKs.mjs.map +1 -0
- package/fesm2022/sneat-extension-splitus-ui.mjs +1 -468
- package/fesm2022/sneat-extension-splitus-ui.mjs.map +1 -1
- package/package.json +4 -3
- package/types/sneat-extension-splitus-ui.d.ts +45 -30
- package/fesm2022/sneat-extension-splitus-ui-new-split-page.component-DkP-7W5H.mjs.map +0 -1
- package/fesm2022/sneat-extension-splitus-ui-split-details-page.component-kzwMFwkF.mjs +0 -87
- package/fesm2022/sneat-extension-splitus-ui-split-details-page.component-kzwMFwkF.mjs.map +0 -1
- package/fesm2022/sneat-extension-splitus-ui-split-status-view-Dk3SLPhh.mjs +0 -44
- package/fesm2022/sneat-extension-splitus-ui-split-status-view-Dk3SLPhh.mjs.map +0 -1
- package/fesm2022/sneat-extension-splitus-ui-splitus-home-page.component-Dp5oLR86.mjs +0 -99
- package/fesm2022/sneat-extension-splitus-ui-splitus-home-page.component-Dp5oLR86.mjs.map +0 -1
- package/fesm2022/sneat-extension-splitus-ui-start-split-page.component-BHsYZbKT.mjs +0 -67
- package/fesm2022/sneat-extension-splitus-ui-start-split-page.component-BHsYZbKT.mjs.map +0 -1
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import * as _angular_core from '@angular/core';
|
|
2
|
+
import { InjectionToken } from '@angular/core';
|
|
2
3
|
import { FormControl } from '@angular/forms';
|
|
3
4
|
import { IIdAndBrief } from '@sneat/core';
|
|
4
5
|
import { IContactBrief } from '@sneat/extension-contactus-contract';
|
|
5
|
-
import {
|
|
6
|
+
import { SplitusCurrencyCode, SplitusBillKind, SplitusUtilityKind, ISplitusDebtusSettlementTargetV1 } from '@sneat/extension-splitus-contract';
|
|
6
7
|
import { ISpaceContext } from '@sneat/space-models';
|
|
7
8
|
import { Route } from '@angular/router';
|
|
8
9
|
|
|
10
|
+
type SplitMode = 'equally' | 'exact-amount' | 'percentage';
|
|
11
|
+
|
|
9
12
|
interface IShareReconciliation {
|
|
10
13
|
readonly ok: boolean;
|
|
11
14
|
/** Explanatory, user-facing message when `ok` is false. */
|
|
@@ -16,11 +19,6 @@ interface IParticipantChoice {
|
|
|
16
19
|
readonly id: string;
|
|
17
20
|
readonly title: string;
|
|
18
21
|
}
|
|
19
|
-
/** One row of the custom-share editor. `id === ''` is the payer. */
|
|
20
|
-
interface IShareRow {
|
|
21
|
-
readonly id: string;
|
|
22
|
-
readonly title: string;
|
|
23
|
-
}
|
|
24
22
|
declare class NewSplitFormComponent {
|
|
25
23
|
private readonly errorLogger;
|
|
26
24
|
private readonly splitusService;
|
|
@@ -29,48 +27,53 @@ declare class NewSplitFormComponent {
|
|
|
29
27
|
private readonly destroyRef;
|
|
30
28
|
private readonly draftSplitStore;
|
|
31
29
|
protected readonly $resumedFromDraft: _angular_core.WritableSignal<boolean>;
|
|
30
|
+
private readonly $billID;
|
|
32
31
|
private draftChecked;
|
|
33
32
|
protected readonly $space: _angular_core.WritableSignal<ISpaceContext | undefined>;
|
|
34
33
|
set space(value: ISpaceContext | undefined);
|
|
35
|
-
private tryResumeDraft;
|
|
36
|
-
protected dismissDraftBanner(): void;
|
|
37
34
|
protected readonly $currentUserID: _angular_core.WritableSignal<string | undefined>;
|
|
38
|
-
/** The authenticated user's ID — used to exclude the payer's own contact. */
|
|
39
35
|
set currentUserID(value: string | undefined);
|
|
36
|
+
private tryResumeDraft;
|
|
37
|
+
protected dismissDraftBanner(): void;
|
|
40
38
|
private contactsSubscription?;
|
|
41
39
|
private watchedSpaceID?;
|
|
42
40
|
protected readonly $contacts: _angular_core.WritableSignal<IIdAndBrief<IContactBrief>[] | undefined>;
|
|
43
41
|
protected readonly $contactsError: _angular_core.WritableSignal<string | undefined>;
|
|
44
42
|
private watchContacts;
|
|
45
|
-
|
|
46
|
-
* The space's contactus people offered as participants. The payer (the
|
|
47
|
-
* authenticated user) is always a participant and must not be listed in
|
|
48
|
-
* `participantContactIDs`, so their own contact is excluded from the picker.
|
|
49
|
-
*/
|
|
43
|
+
protected readonly $payer: _angular_core.Signal<IParticipantChoice | undefined>;
|
|
50
44
|
protected readonly $participantChoices: _angular_core.Signal<readonly IParticipantChoice[] | undefined>;
|
|
51
45
|
protected readonly $selectedContactIDs: _angular_core.WritableSignal<readonly string[]>;
|
|
52
46
|
protected onParticipantToggle(contactID: string, checked: boolean): void;
|
|
53
47
|
protected onParticipantChecked(contactID: string, event: Event): void;
|
|
54
48
|
protected readonly title: FormControl<string>;
|
|
55
|
-
protected readonly currency: FormControl<
|
|
56
|
-
protected readonly amount: FormControl<
|
|
49
|
+
protected readonly currency: FormControl<SplitusCurrencyCode>;
|
|
50
|
+
protected readonly amount: FormControl<string>;
|
|
51
|
+
protected readonly billKind: FormControl<SplitusBillKind>;
|
|
52
|
+
protected readonly utilityKind: FormControl<SplitusUtilityKind>;
|
|
53
|
+
protected readonly providerName: FormControl<string>;
|
|
54
|
+
protected readonly periodStart: FormControl<string>;
|
|
55
|
+
protected readonly periodEnd: FormControl<string>;
|
|
57
56
|
private readonly $amount;
|
|
58
|
-
protected readonly
|
|
57
|
+
protected readonly $billKind: _angular_core.Signal<SplitusBillKind>;
|
|
58
|
+
private readonly $periodStart;
|
|
59
|
+
private readonly $periodEnd;
|
|
60
|
+
protected readonly currencies: readonly SplitusCurrencyCode[];
|
|
61
|
+
protected readonly utilityKinds: readonly {
|
|
62
|
+
value: SplitusUtilityKind;
|
|
63
|
+
title: string;
|
|
64
|
+
}[];
|
|
59
65
|
protected readonly $splitMode: _angular_core.WritableSignal<SplitMode>;
|
|
60
|
-
protected setSplitMode(mode: SplitMode): void;
|
|
61
66
|
protected onSplitModeChange(event: Event): void;
|
|
62
|
-
/** Raw share inputs keyed by contact ID ('' = the payer). */
|
|
63
67
|
protected readonly $shareValues: _angular_core.WritableSignal<Readonly<Record<string, string>>>;
|
|
64
|
-
protected setShareValue(contactID: string, value: string): void;
|
|
65
68
|
protected shareValue(contactID: string): string;
|
|
66
69
|
protected onShareInput(contactID: string, event: Event): void;
|
|
67
|
-
|
|
68
|
-
protected readonly $shareRows: _angular_core.Signal<readonly IShareRow[]>;
|
|
69
|
-
/** Live reconciliation of custom shares against the expense total. */
|
|
70
|
+
protected readonly $shareRows: _angular_core.Signal<readonly IParticipantChoice[]>;
|
|
70
71
|
protected readonly $reconciliation: _angular_core.Signal<IShareReconciliation>;
|
|
72
|
+
private readonly $utilityValid;
|
|
71
73
|
protected readonly $canSubmit: _angular_core.Signal<boolean>;
|
|
72
74
|
protected readonly $submitting: _angular_core.WritableSignal<boolean>;
|
|
73
75
|
protected readonly $submitError: _angular_core.WritableSignal<string | undefined>;
|
|
76
|
+
private owedAllocations;
|
|
74
77
|
protected submit(): void;
|
|
75
78
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<NewSplitFormComponent, never>;
|
|
76
79
|
static ɵcmp: _angular_core.ɵɵComponentDeclaration<NewSplitFormComponent, "sneat-splitus-new-split-form", never, { "space": { "alias": "space"; "required": true; }; "currentUserID": { "alias": "currentUserID"; "required": false; }; }, {}, never, never, true, never>;
|
|
@@ -78,18 +81,20 @@ declare class NewSplitFormComponent {
|
|
|
78
81
|
|
|
79
82
|
declare const spacePagesRoutes: Route[];
|
|
80
83
|
|
|
81
|
-
declare const
|
|
84
|
+
declare const startBillRoute: Route;
|
|
85
|
+
declare const legacyStartSplitRoute: Route;
|
|
82
86
|
|
|
83
87
|
/**
|
|
84
88
|
* A single in-progress "start a split" draft — captured before the user has
|
|
85
89
|
* a space or is even signed in (see StartSplitPageComponent), resumed into
|
|
86
|
-
* NewSplitFormComponent once they are.
|
|
87
|
-
*
|
|
90
|
+
* NewSplitFormComponent once they are. The canonical journey requirements are
|
|
91
|
+
* maintained in the Backstage SpecScore Splitus features.
|
|
88
92
|
*/
|
|
89
93
|
interface IDraftSplit {
|
|
94
|
+
readonly billID: string;
|
|
90
95
|
readonly title: string;
|
|
91
|
-
readonly amount:
|
|
92
|
-
readonly currencyCode:
|
|
96
|
+
readonly amount: string;
|
|
97
|
+
readonly currencyCode: SplitusCurrencyCode;
|
|
93
98
|
readonly createdAt: number;
|
|
94
99
|
}
|
|
95
100
|
declare const DRAFT_SPLIT_STORAGE_KEY = "sneat.splitus.draftSplit";
|
|
@@ -102,5 +107,15 @@ declare class DraftSplitStore {
|
|
|
102
107
|
static ɵprov: _angular_core.ɵɵInjectableDeclaration<DraftSplitStore>;
|
|
103
108
|
}
|
|
104
109
|
|
|
105
|
-
|
|
106
|
-
|
|
110
|
+
interface ISplitusSettlementTarget extends ISplitusDebtusSettlementTargetV1 {
|
|
111
|
+
readonly spaceType: string;
|
|
112
|
+
}
|
|
113
|
+
/** App-owned cross-product navigation boundary for settling a split share. */
|
|
114
|
+
type SplitusSettlementUrlBuilder = (target: ISplitusSettlementTarget) => string | undefined;
|
|
115
|
+
declare const SPLITUS_SETTLEMENT_URL_BUILDER: InjectionToken<SplitusSettlementUrlBuilder>;
|
|
116
|
+
|
|
117
|
+
/** URL opened after an unauthenticated bill draft is saved. */
|
|
118
|
+
declare const SPLITUS_START_CONTINUATION_URL: InjectionToken<string>;
|
|
119
|
+
|
|
120
|
+
export { DRAFT_SPLIT_STORAGE_KEY, DraftSplitStore, NewSplitFormComponent, SPLITUS_SETTLEMENT_URL_BUILDER, SPLITUS_START_CONTINUATION_URL, legacyStartSplitRoute, spacePagesRoutes, startBillRoute };
|
|
121
|
+
export type { IDraftSplit, ISplitusSettlementTarget, SplitusSettlementUrlBuilder };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sneat-extension-splitus-ui-new-split-page.component-DkP-7W5H.mjs","sources":["../../../../../../libs/extensions/splitus/ui/src/lib/pages/new-split/new-split-page.component.ts","../../../../../../libs/extensions/splitus/ui/src/lib/pages/new-split/new-split-page.component.html"],"sourcesContent":["import { Component } from '@angular/core';\nimport {\n IonBackButton,\n IonButtons,\n IonContent,\n IonHeader,\n IonTitle,\n IonToolbar,\n} from '@ionic/angular';\nimport {\n SpaceComponentBaseParams,\n SpacePageBaseComponent,\n} from '@sneat/space-components';\nimport { ClassName } from '@sneat/ui';\nimport { NewSplitFormComponent } from '../../components';\n\n// Create-split page. Entry: the \"New split\" button on the splits home page.\n// Exit: the hosted form redirects to `split/:splitID` (details) on success\n// with replaceUrl; Back/cancel returns to `splits`.\n@Component({\n selector: 'sneat-splitus-new-split-page',\n templateUrl: './new-split-page.component.html',\n imports: [\n IonHeader,\n IonToolbar,\n IonButtons,\n IonBackButton,\n IonTitle,\n IonContent,\n NewSplitFormComponent,\n ],\n providers: [\n { provide: ClassName, useValue: 'NewSplitPageComponent' },\n SpaceComponentBaseParams,\n ],\n})\nexport class NewSplitPageComponent extends SpacePageBaseComponent {\n constructor() {\n super();\n this.$defaultBackUrlSpacePath.set('splits');\n }\n}\n","<ion-header>\n <ion-toolbar color=\"light\">\n <ion-buttons slot=\"start\">\n <ion-back-button [defaultHref]=\"$defaultBackUrl()\" />\n </ion-buttons>\n <ion-title>New split</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n <sneat-splitus-new-split-form\n [space]=\"space\"\n [currentUserID]=\"currentUserId\"\n />\n</ion-content>\n"],"names":[],"mappings":";;;;;;;AAgBA;AACA;AACA;AAkBM,MAAO,qBAAsB,SAAQ,sBAAsB,CAAA;AAC/D,IAAA,WAAA,GAAA;AACE,QAAA,KAAK,EAAE;AACP,QAAA,IAAI,CAAC,wBAAwB,CAAC,GAAG,CAAC,QAAQ,CAAC;IAC7C;uGAJW,qBAAqB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAArB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,qBAAqB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,8BAAA,EAAA,SAAA,EALrB;AACT,YAAA,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,uBAAuB,EAAE;YACzD,wBAAwB;AACzB,SAAA,EAAA,eAAA,EAAA,IAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EClCH,mYAeA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EDQI,SAAS,EAAA,QAAA,EAAA,YAAA,EAAA,MAAA,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,aAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACT,UAAU,EAAA,QAAA,EAAA,aAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACV,UAAU,EAAA,QAAA,EAAA,aAAA,EAAA,MAAA,EAAA,CAAA,UAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACV,aAAa,EAAA,QAAA,EAAA,iBAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACb,QAAQ,EAAA,QAAA,EAAA,WAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACR,UAAU,kOACV,qBAAqB,EAAA,QAAA,EAAA,8BAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,eAAA,CAAA,EAAA,CAAA,EAAA,CAAA;;2FAOZ,qBAAqB,EAAA,UAAA,EAAA,CAAA;kBAjBjC,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,8BAA8B,EAAA,OAAA,EAE/B;wBACP,SAAS;wBACT,UAAU;wBACV,UAAU;wBACV,aAAa;wBACb,QAAQ;wBACR,UAAU;wBACV,qBAAqB;qBACtB,EAAA,SAAA,EACU;AACT,wBAAA,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,uBAAuB,EAAE;wBACzD,wBAAwB;AACzB,qBAAA,EAAA,QAAA,EAAA,mYAAA,EAAA;;;;;"}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, signal, Component } from '@angular/core';
|
|
3
|
-
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
4
|
-
import { IonHeader, IonToolbar, IonButtons, IonBackButton, IonTitle, IonContent, IonCard, IonCardHeader, IonCardTitle, IonCardContent, IonList, IonItem, IonLabel, IonNote, IonBadge, IonButton, IonSpinner } from '@ionic/angular';
|
|
5
|
-
import { SPLITUS_SERVICE } from '@sneat/extension-splitus-contract';
|
|
6
|
-
import { SpacePageBaseComponent, SpaceComponentBaseParams } from '@sneat/space-components';
|
|
7
|
-
import { ClassName } from '@sneat/ui';
|
|
8
|
-
import { combineLatest, switchMap } from 'rxjs';
|
|
9
|
-
import { s as statusColor, a as settleInDebtusUrl } from './sneat-extension-splitus-ui-split-status-view-Dk3SLPhh.mjs';
|
|
10
|
-
|
|
11
|
-
// Split details — per-participant shares with settled/outstanding read
|
|
12
|
-
// straight from getSplit (splitus#ac:settle-up-single-source-of-truth: no
|
|
13
|
-
// client-side settled logic or storage), plus a "Settle in Debtus" link on
|
|
14
|
-
// outstanding rows. Loads by the :splitID in the URL, so deep links and
|
|
15
|
-
// refreshes work without router state. Entry: the splits list
|
|
16
|
-
// (splitus-home-page) and new-split's post-create redirect. Exit: back to
|
|
17
|
-
// `splits`, or out to debtus.app to actually settle.
|
|
18
|
-
class SplitDetailsPageComponent extends SpacePageBaseComponent {
|
|
19
|
-
splitusService = inject(SPLITUS_SERVICE);
|
|
20
|
-
$loading = signal(true, /* @ts-ignore */
|
|
21
|
-
...(ngDevMode ? [{ debugName: "$loading" }] : /* istanbul ignore next */ []));
|
|
22
|
-
$error = signal(undefined, /* @ts-ignore */
|
|
23
|
-
...(ngDevMode ? [{ debugName: "$error" }] : /* istanbul ignore next */ []));
|
|
24
|
-
$split = signal(undefined, /* @ts-ignore */
|
|
25
|
-
...(ngDevMode ? [{ debugName: "$split" }] : /* istanbul ignore next */ []));
|
|
26
|
-
statusColor = statusColor;
|
|
27
|
-
constructor() {
|
|
28
|
-
super();
|
|
29
|
-
this.$defaultBackUrlSpacePath.set('splits');
|
|
30
|
-
combineLatest([this.spaceIDChanged$, this.route.paramMap])
|
|
31
|
-
.pipe(switchMap(([spaceID, params]) => {
|
|
32
|
-
const splitID = params.get('splitID') ?? '';
|
|
33
|
-
this.$loading.set(true);
|
|
34
|
-
this.$error.set(undefined);
|
|
35
|
-
return this.splitusService.getSplit(spaceID ?? '', splitID);
|
|
36
|
-
}), takeUntilDestroyed())
|
|
37
|
-
.subscribe({
|
|
38
|
-
next: (split) => {
|
|
39
|
-
this.$split.set(split);
|
|
40
|
-
this.$loading.set(false);
|
|
41
|
-
},
|
|
42
|
-
error: (err) => {
|
|
43
|
-
this.$error.set('Failed to load split');
|
|
44
|
-
this.$loading.set(false);
|
|
45
|
-
this.errorLogger.logError(err, 'Failed to load split', {
|
|
46
|
-
show: false,
|
|
47
|
-
});
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
settleUrl(participant) {
|
|
52
|
-
return settleInDebtusUrl(participant, this.$spaceType(), this.$spaceID());
|
|
53
|
-
}
|
|
54
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SplitDetailsPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
55
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: SplitDetailsPageComponent, isStandalone: true, selector: "sneat-splitus-split-details-page", providers: [
|
|
56
|
-
{ provide: ClassName, useValue: 'SplitDetailsPageComponent' },
|
|
57
|
-
SpaceComponentBaseParams,
|
|
58
|
-
], usesInheritance: true, ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"light\">\n <ion-buttons slot=\"start\">\n <ion-back-button [defaultHref]=\"$defaultBackUrl()\" />\n </ion-buttons>\n <ion-title>{{ $split()?.title || 'Split' }}</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n @if ($loading()) {\n <ion-card>\n <ion-card-content>\n <ion-spinner name=\"lines\" class=\"ion-margin-end\" />\n Loading\u2026\n </ion-card-content>\n </ion-card>\n } @else if ($error()) {\n <ion-card>\n <ion-card-content>\n <ion-note color=\"danger\">{{ $error() }}</ion-note>\n </ion-card-content>\n </ion-card>\n } @else if ($split(); as split) {\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n {{ split.title || 'Split' }}\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <p>Total: {{ split.amount.toFixed(2) }} {{ split.currency }}</p>\n <ion-badge [color]=\"statusColor(split.status)\">{{\n split.status\n }}</ion-badge>\n </ion-card-content>\n </ion-card>\n\n <ion-card>\n <ion-card-header>\n <ion-card-title>Participants</ion-card-title>\n </ion-card-header>\n <ion-list>\n @for (p of split.participants; track $index) {\n <ion-item>\n <ion-label>\n <h2>{{ p.name }}{{ p.isPayer ? ' (payer)' : '' }}</h2>\n <p>{{ p.share.toFixed(2) }} {{ split.currency }}</p>\n </ion-label>\n <div slot=\"end\" class=\"ion-text-end\">\n <ion-badge [color]=\"statusColor(p.status)\">{{\n p.status\n }}</ion-badge>\n @if (settleUrl(p); as url) {\n <div class=\"ion-margin-top\">\n <ion-button\n [href]=\"url\"\n target=\"_blank\"\n rel=\"noopener\"\n size=\"small\"\n fill=\"outline\"\n >\n Settle in Debtus\n </ion-button>\n </div>\n }\n </div>\n </ion-item>\n } @empty {\n <ion-item lines=\"none\">\n <ion-label color=\"medium\">No participants yet.</ion-label>\n </ion-item>\n }\n </ion-list>\n </ion-card>\n }\n</ion-content>\n", dependencies: [{ kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonBackButton, selector: "ion-back-button" }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"], outputs: ["ionScrollStart", "ionScroll", "ionScrollEnd"] }, { kind: "component", type: IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { 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: IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "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: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: IonBadge, selector: "ion-badge", inputs: ["color", "mode"] }, { 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: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }] });
|
|
59
|
-
}
|
|
60
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SplitDetailsPageComponent, decorators: [{
|
|
61
|
-
type: Component,
|
|
62
|
-
args: [{ selector: 'sneat-splitus-split-details-page', imports: [
|
|
63
|
-
IonHeader,
|
|
64
|
-
IonToolbar,
|
|
65
|
-
IonButtons,
|
|
66
|
-
IonBackButton,
|
|
67
|
-
IonTitle,
|
|
68
|
-
IonContent,
|
|
69
|
-
IonCard,
|
|
70
|
-
IonCardHeader,
|
|
71
|
-
IonCardTitle,
|
|
72
|
-
IonCardContent,
|
|
73
|
-
IonList,
|
|
74
|
-
IonItem,
|
|
75
|
-
IonLabel,
|
|
76
|
-
IonNote,
|
|
77
|
-
IonBadge,
|
|
78
|
-
IonButton,
|
|
79
|
-
IonSpinner,
|
|
80
|
-
], providers: [
|
|
81
|
-
{ provide: ClassName, useValue: 'SplitDetailsPageComponent' },
|
|
82
|
-
SpaceComponentBaseParams,
|
|
83
|
-
], template: "<ion-header>\n <ion-toolbar color=\"light\">\n <ion-buttons slot=\"start\">\n <ion-back-button [defaultHref]=\"$defaultBackUrl()\" />\n </ion-buttons>\n <ion-title>{{ $split()?.title || 'Split' }}</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n @if ($loading()) {\n <ion-card>\n <ion-card-content>\n <ion-spinner name=\"lines\" class=\"ion-margin-end\" />\n Loading\u2026\n </ion-card-content>\n </ion-card>\n } @else if ($error()) {\n <ion-card>\n <ion-card-content>\n <ion-note color=\"danger\">{{ $error() }}</ion-note>\n </ion-card-content>\n </ion-card>\n } @else if ($split(); as split) {\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n {{ split.title || 'Split' }}\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <p>Total: {{ split.amount.toFixed(2) }} {{ split.currency }}</p>\n <ion-badge [color]=\"statusColor(split.status)\">{{\n split.status\n }}</ion-badge>\n </ion-card-content>\n </ion-card>\n\n <ion-card>\n <ion-card-header>\n <ion-card-title>Participants</ion-card-title>\n </ion-card-header>\n <ion-list>\n @for (p of split.participants; track $index) {\n <ion-item>\n <ion-label>\n <h2>{{ p.name }}{{ p.isPayer ? ' (payer)' : '' }}</h2>\n <p>{{ p.share.toFixed(2) }} {{ split.currency }}</p>\n </ion-label>\n <div slot=\"end\" class=\"ion-text-end\">\n <ion-badge [color]=\"statusColor(p.status)\">{{\n p.status\n }}</ion-badge>\n @if (settleUrl(p); as url) {\n <div class=\"ion-margin-top\">\n <ion-button\n [href]=\"url\"\n target=\"_blank\"\n rel=\"noopener\"\n size=\"small\"\n fill=\"outline\"\n >\n Settle in Debtus\n </ion-button>\n </div>\n }\n </div>\n </ion-item>\n } @empty {\n <ion-item lines=\"none\">\n <ion-label color=\"medium\">No participants yet.</ion-label>\n </ion-item>\n }\n </ion-list>\n </ion-card>\n }\n</ion-content>\n" }]
|
|
84
|
-
}], ctorParameters: () => [] });
|
|
85
|
-
|
|
86
|
-
export { SplitDetailsPageComponent };
|
|
87
|
-
//# sourceMappingURL=sneat-extension-splitus-ui-split-details-page.component-kzwMFwkF.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sneat-extension-splitus-ui-split-details-page.component-kzwMFwkF.mjs","sources":["../../../../../../libs/extensions/splitus/ui/src/lib/pages/split-details/split-details-page.component.ts","../../../../../../libs/extensions/splitus/ui/src/lib/pages/split-details/split-details-page.component.html"],"sourcesContent":["import { Component, inject, signal } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport {\n IonBackButton,\n IonBadge,\n IonButton,\n IonButtons,\n IonCard,\n IonCardContent,\n IonCardHeader,\n IonCardTitle,\n IonContent,\n IonHeader,\n IonItem,\n IonLabel,\n IonList,\n IonNote,\n IonSpinner,\n IonTitle,\n IonToolbar,\n} from '@ionic/angular';\nimport { ISplit, ISplitParticipant, SPLITUS_SERVICE } from '@sneat/extension-splitus-contract';\nimport {\n SpaceComponentBaseParams,\n SpacePageBaseComponent,\n} from '@sneat/space-components';\nimport { ClassName } from '@sneat/ui';\nimport { combineLatest, switchMap } from 'rxjs';\nimport { settleInDebtusUrl, statusColor } from '../split-status-view';\n\n// Split details — per-participant shares with settled/outstanding read\n// straight from getSplit (splitus#ac:settle-up-single-source-of-truth: no\n// client-side settled logic or storage), plus a \"Settle in Debtus\" link on\n// outstanding rows. Loads by the :splitID in the URL, so deep links and\n// refreshes work without router state. Entry: the splits list\n// (splitus-home-page) and new-split's post-create redirect. Exit: back to\n// `splits`, or out to debtus.app to actually settle.\n@Component({\n selector: 'sneat-splitus-split-details-page',\n templateUrl: './split-details-page.component.html',\n imports: [\n IonHeader,\n IonToolbar,\n IonButtons,\n IonBackButton,\n IonTitle,\n IonContent,\n IonCard,\n IonCardHeader,\n IonCardTitle,\n IonCardContent,\n IonList,\n IonItem,\n IonLabel,\n IonNote,\n IonBadge,\n IonButton,\n IonSpinner,\n ],\n providers: [\n { provide: ClassName, useValue: 'SplitDetailsPageComponent' },\n SpaceComponentBaseParams,\n ],\n})\nexport class SplitDetailsPageComponent extends SpacePageBaseComponent {\n private readonly splitusService = inject(SPLITUS_SERVICE);\n\n protected readonly $loading = signal(true);\n protected readonly $error = signal<string | undefined>(undefined);\n protected readonly $split = signal<ISplit | undefined>(undefined);\n\n protected readonly statusColor = statusColor;\n\n constructor() {\n super();\n this.$defaultBackUrlSpacePath.set('splits');\n combineLatest([this.spaceIDChanged$, this.route.paramMap])\n .pipe(\n switchMap(([spaceID, params]) => {\n const splitID = params.get('splitID') ?? '';\n this.$loading.set(true);\n this.$error.set(undefined);\n return this.splitusService.getSplit(spaceID ?? '', splitID);\n }),\n takeUntilDestroyed(),\n )\n .subscribe({\n next: (split) => {\n this.$split.set(split);\n this.$loading.set(false);\n },\n error: (err) => {\n this.$error.set('Failed to load split');\n this.$loading.set(false);\n this.errorLogger.logError(err, 'Failed to load split', {\n show: false,\n });\n },\n });\n }\n\n protected settleUrl(participant: ISplitParticipant): string | undefined {\n return settleInDebtusUrl(\n participant,\n this.$spaceType(),\n this.$spaceID(),\n );\n }\n}\n","<ion-header>\n <ion-toolbar color=\"light\">\n <ion-buttons slot=\"start\">\n <ion-back-button [defaultHref]=\"$defaultBackUrl()\" />\n </ion-buttons>\n <ion-title>{{ $split()?.title || 'Split' }}</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n @if ($loading()) {\n <ion-card>\n <ion-card-content>\n <ion-spinner name=\"lines\" class=\"ion-margin-end\" />\n Loading…\n </ion-card-content>\n </ion-card>\n } @else if ($error()) {\n <ion-card>\n <ion-card-content>\n <ion-note color=\"danger\">{{ $error() }}</ion-note>\n </ion-card-content>\n </ion-card>\n } @else if ($split(); as split) {\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n {{ split.title || 'Split' }}\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <p>Total: {{ split.amount.toFixed(2) }} {{ split.currency }}</p>\n <ion-badge [color]=\"statusColor(split.status)\">{{\n split.status\n }}</ion-badge>\n </ion-card-content>\n </ion-card>\n\n <ion-card>\n <ion-card-header>\n <ion-card-title>Participants</ion-card-title>\n </ion-card-header>\n <ion-list>\n @for (p of split.participants; track $index) {\n <ion-item>\n <ion-label>\n <h2>{{ p.name }}{{ p.isPayer ? ' (payer)' : '' }}</h2>\n <p>{{ p.share.toFixed(2) }} {{ split.currency }}</p>\n </ion-label>\n <div slot=\"end\" class=\"ion-text-end\">\n <ion-badge [color]=\"statusColor(p.status)\">{{\n p.status\n }}</ion-badge>\n @if (settleUrl(p); as url) {\n <div class=\"ion-margin-top\">\n <ion-button\n [href]=\"url\"\n target=\"_blank\"\n rel=\"noopener\"\n size=\"small\"\n fill=\"outline\"\n >\n Settle in Debtus\n </ion-button>\n </div>\n }\n </div>\n </ion-item>\n } @empty {\n <ion-item lines=\"none\">\n <ion-label color=\"medium\">No participants yet.</ion-label>\n </ion-item>\n }\n </ion-list>\n </ion-card>\n }\n</ion-content>\n"],"names":[],"mappings":"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"}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
// Pure view-mapping helpers shared by the splits home list and the split
|
|
2
|
-
// details page. Kept side-effect-free and unit-tested directly (see
|
|
3
|
-
// split-status-view.spec.ts), the same way new-split-form's split-shares.ts
|
|
4
|
-
// isolates its reconciliation logic from the component.
|
|
5
|
-
/**
|
|
6
|
-
* Maps a split/participant status string to a badge color. This is the ONLY
|
|
7
|
-
* thing derived from `status` client-side — the status text itself is always
|
|
8
|
-
* rendered verbatim, exactly as returned by getSplit/getSplits, never
|
|
9
|
-
* recomputed or renamed here (splitus#ac:settle-up-single-source-of-truth:
|
|
10
|
-
* Splitus reads settled/unsettled state from Debtus and never keeps its own).
|
|
11
|
-
*/
|
|
12
|
-
function statusColor(status) {
|
|
13
|
-
return status === 'settled' ? 'success' : 'warning';
|
|
14
|
-
}
|
|
15
|
-
// Debtus is a separate app — splitus-app's space shell mounts only the
|
|
16
|
-
// splitus routes (see splitus-space.routes.ts), not debtus's, to keep the two
|
|
17
|
-
// apps decoupled. So an outstanding share's "Settle in Debtus" affordance
|
|
18
|
-
// links out to the debtus.app settle-up surface by an absolute URL rather
|
|
19
|
-
// than an in-app route (mounting debtus's routes/providers into splitus-app
|
|
20
|
-
// just for this one link would undo that intentional decoupling).
|
|
21
|
-
const DEBTUS_APP_BASE_URL = 'https://debtus.app';
|
|
22
|
-
/**
|
|
23
|
-
* The settle-up link for one participant row, or undefined when settling
|
|
24
|
-
* doesn't apply: the payer never settles with themselves, a settled share has
|
|
25
|
-
* nothing left to settle, and a participant with no contactID (e.g. a
|
|
26
|
-
* userID-only participant) has no debtus counterparty to link to.
|
|
27
|
-
*/
|
|
28
|
-
function settleInDebtusUrl(participant, spaceType, spaceID) {
|
|
29
|
-
if (participant.status !== 'outstanding' || participant.isPayer) {
|
|
30
|
-
return undefined;
|
|
31
|
-
}
|
|
32
|
-
const contactID = participant.contactID;
|
|
33
|
-
if (!contactID || !spaceType || !spaceID) {
|
|
34
|
-
return undefined;
|
|
35
|
-
}
|
|
36
|
-
return `${DEBTUS_APP_BASE_URL}/space/${spaceType}/${spaceID}/settle-up?contactID=${encodeURIComponent(contactID)}`;
|
|
37
|
-
}
|
|
38
|
-
/** "1 member" / "2 members" — used on the splits list rows. */
|
|
39
|
-
function formatMemberCount(count) {
|
|
40
|
-
return `${count} ${count === 1 ? 'member' : 'members'}`;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export { settleInDebtusUrl as a, formatMemberCount as f, statusColor as s };
|
|
44
|
-
//# sourceMappingURL=sneat-extension-splitus-ui-split-status-view-Dk3SLPhh.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sneat-extension-splitus-ui-split-status-view-Dk3SLPhh.mjs","sources":["../../../../../../libs/extensions/splitus/ui/src/lib/pages/split-status-view.ts"],"sourcesContent":["import { ISplitParticipant } from '@sneat/extension-splitus-contract';\n\n// Pure view-mapping helpers shared by the splits home list and the split\n// details page. Kept side-effect-free and unit-tested directly (see\n// split-status-view.spec.ts), the same way new-split-form's split-shares.ts\n// isolates its reconciliation logic from the component.\n\n/**\n * Maps a split/participant status string to a badge color. This is the ONLY\n * thing derived from `status` client-side — the status text itself is always\n * rendered verbatim, exactly as returned by getSplit/getSplits, never\n * recomputed or renamed here (splitus#ac:settle-up-single-source-of-truth:\n * Splitus reads settled/unsettled state from Debtus and never keeps its own).\n */\nexport function statusColor(status: string): 'success' | 'warning' {\n return status === 'settled' ? 'success' : 'warning';\n}\n\n// Debtus is a separate app — splitus-app's space shell mounts only the\n// splitus routes (see splitus-space.routes.ts), not debtus's, to keep the two\n// apps decoupled. So an outstanding share's \"Settle in Debtus\" affordance\n// links out to the debtus.app settle-up surface by an absolute URL rather\n// than an in-app route (mounting debtus's routes/providers into splitus-app\n// just for this one link would undo that intentional decoupling).\nconst DEBTUS_APP_BASE_URL = 'https://debtus.app';\n\n/**\n * The settle-up link for one participant row, or undefined when settling\n * doesn't apply: the payer never settles with themselves, a settled share has\n * nothing left to settle, and a participant with no contactID (e.g. a\n * userID-only participant) has no debtus counterparty to link to.\n */\nexport function settleInDebtusUrl(\n participant: Pick<ISplitParticipant, 'status' | 'isPayer' | 'contactID'>,\n spaceType: string | undefined,\n spaceID: string | undefined,\n): string | undefined {\n if (participant.status !== 'outstanding' || participant.isPayer) {\n return undefined;\n }\n const contactID = participant.contactID;\n if (!contactID || !spaceType || !spaceID) {\n return undefined;\n }\n return `${DEBTUS_APP_BASE_URL}/space/${spaceType}/${spaceID}/settle-up?contactID=${encodeURIComponent(contactID)}`;\n}\n\n/** \"1 member\" / \"2 members\" — used on the splits list rows. */\nexport function formatMemberCount(count: number): string {\n return `${count} ${count === 1 ? 'member' : 'members'}`;\n}\n"],"names":[],"mappings":"AAEA;AACA;AACA;AACA;AAEA;;;;;;AAMG;AACG,SAAU,WAAW,CAAC,MAAc,EAAA;IACxC,OAAO,MAAM,KAAK,SAAS,GAAG,SAAS,GAAG,SAAS;AACrD;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM,mBAAmB,GAAG,oBAAoB;AAEhD;;;;;AAKG;SACa,iBAAiB,CAC/B,WAAwE,EACxE,SAA6B,EAC7B,OAA2B,EAAA;IAE3B,IAAI,WAAW,CAAC,MAAM,KAAK,aAAa,IAAI,WAAW,CAAC,OAAO,EAAE;AAC/D,QAAA,OAAO,SAAS;IAClB;AACA,IAAA,MAAM,SAAS,GAAG,WAAW,CAAC,SAAS;IACvC,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS,IAAI,CAAC,OAAO,EAAE;AACxC,QAAA,OAAO,SAAS;IAClB;AACA,IAAA,OAAO,CAAA,EAAG,mBAAmB,CAAA,OAAA,EAAU,SAAS,CAAA,CAAA,EAAI,OAAO,CAAA,qBAAA,EAAwB,kBAAkB,CAAC,SAAS,CAAC,CAAA,CAAE;AACpH;AAEA;AACM,SAAU,iBAAiB,CAAC,KAAa,EAAA;AAC7C,IAAA,OAAO,CAAA,EAAG,KAAK,CAAA,CAAA,EAAI,KAAK,KAAK,CAAC,GAAG,QAAQ,GAAG,SAAS,EAAE;AACzD;;;;"}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, signal, Component } from '@angular/core';
|
|
3
|
-
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
4
|
-
import { IonHeader, IonToolbar, IonButtons, IonBackButton, IonContent, IonCard, IonCardContent, IonButton, IonIcon, IonList, IonItem, IonLabel, IonNote, IonBadge, IonSpinner } from '@ionic/angular';
|
|
5
|
-
import { SPLITUS_SERVICE } from '@sneat/extension-splitus-contract';
|
|
6
|
-
import { SpacePageBaseComponent, SpacePageTitleComponent, SpaceComponentBaseParams } from '@sneat/space-components';
|
|
7
|
-
import { SpaceServiceModule } from '@sneat/space-services';
|
|
8
|
-
import { ClassName } from '@sneat/ui';
|
|
9
|
-
import { switchMap, EMPTY } from 'rxjs';
|
|
10
|
-
import { DraftSplitStore } from './sneat-extension-splitus-ui.mjs';
|
|
11
|
-
import { s as statusColor, f as formatMemberCount } from './sneat-extension-splitus-ui-split-status-view-Dk3SLPhh.mjs';
|
|
12
|
-
|
|
13
|
-
// Splits home — the splitus entry point for a space: the space's splits
|
|
14
|
-
// (read via getSplits) plus the "New split" action. Entry: space navigation /
|
|
15
|
-
// the splitus-space default redirect. Exit: "New split" -> new-split; each
|
|
16
|
-
// row -> split/:splitID.
|
|
17
|
-
class SplitusHomePageComponent extends SpacePageBaseComponent {
|
|
18
|
-
splitusService = inject(SPLITUS_SERVICE);
|
|
19
|
-
draftSplitStore = inject(DraftSplitStore);
|
|
20
|
-
$loading = signal(true, /* @ts-ignore */
|
|
21
|
-
...(ngDevMode ? [{ debugName: "$loading" }] : /* istanbul ignore next */ []));
|
|
22
|
-
$error = signal(undefined, /* @ts-ignore */
|
|
23
|
-
...(ngDevMode ? [{ debugName: "$error" }] : /* istanbul ignore next */ []));
|
|
24
|
-
$splits = signal([], /* @ts-ignore */
|
|
25
|
-
...(ngDevMode ? [{ debugName: "$splits" }] : /* istanbul ignore next */ []));
|
|
26
|
-
statusColor = statusColor;
|
|
27
|
-
formatMemberCount = formatMemberCount;
|
|
28
|
-
constructor() {
|
|
29
|
-
super();
|
|
30
|
-
this.spaceIDChanged$
|
|
31
|
-
.pipe(switchMap((spaceID) => {
|
|
32
|
-
if (spaceID && this.draftSplitStore.hasPending()) {
|
|
33
|
-
// A bill was started before sign-in (see StartSplitPageComponent)
|
|
34
|
-
// — skip the splits list and go straight to finishing it.
|
|
35
|
-
this.spaceNav.navigateForwardToSpacePage(this.space, 'new-split');
|
|
36
|
-
return EMPTY;
|
|
37
|
-
}
|
|
38
|
-
this.$loading.set(true);
|
|
39
|
-
this.$error.set(undefined);
|
|
40
|
-
return this.splitusService.getSplits(spaceID ?? '');
|
|
41
|
-
}), takeUntilDestroyed())
|
|
42
|
-
.subscribe({
|
|
43
|
-
next: (splits) => {
|
|
44
|
-
this.$splits.set(splits);
|
|
45
|
-
this.$loading.set(false);
|
|
46
|
-
},
|
|
47
|
-
error: (err) => {
|
|
48
|
-
this.$error.set('Failed to load splits');
|
|
49
|
-
this.$loading.set(false);
|
|
50
|
-
this.errorLogger.logError(err, 'Failed to load splits');
|
|
51
|
-
},
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
goNewSplit() {
|
|
55
|
-
this.spaceNav.navigateForwardToSpacePage(this.space, 'new-split');
|
|
56
|
-
}
|
|
57
|
-
openSplit(splitID) {
|
|
58
|
-
this.spaceNav.navigateForwardToSpacePage(this.space, `split/${splitID}`);
|
|
59
|
-
}
|
|
60
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SplitusHomePageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
61
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: SplitusHomePageComponent, isStandalone: true, selector: "sneat-splitus-home-page", providers: [
|
|
62
|
-
{
|
|
63
|
-
provide: ClassName,
|
|
64
|
-
useValue: 'SplitusHomePageComponent',
|
|
65
|
-
},
|
|
66
|
-
SpaceComponentBaseParams,
|
|
67
|
-
], usesInheritance: true, ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"light\" class=\"with-back-button with-end-buttons\">\n <ion-buttons slot=\"start\">\n <!-- This is the top page of a space, so \"back\" should leave the space and\n return to the app home (the spaces list), not to the space's own root\n (which redirects straight back here). -->\n <ion-back-button defaultHref=\"/\" />\n </ion-buttons>\n <sneat-space-page-title\n [space]=\"space\"\n generalTitle=\"Splits\"\n icon=\"\uD83D\uDCB8\"\n [titlesBySpaceType]=\"{\n personal: 'Personal splits',\n family: 'Family splits',\n }\"\n />\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n <ion-card>\n <ion-card-content>\n <ion-button expand=\"block\" (click)=\"goNewSplit()\">\n <ion-icon name=\"add\" slot=\"start\" />\n New split\n </ion-button>\n </ion-card-content>\n </ion-card>\n\n <ion-card>\n @if ($loading()) {\n <ion-card-content>\n <ion-spinner name=\"dots\" />\n </ion-card-content>\n } @else if ($error()) {\n <ion-card-content>\n <ion-note color=\"danger\">{{ $error() }}</ion-note>\n </ion-card-content>\n } @else if ($splits().length) {\n <ion-list>\n @for (s of $splits(); track s.id) {\n <ion-item button (click)=\"openSplit(s.id)\">\n <ion-label>\n <h2>{{ s.title || 'Split' }}</h2>\n <p>\n {{ s.amount.toFixed(2) }} {{ s.currency }} \u00B7\n {{ formatMemberCount(s.membersCount) }}\n </p>\n </ion-label>\n <ion-badge slot=\"end\" [color]=\"statusColor(s.status)\">{{\n s.status\n }}</ion-badge>\n </ion-item>\n }\n </ion-list>\n } @else {\n <ion-card-content class=\"ion-text-center\">\n <ion-note color=\"medium\">No splits yet.</ion-note>\n <div class=\"ion-margin-top\">\n <ion-button (click)=\"goNewSplit()\">\n <ion-icon name=\"add\" slot=\"start\" />\n Create your first split\n </ion-button>\n </div>\n </ion-card-content>\n }\n </ion-card>\n</ion-content>\n", dependencies: [{ kind: "component", type: SpacePageTitleComponent, selector: "sneat-space-page-title", inputs: ["icon", "generalTitle", "space", "titlesBySpaceType"] }, { kind: "ngmodule", type: SpaceServiceModule }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonBackButton, selector: "ion-back-button" }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"], outputs: ["ionScrollStart", "ionScroll", "ionScrollEnd"] }, { 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: 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: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "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: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: IonBadge, selector: "ion-badge", inputs: ["color", "mode"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }] });
|
|
68
|
-
}
|
|
69
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SplitusHomePageComponent, decorators: [{
|
|
70
|
-
type: Component,
|
|
71
|
-
args: [{ selector: 'sneat-splitus-home-page', imports: [
|
|
72
|
-
SpacePageTitleComponent,
|
|
73
|
-
SpaceServiceModule,
|
|
74
|
-
IonHeader,
|
|
75
|
-
IonToolbar,
|
|
76
|
-
IonButtons,
|
|
77
|
-
IonBackButton,
|
|
78
|
-
IonContent,
|
|
79
|
-
IonCard,
|
|
80
|
-
IonCardContent,
|
|
81
|
-
IonButton,
|
|
82
|
-
IonIcon,
|
|
83
|
-
IonList,
|
|
84
|
-
IonItem,
|
|
85
|
-
IonLabel,
|
|
86
|
-
IonNote,
|
|
87
|
-
IonBadge,
|
|
88
|
-
IonSpinner,
|
|
89
|
-
], providers: [
|
|
90
|
-
{
|
|
91
|
-
provide: ClassName,
|
|
92
|
-
useValue: 'SplitusHomePageComponent',
|
|
93
|
-
},
|
|
94
|
-
SpaceComponentBaseParams,
|
|
95
|
-
], template: "<ion-header>\n <ion-toolbar color=\"light\" class=\"with-back-button with-end-buttons\">\n <ion-buttons slot=\"start\">\n <!-- This is the top page of a space, so \"back\" should leave the space and\n return to the app home (the spaces list), not to the space's own root\n (which redirects straight back here). -->\n <ion-back-button defaultHref=\"/\" />\n </ion-buttons>\n <sneat-space-page-title\n [space]=\"space\"\n generalTitle=\"Splits\"\n icon=\"\uD83D\uDCB8\"\n [titlesBySpaceType]=\"{\n personal: 'Personal splits',\n family: 'Family splits',\n }\"\n />\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n <ion-card>\n <ion-card-content>\n <ion-button expand=\"block\" (click)=\"goNewSplit()\">\n <ion-icon name=\"add\" slot=\"start\" />\n New split\n </ion-button>\n </ion-card-content>\n </ion-card>\n\n <ion-card>\n @if ($loading()) {\n <ion-card-content>\n <ion-spinner name=\"dots\" />\n </ion-card-content>\n } @else if ($error()) {\n <ion-card-content>\n <ion-note color=\"danger\">{{ $error() }}</ion-note>\n </ion-card-content>\n } @else if ($splits().length) {\n <ion-list>\n @for (s of $splits(); track s.id) {\n <ion-item button (click)=\"openSplit(s.id)\">\n <ion-label>\n <h2>{{ s.title || 'Split' }}</h2>\n <p>\n {{ s.amount.toFixed(2) }} {{ s.currency }} \u00B7\n {{ formatMemberCount(s.membersCount) }}\n </p>\n </ion-label>\n <ion-badge slot=\"end\" [color]=\"statusColor(s.status)\">{{\n s.status\n }}</ion-badge>\n </ion-item>\n }\n </ion-list>\n } @else {\n <ion-card-content class=\"ion-text-center\">\n <ion-note color=\"medium\">No splits yet.</ion-note>\n <div class=\"ion-margin-top\">\n <ion-button (click)=\"goNewSplit()\">\n <ion-icon name=\"add\" slot=\"start\" />\n Create your first split\n </ion-button>\n </div>\n </ion-card-content>\n }\n </ion-card>\n</ion-content>\n" }]
|
|
96
|
-
}], ctorParameters: () => [] });
|
|
97
|
-
|
|
98
|
-
export { SplitusHomePageComponent };
|
|
99
|
-
//# sourceMappingURL=sneat-extension-splitus-ui-splitus-home-page.component-Dp5oLR86.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sneat-extension-splitus-ui-splitus-home-page.component-Dp5oLR86.mjs","sources":["../../../../../../libs/extensions/splitus/ui/src/lib/pages/splitus-home/splitus-home-page.component.ts","../../../../../../libs/extensions/splitus/ui/src/lib/pages/splitus-home/splitus-home-page.component.html"],"sourcesContent":["import { Component, inject, signal } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport {\n IonBackButton,\n IonBadge,\n IonButton,\n IonButtons,\n IonCard,\n IonCardContent,\n IonContent,\n IonHeader,\n IonIcon,\n IonItem,\n IonLabel,\n IonList,\n IonNote,\n IonSpinner,\n IonToolbar,\n} from '@ionic/angular';\nimport { ISplitListItem, SPLITUS_SERVICE } from '@sneat/extension-splitus-contract';\nimport {\n SpaceComponentBaseParams,\n SpacePageTitleComponent,\n SpacePageBaseComponent,\n} from '@sneat/space-components';\nimport { SpaceServiceModule } from '@sneat/space-services';\nimport { ClassName } from '@sneat/ui';\nimport { EMPTY, switchMap } from 'rxjs';\nimport { DraftSplitStore } from '../../services/draft-split-store.service';\nimport { formatMemberCount, statusColor } from '../split-status-view';\n\n// Splits home — the splitus entry point for a space: the space's splits\n// (read via getSplits) plus the \"New split\" action. Entry: space navigation /\n// the splitus-space default redirect. Exit: \"New split\" -> new-split; each\n// row -> split/:splitID.\n@Component({\n selector: 'sneat-splitus-home-page',\n templateUrl: './splitus-home-page.component.html',\n imports: [\n SpacePageTitleComponent,\n SpaceServiceModule,\n IonHeader,\n IonToolbar,\n IonButtons,\n IonBackButton,\n IonContent,\n IonCard,\n IonCardContent,\n IonButton,\n IonIcon,\n IonList,\n IonItem,\n IonLabel,\n IonNote,\n IonBadge,\n IonSpinner,\n ],\n providers: [\n {\n provide: ClassName,\n useValue: 'SplitusHomePageComponent',\n },\n SpaceComponentBaseParams,\n ],\n})\nexport class SplitusHomePageComponent extends SpacePageBaseComponent {\n private readonly splitusService = inject(SPLITUS_SERVICE);\n private readonly draftSplitStore = inject(DraftSplitStore);\n\n protected readonly $loading = signal(true);\n protected readonly $error = signal<string | undefined>(undefined);\n protected readonly $splits = signal<ISplitListItem[]>([]);\n\n protected readonly statusColor = statusColor;\n protected readonly formatMemberCount = formatMemberCount;\n\n constructor() {\n super();\n this.spaceIDChanged$\n .pipe(\n switchMap((spaceID) => {\n if (spaceID && this.draftSplitStore.hasPending()) {\n // A bill was started before sign-in (see StartSplitPageComponent)\n // — skip the splits list and go straight to finishing it.\n this.spaceNav.navigateForwardToSpacePage(this.space, 'new-split');\n return EMPTY;\n }\n this.$loading.set(true);\n this.$error.set(undefined);\n return this.splitusService.getSplits(spaceID ?? '');\n }),\n takeUntilDestroyed(),\n )\n .subscribe({\n next: (splits) => {\n this.$splits.set(splits);\n this.$loading.set(false);\n },\n error: (err) => {\n this.$error.set('Failed to load splits');\n this.$loading.set(false);\n this.errorLogger.logError(err, 'Failed to load splits');\n },\n });\n }\n\n protected goNewSplit(): void {\n this.spaceNav.navigateForwardToSpacePage(this.space, 'new-split');\n }\n\n protected openSplit(splitID: string): void {\n this.spaceNav.navigateForwardToSpacePage(this.space, `split/${splitID}`);\n }\n}\n","<ion-header>\n <ion-toolbar color=\"light\" class=\"with-back-button with-end-buttons\">\n <ion-buttons slot=\"start\">\n <!-- This is the top page of a space, so \"back\" should leave the space and\n return to the app home (the spaces list), not to the space's own root\n (which redirects straight back here). -->\n <ion-back-button defaultHref=\"/\" />\n </ion-buttons>\n <sneat-space-page-title\n [space]=\"space\"\n generalTitle=\"Splits\"\n icon=\"💸\"\n [titlesBySpaceType]=\"{\n personal: 'Personal splits',\n family: 'Family splits',\n }\"\n />\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n <ion-card>\n <ion-card-content>\n <ion-button expand=\"block\" (click)=\"goNewSplit()\">\n <ion-icon name=\"add\" slot=\"start\" />\n New split\n </ion-button>\n </ion-card-content>\n </ion-card>\n\n <ion-card>\n @if ($loading()) {\n <ion-card-content>\n <ion-spinner name=\"dots\" />\n </ion-card-content>\n } @else if ($error()) {\n <ion-card-content>\n <ion-note color=\"danger\">{{ $error() }}</ion-note>\n </ion-card-content>\n } @else if ($splits().length) {\n <ion-list>\n @for (s of $splits(); track s.id) {\n <ion-item button (click)=\"openSplit(s.id)\">\n <ion-label>\n <h2>{{ s.title || 'Split' }}</h2>\n <p>\n {{ s.amount.toFixed(2) }} {{ s.currency }} ·\n {{ formatMemberCount(s.membersCount) }}\n </p>\n </ion-label>\n <ion-badge slot=\"end\" [color]=\"statusColor(s.status)\">{{\n s.status\n }}</ion-badge>\n </ion-item>\n }\n </ion-list>\n } @else {\n <ion-card-content class=\"ion-text-center\">\n <ion-note color=\"medium\">No splits yet.</ion-note>\n <div class=\"ion-margin-top\">\n <ion-button (click)=\"goNewSplit()\">\n <ion-icon name=\"add\" slot=\"start\" />\n Create your first split\n </ion-button>\n </div>\n </ion-card-content>\n }\n </ion-card>\n</ion-content>\n"],"names":[],"mappings":"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"}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, Component } from '@angular/core';
|
|
3
|
-
import * as i1 from '@angular/forms';
|
|
4
|
-
import { FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
|
|
5
|
-
import { Router } from '@angular/router';
|
|
6
|
-
import { IonHeader, IonToolbar, IonTitle, IonContent, IonCard, IonCardHeader, IonCardTitle, IonItem, IonInput, IonSelect, IonSelectOption, IonNote, IonButton } from '@ionic/angular';
|
|
7
|
-
import { DraftSplitStore } from './sneat-extension-splitus-ui.mjs';
|
|
8
|
-
|
|
9
|
-
// Unguarded entry point: capture the bill basics (title/amount/currency)
|
|
10
|
-
// without requiring sign-in or a space. Saves a local draft and sends the
|
|
11
|
-
// user to '' (home) — the existing AuthGuard/space-list flow takes it from
|
|
12
|
-
// there: signed out -> /login; signed in with zero spaces -> the home page's
|
|
13
|
-
// own "create a team" step; signed in with a space -> that space's splits
|
|
14
|
-
// page, which redirects into new-split when a pending draft exists (see
|
|
15
|
-
// SplitusHomePageComponent). See
|
|
16
|
-
// docs/superpowers/specs/2026-07-03-anonymous-first-split-design.md.
|
|
17
|
-
class StartSplitPageComponent {
|
|
18
|
-
draftSplitStore = inject(DraftSplitStore);
|
|
19
|
-
router = inject(Router);
|
|
20
|
-
title = new FormControl('', {
|
|
21
|
-
nonNullable: true,
|
|
22
|
-
});
|
|
23
|
-
currency = new FormControl('EUR', {
|
|
24
|
-
nonNullable: true,
|
|
25
|
-
});
|
|
26
|
-
amount = new FormControl(null, [
|
|
27
|
-
Validators.required,
|
|
28
|
-
Validators.min(0.01),
|
|
29
|
-
]);
|
|
30
|
-
currencies = ['EUR', 'USD'];
|
|
31
|
-
onContinue() {
|
|
32
|
-
this.amount.markAsTouched();
|
|
33
|
-
if (this.amount.invalid) {
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
this.draftSplitStore.save({
|
|
37
|
-
title: this.title.value.trim(),
|
|
38
|
-
amount: this.amount.value ?? 0,
|
|
39
|
-
currencyCode: this.currency.value,
|
|
40
|
-
});
|
|
41
|
-
this.router.navigateByUrl('/');
|
|
42
|
-
}
|
|
43
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: StartSplitPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
44
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: StartSplitPageComponent, isStandalone: true, selector: "sneat-splitus-start-split-page", ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"light\">\n <ion-title>Split a bill</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>What's the bill?</ion-card-title>\n </ion-card-header>\n <ion-item>\n <ion-input\n label=\"Title (optional)\"\n labelPlacement=\"stacked\"\n [formControl]=\"title\"\n placeholder=\"e.g. Dinner at Luigi's\"\n autocapitalize=\"sentences\"\n />\n </ion-item>\n <ion-item>\n <ion-select\n label=\"Currency\"\n interface=\"popover\"\n [formControl]=\"currency\"\n >\n @for (c of currencies; track c) {\n <ion-select-option [value]=\"c\">{{ c }}</ion-select-option>\n }\n </ion-select>\n </ion-item>\n <ion-item>\n <ion-input\n label=\"Amount\"\n labelPlacement=\"stacked\"\n type=\"number\"\n inputmode=\"decimal\"\n [formControl]=\"amount\"\n placeholder=\"0.00\"\n />\n </ion-item>\n @if (amount.touched && amount.invalid) {\n <ion-note color=\"danger\" class=\"ion-padding-start\">\n Enter an amount greater than zero.\n </ion-note>\n }\n </ion-card>\n\n <div class=\"ion-padding\">\n <ion-button expand=\"block\" color=\"primary\" (click)=\"onContinue()\">\n Continue\n </ion-button>\n <ion-note\n color=\"medium\"\n class=\"ion-text-center\"\n style=\"display: block; margin-top: 8px;\"\n >\n You'll sign in next to add who's splitting this.\n </ion-note>\n </div>\n</ion-content>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"], outputs: ["ionScrollStart", "ionScroll", "ionScrollEnd"] }, { kind: "component", type: IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { 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: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "value"] }, { 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: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { 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"] }] });
|
|
45
|
-
}
|
|
46
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: StartSplitPageComponent, decorators: [{
|
|
47
|
-
type: Component,
|
|
48
|
-
args: [{ selector: 'sneat-splitus-start-split-page', imports: [
|
|
49
|
-
ReactiveFormsModule,
|
|
50
|
-
IonHeader,
|
|
51
|
-
IonToolbar,
|
|
52
|
-
IonTitle,
|
|
53
|
-
IonContent,
|
|
54
|
-
IonCard,
|
|
55
|
-
IonCardHeader,
|
|
56
|
-
IonCardTitle,
|
|
57
|
-
IonItem,
|
|
58
|
-
IonInput,
|
|
59
|
-
IonSelect,
|
|
60
|
-
IonSelectOption,
|
|
61
|
-
IonNote,
|
|
62
|
-
IonButton,
|
|
63
|
-
], template: "<ion-header>\n <ion-toolbar color=\"light\">\n <ion-title>Split a bill</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>What's the bill?</ion-card-title>\n </ion-card-header>\n <ion-item>\n <ion-input\n label=\"Title (optional)\"\n labelPlacement=\"stacked\"\n [formControl]=\"title\"\n placeholder=\"e.g. Dinner at Luigi's\"\n autocapitalize=\"sentences\"\n />\n </ion-item>\n <ion-item>\n <ion-select\n label=\"Currency\"\n interface=\"popover\"\n [formControl]=\"currency\"\n >\n @for (c of currencies; track c) {\n <ion-select-option [value]=\"c\">{{ c }}</ion-select-option>\n }\n </ion-select>\n </ion-item>\n <ion-item>\n <ion-input\n label=\"Amount\"\n labelPlacement=\"stacked\"\n type=\"number\"\n inputmode=\"decimal\"\n [formControl]=\"amount\"\n placeholder=\"0.00\"\n />\n </ion-item>\n @if (amount.touched && amount.invalid) {\n <ion-note color=\"danger\" class=\"ion-padding-start\">\n Enter an amount greater than zero.\n </ion-note>\n }\n </ion-card>\n\n <div class=\"ion-padding\">\n <ion-button expand=\"block\" color=\"primary\" (click)=\"onContinue()\">\n Continue\n </ion-button>\n <ion-note\n color=\"medium\"\n class=\"ion-text-center\"\n style=\"display: block; margin-top: 8px;\"\n >\n You'll sign in next to add who's splitting this.\n </ion-note>\n </div>\n</ion-content>\n" }]
|
|
64
|
-
}] });
|
|
65
|
-
|
|
66
|
-
export { StartSplitPageComponent };
|
|
67
|
-
//# sourceMappingURL=sneat-extension-splitus-ui-start-split-page.component-BHsYZbKT.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sneat-extension-splitus-ui-start-split-page.component-BHsYZbKT.mjs","sources":["../../../../../../libs/extensions/splitus/ui/src/lib/pages/start-split/start-split-page.component.ts","../../../../../../libs/extensions/splitus/ui/src/lib/pages/start-split/start-split-page.component.html"],"sourcesContent":["import { Component, inject } from '@angular/core';\nimport { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport {\n IonButton,\n IonCard,\n IonCardHeader,\n IonCardTitle,\n IonContent,\n IonHeader,\n IonInput,\n IonItem,\n IonNote,\n IonSelect,\n IonSelectOption,\n IonTitle,\n IonToolbar,\n} from '@ionic/angular';\nimport { CurrencyCode } from '@sneat/extension-splitus-contract';\nimport { DraftSplitStore } from '../../services/draft-split-store.service';\n\n// Unguarded entry point: capture the bill basics (title/amount/currency)\n// without requiring sign-in or a space. Saves a local draft and sends the\n// user to '' (home) — the existing AuthGuard/space-list flow takes it from\n// there: signed out -> /login; signed in with zero spaces -> the home page's\n// own \"create a team\" step; signed in with a space -> that space's splits\n// page, which redirects into new-split when a pending draft exists (see\n// SplitusHomePageComponent). See\n// docs/superpowers/specs/2026-07-03-anonymous-first-split-design.md.\n@Component({\n selector: 'sneat-splitus-start-split-page',\n templateUrl: './start-split-page.component.html',\n imports: [\n ReactiveFormsModule,\n IonHeader,\n IonToolbar,\n IonTitle,\n IonContent,\n IonCard,\n IonCardHeader,\n IonCardTitle,\n IonItem,\n IonInput,\n IonSelect,\n IonSelectOption,\n IonNote,\n IonButton,\n ],\n})\nexport class StartSplitPageComponent {\n private readonly draftSplitStore = inject(DraftSplitStore);\n private readonly router = inject(Router);\n\n protected readonly title = new FormControl<string>('', {\n nonNullable: true,\n });\n protected readonly currency = new FormControl<CurrencyCode>('EUR', {\n nonNullable: true,\n });\n protected readonly amount = new FormControl<number | null>(null, [\n Validators.required,\n Validators.min(0.01),\n ]);\n\n protected readonly currencies: CurrencyCode[] = ['EUR', 'USD'];\n\n protected onContinue(): void {\n this.amount.markAsTouched();\n if (this.amount.invalid) {\n return;\n }\n this.draftSplitStore.save({\n title: this.title.value.trim(),\n amount: this.amount.value ?? 0,\n currencyCode: this.currency.value,\n });\n this.router.navigateByUrl('/');\n }\n}\n","<ion-header>\n <ion-toolbar color=\"light\">\n <ion-title>Split a bill</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>What's the bill?</ion-card-title>\n </ion-card-header>\n <ion-item>\n <ion-input\n label=\"Title (optional)\"\n labelPlacement=\"stacked\"\n [formControl]=\"title\"\n placeholder=\"e.g. Dinner at Luigi's\"\n autocapitalize=\"sentences\"\n />\n </ion-item>\n <ion-item>\n <ion-select\n label=\"Currency\"\n interface=\"popover\"\n [formControl]=\"currency\"\n >\n @for (c of currencies; track c) {\n <ion-select-option [value]=\"c\">{{ c }}</ion-select-option>\n }\n </ion-select>\n </ion-item>\n <ion-item>\n <ion-input\n label=\"Amount\"\n labelPlacement=\"stacked\"\n type=\"number\"\n inputmode=\"decimal\"\n [formControl]=\"amount\"\n placeholder=\"0.00\"\n />\n </ion-item>\n @if (amount.touched && amount.invalid) {\n <ion-note color=\"danger\" class=\"ion-padding-start\">\n Enter an amount greater than zero.\n </ion-note>\n }\n </ion-card>\n\n <div class=\"ion-padding\">\n <ion-button expand=\"block\" color=\"primary\" (click)=\"onContinue()\">\n Continue\n </ion-button>\n <ion-note\n color=\"medium\"\n class=\"ion-text-center\"\n style=\"display: block; margin-top: 8px;\"\n >\n You'll sign in next to add who's splitting this.\n </ion-note>\n </div>\n</ion-content>\n"],"names":[],"mappings":"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"}
|