@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,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
- };