@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.
- package/dist/components/DisplayBox.d.ts +1 -1
- package/dist/components/DisplayBox.d.ts.map +1 -1
- package/dist/components/DisplayBox.js +2 -4
- package/dist/components/DisplayBox.js.map +1 -1
- package/dist/components/ErrorMessages.d.ts.map +1 -1
- package/dist/components/ErrorMessages.js +1 -1
- package/dist/components/ErrorMessages.js.map +1 -1
- package/dist/components/ExportLink.d.ts.map +1 -1
- package/dist/components/ExportLink.js +10 -10
- package/dist/components/ExportLink.js.map +1 -1
- package/dist/components/FloatingSupport.js.map +1 -1
- package/dist/components/FormCardPayment.d.ts.map +1 -1
- package/dist/components/FormCardPayment.js +20 -5
- package/dist/components/FormCardPayment.js.map +1 -1
- package/dist/components/FormSubmissionEdit.d.ts.map +1 -1
- package/dist/components/FormSubmissionEdit.js +8 -10
- package/dist/components/FormSubmissionEdit.js.map +1 -1
- package/dist/components/HelpIcon.d.ts.map +1 -1
- package/dist/components/HelpIcon.js.map +1 -1
- package/dist/components/ImageEditor.d.ts.map +1 -1
- package/dist/components/ImageEditor.js +10 -10
- package/dist/components/ImageEditor.js.map +1 -1
- package/dist/components/InputBox.d.ts +2 -1
- package/dist/components/InputBox.d.ts.map +1 -1
- package/dist/components/InputBox.js +5 -7
- package/dist/components/InputBox.js.map +1 -1
- package/dist/components/Loading.d.ts.map +1 -1
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/PageHeader.js +14 -14
- package/dist/components/PersonAvatar.d.ts.map +1 -1
- package/dist/components/PersonAvatar.js +9 -16
- package/dist/components/PersonAvatar.js.map +1 -1
- package/dist/components/QuestionEdit.js +3 -3
- package/dist/components/QuestionEdit.js.map +1 -1
- package/dist/components/SmallButton.d.ts.map +1 -1
- package/dist/components/SmallButton.js +2 -1
- package/dist/components/SmallButton.js.map +1 -1
- package/dist/components/SupportModal.js.map +1 -1
- package/dist/components/gallery/GalleryModal.js +10 -10
- package/dist/components/gallery/GalleryModal.js.map +1 -1
- package/dist/components/gallery/StockPhotos.js +12 -12
- package/dist/components/gallery/StockPhotos.js.map +1 -1
- package/dist/components/header/Banner.d.ts.map +1 -1
- package/dist/components/header/Banner.js.map +1 -1
- package/dist/components/header/PrimaryMenu.js +4 -4
- package/dist/components/header/PrimaryMenu.js.map +1 -1
- package/dist/components/header/SecondaryMenu.d.ts.map +1 -1
- package/dist/components/header/SecondaryMenu.js.map +1 -1
- package/dist/components/header/SecondaryMenuAlt.d.ts.map +1 -1
- package/dist/components/header/SecondaryMenuAlt.js.map +1 -1
- package/dist/components/header/SiteHeader.d.ts.map +1 -1
- package/dist/components/header/SiteHeader.js +7 -7
- package/dist/components/header/SiteHeader.js.map +1 -1
- package/dist/components/header/SupportDrawer.js +1 -1
- package/dist/components/header/SupportDrawer.js.map +1 -1
- package/dist/components/notes/AddNote.js +14 -14
- package/dist/components/notes/Note.d.ts.map +1 -1
- package/dist/components/notes/Note.js +7 -7
- package/dist/components/notes/Note.js.map +1 -1
- package/dist/components/notes/Notes.d.ts.map +1 -1
- package/dist/components/notes/Notes.js +4 -4
- package/dist/components/notes/Notes.js.map +1 -1
- package/dist/components/wrapper/AppList.d.ts.map +1 -1
- package/dist/components/wrapper/AppList.js.map +1 -1
- package/dist/components/wrapper/ChurchList.js +5 -5
- package/dist/components/wrapper/ChurchList.js.map +1 -1
- package/dist/components/wrapper/NavItem.js +1 -1
- package/dist/components/wrapper/NavItem.js.map +1 -1
- package/dist/components/wrapper/NewPrivateMessage.d.ts.map +1 -1
- package/dist/components/wrapper/NewPrivateMessage.js +7 -6
- package/dist/components/wrapper/NewPrivateMessage.js.map +1 -1
- package/dist/components/wrapper/Notifications.d.ts.map +1 -1
- package/dist/components/wrapper/Notifications.js +12 -14
- package/dist/components/wrapper/Notifications.js.map +1 -1
- package/dist/components/wrapper/PrivateMessageDetails.js +10 -10
- package/dist/components/wrapper/PrivateMessages.d.ts.map +1 -1
- package/dist/components/wrapper/PrivateMessages.js +119 -123
- package/dist/components/wrapper/PrivateMessages.js.map +1 -1
- package/dist/components/wrapper/UserMenu.d.ts.map +1 -1
- package/dist/components/wrapper/UserMenu.js +15 -15
- package/dist/components/wrapper/UserMenu.js.map +1 -1
- package/dist/helpers/AnalyticsHelper.d.ts.map +1 -1
- package/dist/helpers/AnalyticsHelper.js +3 -3
- package/dist/helpers/AnalyticsHelper.js.map +1 -1
- package/dist/helpers/ArrayHelper.d.ts.map +1 -1
- package/dist/helpers/ArrayHelper.js.map +1 -1
- package/dist/helpers/CurrencyHelper.d.ts +4 -0
- package/dist/helpers/CurrencyHelper.d.ts.map +1 -1
- package/dist/helpers/CurrencyHelper.js +65 -0
- package/dist/helpers/CurrencyHelper.js.map +1 -1
- package/dist/helpers/ErrorHelper.d.ts.map +1 -1
- package/dist/helpers/ErrorHelper.js.map +1 -1
- package/dist/helpers/EventHelper.d.ts.map +1 -1
- package/dist/helpers/EventHelper.js +1 -1
- package/dist/helpers/EventHelper.js.map +1 -1
- package/dist/helpers/FileHelper.d.ts.map +1 -1
- package/dist/helpers/FileHelper.js +3 -1
- package/dist/helpers/FileHelper.js.map +1 -1
- package/dist/helpers/Locale.d.ts.map +1 -1
- package/dist/helpers/Locale.js +10 -16
- package/dist/helpers/Locale.js.map +1 -1
- package/dist/helpers/NotificationService.js +4 -4
- package/dist/helpers/PersonHelper.d.ts.map +1 -1
- package/dist/helpers/PersonHelper.js +5 -4
- package/dist/helpers/PersonHelper.js.map +1 -1
- package/dist/helpers/SlugHelper.d.ts.map +1 -1
- package/dist/helpers/SlugHelper.js +5 -3
- package/dist/helpers/SlugHelper.js.map +1 -1
- package/dist/helpers/SocketHelper.d.ts.map +1 -1
- package/dist/helpers/SocketHelper.js +5 -10
- package/dist/helpers/SocketHelper.js.map +1 -1
- package/dist/helpers/UniqueIdHelper.d.ts.map +1 -1
- package/dist/helpers/UniqueIdHelper.js +132 -7
- package/dist/helpers/UniqueIdHelper.js.map +1 -1
- package/dist/helpers/UserHelper.d.ts.map +1 -1
- package/dist/helpers/UserHelper.js +2 -2
- package/dist/helpers/UserHelper.js.map +1 -1
- package/dist/hooks/useMountedState.d.ts.map +1 -1
- package/dist/hooks/useMountedState.js +1 -1
- package/dist/hooks/useMountedState.js.map +1 -1
- package/dist/hooks/useNotifications.d.ts +2 -2
- package/dist/hooks/useNotifications.js +5 -5
- package/dist/public/locales/de.json +15 -7
- package/dist/public/locales/es.json +193 -190
- package/dist/public/locales/fr.json +15 -7
- package/dist/public/locales/hi.json +15 -7
- package/dist/public/locales/it.json +15 -7
- package/dist/public/locales/ko.json +15 -7
- package/dist/public/locales/no.json +15 -7
- package/dist/public/locales/pt.json +15 -7
- package/dist/public/locales/ru.json +15 -7
- package/dist/public/locales/tl.json +15 -7
- package/dist/public/locales/zh.json +15 -7
- package/package.json +106 -73
- package/dist/helpers/DateHelper.d.ts +0 -18
- package/dist/helpers/DateHelper.d.ts.map +0 -1
- package/dist/helpers/DateHelper.js +0 -102
- package/dist/helpers/DateHelper.js.map +0 -1
- package/public/css/cropper.css +0 -309
- package/public/css/styles.css +0 -112
- package/public/locales/de.json +0 -273
- package/public/locales/en.json +0 -281
- package/public/locales/es.json +0 -278
- package/public/locales/fr.json +0 -273
- package/public/locales/hi.json +0 -273
- package/public/locales/it.json +0 -273
- package/public/locales/ko.json +0 -273
- package/public/locales/no.json +0 -273
- package/public/locales/pt.json +0 -273
- package/public/locales/ru.json +0 -273
- package/public/locales/tl.json +0 -273
- package/public/locales/zh.json +0 -273
- package/src/components/DisplayBox.tsx +0 -83
- package/src/components/ErrorMessages.tsx +0 -28
- package/src/components/ExportLink.tsx +0 -81
- package/src/components/FloatingSupport.tsx +0 -18
- package/src/components/FormCardPayment.tsx +0 -184
- package/src/components/FormSubmissionEdit.tsx +0 -168
- package/src/components/HelpIcon.tsx +0 -12
- package/src/components/ImageEditor.tsx +0 -167
- package/src/components/InputBox.tsx +0 -96
- package/src/components/Loading.tsx +0 -31
- package/src/components/PageHeader.tsx +0 -111
- package/src/components/PersonAvatar.tsx +0 -78
- package/src/components/QuestionEdit.tsx +0 -99
- package/src/components/SmallButton.tsx +0 -42
- package/src/components/SupportModal.tsx +0 -32
- package/src/components/TabPanel.tsx +0 -28
- package/src/components/gallery/GalleryModal.tsx +0 -170
- package/src/components/gallery/StockPhotos.tsx +0 -96
- package/src/components/gallery/index.ts +0 -2
- package/src/components/header/Banner.tsx +0 -11
- package/src/components/header/PrimaryMenu.tsx +0 -101
- package/src/components/header/SecondaryMenu.tsx +0 -23
- package/src/components/header/SecondaryMenuAlt.tsx +0 -40
- package/src/components/header/SiteHeader.tsx +0 -205
- package/src/components/header/SupportDrawer.tsx +0 -112
- package/src/components/header/index.tsx +0 -2
- package/src/components/index.tsx +0 -20
- package/src/components/notes/AddNote.tsx +0 -185
- package/src/components/notes/Note.tsx +0 -84
- package/src/components/notes/Notes.tsx +0 -208
- package/src/components/notes/index.ts +0 -3
- package/src/components/wrapper/AppList.tsx +0 -18
- package/src/components/wrapper/ChurchList.tsx +0 -145
- package/src/components/wrapper/NavItem.tsx +0 -47
- package/src/components/wrapper/NewPrivateMessage.tsx +0 -249
- package/src/components/wrapper/Notifications.tsx +0 -220
- package/src/components/wrapper/PrivateMessageDetails.tsx +0 -106
- package/src/components/wrapper/PrivateMessages.tsx +0 -574
- package/src/components/wrapper/UserMenu.tsx +0 -384
- package/src/components/wrapper/index.tsx +0 -8
- package/src/helpers/AnalyticsHelper.ts +0 -44
- package/src/helpers/AppearanceHelper.ts +0 -73
- package/src/helpers/ArrayHelper.ts +0 -87
- package/src/helpers/CurrencyHelper.ts +0 -10
- package/src/helpers/DateHelper.ts +0 -104
- package/src/helpers/ErrorHelper.ts +0 -41
- package/src/helpers/EventHelper.ts +0 -49
- package/src/helpers/FileHelper.ts +0 -31
- package/src/helpers/Locale.ts +0 -457
- package/src/helpers/NotificationService.ts +0 -233
- package/src/helpers/PersonHelper.ts +0 -62
- package/src/helpers/SlugHelper.ts +0 -27
- package/src/helpers/SocketHelper.ts +0 -183
- package/src/helpers/UniqueIdHelper.ts +0 -36
- package/src/helpers/UserHelper.ts +0 -103
- package/src/helpers/createEmotionCache.ts +0 -17
- package/src/helpers/index.ts +0 -58
- package/src/hooks/index.ts +0 -3
- package/src/hooks/useMountedState.ts +0 -18
- package/src/hooks/useNotifications.ts +0 -95
- package/src/index.ts +0 -3
- package/src/types/interface-extensions.d.ts +0 -12
- 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
|
-
};
|