@churchapps/apphelper 0.6.15 → 0.6.17

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 (218) hide show
  1. package/dist/components/DisplayBox.d.ts +1 -1
  2. package/dist/components/DisplayBox.d.ts.map +1 -1
  3. package/dist/components/DisplayBox.js +2 -4
  4. package/dist/components/DisplayBox.js.map +1 -1
  5. package/dist/components/ErrorMessages.d.ts.map +1 -1
  6. package/dist/components/ErrorMessages.js +1 -1
  7. package/dist/components/ErrorMessages.js.map +1 -1
  8. package/dist/components/ExportLink.d.ts.map +1 -1
  9. package/dist/components/ExportLink.js +10 -10
  10. package/dist/components/ExportLink.js.map +1 -1
  11. package/dist/components/FloatingSupport.js.map +1 -1
  12. package/dist/components/FormCardPayment.d.ts.map +1 -1
  13. package/dist/components/FormCardPayment.js +20 -5
  14. package/dist/components/FormCardPayment.js.map +1 -1
  15. package/dist/components/FormSubmissionEdit.d.ts.map +1 -1
  16. package/dist/components/FormSubmissionEdit.js +8 -10
  17. package/dist/components/FormSubmissionEdit.js.map +1 -1
  18. package/dist/components/HelpIcon.d.ts.map +1 -1
  19. package/dist/components/HelpIcon.js.map +1 -1
  20. package/dist/components/ImageEditor.d.ts +1 -1
  21. package/dist/components/ImageEditor.d.ts.map +1 -1
  22. package/dist/components/ImageEditor.js +11 -11
  23. package/dist/components/ImageEditor.js.map +1 -1
  24. package/dist/components/InputBox.d.ts +1 -1
  25. package/dist/components/InputBox.d.ts.map +1 -1
  26. package/dist/components/InputBox.js +4 -6
  27. package/dist/components/InputBox.js.map +1 -1
  28. package/dist/components/Loading.d.ts.map +1 -1
  29. package/dist/components/Loading.js.map +1 -1
  30. package/dist/components/PageHeader.js +14 -14
  31. package/dist/components/PageHeader.js.map +1 -1
  32. package/dist/components/PersonAvatar.d.ts.map +1 -1
  33. package/dist/components/PersonAvatar.js +9 -16
  34. package/dist/components/PersonAvatar.js.map +1 -1
  35. package/dist/components/QuestionEdit.js +3 -3
  36. package/dist/components/QuestionEdit.js.map +1 -1
  37. package/dist/components/SmallButton.d.ts.map +1 -1
  38. package/dist/components/SmallButton.js +2 -1
  39. package/dist/components/SmallButton.js.map +1 -1
  40. package/dist/components/SupportModal.js.map +1 -1
  41. package/dist/components/gallery/GalleryModal.js +10 -10
  42. package/dist/components/gallery/GalleryModal.js.map +1 -1
  43. package/dist/components/gallery/StockPhotos.js +12 -12
  44. package/dist/components/gallery/StockPhotos.js.map +1 -1
  45. package/dist/components/header/Banner.d.ts.map +1 -1
  46. package/dist/components/header/Banner.js.map +1 -1
  47. package/dist/components/header/PrimaryMenu.js +4 -4
  48. package/dist/components/header/PrimaryMenu.js.map +1 -1
  49. package/dist/components/header/SecondaryMenu.d.ts.map +1 -1
  50. package/dist/components/header/SecondaryMenu.js.map +1 -1
  51. package/dist/components/header/SecondaryMenuAlt.d.ts.map +1 -1
  52. package/dist/components/header/SecondaryMenuAlt.js.map +1 -1
  53. package/dist/components/header/SiteHeader.d.ts.map +1 -1
  54. package/dist/components/header/SiteHeader.js +7 -7
  55. package/dist/components/header/SiteHeader.js.map +1 -1
  56. package/dist/components/header/SupportDrawer.js +2 -2
  57. package/dist/components/header/SupportDrawer.js.map +1 -1
  58. package/dist/components/notes/AddNote.js +14 -14
  59. package/dist/components/notes/Note.d.ts.map +1 -1
  60. package/dist/components/notes/Note.js +7 -7
  61. package/dist/components/notes/Note.js.map +1 -1
  62. package/dist/components/notes/Notes.d.ts.map +1 -1
  63. package/dist/components/notes/Notes.js +4 -4
  64. package/dist/components/notes/Notes.js.map +1 -1
  65. package/dist/components/wrapper/AppList.d.ts.map +1 -1
  66. package/dist/components/wrapper/AppList.js.map +1 -1
  67. package/dist/components/wrapper/ChurchList.js +5 -5
  68. package/dist/components/wrapper/ChurchList.js.map +1 -1
  69. package/dist/components/wrapper/NavItem.js +9 -3
  70. package/dist/components/wrapper/NavItem.js.map +1 -1
  71. package/dist/components/wrapper/NewPrivateMessage.d.ts.map +1 -1
  72. package/dist/components/wrapper/NewPrivateMessage.js +7 -6
  73. package/dist/components/wrapper/NewPrivateMessage.js.map +1 -1
  74. package/dist/components/wrapper/Notifications.d.ts.map +1 -1
  75. package/dist/components/wrapper/Notifications.js +12 -14
  76. package/dist/components/wrapper/Notifications.js.map +1 -1
  77. package/dist/components/wrapper/PrivateMessageDetails.js +10 -10
  78. package/dist/components/wrapper/PrivateMessages.d.ts.map +1 -1
  79. package/dist/components/wrapper/PrivateMessages.js +119 -123
  80. package/dist/components/wrapper/PrivateMessages.js.map +1 -1
  81. package/dist/components/wrapper/UserMenu.d.ts.map +1 -1
  82. package/dist/components/wrapper/UserMenu.js +15 -15
  83. package/dist/components/wrapper/UserMenu.js.map +1 -1
  84. package/dist/helpers/AnalyticsHelper.d.ts.map +1 -1
  85. package/dist/helpers/AnalyticsHelper.js +3 -3
  86. package/dist/helpers/AnalyticsHelper.js.map +1 -1
  87. package/dist/helpers/ArrayHelper.d.ts.map +1 -1
  88. package/dist/helpers/ArrayHelper.js.map +1 -1
  89. package/dist/helpers/CurrencyHelper.d.ts +4 -0
  90. package/dist/helpers/CurrencyHelper.d.ts.map +1 -1
  91. package/dist/helpers/CurrencyHelper.js +65 -0
  92. package/dist/helpers/CurrencyHelper.js.map +1 -1
  93. package/dist/helpers/ErrorHelper.d.ts.map +1 -1
  94. package/dist/helpers/ErrorHelper.js +2 -1
  95. package/dist/helpers/ErrorHelper.js.map +1 -1
  96. package/dist/helpers/EventHelper.d.ts.map +1 -1
  97. package/dist/helpers/EventHelper.js +1 -1
  98. package/dist/helpers/EventHelper.js.map +1 -1
  99. package/dist/helpers/FileHelper.d.ts.map +1 -1
  100. package/dist/helpers/FileHelper.js +3 -1
  101. package/dist/helpers/FileHelper.js.map +1 -1
  102. package/dist/helpers/Locale.d.ts.map +1 -1
  103. package/dist/helpers/Locale.js +10 -16
  104. package/dist/helpers/Locale.js.map +1 -1
  105. package/dist/helpers/NotificationService.js +4 -4
  106. package/dist/helpers/PersonHelper.d.ts.map +1 -1
  107. package/dist/helpers/PersonHelper.js +5 -4
  108. package/dist/helpers/PersonHelper.js.map +1 -1
  109. package/dist/helpers/SlugHelper.d.ts.map +1 -1
  110. package/dist/helpers/SlugHelper.js +5 -3
  111. package/dist/helpers/SlugHelper.js.map +1 -1
  112. package/dist/helpers/SocketHelper.d.ts.map +1 -1
  113. package/dist/helpers/SocketHelper.js +5 -12
  114. package/dist/helpers/SocketHelper.js.map +1 -1
  115. package/dist/helpers/UniqueIdHelper.d.ts.map +1 -1
  116. package/dist/helpers/UniqueIdHelper.js +132 -7
  117. package/dist/helpers/UniqueIdHelper.js.map +1 -1
  118. package/dist/helpers/UserHelper.d.ts.map +1 -1
  119. package/dist/helpers/UserHelper.js +2 -2
  120. package/dist/helpers/UserHelper.js.map +1 -1
  121. package/dist/hooks/useMountedState.d.ts.map +1 -1
  122. package/dist/hooks/useMountedState.js +1 -1
  123. package/dist/hooks/useMountedState.js.map +1 -1
  124. package/dist/hooks/useNotifications.d.ts +2 -2
  125. package/dist/hooks/useNotifications.js +5 -5
  126. package/dist/public/locales/de.json +15 -7
  127. package/dist/public/locales/es.json +193 -190
  128. package/dist/public/locales/fr.json +15 -7
  129. package/dist/public/locales/hi.json +15 -7
  130. package/dist/public/locales/it.json +15 -7
  131. package/dist/public/locales/ko.json +15 -7
  132. package/dist/public/locales/no.json +15 -7
  133. package/dist/public/locales/pt.json +15 -7
  134. package/dist/public/locales/ru.json +15 -7
  135. package/dist/public/locales/tl.json +15 -7
  136. package/dist/public/locales/zh.json +15 -7
  137. package/package.json +106 -73
  138. package/dist/helpers/DateHelper.d.ts +0 -18
  139. package/dist/helpers/DateHelper.d.ts.map +0 -1
  140. package/dist/helpers/DateHelper.js +0 -113
  141. package/dist/helpers/DateHelper.js.map +0 -1
  142. package/public/css/cropper.css +0 -309
  143. package/public/css/styles.css +0 -112
  144. package/public/locales/de.json +0 -273
  145. package/public/locales/en.json +0 -281
  146. package/public/locales/es.json +0 -278
  147. package/public/locales/fr.json +0 -273
  148. package/public/locales/hi.json +0 -273
  149. package/public/locales/it.json +0 -273
  150. package/public/locales/ko.json +0 -273
  151. package/public/locales/no.json +0 -273
  152. package/public/locales/pt.json +0 -273
  153. package/public/locales/ru.json +0 -273
  154. package/public/locales/tl.json +0 -273
  155. package/public/locales/zh.json +0 -273
  156. package/src/components/DisplayBox.tsx +0 -83
  157. package/src/components/ErrorMessages.tsx +0 -28
  158. package/src/components/ExportLink.tsx +0 -81
  159. package/src/components/FloatingSupport.tsx +0 -18
  160. package/src/components/FormCardPayment.tsx +0 -184
  161. package/src/components/FormSubmissionEdit.tsx +0 -168
  162. package/src/components/HelpIcon.tsx +0 -12
  163. package/src/components/ImageEditor.tsx +0 -167
  164. package/src/components/InputBox.tsx +0 -97
  165. package/src/components/Loading.tsx +0 -31
  166. package/src/components/PageHeader.tsx +0 -111
  167. package/src/components/PersonAvatar.tsx +0 -78
  168. package/src/components/QuestionEdit.tsx +0 -99
  169. package/src/components/SmallButton.tsx +0 -42
  170. package/src/components/SupportModal.tsx +0 -32
  171. package/src/components/TabPanel.tsx +0 -28
  172. package/src/components/gallery/GalleryModal.tsx +0 -170
  173. package/src/components/gallery/StockPhotos.tsx +0 -96
  174. package/src/components/gallery/index.ts +0 -2
  175. package/src/components/header/Banner.tsx +0 -11
  176. package/src/components/header/PrimaryMenu.tsx +0 -101
  177. package/src/components/header/SecondaryMenu.tsx +0 -23
  178. package/src/components/header/SecondaryMenuAlt.tsx +0 -40
  179. package/src/components/header/SiteHeader.tsx +0 -205
  180. package/src/components/header/SupportDrawer.tsx +0 -112
  181. package/src/components/header/index.tsx +0 -2
  182. package/src/components/index.tsx +0 -20
  183. package/src/components/notes/AddNote.tsx +0 -185
  184. package/src/components/notes/Note.tsx +0 -84
  185. package/src/components/notes/Notes.tsx +0 -208
  186. package/src/components/notes/index.ts +0 -3
  187. package/src/components/wrapper/AppList.tsx +0 -18
  188. package/src/components/wrapper/ChurchList.tsx +0 -145
  189. package/src/components/wrapper/NavItem.tsx +0 -47
  190. package/src/components/wrapper/NewPrivateMessage.tsx +0 -249
  191. package/src/components/wrapper/Notifications.tsx +0 -220
  192. package/src/components/wrapper/PrivateMessageDetails.tsx +0 -106
  193. package/src/components/wrapper/PrivateMessages.tsx +0 -574
  194. package/src/components/wrapper/UserMenu.tsx +0 -384
  195. package/src/components/wrapper/index.tsx +0 -8
  196. package/src/helpers/AnalyticsHelper.ts +0 -44
  197. package/src/helpers/AppearanceHelper.ts +0 -73
  198. package/src/helpers/ArrayHelper.ts +0 -87
  199. package/src/helpers/CurrencyHelper.ts +0 -10
  200. package/src/helpers/DateHelper.ts +0 -116
  201. package/src/helpers/ErrorHelper.ts +0 -41
  202. package/src/helpers/EventHelper.ts +0 -49
  203. package/src/helpers/FileHelper.ts +0 -31
  204. package/src/helpers/Locale.ts +0 -457
  205. package/src/helpers/NotificationService.ts +0 -233
  206. package/src/helpers/PersonHelper.ts +0 -62
  207. package/src/helpers/SlugHelper.ts +0 -27
  208. package/src/helpers/SocketHelper.ts +0 -183
  209. package/src/helpers/UniqueIdHelper.ts +0 -36
  210. package/src/helpers/UserHelper.ts +0 -103
  211. package/src/helpers/createEmotionCache.ts +0 -17
  212. package/src/helpers/index.ts +0 -58
  213. package/src/hooks/index.ts +0 -3
  214. package/src/hooks/useMountedState.ts +0 -18
  215. package/src/hooks/useNotifications.ts +0 -95
  216. package/src/index.ts +0 -3
  217. package/src/types/interface-extensions.d.ts +0 -12
  218. package/tsconfig.json +0 -32
@@ -1,384 +0,0 @@
1
- "use client";
2
-
3
- import React from "react";
4
- import { ApiHelper, UserHelper, LoginUserChurchInterface, UserContextInterface, CommonEnvironmentHelper } from "@churchapps/helpers";
5
- import { Avatar, Menu, Typography, Icon, Button, Box, Badge, Dialog, DialogContent, DialogTitle } from "@mui/material";
6
- import { NavItem, AppList } from ".";
7
- import { ChurchList } from "./ChurchList";
8
- import { TabPanel } from "../TabPanel";
9
- import { Locale } from "../../helpers";
10
- import { PrivateMessages } from "./PrivateMessages";
11
- import { Notifications } from "./Notifications";
12
- import { useCookies, CookiesProvider } from "react-cookie";
13
- import { NotificationService } from "../../helpers/NotificationService";
14
- import { SocketHelper } from "../../helpers";
15
-
16
-
17
- interface Props {
18
- notificationCounts: { notificationCount: number, pmCount: number };
19
- loadCounts: () => void;
20
- userName: string;
21
- profilePicture: string;
22
- context: UserContextInterface;
23
- appName: string;
24
- onNavigate: (url: string) => void;
25
- }
26
-
27
- // Create a persistent store for modal state that survives component re-renders
28
- const modalStateStore = {
29
- showPM: false,
30
- showNotifications: false,
31
- listeners: new Set<() => void>(),
32
-
33
- setShowPM(value: boolean) {
34
- this.showPM = value;
35
- this.listeners.forEach((listener: () => void) => listener());
36
- },
37
-
38
- setShowNotifications(value: boolean) {
39
- this.showNotifications = value;
40
- this.listeners.forEach((listener: () => void) => listener());
41
- },
42
-
43
- subscribe(listener: () => void) {
44
- this.listeners.add(listener);
45
- return () => this.listeners.delete(listener);
46
- }
47
- };
48
-
49
- const UserMenuContent: React.FC<Props> = React.memo((props) => {
50
- const userName = props.userName;
51
- const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
52
- const [, forceUpdate] = React.useReducer(x => x + 1, 0);
53
- const [refreshKey, setRefreshKey] = React.useState(() => Math.random());
54
- const [, , removeCookie] = useCookies(["lastChurchId"]);
55
- const [directNotificationCounts, setDirectNotificationCounts] = React.useState(() => props.notificationCounts || { notificationCount: 0, pmCount: 0 });
56
- const open = Boolean(anchorEl);
57
-
58
- // Subscribe to modal state changes
59
- React.useEffect(() => {
60
- return modalStateStore.subscribe(forceUpdate);
61
- }, [forceUpdate]);
62
-
63
- // Subscribe directly to NotificationService to update badge counts without re-renders
64
- React.useEffect(() => {
65
- const notificationService = NotificationService.getInstance();
66
- const unsubscribe = notificationService.subscribe((newCounts) => {
67
- setDirectNotificationCounts(newCounts);
68
- });
69
-
70
- // Initialize with current counts
71
- if (notificationService.isReady()) {
72
- setDirectNotificationCounts(notificationService.getCounts());
73
- }
74
-
75
- return unsubscribe;
76
- }, []);
77
-
78
- const showPM = modalStateStore.showPM;
79
- const showNotifications = modalStateStore.showNotifications;
80
-
81
- // Create a stable callback for onUpdate that doesn't depend on props
82
- const stableOnUpdate = React.useCallback(() => {
83
- // Use NotificationService directly to avoid dependency on props.loadCounts
84
- NotificationService.getInstance().refresh();
85
- }, []);
86
-
87
-
88
- const handleClick = (e: React.MouseEvent<HTMLElement>) => {
89
- e.preventDefault();
90
- setAnchorEl(e.currentTarget);
91
- };
92
-
93
- const handleClose = () => {
94
- setTabIndex(0);
95
- setAnchorEl(null);
96
- };
97
-
98
- const handleSwitchChurch = () => {
99
- removeCookie("lastChurchId", { path: "/" });
100
- setTabIndex(2);
101
- };
102
-
103
- const getMainLinks = () => {
104
- const jwt = ApiHelper.getConfig("MembershipApi").jwt;
105
- const churchId = UserHelper.currentUserChurch.church.id;
106
- let result: React.ReactElement[] = [];
107
-
108
- result.push(<NavItem onClick={() => {modalStateStore.setShowPM(true)}} label={getLabel("wrapper.messages", "Messages")} icon="mail" key="/messages" onNavigate={props.onNavigate} badgeCount={directNotificationCounts.pmCount} />);
109
-
110
- result.push(<NavItem onClick={() => {modalStateStore.setShowNotifications(true)}} label={getLabel("wrapper.notifications", "Notifications")} icon="notifications" key="/notifications" onNavigate={props.onNavigate} badgeCount={directNotificationCounts.notificationCount} />);
111
-
112
- result.push(<NavItem label={getLabel("wrapper.editProfile", "Edit Profile")} key="EditProfile" icon="person" onClick={() => { setTabIndex(3); }} />);
113
- // Create logout URL with current page as return URL
114
- const currentPath = typeof window !== 'undefined' ? window.location.pathname + window.location.search : '/';
115
- const logoutUrl = `/login?action=logout&returnUrl=${encodeURIComponent(currentPath)}`;
116
- result.push(<NavItem url={logoutUrl} label={getLabel("wrapper.logout", "Logout")} icon="logout" key="/logout" onNavigate={props.onNavigate} />);
117
- result.push(<div key="divider" style={{borderTop:"1px solid #CCC", paddingTop:2, paddingBottom:2}}></div>)
118
- result.push(<NavItem label={getLabel("wrapper.switchApp", "Switch App")} key="Switch App" icon="apps" onClick={() => { setTabIndex(1); }} />);
119
- result.push(<NavItem label={getLabel("wrapper.switchChurch", "Switch Church")} key="Switch Church" icon="church" onClick={handleSwitchChurch} />);
120
- return result;
121
- }
122
-
123
- const getProfilePic = () => {
124
- if (props.profilePicture) return props.profilePicture
125
- else return "/images/sample-profile.png";
126
- }
127
-
128
- const paperProps = {
129
- elevation: 0,
130
- sx: {
131
- overflow: "visible",
132
- filter: "drop-shadow(0px 2px 8px rgba(0,0,0,0.32))",
133
- mt: 1.5,
134
- "& .MuiAvatar-root": { width: 32, height: 32, ml: -0.5, mr: 1 },
135
- minWidth: 450
136
- }
137
- };
138
-
139
- const handleItemClick = (e: React.MouseEvent<HTMLDivElement>) => {
140
- // Handle menu item clicks if needed
141
- }
142
-
143
- const [tabIndex, setTabIndex] = React.useState(0);
144
-
145
- // Helper function to get label with fallback
146
- const getLabel = (key: string, fallback: string) => {
147
- const label = Locale.label(key);
148
- return label && label !== key ? label : fallback;
149
- };
150
-
151
- const getTabs = () => {
152
- return (
153
- <Box sx={{ borderBottom: 1, borderColor: "divider" }}>
154
- <TabPanel value={tabIndex} index={0}>
155
- {getMainLinks()}
156
- </TabPanel>
157
- <TabPanel value={tabIndex} index={1}>
158
- <NavItem label="Back" key="AppBack" icon="arrow_back" onClick={() => { setTabIndex(0); }} />
159
- <AppList currentUserChurch={props.context?.userChurch} appName={props.appName} onNavigate={props.onNavigate} />
160
- </TabPanel>
161
- <TabPanel value={tabIndex} index={2}>
162
- <div style={{ maxHeight: '70vh', overflowY: "auto" }}>
163
- <NavItem label="Back" key="ChurchBack" icon="arrow_back" onClick={() => { setTabIndex(0); }} />
164
- {(() => {
165
- // Check if userChurches is actually the userChurch object
166
- if (props.context?.userChurches && !Array.isArray(props.context.userChurches) && (props.context.userChurches as any).id) {
167
- console.error('UserMenu - ERROR: context.userChurches contains a single church object instead of an array!');
168
- const churchArray = props.context.userChurch ? [props.context.userChurch] : [];
169
- return <ChurchList userChurches={churchArray} currentUserChurch={props.context?.userChurch} context={props.context} onDelete={handleClose} onChurchChange={() => {
170
- handleClose();
171
- // Don't navigate - just close the menu and let the context update trigger re-renders
172
- }} />;
173
- }
174
-
175
- if (!props.context?.userChurches) {
176
- return <Typography sx={{ p: 2, color: 'text.secondary' }}>Loading churches...</Typography>;
177
- } else if (!Array.isArray(props.context.userChurches)) {
178
- return <Typography sx={{ p: 2, color: 'text.secondary', fontWeight: 'bold' }}>Error: Invalid church data format</Typography>;
179
- } else if (props.context.userChurches.length === 0) {
180
- return <Typography sx={{ p: 2, color: 'text.secondary' }}>No churches available</Typography>;
181
- } else {
182
- // Ensure we always pass an array
183
- const churchesArray = Array.isArray(props.context.userChurches)
184
- ? props.context.userChurches
185
- : [props.context.userChurches];
186
- return <ChurchList userChurches={churchesArray} currentUserChurch={props.context?.userChurch} context={props.context} onDelete={handleClose} onChurchChange={() => {
187
- handleClose();
188
- // Don't navigate - just close the menu and let the context update trigger re-renders
189
- }} />;
190
- }
191
- })()}
192
- </div>
193
- </TabPanel>
194
- <TabPanel value={tabIndex} index={3}>
195
- <NavItem label="Back" key="EditBack" icon="arrow_back" onClick={() => { setTabIndex(0); }} />
196
- <NavItem url={"/profile"} key="/profile" label={getLabel("wrapper.editAccount", "Edit Account")} icon="settings" onNavigate={props.onNavigate} />
197
- {getChurchProfileLink()}
198
- </TabPanel>
199
- </Box>
200
- );
201
- };
202
-
203
- const getChurchProfileLink = () => {
204
- const personId = props.context?.person?.id;
205
- if (!personId) return null;
206
-
207
- const isB1App = props.appName === "B1.church" || props.appName === "B1App" || props.appName === "B1";
208
-
209
- if (isB1App) return <NavItem url={`/my/community/${personId}`} label={getLabel("wrapper.editChurchProfile", "Edit Church Profile")} icon="church" onNavigate={props.onNavigate} />;
210
- else {
211
- const jwt = ApiHelper.getConfig("MembershipApi").jwt;
212
- const churchId = props.context?.userChurch?.church?.id;
213
- const subDomain = props.context?.userChurch?.church?.subDomain;
214
- const b1Url = CommonEnvironmentHelper.B1Root.replace("{key}", subDomain);
215
- const returnUrl = encodeURIComponent(`/my/community/${personId}`);
216
- return <NavItem url={`${b1Url}/login?jwt=${jwt}&churchId=${churchId}&returnUrl=${returnUrl}`} external={true} label={getLabel("wrapper.editChurchProfile", "Edit Church Profile")} icon="church" onNavigate={props.onNavigate} />;
217
- }
218
- };
219
-
220
- const getModals = () => {
221
- return (
222
- <>
223
- <Dialog
224
- id="private-messages-modal"
225
- open={showPM}
226
- onClose={() => {
227
- modalStateStore.setShowPM(false);
228
- }}
229
- maxWidth="md"
230
- fullWidth
231
- PaperProps={{
232
- sx: {
233
- height: '80vh',
234
- maxHeight: '700px',
235
- display: 'flex',
236
- flexDirection: 'column'
237
- }
238
- }}
239
- >
240
- <DialogTitle id="private-messages-title">{getLabel("wrapper.messages", "Messages")}</DialogTitle>
241
- <DialogContent
242
- sx={{
243
- flex: 1,
244
- display: 'flex',
245
- flexDirection: 'column',
246
- p: 0,
247
- overflow: 'hidden',
248
- minHeight: 0
249
- }}
250
- >
251
- <PrivateMessages context={props.context} refreshKey={currentRefreshKey} onUpdate={stableOnUpdate} />
252
- </DialogContent>
253
- </Dialog>
254
-
255
- <Dialog
256
- id="notifications-modal"
257
- open={showNotifications}
258
- onClose={() => {
259
- modalStateStore.setShowNotifications(false);
260
- }}
261
- maxWidth="md"
262
- fullWidth
263
- >
264
- <DialogTitle id="notifications-title">{getLabel("wrapper.notifications", "Notifications")}</DialogTitle>
265
- <DialogContent>
266
- <Notifications context={props.context} appName={props.appName} onUpdate={props.loadCounts} onNavigate={props.onNavigate} />
267
- </DialogContent>
268
- </Dialog>
269
- </>
270
- );
271
- }
272
-
273
- const totalNotifcations = directNotificationCounts.notificationCount + directNotificationCounts.pmCount;
274
-
275
- // Use a ref to track if we should update refresh key
276
- const stableRefreshKeyRef = React.useRef(refreshKey);
277
-
278
- // Set up WebSocket handlers to update refreshKey when messages arrive
279
- React.useEffect(() => {
280
- if (!props.context?.person?.id) return;
281
-
282
- const handleMessageUpdate = (data: any) => {
283
- // Only update refreshKey if a modal is open to trigger child updates
284
- if (modalStateStore.showPM || modalStateStore.showNotifications) {
285
- const newKey = Math.random();
286
- setRefreshKey(newKey);
287
- stableRefreshKeyRef.current = newKey;
288
- }
289
- };
290
-
291
- const handlePrivateMessage = (data: any) => {
292
- // Only update refreshKey if PM modal is open
293
- if (modalStateStore.showPM) {
294
- const newKey = Math.random();
295
- setRefreshKey(newKey);
296
- stableRefreshKeyRef.current = newKey;
297
- }
298
- };
299
-
300
- const handleNotification = (data: any) => {
301
- // Update refreshKey if any modal is open to trigger child updates
302
- if (modalStateStore.showPM || modalStateStore.showNotifications) {
303
- const newKey = Math.random();
304
- setRefreshKey(newKey);
305
- stableRefreshKeyRef.current = newKey;
306
- }
307
- };
308
-
309
- // Register WebSocket handlers
310
- const messageHandlerId = `UserMenu-MessageUpdate-${props.context.person.id}`;
311
- const privateMessageHandlerId = `UserMenu-PrivateMessage-${props.context.person.id}`;
312
- const notificationHandlerId = `UserMenu-Notification-${props.context.person.id}`;
313
-
314
- SocketHelper.addHandler("message", messageHandlerId, handleMessageUpdate);
315
- SocketHelper.addHandler("privateMessage", privateMessageHandlerId, handlePrivateMessage);
316
- SocketHelper.addHandler("notification", notificationHandlerId, handleNotification);
317
-
318
- // Cleanup
319
- return () => {
320
- SocketHelper.removeHandler(messageHandlerId);
321
- SocketHelper.removeHandler(privateMessageHandlerId);
322
- SocketHelper.removeHandler(notificationHandlerId);
323
- };
324
- }, [props.context?.person?.id]); // Removed showPM, showNotifications dependencies
325
-
326
-
327
- // Use current refresh key
328
- const currentRefreshKey = refreshKey;
329
-
330
- return (
331
- <>
332
- <Button id="user-menu-button" onClick={handleClick} color="inherit" aria-controls={open ? "account-menu" : undefined} aria-haspopup="true" aria-expanded={open ? "true" : undefined} style={{ textTransform: "none" }} endIcon={<Icon>expand_more</Icon>}>
333
- <Badge id="user-menu-notification-badge" badgeContent={totalNotifcations} color="error" invisible={totalNotifcations===0}>
334
- <Avatar id="user-menu-avatar" src={getProfilePic()} sx={{ width: 32, height: 32, marginRight: 1 }}></Avatar>
335
- </Badge>
336
- </Button>
337
-
338
- <Menu anchorEl={anchorEl} id="user-menu-dropdown" open={open} onClose={handleClose} onClick={(e) => { handleItemClick(e) }} slotProps={{ paper: paperProps }} transformOrigin={{ horizontal: "right", vertical: "top" }} anchorOrigin={{ horizontal: "right", vertical: "bottom" }} sx={{ "& .MuiBox-root": { borderBottom: 0 } }}>
339
- {getTabs()}
340
- </Menu>
341
- {getModals()}
342
- </>
343
- );
344
- });
345
-
346
- export const UserMenu: React.FC<Props> = React.memo((props) => {
347
- return (
348
- <CookiesProvider defaultSetOptions={{ path: '/' }}>
349
- <UserMenuContent {...props} />
350
- </CookiesProvider>
351
- );
352
- }, (prevProps, nextProps) => {
353
- // Only re-render if essential props change, ignore notification count changes completely
354
- if (prevProps.userName !== nextProps.userName) {
355
- return false;
356
- }
357
-
358
- if (prevProps.profilePicture !== nextProps.profilePicture) {
359
- return false;
360
- }
361
-
362
- if (prevProps.appName !== nextProps.appName) {
363
- return false;
364
- }
365
-
366
- // Check if context has actually changed (deep comparison of relevant parts)
367
- if (prevProps.context?.person?.id !== nextProps.context?.person?.id ||
368
- prevProps.context?.userChurch?.church?.id !== nextProps.context?.userChurch?.church?.id) {
369
- return false;
370
- }
371
-
372
- // Check if userChurches array changed in context
373
- if (prevProps.context?.userChurches?.length !== nextProps.context?.userChurches?.length) {
374
- return false;
375
- }
376
-
377
- // Check if loadCounts function reference changed (important for functionality)
378
- if (prevProps.loadCounts !== nextProps.loadCounts) {
379
- return false;
380
- }
381
-
382
- // Ignore both notificationCounts and onNavigate changes as they don't affect the component
383
- return true; // Skip re-render
384
- });
@@ -1,8 +0,0 @@
1
- export { NavItem } from "./NavItem";
2
- export { AppList } from "./AppList";
3
- export { ChurchList } from "./ChurchList";
4
- export { UserMenu } from "./UserMenu";
5
- export { NewPrivateMessage } from "./NewPrivateMessage";
6
- export { PrivateMessages } from "./PrivateMessages";
7
- export { PrivateMessageDetails } from "./PrivateMessageDetails";
8
- export { Notifications } from "./Notifications";
@@ -1,44 +0,0 @@
1
- import ReactGA from "react-ga4";
2
- import { CommonEnvironmentHelper, UserHelper } from "@churchapps/helpers";
3
-
4
- export class AnalyticsHelper {
5
-
6
- static init = () => {
7
- if (CommonEnvironmentHelper.GoogleAnalyticsTag !== "" && typeof(window)!=="undefined") {
8
- try {
9
- ReactGA.initialize([{trackingId: CommonEnvironmentHelper.GoogleAnalyticsTag}]);
10
- AnalyticsHelper.logPageView();
11
- } catch (error) {
12
- console.warn('Analytics initialization failed:', error);
13
- }
14
- }
15
- }
16
-
17
- static logPageView = () => {
18
- if (CommonEnvironmentHelper.GoogleAnalyticsTag !== "" && typeof(window)!=="undefined") {
19
- try {
20
- this.setChurchKey();
21
- ReactGA.send({ hitType: "pageview", page: window.location.pathname + window.location.search });
22
- } catch (error) {
23
- console.warn('Analytics page view logging failed:', error);
24
- }
25
- }
26
- }
27
-
28
- static logEvent = (category: string, action: string, label?:string) => {
29
- if (CommonEnvironmentHelper.GoogleAnalyticsTag !== "" && typeof(window)!=="undefined") {
30
- try {
31
- this.setChurchKey();
32
- ReactGA.event({ category, action, label });
33
- } catch (error) {
34
- console.warn('Analytics event logging failed:', error);
35
- }
36
- }
37
- }
38
-
39
- private static setChurchKey = () => {
40
- const churchKey = UserHelper?.currentUserChurch?.church?.subDomain;
41
- if (churchKey) ReactGA.set({church_key: churchKey });
42
- }
43
-
44
- }
@@ -1,73 +0,0 @@
1
-
2
- export type AppearanceInterface = { wrapperBackground?: string, logoLight?: string, logoDark?: string, favicon_400x400?: string, favicon_16x16?: string, ogImage?: string }
3
-
4
- export class AppearanceHelper {
5
-
6
- public static getLogoDark(appearanceSettings: AppearanceInterface, defaultLogo: string) {
7
- return (appearanceSettings?.logoDark) ? appearanceSettings.logoDark : defaultLogo;
8
- }
9
-
10
- public static getLogoLight(appearanceSettings: AppearanceInterface, defaultLogo: string) {
11
- return (appearanceSettings?.logoLight) ? appearanceSettings.logoLight : defaultLogo;
12
- }
13
-
14
- public static getFavicon(appearanceSettings: AppearanceInterface, size: "400" | "16") {
15
- if (size === "400") {
16
- return appearanceSettings?.favicon_400x400;
17
- }
18
-
19
- if (size === "16") {
20
- return appearanceSettings?.favicon_16x16;
21
- }
22
-
23
- return;
24
- }
25
-
26
- public static getLogo(appearanceSettings: AppearanceInterface, defaultLogoLight: string, defaultLogoDark: string, backgroundColor: string) {
27
- const isDark = (appearanceSettings.logoDark) ? this.isDark(backgroundColor) : false;
28
- if (isDark) return this.getLogoDark(appearanceSettings, defaultLogoDark);
29
- else return this.getLogoLight(appearanceSettings, defaultLogoLight);
30
- }
31
-
32
- public static getLogoByTextColor(logoLight: string, logoDark: string, textColor: string) {
33
- return (this.isDark(textColor)) ? logoLight : logoDark;
34
- }
35
-
36
- private static isDark(backgroundColor: string) {
37
- let valid = false;
38
- let r = 0;
39
- let g = 0;
40
- let b = 0;
41
-
42
- if (backgroundColor.match(/#[0-9a-fA-F{6}]/)) {
43
- r = this.getHexValue(backgroundColor.substring(1, 2));
44
- g = this.getHexValue(backgroundColor.substring(3, 4));
45
- b = this.getHexValue(backgroundColor.substring(5, 6));
46
- valid = true;
47
- } else if (backgroundColor.match(/#[0-9a-fA-F{3}]/)) {
48
- r = this.getHexValue(backgroundColor.substring(1, 1));
49
- g = this.getHexValue(backgroundColor.substring(2, 2));
50
- b = this.getHexValue(backgroundColor.substring(3, 3));
51
- valid = true;
52
- }
53
-
54
- if (!valid) return false;
55
- else {
56
- //HSP brightness formula. Some colors have a bigger impact on our perceived brightness than others.
57
- const rWeight = .299 * Math.pow(r, 2);
58
- const gWeight = .587 * Math.pow(g, 2);
59
- const bWeight = .114 * Math.pow(b, 2);
60
- const brightness = Math.sqrt(rWeight + gWeight + bWeight);
61
- //return brightness < 128; //
62
- return brightness < 156;
63
- }
64
-
65
- }
66
-
67
- private static getHexValue(hex: string) {
68
- let result = parseInt(hex, 16);
69
- if (hex.length === 1) result = result * 16;
70
- return result;
71
- }
72
-
73
- }
@@ -1,87 +0,0 @@
1
- import { UniqueIdHelper } from "./UniqueIdHelper";
2
-
3
- export class ArrayHelper {
4
-
5
- static getIds(array: any[], propertyName: string) {
6
- const result: string[] = [];
7
- for (const item of array) {
8
- const id = item[propertyName]?.toString();
9
- if (!UniqueIdHelper.isMissing(id) && result.indexOf(id) === -1) result.push(id);
10
- }
11
- return result;
12
- }
13
-
14
- static sortBy(array: any[], propertyName: string, descending: boolean = false) {
15
- array.sort((a, b) => {
16
- const valA = a[propertyName];
17
- const valB = b[propertyName];
18
- if (valA < valB) return descending ? 1 : -1;
19
- else return descending ? -1 : 1;
20
- });
21
- }
22
-
23
- static getIndex(array: any[], propertyName: string, value: any) {
24
- for (let i = 0; i < array.length; i++) {
25
- const item = array[i];
26
- if (ArrayHelper.compare(item, propertyName, value)) return i;
27
- }
28
- return -1;
29
- }
30
-
31
- static getOne(array: any[], propertyName: string, value: any) {
32
- for (const item of array || []) if (ArrayHelper.compare(item, propertyName, value)) return item;
33
- return null
34
- }
35
-
36
- static getAll(array: any[], propertyName: string, value: any) {
37
- const result: any[] = []
38
- for (const item of array || []) {
39
- if (ArrayHelper.compare(item, propertyName, value)) result.push(item);
40
- }
41
- return result;
42
- }
43
-
44
- static getAllArray(array: any[], propertyName: string, values: any[]) {
45
- const result: any[] = []
46
- for (const item of array || []) if (values.indexOf(item[propertyName]) > -1) result.push(item);
47
- return result;
48
- }
49
-
50
- static getAllContaining(array: any[], propertyName: string, value: any) {
51
- const result: any[] = []
52
- for (const item of array) if (item[propertyName].toString().toLowerCase().indexOf(value.toLowerCase()) > -1) result.push(item);
53
- return result;
54
- }
55
-
56
- private static compare(item: any, propertyName: string, value: any) {
57
- const propChain = propertyName.split(".");
58
- if (propChain.length === 1) return item[propertyName] === value;
59
- else {
60
- let obj = item;
61
- for (let i = 0; i < propChain.length - 1; i++) {
62
- if (obj) obj = obj[propChain[i]];
63
- }
64
- return obj[propChain[propChain.length - 1]] === value;
65
- }
66
- }
67
-
68
- static getUniqueValues(array: any[], propertyName: string) {
69
- const result: any[] = [];
70
-
71
- for (const item of array) {
72
- const val = (propertyName.indexOf(".") === -1) ? item[propertyName] : this.getDeepValue(item, propertyName)
73
- if (result.indexOf(val) === -1) result.push(val);
74
- }
75
- return result;
76
- }
77
-
78
- static getDeepValue(item: any, propertyName: string) {
79
- const propertyNames = propertyName.split(".");
80
- let result: any = item;
81
- propertyNames.forEach(name => {
82
- result = result[name];
83
- });
84
- return result;
85
- }
86
-
87
- }
@@ -1,10 +0,0 @@
1
- export class CurrencyHelper {
2
- static formatCurrency(amount: number) {
3
- const formatter = new Intl.NumberFormat("en-US", {
4
- style: "currency",
5
- currency: "USD",
6
- minimumFractionDigits: 2
7
- });
8
- return formatter.format(amount);
9
- }
10
- }