@sneat/space-components 0.27.9 → 0.27.11
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.
|
@@ -45,7 +45,8 @@ export class SpacesCardComponent {
|
|
|
45
45
|
}
|
|
46
46
|
return Object.entries(record.spaces ?? {})
|
|
47
47
|
.map(([id, brief]) => ({ id, brief }))
|
|
48
|
-
.filter(({ brief }) => !this.spaceType || brief.type === this.spaceType)
|
|
48
|
+
.filter(({ brief }) => (!this.spaceType || brief.type === this.spaceType) &&
|
|
49
|
+
(!this.groupKind || brief.groupKind === this.groupKind))
|
|
49
50
|
.sort((a, b) => (a.brief.title > b.brief.title ? 1 : -1));
|
|
50
51
|
}, /* @ts-ignore */
|
|
51
52
|
...(ngDevMode ? [{ debugName: "spaces" }] : /* istanbul ignore next */ []));
|
|
@@ -55,7 +56,12 @@ export class SpacesCardComponent {
|
|
|
55
56
|
this.spaceContexts = computed(() => this.spaces()?.map(({ id, brief }) => ({
|
|
56
57
|
id,
|
|
57
58
|
type: brief.type,
|
|
58
|
-
brief: {
|
|
59
|
+
brief: {
|
|
60
|
+
title: brief.title,
|
|
61
|
+
type: brief.type,
|
|
62
|
+
groupKind: brief.groupKind,
|
|
63
|
+
roles: brief.roles,
|
|
64
|
+
},
|
|
59
65
|
})), /* @ts-ignore */
|
|
60
66
|
...(ngDevMode ? [{ debugName: "spaceContexts" }] : /* istanbul ignore next */ []));
|
|
61
67
|
this.loadingState = computed(() => this.userState()?.status === 'authenticated' ? 'Loading' : 'Authenticating', /* @ts-ignore */
|
|
@@ -93,7 +99,8 @@ export class SpacesCardComponent {
|
|
|
93
99
|
return;
|
|
94
100
|
}
|
|
95
101
|
const request = {
|
|
96
|
-
type: 'team',
|
|
102
|
+
type: this.spaceType ?? 'team',
|
|
103
|
+
groupKind: this.groupKind,
|
|
97
104
|
title,
|
|
98
105
|
};
|
|
99
106
|
this.adding.set(true);
|
|
@@ -142,7 +149,7 @@ export class SpacesCardComponent {
|
|
|
142
149
|
this.showAdd.set(false);
|
|
143
150
|
}
|
|
144
151
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SpacesCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
145
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: SpacesCardComponent, isStandalone: true, selector: "sneat-spaces-card", inputs: { title: "title", spaceType: "spaceType", canAdd: "canAdd" }, viewQueries: [{ propertyName: "addSpaceInput", first: true, predicate: ["addTeamInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<ion-card>\n <ion-item class=\"sneat-card-header\" lines=\"full\">\n <ion-label>\n <ion-card-title color=\"medium\">{{ title }}</ion-card-title>\n </ion-label>\n <ion-buttons slot=\"end\" (click)=\"startAddingSpace()\">\n @if (canAdd && !showAdd()) {\n <ion-button color=\"primary\">\n <ion-icon name=\"add\" slot=\"start\" />\n <ion-label>Add</ion-label>\n </ion-button>\n }\n </ion-buttons>\n </ion-item>\n @if (spaceContexts(); as spaceContexts) {\n <ion-list>\n <sneat-spaces-list\n [userID]=\"currentUserID\"\n [spaces]=\"spaceContexts\"\n [allowLeave]=\"true\"\n />\n </ion-list>\n } @else {\n <ion-list>\n <ion-item>\n <ion-spinner name=\"\" slot=\"start\" color=\"medium\" />\n <ion-buttons slot=\"start\">\n <ion-button disabled=\"disabled\" style=\"text-transform: none\"\n >{{ loadingState() }}...\n </ion-button>\n </ion-buttons>\n <ion-skeleton-text animated />\n </ion-item>\n </ion-list>\n }\n\n @if (showAdd()) {\n <ion-item [disabled]=\"adding()\">\n <ion-input\n (keyup.enter)=\"addSpace()\"\n #addTeamInput\n [ngModel]=\"spaceName()\"\n (ngModelChange)=\"spaceName.set($event)\"\n (keyup.escape)=\"cancelAdd()\"\n placeholder=\"New team name\"\n />\n <ion-buttons slot=\"end\">\n <ion-button color=\"primary\" fill=\"solid\" (click)=\"addSpace()\">\n <ion-label>Create</ion-label>\n </ion-button>\n @if (!!spaces()?.length) {\n <ion-button (click)=\"cancelAdd()\" color=\"medium\" title=\"Cancel\">\n @if (adding()) {\n <ion-spinner />\n } @else {\n <ion-icon name=\"close-outline\" />\n }\n </ion-button>\n }\n </ion-buttons>\n </ion-item>\n }\n @if (showAdd()) {\n <ion-card-content>\n <p>Enter team name and click \"Create\" button to add a new team.</p>\n </ion-card-content>\n }\n</ion-card>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { 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: IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { 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: IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { 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: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: SpacesListComponent, selector: "sneat-spaces-list", inputs: ["userID", "spaces", "pathPrefix", "allowLeave"], outputs: ["beforeNavigateToSpace", "leftSpace"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
152
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: SpacesCardComponent, isStandalone: true, selector: "sneat-spaces-card", inputs: { title: "title", spaceType: "spaceType", groupKind: "groupKind", canAdd: "canAdd" }, viewQueries: [{ propertyName: "addSpaceInput", first: true, predicate: ["addTeamInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<ion-card>\n <ion-item class=\"sneat-card-header\" lines=\"full\">\n <ion-label>\n <ion-card-title color=\"medium\">{{ title }}</ion-card-title>\n </ion-label>\n <ion-buttons slot=\"end\" (click)=\"startAddingSpace()\">\n @if (canAdd && !showAdd()) {\n <ion-button color=\"primary\">\n <ion-icon name=\"add\" slot=\"start\" />\n <ion-label>Add</ion-label>\n </ion-button>\n }\n </ion-buttons>\n </ion-item>\n @if (spaceContexts(); as spaceContexts) {\n <ion-list>\n <sneat-spaces-list\n [userID]=\"currentUserID\"\n [spaces]=\"spaceContexts\"\n [allowLeave]=\"true\"\n />\n </ion-list>\n } @else {\n <ion-list>\n <ion-item>\n <ion-spinner name=\"\" slot=\"start\" color=\"medium\" />\n <ion-buttons slot=\"start\">\n <ion-button disabled=\"disabled\" style=\"text-transform: none\"\n >{{ loadingState() }}...\n </ion-button>\n </ion-buttons>\n <ion-skeleton-text animated />\n </ion-item>\n </ion-list>\n }\n\n @if (showAdd()) {\n <ion-item [disabled]=\"adding()\">\n <ion-input\n (keyup.enter)=\"addSpace()\"\n #addTeamInput\n [ngModel]=\"spaceName()\"\n (ngModelChange)=\"spaceName.set($event)\"\n (keyup.escape)=\"cancelAdd()\"\n placeholder=\"New team name\"\n />\n <ion-buttons slot=\"end\">\n <ion-button color=\"primary\" fill=\"solid\" (click)=\"addSpace()\">\n <ion-label>Create</ion-label>\n </ion-button>\n @if (!!spaces()?.length) {\n <ion-button (click)=\"cancelAdd()\" color=\"medium\" title=\"Cancel\">\n @if (adding()) {\n <ion-spinner />\n } @else {\n <ion-icon name=\"close-outline\" />\n }\n </ion-button>\n }\n </ion-buttons>\n </ion-item>\n }\n @if (showAdd()) {\n <ion-card-content>\n <p>Enter team name and click \"Create\" button to add a new team.</p>\n </ion-card-content>\n }\n</ion-card>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { 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: IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { 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: IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { 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: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: SpacesListComponent, selector: "sneat-spaces-list", inputs: ["userID", "spaces", "pathPrefix", "allowLeave"], outputs: ["beforeNavigateToSpace", "leftSpace"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
146
153
|
}
|
|
147
154
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SpacesCardComponent, decorators: [{
|
|
148
155
|
type: Component,
|
|
@@ -166,6 +173,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
166
173
|
type: Input
|
|
167
174
|
}], spaceType: [{
|
|
168
175
|
type: Input
|
|
176
|
+
}], groupKind: [{
|
|
177
|
+
type: Input
|
|
169
178
|
}], canAdd: [{
|
|
170
179
|
type: Input
|
|
171
180
|
}], addSpaceInput: [{ type: i0.ViewChild, args: ['addTeamInput', { isSignal: true }] }] } });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spaces-card.component.js","sourceRoot":"","sources":["../../../../../../../libs/space/components/src/lib/spaces-card/spaces-card.component.ts","../../../../../../../libs/space/components/src/lib/spaces-card/spaces-card.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n effect,\n inject,\n Input,\n signal,\n viewChild,\n} from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { FormsModule } from '@angular/forms';\nimport {\n IonButton,\n IonButtons,\n IonCard,\n IonCardContent,\n IonCardTitle,\n IonIcon,\n IonInput,\n IonItem,\n IonLabel,\n IonList,\n IonSkeletonText,\n IonSpinner,\n ToastController,\n} from '@ionic/angular';\nimport { AnalyticsService, IAnalyticsService } from '@sneat/core';\nimport { IUserSpaceBrief } from '@sneat/auth-models';\nimport { IIdAndBrief } from '@sneat/core';\nimport { ErrorLogger, IErrorLogger } from '@sneat/core';\nimport { ICreateSpaceRequest, ISpaceContext } from '@sneat/space-models';\nimport { SpaceType } from '@sneat/core';\nimport { SpaceNavService, SpaceService } from '@sneat/space-services';\nimport { SneatUserService } from '@sneat/auth-core';\nimport { SpacesListComponent } from '../spaces-list';\n\n// Signal-based + OnPush so the card repaints reactively when the user record\n// loads, instead of mutating fields inside an rxjs subscription and relying on\n// Zone change detection. The previous version stayed stuck on \"Authenticating...\"\n// when the Firestore onSnapshot update landed outside the Angular zone (the\n// record loaded but the view never repainted). toSignal()/computed() repaint\n// correctly under zone.js too — this is not a zoneless change.\n@Component({\n selector: 'sneat-spaces-card',\n templateUrl: './spaces-card.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [\n FormsModule,\n IonInput,\n IonCard,\n IonItem,\n IonLabel,\n IonCardTitle,\n IonButtons,\n IonButton,\n IonIcon,\n IonList,\n IonSpinner,\n IonSkeletonText,\n IonCardContent,\n SpacesListComponent,\n ],\n})\nexport class SpacesCardComponent {\n /** Card heading — a product names its spaces (\"Clubs\", \"Schools\"). */\n @Input() title = 'Spaces';\n\n /**\n * When set, only spaces of this type are listed. A Sneat account holds\n * spaces from every product (family, work, clubs...), and a product home\n * that lists them all reads as a generic space browser rather than the\n * product — sneat.club wants exactly the `club` spaces.\n */\n @Input() spaceType?: SpaceType;\n\n /**\n * Whether the card offers its quick-add form. A product whose spaces are\n * created by a registration flow (decision 0006) passes false and offers\n * its own \"Register ...\" call to action instead.\n */\n @Input() canAdd = true;\n\n private readonly errorLogger = inject<IErrorLogger>(ErrorLogger);\n private readonly navService = inject(SpaceNavService);\n private readonly userService = inject(SneatUserService);\n private readonly spaceService = inject(SpaceService);\n private readonly analyticsService =\n inject<IAnalyticsService>(AnalyticsService);\n private readonly toastController = inject(ToastController);\n\n private readonly addSpaceInput = viewChild<IonInput>('addTeamInput');\n\n private readonly userState = toSignal(this.userService.userState);\n\n // undefined => user record not loaded yet (render the loading row).\n protected readonly spaces = computed<\n IIdAndBrief<IUserSpaceBrief>[] | undefined\n >(() => {\n const record = this.userState()?.record;\n if (!record) {\n return undefined;\n }\n return Object.entries(record.spaces ?? {})\n .map(([id, brief]) => ({ id, brief }))\n .filter(({ brief }) => !this.spaceType || brief.type === this.spaceType)\n .sort((a, b) => (a.brief.title > b.brief.title ? 1 : -1));\n });\n\n // Adapts the user's spaces to the shape SpacesListComponent renders, so the\n // card reuses that component (icon + title decode + navigation) instead of\n // duplicating the row markup.\n protected readonly spaceContexts = computed<ISpaceContext[] | undefined>(\n () =>\n this.spaces()?.map(({ id, brief }) => ({\n id,\n type: brief.type,\n brief: { title: brief.title, type: brief.type, roles: brief.roles },\n })),\n );\n\n protected get currentUserID(): string {\n return this.userService.currentUserID ?? '';\n }\n\n protected readonly loadingState = computed(() =>\n this.userState()?.status === 'authenticated' ? 'Loading' : 'Authenticating',\n );\n\n protected readonly showAdd = signal(false);\n protected readonly spaceName = signal('');\n protected readonly adding = signal(false);\n\n public constructor() {\n // Auto-open the \"add space\" form once we know the user has no spaces —\n // unless adding is disabled (a registration-flow product supplies its own\n // call to action).\n effect(() => {\n const spaces = this.spaces();\n if (spaces && spaces.length === 0 && this.canAdd) {\n this.startAddingSpace();\n }\n });\n }\n\n private navigateToSpace(space: ISpaceContext): void {\n this.navService\n .navigateToSpace(space, 'forward')\n .catch(this.errorLogger.logError);\n }\n\n protected addSpace(): void {\n this.analyticsService.logEvent('addSpace');\n const title = this.spaceName().trim();\n if (!title) {\n this.presentToast('Space name is required', 'tertiary');\n return;\n }\n if (this.spaces()?.find((t) => t.brief.title === title)) {\n this.presentToast('You already have a team with the same name', 'danger');\n return;\n }\n const request: ICreateSpaceRequest = {\n type: 'team',\n title,\n };\n this.adding.set(true);\n this.spaceService.createSpace(request).subscribe({\n next: (space) => {\n this.analyticsService.logEvent('spaceCreated', { space: space.id });\n this.adding.set(false);\n this.spaceName.set('');\n // The user record updates via Firestore, which recomputes `spaces`.\n this.navigateToSpace(space);\n },\n error: (err) => {\n this.errorLogger.logError(err, 'Failed to create new team record');\n this.adding.set(false);\n },\n });\n }\n\n private presentToast(message: string, color: string): void {\n this.toastController\n .create({\n position: 'middle',\n message,\n color,\n duration: 5000,\n keyboardClose: true,\n buttons: [{ role: 'cancel', text: 'OK' }],\n })\n .then((toast) =>\n toast\n .present()\n .catch((err) =>\n this.errorLogger.logError(err, 'Failed to present toast'),\n ),\n )\n .catch((err) => this.errorLogger.logError(err, 'Failed to create toast'));\n }\n\n public startAddingSpace(): void {\n this.showAdd.set(true);\n setTimeout(() => {\n const input = this.addSpaceInput();\n if (!input) {\n return;\n }\n input\n .setFocus()\n .catch((err) =>\n this.errorLogger.logError(err, 'Failed to set focus to addTeamInput'),\n );\n }, 200);\n }\n\n protected cancelAdd(): void {\n this.showAdd.set(false);\n }\n}\n","<ion-card>\n <ion-item class=\"sneat-card-header\" lines=\"full\">\n <ion-label>\n <ion-card-title color=\"medium\">{{ title }}</ion-card-title>\n </ion-label>\n <ion-buttons slot=\"end\" (click)=\"startAddingSpace()\">\n @if (canAdd && !showAdd()) {\n <ion-button color=\"primary\">\n <ion-icon name=\"add\" slot=\"start\" />\n <ion-label>Add</ion-label>\n </ion-button>\n }\n </ion-buttons>\n </ion-item>\n @if (spaceContexts(); as spaceContexts) {\n <ion-list>\n <sneat-spaces-list\n [userID]=\"currentUserID\"\n [spaces]=\"spaceContexts\"\n [allowLeave]=\"true\"\n />\n </ion-list>\n } @else {\n <ion-list>\n <ion-item>\n <ion-spinner name=\"\" slot=\"start\" color=\"medium\" />\n <ion-buttons slot=\"start\">\n <ion-button disabled=\"disabled\" style=\"text-transform: none\"\n >{{ loadingState() }}...\n </ion-button>\n </ion-buttons>\n <ion-skeleton-text animated />\n </ion-item>\n </ion-list>\n }\n\n @if (showAdd()) {\n <ion-item [disabled]=\"adding()\">\n <ion-input\n (keyup.enter)=\"addSpace()\"\n #addTeamInput\n [ngModel]=\"spaceName()\"\n (ngModelChange)=\"spaceName.set($event)\"\n (keyup.escape)=\"cancelAdd()\"\n placeholder=\"New team name\"\n />\n <ion-buttons slot=\"end\">\n <ion-button color=\"primary\" fill=\"solid\" (click)=\"addSpace()\">\n <ion-label>Create</ion-label>\n </ion-button>\n @if (!!spaces()?.length) {\n <ion-button (click)=\"cancelAdd()\" color=\"medium\" title=\"Cancel\">\n @if (adding()) {\n <ion-spinner />\n } @else {\n <ion-icon name=\"close-outline\" />\n }\n </ion-button>\n }\n </ion-buttons>\n </ion-item>\n }\n @if (showAdd()) {\n <ion-card-content>\n <p>Enter team name and click \"Create\" button to add a new team.</p>\n </ion-card-content>\n }\n</ion-card>\n"]}
|
|
1
|
+
{"version":3,"file":"spaces-card.component.js","sourceRoot":"","sources":["../../../../../../../libs/space/components/src/lib/spaces-card/spaces-card.component.ts","../../../../../../../libs/space/components/src/lib/spaces-card/spaces-card.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n effect,\n inject,\n Input,\n signal,\n viewChild,\n} from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { FormsModule } from '@angular/forms';\nimport {\n IonButton,\n IonButtons,\n IonCard,\n IonCardContent,\n IonCardTitle,\n IonIcon,\n IonInput,\n IonItem,\n IonLabel,\n IonList,\n IonSkeletonText,\n IonSpinner,\n ToastController,\n} from '@ionic/angular';\nimport { AnalyticsService, IAnalyticsService } from '@sneat/core';\nimport { IUserSpaceBrief } from '@sneat/auth-models';\nimport { IIdAndBrief } from '@sneat/core';\nimport { ErrorLogger, IErrorLogger } from '@sneat/core';\nimport { ICreateSpaceRequest, ISpaceContext } from '@sneat/space-models';\nimport { GroupKind, SpaceType } from '@sneat/core';\nimport { SpaceNavService, SpaceService } from '@sneat/space-services';\nimport { SneatUserService } from '@sneat/auth-core';\nimport { SpacesListComponent } from '../spaces-list';\n\n// Signal-based + OnPush so the card repaints reactively when the user record\n// loads, instead of mutating fields inside an rxjs subscription and relying on\n// Zone change detection. The previous version stayed stuck on \"Authenticating...\"\n// when the Firestore onSnapshot update landed outside the Angular zone (the\n// record loaded but the view never repainted). toSignal()/computed() repaint\n// correctly under zone.js too — this is not a zoneless change.\n@Component({\n selector: 'sneat-spaces-card',\n templateUrl: './spaces-card.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [\n FormsModule,\n IonInput,\n IonCard,\n IonItem,\n IonLabel,\n IonCardTitle,\n IonButtons,\n IonButton,\n IonIcon,\n IonList,\n IonSpinner,\n IonSkeletonText,\n IonCardContent,\n SpacesListComponent,\n ],\n})\nexport class SpacesCardComponent {\n /** Card heading — a product names its spaces (\"Clubs\", \"Schools\"). */\n @Input() title = 'Spaces';\n\n /**\n * When set, only spaces of this type are listed. A Sneat account holds\n * spaces from every product (family, work, clubs...), and a product home\n * that lists them all reads as a generic space browser rather than the\n * product — sneat.club wants exactly the `club` spaces.\n */\n @Input() spaceType?: SpaceType;\n\n /** Narrows group spaces and tags new groups with a product-specific purpose. */\n @Input() groupKind?: GroupKind;\n\n /**\n * Whether the card offers its quick-add form. A product whose spaces are\n * created by a registration flow (decision 0006) passes false and offers\n * its own \"Register ...\" call to action instead.\n */\n @Input() canAdd = true;\n\n private readonly errorLogger = inject<IErrorLogger>(ErrorLogger);\n private readonly navService = inject(SpaceNavService);\n private readonly userService = inject(SneatUserService);\n private readonly spaceService = inject(SpaceService);\n private readonly analyticsService =\n inject<IAnalyticsService>(AnalyticsService);\n private readonly toastController = inject(ToastController);\n\n private readonly addSpaceInput = viewChild<IonInput>('addTeamInput');\n\n private readonly userState = toSignal(this.userService.userState);\n\n // undefined => user record not loaded yet (render the loading row).\n protected readonly spaces = computed<\n IIdAndBrief<IUserSpaceBrief>[] | undefined\n >(() => {\n const record = this.userState()?.record;\n if (!record) {\n return undefined;\n }\n return Object.entries(record.spaces ?? {})\n .map(([id, brief]) => ({ id, brief }))\n .filter(\n ({ brief }) =>\n (!this.spaceType || brief.type === this.spaceType) &&\n (!this.groupKind || brief.groupKind === this.groupKind),\n )\n .sort((a, b) => (a.brief.title > b.brief.title ? 1 : -1));\n });\n\n // Adapts the user's spaces to the shape SpacesListComponent renders, so the\n // card reuses that component (icon + title decode + navigation) instead of\n // duplicating the row markup.\n protected readonly spaceContexts = computed<ISpaceContext[] | undefined>(\n () =>\n this.spaces()?.map(({ id, brief }) => ({\n id,\n type: brief.type,\n brief: {\n title: brief.title,\n type: brief.type,\n groupKind: brief.groupKind,\n roles: brief.roles,\n },\n })),\n );\n\n protected get currentUserID(): string {\n return this.userService.currentUserID ?? '';\n }\n\n protected readonly loadingState = computed(() =>\n this.userState()?.status === 'authenticated' ? 'Loading' : 'Authenticating',\n );\n\n protected readonly showAdd = signal(false);\n protected readonly spaceName = signal('');\n protected readonly adding = signal(false);\n\n public constructor() {\n // Auto-open the \"add space\" form once we know the user has no spaces —\n // unless adding is disabled (a registration-flow product supplies its own\n // call to action).\n effect(() => {\n const spaces = this.spaces();\n if (spaces && spaces.length === 0 && this.canAdd) {\n this.startAddingSpace();\n }\n });\n }\n\n private navigateToSpace(space: ISpaceContext): void {\n this.navService\n .navigateToSpace(space, 'forward')\n .catch(this.errorLogger.logError);\n }\n\n protected addSpace(): void {\n this.analyticsService.logEvent('addSpace');\n const title = this.spaceName().trim();\n if (!title) {\n this.presentToast('Space name is required', 'tertiary');\n return;\n }\n if (this.spaces()?.find((t) => t.brief.title === title)) {\n this.presentToast('You already have a team with the same name', 'danger');\n return;\n }\n const request: ICreateSpaceRequest = {\n type: this.spaceType ?? 'team',\n groupKind: this.groupKind,\n title,\n };\n this.adding.set(true);\n this.spaceService.createSpace(request).subscribe({\n next: (space) => {\n this.analyticsService.logEvent('spaceCreated', { space: space.id });\n this.adding.set(false);\n this.spaceName.set('');\n // The user record updates via Firestore, which recomputes `spaces`.\n this.navigateToSpace(space);\n },\n error: (err) => {\n this.errorLogger.logError(err, 'Failed to create new team record');\n this.adding.set(false);\n },\n });\n }\n\n private presentToast(message: string, color: string): void {\n this.toastController\n .create({\n position: 'middle',\n message,\n color,\n duration: 5000,\n keyboardClose: true,\n buttons: [{ role: 'cancel', text: 'OK' }],\n })\n .then((toast) =>\n toast\n .present()\n .catch((err) =>\n this.errorLogger.logError(err, 'Failed to present toast'),\n ),\n )\n .catch((err) => this.errorLogger.logError(err, 'Failed to create toast'));\n }\n\n public startAddingSpace(): void {\n this.showAdd.set(true);\n setTimeout(() => {\n const input = this.addSpaceInput();\n if (!input) {\n return;\n }\n input\n .setFocus()\n .catch((err) =>\n this.errorLogger.logError(err, 'Failed to set focus to addTeamInput'),\n );\n }, 200);\n }\n\n protected cancelAdd(): void {\n this.showAdd.set(false);\n }\n}\n","<ion-card>\n <ion-item class=\"sneat-card-header\" lines=\"full\">\n <ion-label>\n <ion-card-title color=\"medium\">{{ title }}</ion-card-title>\n </ion-label>\n <ion-buttons slot=\"end\" (click)=\"startAddingSpace()\">\n @if (canAdd && !showAdd()) {\n <ion-button color=\"primary\">\n <ion-icon name=\"add\" slot=\"start\" />\n <ion-label>Add</ion-label>\n </ion-button>\n }\n </ion-buttons>\n </ion-item>\n @if (spaceContexts(); as spaceContexts) {\n <ion-list>\n <sneat-spaces-list\n [userID]=\"currentUserID\"\n [spaces]=\"spaceContexts\"\n [allowLeave]=\"true\"\n />\n </ion-list>\n } @else {\n <ion-list>\n <ion-item>\n <ion-spinner name=\"\" slot=\"start\" color=\"medium\" />\n <ion-buttons slot=\"start\">\n <ion-button disabled=\"disabled\" style=\"text-transform: none\"\n >{{ loadingState() }}...\n </ion-button>\n </ion-buttons>\n <ion-skeleton-text animated />\n </ion-item>\n </ion-list>\n }\n\n @if (showAdd()) {\n <ion-item [disabled]=\"adding()\">\n <ion-input\n (keyup.enter)=\"addSpace()\"\n #addTeamInput\n [ngModel]=\"spaceName()\"\n (ngModelChange)=\"spaceName.set($event)\"\n (keyup.escape)=\"cancelAdd()\"\n placeholder=\"New team name\"\n />\n <ion-buttons slot=\"end\">\n <ion-button color=\"primary\" fill=\"solid\" (click)=\"addSpace()\">\n <ion-label>Create</ion-label>\n </ion-button>\n @if (!!spaces()?.length) {\n <ion-button (click)=\"cancelAdd()\" color=\"medium\" title=\"Cancel\">\n @if (adding()) {\n <ion-spinner />\n } @else {\n <ion-icon name=\"close-outline\" />\n }\n </ion-button>\n }\n </ion-buttons>\n </ion-item>\n }\n @if (showAdd()) {\n <ion-card-content>\n <p>Enter team name and click \"Create\" button to add a new team.</p>\n </ion-card-content>\n }\n</ion-card>\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { IUserSpaceBrief } from '@sneat/auth-models';
|
|
2
2
|
import { IIdAndBrief } from '@sneat/core';
|
|
3
3
|
import { ISpaceContext } from '@sneat/space-models';
|
|
4
|
-
import { SpaceType } from '@sneat/core';
|
|
4
|
+
import { GroupKind, SpaceType } from '@sneat/core';
|
|
5
5
|
import * as i0 from "@angular/core";
|
|
6
6
|
export declare class SpacesCardComponent {
|
|
7
7
|
/** Card heading — a product names its spaces ("Clubs", "Schools"). */
|
|
@@ -13,6 +13,8 @@ export declare class SpacesCardComponent {
|
|
|
13
13
|
* product — sneat.club wants exactly the `club` spaces.
|
|
14
14
|
*/
|
|
15
15
|
spaceType?: SpaceType;
|
|
16
|
+
/** Narrows group spaces and tags new groups with a product-specific purpose. */
|
|
17
|
+
groupKind?: GroupKind;
|
|
16
18
|
/**
|
|
17
19
|
* Whether the card offers its quick-add form. A product whose spaces are
|
|
18
20
|
* created by a registration flow (decision 0006) passes false and offers
|
|
@@ -41,5 +43,5 @@ export declare class SpacesCardComponent {
|
|
|
41
43
|
startAddingSpace(): void;
|
|
42
44
|
protected cancelAdd(): void;
|
|
43
45
|
static ɵfac: i0.ɵɵFactoryDeclaration<SpacesCardComponent, never>;
|
|
44
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<SpacesCardComponent, "sneat-spaces-card", never, { "title": { "alias": "title"; "required": false; }; "spaceType": { "alias": "spaceType"; "required": false; }; "canAdd": { "alias": "canAdd"; "required": false; }; }, {}, never, never, true, never>;
|
|
46
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SpacesCardComponent, "sneat-spaces-card", never, { "title": { "alias": "title"; "required": false; }; "spaceType": { "alias": "spaceType"; "required": false; }; "groupKind": { "alias": "groupKind"; "required": false; }; "canAdd": { "alias": "canAdd"; "required": false; }; }, {}, never, never, true, never>;
|
|
45
47
|
}
|