dhomie-app 0.0.5 → 0.0.7

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 (133) hide show
  1. package/README.md +273 -250
  2. package/fesm2022/{dhomie-app-all-polls.page-X6CnLaDS.mjs → dhomie-app-all-polls.page-C4KixPzj.mjs} +6 -4
  3. package/fesm2022/dhomie-app-all-polls.page-C4KixPzj.mjs.map +1 -0
  4. package/fesm2022/{dhomie-app-all-polls.routes-CFeBf4Kg.mjs → dhomie-app-all-polls.routes-PQj6MzRg.mjs} +2 -2
  5. package/fesm2022/dhomie-app-all-polls.routes-PQj6MzRg.mjs.map +1 -0
  6. package/fesm2022/{dhomie-app-all-schedules.page-5PkaLPYK.mjs → dhomie-app-all-schedules.page-Ddzmws9b.mjs} +6 -4
  7. package/fesm2022/dhomie-app-all-schedules.page-Ddzmws9b.mjs.map +1 -0
  8. package/fesm2022/{dhomie-app-all-schedules.routes-BOn6nXxi.mjs → dhomie-app-all-schedules.routes-CIQfknrS.mjs} +2 -2
  9. package/fesm2022/dhomie-app-all-schedules.routes-CIQfknrS.mjs.map +1 -0
  10. package/fesm2022/{dhomie-app-apartment.page-BLXw6HpX.mjs → dhomie-app-apartment.page-C3iAYxmx.mjs} +13 -8
  11. package/fesm2022/dhomie-app-apartment.page-C3iAYxmx.mjs.map +1 -0
  12. package/fesm2022/dhomie-app-apartment.routes-BT-QQjCc.mjs +15 -0
  13. package/fesm2022/dhomie-app-apartment.routes-BT-QQjCc.mjs.map +1 -0
  14. package/fesm2022/{dhomie-app-apartment.service-Dt7ypzWP.mjs → dhomie-app-apartment.service-D80oK8mY.mjs} +2 -2
  15. package/fesm2022/dhomie-app-apartment.service-D80oK8mY.mjs.map +1 -0
  16. package/fesm2022/{dhomie-app-chat-create.page-BzEHFsuG.mjs → dhomie-app-chat-create.page-BlttwdJX.mjs} +9 -5
  17. package/fesm2022/dhomie-app-chat-create.page-BlttwdJX.mjs.map +1 -0
  18. package/fesm2022/{dhomie-app-chat.page-I_NppD6M.mjs → dhomie-app-chat.page-dc3ic19h.mjs} +11 -6
  19. package/fesm2022/dhomie-app-chat.page-dc3ic19h.mjs.map +1 -0
  20. package/fesm2022/dhomie-app-chat.routes-BKOlvP3Z.mjs +31 -0
  21. package/fesm2022/dhomie-app-chat.routes-BKOlvP3Z.mjs.map +1 -0
  22. package/fesm2022/dhomie-app-chat.service-XCaGObkk.mjs +48 -0
  23. package/fesm2022/dhomie-app-chat.service-XCaGObkk.mjs.map +1 -0
  24. package/fesm2022/{dhomie-app-construction-updates.page-Cu59baDz.mjs → dhomie-app-construction-updates.page-DWnUfrmq.mjs} +7 -5
  25. package/fesm2022/dhomie-app-construction-updates.page-DWnUfrmq.mjs.map +1 -0
  26. package/fesm2022/{dhomie-app-construction-updates.routes-BolqsuUv.mjs → dhomie-app-construction-updates.routes-DazakF3d.mjs} +2 -2
  27. package/fesm2022/dhomie-app-construction-updates.routes-DazakF3d.mjs.map +1 -0
  28. package/fesm2022/{dhomie-app-construction-updates.service-BElPwLY0.mjs → dhomie-app-construction-updates.service-La7hyNbH.mjs} +2 -2
  29. package/fesm2022/dhomie-app-construction-updates.service-La7hyNbH.mjs.map +1 -0
  30. package/fesm2022/dhomie-app-dhomie-app-D398ZdDA.mjs +2323 -0
  31. package/fesm2022/dhomie-app-dhomie-app-D398ZdDA.mjs.map +1 -0
  32. package/fesm2022/{dhomie-app-documents.page-C3DvisjR.mjs → dhomie-app-documents.page-D6Z-bBJU.mjs} +6 -4
  33. package/fesm2022/dhomie-app-documents.page-D6Z-bBJU.mjs.map +1 -0
  34. package/fesm2022/{dhomie-app-documents.routes-ChFhwRbQ.mjs → dhomie-app-documents.routes-mhJqJ15p.mjs} +2 -2
  35. package/fesm2022/dhomie-app-documents.routes-mhJqJ15p.mjs.map +1 -0
  36. package/fesm2022/dhomie-app-edit-profile.page-Di1u1zrr.mjs +119 -0
  37. package/fesm2022/dhomie-app-edit-profile.page-Di1u1zrr.mjs.map +1 -0
  38. package/fesm2022/{dhomie-app-edit-profile.routes-OYc7WVC5.mjs → dhomie-app-edit-profile.routes-rhqFhnp8.mjs} +2 -2
  39. package/fesm2022/dhomie-app-edit-profile.routes-rhqFhnp8.mjs.map +1 -0
  40. package/fesm2022/dhomie-app-icon.component-DS9P4GWG.mjs +281 -0
  41. package/fesm2022/dhomie-app-icon.component-DS9P4GWG.mjs.map +1 -0
  42. package/fesm2022/dhomie-app-index-opgBKfPr.mjs +637 -0
  43. package/fesm2022/dhomie-app-index-opgBKfPr.mjs.map +1 -0
  44. package/fesm2022/{dhomie-app-language-selection.page-C5FySCDO.mjs → dhomie-app-language-selection.page-BPzfoyYc.mjs} +6 -4
  45. package/fesm2022/dhomie-app-language-selection.page-BPzfoyYc.mjs.map +1 -0
  46. package/fesm2022/{dhomie-app-language-selection.routes-DqcyVIvn.mjs → dhomie-app-language-selection.routes-e7grvD6i.mjs} +2 -2
  47. package/fesm2022/dhomie-app-language-selection.routes-e7grvD6i.mjs.map +1 -0
  48. package/fesm2022/dhomie-app-layout-action.service-FCahocPH.mjs.map +1 -1
  49. package/fesm2022/dhomie-app-layout-title.service-sNCGba1f.mjs.map +1 -1
  50. package/fesm2022/dhomie-app-loading.component-BUk3sOe8.mjs +22 -0
  51. package/fesm2022/dhomie-app-loading.component-BUk3sOe8.mjs.map +1 -0
  52. package/fesm2022/{dhomie-app-main-layout.component-CpFuoMhF.mjs → dhomie-app-main-layout.component-YSEUIffq.mjs} +6 -4
  53. package/fesm2022/dhomie-app-main-layout.component-YSEUIffq.mjs.map +1 -0
  54. package/fesm2022/dhomie-app-new-request.page-DRT_t7aC.mjs +117 -0
  55. package/fesm2022/dhomie-app-new-request.page-DRT_t7aC.mjs.map +1 -0
  56. package/fesm2022/{dhomie-app-new-request.routes-BQPJ1W6X.mjs → dhomie-app-new-request.routes-VOyDSZP3.mjs} +2 -2
  57. package/fesm2022/dhomie-app-new-request.routes-VOyDSZP3.mjs.map +1 -0
  58. package/fesm2022/{dhomie-app-notifications.page-C9HeFpXt.mjs → dhomie-app-notifications.page-C5ulrqTq.mjs} +7 -4
  59. package/fesm2022/dhomie-app-notifications.page-C5ulrqTq.mjs.map +1 -0
  60. package/fesm2022/{dhomie-app-notifications.routes-CktjlrWD.mjs → dhomie-app-notifications.routes-nyJGUyAZ.mjs} +2 -2
  61. package/fesm2022/dhomie-app-notifications.routes-nyJGUyAZ.mjs.map +1 -0
  62. package/fesm2022/{dhomie-app-pdf-viewer.page-Cx56qfsm.mjs → dhomie-app-pdf-viewer.page-UpiOuN3L.mjs} +6 -4
  63. package/fesm2022/dhomie-app-pdf-viewer.page-UpiOuN3L.mjs.map +1 -0
  64. package/fesm2022/{dhomie-app-pdf-viewer.routes-jSkMeSRV.mjs → dhomie-app-pdf-viewer.routes-CWjKohDO.mjs} +2 -2
  65. package/fesm2022/dhomie-app-pdf-viewer.routes-CWjKohDO.mjs.map +1 -0
  66. package/fesm2022/dhomie-app-poll-card.component-BUyBeXNY.mjs +128 -0
  67. package/fesm2022/dhomie-app-poll-card.component-BUyBeXNY.mjs.map +1 -0
  68. package/fesm2022/dhomie-app-profile.page-BX8kXTDT.mjs +159 -0
  69. package/fesm2022/dhomie-app-profile.page-BX8kXTDT.mjs.map +1 -0
  70. package/fesm2022/dhomie-app-profile.routes-CA1sUclX.mjs +15 -0
  71. package/fesm2022/dhomie-app-profile.routes-CA1sUclX.mjs.map +1 -0
  72. package/fesm2022/dhomie-app-request-detail.page-DOxZ4oek.mjs +200 -0
  73. package/fesm2022/dhomie-app-request-detail.page-DOxZ4oek.mjs.map +1 -0
  74. package/fesm2022/{dhomie-app-request-detail.routes-BSsr7O-u.mjs → dhomie-app-request-detail.routes-CMe1mA7I.mjs} +2 -2
  75. package/fesm2022/dhomie-app-request-detail.routes-CMe1mA7I.mjs.map +1 -0
  76. package/fesm2022/{dhomie-app-requests.page-0tUAO39k.mjs → dhomie-app-requests.page-DkPF4jxY.mjs} +15 -11
  77. package/fesm2022/dhomie-app-requests.page-DkPF4jxY.mjs.map +1 -0
  78. package/fesm2022/dhomie-app-requests.routes-BetidN1k.mjs +17 -0
  79. package/fesm2022/dhomie-app-requests.routes-BetidN1k.mjs.map +1 -0
  80. package/fesm2022/{dhomie-app-requests.service-C4TCnstH.mjs → dhomie-app-requests.service-Boe4_VEq.mjs} +2 -2
  81. package/fesm2022/dhomie-app-requests.service-Boe4_VEq.mjs.map +1 -0
  82. package/fesm2022/dhomie-app-select.component-B8-K6prI.mjs +48 -0
  83. package/fesm2022/dhomie-app-select.component-B8-K6prI.mjs.map +1 -0
  84. package/fesm2022/dhomie-app-top-header.component-DqkZjgM1.mjs +92 -0
  85. package/fesm2022/dhomie-app-top-header.component-DqkZjgM1.mjs.map +1 -0
  86. package/fesm2022/dhomie-app.mjs +1 -1
  87. package/index.d.ts +6 -632
  88. package/package.json +6 -1
  89. package/styles/fonts/Montserratarm-Black.otf +0 -0
  90. package/styles/fonts/Montserratarm-Bold.otf +0 -0
  91. package/styles/fonts/Montserratarm-ExtraBold.otf +0 -0
  92. package/styles/fonts/Montserratarm-ExtraLight.otf +0 -0
  93. package/styles/fonts/Montserratarm-Light.otf +0 -0
  94. package/styles/fonts/Montserratarm-Medium.otf +0 -0
  95. package/styles/fonts/Montserratarm-Regular.otf +0 -0
  96. package/styles/fonts/Montserratarm-SemiBold.otf +0 -0
  97. package/styles/fonts/Montserratarm-Thin.otf +0 -0
  98. package/styles/index.css +2 -0
  99. package/fesm2022/dhomie-app-all-polls.page-X6CnLaDS.mjs.map +0 -1
  100. package/fesm2022/dhomie-app-all-polls.routes-CFeBf4Kg.mjs.map +0 -1
  101. package/fesm2022/dhomie-app-all-schedules.page-5PkaLPYK.mjs.map +0 -1
  102. package/fesm2022/dhomie-app-all-schedules.routes-BOn6nXxi.mjs.map +0 -1
  103. package/fesm2022/dhomie-app-apartment.page-BLXw6HpX.mjs.map +0 -1
  104. package/fesm2022/dhomie-app-apartment.service-Dt7ypzWP.mjs.map +0 -1
  105. package/fesm2022/dhomie-app-chat-create.page-BzEHFsuG.mjs.map +0 -1
  106. package/fesm2022/dhomie-app-chat.page-I_NppD6M.mjs.map +0 -1
  107. package/fesm2022/dhomie-app-construction-updates.page-Cu59baDz.mjs.map +0 -1
  108. package/fesm2022/dhomie-app-construction-updates.routes-BolqsuUv.mjs.map +0 -1
  109. package/fesm2022/dhomie-app-construction-updates.service-BElPwLY0.mjs.map +0 -1
  110. package/fesm2022/dhomie-app-dhomie-app-DZZOuYtS.mjs +0 -3612
  111. package/fesm2022/dhomie-app-dhomie-app-DZZOuYtS.mjs.map +0 -1
  112. package/fesm2022/dhomie-app-documents.page-C3DvisjR.mjs.map +0 -1
  113. package/fesm2022/dhomie-app-documents.routes-ChFhwRbQ.mjs.map +0 -1
  114. package/fesm2022/dhomie-app-edit-profile.page-3NQG_6xB.mjs +0 -116
  115. package/fesm2022/dhomie-app-edit-profile.page-3NQG_6xB.mjs.map +0 -1
  116. package/fesm2022/dhomie-app-edit-profile.routes-OYc7WVC5.mjs.map +0 -1
  117. package/fesm2022/dhomie-app-language-selection.page-C5FySCDO.mjs.map +0 -1
  118. package/fesm2022/dhomie-app-language-selection.routes-DqcyVIvn.mjs.map +0 -1
  119. package/fesm2022/dhomie-app-main-layout.component-CpFuoMhF.mjs.map +0 -1
  120. package/fesm2022/dhomie-app-new-request.page-MIFfwn6f.mjs +0 -114
  121. package/fesm2022/dhomie-app-new-request.page-MIFfwn6f.mjs.map +0 -1
  122. package/fesm2022/dhomie-app-new-request.routes-BQPJ1W6X.mjs.map +0 -1
  123. package/fesm2022/dhomie-app-notifications.page-C9HeFpXt.mjs.map +0 -1
  124. package/fesm2022/dhomie-app-notifications.routes-CktjlrWD.mjs.map +0 -1
  125. package/fesm2022/dhomie-app-pdf-viewer.page-Cx56qfsm.mjs.map +0 -1
  126. package/fesm2022/dhomie-app-pdf-viewer.routes-jSkMeSRV.mjs.map +0 -1
  127. package/fesm2022/dhomie-app-profile.page-BrNrFgJ6.mjs +0 -156
  128. package/fesm2022/dhomie-app-profile.page-BrNrFgJ6.mjs.map +0 -1
  129. package/fesm2022/dhomie-app-request-detail.page-YTIzcbbF.mjs +0 -197
  130. package/fesm2022/dhomie-app-request-detail.page-YTIzcbbF.mjs.map +0 -1
  131. package/fesm2022/dhomie-app-request-detail.routes-BSsr7O-u.mjs.map +0 -1
  132. package/fesm2022/dhomie-app-requests.page-0tUAO39k.mjs.map +0 -1
  133. package/fesm2022/dhomie-app-requests.service-C4TCnstH.mjs.map +0 -1
package/README.md CHANGED
@@ -1,250 +1,273 @@
1
- # dhomie-app
2
-
3
- A self-contained Angular micro-app library that handles OIDC authentication (PKCE) and the DHomie feature set. It works in both a Capacitor native shell (iOS / Android) and a plain web shell.
4
-
5
- The library ships with OIDC credentials and API URLs **baked in** at publish time no runtime configuration is required from the consumer.
6
-
7
- ---
8
-
9
- ## Table of Contents
10
-
11
- 1. [Requirements](#1-requirements)
12
- 2. [Installation](#2-installation)
13
- 3. [Register the micro route in the shell app](#3-register-the-micro-route-in-the-shell-app)
14
- 4. [Add the cold-start deep link handler (Capacitor only)](#4-add-the-cold-start-deep-link-handler-capacitor-only)
15
- 5. [Register the custom URL scheme (Capacitor native only)](#5-register-the-custom-url-scheme-capacitor-native-only)
16
- 6. [Making API requests inside micro components](#6-making-api-requests-inside-micro-components)
17
- 7. [How authentication works](#7-how-authentication-works)
18
- 8. [Baked-in configuration reference](#8-baked-in-configuration-reference)
19
- 9. [Publishing a new version](#9-publishing-a-new-version)
20
-
21
- ---
22
-
23
- ## 1. Requirements
24
-
25
- | Peer dependency | Version |
26
- |-------------------------|----------|
27
- | `@angular/common` | `^20.3.0` |
28
- | `@angular/core` | `^20.3.0` |
29
- | `@angular/router` | `^20.3.0` |
30
- | `@capacitor/app` | `^7.0.0` |
31
- | `@capacitor/browser` | `^7.0.0` |
32
- | `@capacitor/core` | `^7.0.0` |
33
- | `@capacitor/preferences`| `^7.0.0` |
34
- | `@ionic/angular` | `^8.0.0` |
35
- | `angular-oauth2-oidc` | `^20.0.0` |
36
-
37
- ---
38
-
39
- ## 2. Installation
40
-
41
- ```bash
42
- npm install dhomie-app
43
- ```
44
-
45
- Then install all peer dependencies that your shell app does not already have:
46
-
47
- ```bash
48
- npm install @angular/common @angular/core @angular/router \
49
- @capacitor/app @capacitor/browser @capacitor/core @capacitor/preferences \
50
- @ionic/angular angular-oauth2-oidc
51
- ```
52
-
53
- ---
54
-
55
- ## 3. Register the micro route in the shell app
56
-
57
- Add a lazy-loaded `micro` route that delegates to `MICRO_ROUTES`. The library self-registers all of its providers (HttpClient, OAuthService, storage, interceptors) inside a child `EnvironmentInjector` — no call to `provideMicroApp()` is needed in the shell.
58
-
59
- ```ts
60
- // src/app/app.routes.ts
61
- import { Routes } from '@angular/router';
62
- import { MICRO_ROUTES } from 'dhomie-app'; // ← named export from the library
63
-
64
- export const routes: Routes = [
65
- {
66
- path: 'home',
67
- loadComponent: () => import('./home/home.page').then((m) => m.HomePage),
68
- },
69
- {
70
- path: 'micro',
71
- loadChildren: () => import('dhomie-app').then((m) => m.MICRO_ROUTES),
72
- },
73
- {
74
- path: '',
75
- redirectTo: 'home',
76
- pathMatch: 'full',
77
- },
78
- ];
79
- ```
80
-
81
- That is all the routing setup you need. The library exposes these internal routes under `/micro`:
82
-
83
- | URL | Purpose |
84
- |---------------------|----------------------------------------------|
85
- | `/micro` | Redirects to `/micro/home` |
86
- | `/micro/home` | Main micro-app view (guarded — login required) |
87
- | `/micro/callback` | OAuth PKCE callback (no guard) |
88
-
89
- ---
90
-
91
- ## 4. Add the cold-start deep link handler (Capacitor only)
92
-
93
- When the OS kills the app while the in-app browser is open (cold-start scenario), the guard's `appUrlOpen` listener is gone. The shell's `AppComponent` must register its own listener to catch the returning callback URL and navigate to `/micro/callback`.
94
-
95
- ```ts
96
- // src/app/app.component.ts
97
- import { Component, inject, OnInit } from '@angular/core';
98
- import { Router } from '@angular/router';
99
- import { App } from '@capacitor/app';
100
- import { IonApp, IonRouterOutlet } from '@ionic/angular/standalone';
101
-
102
- @Component({
103
- selector: 'app-root',
104
- templateUrl: 'app.component.html',
105
- imports: [IonApp, IonRouterOutlet],
106
- })
107
- export class AppComponent implements OnInit {
108
- private readonly router = inject(Router);
109
-
110
- ngOnInit(): void {
111
- App.addListener('appUrlOpen', (event) => {
112
- // Only handle the DHomie custom scheme.
113
- if (!event.url.startsWith('thehoodapp://')) {
114
- return;
115
- }
116
-
117
- // Parse query params from the custom-scheme callback URL.
118
- // e.g. com.thehood.app://micro/callback?code=abc&state=xyz
119
- const callbackUrl = new URL(event.url);
120
- const queryParams: Record<string, string> = {};
121
- callbackUrl.searchParams.forEach((value, key) => {
122
- queryParams[key] = value;
123
- });
124
-
125
- this.router.navigate(['/micro/callback'], { queryParams });
126
- });
127
- }
128
- }
129
- ```
130
-
131
- > **Note on cold-start**: if the OS killed the app during the browser session, the PKCE `code_verifier` stored in RAM is lost. The callback component's error handler catches the failed token exchange and redirects to `/micro/home`, which restarts the login flow from scratch. This is the expected and accepted behavior for cold-start.
132
-
133
- ---
134
-
135
- ## 5. Register the custom URL scheme (Capacitor native only)
136
-
137
- The mobile OAuth redirect URI is `com.thehood.app://micro/callback`. You must register this scheme in both native projects.
138
-
139
- ### iOS — `ios/App/App/Info.plist`
140
-
141
- ```xml
142
- <key>CFBundleURLTypes</key>
143
- <array>
144
- <dict>
145
- <key>CFBundleURLSchemes</key>
146
- <array>
147
- <string>com.thehood.app</string>
148
- </array>
149
- </dict>
150
- </array>
151
- ```
152
-
153
- ### Android — `android/app/src/main/AndroidManifest.xml`
154
-
155
- Inside the main `<activity>` tag:
156
-
157
- ```xml
158
- <intent-filter>
159
- <action android:name="android.intent.action.VIEW" />
160
- <category android:name="android.intent.category.DEFAULT" />
161
- <category android:name="android.intent.category.BROWSABLE" />
162
- <data android:scheme="com.thehood.app" />
163
- </intent-filter>
164
- ```
165
-
166
- ---
167
-
168
- ## 6. Making API requests inside micro components
169
-
170
- The library provides its own child-scoped `HttpClient`. Use the `IS_MICRO_API` context token on every request so the micro interceptor can:
171
-
172
- - Prepend the API base URL to relative paths.
173
- - Attach the Bearer token.
174
- - Handle 401 errors with an automatic token refresh.
175
-
176
- ```ts
177
- import { HttpClient, HttpContext } from '@angular/common/http';
178
- import { inject } from '@angular/core';
179
- import { IS_MICRO_API } from 'dhomie-app';
180
-
181
- // Inside a micro component or service:
182
- const http = inject(HttpClient);
183
-
184
- http.get('/properties', {
185
- context: new HttpContext().set(IS_MICRO_API, true),
186
- });
187
- // → sends GET https://api.dhomie.com/v1/properties
188
- // with Authorization: Bearer <current_token>
189
- ```
190
-
191
- > Requests made **without** `IS_MICRO_API` (or with it set to `false`) pass through unchanged — parent-app interceptors are not involved in micro-app calls, and vice versa.
192
-
193
- ---
194
-
195
- ## 7. How authentication works
196
-
197
- The guard (`microAuthGuard`) runs before every protected route under `/micro/home`.
198
-
199
- **Step-by-step sequence:**
200
-
201
- 1. **Hydrate storage** — token data is loaded from `@capacitor/preferences` into RAM. This is memoized and instant on repeat navigations.
202
- 2. **Check token** — if `hasValidAccessToken()` returns `true`, navigation is allowed immediately.
203
- 3. **Load discovery document** — the OIDC `.well-known` endpoint is fetched once and cached.
204
- 4. **Platform split:**
205
- - **Native (Capacitor)** — generates a PKCE authorization URL, registers an `appUrlOpen` listener, then opens the URL in the system browser via `@capacitor/browser`. Returns `false` (blocks navigation).
206
- - **Web** — calls `initCodeFlow()` which redirects `window.location` to the authorization endpoint. Returns `false`.
207
- 5. **Callback** — when the auth server redirects back, `MicroCallbackComponent` (at `/micro/callback`) calls `tryLoginCodeFlow()`, exchanges the code for tokens, and stores them via `CapacitorOAuthStorage`.
208
- 6. **Token refresh** — `MicroRefreshSchedulerService` proactively refreshes tokens before expiry. `MicroTokenRefreshService` handles concurrent 401s with a shared refresh lock.
209
-
210
- ---
211
-
212
- ## 8. Baked-in configuration reference
213
-
214
- All OIDC credentials and API URLs are inlined into the published FESM bundle at build time. No runtime configuration is needed. The values correspond to `env.prod.ts`:
215
-
216
- | Field | Baked-in value |
217
- |-----------------------------|---------------------------------------------|
218
- | `oidc.issuer` | `https://auth.dhomie.com` |
219
- | `oidc.clientId` | `dhomie-micro-client` |
220
- | `oidc.scope` | `openid profile email offline_access` |
221
- | `oidc.redirectUri` (web) | `https://app.dhomie.com/micro/callback` |
222
- | `oidc.mobileRedirectUri` | `com.thehood.app://micro/callback` |
223
- | `oidc.responseType` | `code` (PKCE) |
224
- | `microApiBaseUrl` | `https://api.dhomie.com/v1` |
225
-
226
- ---
227
-
228
- ## 9. Publishing a new version
229
-
230
- Run the publish script from the **project root**:
231
-
232
- ```bash
233
- # Without 2FA OTP
234
- npm run publish:dhomie-app
235
-
236
- # With 2FA OTP
237
- npm run publish:dhomie-app -- 123456
238
- ```
239
-
240
- The script performs these steps automatically:
241
-
242
- | Step | Action |
243
- |------|--------|
244
- | 0 | Bumps the **patch** version in `projects/dhomie-app/package.json` |
245
- | 1 | Builds the library in production mode (`ng build dhomie-app --configuration production`) |
246
- | 2 | Inlines the `env.prod.ts` values into the FESM bundle (replaces the `dhomie-env` virtual import) |
247
- | 3 | Removes the stale source map and any `.npmrc` copied into `dist/dhomie-app` |
248
- | 4 | Publishes `./dist/dhomie-app` to npm with `--access public` |
249
-
250
- To bump the **minor** or **major** version instead, edit `projects/dhomie-app/package.json` manually before running the script.
1
+ # dhomie-app
2
+
3
+ A self-contained Angular library that delivers the full DHomie feature set — OIDC authentication (PKCE), apartment management, chat, documents, schedules, and more. Works in both a Capacitor native shell (iOS / Android) and a plain Angular web app.
4
+
5
+ The consumer supplies a `MicroEnv` configuration object at route registration time. The library self-provisions all providers (OAuth, HTTP interceptor, SignalR, storage, i18n, fonts) inside a child `EnvironmentInjector` nothing leaks into the host application.
6
+
7
+ ---
8
+
9
+ ## Table of Contents
10
+
11
+ 1. [Requirements](#1-requirements)
12
+ 2. [Installation](#2-installation)
13
+ 3. [Styles setup](#3-styles-setup)
14
+ 4. [Configure the environment](#4-configure-the-environment)
15
+ 5. [Register routes in the host app](#5-register-routes-in-the-host-app)
16
+ 6. [Trigger login](#6-trigger-login)
17
+ 7. [Register the custom URL scheme (native only)](#7-register-the-custom-url-scheme-native-only)
18
+ 8. [How authentication works](#8-how-authentication-works)
19
+ 9. [Available routes](#9-available-routes)
20
+ 10. [Publishing a new version](#10-publishing-a-new-version)
21
+
22
+ ---
23
+
24
+ ## 1. Requirements
25
+
26
+ | Peer dependency | Version |
27
+ |----------------------------------|-----------|
28
+ | `@angular/common` | `^20.3.0` |
29
+ | `@angular/core` | `^20.3.0` |
30
+ | `@angular/forms` | `^20.3.0` |
31
+ | `@angular/platform-browser` | `^20.3.0` |
32
+ | `@angular/router` | `^20.3.0` |
33
+ | `@capacitor/app` | `^7.0.0` |
34
+ | `@capacitor/browser` | `^7.0.0` |
35
+ | `@capacitor/core` | `^7.0.0` |
36
+ | `@capacitor/device` | `^7.0.0` |
37
+ | `@capacitor/preferences` | `^7.0.0` |
38
+ | `@capacitor/push-notifications` | `^7.0.0` |
39
+ | `@ionic/angular` | `^8.0.0` |
40
+ | `@microsoft/signalr` | `^8.0.0` |
41
+ | `@ngx-translate/core` | `^17.0.0` |
42
+ | `angular-oauth2-oidc` | `^20.0.0` |
43
+
44
+ ---
45
+
46
+ ## 2. Installation
47
+
48
+ ```bash
49
+ npm install dhomie-app
50
+ ```
51
+
52
+ Install any peer dependencies your host app does not already have:
53
+
54
+ ```bash
55
+ npm install \
56
+ @capacitor/app @capacitor/browser @capacitor/core \
57
+ @capacitor/device @capacitor/preferences @capacitor/push-notifications \
58
+ @ionic/angular @microsoft/signalr @ngx-translate/core angular-oauth2-oidc
59
+ ```
60
+
61
+ For native platforms, sync after installation:
62
+
63
+ ```bash
64
+ npx cap sync
65
+ ```
66
+
67
+ ---
68
+
69
+ ## 3. Styles setup
70
+
71
+ The library ships pre-compiled CSS (Tailwind utilities + design tokens + fonts) inside the npm package. Add one line to your `angular.json`:
72
+
73
+ ```json
74
+ "styles": [
75
+ "src/global.scss",
76
+ "node_modules/dhomie-app/styles/index.css"
77
+ ]
78
+ ```
79
+
80
+ No Tailwind installation, no PostCSS config, and no `@font-face` declarations are needed in the host app. The `Montserrat arm` font is bundled inside `node_modules/dhomie-app/styles/fonts/`.
81
+
82
+ ---
83
+
84
+ ## 4. Configure the environment
85
+
86
+ Create an environment file that implements `MicroEnv`:
87
+
88
+ ```ts
89
+ // src/environments/environment.ts
90
+ import type { MicroEnv } from 'dhomie-app';
91
+
92
+ export const environment: MicroEnv = {
93
+ production: false,
94
+ oidc: {
95
+ issuer: 'https://your-oidc-server.com',
96
+ clientId: 'web',
97
+ scope: 'openid profile offline_access app',
98
+ redirectUri: 'https://localhost:3001/login-callback', // web OAuth callback
99
+ responseType: 'code',
100
+ showDebugInformation: true,
101
+ useSilentRefresh: false,
102
+ android: {
103
+ clientId: 'android',
104
+ redirectUri: 'com.your.app:/callback',
105
+ },
106
+ ios: {
107
+ clientId: 'ios',
108
+ redirectUri: 'yourapp:/callback',
109
+ },
110
+ },
111
+ microApiBaseUrl: 'https://your-api.com',
112
+ routes: {
113
+ homePath: 'app', // authenticated shell: /microapp/app/...
114
+ callbackPath: 'login-callback', // OAuth callback: /microapp/login-callback
115
+ },
116
+ defaultLanguage: 'am', // 'am' | 'en' | 'ru'
117
+ };
118
+ ```
119
+
120
+ Create a matching production file at `src/environments/environment.prod.ts` with `production: true` and your production URLs.
121
+
122
+ ---
123
+
124
+ ## 5. Register routes in the host app
125
+
126
+ Two routes are required: one for the web OAuth callback and one that lazy-loads the library.
127
+
128
+ ```ts
129
+ // src/app/app.routes.ts
130
+ import { Routes } from '@angular/router';
131
+ import { OAuthCallbackComponent } from 'dhomie-app';
132
+ import { environment } from '../environments/environment';
133
+
134
+ export const routes: Routes = [
135
+ {
136
+ path: '',
137
+ loadComponent: () => import('./login/login.page').then(m => m.LoginPage),
138
+ },
139
+ {
140
+ // Web OAuth: the IdP redirects here, then this component forwards
141
+ // query params to the library's internal callback route.
142
+ path: 'login-callback',
143
+ component: OAuthCallbackComponent,
144
+ data: { callbackRoute: `/microapp/${environment.routes.callbackPath}` },
145
+ },
146
+ {
147
+ // All library routes + providers are mounted here.
148
+ path: 'microapp',
149
+ loadChildren: () => import('dhomie-app').then(m => m.MICRO_APP_ROUTES(environment)),
150
+ },
151
+ ];
152
+ ```
153
+
154
+ `MICRO_APP_ROUTES(environment)` returns the full route tree and registers all library providers inside a scoped child injector — no `providers` array changes are needed in the host `AppModule` or `bootstrapApplication`.
155
+
156
+ ---
157
+
158
+ ## 6. Trigger login
159
+
160
+ Navigate to the library mount path from anywhere in the host app:
161
+
162
+ ```ts
163
+ this.router.navigate(['/microapp']);
164
+ ```
165
+
166
+ The library's guard handles the rest automatically:
167
+
168
+ - **Web** — redirects `window.location` to the OIDC authorization endpoint.
169
+ - **Native (iOS / Android)** — opens the system browser via `@capacitor/browser`, then listens for the `appUrlOpen` deep link.
170
+
171
+ ---
172
+
173
+ ## 7. Register the custom URL scheme (native only)
174
+
175
+ The native OAuth callback uses a custom URL scheme. Register it in both native projects.
176
+
177
+ ### iOS `ios/App/App/Info.plist`
178
+
179
+ ```xml
180
+ <key>CFBundleURLTypes</key>
181
+ <array>
182
+ <dict>
183
+ <key>CFBundleURLSchemes</key>
184
+ <array>
185
+ <string>yourapp</string>
186
+ </array>
187
+ </dict>
188
+ </array>
189
+ ```
190
+
191
+ ### Android `android/app/src/main/AndroidManifest.xml`
192
+
193
+ Inside the main `<activity>` tag:
194
+
195
+ ```xml
196
+ <intent-filter>
197
+ <action android:name="android.intent.action.VIEW" />
198
+ <category android:name="android.intent.category.DEFAULT" />
199
+ <category android:name="android.intent.category.BROWSABLE" />
200
+ <data android:scheme="com.your.app" />
201
+ </intent-filter>
202
+ ```
203
+
204
+ Replace the scheme with the value you set in `oidc.android.redirectUri` and `oidc.ios.redirectUri`.
205
+
206
+ ---
207
+
208
+ ## 8. How authentication works
209
+
210
+ The `microAuthGuard` runs before every protected route under `/microapp/app`.
211
+
212
+ | Step | What happens |
213
+ |------|--------------|
214
+ | 1 | **Hydrate storage** tokens are loaded from `@capacitor/preferences` into RAM (memoized). |
215
+ | 2 | **Check token** — if `hasValidAccessToken()` is true, navigation proceeds immediately. |
216
+ | 3 | **Load discovery document** — fetched once from `oidc.issuer/.well-known/openid-configuration`. |
217
+ | 4a | **Native** — generates PKCE URL → opens `@capacitor/browser` → listens for `appUrlOpen`. |
218
+ | 4b | **Web** — calls `initCodeFlow()` → browser redirects to the IdP authorization page. |
219
+ | 5 | **Callback** — `OAuthCallbackComponent` (web) or `appUrlOpen` (native) delivers `code` + `state` to `/microapp/login-callback`, which exchanges them for tokens. |
220
+ | 6 | **Token lifecycle** proactive refresh 60 s before expiry; concurrent 401s share a single refresh lock. |
221
+
222
+ > **Cold-start edge case (native):** if the OS kills the app while the browser is open, the PKCE `code_verifier` in RAM is lost. The callback detects this and restarts the login flow from scratch. This is the accepted behavior — synchronous secure storage is not available on Capacitor.
223
+
224
+ ---
225
+
226
+ ## 9. Available routes
227
+
228
+ After a successful login the library serves these routes under `/microapp/app/`:
229
+
230
+ | Route | Feature |
231
+ |-------------------------------|--------------------------|
232
+ | `apartment` | Apartment overview |
233
+ | `chat` | Real-time chat |
234
+ | `requests` | Service requests list |
235
+ | `new-request` | Create a request |
236
+ | `request-detail` | Request details |
237
+ | `all-schedules` | Payment schedules |
238
+ | `all-polls` | Community polls |
239
+ | `documents` | Document library |
240
+ | `construction-updates` | Construction news |
241
+ | `notifications` | Push notifications list |
242
+ | `profile` | User profile |
243
+ | `edit-profile` | Edit profile |
244
+ | `language-selection` | Change app language |
245
+ | `pdf-viewer` | In-app PDF viewer |
246
+
247
+ ---
248
+
249
+ ## 10. Publishing a new version
250
+
251
+ Run from the **project root**:
252
+
253
+ ```bash
254
+ # Without 2FA OTP
255
+ npm run publish:dhomie-app
256
+
257
+ # With 2FA OTP
258
+ npm run publish:dhomie-app -- 123456
259
+ ```
260
+
261
+ The script runs these steps automatically:
262
+
263
+ | Step | Action |
264
+ |------|--------|
265
+ | 0 | Bumps the **patch** version in `projects/dhomie-app/package.json` |
266
+ | 1 | Builds the library (`ng build dhomie-app --configuration production`) |
267
+ | 1b | Compiles Tailwind CSS → `dist/dhomie-app/styles/index.css` |
268
+ | 1c | Copies fonts → `dist/dhomie-app/styles/fonts/` |
269
+ | 2 | _(no-op)_ Environment is injected at runtime via `MICRO_ENV` DI token |
270
+ | 3 | Removes stale source map and `.npmrc` from `dist/dhomie-app` |
271
+ | 4 | Publishes `./dist/dhomie-app` to npm with `--access public` |
272
+
273
+ To bump **minor** or **major** version, edit `projects/dhomie-app/package.json` manually before running the script.
@@ -5,7 +5,9 @@ import { ActivatedRoute } from '@angular/router';
5
5
  import { IonContent, IonInfiniteScroll, IonInfiniteScrollContent } from '@ionic/angular/standalone';
6
6
  import { TranslatePipe } from '@ngx-translate/core';
7
7
  import { catchError, of, map, switchMap } from 'rxjs';
8
- import { q as AllPollsService, P as PollNotificationService, w as DhPollCardComponent, o as DhLoadingComponent } from './dhomie-app-dhomie-app-DZZOuYtS.mjs';
8
+ import { D as DhLoadingComponent } from './dhomie-app-loading.component-BUk3sOe8.mjs';
9
+ import { a as DhPollCardComponent } from './dhomie-app-poll-card.component-BUyBeXNY.mjs';
10
+ import { d as AllPollsService, P as PollNotificationService } from './dhomie-app-dhomie-app-D398ZdDA.mjs';
9
11
 
10
12
  const PAGE_SIZE = 20;
11
13
  class AllPollsPage {
@@ -107,7 +109,7 @@ class AllPollsPage {
107
109
  return state?.polls ?? null;
108
110
  }
109
111
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AllPollsPage, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
110
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: AllPollsPage, isStandalone: true, selector: "dh-all-polls-page", ngImport: i0, template: "<dh-loading [isOpen]=\"isLoading()\" />\r\n\r\n<ion-content [fullscreen]=\"true\" class=\"dh-all-polls-content\">\r\n <div class=\"dh-all-polls-container\">\r\n @if (hasError()) {\r\n <p class=\"dh-all-polls-state-text\">{{ 'states.loadFailed' | translate }}</p>\r\n } @else if (polls().length > 0) {\r\n @for (poll of polls(); track poll.id) {\r\n <dh-poll-card\r\n [poll]=\"poll\"\r\n (vote)=\"onVote(poll.id, $event)\"\r\n (changeAnswer)=\"onChangeAnswer(poll.id)\"\r\n ></dh-poll-card>\r\n }\r\n } @else {\r\n <p class=\"dh-all-polls-state-text\">{{ 'polls.empty' | translate }}</p>\r\n }\r\n </div>\r\n\r\n <ion-infinite-scroll [disabled]=\"!hasMore()\" (ionInfinite)=\"onIonInfinite($event)\">\r\n <ion-infinite-scroll-content />\r\n </ion-infinite-scroll>\r\n</ion-content>\r\n", dependencies: [{ kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonInfiniteScroll, selector: "ion-infinite-scroll", inputs: ["disabled", "position", "threshold"] }, { kind: "component", type: IonInfiniteScrollContent, selector: "ion-infinite-scroll-content", inputs: ["loadingSpinner", "loadingText"] }, { kind: "component", type: DhPollCardComponent, selector: "dh-poll-card", inputs: ["poll"], outputs: ["vote", "changeAnswer"] }, { kind: "component", type: DhLoadingComponent, selector: "dh-loading", inputs: ["trigger", "isOpen", "duration", "spinner", "inline"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
112
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: AllPollsPage, isStandalone: true, selector: "dh-all-polls-page", ngImport: i0, template: "<dh-loading [isOpen]=\"isLoading()\" />\n\n<ion-content [fullscreen]=\"true\" class=\"dh-all-polls-content\">\n <div class=\"dh-all-polls-container\">\n @if (hasError()) {\n <p class=\"dh-all-polls-state-text\">{{ 'states.loadFailed' | translate }}</p>\n } @else if (polls().length > 0) {\n @for (poll of polls(); track poll.id) {\n <dh-poll-card\n [poll]=\"poll\"\n (vote)=\"onVote(poll.id, $event)\"\n (changeAnswer)=\"onChangeAnswer(poll.id)\"\n ></dh-poll-card>\n }\n } @else {\n <p class=\"dh-all-polls-state-text\">{{ 'polls.empty' | translate }}</p>\n }\n </div>\n\n <ion-infinite-scroll [disabled]=\"!hasMore()\" (ionInfinite)=\"onIonInfinite($event)\">\n <ion-infinite-scroll-content />\n </ion-infinite-scroll>\n</ion-content>\n", dependencies: [{ kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonInfiniteScroll, selector: "ion-infinite-scroll", inputs: ["disabled", "position", "threshold"] }, { kind: "component", type: IonInfiniteScrollContent, selector: "ion-infinite-scroll-content", inputs: ["loadingSpinner", "loadingText"] }, { kind: "component", type: DhPollCardComponent, selector: "dh-poll-card", inputs: ["poll"], outputs: ["vote", "changeAnswer"] }, { kind: "component", type: DhLoadingComponent, selector: "dh-loading", inputs: ["trigger", "isOpen", "duration", "spinner", "inline"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
111
113
  }
112
114
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AllPollsPage, decorators: [{
113
115
  type: Component,
@@ -118,8 +120,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
118
120
  TranslatePipe,
119
121
  DhPollCardComponent,
120
122
  DhLoadingComponent,
121
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<dh-loading [isOpen]=\"isLoading()\" />\r\n\r\n<ion-content [fullscreen]=\"true\" class=\"dh-all-polls-content\">\r\n <div class=\"dh-all-polls-container\">\r\n @if (hasError()) {\r\n <p class=\"dh-all-polls-state-text\">{{ 'states.loadFailed' | translate }}</p>\r\n } @else if (polls().length > 0) {\r\n @for (poll of polls(); track poll.id) {\r\n <dh-poll-card\r\n [poll]=\"poll\"\r\n (vote)=\"onVote(poll.id, $event)\"\r\n (changeAnswer)=\"onChangeAnswer(poll.id)\"\r\n ></dh-poll-card>\r\n }\r\n } @else {\r\n <p class=\"dh-all-polls-state-text\">{{ 'polls.empty' | translate }}</p>\r\n }\r\n </div>\r\n\r\n <ion-infinite-scroll [disabled]=\"!hasMore()\" (ionInfinite)=\"onIonInfinite($event)\">\r\n <ion-infinite-scroll-content />\r\n </ion-infinite-scroll>\r\n</ion-content>\r\n" }]
123
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<dh-loading [isOpen]=\"isLoading()\" />\n\n<ion-content [fullscreen]=\"true\" class=\"dh-all-polls-content\">\n <div class=\"dh-all-polls-container\">\n @if (hasError()) {\n <p class=\"dh-all-polls-state-text\">{{ 'states.loadFailed' | translate }}</p>\n } @else if (polls().length > 0) {\n @for (poll of polls(); track poll.id) {\n <dh-poll-card\n [poll]=\"poll\"\n (vote)=\"onVote(poll.id, $event)\"\n (changeAnswer)=\"onChangeAnswer(poll.id)\"\n ></dh-poll-card>\n }\n } @else {\n <p class=\"dh-all-polls-state-text\">{{ 'polls.empty' | translate }}</p>\n }\n </div>\n\n <ion-infinite-scroll [disabled]=\"!hasMore()\" (ionInfinite)=\"onIonInfinite($event)\">\n <ion-infinite-scroll-content />\n </ion-infinite-scroll>\n</ion-content>\n" }]
122
124
  }] });
123
125
 
124
126
  export { AllPollsPage };
125
- //# sourceMappingURL=dhomie-app-all-polls.page-X6CnLaDS.mjs.map
127
+ //# sourceMappingURL=dhomie-app-all-polls.page-C4KixPzj.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dhomie-app-all-polls.page-C4KixPzj.mjs","sources":["../../../projects/dhomie-app/src/lib/pages/all-polls/all-polls.page.ts","../../../projects/dhomie-app/src/lib/pages/all-polls/all-polls.page.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n DestroyRef,\n inject,\n OnInit,\n signal,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { ActivatedRoute } from '@angular/router';\nimport {\n IonContent,\n IonInfiniteScroll,\n IonInfiniteScrollContent,\n InfiniteScrollCustomEvent,\n} from '@ionic/angular/standalone';\nimport { TranslatePipe } from '@ngx-translate/core';\nimport { catchError, map, of, switchMap } from 'rxjs';\nimport { DhLoadingComponent } from '../../components/loading/loading.component';\nimport { DhPollCardComponent } from '../../components/poll-card/poll-card.component';\nimport { DhPoll } from '../../components/poll-card/poll-card.model';\nimport { PollNotificationService } from '../../shared/signalr/poll-notification.service';\nimport { AllPollsNavigationState } from './all-polls.models';\nimport { AllPollsService } from './all-polls.service';\n\nconst PAGE_SIZE = 20;\n\n@Component({\n selector: 'dh-all-polls-page',\n standalone: true,\n imports: [\n IonContent,\n IonInfiniteScroll,\n IonInfiniteScrollContent,\n TranslatePipe,\n DhPollCardComponent,\n DhLoadingComponent,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './all-polls.page.html',\n})\nexport class AllPollsPage implements OnInit {\n private readonly activatedRoute = inject(ActivatedRoute);\n private readonly destroyRef = inject(DestroyRef);\n private readonly allPollsService = inject(AllPollsService);\n private readonly pollNotification = inject(PollNotificationService);\n private readonly currentCommunityId = signal('');\n\n readonly polls = signal<DhPoll[]>(this.readSeedPolls() ?? []);\n readonly status = signal<'idle' | 'loading' | 'success' | 'error'>(\n this.polls().length ? 'success' : 'idle',\n );\n readonly isLoadingMore = signal(false);\n readonly hasMore = signal(false);\n\n readonly isLoading = computed(() => this.status() === 'loading');\n readonly hasError = computed(() => this.status() === 'error');\n readonly hasCommunityContext = computed(() => !!this.currentCommunityId());\n\n onVote(pollId: string, optionId: string): void {\n const communityId = this.currentCommunityId();\n if (!communityId) return;\n\n this.allPollsService\n .submitVote(pollId, optionId, communityId)\n .pipe(\n catchError(() => of(null)),\n takeUntilDestroyed(this.destroyRef),\n )\n .subscribe((result) => {\n if (result !== null) {\n this.polls.update((items) =>\n this.allPollsService.vote(items, pollId, optionId),\n );\n }\n });\n }\n\n onChangeAnswer(pollId: string): void {\n this.polls.update((items) => this.allPollsService.resetVote(items, pollId));\n }\n\n onIonInfinite(event: InfiniteScrollCustomEvent): void {\n const communityId = this.currentCommunityId();\n if (!communityId || this.isLoadingMore()) return;\n\n this.isLoadingMore.set(true);\n\n this.allPollsService\n .getPolls(communityId, { skip: this.polls().length, take: PAGE_SIZE })\n .pipe(\n catchError(() => of<DhPoll[]>([])),\n takeUntilDestroyed(this.destroyRef),\n )\n .subscribe((newPolls) => {\n this.polls.update((current) => [...current, ...newPolls]);\n this.hasMore.set(newPolls.length === PAGE_SIZE);\n this.isLoadingMore.set(false);\n event.target.complete();\n });\n }\n\n ngOnInit(): void {\n this.listenForNewPolls();\n\n this.activatedRoute.queryParamMap\n .pipe(\n map((params) => params.get('communityId') ?? ''),\n switchMap((communityId) => {\n this.currentCommunityId.set(communityId);\n\n if (!communityId) {\n this.status.set('idle');\n return of<DhPoll[] | null>(null);\n }\n\n this.status.set('loading');\n return this.allPollsService\n .getPolls(communityId, { skip: 0, take: PAGE_SIZE })\n .pipe(\n catchError(() => {\n this.status.set('error');\n return of<DhPoll[]>([]);\n }),\n );\n }),\n takeUntilDestroyed(this.destroyRef),\n )\n .subscribe((polls) => {\n if (polls === null) {\n this.polls.set([]);\n this.hasMore.set(false);\n return;\n }\n\n this.polls.set(polls);\n this.hasMore.set(polls.length === PAGE_SIZE);\n if (this.status() !== 'error') {\n this.status.set('success');\n }\n });\n }\n\n private listenForNewPolls(): void {\n this.pollNotification.pollCreated$\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(({ pollId }) => {\n const communityId = this.currentCommunityId();\n if (!communityId || !pollId) {\n return;\n }\n\n this.allPollsService\n .getPoll(communityId, pollId)\n .pipe(\n catchError(() => of(null)),\n takeUntilDestroyed(this.destroyRef),\n )\n .subscribe((poll) => {\n if (poll) {\n this.polls.update((current) => [poll, ...current]);\n }\n });\n });\n }\n\n private readSeedPolls(): DhPoll[] | null {\n const state = history.state as AllPollsNavigationState | null;\n return state?.polls ?? null;\n }\n}\n","<dh-loading [isOpen]=\"isLoading()\" />\n\n<ion-content [fullscreen]=\"true\" class=\"dh-all-polls-content\">\n <div class=\"dh-all-polls-container\">\n @if (hasError()) {\n <p class=\"dh-all-polls-state-text\">{{ 'states.loadFailed' | translate }}</p>\n } @else if (polls().length > 0) {\n @for (poll of polls(); track poll.id) {\n <dh-poll-card\n [poll]=\"poll\"\n (vote)=\"onVote(poll.id, $event)\"\n (changeAnswer)=\"onChangeAnswer(poll.id)\"\n ></dh-poll-card>\n }\n } @else {\n <p class=\"dh-all-polls-state-text\">{{ 'polls.empty' | translate }}</p>\n }\n </div>\n\n <ion-infinite-scroll [disabled]=\"!hasMore()\" (ionInfinite)=\"onIonInfinite($event)\">\n <ion-infinite-scroll-content />\n </ion-infinite-scroll>\n</ion-content>\n"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  const ALL_POLLS_ROUTES = [
2
2
  {
3
3
  path: '',
4
- loadComponent: () => import('./dhomie-app-all-polls.page-X6CnLaDS.mjs').then((m) => m.AllPollsPage),
4
+ loadComponent: () => import('./dhomie-app-all-polls.page-C4KixPzj.mjs').then((m) => m.AllPollsPage),
5
5
  data: {
6
6
  layout: {
7
7
  hideHeader: false,
@@ -15,4 +15,4 @@ const ALL_POLLS_ROUTES = [
15
15
  ];
16
16
 
17
17
  export { ALL_POLLS_ROUTES };
18
- //# sourceMappingURL=dhomie-app-all-polls.routes-CFeBf4Kg.mjs.map
18
+ //# sourceMappingURL=dhomie-app-all-polls.routes-PQj6MzRg.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dhomie-app-all-polls.routes-PQj6MzRg.mjs","sources":["../../../projects/dhomie-app/src/lib/pages/all-polls/all-polls.routes.ts"],"sourcesContent":["import { Routes } from '@angular/router';\nimport { LayoutConfig } from '../../layout/main-layout/layout-config.model';\n\nexport const ALL_POLLS_ROUTES: Routes = [\n {\n path: '',\n loadComponent: () => import('./all-polls.page').then((m) => m.AllPollsPage),\n data: {\n layout: {\n hideHeader: false,\n hideBottomNav: true,\n hideToolbar: true,\n showBack: true,\n title: 'polls.title',\n } satisfies LayoutConfig,\n },\n },\n];\n"],"names":[],"mappings":"AAGO,MAAM,gBAAgB,GAAW;AACtC,IAAA;AACE,QAAA,IAAI,EAAE,EAAE;AACR,QAAA,aAAa,EAAE,MAAM,OAAO,0CAAkB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC;AAC3E,QAAA,IAAI,EAAE;AACJ,YAAA,MAAM,EAAE;AACN,gBAAA,UAAU,EAAE,KAAK;AACjB,gBAAA,aAAa,EAAE,IAAI;AACnB,gBAAA,WAAW,EAAE,IAAI;AACjB,gBAAA,QAAQ,EAAE,IAAI;AACd,gBAAA,KAAK,EAAE,aAAa;AACE,aAAA;AACzB,SAAA;AACF,KAAA;;;;;"}