@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.
Files changed (25) hide show
  1. 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
  2. package/fesm2022/sneat-extension-splitus-ui-new-split-page.component-DqIE_0pp.mjs.map +1 -0
  3. package/fesm2022/sneat-extension-splitus-ui-sneat-extension-splitus-ui-CX1eiAAR.mjs +558 -0
  4. package/fesm2022/sneat-extension-splitus-ui-sneat-extension-splitus-ui-CX1eiAAR.mjs.map +1 -0
  5. package/fesm2022/sneat-extension-splitus-ui-split-details-page.component-fkoVkmXD.mjs +105 -0
  6. package/fesm2022/sneat-extension-splitus-ui-split-details-page.component-fkoVkmXD.mjs.map +1 -0
  7. package/fesm2022/sneat-extension-splitus-ui-split-status-view-CRt2FKg1.mjs +7 -0
  8. package/fesm2022/sneat-extension-splitus-ui-split-status-view-CRt2FKg1.mjs.map +1 -0
  9. package/fesm2022/sneat-extension-splitus-ui-splitus-home-page.component-tUSn27LG.mjs +100 -0
  10. package/fesm2022/sneat-extension-splitus-ui-splitus-home-page.component-tUSn27LG.mjs.map +1 -0
  11. package/fesm2022/sneat-extension-splitus-ui-start-split-page.component-Br8thaKs.mjs +74 -0
  12. package/fesm2022/sneat-extension-splitus-ui-start-split-page.component-Br8thaKs.mjs.map +1 -0
  13. package/fesm2022/sneat-extension-splitus-ui.mjs +1 -468
  14. package/fesm2022/sneat-extension-splitus-ui.mjs.map +1 -1
  15. package/package.json +4 -3
  16. package/types/sneat-extension-splitus-ui.d.ts +45 -30
  17. package/fesm2022/sneat-extension-splitus-ui-new-split-page.component-DkP-7W5H.mjs.map +0 -1
  18. package/fesm2022/sneat-extension-splitus-ui-split-details-page.component-kzwMFwkF.mjs +0 -87
  19. package/fesm2022/sneat-extension-splitus-ui-split-details-page.component-kzwMFwkF.mjs.map +0 -1
  20. package/fesm2022/sneat-extension-splitus-ui-split-status-view-Dk3SLPhh.mjs +0 -44
  21. package/fesm2022/sneat-extension-splitus-ui-split-status-view-Dk3SLPhh.mjs.map +0 -1
  22. package/fesm2022/sneat-extension-splitus-ui-splitus-home-page.component-Dp5oLR86.mjs +0 -99
  23. package/fesm2022/sneat-extension-splitus-ui-splitus-home-page.component-Dp5oLR86.mjs.map +0 -1
  24. package/fesm2022/sneat-extension-splitus-ui-start-split-page.component-BHsYZbKT.mjs +0 -67
  25. package/fesm2022/sneat-extension-splitus-ui-start-split-page.component-BHsYZbKT.mjs.map +0 -1
@@ -1,469 +1,2 @@
1
- import * as i0 from '@angular/core';
2
- import { Injectable, inject, DestroyRef, signal, computed, Input, Component } from '@angular/core';
3
- import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
4
- import * as i1 from '@angular/forms';
5
- import { FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
6
- import { IonCard, IonCardHeader, IonCardTitle, IonCardContent, IonItem, IonLabel, IonNote, IonSelect, IonSelectOption, IonInput, IonCheckbox, IonSegment, IonSegmentButton, IonButton, IonSpinner } from '@ionic/angular';
7
- import { ErrorLogger } from '@sneat/core';
8
- import { CONTACTUS_SPACE_SERVICE } from '@sneat/extension-contactus-contract';
9
- import { SPLITUS_SERVICE } from '@sneat/extension-splitus-contract';
10
- import { SpaceNavService } from '@sneat/space-services';
11
-
12
- const DRAFT_SPLIT_STORAGE_KEY = 'sneat.splitus.draftSplit';
13
- const MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24h
14
- function isDraftSplit(value) {
15
- if (typeof value !== 'object' || value === null) {
16
- return false;
17
- }
18
- const v = value;
19
- return (typeof v['title'] === 'string' &&
20
- typeof v['amount'] === 'number' &&
21
- typeof v['currencyCode'] === 'string' &&
22
- typeof v['createdAt'] === 'number');
23
- }
24
- class DraftSplitStore {
25
- save(draft) {
26
- const full = { ...draft, createdAt: Date.now() };
27
- try {
28
- localStorage.setItem(DRAFT_SPLIT_STORAGE_KEY, JSON.stringify(full));
29
- }
30
- catch {
31
- // Storage unavailable (private mode, quota) — fail open, never block
32
- // navigation over a lost draft.
33
- }
34
- }
35
- read() {
36
- let raw;
37
- try {
38
- raw = localStorage.getItem(DRAFT_SPLIT_STORAGE_KEY);
39
- }
40
- catch {
41
- return undefined;
42
- }
43
- if (!raw) {
44
- return undefined;
45
- }
46
- let parsed;
47
- try {
48
- parsed = JSON.parse(raw);
49
- }
50
- catch {
51
- return undefined;
52
- }
53
- if (!isDraftSplit(parsed) || Date.now() - parsed.createdAt > MAX_AGE_MS) {
54
- return undefined;
55
- }
56
- return parsed;
57
- }
58
- hasPending() {
59
- return this.read() !== undefined;
60
- }
61
- clear() {
62
- try {
63
- localStorage.removeItem(DRAFT_SPLIT_STORAGE_KEY);
64
- }
65
- catch {
66
- // ignore
67
- }
68
- }
69
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: DraftSplitStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
70
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: DraftSplitStore, providedIn: 'root' });
71
- }
72
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: DraftSplitStore, decorators: [{
73
- type: Injectable,
74
- args: [{ providedIn: 'root' }]
75
- }] });
76
-
77
- /**
78
- * Parses a decimal user input ("33.34", 50) into integer hundredths (3334).
79
- * Returns undefined for blank, non-numeric or negative values.
80
- */
81
- function parseDecimal(raw) {
82
- if (raw === null || raw === undefined) {
83
- return undefined;
84
- }
85
- const s = String(raw).trim();
86
- if (!s) {
87
- return undefined;
88
- }
89
- const n = Number(s);
90
- if (!Number.isFinite(n) || n < 0) {
91
- return undefined;
92
- }
93
- return Math.round(n * 100);
94
- }
95
- /** Formats integer hundredths as a 2-decimal string: 3334 → "33.34". */
96
- function formatHundredths(v) {
97
- return (v / 100).toFixed(2);
98
- }
99
- /** Formats basis points as a trimmed percent number: 3334 → "33.34", 5000 → "50". */
100
- function formatPercentValue(v) {
101
- return String(v / 100);
102
- }
103
- /**
104
- * Live reconciliation of custom shares against the expense total.
105
- *
106
- * - `equally`: always ok — there are no share inputs; the backend splits.
107
- * - `exact-amount`: every share must be entered and sum exactly to the total.
108
- * - `percentage`: every percentage must be entered and sum exactly to 100%.
109
- *
110
- * @param totalHundredths the expense total in hundredths (cents), if entered.
111
- * @param shares raw share inputs, one per participant (payer included).
112
- */
113
- function reconcileShares(mode, totalHundredths, shares) {
114
- if (mode === 'equally') {
115
- return { ok: true };
116
- }
117
- const parsed = shares.map(parseDecimal);
118
- if (parsed.some((v) => v === undefined)) {
119
- return {
120
- ok: false,
121
- error: mode === 'percentage'
122
- ? 'Enter a percentage for every participant.'
123
- : 'Enter a share for every participant.',
124
- };
125
- }
126
- const entered = parsed.reduce((sum, v) => sum + v, 0);
127
- if (mode === 'percentage') {
128
- if (entered === 100_00) {
129
- return { ok: true };
130
- }
131
- return {
132
- ok: false,
133
- error: `Percentages sum to ${formatPercentValue(entered)}% — they must sum to exactly 100%.`,
134
- };
135
- }
136
- // exact-amount
137
- if (totalHundredths === undefined) {
138
- return { ok: false, error: 'Enter the expense total first.' };
139
- }
140
- if (entered === totalHundredths) {
141
- return { ok: true };
142
- }
143
- const delta = entered - totalHundredths;
144
- const direction = delta < 0
145
- ? `${formatHundredths(-delta)} short of`
146
- : `${formatHundredths(delta)} over`;
147
- return {
148
- ok: false,
149
- error: `Shares sum to ${formatHundredths(entered)} — ${direction} the ${formatHundredths(totalHundredths)} total.`,
150
- };
151
- }
152
-
153
- /** Pulls a human message out of an HTTP error (Go handlers reply plain text). */
154
- function extractServerMessage(err) {
155
- if (typeof err !== 'object' || !err) {
156
- return typeof err === 'string' && err.trim() ? err.trim() : undefined;
157
- }
158
- const e = err;
159
- if (typeof e.error === 'string' && e.error.trim()) {
160
- return e.error.trim();
161
- }
162
- if (typeof e.error === 'object' && e.error) {
163
- const m = e.error.message;
164
- if (typeof m === 'string' && m.trim()) {
165
- return m.trim();
166
- }
167
- }
168
- return typeof e.message === 'string' && e.message.trim()
169
- ? e.message.trim()
170
- : undefined;
171
- }
172
- // Create-split form: amount + currency, participants picked from the space's
173
- // existing contactus people (no free-text name entry — AC
174
- // participants-from-contactus-membership-enforced), and an
175
- // equally/exact-amount/percentage share editor with live reconciliation that
176
- // blocks submit until custom shares sum to the total (AC
177
- // custom-shares-must-sum). On success redirects to the new split's details
178
- // page with replaceUrl (flows.md create→details default).
179
- class NewSplitFormComponent {
180
- errorLogger = inject(ErrorLogger);
181
- splitusService = inject(SPLITUS_SERVICE);
182
- contactusSpaceService = inject(CONTACTUS_SPACE_SERVICE);
183
- spaceNavService = inject(SpaceNavService);
184
- destroyRef = inject(DestroyRef);
185
- draftSplitStore = inject(DraftSplitStore);
186
- $resumedFromDraft = signal(false, /* @ts-ignore */
187
- ...(ngDevMode ? [{ debugName: "$resumedFromDraft" }] : /* istanbul ignore next */ []));
188
- draftChecked = false;
189
- $space = signal(undefined, /* @ts-ignore */
190
- ...(ngDevMode ? [{ debugName: "$space" }] : /* istanbul ignore next */ []));
191
- // TODO: Skipped for migration because:
192
- // Accessor inputs cannot be migrated as they are too complex.
193
- set space(value) {
194
- this.$space.set(value);
195
- this.watchContacts(value?.id);
196
- this.tryResumeDraft();
197
- }
198
- tryResumeDraft() {
199
- if (this.draftChecked) {
200
- return;
201
- }
202
- this.draftChecked = true;
203
- const draft = this.draftSplitStore.read();
204
- if (!draft) {
205
- return;
206
- }
207
- this.title.setValue(draft.title);
208
- this.currency.setValue(draft.currencyCode);
209
- this.amount.setValue(draft.amount);
210
- this.$resumedFromDraft.set(true);
211
- }
212
- dismissDraftBanner() {
213
- this.$resumedFromDraft.set(false);
214
- this.draftSplitStore.clear();
215
- }
216
- $currentUserID = signal(undefined, /* @ts-ignore */
217
- ...(ngDevMode ? [{ debugName: "$currentUserID" }] : /* istanbul ignore next */ []));
218
- /** The authenticated user's ID — used to exclude the payer's own contact. */
219
- // TODO: Skipped for migration because:
220
- // Accessor inputs cannot be migrated as they are too complex.
221
- set currentUserID(value) {
222
- this.$currentUserID.set(value);
223
- }
224
- // ---- participants (sourced from contactus — never typed in) ----
225
- contactsSubscription;
226
- watchedSpaceID;
227
- $contacts = signal(undefined, /* @ts-ignore */
228
- ...(ngDevMode ? [{ debugName: "$contacts" }] : /* istanbul ignore next */ []));
229
- $contactsError = signal(undefined, /* @ts-ignore */
230
- ...(ngDevMode ? [{ debugName: "$contactsError" }] : /* istanbul ignore next */ []));
231
- watchContacts(spaceID) {
232
- if (spaceID === this.watchedSpaceID) {
233
- return;
234
- }
235
- this.watchedSpaceID = spaceID;
236
- this.contactsSubscription?.unsubscribe();
237
- this.$contacts.set(undefined);
238
- this.$contactsError.set(undefined);
239
- if (!spaceID) {
240
- return;
241
- }
242
- this.contactsSubscription = this.contactusSpaceService
243
- .watchContactBriefs(spaceID)
244
- .pipe(takeUntilDestroyed(this.destroyRef))
245
- .subscribe({
246
- next: (contacts) => this.$contacts.set(contacts),
247
- error: (err) => {
248
- this.$contactsError.set('Failed to load contacts.');
249
- this.errorLogger.logError(err, 'Failed to load space contacts', {
250
- show: false,
251
- });
252
- },
253
- });
254
- }
255
- /**
256
- * The space's contactus people offered as participants. The payer (the
257
- * authenticated user) is always a participant and must not be listed in
258
- * `participantContactIDs`, so their own contact is excluded from the picker.
259
- */
260
- $participantChoices = computed(() => {
261
- const userID = this.$currentUserID();
262
- return this.$contacts()
263
- ?.filter((c) => !userID || c.brief?.userID !== userID)
264
- .map((c) => ({
265
- id: c.id,
266
- title: c.brief?.title || c.brief?.shortTitle || c.id,
267
- }));
268
- }, /* @ts-ignore */
269
- ...(ngDevMode ? [{ debugName: "$participantChoices" }] : /* istanbul ignore next */ []));
270
- $selectedContactIDs = signal([], /* @ts-ignore */
271
- ...(ngDevMode ? [{ debugName: "$selectedContactIDs" }] : /* istanbul ignore next */ []));
272
- onParticipantToggle(contactID, checked) {
273
- this.$selectedContactIDs.update((ids) => {
274
- const without = ids.filter((id) => id !== contactID);
275
- return checked ? [...without, contactID] : without;
276
- });
277
- }
278
- onParticipantChecked(contactID, event) {
279
- const checked = !!event.detail
280
- ?.checked;
281
- this.onParticipantToggle(contactID, checked);
282
- }
283
- // ---- expense fields ----
284
- title = new FormControl('', { nonNullable: true });
285
- // TODO: pre-fill from a per-space default currency once spaces carry one;
286
- // EUR is the app-wide default (same as debtus's new-transfer form).
287
- currency = new FormControl('EUR', {
288
- nonNullable: true,
289
- });
290
- amount = new FormControl(null, [
291
- Validators.required,
292
- Validators.min(0.01),
293
- ]);
294
- $amount = toSignal(this.amount.valueChanges, {
295
- initialValue: this.amount.value,
296
- });
297
- currencies = ['EUR', 'USD'];
298
- // ---- split mode + custom shares ----
299
- $splitMode = signal('equally', /* @ts-ignore */
300
- ...(ngDevMode ? [{ debugName: "$splitMode" }] : /* istanbul ignore next */ []));
301
- setSplitMode(mode) {
302
- this.$splitMode.set(mode);
303
- }
304
- onSplitModeChange(event) {
305
- const value = event.detail?.value;
306
- if (value === 'equally' ||
307
- value === 'exact-amount' ||
308
- value === 'percentage') {
309
- this.setSplitMode(value);
310
- }
311
- }
312
- /** Raw share inputs keyed by contact ID ('' = the payer). */
313
- $shareValues = signal({}, /* @ts-ignore */
314
- ...(ngDevMode ? [{ debugName: "$shareValues" }] : /* istanbul ignore next */ []));
315
- setShareValue(contactID, value) {
316
- this.$shareValues.update((values) => ({ ...values, [contactID]: value }));
317
- }
318
- shareValue(contactID) {
319
- const value = this.$shareValues()[contactID];
320
- return value ?? '';
321
- }
322
- onShareInput(contactID, event) {
323
- const value = event.detail
324
- ?.value;
325
- this.setShareValue(contactID, value ?? '');
326
- }
327
- /** Share-editor rows: the payer first, then each selected participant. */
328
- $shareRows = computed(() => {
329
- const choices = this.$participantChoices() ?? [];
330
- const selected = this.$selectedContactIDs();
331
- return [
332
- { id: '', title: 'You (payer)' },
333
- ...choices.filter((c) => selected.includes(c.id)),
334
- ];
335
- }, /* @ts-ignore */
336
- ...(ngDevMode ? [{ debugName: "$shareRows" }] : /* istanbul ignore next */ []));
337
- /** Live reconciliation of custom shares against the expense total. */
338
- $reconciliation = computed(() => reconcileShares(this.$splitMode(), parseDecimal(this.$amount()), this.$shareRows().map((row) => this.$shareValues()[row.id])), /* @ts-ignore */
339
- ...(ngDevMode ? [{ debugName: "$reconciliation" }] : /* istanbul ignore next */ []));
340
- $canSubmit = computed(() => {
341
- const total = parseDecimal(this.$amount());
342
- return (total !== undefined &&
343
- total > 0 &&
344
- this.$selectedContactIDs().length > 0 &&
345
- this.$reconciliation().ok);
346
- }, /* @ts-ignore */
347
- ...(ngDevMode ? [{ debugName: "$canSubmit" }] : /* istanbul ignore next */ []));
348
- // ---- submit + exit ----
349
- $submitting = signal(false, /* @ts-ignore */
350
- ...(ngDevMode ? [{ debugName: "$submitting" }] : /* istanbul ignore next */ []));
351
- $submitError = signal(undefined, /* @ts-ignore */
352
- ...(ngDevMode ? [{ debugName: "$submitError" }] : /* istanbul ignore next */ []));
353
- submit() {
354
- this.amount.markAsTouched();
355
- if (this.$submitting() || !this.$canSubmit()) {
356
- return;
357
- }
358
- const space = this.$space();
359
- if (!space?.id) {
360
- return;
361
- }
362
- const totalCents = parseDecimal(this.$amount());
363
- if (totalCents === undefined) {
364
- return;
365
- }
366
- const mode = this.$splitMode();
367
- const shares = mode === 'equally'
368
- ? undefined
369
- : this.$shareRows().map((row) => {
370
- const value = parseDecimal(this.$shareValues()[row.id]) ?? 0;
371
- return mode === 'percentage'
372
- ? { contactID: row.id, percent: formatPercentValue(value) }
373
- : { contactID: row.id, amount: formatHundredths(value) };
374
- });
375
- const request = {
376
- spaceID: space.id,
377
- title: this.title.value.trim() || undefined,
378
- currency: this.currency.value,
379
- amount: formatHundredths(totalCents),
380
- splitMode: mode,
381
- participantContactIDs: [...this.$selectedContactIDs()],
382
- shares,
383
- };
384
- this.$submitting.set(true);
385
- this.$submitError.set(undefined);
386
- this.splitusService.createSplit(request).subscribe({
387
- next: (response) => {
388
- this.draftSplitStore.clear();
389
- // flows.md: after a successful create, go to the new entity's details,
390
- // replacing the create page in history so Back skips the stale form.
391
- this.spaceNavService.navigateForwardToSpacePage(space, `split/${response.id}`, { replaceUrl: true });
392
- },
393
- error: (err) => {
394
- this.$submitting.set(false);
395
- const serverMessage = extractServerMessage(err);
396
- this.$submitError.set(serverMessage
397
- ? `Failed to create split: ${serverMessage}`
398
- : 'Failed to create split. Please try again.');
399
- this.errorLogger.logError(err, 'Failed to create split', {
400
- show: false,
401
- });
402
- },
403
- });
404
- }
405
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: NewSplitFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
406
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: NewSplitFormComponent, isStandalone: true, selector: "sneat-splitus-new-split-form", inputs: { space: "space", currentUserID: "currentUserID" }, ngImport: i0, template: "@if ($resumedFromDraft()) {\n <ion-card color=\"light\">\n <ion-card-content\n style=\"display: flex; align-items: center; justify-content: space-between; gap: 8px;\"\n >\n <ion-note color=\"medium\"\n >Picking up your bill from earlier \u2014 edit anything below.</ion-note\n >\n <ion-button fill=\"clear\" size=\"small\" (click)=\"dismissDraftBanner()\"\n >Dismiss</ion-button\n >\n </ion-card-content>\n </ion-card>\n}\n\n<ion-card>\n <ion-card-header>\n <ion-card-title>Expense</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<ion-card>\n <ion-card-header>\n <ion-card-title>Participants</ion-card-title>\n </ion-card-header>\n <!-- The payer (you) is always a participant. -->\n <ion-item>\n <ion-label>You (payer)</ion-label>\n <ion-note slot=\"end\" color=\"medium\">always included</ion-note>\n </ion-item>\n <!-- Participants come from the space's existing contactus people \u2014\n there is deliberately no free-text name entry here. -->\n @for (contact of $participantChoices() ?? []; track contact.id) {\n <ion-item>\n <ion-checkbox\n labelPlacement=\"end\"\n justify=\"start\"\n [checked]=\"$selectedContactIDs().includes(contact.id)\"\n (ionChange)=\"onParticipantChecked(contact.id, $event)\"\n >\n {{ contact.title }}\n </ion-checkbox>\n </ion-item>\n } @empty {\n <ion-item lines=\"none\">\n @if ($contactsError()) {\n <ion-label color=\"danger\">{{ $contactsError() }}</ion-label>\n } @else if ($contacts()) {\n <ion-label color=\"medium\" class=\"ion-text-wrap\">\n No contacts yet. Add people to this space first \u2014 splits are shared\n with the space's existing contacts.\n </ion-label>\n } @else {\n <ion-label color=\"medium\">Loading\u2026</ion-label>\n }\n </ion-item>\n }\n</ion-card>\n\n<ion-card>\n <ion-card-header>\n <ion-card-title>How to split</ion-card-title>\n </ion-card-header>\n <ion-segment\n [value]=\"$splitMode()\"\n (ionChange)=\"onSplitModeChange($event)\"\n >\n <ion-segment-button value=\"equally\">\n <ion-label>Equally</ion-label>\n </ion-segment-button>\n <ion-segment-button value=\"exact-amount\">\n <ion-label>Amounts</ion-label>\n </ion-segment-button>\n <ion-segment-button value=\"percentage\">\n <ion-label>Percentages</ion-label>\n </ion-segment-button>\n </ion-segment>\n\n @if ($splitMode() !== 'equally') {\n @for (row of $shareRows(); track row.id) {\n <ion-item>\n <ion-input\n [label]=\"row.title\"\n labelPlacement=\"stacked\"\n type=\"number\"\n inputmode=\"decimal\"\n [placeholder]=\"$splitMode() === 'percentage' ? '0' : '0.00'\"\n [value]=\"shareValue(row.id)\"\n (ionInput)=\"onShareInput(row.id, $event)\"\n />\n @if ($splitMode() === 'percentage') {\n <ion-note slot=\"end\">%</ion-note>\n }\n </ion-item>\n }\n <!-- Live reconciliation: submit stays blocked until shares sum to the\n total (or percentages to 100%). -->\n @if ($reconciliation(); as reconciliation) {\n @if (!reconciliation.ok) {\n <ion-note color=\"danger\" class=\"ion-padding-start\">\n {{ reconciliation.error }}\n </ion-note>\n } @else {\n <ion-note color=\"success\" class=\"ion-padding-start\">\n Shares reconcile to the total. \u2713\n </ion-note>\n }\n }\n } @else {\n <ion-item lines=\"none\">\n <ion-note color=\"medium\">\n The total is split equally between you and the selected participants.\n </ion-note>\n </ion-item>\n }\n</ion-card>\n\n<div class=\"ion-padding\">\n <ion-button\n expand=\"block\"\n color=\"primary\"\n [disabled]=\"$submitting() || !$canSubmit()\"\n (click)=\"submit()\"\n >\n @if ($submitting()) {\n <ion-spinner name=\"dots\" slot=\"start\" />\n Creating\u2026\n } @else {\n Create split\n }\n </ion-button>\n @if ($submitError()) {\n <ion-note color=\"danger\">{{ $submitError() }}</ion-note>\n }\n</div>\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: 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: 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: 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: 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: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonSegment, selector: "ion-segment", inputs: ["color", "disabled", "mode", "scrollable", "selectOnFocus", "swipeGesture", "value"] }, { kind: "component", type: IonSegmentButton, selector: "ion-segment-button", inputs: ["contentId", "disabled", "layout", "mode", "type", "value"] }, { 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"] }] });
407
- }
408
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: NewSplitFormComponent, decorators: [{
409
- type: Component,
410
- args: [{ selector: 'sneat-splitus-new-split-form', imports: [
411
- ReactiveFormsModule,
412
- IonCard,
413
- IonCardHeader,
414
- IonCardTitle,
415
- IonCardContent,
416
- IonItem,
417
- IonLabel,
418
- IonNote,
419
- IonSelect,
420
- IonSelectOption,
421
- IonInput,
422
- IonCheckbox,
423
- IonSegment,
424
- IonSegmentButton,
425
- IonButton,
426
- IonSpinner,
427
- ], template: "@if ($resumedFromDraft()) {\n <ion-card color=\"light\">\n <ion-card-content\n style=\"display: flex; align-items: center; justify-content: space-between; gap: 8px;\"\n >\n <ion-note color=\"medium\"\n >Picking up your bill from earlier \u2014 edit anything below.</ion-note\n >\n <ion-button fill=\"clear\" size=\"small\" (click)=\"dismissDraftBanner()\"\n >Dismiss</ion-button\n >\n </ion-card-content>\n </ion-card>\n}\n\n<ion-card>\n <ion-card-header>\n <ion-card-title>Expense</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<ion-card>\n <ion-card-header>\n <ion-card-title>Participants</ion-card-title>\n </ion-card-header>\n <!-- The payer (you) is always a participant. -->\n <ion-item>\n <ion-label>You (payer)</ion-label>\n <ion-note slot=\"end\" color=\"medium\">always included</ion-note>\n </ion-item>\n <!-- Participants come from the space's existing contactus people \u2014\n there is deliberately no free-text name entry here. -->\n @for (contact of $participantChoices() ?? []; track contact.id) {\n <ion-item>\n <ion-checkbox\n labelPlacement=\"end\"\n justify=\"start\"\n [checked]=\"$selectedContactIDs().includes(contact.id)\"\n (ionChange)=\"onParticipantChecked(contact.id, $event)\"\n >\n {{ contact.title }}\n </ion-checkbox>\n </ion-item>\n } @empty {\n <ion-item lines=\"none\">\n @if ($contactsError()) {\n <ion-label color=\"danger\">{{ $contactsError() }}</ion-label>\n } @else if ($contacts()) {\n <ion-label color=\"medium\" class=\"ion-text-wrap\">\n No contacts yet. Add people to this space first \u2014 splits are shared\n with the space's existing contacts.\n </ion-label>\n } @else {\n <ion-label color=\"medium\">Loading\u2026</ion-label>\n }\n </ion-item>\n }\n</ion-card>\n\n<ion-card>\n <ion-card-header>\n <ion-card-title>How to split</ion-card-title>\n </ion-card-header>\n <ion-segment\n [value]=\"$splitMode()\"\n (ionChange)=\"onSplitModeChange($event)\"\n >\n <ion-segment-button value=\"equally\">\n <ion-label>Equally</ion-label>\n </ion-segment-button>\n <ion-segment-button value=\"exact-amount\">\n <ion-label>Amounts</ion-label>\n </ion-segment-button>\n <ion-segment-button value=\"percentage\">\n <ion-label>Percentages</ion-label>\n </ion-segment-button>\n </ion-segment>\n\n @if ($splitMode() !== 'equally') {\n @for (row of $shareRows(); track row.id) {\n <ion-item>\n <ion-input\n [label]=\"row.title\"\n labelPlacement=\"stacked\"\n type=\"number\"\n inputmode=\"decimal\"\n [placeholder]=\"$splitMode() === 'percentage' ? '0' : '0.00'\"\n [value]=\"shareValue(row.id)\"\n (ionInput)=\"onShareInput(row.id, $event)\"\n />\n @if ($splitMode() === 'percentage') {\n <ion-note slot=\"end\">%</ion-note>\n }\n </ion-item>\n }\n <!-- Live reconciliation: submit stays blocked until shares sum to the\n total (or percentages to 100%). -->\n @if ($reconciliation(); as reconciliation) {\n @if (!reconciliation.ok) {\n <ion-note color=\"danger\" class=\"ion-padding-start\">\n {{ reconciliation.error }}\n </ion-note>\n } @else {\n <ion-note color=\"success\" class=\"ion-padding-start\">\n Shares reconcile to the total. \u2713\n </ion-note>\n }\n }\n } @else {\n <ion-item lines=\"none\">\n <ion-note color=\"medium\">\n The total is split equally between you and the selected participants.\n </ion-note>\n </ion-item>\n }\n</ion-card>\n\n<div class=\"ion-padding\">\n <ion-button\n expand=\"block\"\n color=\"primary\"\n [disabled]=\"$submitting() || !$canSubmit()\"\n (click)=\"submit()\"\n >\n @if ($submitting()) {\n <ion-spinner name=\"dots\" slot=\"start\" />\n Creating\u2026\n } @else {\n Create split\n }\n </ion-button>\n @if ($submitError()) {\n <ion-note color=\"danger\">{{ $submitError() }}</ion-note>\n }\n</div>\n" }]
428
- }], propDecorators: { space: [{
429
- type: Input,
430
- args: [{ required: true }]
431
- }], currentUserID: [{
432
- type: Input
433
- }] } });
434
-
435
- // Splitus space-scoped routes, mounted under space/:spaceType/:spaceID.
436
- // Flow: splits (home) --"New split"--> new-split --create--> split/:splitID.
437
- const spacePagesRoutes = [
438
- {
439
- path: 'splits',
440
- data: { title: 'Splits' },
441
- loadComponent: () => import('./sneat-extension-splitus-ui-splitus-home-page.component-Dp5oLR86.mjs').then((m) => m.SplitusHomePageComponent),
442
- },
443
- {
444
- path: 'new-split',
445
- data: { title: 'New split' },
446
- loadComponent: () => import('./sneat-extension-splitus-ui-new-split-page.component-DkP-7W5H.mjs').then((m) => m.NewSplitPageComponent),
447
- },
448
- {
449
- path: 'split/:splitID',
450
- data: { title: 'Split' },
451
- loadComponent: () => import('./sneat-extension-splitus-ui-split-details-page.component-kzwMFwkF.mjs').then((m) => m.SplitDetailsPageComponent),
452
- },
453
- ];
454
-
455
- // Unguarded entry point for a first-time visitor to start a split before
456
- // signing in. Deliberately NOT space-scoped — kept separate from
457
- // spacePagesRoutes, which mounts under space/:spaceType/:spaceID.
458
- const startSplitRoute = {
459
- path: 'start-split',
460
- data: { title: 'Split a bill' },
461
- loadComponent: () => import('./sneat-extension-splitus-ui-start-split-page.component-BHsYZbKT.mjs').then((m) => m.StartSplitPageComponent),
462
- };
463
-
464
- /**
465
- * Generated bundle index. Do not edit.
466
- */
467
-
468
- export { DRAFT_SPLIT_STORAGE_KEY, DraftSplitStore, NewSplitFormComponent, spacePagesRoutes, startSplitRoute };
1
+ export { c as DRAFT_SPLIT_STORAGE_KEY, D as DraftSplitStore, N as NewSplitFormComponent, b as SPLITUS_SETTLEMENT_URL_BUILDER, S as SPLITUS_START_CONTINUATION_URL, l as legacyStartSplitRoute, s as spacePagesRoutes, d as startBillRoute } from './sneat-extension-splitus-ui-sneat-extension-splitus-ui-CX1eiAAR.mjs';
469
2
  //# sourceMappingURL=sneat-extension-splitus-ui.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"sneat-extension-splitus-ui.mjs","sources":["../../../../../../libs/extensions/splitus/ui/src/lib/services/draft-split-store.service.ts","../../../../../../libs/extensions/splitus/ui/src/lib/components/new-split-form/split-shares.ts","../../../../../../libs/extensions/splitus/ui/src/lib/components/new-split-form/new-split-form.component.ts","../../../../../../libs/extensions/splitus/ui/src/lib/components/new-split-form/new-split-form.component.html","../../../../../../libs/extensions/splitus/ui/src/lib/pages/space-pages-routing.ts","../../../../../../libs/extensions/splitus/ui/src/lib/pages/start-split/start-split.routes.ts","../../../../../../libs/extensions/splitus/ui/src/sneat-extension-splitus-ui.ts"],"sourcesContent":["import { Injectable } from '@angular/core';\nimport { CurrencyCode } from '@sneat/extension-splitus-contract';\n\n/**\n * A single in-progress \"start a split\" draft — captured before the user has\n * a space or is even signed in (see StartSplitPageComponent), resumed into\n * NewSplitFormComponent once they are. See\n * docs/superpowers/specs/2026-07-03-anonymous-first-split-design.md.\n */\nexport interface IDraftSplit {\n readonly title: string;\n readonly amount: number;\n readonly currencyCode: CurrencyCode;\n readonly createdAt: number; // epoch ms\n}\n\nexport const DRAFT_SPLIT_STORAGE_KEY = 'sneat.splitus.draftSplit';\n\nconst MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24h\n\nfunction isDraftSplit(value: unknown): value is IDraftSplit {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n const v = value as Record<string, unknown>;\n return (\n typeof v['title'] === 'string' &&\n typeof v['amount'] === 'number' &&\n typeof v['currencyCode'] === 'string' &&\n typeof v['createdAt'] === 'number'\n );\n}\n\n@Injectable({ providedIn: 'root' })\nexport class DraftSplitStore {\n save(draft: Omit<IDraftSplit, 'createdAt'>): void {\n const full: IDraftSplit = { ...draft, createdAt: Date.now() };\n try {\n localStorage.setItem(DRAFT_SPLIT_STORAGE_KEY, JSON.stringify(full));\n } catch {\n // Storage unavailable (private mode, quota) — fail open, never block\n // navigation over a lost draft.\n }\n }\n\n read(): IDraftSplit | undefined {\n let raw: string | null;\n try {\n raw = localStorage.getItem(DRAFT_SPLIT_STORAGE_KEY);\n } catch {\n return undefined;\n }\n if (!raw) {\n return undefined;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return undefined;\n }\n if (!isDraftSplit(parsed) || Date.now() - parsed.createdAt > MAX_AGE_MS) {\n return undefined;\n }\n return parsed;\n }\n\n hasPending(): boolean {\n return this.read() !== undefined;\n }\n\n clear(): void {\n try {\n localStorage.removeItem(DRAFT_SPLIT_STORAGE_KEY);\n } catch {\n // ignore\n }\n }\n}\n","import { SplitMode } from '@sneat/extension-splitus-contract';\n\n// Pure client-side share reconciliation for the create-split form.\n// All arithmetic is done in integer hundredths (cents for amounts, basis\n// points for percentages) so \"0.10 + 0.20 == 0.30\" holds exactly — the same\n// fixed-point discipline the backend applies (decimal.Decimal64p2).\n\nexport interface IShareReconciliation {\n readonly ok: boolean;\n /** Explanatory, user-facing message when `ok` is false. */\n readonly error?: string;\n}\n\n/**\n * Parses a decimal user input (\"33.34\", 50) into integer hundredths (3334).\n * Returns undefined for blank, non-numeric or negative values.\n */\nexport function parseDecimal(\n raw: string | number | null | undefined,\n): number | undefined {\n if (raw === null || raw === undefined) {\n return undefined;\n }\n const s = String(raw).trim();\n if (!s) {\n return undefined;\n }\n const n = Number(s);\n if (!Number.isFinite(n) || n < 0) {\n return undefined;\n }\n return Math.round(n * 100);\n}\n\n/** Formats integer hundredths as a 2-decimal string: 3334 → \"33.34\". */\nexport function formatHundredths(v: number): string {\n return (v / 100).toFixed(2);\n}\n\n/** Formats basis points as a trimmed percent number: 3334 → \"33.34\", 5000 → \"50\". */\nexport function formatPercentValue(v: number): string {\n return String(v / 100);\n}\n\n/**\n * Live reconciliation of custom shares against the expense total.\n *\n * - `equally`: always ok — there are no share inputs; the backend splits.\n * - `exact-amount`: every share must be entered and sum exactly to the total.\n * - `percentage`: every percentage must be entered and sum exactly to 100%.\n *\n * @param totalHundredths the expense total in hundredths (cents), if entered.\n * @param shares raw share inputs, one per participant (payer included).\n */\nexport function reconcileShares(\n mode: SplitMode,\n totalHundredths: number | undefined,\n shares: readonly (string | number | null | undefined)[],\n): IShareReconciliation {\n if (mode === 'equally') {\n return { ok: true };\n }\n\n const parsed = shares.map(parseDecimal);\n if (parsed.some((v) => v === undefined)) {\n return {\n ok: false,\n error:\n mode === 'percentage'\n ? 'Enter a percentage for every participant.'\n : 'Enter a share for every participant.',\n };\n }\n const entered = (parsed as number[]).reduce((sum, v) => sum + v, 0);\n\n if (mode === 'percentage') {\n if (entered === 100_00) {\n return { ok: true };\n }\n return {\n ok: false,\n error: `Percentages sum to ${formatPercentValue(entered)}% — they must sum to exactly 100%.`,\n };\n }\n\n // exact-amount\n if (totalHundredths === undefined) {\n return { ok: false, error: 'Enter the expense total first.' };\n }\n if (entered === totalHundredths) {\n return { ok: true };\n }\n const delta = entered - totalHundredths;\n const direction =\n delta < 0\n ? `${formatHundredths(-delta)} short of`\n : `${formatHundredths(delta)} over`;\n return {\n ok: false,\n error: `Shares sum to ${formatHundredths(entered)} — ${direction} the ${formatHundredths(totalHundredths)} total.`,\n };\n}\n","import {\n Component,\n DestroyRef,\n Input,\n computed,\n inject,\n signal,\n} from '@angular/core';\nimport { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';\nimport {\n FormControl,\n ReactiveFormsModule,\n Validators,\n} from '@angular/forms';\nimport {\n IonButton,\n IonCard,\n IonCardContent,\n IonCardHeader,\n IonCardTitle,\n IonCheckbox,\n IonInput,\n IonItem,\n IonLabel,\n IonNote,\n IonSegment,\n IonSegmentButton,\n IonSelect,\n IonSelectOption,\n IonSpinner,\n} from '@ionic/angular';\nimport { ErrorLogger, IErrorLogger, IIdAndBrief } from '@sneat/core';\nimport {\n CONTACTUS_SPACE_SERVICE,\n IContactBrief,\n} from '@sneat/extension-contactus-contract';\nimport {\n CurrencyCode,\n ICreateSplitRequest,\n ISplitShare,\n SPLITUS_SERVICE,\n SplitMode,\n} from '@sneat/extension-splitus-contract';\nimport { ISpaceContext } from '@sneat/space-models';\nimport { SpaceNavService } from '@sneat/space-services';\nimport { Subscription } from 'rxjs';\nimport { DraftSplitStore } from '../../services/draft-split-store.service';\nimport {\n formatHundredths,\n formatPercentValue,\n parseDecimal,\n reconcileShares,\n} from './split-shares';\n\ninterface IParticipantChoice {\n readonly id: string;\n readonly title: string;\n}\n\n/** One row of the custom-share editor. `id === ''` is the payer. */\ninterface IShareRow {\n readonly id: string;\n readonly title: string;\n}\n\n/** Pulls a human message out of an HTTP error (Go handlers reply plain text). */\nfunction extractServerMessage(err: unknown): string | undefined {\n if (typeof err !== 'object' || !err) {\n return typeof err === 'string' && err.trim() ? err.trim() : undefined;\n }\n const e = err as { error?: unknown; message?: unknown };\n if (typeof e.error === 'string' && e.error.trim()) {\n return e.error.trim();\n }\n if (typeof e.error === 'object' && e.error) {\n const m = (e.error as { message?: unknown }).message;\n if (typeof m === 'string' && m.trim()) {\n return m.trim();\n }\n }\n return typeof e.message === 'string' && e.message.trim()\n ? e.message.trim()\n : undefined;\n}\n\n// Create-split form: amount + currency, participants picked from the space's\n// existing contactus people (no free-text name entry — AC\n// participants-from-contactus-membership-enforced), and an\n// equally/exact-amount/percentage share editor with live reconciliation that\n// blocks submit until custom shares sum to the total (AC\n// custom-shares-must-sum). On success redirects to the new split's details\n// page with replaceUrl (flows.md create→details default).\n@Component({\n selector: 'sneat-splitus-new-split-form',\n templateUrl: './new-split-form.component.html',\n imports: [\n ReactiveFormsModule,\n IonCard,\n IonCardHeader,\n IonCardTitle,\n IonCardContent,\n IonItem,\n IonLabel,\n IonNote,\n IonSelect,\n IonSelectOption,\n IonInput,\n IonCheckbox,\n IonSegment,\n IonSegmentButton,\n IonButton,\n IonSpinner,\n ],\n})\nexport class NewSplitFormComponent {\n private readonly errorLogger = inject<IErrorLogger>(ErrorLogger);\n private readonly splitusService = inject(SPLITUS_SERVICE);\n private readonly contactusSpaceService = inject(CONTACTUS_SPACE_SERVICE);\n private readonly spaceNavService = inject(SpaceNavService);\n private readonly destroyRef = inject(DestroyRef);\n private readonly draftSplitStore = inject(DraftSplitStore);\n\n protected readonly $resumedFromDraft = signal(false);\n private draftChecked = false;\n\n protected readonly $space = signal<ISpaceContext | undefined>(undefined);\n // TODO: Skipped for migration because:\n // Accessor inputs cannot be migrated as they are too complex.\n @Input({ required: true }) public set space(value: ISpaceContext | undefined) {\n this.$space.set(value);\n this.watchContacts(value?.id);\n this.tryResumeDraft();\n }\n\n private tryResumeDraft(): void {\n if (this.draftChecked) {\n return;\n }\n this.draftChecked = true;\n const draft = this.draftSplitStore.read();\n if (!draft) {\n return;\n }\n this.title.setValue(draft.title);\n this.currency.setValue(draft.currencyCode);\n this.amount.setValue(draft.amount);\n this.$resumedFromDraft.set(true);\n }\n\n protected dismissDraftBanner(): void {\n this.$resumedFromDraft.set(false);\n this.draftSplitStore.clear();\n }\n\n protected readonly $currentUserID = signal<string | undefined>(undefined);\n /** The authenticated user's ID — used to exclude the payer's own contact. */\n // TODO: Skipped for migration because:\n // Accessor inputs cannot be migrated as they are too complex.\n @Input() public set currentUserID(value: string | undefined) {\n this.$currentUserID.set(value);\n }\n\n // ---- participants (sourced from contactus — never typed in) ----\n\n private contactsSubscription?: Subscription;\n private watchedSpaceID?: string;\n protected readonly $contacts = signal<\n IIdAndBrief<IContactBrief>[] | undefined\n >(undefined);\n protected readonly $contactsError = signal<string | undefined>(undefined);\n\n private watchContacts(spaceID: string | undefined): void {\n if (spaceID === this.watchedSpaceID) {\n return;\n }\n this.watchedSpaceID = spaceID;\n this.contactsSubscription?.unsubscribe();\n this.$contacts.set(undefined);\n this.$contactsError.set(undefined);\n if (!spaceID) {\n return;\n }\n this.contactsSubscription = this.contactusSpaceService\n .watchContactBriefs(spaceID)\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe({\n next: (contacts) => this.$contacts.set(contacts),\n error: (err) => {\n this.$contactsError.set('Failed to load contacts.');\n this.errorLogger.logError(err, 'Failed to load space contacts', {\n show: false,\n });\n },\n });\n }\n\n /**\n * The space's contactus people offered as participants. The payer (the\n * authenticated user) is always a participant and must not be listed in\n * `participantContactIDs`, so their own contact is excluded from the picker.\n */\n protected readonly $participantChoices = computed<\n readonly IParticipantChoice[] | undefined\n >(() => {\n const userID = this.$currentUserID();\n return this.$contacts()\n ?.filter((c) => !userID || c.brief?.userID !== userID)\n .map((c) => ({\n id: c.id,\n title: c.brief?.title || c.brief?.shortTitle || c.id,\n }));\n });\n\n protected readonly $selectedContactIDs = signal<readonly string[]>([]);\n\n protected onParticipantToggle(contactID: string, checked: boolean): void {\n this.$selectedContactIDs.update((ids) => {\n const without = ids.filter((id) => id !== contactID);\n return checked ? [...without, contactID] : without;\n });\n }\n\n protected onParticipantChecked(contactID: string, event: Event): void {\n const checked = !!(event as CustomEvent<{ checked?: boolean }>).detail\n ?.checked;\n this.onParticipantToggle(contactID, checked);\n }\n\n // ---- expense fields ----\n\n protected readonly title = new FormControl<string>('', { nonNullable: true });\n // TODO: pre-fill from a per-space default currency once spaces carry one;\n // EUR is the app-wide default (same as debtus's new-transfer form).\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 private readonly $amount = toSignal(this.amount.valueChanges, {\n initialValue: this.amount.value,\n });\n\n protected readonly currencies: CurrencyCode[] = ['EUR', 'USD'];\n\n // ---- split mode + custom shares ----\n\n protected readonly $splitMode = signal<SplitMode>('equally');\n\n protected setSplitMode(mode: SplitMode): void {\n this.$splitMode.set(mode);\n }\n\n protected onSplitModeChange(event: Event): void {\n const value = (event as CustomEvent<{ value?: string }>).detail?.value;\n if (\n value === 'equally' ||\n value === 'exact-amount' ||\n value === 'percentage'\n ) {\n this.setSplitMode(value);\n }\n }\n\n /** Raw share inputs keyed by contact ID ('' = the payer). */\n protected readonly $shareValues = signal<Readonly<Record<string, string>>>(\n {},\n );\n\n protected setShareValue(contactID: string, value: string): void {\n this.$shareValues.update((values) => ({ ...values, [contactID]: value }));\n }\n\n protected shareValue(contactID: string): string {\n const value: string | undefined = this.$shareValues()[contactID];\n return value ?? '';\n }\n\n protected onShareInput(contactID: string, event: Event): void {\n const value = (event as CustomEvent<{ value?: string | null }>).detail\n ?.value;\n this.setShareValue(contactID, value ?? '');\n }\n\n /** Share-editor rows: the payer first, then each selected participant. */\n protected readonly $shareRows = computed<readonly IShareRow[]>(() => {\n const choices = this.$participantChoices() ?? [];\n const selected = this.$selectedContactIDs();\n return [\n { id: '', title: 'You (payer)' },\n ...choices.filter((c) => selected.includes(c.id)),\n ];\n });\n\n /** Live reconciliation of custom shares against the expense total. */\n protected readonly $reconciliation = computed(() =>\n reconcileShares(\n this.$splitMode(),\n parseDecimal(this.$amount()),\n this.$shareRows().map((row) => this.$shareValues()[row.id]),\n ),\n );\n\n protected readonly $canSubmit = computed(() => {\n const total = parseDecimal(this.$amount());\n return (\n total !== undefined &&\n total > 0 &&\n this.$selectedContactIDs().length > 0 &&\n this.$reconciliation().ok\n );\n });\n\n // ---- submit + exit ----\n\n protected readonly $submitting = signal(false);\n protected readonly $submitError = signal<string | undefined>(undefined);\n\n protected submit(): void {\n this.amount.markAsTouched();\n if (this.$submitting() || !this.$canSubmit()) {\n return;\n }\n const space = this.$space();\n if (!space?.id) {\n return;\n }\n const totalCents = parseDecimal(this.$amount());\n if (totalCents === undefined) {\n return;\n }\n const mode = this.$splitMode();\n const shares: ISplitShare[] | undefined =\n mode === 'equally'\n ? undefined\n : this.$shareRows().map((row) => {\n const value = parseDecimal(this.$shareValues()[row.id]) ?? 0;\n return mode === 'percentage'\n ? { contactID: row.id, percent: formatPercentValue(value) }\n : { contactID: row.id, amount: formatHundredths(value) };\n });\n const request: ICreateSplitRequest = {\n spaceID: space.id,\n title: this.title.value.trim() || undefined,\n currency: this.currency.value,\n amount: formatHundredths(totalCents),\n splitMode: mode,\n participantContactIDs: [...this.$selectedContactIDs()],\n shares,\n };\n this.$submitting.set(true);\n this.$submitError.set(undefined);\n this.splitusService.createSplit(request).subscribe({\n next: (response) => {\n this.draftSplitStore.clear();\n // flows.md: after a successful create, go to the new entity's details,\n // replacing the create page in history so Back skips the stale form.\n this.spaceNavService.navigateForwardToSpacePage(\n space,\n `split/${response.id}`,\n { replaceUrl: true },\n );\n },\n error: (err) => {\n this.$submitting.set(false);\n const serverMessage = extractServerMessage(err);\n this.$submitError.set(\n serverMessage\n ? `Failed to create split: ${serverMessage}`\n : 'Failed to create split. Please try again.',\n );\n this.errorLogger.logError(err, 'Failed to create split', {\n show: false,\n });\n },\n });\n }\n}\n","@if ($resumedFromDraft()) {\n <ion-card color=\"light\">\n <ion-card-content\n style=\"display: flex; align-items: center; justify-content: space-between; gap: 8px;\"\n >\n <ion-note color=\"medium\"\n >Picking up your bill from earlier — edit anything below.</ion-note\n >\n <ion-button fill=\"clear\" size=\"small\" (click)=\"dismissDraftBanner()\"\n >Dismiss</ion-button\n >\n </ion-card-content>\n </ion-card>\n}\n\n<ion-card>\n <ion-card-header>\n <ion-card-title>Expense</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<ion-card>\n <ion-card-header>\n <ion-card-title>Participants</ion-card-title>\n </ion-card-header>\n <!-- The payer (you) is always a participant. -->\n <ion-item>\n <ion-label>You (payer)</ion-label>\n <ion-note slot=\"end\" color=\"medium\">always included</ion-note>\n </ion-item>\n <!-- Participants come from the space's existing contactus people —\n there is deliberately no free-text name entry here. -->\n @for (contact of $participantChoices() ?? []; track contact.id) {\n <ion-item>\n <ion-checkbox\n labelPlacement=\"end\"\n justify=\"start\"\n [checked]=\"$selectedContactIDs().includes(contact.id)\"\n (ionChange)=\"onParticipantChecked(contact.id, $event)\"\n >\n {{ contact.title }}\n </ion-checkbox>\n </ion-item>\n } @empty {\n <ion-item lines=\"none\">\n @if ($contactsError()) {\n <ion-label color=\"danger\">{{ $contactsError() }}</ion-label>\n } @else if ($contacts()) {\n <ion-label color=\"medium\" class=\"ion-text-wrap\">\n No contacts yet. Add people to this space first — splits are shared\n with the space's existing contacts.\n </ion-label>\n } @else {\n <ion-label color=\"medium\">Loading…</ion-label>\n }\n </ion-item>\n }\n</ion-card>\n\n<ion-card>\n <ion-card-header>\n <ion-card-title>How to split</ion-card-title>\n </ion-card-header>\n <ion-segment\n [value]=\"$splitMode()\"\n (ionChange)=\"onSplitModeChange($event)\"\n >\n <ion-segment-button value=\"equally\">\n <ion-label>Equally</ion-label>\n </ion-segment-button>\n <ion-segment-button value=\"exact-amount\">\n <ion-label>Amounts</ion-label>\n </ion-segment-button>\n <ion-segment-button value=\"percentage\">\n <ion-label>Percentages</ion-label>\n </ion-segment-button>\n </ion-segment>\n\n @if ($splitMode() !== 'equally') {\n @for (row of $shareRows(); track row.id) {\n <ion-item>\n <ion-input\n [label]=\"row.title\"\n labelPlacement=\"stacked\"\n type=\"number\"\n inputmode=\"decimal\"\n [placeholder]=\"$splitMode() === 'percentage' ? '0' : '0.00'\"\n [value]=\"shareValue(row.id)\"\n (ionInput)=\"onShareInput(row.id, $event)\"\n />\n @if ($splitMode() === 'percentage') {\n <ion-note slot=\"end\">%</ion-note>\n }\n </ion-item>\n }\n <!-- Live reconciliation: submit stays blocked until shares sum to the\n total (or percentages to 100%). -->\n @if ($reconciliation(); as reconciliation) {\n @if (!reconciliation.ok) {\n <ion-note color=\"danger\" class=\"ion-padding-start\">\n {{ reconciliation.error }}\n </ion-note>\n } @else {\n <ion-note color=\"success\" class=\"ion-padding-start\">\n Shares reconcile to the total. ✓\n </ion-note>\n }\n }\n } @else {\n <ion-item lines=\"none\">\n <ion-note color=\"medium\">\n The total is split equally between you and the selected participants.\n </ion-note>\n </ion-item>\n }\n</ion-card>\n\n<div class=\"ion-padding\">\n <ion-button\n expand=\"block\"\n color=\"primary\"\n [disabled]=\"$submitting() || !$canSubmit()\"\n (click)=\"submit()\"\n >\n @if ($submitting()) {\n <ion-spinner name=\"dots\" slot=\"start\" />\n Creating…\n } @else {\n Create split\n }\n </ion-button>\n @if ($submitError()) {\n <ion-note color=\"danger\">{{ $submitError() }}</ion-note>\n }\n</div>\n","import { Route } from '@angular/router';\n\n// Splitus space-scoped routes, mounted under space/:spaceType/:spaceID.\n// Flow: splits (home) --\"New split\"--> new-split --create--> split/:splitID.\nexport const spacePagesRoutes: Route[] = [\n {\n path: 'splits',\n data: { title: 'Splits' },\n loadComponent: () =>\n import('./splitus-home/splitus-home-page.component').then(\n (m) => m.SplitusHomePageComponent,\n ),\n },\n {\n path: 'new-split',\n data: { title: 'New split' },\n loadComponent: () =>\n import('./new-split/new-split-page.component').then(\n (m) => m.NewSplitPageComponent,\n ),\n },\n {\n path: 'split/:splitID',\n data: { title: 'Split' },\n loadComponent: () =>\n import('./split-details/split-details-page.component').then(\n (m) => m.SplitDetailsPageComponent,\n ),\n },\n];\n","import { Route } from '@angular/router';\n\n// Unguarded entry point for a first-time visitor to start a split before\n// signing in. Deliberately NOT space-scoped — kept separate from\n// spacePagesRoutes, which mounts under space/:spaceType/:spaceID.\nexport const startSplitRoute: Route = {\n path: 'start-split',\n data: { title: 'Split a bill' },\n loadComponent: () =>\n import('./start-split-page.component').then(\n (m) => m.StartSplitPageComponent,\n ),\n};\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sneat-extension-splitus-ui.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sneat/extension-splitus-ui",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "peerDependencies": {
5
5
  "@angular/common": ">=22.0.0 <23.0.0",
6
6
  "@angular/core": ">=22.0.0 <23.0.0",
@@ -8,13 +8,14 @@
8
8
  "@angular/router": ">=22.0.0 <23.0.0",
9
9
  "@ionic/angular": ">=9.0.0 <10.0.0",
10
10
  "rxjs": "^7.0.0",
11
- "@sneat/extension-contactus-contract": "0.12.2",
11
+ "@sneat/extension-contactus-contract": "^0.12.2",
12
12
  "@sneat/core": "^0.27.0",
13
13
  "@sneat/space-components": "^0.27.0",
14
14
  "@sneat/space-models": "^0.27.0",
15
15
  "@sneat/space-services": "^0.27.0",
16
16
  "@sneat/ui": "^0.27.0",
17
- "@sneat/extension-splitus-contract": "^0.2.0"
17
+ "@sneat/random": "^0.0.4",
18
+ "@sneat/extension-splitus-contract": "0.2.2"
18
19
  },
19
20
  "dependencies": {
20
21
  "tslib": "^2.3.0"