@propbinder/mobile-design 0.2.47 → 0.2.50

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 (221) hide show
  1. package/ng-package.json +24 -0
  2. package/package.json +3 -39
  3. package/src/animations/page-transitions.ts +165 -0
  4. package/src/assets/fonts/brockmann-mediumitalic-webfont.woff2 +0 -0
  5. package/src/assets/fonts/brockmann-regularitalic-webfont.woff2 +0 -0
  6. package/src/assets/fonts/brockmann-semibolditalic-webfont.woff2 +0 -0
  7. package/src/components/action-list-item/ds-mobile-action-list-item.ts +102 -0
  8. package/src/components/action-list-item/index.ts +2 -0
  9. package/src/components/app-icon/ds-app-icon.ts +133 -0
  10. package/src/components/app-icon/index.ts +2 -0
  11. package/src/components/attachment-preview/ds-mobile-attachment-preview.css +139 -0
  12. package/src/components/attachment-preview/ds-mobile-attachment-preview.ts +164 -0
  13. package/src/components/attachment-preview/index.ts +1 -0
  14. package/src/components/avatar-with-badge/ds-avatar-with-badge.ts +142 -0
  15. package/src/components/avatar-with-badge/index.ts +2 -0
  16. package/src/components/booking-modal/ds-mobile-booking-confirmation-wrapper.ts +71 -0
  17. package/src/components/booking-modal/ds-mobile-booking-modal.service.ts +121 -0
  18. package/src/components/booking-modal/ds-mobile-booking-modal.ts +598 -0
  19. package/src/components/booking-modal/ds-mobile-booking-summary.ts +161 -0
  20. package/src/components/booking-modal/index.ts +4 -0
  21. package/src/components/bottom-sheet/ds-mobile-actions-bottom-sheet.ts +266 -0
  22. package/src/components/bottom-sheet/ds-mobile-bottom-sheet-header.ts +146 -0
  23. package/src/components/bottom-sheet/ds-mobile-bottom-sheet-wrapper.ts +156 -0
  24. package/src/components/bottom-sheet/ds-mobile-bottom-sheet.css +101 -0
  25. package/src/components/bottom-sheet/ds-mobile-bottom-sheet.service.ts +169 -0
  26. package/src/components/bottom-sheet/ds-mobile-confirmation-sheet.ts +211 -0
  27. package/src/components/bottom-sheet/ds-mobile-post-create-bottom-sheet.ts +578 -0
  28. package/src/components/bottom-sheet/ds-mobile-profile-actions-sheet.ts +614 -0
  29. package/src/components/bottom-sheet/index.ts +8 -0
  30. package/src/components/bottom-sheet/modal-shadow-fix.ts +42 -0
  31. package/src/components/card-inline/ds-mobile-card-inline.ts +301 -0
  32. package/src/components/card-inline/index.ts +2 -0
  33. package/src/components/card-inline-banner/ds-mobile-card-inline-banner.ts +118 -0
  34. package/src/components/card-inline-banner/index.ts +1 -0
  35. package/src/components/card-inline-contact/ds-mobile-card-inline-contact.ts +120 -0
  36. package/src/components/card-inline-contact/index.ts +1 -0
  37. package/src/components/card-inline-file/ds-mobile-card-inline-file.ts +141 -0
  38. package/src/components/card-inline-file/index.ts +1 -0
  39. package/src/components/chat-modal/ds-mobile-chat-modal.css +159 -0
  40. package/src/components/chat-modal/ds-mobile-chat-modal.service.ts +105 -0
  41. package/src/components/chat-modal/ds-mobile-chat-modal.ts +918 -0
  42. package/src/components/chat-modal/index.ts +8 -0
  43. package/src/components/comment/ds-mobile-comment.ts +568 -0
  44. package/src/components/comment/index.ts +2 -0
  45. package/src/components/contact-list-item/ds-mobile-contact-list-item.ts +182 -0
  46. package/src/components/contact-list-item/index.ts +2 -0
  47. package/src/components/content/ds-mobile-content.ts +139 -0
  48. package/src/components/content/index.ts +2 -0
  49. package/src/components/dropdown/ds-mobile-dropdown.css +199 -0
  50. package/src/components/dropdown/ds-mobile-dropdown.ts +340 -0
  51. package/src/components/dropdown/index.ts +2 -0
  52. package/src/components/ds-mobile-tabs.css +407 -0
  53. package/src/components/ds-mobile-tabs.ts +216 -0
  54. package/src/components/empty-state/ds-mobile-empty-state.ts +120 -0
  55. package/src/components/empty-state/index.ts +2 -0
  56. package/src/components/fab/ds-mobile-fab.ts +315 -0
  57. package/src/components/fab/index.ts +1 -0
  58. package/src/components/facility-creation-modal/ds-mobile-facility-creation-confirmation-wrapper.ts +121 -0
  59. package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.css +189 -0
  60. package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.service.ts +135 -0
  61. package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.ts +656 -0
  62. package/src/components/facility-creation-modal/index.ts +9 -0
  63. package/src/components/facility-creation-modal/sheets/ds-mobile-access-sheet.ts +105 -0
  64. package/src/components/facility-creation-modal/sheets/ds-mobile-price-sheet.ts +188 -0
  65. package/src/components/facility-creation-modal/sheets/ds-mobile-when-can-book-sheet.ts +460 -0
  66. package/src/components/facility-creation-modal/sheets/ds-mobile-who-can-book-sheet.ts +134 -0
  67. package/src/components/facility-detail-modal/ds-mobile-facility-detail-modal.service.ts +69 -0
  68. package/src/components/facility-detail-modal/ds-mobile-facility-detail-modal.ts +379 -0
  69. package/src/components/facility-detail-modal/index.ts +2 -0
  70. package/src/components/file-attachment/ds-mobile-file-attachment.ts +164 -0
  71. package/src/components/file-attachment/index.ts +2 -0
  72. package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.css +214 -0
  73. package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.service.ts +84 -0
  74. package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.ts +424 -0
  75. package/src/components/handbook-detail-modal/index.ts +3 -0
  76. package/src/components/handbook-folder/ds-mobile-handbook-folder-mini.ts +175 -0
  77. package/src/components/handbook-folder/ds-mobile-handbook-folder.ts +533 -0
  78. package/src/components/handbook-folder/index.ts +4 -0
  79. package/src/components/header-content/ds-mobile-header-content.ts +222 -0
  80. package/src/components/header-content/index.ts +2 -0
  81. package/src/components/illustration/ds-mobile-illustration.ts +124 -0
  82. package/src/components/illustration/index.ts +2 -0
  83. package/src/components/index.ts +124 -0
  84. package/src/components/inline-photo/ds-mobile-inline-photo.ts +361 -0
  85. package/src/components/inline-photo/index.ts +1 -0
  86. package/src/components/inline-tabs/ds-mobile-inline-tabs.ts +132 -0
  87. package/src/components/inline-tabs/index.ts +2 -0
  88. package/src/components/interactive-list-item-booking/ds-mobile-interactive-list-item-booking.ts +350 -0
  89. package/src/components/interactive-list-item-booking/index.ts +1 -0
  90. package/src/components/interactive-list-item-inquiry/ds-mobile-interactive-list-item-inquiry.ts +321 -0
  91. package/src/components/interactive-list-item-inquiry/index.ts +2 -0
  92. package/src/components/interactive-list-item-message/ds-mobile-interactive-list-item-message.ts +237 -0
  93. package/src/components/interactive-list-item-message/index.ts +2 -0
  94. package/src/components/interactive-list-item-post/ds-mobile-interactive-list-item-post.ts +549 -0
  95. package/src/components/interactive-list-item-post/ds-mobile-post-pdf-attachment.ts +124 -0
  96. package/src/components/interactive-list-item-post/index.ts +13 -0
  97. package/src/components/lightbox/ds-mobile-lightbox-footer.ts +315 -0
  98. package/src/components/lightbox/ds-mobile-lightbox-header.ts +202 -0
  99. package/src/components/lightbox/ds-mobile-lightbox-image.ts +484 -0
  100. package/src/components/lightbox/ds-mobile-lightbox-pdf.css +377 -0
  101. package/src/components/lightbox/ds-mobile-lightbox-pdf.ts +374 -0
  102. package/src/components/lightbox/ds-mobile-lightbox.css +587 -0
  103. package/src/components/lightbox/ds-mobile-lightbox.service.ts +296 -0
  104. package/src/components/lightbox/ds-mobile-lightbox.ts +529 -0
  105. package/src/components/lightbox/index.ts +22 -0
  106. package/src/components/list-item/ds-mobile-list-item.ts +603 -0
  107. package/src/components/list-item/index.ts +2 -0
  108. package/src/components/list-item-static/ds-mobile-list-item-static.ts +133 -0
  109. package/src/components/list-item-static/index.ts +2 -0
  110. package/src/components/loader-overlay/ds-mobile-loader-overlay.css +49 -0
  111. package/src/components/loader-overlay/ds-mobile-loader-overlay.ts +77 -0
  112. package/src/components/loader-overlay/index.ts +1 -0
  113. package/src/components/logo/ds-logo.ts +95 -0
  114. package/src/components/logo/index.ts +2 -0
  115. package/src/components/message-bubble/ds-mobile-message-bubble.ts +633 -0
  116. package/src/components/message-bubble/index.ts +7 -0
  117. package/src/components/message-composer/ds-mobile-message-composer.ts +1146 -0
  118. package/src/components/message-composer/index.ts +7 -0
  119. package/src/components/modal/ds-mobile-modal.css +163 -0
  120. package/src/components/modal/ds-mobile-modal.service.ts +329 -0
  121. package/src/components/modal/index.ts +8 -0
  122. package/src/components/modal-base/ds-mobile-modal-base.css +378 -0
  123. package/src/components/modal-base/ds-mobile-modal-base.ts +261 -0
  124. package/src/components/modal-base/index.ts +2 -0
  125. package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.css +112 -0
  126. package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.service.ts +93 -0
  127. package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.ts +442 -0
  128. package/src/components/new-inquiry-modal/index.ts +4 -0
  129. package/src/components/offline-banner/ds-mobile-offline-banner.ts +135 -0
  130. package/src/components/offline-banner/index.ts +1 -0
  131. package/src/components/page-details/ds-mobile-page-details.css +83 -0
  132. package/src/components/page-details/ds-mobile-page-details.ts +282 -0
  133. package/src/components/page-details/index.ts +2 -0
  134. package/src/components/page-main/ds-mobile-page-main.css +68 -0
  135. package/src/components/page-main/ds-mobile-page-main.ts +421 -0
  136. package/src/components/page-main/index.ts +2 -0
  137. package/src/components/post-composer/ds-mobile-post-composer.ts +140 -0
  138. package/src/components/post-composer/index.ts +2 -0
  139. package/src/components/post-detail-modal/ds-mobile-post-detail-modal.css +390 -0
  140. package/src/components/post-detail-modal/ds-mobile-post-detail-modal.service.ts +108 -0
  141. package/src/components/post-detail-modal/ds-mobile-post-detail-modal.ts +722 -0
  142. package/src/components/post-detail-modal/index.ts +9 -0
  143. package/src/components/property-banner/ds-mobile-property-banner.ts +95 -0
  144. package/src/components/property-banner/index.ts +2 -0
  145. package/src/components/section/ds-mobile-section.ts +263 -0
  146. package/src/components/section/index.ts +2 -0
  147. package/src/components/shared/directives/index.ts +2 -0
  148. package/src/components/shared/directives/long-press.directive.ts +212 -0
  149. package/src/components/shared/index.ts +3 -0
  150. package/src/components/shared/mobile-modal-base.ts +457 -0
  151. package/src/components/shared/mobile-page-base.ts +204 -0
  152. package/src/components/swiper/ds-mobile-swiper-with-nav.ts +160 -0
  153. package/src/components/swiper/ds-mobile-swiper.ts +327 -0
  154. package/src/components/swiper/index.ts +3 -0
  155. package/src/components/system-message-banner/ds-mobile-system-message-banner.ts +129 -0
  156. package/src/components/system-message-banner/index.ts +2 -0
  157. package/src/components/tab-bar/ds-mobile-tab-bar.css +533 -0
  158. package/src/components/tab-bar/ds-mobile-tab-bar.ts +735 -0
  159. package/src/components/tab-bar/index.ts +2 -0
  160. package/src/components/tabs/ds-mobile-tabs.css +25 -0
  161. package/src/components/tabs/ds-mobile-tabs.ts +89 -0
  162. package/src/components/tabs/index.ts +2 -0
  163. package/src/components/text-input/ds-text-input.ts +287 -0
  164. package/src/components/text-input/index.ts +2 -0
  165. package/src/examples/booking.page.ts +434 -0
  166. package/src/examples/community.page.ts +776 -0
  167. package/src/examples/handbook.page.ts +324 -0
  168. package/src/examples/home.page.ts +347 -0
  169. package/src/examples/index.ts +12 -0
  170. package/src/examples/inquiries.example.ts +273 -0
  171. package/src/examples/inquiry-detail.example.css +189 -0
  172. package/src/examples/inquiry-detail.example.ts +415 -0
  173. package/src/examples/mobile-tabs-example.component.ts +208 -0
  174. package/src/examples/post-create.page.ts +311 -0
  175. package/src/examples/post-detail.page.ts +296 -0
  176. package/src/examples/sign-in.page.ts +291 -0
  177. package/src/examples/whitelabel-demo-modal.component.ts +1094 -0
  178. package/src/examples/whitelabel-demo-modal.service.ts +77 -0
  179. package/src/models/index.ts +7 -0
  180. package/src/models/post.model.ts +41 -0
  181. package/src/pages/community.page.ts +769 -0
  182. package/src/pages/handbook.page.ts +388 -0
  183. package/src/pages/home.page.ts +303 -0
  184. package/src/pages/index.ts +11 -0
  185. package/src/pages/inquiries.example.ts +273 -0
  186. package/src/pages/inquiry-detail.example.css +189 -0
  187. package/src/pages/inquiry-detail.example.ts +415 -0
  188. package/src/pages/mobile-tabs-example.component.ts +179 -0
  189. package/src/pages/post-create.page.ts +311 -0
  190. package/src/pages/post-detail.page.ts +296 -0
  191. package/src/pages/sign-in.page.ts +291 -0
  192. package/src/pages/whitelabel-demo-modal.component.ts +1094 -0
  193. package/src/pages/whitelabel-demo-modal.service.ts +77 -0
  194. package/src/public-api.ts +6 -0
  195. package/src/services/base-modal.service.ts +101 -0
  196. package/src/services/index.ts +11 -0
  197. package/src/services/posts.service.ts +542 -0
  198. package/src/services/tracking-permission.service.ts +88 -0
  199. package/src/services/user.service.ts +60 -0
  200. package/src/services/whitelabel.service.ts +675 -0
  201. package/{styles → src/styles}/ionic.css +25 -0
  202. package/tsconfig.lib.json +17 -0
  203. package/tsconfig.lib.prod.json +9 -0
  204. package/tsconfig.spec.json +13 -0
  205. package/fesm2022/propbinder-mobile-design.mjs +0 -26136
  206. package/fesm2022/propbinder-mobile-design.mjs.map +0 -1
  207. package/index.d.ts +0 -8154
  208. /package/{assets → src/assets}/fonts/Brockmann-Bold.otf +0 -0
  209. /package/{assets → src/assets}/fonts/Brockmann-BoldItalic.otf +0 -0
  210. /package/{assets → src/assets}/fonts/Brockmann-Medium.otf +0 -0
  211. /package/{assets → src/assets}/fonts/Brockmann-MediumItalic.otf +0 -0
  212. /package/{assets → src/assets}/fonts/Brockmann-Regular.otf +0 -0
  213. /package/{assets → src/assets}/fonts/Brockmann-RegularItalic.otf +0 -0
  214. /package/{assets → src/assets}/fonts/Brockmann-SemiBold.otf +0 -0
  215. /package/{assets → src/assets}/fonts/Brockmann-SemiBoldItalic.otf +0 -0
  216. /package/{assets → src/assets}/fonts/Brockmann_desktop_license.pdf +0 -0
  217. /package/{assets → src/assets}/fonts/brockmann-medium-webfont.woff2 +0 -0
  218. /package/{assets → src/assets}/fonts/brockmann-regular-webfont.woff2 +0 -0
  219. /package/{assets → src/assets}/fonts/brockmann-semibold-webfont.woff2 +0 -0
  220. /package/{styles → src/components/shared}/mobile-common.css +0 -0
  221. /package/{styles → src/components/shared}/mobile-page-base.css +0 -0
@@ -0,0 +1,769 @@
1
+ import { Component, computed, ViewChild } from '@angular/core';
2
+ import { Router, ActivatedRoute } from '@angular/router';
3
+ import { IonInfiniteScroll, IonInfiniteScrollContent } from '@ionic/angular/standalone';
4
+ import { DsIconButtonComponent } from '@propbinder/design-system';
5
+ import { DsMobilePageMainComponent } from '../components/page-main';
6
+ import { DsMobileSectionComponent } from '../components/section';
7
+ import { DsMobileSwiperComponent } from '../components/swiper/ds-mobile-swiper';
8
+ import { DsMobileInteractiveListItemPostComponent } from '../components/interactive-list-item-post';
9
+ import { DsMobileCardInlineFileComponent } from '../components/card-inline-file';
10
+ import { DsMobileOfflineBannerComponent } from '../components/offline-banner';
11
+ import {
12
+ PostContentComponent,
13
+ PostTextComponent,
14
+ PostMediaComponent,
15
+ PostAttachmentsComponent,
16
+ PostActionsComponent,
17
+ ActionLikeComponent,
18
+ ActionCommentComponent
19
+ } from '../components/interactive-list-item-post';
20
+ import { DsMobilePostComposerComponent } from '../components/post-composer';
21
+ import { DsMobileBottomSheetService } from '../components/bottom-sheet/ds-mobile-bottom-sheet.service';
22
+ import { DsMobilePostCreateBottomSheetComponent } from '../components/bottom-sheet/ds-mobile-post-create-bottom-sheet';
23
+ import { DsMobilePostActionsBottomSheetComponent, PostActionResult } from '../components/bottom-sheet';
24
+ import { DsMobileLightboxService, LightboxAuthor } from '../components/lightbox';
25
+ import { DsMobilePostDetailModalService } from '../components/post-detail-modal';
26
+ import { DsMobileInlinePhotoComponent } from '../components/inline-photo';
27
+ import { UserService } from '../services/user.service';
28
+ import { PostsService } from '../services/posts.service';
29
+ import { Post } from '../models/post.model';
30
+
31
+ @Component({
32
+ selector: 'app-mobile-community-page',
33
+ standalone: true,
34
+ imports: [
35
+ DsMobilePageMainComponent,
36
+ DsMobileSectionComponent,
37
+ DsMobileInteractiveListItemPostComponent,
38
+ DsMobilePostComposerComponent,
39
+ DsMobileSwiperComponent,
40
+ PostContentComponent,
41
+ PostTextComponent,
42
+ PostMediaComponent,
43
+ PostAttachmentsComponent,
44
+ PostActionsComponent,
45
+ ActionLikeComponent,
46
+ ActionCommentComponent,
47
+ DsMobileCardInlineFileComponent,
48
+ DsIconButtonComponent,
49
+ DsMobileInlinePhotoComponent,
50
+ DsMobileOfflineBannerComponent,
51
+ IonInfiniteScroll,
52
+ IonInfiniteScrollContent
53
+ ],
54
+ styles: [`
55
+ /* Pinned posts swiper wrapper */
56
+ .pinned-posts-swiper-wrapper {
57
+ padding: 0;
58
+ position: relative;
59
+ }
60
+
61
+ /* Navigation buttons */
62
+ .swiper-nav-buttons {
63
+ position: absolute;
64
+ top: 50%;
65
+ transform: translateY(-50%);
66
+ left: -48px;
67
+ right: -48px;
68
+ display: flex;
69
+ justify-content: space-between;
70
+ pointer-events: none;
71
+ z-index: 10;
72
+ }
73
+
74
+ .swiper-nav-button {
75
+ pointer-events: auto;
76
+ }
77
+
78
+ /* Force buttons to be perfectly round */
79
+ ::ng-deep .swiper-nav-button button {
80
+ border-radius: 50% !important;
81
+ width: 48px !important;
82
+ height: 48px !important;
83
+ padding: 0 !important;
84
+ }
85
+
86
+ /* Hide on mobile */
87
+ @media (max-width: 767px) {
88
+ .swiper-nav-buttons {
89
+ display: none;
90
+ }
91
+ }
92
+
93
+ /* Swiper slide styling for pinned posts */
94
+ ::ng-deep .pinned-posts-swiper .swiper-slide {
95
+ width: 100%;
96
+ max-width: 600px;
97
+ height: auto;
98
+ }
99
+
100
+ /* Desktop: Remove max-width constraint */
101
+ @media (min-width: 768px) {
102
+ ::ng-deep .pinned-posts-swiper .swiper-slide {
103
+ max-width: 100%;
104
+ }
105
+ }
106
+
107
+ /* Post item inside swiper slide */
108
+ .swiper-post-item {
109
+ width: 100%;
110
+ height: auto;
111
+ }
112
+
113
+ /* Ensure post content doesn't get cropped */
114
+ ::ng-deep .pinned-posts-swiper .swiper-slide ds-mobile-interactive-list-item-post {
115
+ height: auto;
116
+ }
117
+
118
+ ::ng-deep .pinned-posts-swiper .swiper-wrapper {
119
+ height: auto;
120
+ align-items: flex-start;
121
+ }
122
+
123
+ .post-list-wrapper {
124
+ display: flex;
125
+ flex-direction: column;
126
+ }
127
+
128
+ .clickable-image {
129
+ cursor: pointer;
130
+ transition: transform 0.2s ease, opacity 0.2s ease;
131
+ border-radius: 8px;
132
+ display: block;
133
+ width: 100%;
134
+ aspect-ratio: 16/9;
135
+ object-fit: cover;
136
+ }
137
+
138
+ .clickable-image:active {
139
+ transform: scale(0.98);
140
+ opacity: 0.9;
141
+ }
142
+
143
+ /* Empty State */
144
+ .community-empty-state {
145
+ display: flex;
146
+ flex-direction: column;
147
+ align-items: center;
148
+ justify-content: center;
149
+ padding: 60px 20px;
150
+ text-align: center;
151
+ }
152
+
153
+ .empty-state-image {
154
+ width: 96px;
155
+ height: 96px;
156
+ margin-bottom: 24px;
157
+ }
158
+
159
+ .empty-state-title {
160
+ font-family: 'Brockmann', sans-serif;
161
+ font-size: var(--font-size-base);
162
+ font-weight: 600;
163
+ color: var(--color-text-primary);
164
+ margin: 16px 0 8px 0;
165
+ }
166
+
167
+ .empty-state-description {
168
+ font-family: 'Brockmann', sans-serif;
169
+ font-size: var(--font-size-sm);
170
+ color: var(--color-text-secondary);
171
+ margin: 0;
172
+ }
173
+
174
+ /* Infinite Scroll Spinner Styling */
175
+ ion-infinite-scroll {
176
+ --color: var(--color-primary-surface);
177
+ }
178
+
179
+ ion-infinite-scroll-content {
180
+ --color: var(--color-primary-surface);
181
+ }
182
+
183
+ /* Target the actual spinner element */
184
+ ion-infinite-scroll-content::part(spinner) {
185
+ color: var(--color-primary-surface);
186
+ }
187
+ `],
188
+ template: `
189
+ <ds-mobile-page-main
190
+ #pageComponent
191
+ title="Fællesskab"
192
+ [avatarInitials]="userService.avatarInitials()"
193
+ [avatarType]="userService.avatarType()"
194
+ (refresh)="handleRefresh($event)">
195
+
196
+ <!-- Offline indicator -->
197
+ @if (pageComponent.isOffline()) {
198
+ <ds-mobile-offline-banner
199
+ offline-indicator
200
+ title="Ingen internetforbindelse"
201
+ message="Nogle funktioner kan være utilgængelige">
202
+ </ds-mobile-offline-banner>
203
+ }
204
+
205
+ <!-- Post Composer in header-expandable -->
206
+ <ds-mobile-post-composer
207
+ header-content
208
+ [avatarInitials]="userService.avatarInitials()"
209
+ [avatarType]="userService.avatarType()"
210
+ [avatarSrc]="userService.avatarSrc()"
211
+ (composerClick)="openPostCreator()"
212
+ />
213
+
214
+ <!-- Pinned Posts Section -->
215
+ <ds-mobile-section
216
+ headline="Fastgjorte opslag"
217
+ icon="remixPushpinFill">
218
+ <div class="pinned-posts-swiper-wrapper">
219
+ <ds-mobile-swiper
220
+ #pinnedSwiper
221
+ class="pinned-posts-swiper"
222
+ [slideWidth]="'100%'"
223
+ [gap]="32"
224
+ [pagination]="true"
225
+ [autoHeight]="true"
226
+ [progressiveOpacity]="true"
227
+ [progressiveScale]="true">
228
+ @for (post of pinnedPosts(); track post.id) {
229
+ <div class="swiper-slide">
230
+ <ds-mobile-interactive-list-item-post
231
+ class="swiper-post-item"
232
+ [authorName]="post.authorName"
233
+ [authorRole]="post.authorRole"
234
+ [timestamp]="post.timestamp"
235
+ [avatarInitials]="post.avatarInitials || ''"
236
+ [avatarType]="post.avatarType"
237
+ [avatarSrc]="post.avatarSrc || ''"
238
+ [showBadge]="true"
239
+ [clickable]="true"
240
+ (postClick)="openPost(post.id)"
241
+ (commentClick)="openPost(post.id, true)"
242
+ (longPress)="handlePostLongPress(post.id, false)">
243
+
244
+ <post-content>
245
+ <post-text>{{ post.content }}</post-text>
246
+
247
+ @if (post.id === 'post-4') {
248
+ <post-attachments>
249
+ <ds-mobile-card-inline-file
250
+ [fileName]="'Husregler.pdf'"
251
+ [fileSize]="'245 KB'"
252
+ [variant]="'pdf'"
253
+ [layout]="'compact'"
254
+ (fileClick)="openHouseRulesPdf()">
255
+ </ds-mobile-card-inline-file>
256
+ </post-attachments>
257
+ }
258
+ </post-content>
259
+
260
+ <post-actions>
261
+ <action-like [count]="post.likeCount" [active]="post.isLiked" />
262
+ <action-comment [count]="post.commentCount" (commentClick)="openPost(post.id, true)" />
263
+ </post-actions>
264
+ </ds-mobile-interactive-list-item-post>
265
+ </div>
266
+ }
267
+ </ds-mobile-swiper>
268
+
269
+ <!-- Navigation Buttons -->
270
+ <div class="swiper-nav-buttons">
271
+ <ds-icon-button
272
+ class="swiper-nav-button"
273
+ icon="remixArrowLeftSLine"
274
+ variant="ghost"
275
+ size="sm"
276
+ [disabled]="isFirstSlide()"
277
+ (clicked)="slidePrev()"
278
+ aria-label="Previous post"
279
+ />
280
+ <ds-icon-button
281
+ class="swiper-nav-button"
282
+ icon="remixArrowRightSLine"
283
+ variant="ghost"
284
+ size="sm"
285
+ [disabled]="isLastSlide()"
286
+ (clicked)="slideNext()"
287
+ aria-label="Next post"
288
+ />
289
+ </div>
290
+ </div>
291
+ </ds-mobile-section>
292
+
293
+ <!-- All Posts Section -->
294
+ <ds-mobile-section
295
+ headline="Alle opslag">
296
+ @if (hasAnyPosts()) {
297
+ <div class="post-list-wrapper">
298
+ <!-- All Posts Loop -->
299
+ @for (post of allPosts(); track post.id) {
300
+ @if (post.hasInlinePhotos && post.id === 'post-2') {
301
+ <!-- Post 2: With multiple images (grid layout) -->
302
+ <ds-mobile-interactive-list-item-post
303
+ [authorName]="post.authorName"
304
+ [authorRole]="post.authorRole"
305
+ [timestamp]="post.timestamp"
306
+ [avatarInitials]="post.avatarInitials || ''"
307
+ [avatarType]="post.avatarType"
308
+ [clickable]="true"
309
+ (postClick)="openPost(post.id)"
310
+ (commentClick)="openPost(post.id, true)"
311
+ (longPress)="handlePostLongPress(post.id, post.authorRole === 'Dig')">
312
+
313
+ <post-content>
314
+ <post-text>{{ post.content }}</post-text>
315
+ <ds-mobile-inline-photo
316
+ [images]="[
317
+ '/Assets/Dummy-photos/balcony-view.jpg',
318
+ '/Assets/Dummy-photos/staircase.jpg',
319
+ '/Assets/Dummy-photos/park.jpg',
320
+ '/Assets/Dummy-photos/yard.jpg'
321
+ ]"
322
+ [author]="{
323
+ name: 'Sophie Andersen',
324
+ role: 'Lejer',
325
+ avatarInitials: 'SA',
326
+ avatarType: 'initials',
327
+ timestamp: '4t siden'
328
+ }"
329
+ />
330
+ </post-content>
331
+
332
+ <post-actions>
333
+ <action-like [active]="post.isLiked" [count]="post.likeCount" />
334
+ <action-comment [count]="post.commentCount" (commentClick)="openPost(post.id, true)" />
335
+ </post-actions>
336
+ </ds-mobile-interactive-list-item-post>
337
+ } @else if (post.hasInlinePhotos && post.id === 'post-3.5') {
338
+ <!-- Post 3.5: Property Manager showcase with 6+ photos -->
339
+ <ds-mobile-interactive-list-item-post
340
+ [authorName]="post.authorName"
341
+ [authorRole]="post.authorRole"
342
+ [timestamp]="post.timestamp"
343
+ [avatarInitials]="post.avatarInitials || ''"
344
+ [avatarType]="post.avatarType"
345
+ [showBadge]="post.showBadge || false"
346
+ [clickable]="true"
347
+ (postClick)="openPost(post.id)"
348
+ (commentClick)="openPost(post.id, true)"
349
+ (longPress)="handlePostLongPress(post.id, false)">
350
+
351
+ <post-content>
352
+ <post-text>{{ post.content }}</post-text>
353
+ <ds-mobile-inline-photo
354
+ [images]="[
355
+ '/Assets/Dummy-photos/mailboxes.jpg',
356
+ '/Assets/Dummy-photos/staircase.jpg',
357
+ '/Assets/Dummy-photos/yard.jpg',
358
+ '/Assets/Dummy-photos/park.jpg',
359
+ '/Assets/Dummy-photos/balcony-view.jpg',
360
+ '/Assets/Dummy-photos/handyman.jpg'
361
+ ]"
362
+ [author]="{
363
+ name: 'Karen Nielsen',
364
+ role: 'Ejendomsadministrator',
365
+ avatarInitials: 'KN',
366
+ avatarType: 'initials',
367
+ timestamp: '2d siden'
368
+ }"
369
+ [maxVisible]="5"
370
+ />
371
+ </post-content>
372
+
373
+ <post-actions>
374
+ <action-like [count]="post.likeCount" />
375
+ <action-comment [count]="post.commentCount" (commentClick)="openPost(post.id, true)" />
376
+ </post-actions>
377
+ </ds-mobile-interactive-list-item-post>
378
+ } @else {
379
+ <!-- Regular Post -->
380
+ <ds-mobile-interactive-list-item-post
381
+ [authorName]="post.authorName"
382
+ [authorRole]="post.authorRole"
383
+ [timestamp]="post.timestamp"
384
+ [avatarType]="post.avatarType"
385
+ [avatarSrc]="post.avatarSrc || ''"
386
+ [avatarInitials]="post.avatarInitials || ''"
387
+ [clickable]="true"
388
+ (postClick)="openPost(post.id)"
389
+ (commentClick)="openPost(post.id, true)"
390
+ (longPress)="handlePostLongPress(post.id, post.authorRole === 'Dig')">
391
+
392
+ <post-content>
393
+ @if (post.content) {
394
+ <post-text>{{ post.content }}</post-text>
395
+ }
396
+ @if (post.imageSrc) {
397
+ <post-media>
398
+ <img
399
+ [src]="post.imageSrc"
400
+ [alt]="post.imageAlt || 'Posted image'"
401
+ class="clickable-image"
402
+ (click)="openImageLightbox(post.imageSrc, post.imageAlt || 'Posted image', post.content, $event)"
403
+ />
404
+ </post-media>
405
+ }
406
+ </post-content>
407
+
408
+ <post-actions>
409
+ <action-like [count]="post.likeCount" [active]="post.isLiked" />
410
+ <action-comment [count]="post.commentCount" (commentClick)="openPost(post.id, true)" />
411
+ </post-actions>
412
+ </ds-mobile-interactive-list-item-post>
413
+ }
414
+ }
415
+ </div>
416
+ } @else {
417
+ <!-- Empty State -->
418
+ <div class="community-empty-state">
419
+ <img
420
+ src="/Assets/Empty%20state-chat.png"
421
+ alt="Ingen opslag endnu"
422
+ class="empty-state-image"
423
+ />
424
+ <h3 class="empty-state-title">Ingen opslag endnu</h3>
425
+ <p class="empty-state-description">Vær den første til at dele noget med dit fællesskab</p>
426
+ </div>
427
+ }
428
+
429
+ <!-- Infinite Scroll -->
430
+ @if (hasAnyPosts()) {
431
+ <ion-infinite-scroll
432
+ threshold="100px"
433
+ [disabled]="!hasMorePosts() || pageComponent.isOffline()"
434
+ (ionInfinite)="onInfiniteScroll($event)">
435
+ <ion-infinite-scroll-content
436
+ loadingSpinner="crescent">
437
+ </ion-infinite-scroll-content>
438
+ </ion-infinite-scroll>
439
+ }
440
+ </ds-mobile-section>
441
+ </ds-mobile-page-main>
442
+ `
443
+ })
444
+ export class MobileCommunityPageComponent {
445
+ @ViewChild('pageComponent') pageComponent!: DsMobilePageMainComponent;
446
+ @ViewChild('pinnedSwiper') pinnedSwiper?: DsMobileSwiperComponent;
447
+
448
+ // Get posts from service (using computed for safe initialization)
449
+ allPosts = computed(() => this.postsService.posts());
450
+
451
+ // Get pinned posts - filter by isPinned flag
452
+ // In a real app, these would have a 'pinned' flag in the database
453
+ pinnedPosts = computed(() => {
454
+ // Get all posts that are explicitly marked as pinned
455
+ const allPosts = this.postsService.posts();
456
+ return allPosts.filter(post => post.isPinned === true);
457
+ });
458
+
459
+ // Computed to check if there are any posts to display
460
+ hasAnyPosts = computed(() => {
461
+ return this.allPosts().length > 0;
462
+ });
463
+
464
+ // Computed to check if there are more posts to load
465
+ hasMorePosts = computed(() => {
466
+ return this.postsService.hasMorePosts();
467
+ });
468
+
469
+ constructor(
470
+ private router: Router,
471
+ private route: ActivatedRoute,
472
+ private bottomSheet: DsMobileBottomSheetService,
473
+ private lightbox: DsMobileLightboxService,
474
+ private postModal: DsMobilePostDetailModalService,
475
+ public userService: UserService,
476
+ private postsService: PostsService
477
+ ) {}
478
+
479
+ /**
480
+ * Handle infinite scroll event
481
+ * Loads more posts when user scrolls to bottom
482
+ */
483
+ async onInfiniteScroll(event: any): Promise<void> {
484
+ console.log('[Community] Infinite scroll triggered');
485
+
486
+ const hasMore = await this.postsService.loadMorePosts();
487
+
488
+ if (hasMore) {
489
+ console.log('[Community] Loaded more posts');
490
+ } else {
491
+ console.log('[Community] No more posts to load');
492
+ }
493
+
494
+ // Complete the infinite scroll
495
+ event.target.complete();
496
+ }
497
+
498
+ handleRefresh(event: any): void {
499
+ console.log('Pull-to-refresh triggered');
500
+
501
+ // Check if offline and complete immediately
502
+ if (this.pageComponent?.isOffline()) {
503
+ console.log('Cannot refresh while offline');
504
+ event.target.complete();
505
+ return;
506
+ }
507
+
508
+ // Reset infinite scroll state
509
+ this.postsService.resetPagination();
510
+
511
+ setTimeout(() => {
512
+ console.log('Refresh complete');
513
+ event.target.complete();
514
+ }, 1000);
515
+ }
516
+
517
+ /**
518
+ * Open post detail modal
519
+ * Gets post data from service and opens modal
520
+ */
521
+ async openPost(postId: string, focusComment: boolean = false): Promise<void> {
522
+ console.log('[Community] ===== openPost called =====', postId, 'Focus comment:', focusComment);
523
+
524
+ const post = this.postsService.getPostById(postId);
525
+
526
+ if (post) {
527
+ // Convert Post model to modal format (add postId and focusComment)
528
+ // Filter out 'icon' avatarType if present, as modal only supports 'photo' | 'initials'
529
+ const postData = {
530
+ ...post,
531
+ postId: post.id,
532
+ avatarType: post.avatarType === 'icon' ? undefined : post.avatarType,
533
+ focusComment
534
+ };
535
+
536
+ await this.postModal.open(postData, {
537
+ currentUserName: 'Lars Mikkelsen', // Current user name
538
+ currentUserInitials: this.userService.avatarInitials()
539
+ });
540
+ }
541
+ }
542
+
543
+ async openPostCreator(): Promise<void> {
544
+ // Open the post creator as a bottom sheet modal
545
+ // Using 95% initial height to maximize keyboard auto-open chances
546
+ const sheet = await this.bottomSheet.create({
547
+ component: DsMobilePostCreateBottomSheetComponent,
548
+ componentProps: {
549
+ // This helps the component know it should auto-focus
550
+ autoFocus: true
551
+ },
552
+ breakpoints: [0, 0.95, 1],
553
+ initialBreakpoint: 0.95,
554
+ handle: true
555
+ });
556
+
557
+ // Handle the result when the sheet is dismissed
558
+ const result = await sheet.onWillDismiss();
559
+ if (result.role === 'post' && result.data) {
560
+ console.log('New post created:', result.data);
561
+
562
+ // Create a new post object
563
+ const newPost: Post = {
564
+ id: `user-post-${Date.now()}`, // Generate unique ID
565
+ authorName: 'Lars Mikkelsen', // Current user name
566
+ authorRole: 'Dig',
567
+ timestamp: 'Lige nu',
568
+ avatarType: this.userService.avatarType() as 'photo' | 'initials' | 'icon',
569
+ avatarSrc: this.userService.avatarSrc(),
570
+ avatarInitials: this.userService.avatarInitials(),
571
+ content: result.data.content,
572
+ imageSrc: result.data.images && result.data.images.length > 0 ? result.data.images[0] : undefined,
573
+ imageAlt: result.data.images && result.data.images.length > 0 ? 'Slået billede op' : undefined,
574
+ isLiked: false,
575
+ likeCount: 0,
576
+ commentCount: 0,
577
+ comments: []
578
+ };
579
+
580
+ // Add to the beginning of the posts array via service
581
+ this.postsService.addPost(newPost);
582
+ }
583
+ }
584
+
585
+ /**
586
+ * Open an image in the lightbox viewer
587
+ * Prevents the post click event from firing
588
+ */
589
+ async openImageLightbox(imageSrc: string, title: string, description: string, event: Event): Promise<void> {
590
+ console.log('[Community] Opening lightbox for image:', imageSrc);
591
+
592
+ // Prevent the post card click event from firing
593
+ event.stopPropagation();
594
+
595
+ const authorMeta: LightboxAuthor = {
596
+ name: 'Sophie Andersen',
597
+ role: 'Lejer',
598
+ avatarInitials: 'SA',
599
+ timestamp: '4t siden'
600
+ };
601
+
602
+ // Open the lightbox with the image
603
+ await this.lightbox.open({
604
+ images: [
605
+ {
606
+ type: 'image',
607
+ src: imageSrc,
608
+ alt: title,
609
+ title: title,
610
+ description: description,
611
+ isLiked: true,
612
+ likeCount: 156,
613
+ commentCount: 34
614
+ }
615
+ ],
616
+ author: authorMeta,
617
+ enableZoom: true,
618
+ showControls: false, // Single image, no need for controls
619
+ showActions: true, // Show like & comment actions
620
+ showInfo: true
621
+ });
622
+ }
623
+
624
+ async openHouseRulesPdf(): Promise<void> {
625
+ console.log('[Community] Opening House Rules PDF');
626
+
627
+ // Author metadata
628
+ const authorMeta: LightboxAuthor = {
629
+ name: 'Karen Nielsen',
630
+ role: 'Ejendomsadministrator',
631
+ avatarInitials: 'KN',
632
+ timestamp: '2d siden'
633
+ };
634
+
635
+ // Open the PDF lightbox
636
+ // Use absolute path for production deployment (Vercel, etc.)
637
+ await this.lightbox.openPdf({
638
+ pdf: {
639
+ type: 'pdf',
640
+ src: '/Assets/House_Rules.pdf', // Capital A to match public/Assets folder structure
641
+ title: 'House Rules',
642
+ description: 'Building regulations and community guidelines',
643
+ fileSize: 250880, // 245 KB in bytes
644
+ pageCount: 8
645
+ },
646
+ author: authorMeta
647
+ });
648
+ }
649
+
650
+ /**
651
+ * Handle long press on a post to show action sheet
652
+ */
653
+ async handlePostLongPress(postId: string, isOwnPost: boolean): Promise<void> {
654
+ console.log('[Community] Post long pressed:', postId, 'isOwn:', isOwnPost);
655
+
656
+ const sheet = await this.bottomSheet.create({
657
+ component: DsMobilePostActionsBottomSheetComponent,
658
+ componentProps: {
659
+ isOwnContent: isOwnPost
660
+ },
661
+ breakpoints: [0, 1],
662
+ initialBreakpoint: 1,
663
+ handle: true,
664
+ backdropDismiss: true,
665
+ cssClass: 'auto-height'
666
+ });
667
+
668
+ const result = await sheet.onWillDismiss();
669
+
670
+ if (result.role === 'select' && result.data) {
671
+ const action = (result.data as PostActionResult).action;
672
+
673
+ switch (action) {
674
+ case 'edit':
675
+ console.log('Edit post:', postId);
676
+
677
+ // Get post from service
678
+ const post = this.postsService.getPostById(postId);
679
+ if (!post) {
680
+ console.error('Post not found:', postId);
681
+ return;
682
+ }
683
+
684
+ // Open the bottom sheet in edit mode
685
+ const editSheet = await this.bottomSheet.create({
686
+ component: DsMobilePostCreateBottomSheetComponent,
687
+ componentProps: {
688
+ autoFocus: true,
689
+ isEditMode: true,
690
+ postId: postId,
691
+ initialContent: post.content
692
+ },
693
+ breakpoints: [0, 0.95, 1],
694
+ initialBreakpoint: 0.95,
695
+ handle: true,
696
+ backdropBlur: true,
697
+ backdropOpacity: 0.6
698
+ });
699
+
700
+ // Handle the result when the sheet is dismissed
701
+ const editResult = await editSheet.onWillDismiss();
702
+ if (editResult.role === 'post' && editResult.data) {
703
+ console.log('Post updated:', editResult.data);
704
+
705
+ // Update the post via service
706
+ this.postsService.updatePost(postId, {
707
+ content: editResult.data.content,
708
+ timestamp: 'Lige nu'
709
+ });
710
+ }
711
+ break;
712
+
713
+ case 'delete':
714
+ console.log('Delete post:', postId);
715
+ if (confirm('Er du sikker på, at du vil slette dette opslag?')) {
716
+ this.postsService.deletePost(postId);
717
+ }
718
+ break;
719
+
720
+ case 'like':
721
+ console.log('Like post:', postId);
722
+ // Toggle like - in a real app, this would call an API
723
+ const likePost = this.postsService.getPostById(postId);
724
+ if (likePost) {
725
+ this.postsService.updatePost(postId, {
726
+ isLiked: !likePost.isLiked,
727
+ likeCount: likePost.isLiked ? likePost.likeCount - 1 : likePost.likeCount + 1
728
+ });
729
+ }
730
+ break;
731
+
732
+ case 'reply':
733
+ console.log('Reply to post:', postId);
734
+ // Open the post detail modal with comment input focused
735
+ await this.openPost(postId, true);
736
+ break;
737
+ }
738
+ }
739
+ }
740
+
741
+ /**
742
+ * Navigate to previous slide in pinned posts
743
+ */
744
+ slidePrev(): void {
745
+ this.pinnedSwiper?.slidePrev();
746
+ }
747
+
748
+ /**
749
+ * Navigate to next slide in pinned posts
750
+ */
751
+ slideNext(): void {
752
+ this.pinnedSwiper?.slideNext();
753
+ }
754
+
755
+ /**
756
+ * Check if currently on first slide
757
+ */
758
+ isFirstSlide(): boolean {
759
+ return this.pinnedSwiper?.isBeginning() ?? true;
760
+ }
761
+
762
+ /**
763
+ * Check if currently on last slide
764
+ */
765
+ isLastSlide(): boolean {
766
+ return this.pinnedSwiper?.isEnd() ?? true;
767
+ }
768
+ }
769
+