@churchapps/apphelper 0.6.14 → 0.6.16

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 (215) 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.map +1 -1
  21. package/dist/components/ImageEditor.js +10 -10
  22. package/dist/components/ImageEditor.js.map +1 -1
  23. package/dist/components/InputBox.d.ts +2 -1
  24. package/dist/components/InputBox.d.ts.map +1 -1
  25. package/dist/components/InputBox.js +5 -7
  26. package/dist/components/InputBox.js.map +1 -1
  27. package/dist/components/Loading.d.ts.map +1 -1
  28. package/dist/components/Loading.js.map +1 -1
  29. package/dist/components/PageHeader.js +14 -14
  30. package/dist/components/PersonAvatar.d.ts.map +1 -1
  31. package/dist/components/PersonAvatar.js +9 -16
  32. package/dist/components/PersonAvatar.js.map +1 -1
  33. package/dist/components/QuestionEdit.js +3 -3
  34. package/dist/components/QuestionEdit.js.map +1 -1
  35. package/dist/components/SmallButton.d.ts.map +1 -1
  36. package/dist/components/SmallButton.js +2 -1
  37. package/dist/components/SmallButton.js.map +1 -1
  38. package/dist/components/SupportModal.js.map +1 -1
  39. package/dist/components/gallery/GalleryModal.js +10 -10
  40. package/dist/components/gallery/GalleryModal.js.map +1 -1
  41. package/dist/components/gallery/StockPhotos.js +12 -12
  42. package/dist/components/gallery/StockPhotos.js.map +1 -1
  43. package/dist/components/header/Banner.d.ts.map +1 -1
  44. package/dist/components/header/Banner.js.map +1 -1
  45. package/dist/components/header/PrimaryMenu.js +4 -4
  46. package/dist/components/header/PrimaryMenu.js.map +1 -1
  47. package/dist/components/header/SecondaryMenu.d.ts.map +1 -1
  48. package/dist/components/header/SecondaryMenu.js.map +1 -1
  49. package/dist/components/header/SecondaryMenuAlt.d.ts.map +1 -1
  50. package/dist/components/header/SecondaryMenuAlt.js.map +1 -1
  51. package/dist/components/header/SiteHeader.d.ts.map +1 -1
  52. package/dist/components/header/SiteHeader.js +7 -7
  53. package/dist/components/header/SiteHeader.js.map +1 -1
  54. package/dist/components/header/SupportDrawer.js +1 -1
  55. package/dist/components/header/SupportDrawer.js.map +1 -1
  56. package/dist/components/notes/AddNote.js +14 -14
  57. package/dist/components/notes/Note.d.ts.map +1 -1
  58. package/dist/components/notes/Note.js +7 -7
  59. package/dist/components/notes/Note.js.map +1 -1
  60. package/dist/components/notes/Notes.d.ts.map +1 -1
  61. package/dist/components/notes/Notes.js +4 -4
  62. package/dist/components/notes/Notes.js.map +1 -1
  63. package/dist/components/wrapper/AppList.d.ts.map +1 -1
  64. package/dist/components/wrapper/AppList.js.map +1 -1
  65. package/dist/components/wrapper/ChurchList.js +5 -5
  66. package/dist/components/wrapper/ChurchList.js.map +1 -1
  67. package/dist/components/wrapper/NavItem.js +1 -1
  68. package/dist/components/wrapper/NavItem.js.map +1 -1
  69. package/dist/components/wrapper/NewPrivateMessage.d.ts.map +1 -1
  70. package/dist/components/wrapper/NewPrivateMessage.js +7 -6
  71. package/dist/components/wrapper/NewPrivateMessage.js.map +1 -1
  72. package/dist/components/wrapper/Notifications.d.ts.map +1 -1
  73. package/dist/components/wrapper/Notifications.js +12 -14
  74. package/dist/components/wrapper/Notifications.js.map +1 -1
  75. package/dist/components/wrapper/PrivateMessageDetails.js +10 -10
  76. package/dist/components/wrapper/PrivateMessages.d.ts.map +1 -1
  77. package/dist/components/wrapper/PrivateMessages.js +119 -123
  78. package/dist/components/wrapper/PrivateMessages.js.map +1 -1
  79. package/dist/components/wrapper/UserMenu.d.ts.map +1 -1
  80. package/dist/components/wrapper/UserMenu.js +15 -15
  81. package/dist/components/wrapper/UserMenu.js.map +1 -1
  82. package/dist/helpers/AnalyticsHelper.d.ts.map +1 -1
  83. package/dist/helpers/AnalyticsHelper.js +3 -3
  84. package/dist/helpers/AnalyticsHelper.js.map +1 -1
  85. package/dist/helpers/ArrayHelper.d.ts.map +1 -1
  86. package/dist/helpers/ArrayHelper.js.map +1 -1
  87. package/dist/helpers/CurrencyHelper.d.ts +4 -0
  88. package/dist/helpers/CurrencyHelper.d.ts.map +1 -1
  89. package/dist/helpers/CurrencyHelper.js +65 -0
  90. package/dist/helpers/CurrencyHelper.js.map +1 -1
  91. package/dist/helpers/ErrorHelper.d.ts.map +1 -1
  92. package/dist/helpers/ErrorHelper.js.map +1 -1
  93. package/dist/helpers/EventHelper.d.ts.map +1 -1
  94. package/dist/helpers/EventHelper.js +1 -1
  95. package/dist/helpers/EventHelper.js.map +1 -1
  96. package/dist/helpers/FileHelper.d.ts.map +1 -1
  97. package/dist/helpers/FileHelper.js +3 -1
  98. package/dist/helpers/FileHelper.js.map +1 -1
  99. package/dist/helpers/Locale.d.ts.map +1 -1
  100. package/dist/helpers/Locale.js +10 -16
  101. package/dist/helpers/Locale.js.map +1 -1
  102. package/dist/helpers/NotificationService.js +4 -4
  103. package/dist/helpers/PersonHelper.d.ts.map +1 -1
  104. package/dist/helpers/PersonHelper.js +5 -4
  105. package/dist/helpers/PersonHelper.js.map +1 -1
  106. package/dist/helpers/SlugHelper.d.ts.map +1 -1
  107. package/dist/helpers/SlugHelper.js +5 -3
  108. package/dist/helpers/SlugHelper.js.map +1 -1
  109. package/dist/helpers/SocketHelper.d.ts.map +1 -1
  110. package/dist/helpers/SocketHelper.js +5 -10
  111. package/dist/helpers/SocketHelper.js.map +1 -1
  112. package/dist/helpers/UniqueIdHelper.d.ts.map +1 -1
  113. package/dist/helpers/UniqueIdHelper.js +132 -7
  114. package/dist/helpers/UniqueIdHelper.js.map +1 -1
  115. package/dist/helpers/UserHelper.d.ts.map +1 -1
  116. package/dist/helpers/UserHelper.js +2 -2
  117. package/dist/helpers/UserHelper.js.map +1 -1
  118. package/dist/hooks/useMountedState.d.ts.map +1 -1
  119. package/dist/hooks/useMountedState.js +1 -1
  120. package/dist/hooks/useMountedState.js.map +1 -1
  121. package/dist/hooks/useNotifications.d.ts +2 -2
  122. package/dist/hooks/useNotifications.js +5 -5
  123. package/dist/public/locales/de.json +15 -7
  124. package/dist/public/locales/es.json +193 -190
  125. package/dist/public/locales/fr.json +15 -7
  126. package/dist/public/locales/hi.json +15 -7
  127. package/dist/public/locales/it.json +15 -7
  128. package/dist/public/locales/ko.json +15 -7
  129. package/dist/public/locales/no.json +15 -7
  130. package/dist/public/locales/pt.json +15 -7
  131. package/dist/public/locales/ru.json +15 -7
  132. package/dist/public/locales/tl.json +15 -7
  133. package/dist/public/locales/zh.json +15 -7
  134. package/package.json +106 -73
  135. package/dist/helpers/DateHelper.d.ts +0 -18
  136. package/dist/helpers/DateHelper.d.ts.map +0 -1
  137. package/dist/helpers/DateHelper.js +0 -102
  138. package/dist/helpers/DateHelper.js.map +0 -1
  139. package/public/css/cropper.css +0 -309
  140. package/public/css/styles.css +0 -112
  141. package/public/locales/de.json +0 -273
  142. package/public/locales/en.json +0 -281
  143. package/public/locales/es.json +0 -278
  144. package/public/locales/fr.json +0 -273
  145. package/public/locales/hi.json +0 -273
  146. package/public/locales/it.json +0 -273
  147. package/public/locales/ko.json +0 -273
  148. package/public/locales/no.json +0 -273
  149. package/public/locales/pt.json +0 -273
  150. package/public/locales/ru.json +0 -273
  151. package/public/locales/tl.json +0 -273
  152. package/public/locales/zh.json +0 -273
  153. package/src/components/DisplayBox.tsx +0 -83
  154. package/src/components/ErrorMessages.tsx +0 -28
  155. package/src/components/ExportLink.tsx +0 -81
  156. package/src/components/FloatingSupport.tsx +0 -18
  157. package/src/components/FormCardPayment.tsx +0 -184
  158. package/src/components/FormSubmissionEdit.tsx +0 -168
  159. package/src/components/HelpIcon.tsx +0 -12
  160. package/src/components/ImageEditor.tsx +0 -167
  161. package/src/components/InputBox.tsx +0 -96
  162. package/src/components/Loading.tsx +0 -31
  163. package/src/components/PageHeader.tsx +0 -111
  164. package/src/components/PersonAvatar.tsx +0 -78
  165. package/src/components/QuestionEdit.tsx +0 -99
  166. package/src/components/SmallButton.tsx +0 -42
  167. package/src/components/SupportModal.tsx +0 -32
  168. package/src/components/TabPanel.tsx +0 -28
  169. package/src/components/gallery/GalleryModal.tsx +0 -170
  170. package/src/components/gallery/StockPhotos.tsx +0 -96
  171. package/src/components/gallery/index.ts +0 -2
  172. package/src/components/header/Banner.tsx +0 -11
  173. package/src/components/header/PrimaryMenu.tsx +0 -101
  174. package/src/components/header/SecondaryMenu.tsx +0 -23
  175. package/src/components/header/SecondaryMenuAlt.tsx +0 -40
  176. package/src/components/header/SiteHeader.tsx +0 -205
  177. package/src/components/header/SupportDrawer.tsx +0 -112
  178. package/src/components/header/index.tsx +0 -2
  179. package/src/components/index.tsx +0 -20
  180. package/src/components/notes/AddNote.tsx +0 -185
  181. package/src/components/notes/Note.tsx +0 -84
  182. package/src/components/notes/Notes.tsx +0 -208
  183. package/src/components/notes/index.ts +0 -3
  184. package/src/components/wrapper/AppList.tsx +0 -18
  185. package/src/components/wrapper/ChurchList.tsx +0 -145
  186. package/src/components/wrapper/NavItem.tsx +0 -47
  187. package/src/components/wrapper/NewPrivateMessage.tsx +0 -249
  188. package/src/components/wrapper/Notifications.tsx +0 -220
  189. package/src/components/wrapper/PrivateMessageDetails.tsx +0 -106
  190. package/src/components/wrapper/PrivateMessages.tsx +0 -574
  191. package/src/components/wrapper/UserMenu.tsx +0 -384
  192. package/src/components/wrapper/index.tsx +0 -8
  193. package/src/helpers/AnalyticsHelper.ts +0 -44
  194. package/src/helpers/AppearanceHelper.ts +0 -73
  195. package/src/helpers/ArrayHelper.ts +0 -87
  196. package/src/helpers/CurrencyHelper.ts +0 -10
  197. package/src/helpers/DateHelper.ts +0 -104
  198. package/src/helpers/ErrorHelper.ts +0 -41
  199. package/src/helpers/EventHelper.ts +0 -49
  200. package/src/helpers/FileHelper.ts +0 -31
  201. package/src/helpers/Locale.ts +0 -457
  202. package/src/helpers/NotificationService.ts +0 -233
  203. package/src/helpers/PersonHelper.ts +0 -62
  204. package/src/helpers/SlugHelper.ts +0 -27
  205. package/src/helpers/SocketHelper.ts +0 -183
  206. package/src/helpers/UniqueIdHelper.ts +0 -36
  207. package/src/helpers/UserHelper.ts +0 -103
  208. package/src/helpers/createEmotionCache.ts +0 -17
  209. package/src/helpers/index.ts +0 -58
  210. package/src/hooks/index.ts +0 -3
  211. package/src/hooks/useMountedState.ts +0 -18
  212. package/src/hooks/useNotifications.ts +0 -95
  213. package/src/index.ts +0 -3
  214. package/src/types/interface-extensions.d.ts +0 -12
  215. package/tsconfig.json +0 -32
@@ -1,145 +0,0 @@
1
- "use client";
2
-
3
- import React, { useState } from "react";
4
- import { LoginUserChurchInterface, UserContextInterface, ArrayHelper } from "@churchapps/helpers";
5
- import { ApiHelper } from "@churchapps/helpers";
6
- import { UserHelper } from "../../helpers/UserHelper";
7
- import { NavItem } from "./NavItem";
8
- import { Locale } from "../../helpers";
9
-
10
- export interface Props { userChurches: LoginUserChurchInterface[], currentUserChurch: LoginUserChurchInterface, context: UserContextInterface, onDelete?: () => void, onChurchChange?: () => void }
11
-
12
- export const ChurchList: React.FC<Props> = props => {
13
- const [userChurches, setUserChurches] = useState(() => {
14
- try {
15
- // If we have currentUserChurch, use it as fallback
16
- if (props.currentUserChurch && (!props.userChurches || !Array.isArray(props.userChurches))) {
17
- return [props.currentUserChurch];
18
- }
19
-
20
- let churches = props.userChurches;
21
-
22
- // Ensure we have an array
23
- if (!Array.isArray(churches)) {
24
- console.warn('ChurchList - Expected array but got:', typeof churches, churches);
25
- // If it's a plain church object and we have currentUserChurch, use that
26
- if (props.currentUserChurch) {
27
- return [props.currentUserChurch];
28
- }
29
- churches = [];
30
- }
31
-
32
- // Filter for valid userChurch objects (should have church property)
33
- const validChurches = churches.filter(uc => {
34
- const isValid = uc && uc.church && uc.church.id;
35
- if (!isValid) {
36
- console.warn('ChurchList - Invalid church structure:', uc);
37
- }
38
- return isValid;
39
- });
40
-
41
- // If no valid churches but we have currentUserChurch, use it
42
- if (validChurches.length === 0 && props.currentUserChurch) {
43
- return [props.currentUserChurch];
44
- }
45
-
46
- return validChurches;
47
- } catch (error) {
48
- console.error('ChurchList - Error processing churches:', error);
49
- // Last resort: if we have currentUserChurch, use it
50
- if (props.currentUserChurch) {
51
- return [props.currentUserChurch];
52
- }
53
- return [];
54
- }
55
- });
56
-
57
- // Update local state when props change
58
- React.useEffect(() => {
59
- try {
60
- // If we have currentUserChurch, use it as fallback
61
- if (props.currentUserChurch && (!props.userChurches || !Array.isArray(props.userChurches))) {
62
- setUserChurches([props.currentUserChurch]);
63
- return;
64
- }
65
-
66
- let churches = props.userChurches;
67
-
68
- // Ensure we have an array
69
- if (!Array.isArray(churches)) {
70
- if (props.currentUserChurch) {
71
- setUserChurches([props.currentUserChurch]);
72
- return;
73
- }
74
- churches = [];
75
- }
76
-
77
- // Filter for valid userChurch objects
78
- const validChurches = churches.filter(uc => uc && uc.church && uc.church.id);
79
-
80
- // If no valid churches but we have currentUserChurch, use it
81
- if (validChurches.length === 0 && props.currentUserChurch) {
82
- setUserChurches([props.currentUserChurch]);
83
- } else {
84
- setUserChurches(validChurches);
85
- }
86
- } catch (error) {
87
- console.error('ChurchList useEffect - Error updating churches:', error);
88
- if (props.currentUserChurch) {
89
- setUserChurches([props.currentUserChurch]);
90
- } else {
91
- setUserChurches([]);
92
- }
93
- }
94
- }, [props.userChurches, props.currentUserChurch]);
95
-
96
- const handleDelete = async (uc: LoginUserChurchInterface) => {
97
- // Helper function to get label with fallback
98
- const getLabel = (key: string, fallback: string) => {
99
- const label = Locale.label(key);
100
- return label && label !== key ? label : fallback;
101
- };
102
-
103
- const confirmMessage = getLabel("wrapper.sureRemoveChurch", "Are you sure you wish to delete this church? You will no longer be a member of {}.").replace("{}", uc.church.name?.toUpperCase());
104
- if (window.confirm(confirmMessage)) {
105
- await ApiHelper.delete(`/userchurch/record/${props.context.user.id}/${uc.church.id}/${uc.person.id}`, "MembershipApi");
106
- await ApiHelper.delete(`/rolemembers/self/${uc.church.id}/${props.context.user.id}`, "MembershipApi");
107
- // remove the same from userChurches
108
- const idx = ArrayHelper.getIndex(UserHelper.userChurches, "church.id", uc.church.id);
109
- if (idx > -1) UserHelper.userChurches.splice(idx, 1);
110
- props?.onDelete();
111
- }
112
- }
113
-
114
- // Helper function to get label with fallback
115
- const getLabel = (key: string, fallback: string) => {
116
- const label = Locale.label(key);
117
- return label && label !== key ? label : fallback;
118
- };
119
-
120
- let result: React.ReactElement[] = [];
121
-
122
- userChurches.forEach(uc => {
123
- const userChurch = uc;
124
- const churchName = uc.church.name;
125
- result.push(<NavItem
126
- key={userChurch.church.id}
127
- selected={(uc.church.id === props.currentUserChurch.church.id) && true}
128
- onClick={async () => {
129
- await UserHelper.selectChurch(props.context, userChurch.church.id, null);
130
-
131
- // Call the onChurchChange callback if provided
132
- if (props.onChurchChange) {
133
- props.onChurchChange();
134
- }
135
- }}
136
- label={churchName || "Unknown"}
137
- icon="church"
138
- deleteIcon={uc.church.id !== props.currentUserChurch.church.id ? "delete" : null}
139
- deleteLabel={getLabel("wrapper.deleteChurch", "Delete")}
140
- deleteFunction={() => { handleDelete(uc); }}
141
- />);
142
- });
143
-
144
- return <div id="church-list">{result}</div>;
145
- };
@@ -1,47 +0,0 @@
1
- "use client";
2
-
3
- import React from "react";
4
- import { Badge, Icon, IconButton, ListItemButton, ListItemIcon, ListItemText, Tooltip } from "@mui/material";
5
-
6
- interface Props {
7
- badgeCount?: number;
8
- url?: string;
9
- target?: string;
10
- label: string;
11
- icon: string;
12
- onClick?: () => void;
13
- onNavigate?: (url: string) => void;
14
- external?: boolean;
15
- selected?: boolean;
16
- deleteIcon?: string;
17
- deleteLabel?: string;
18
- deleteFunction?: () => void;
19
- }
20
-
21
- export const NavItem: React.FC<Props> = (props) => {
22
-
23
- const getIcon = () => {
24
- if (props.badgeCount && props.badgeCount > 0) return <Badge badgeContent={props.badgeCount} color="error"><Icon>{props.icon}</Icon></Badge>
25
- else return <Icon>{props.icon}</Icon>
26
- }
27
-
28
- const getLinkContents = () => (<ListItemButton data-testid={`nav-item-${props.label?.toLowerCase()?.replace(/\s+/g, '-')}`}>
29
- <Tooltip title={props.label || ""} arrow placement="right">
30
- <ListItemIcon sx={{ minWidth: "40px" }}>{getIcon()}</ListItemIcon>
31
- </Tooltip>
32
- <ListItemText primary={props.label} />
33
- {props?.deleteIcon
34
- ? (
35
- <Tooltip title={props.deleteLabel || ""} arrow placement="left">
36
- <IconButton onClick={props.deleteFunction ? (e) => { e.stopPropagation(); e.preventDefault(); props.deleteFunction() } : null} sx={{ color: "#f7a9a9" }} size="small" aria-label={props.deleteLabel || "Delete item"} data-testid="nav-item-delete">
37
- <Icon sx={{ fontSize: 19 }}>delete</Icon>
38
- </IconButton>
39
- </Tooltip>
40
- )
41
- : ""}
42
- </ListItemButton>)
43
-
44
- if (props.external) return (<a href={props.url} target={props.target} rel="noreferrer" style={{ textDecoration: "none" }} className={(props.selected) ? "selected" : ""} onClick={(e) => { e.preventDefault(); props.onClick ? props.onClick() : window.location.href = props.url }}>{getLinkContents()}</a>)
45
- else return (<a href="about:blank" style={{ textDecoration: "none" }} className={(props.selected) ? "selected" : ""} onClick={(e) => { e.preventDefault(); props.onClick ? props.onClick() : props.onNavigate(props.url) }}>{getLinkContents()}</a>)
46
- //else return (<StyledNavLink to={props.url || "about:blank"} target={props.target} className={(props.selected) ? "selected" : ""} onClick={(e) => { e.preventDefault(); (props.onClick) ? props.onClick() : props.onNavigate(props.url)}}>{getLinkContents()}</StyledNavLink>)
47
- };
@@ -1,249 +0,0 @@
1
- "use client";
2
-
3
- import {
4
- Button,
5
- TextField,
6
- Paper,
7
- Box,
8
- Typography,
9
- Stack,
10
- IconButton,
11
- List,
12
- ListItemAvatar,
13
- ListItemText,
14
- ListItemButton,
15
- InputAdornment,
16
- Divider,
17
- Skeleton
18
- } from "@mui/material";
19
- import {
20
- ArrowBack as ArrowBackIcon,
21
- PersonSearch as PersonSearchIcon
22
- } from "@mui/icons-material";
23
- import React, { useEffect, useState } from "react";
24
- import { ApiHelper, Locale } from "../../helpers";
25
- import { ConversationInterface, PersonInterface, PrivateMessageInterface, UserContextInterface } from "@churchapps/helpers";
26
- import { AddNote } from "../notes/AddNote";
27
- import { PersonAvatar } from "../PersonAvatar";
28
-
29
- interface Props {
30
- context: UserContextInterface;
31
- onSelectMessage: (pm: PrivateMessageInterface) => void
32
- onBack: () => void
33
- selectedPerson?: PersonInterface
34
- }
35
-
36
- export const NewPrivateMessage: React.FC<Props> = (props) => {
37
-
38
- const [searchText, setSearchText] = React.useState("");
39
- const [searchResults, setSearchResults] = React.useState([]);
40
- const [selectedPerson, setSelectedPerson] = React.useState<PersonInterface>(null);
41
-
42
-
43
- const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
44
- setSearchText(e.currentTarget.value);
45
- }
46
-
47
- /*
48
- const handleKeyDown = (e: React.KeyboardEvent<any>) => {
49
- //if (e.key === "Enter") { e.preventDefault(); handleSubmit(null); }
50
- }
51
- */
52
-
53
- const handlePersonSelected = async (person: PersonInterface) => {
54
- try {
55
- const existing: PrivateMessageInterface = await ApiHelper.get("/privateMessages/existing/" + person.id, "MessagingApi");
56
- if (existing?.id) {
57
- existing.person = person;
58
- props.onSelectMessage(existing);
59
- return;
60
- }
61
- } catch (error) {
62
- // No existing conversation found, continue to create new one
63
- }
64
- setSelectedPerson(person);
65
- }
66
-
67
-
68
- const handleNoteAdded = () => {
69
- handlePersonSelected(selectedPerson);
70
- }
71
-
72
- const createConversation = async () => {
73
- const conv: ConversationInterface = { allowAnonymousPosts: false, contentType: "privateMessage", contentId: props.context.person.id, title: props.context.person.name.display + " " + Locale.label("wrapper.privateMessage", "Private Message"), visibility: "hidden" }
74
- const result: ConversationInterface[] = await ApiHelper.post("/conversations", [conv], "MessagingApi");
75
-
76
- const pm: PrivateMessageInterface = {
77
- fromPersonId: props.context.person.id,
78
- toPersonId: selectedPerson.id,
79
- conversationId: result[0].id
80
- }
81
- const privateMessages: PrivateMessageInterface[] = await ApiHelper.post("/privateMessages", [pm], "MessagingApi");
82
- return privateMessages[0].conversationId;
83
- }
84
-
85
-
86
- useEffect(() => {
87
- if (props.selectedPerson) handlePersonSelected(props.selectedPerson);
88
- }, [props.selectedPerson]);
89
-
90
-
91
- const [isSearching, setIsSearching] = useState(false);
92
-
93
- const handleSearchSubmit = async (e: React.MouseEvent) => {
94
- if (e !== null) e.preventDefault();
95
- if (!searchText.trim()) return;
96
-
97
- setIsSearching(true);
98
- let term = escape(searchText.trim());
99
- const data = await ApiHelper.get("/people/search?term=" + term, "MembershipApi");
100
- setSearchResults(data);
101
- setIsSearching(false);
102
- };
103
-
104
- if (!selectedPerson) return (
105
- <Paper elevation={0} sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
106
- <Box sx={{ p: 2, borderBottom: 1, borderColor: 'divider' }}>
107
- <Stack direction="row" alignItems="center" spacing={2}>
108
- <IconButton onClick={props.onBack}>
109
- <ArrowBackIcon />
110
- </IconButton>
111
- <Typography variant="h6" component="h2">
112
- {Locale.label("wrapper.newPrivateMessage", "New Private Message")}
113
- </Typography>
114
- </Stack>
115
- </Box>
116
-
117
- <Box sx={{ p: 3 }}>
118
- <Stack spacing={3}>
119
- <Box>
120
- <Typography variant="body1" color="textSecondary" gutterBottom>
121
- {Locale.label("wrapper.searchForPerson", "Search for a person to message")}
122
- </Typography>
123
- <TextField
124
- fullWidth
125
- placeholder="Search by name..."
126
- id="searchText"
127
- data-testid="search-input"
128
- value={searchText}
129
- onChange={handleChange}
130
- onKeyDown={(e) => {
131
- e.stopPropagation();
132
- if (e.key === 'Enter') handleSearchSubmit(null);
133
- }}
134
- InputProps={{
135
- startAdornment: (
136
- <InputAdornment position="start">
137
- <PersonSearchIcon color="action" />
138
- </InputAdornment>
139
- ),
140
- endAdornment: (
141
- <InputAdornment position="end">
142
- <Button
143
- variant="contained"
144
- size="small"
145
- onClick={handleSearchSubmit}
146
- disabled={!searchText.trim() || isSearching}
147
- >
148
- {Locale.label("common.search", "Search")}
149
- </Button>
150
- </InputAdornment>
151
- )
152
- }}
153
- sx={{ mt: 1 }}
154
- />
155
- </Box>
156
-
157
- {isSearching && (
158
- <Box>
159
- {[...Array(3)].map((_, index) => (
160
- <Box key={`skeleton-${index}`} sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
161
- <Skeleton variant="circular" width={48} height={48} sx={{ mr: 2 }} />
162
- <Skeleton variant="text" width="60%" height={24} />
163
- </Box>
164
- ))}
165
- </Box>
166
- )}
167
-
168
- {!isSearching && searchResults.length > 0 && (
169
- <Box>
170
- <Typography variant="subtitle2" color="textSecondary" gutterBottom>
171
- {searchResults.length} {searchResults.length === 1 ? 'person' : 'people'} found
172
- </Typography>
173
- <List sx={{ bgcolor: 'background.paper', borderRadius: 1 }}>
174
- {searchResults.map((person, index) => (
175
- <React.Fragment key={person.id}>
176
- <ListItemButton
177
- onClick={() => handlePersonSelected(person)}
178
- sx={{ py: 2 }}
179
- >
180
- <ListItemAvatar>
181
- <PersonAvatar person={person} size="small" />
182
- </ListItemAvatar>
183
- <ListItemText
184
- primary={person.name.display}
185
- secondary={person.contactInfo?.email || ''}
186
- />
187
- </ListItemButton>
188
- {index < searchResults.length - 1 && <Divider />}
189
- </React.Fragment>
190
- ))}
191
- </List>
192
- </Box>
193
- )}
194
-
195
- {!isSearching && searchText && searchResults.length === 0 && (
196
- <Box sx={{ textAlign: 'center', py: 4 }}>
197
- <PersonSearchIcon sx={{ fontSize: 48, color: 'grey.400', mb: 2 }} />
198
- <Typography variant="h6" color="textSecondary">
199
- No people found
200
- </Typography>
201
- <Typography variant="body2" color="textSecondary">
202
- Try searching with a different name
203
- </Typography>
204
- </Box>
205
- )}
206
- </Stack>
207
- </Box>
208
- </Paper>
209
- );
210
- else {
211
- return (
212
- <Paper elevation={0} sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
213
- <Box sx={{ p: 2, borderBottom: 1, borderColor: 'divider' }}>
214
- <Stack direction="row" alignItems="center" spacing={2}>
215
- <IconButton onClick={props.onBack}>
216
- <ArrowBackIcon />
217
- </IconButton>
218
- <Typography variant="h6" component="h2">
219
- {Locale.label("wrapper.newPrivateMessage", "New Private Message")}
220
- </Typography>
221
- </Stack>
222
- </Box>
223
-
224
- <Box sx={{ p: 3 }}>
225
- <Stack direction="row" spacing={2} alignItems="center" sx={{ mb: 3 }}>
226
- <PersonAvatar person={selectedPerson} size="medium" />
227
- <Box>
228
- <Typography variant="subtitle1" fontWeight="medium">
229
- {selectedPerson.name.display}
230
- </Typography>
231
- {selectedPerson.contactInfo?.email && (
232
- <Typography variant="body2" color="textSecondary">
233
- {selectedPerson.contactInfo.email}
234
- </Typography>
235
- )}
236
- </Box>
237
- </Stack>
238
- <Divider sx={{ mb: 3 }} />
239
- <AddNote
240
- context={props.context}
241
- conversationId={null}
242
- onUpdate={handleNoteAdded}
243
- createConversation={createConversation}
244
- />
245
- </Box>
246
- </Paper>
247
- )
248
- }
249
- }
@@ -1,220 +0,0 @@
1
- "use client";
2
-
3
- import React, { useState } from "react";
4
- import { ApiHelper } from "@churchapps/helpers";
5
- import {
6
- Box,
7
- Stack,
8
- Typography,
9
- Paper,
10
- List,
11
- ListItem,
12
- ListItemAvatar,
13
- ListItemText,
14
- Avatar,
15
- Chip,
16
- Divider,
17
- IconButton,
18
- Skeleton
19
- } from "@mui/material";
20
- import {
21
- Notifications as NotificationsIcon,
22
- Task as TaskIcon,
23
- Assignment as AssignmentIcon,
24
- OpenInNew as OpenInNewIcon
25
- } from "@mui/icons-material";
26
- import { NotificationInterface, UserContextInterface } from "@churchapps/helpers";
27
- import { DateHelper } from "../../helpers";
28
-
29
- interface Props {
30
- appName: string;
31
- context: UserContextInterface;
32
- onNavigate: (url: string) => void;
33
- onUpdate: () => void;
34
- }
35
-
36
- export const Notifications: React.FC<Props> = (props) => {
37
- const [notifications, setNotifications] = useState<NotificationInterface[]>([]);
38
- const [isLoading, setIsLoading] = useState(true);
39
-
40
- const loadData = async () => {
41
- setIsLoading(true);
42
- const n: NotificationInterface[] = await ApiHelper.get("/notifications/my", "MessagingApi");
43
- setNotifications(n);
44
- setIsLoading(false);
45
- props.onUpdate();
46
- }
47
-
48
- React.useEffect(() => { loadData(); }, []); //eslint-disable-line
49
-
50
- const getAppUrl = (appName:string) => {
51
- switch (appName) {
52
- case props.appName.toLowerCase(): return "";
53
- case "b1admin": return "https://admin.b1.church";
54
- case "b1": return "https://" + props.context.userChurch.church.subDomain + ".b1.church";
55
- default: return "";
56
- }
57
- }
58
-
59
- const handleClick = (notification:NotificationInterface) => {
60
- let app = "";
61
- let path = "";
62
- switch (notification.contentType) {
63
- case "task": app = "b1admin"; path = "/tasks/" + notification.contentId; break;
64
- case "assignment": app = "b1"; path = "/my/plans/" + notification.contentId; break;
65
- }
66
-
67
- const appUrl = getAppUrl(app);
68
- if (appUrl === "") {
69
- props.onNavigate(path);
70
- }
71
- else {
72
- window.open(appUrl + path, "_blank");
73
- }
74
- }
75
-
76
- const getNotificationIcon = (contentType: string) => {
77
- switch (contentType) {
78
- case "task": return <TaskIcon />;
79
- case "assignment": return <AssignmentIcon />;
80
- default: return <NotificationsIcon />;
81
- }
82
- }
83
-
84
- const getNotificationList = () => {
85
- if (notifications.length === 0) {
86
- return (
87
- <Box id="notifications-empty" sx={{ textAlign: 'center', py: 4 }}>
88
- <NotificationsIcon sx={{ fontSize: 48, color: 'grey.400', mb: 2 }} />
89
- <Typography variant="h6" color="textSecondary">
90
- No notifications
91
- </Typography>
92
- <Typography variant="body2" color="textSecondary">
93
- You're all caught up!
94
- </Typography>
95
- </Box>
96
- );
97
- }
98
-
99
- return (
100
- <List id="notifications-list" sx={{ width: '100%' }}>
101
- {notifications.map((notification, index) => {
102
- let datePosted = new Date(notification.timeSent);
103
- const displayDuration = DateHelper.getDisplayDuration(datePosted);
104
- const isUnread = notification.isNew;
105
-
106
- return (
107
- <React.Fragment key={notification.id}>
108
- <ListItem
109
- id={`notification-item-${notification.id}`}
110
- component="button"
111
- onClick={() => handleClick(notification)}
112
- sx={{
113
- alignItems: 'flex-start',
114
- py: 2,
115
- cursor: 'pointer',
116
- bgcolor: isUnread ? 'action.hover' : 'transparent',
117
- '&:hover': {
118
- bgcolor: 'action.hover'
119
- },
120
- borderRadius: 1,
121
- mb: 0.5
122
- }}
123
- >
124
- <ListItemAvatar>
125
- <Avatar
126
- sx={{
127
- bgcolor: isUnread ? 'primary.main' : 'grey.400',
128
- width: 48,
129
- height: 48
130
- }}
131
- >
132
- {getNotificationIcon(notification.contentType)}
133
- </Avatar>
134
- </ListItemAvatar>
135
- <ListItemText
136
- primary={
137
- <Stack direction="row" justifyContent="space-between" alignItems="flex-start">
138
- <Typography
139
- variant="body1"
140
- sx={{
141
- fontWeight: isUnread ? 600 : 400,
142
- flex: 1,
143
- pr: 1
144
- }}
145
- >
146
- {notification.message}
147
- </Typography>
148
- <Stack direction="row" alignItems="center" spacing={1}>
149
- {isUnread && (
150
- <Chip
151
- size="small"
152
- label="New"
153
- color="primary"
154
- sx={{ height: 20, fontSize: '0.7rem' }}
155
- />
156
- )}
157
- <Typography variant="caption" color="textSecondary">
158
- {displayDuration}
159
- </Typography>
160
- </Stack>
161
- </Stack>
162
- }
163
- secondary={
164
- notification.link && (
165
- <Box sx={{ mt: 1 }}>
166
- <IconButton
167
- size="small"
168
- onClick={(e) => {
169
- e.stopPropagation();
170
- window.open(notification.link, '_blank');
171
- }}
172
- sx={{ p: 0.5 }}
173
- >
174
- <OpenInNewIcon fontSize="small" />
175
- <Typography variant="caption" sx={{ ml: 0.5 }}>
176
- View Details
177
- </Typography>
178
- </IconButton>
179
- </Box>
180
- )
181
- }
182
- />
183
- </ListItem>
184
- {index < notifications.length - 1 && <Divider variant="inset" component="li" />}
185
- </React.Fragment>
186
- );
187
- })}
188
- </List>
189
- );
190
- }
191
-
192
-
193
- return (
194
- <Paper id="notifications-panel" elevation={0} sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
195
- <Box id="notifications-header" sx={{ p: 2, borderBottom: 1, borderColor: 'divider' }}>
196
- <Typography variant="h6" component="h2">
197
- Notifications
198
- </Typography>
199
- </Box>
200
-
201
- <Box id="notifications-content" sx={{ flex: 1, overflow: 'auto' }}>
202
- {isLoading ? (
203
- <Box sx={{ p: 2 }}>
204
- {[...Array(3)].map((_, index) => (
205
- <Box key={`skeleton-${index}`} sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
206
- <Skeleton variant="circular" width={48} height={48} sx={{ mr: 2 }} />
207
- <Box sx={{ flex: 1 }}>
208
- <Skeleton variant="text" width="80%" height={24} />
209
- <Skeleton variant="text" width="40%" height={20} />
210
- </Box>
211
- </Box>
212
- ))}
213
- </Box>
214
- ) : (
215
- getNotificationList()
216
- )}
217
- </Box>
218
- </Paper>
219
- );
220
- };