@churchapps/apphelper 0.4.17 → 0.4.18
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/FormCardPayment.js +2 -2
- package/dist/components/FormCardPayment.js.map +1 -1
- package/dist/components/FormSubmissionEdit.d.ts.map +1 -1
- package/dist/components/FormSubmissionEdit.js +4 -5
- package/dist/components/FormSubmissionEdit.js.map +1 -1
- package/dist/components/PageHeader.d.ts +15 -0
- package/dist/components/PageHeader.d.ts.map +1 -0
- package/dist/components/PageHeader.js +41 -0
- package/dist/components/PageHeader.js.map +1 -0
- package/dist/components/PersonAvatar.d.ts +12 -0
- package/dist/components/PersonAvatar.d.ts.map +1 -0
- package/dist/components/PersonAvatar.js +55 -0
- package/dist/components/PersonAvatar.js.map +1 -0
- package/dist/components/header/SiteHeader.d.ts +2 -1
- package/dist/components/header/SiteHeader.d.ts.map +1 -1
- package/dist/components/header/SiteHeader.js +100 -4
- package/dist/components/header/SiteHeader.js.map +1 -1
- package/dist/components/header/SupportDrawer.js.map +1 -1
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +2 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/notes/AddNote.d.ts.map +1 -1
- package/dist/components/notes/AddNote.js +45 -7
- package/dist/components/notes/AddNote.js.map +1 -1
- package/dist/components/notes/Note.d.ts.map +1 -1
- package/dist/components/notes/Note.js +6 -6
- 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 +120 -20
- package/dist/components/notes/Notes.js.map +1 -1
- package/dist/components/wrapper/ChurchList.d.ts.map +1 -1
- package/dist/components/wrapper/ChurchList.js +44 -6
- package/dist/components/wrapper/ChurchList.js.map +1 -1
- package/dist/components/wrapper/NewPrivateMessage.d.ts.map +1 -1
- package/dist/components/wrapper/NewPrivateMessage.js +28 -21
- 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 +47 -20
- package/dist/components/wrapper/Notifications.js.map +1 -1
- package/dist/components/wrapper/PrivateMessageDetails.d.ts +1 -0
- package/dist/components/wrapper/PrivateMessageDetails.d.ts.map +1 -1
- package/dist/components/wrapper/PrivateMessageDetails.js +53 -4
- package/dist/components/wrapper/PrivateMessageDetails.js.map +1 -1
- package/dist/components/wrapper/PrivateMessages.d.ts.map +1 -1
- package/dist/components/wrapper/PrivateMessages.js +360 -41
- 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 +163 -26
- package/dist/components/wrapper/UserMenu.js.map +1 -1
- package/dist/components/wrapper/index.d.ts +2 -1
- package/dist/components/wrapper/index.d.ts.map +1 -1
- package/dist/components/wrapper/index.js +2 -1
- package/dist/components/wrapper/index.js.map +1 -1
- package/dist/helpers/ArrayHelper.d.ts.map +1 -1
- package/dist/helpers/ArrayHelper.js +0 -1
- package/dist/helpers/ArrayHelper.js.map +1 -1
- package/dist/helpers/ErrorHelper.js +1 -1
- package/dist/helpers/ErrorHelper.js.map +1 -1
- package/dist/helpers/EventHelper.d.ts.map +1 -1
- package/dist/helpers/EventHelper.js +0 -3
- package/dist/helpers/EventHelper.js.map +1 -1
- package/dist/helpers/Locale.d.ts +1 -1
- package/dist/helpers/Locale.d.ts.map +1 -1
- package/dist/helpers/Locale.js +7 -2
- package/dist/helpers/Locale.js.map +1 -1
- package/dist/helpers/NotificationService.d.ts +56 -0
- package/dist/helpers/NotificationService.d.ts.map +1 -0
- package/dist/helpers/NotificationService.js +176 -0
- package/dist/helpers/NotificationService.js.map +1 -0
- package/dist/helpers/SocketHelper.d.ts.map +1 -1
- package/dist/helpers/SocketHelper.js +22 -17
- package/dist/helpers/SocketHelper.js.map +1 -1
- package/dist/helpers/UserHelper.js +2 -2
- package/dist/helpers/UserHelper.js.map +1 -1
- package/dist/helpers/index.d.ts +2 -0
- package/dist/helpers/index.d.ts.map +1 -1
- package/dist/helpers/index.js +1 -0
- package/dist/helpers/index.js.map +1 -1
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/useNotifications.d.ts +30 -0
- package/dist/hooks/useNotifications.d.ts.map +1 -0
- package/dist/hooks/useNotifications.js +79 -0
- package/dist/hooks/useNotifications.js.map +1 -0
- package/dist/public/css/styles.css +6 -2
- package/package.json +1 -1
- package/public/css/styles.css +6 -2
- package/src/components/FormCardPayment.tsx +2 -2
- package/src/components/FormSubmissionEdit.tsx +5 -6
- package/src/components/PageHeader.tsx +107 -0
- package/src/components/PersonAvatar.tsx +78 -0
- package/src/components/header/SiteHeader.tsx +131 -8
- package/src/components/header/SupportDrawer.tsx +1 -1
- package/src/components/index.tsx +2 -0
- package/src/components/notes/AddNote.tsx +105 -19
- package/src/components/notes/Note.tsx +43 -22
- package/src/components/notes/Notes.tsx +160 -21
- package/src/components/wrapper/ChurchList.tsx +45 -5
- package/src/components/wrapper/NewPrivateMessage.tsx +181 -44
- package/src/components/wrapper/Notifications.tsx +164 -29
- package/src/components/wrapper/PrivateMessageDetails.tsx +100 -13
- package/src/components/wrapper/PrivateMessages.tsx +539 -65
- package/src/components/wrapper/UserMenu.tsx +217 -34
- package/src/components/wrapper/index.tsx +3 -2
- package/src/helpers/ArrayHelper.ts +0 -1
- package/src/helpers/ErrorHelper.ts +1 -1
- package/src/helpers/EventHelper.ts +0 -3
- package/src/helpers/Locale.ts +7 -2
- package/src/helpers/NotificationService.ts +211 -0
- package/src/helpers/SocketHelper.ts +23 -17
- package/src/helpers/UserHelper.ts +2 -2
- package/src/helpers/index.ts +2 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useNotifications.ts +94 -0
- package/dist/components/wrapper/Drawers.d.ts +0 -5
- package/dist/components/wrapper/Drawers.d.ts.map +0 -1
- package/dist/components/wrapper/Drawers.js +0 -49
- package/dist/components/wrapper/Drawers.js.map +0 -1
- package/dist/components/wrapper/SiteWrapper.d.ts +0 -15
- package/dist/components/wrapper/SiteWrapper.d.ts.map +0 -1
- package/dist/components/wrapper/SiteWrapper.js +0 -60
- package/dist/components/wrapper/SiteWrapper.js.map +0 -1
- package/dist/components/wrapper/TabPanel.d.ts +0 -9
- package/dist/components/wrapper/TabPanel.d.ts.map +0 -1
- package/dist/components/wrapper/TabPanel.js +0 -17
- package/dist/components/wrapper/TabPanel.js.map +0 -1
- package/dist/helpers/ApiHelper.d.ts +0 -18
- package/dist/helpers/ApiHelper.d.ts.map +0 -1
- package/dist/helpers/ApiHelper.js +0 -119
- package/dist/helpers/ApiHelper.js.map +0 -1
- package/src/components/wrapper/Drawers.tsx +0 -62
- package/src/components/wrapper/SiteWrapper.tsx +0 -110
- package/src/components/wrapper/TabPanel.tsx +0 -32
- package/src/helpers/ApiHelper.ts +0 -127
|
@@ -58,10 +58,9 @@ export const FormSubmissionEdit: React.FC<Props> = ({showHeader = true, noBackgr
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
const loadData = () => {
|
|
61
|
-
|
|
62
|
-
if (!UniqueIdHelper.isMissing(props.
|
|
63
|
-
else if (!UniqueIdHelper.isMissing(props.
|
|
64
|
-
else if (!UniqueIdHelper.isMissing(props.unRestrictedFormId)) ApiHelper.get("/questions/unrestricted?formId=" + props.unRestrictedFormId, "MembershipApi").then(data => setFormSubmissionData(data));
|
|
61
|
+
if (!UniqueIdHelper.isMissing(props.formSubmissionId)) ApiHelper.get("/formsubmissions/" + props.formSubmissionId + "/?include=questions,answers,form", "MembershipApi").then((data: any) => setFormSubmission(data));
|
|
62
|
+
else if (!UniqueIdHelper.isMissing(props.addFormId)) ApiHelper.get("/questions/?formId=" + props.addFormId, "MembershipApi").then((data: any) => setFormSubmissionData(data));
|
|
63
|
+
else if (!UniqueIdHelper.isMissing(props.unRestrictedFormId)) ApiHelper.get("/questions/unrestricted?formId=" + props.unRestrictedFormId, "MembershipApi").then((data: any) => setFormSubmissionData(data));
|
|
65
64
|
}
|
|
66
65
|
|
|
67
66
|
const getDefaultValue = (q: QuestionInterface) => {
|
|
@@ -116,7 +115,7 @@ export const FormSubmissionEdit: React.FC<Props> = ({showHeader = true, noBackgr
|
|
|
116
115
|
fs.submissionDate = new Date();
|
|
117
116
|
fs.churchId = props.churchId || null;
|
|
118
117
|
|
|
119
|
-
ApiHelper.post("/formsubmissions/", [fs], "MembershipApi").then((res) => {
|
|
118
|
+
ApiHelper.post("/formsubmissions/", [fs], "MembershipApi").then((res: any) => {
|
|
120
119
|
if (res?.[0]?.error) {
|
|
121
120
|
setErrors([res?.[0].error]);
|
|
122
121
|
} else {
|
|
@@ -143,7 +142,7 @@ export const FormSubmissionEdit: React.FC<Props> = ({showHeader = true, noBackgr
|
|
|
143
142
|
|
|
144
143
|
React.useEffect(() => {
|
|
145
144
|
if (props.churchId) {
|
|
146
|
-
ApiHelper.get("/gateways/churchId/" + props.churchId, "GivingApi").then(data => {
|
|
145
|
+
ApiHelper.get("/gateways/churchId/" + props.churchId, "GivingApi").then((data: any) => {
|
|
147
146
|
if (data.length && data[0]?.publicKey) {
|
|
148
147
|
setStripe(loadStripe(data[0].publicKey));
|
|
149
148
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React, { ReactNode } from "react";
|
|
2
|
+
import { Box, Typography, Stack } from "@mui/material";
|
|
3
|
+
|
|
4
|
+
interface PageHeaderProps {
|
|
5
|
+
icon: ReactNode;
|
|
6
|
+
title: string;
|
|
7
|
+
subtitle?: string;
|
|
8
|
+
children?: ReactNode; // For action buttons or tabs
|
|
9
|
+
statistics?: Array<{ icon: ReactNode; value: string; label: string }>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const PageHeader: React.FC<PageHeaderProps> = ({ icon, title, subtitle, children, statistics }) => {
|
|
13
|
+
return (
|
|
14
|
+
<Box sx={{
|
|
15
|
+
backgroundColor: "var(--c1l2)",
|
|
16
|
+
color: "#FFF",
|
|
17
|
+
position: 'relative',
|
|
18
|
+
left: '50%',
|
|
19
|
+
right: '50%',
|
|
20
|
+
marginLeft: '-50vw',
|
|
21
|
+
marginRight: '-50vw',
|
|
22
|
+
width: '100vw',
|
|
23
|
+
'--c1': '#1565C0',
|
|
24
|
+
'--c1d1': '#1358AD',
|
|
25
|
+
'--c1d2': '#114A99',
|
|
26
|
+
'--c1l2': '#568BDA'
|
|
27
|
+
}}>
|
|
28
|
+
<Box sx={{
|
|
29
|
+
paddingX: { xs: 2, sm: 3, md: 4 },
|
|
30
|
+
paddingY: 3
|
|
31
|
+
}}>
|
|
32
|
+
<Stack direction={{ xs: "column", md: "row" }} spacing={{ xs: 2, md: 4 }} alignItems={{ xs: "flex-start", md: "center" }} sx={{ width: "100%" }}>
|
|
33
|
+
{/* Left side: Title and Icon */}
|
|
34
|
+
<Stack direction="row" spacing={2} alignItems="center" sx={{ flex: 1 }}>
|
|
35
|
+
<Box
|
|
36
|
+
sx={{
|
|
37
|
+
backgroundColor: "rgba(255,255,255,0.2)",
|
|
38
|
+
borderRadius: "12px",
|
|
39
|
+
p: 1.5,
|
|
40
|
+
display: "flex",
|
|
41
|
+
alignItems: "center",
|
|
42
|
+
justifyContent: "center",
|
|
43
|
+
}}
|
|
44
|
+
>
|
|
45
|
+
{React.cloneElement(icon as React.ReactElement<any>, { sx: { fontSize: 32, color: "#FFF" } })}
|
|
46
|
+
</Box>
|
|
47
|
+
<Box>
|
|
48
|
+
<Typography
|
|
49
|
+
variant="h4"
|
|
50
|
+
sx={{
|
|
51
|
+
fontWeight: 600,
|
|
52
|
+
mb: 0.5,
|
|
53
|
+
fontSize: { xs: "1.75rem", md: "2.125rem" },
|
|
54
|
+
}}
|
|
55
|
+
>
|
|
56
|
+
{title}
|
|
57
|
+
</Typography>
|
|
58
|
+
{subtitle && (
|
|
59
|
+
<Typography
|
|
60
|
+
variant="body1"
|
|
61
|
+
sx={{
|
|
62
|
+
color: "rgba(255,255,255,0.9)",
|
|
63
|
+
fontSize: { xs: "0.875rem", md: "1rem" },
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
{subtitle}
|
|
67
|
+
</Typography>
|
|
68
|
+
)}
|
|
69
|
+
</Box>
|
|
70
|
+
</Stack>
|
|
71
|
+
|
|
72
|
+
{/* Right side: Action Buttons/Tabs */}
|
|
73
|
+
{children && (
|
|
74
|
+
<Stack
|
|
75
|
+
direction="row"
|
|
76
|
+
spacing={1}
|
|
77
|
+
sx={{
|
|
78
|
+
flexShrink: 0,
|
|
79
|
+
justifyContent: { xs: "flex-start", md: "flex-end" },
|
|
80
|
+
width: { xs: "100%", md: "auto" },
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
{children}
|
|
84
|
+
</Stack>
|
|
85
|
+
)}
|
|
86
|
+
</Stack>
|
|
87
|
+
|
|
88
|
+
{/* Statistics row */}
|
|
89
|
+
{statistics && statistics.length > 0 && (
|
|
90
|
+
<Stack direction={{ xs: "column", sm: "row" }} spacing={3} sx={{ mt: 3 }}>
|
|
91
|
+
{statistics.map((stat, index) => (
|
|
92
|
+
<Stack key={index} direction="row" spacing={1} alignItems="center">
|
|
93
|
+
{React.cloneElement(stat.icon as React.ReactElement<any>, { sx: { color: "#FFF", fontSize: 20 } })}
|
|
94
|
+
<Typography variant="h6" sx={{ color: "#FFF", fontWeight: 600, mr: 1 }}>
|
|
95
|
+
{stat.value}
|
|
96
|
+
</Typography>
|
|
97
|
+
<Typography variant="body2" sx={{ color: "rgba(255,255,255,0.9)", fontSize: "0.875rem" }}>
|
|
98
|
+
{stat.label}
|
|
99
|
+
</Typography>
|
|
100
|
+
</Stack>
|
|
101
|
+
))}
|
|
102
|
+
</Stack>
|
|
103
|
+
)}
|
|
104
|
+
</Box>
|
|
105
|
+
</Box>
|
|
106
|
+
);
|
|
107
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { Avatar, SxProps } from "@mui/material";
|
|
5
|
+
import { PersonInterface } from "@churchapps/helpers";
|
|
6
|
+
import { PersonHelper } from "../helpers";
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
person: PersonInterface;
|
|
10
|
+
size?: "small" | "medium" | "large" | "xlarge" | "xxlarge" | "responsive";
|
|
11
|
+
sx?: SxProps;
|
|
12
|
+
onClick?: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const PersonAvatar: React.FC<Props> = ({ person, size = "medium", sx, onClick }) => {
|
|
16
|
+
const [imageError, setImageError] = React.useState(false);
|
|
17
|
+
|
|
18
|
+
const getSizeProps = () => {
|
|
19
|
+
switch (size) {
|
|
20
|
+
case "small":
|
|
21
|
+
return { width: 48, height: 48 };
|
|
22
|
+
case "medium":
|
|
23
|
+
return { width: 56, height: 56 };
|
|
24
|
+
case "large":
|
|
25
|
+
return { width: 80, height: 80 };
|
|
26
|
+
case "xlarge":
|
|
27
|
+
return { width: 100, height: 100 };
|
|
28
|
+
case "xxlarge":
|
|
29
|
+
return { width: 120, height: 120 };
|
|
30
|
+
case "responsive":
|
|
31
|
+
return { width: { xs: 70, sm: 80, md: 100 }, height: { xs: 70, sm: 80, md: 100 } };
|
|
32
|
+
default:
|
|
33
|
+
return { width: 56, height: 56 };
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const getInitials = () => {
|
|
38
|
+
if (!person?.name?.display) return "?";
|
|
39
|
+
|
|
40
|
+
const names = person.name.display.trim().split(" ");
|
|
41
|
+
if (names.length >= 2) {
|
|
42
|
+
return (names[0][0] + names[names.length - 1][0]).toUpperCase();
|
|
43
|
+
} else if (names.length === 1) {
|
|
44
|
+
return names[0][0]?.toUpperCase() || "?";
|
|
45
|
+
}
|
|
46
|
+
return "?";
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const photoUrl = PersonHelper.getPhotoUrl(person);
|
|
50
|
+
const sizeProps = getSizeProps();
|
|
51
|
+
|
|
52
|
+
// Combine default styles with custom sx
|
|
53
|
+
const combinedSx = {
|
|
54
|
+
...sizeProps,
|
|
55
|
+
cursor: onClick ? "pointer" : "default",
|
|
56
|
+
"&:hover": onClick ? {
|
|
57
|
+
opacity: 0.8,
|
|
58
|
+
transition: "opacity 0.2s ease-in-out",
|
|
59
|
+
} : {},
|
|
60
|
+
...sx,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const handleImageError = () => {
|
|
64
|
+
setImageError(true);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<Avatar
|
|
69
|
+
src={!imageError ? photoUrl : undefined}
|
|
70
|
+
alt={person?.name?.display || "User avatar"}
|
|
71
|
+
sx={combinedSx}
|
|
72
|
+
onClick={onClick}
|
|
73
|
+
onError={handleImageError}
|
|
74
|
+
>
|
|
75
|
+
{(imageError || !photoUrl) && getInitials()}
|
|
76
|
+
</Avatar>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
@@ -7,7 +7,8 @@ import { PrimaryMenu } from "./PrimaryMenu";
|
|
|
7
7
|
import { SecondaryMenu } from "./SecondaryMenu";
|
|
8
8
|
import { SecondaryMenuAlt } from "./SecondaryMenuAlt";
|
|
9
9
|
import { SupportDrawer } from "./SupportDrawer";
|
|
10
|
-
import { UserContextInterface } from "@churchapps/helpers";
|
|
10
|
+
import { UserContextInterface, CommonEnvironmentHelper } from "@churchapps/helpers";
|
|
11
|
+
import { NotificationService } from "../../helpers/NotificationService";
|
|
11
12
|
|
|
12
13
|
type Props = {
|
|
13
14
|
primaryMenuLabel: string;
|
|
@@ -19,11 +20,68 @@ type Props = {
|
|
|
19
20
|
onNavigate: (url: string) => void;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
export const SiteHeader = (props:Props) => {
|
|
23
|
+
export const SiteHeader = React.memo((props:Props) => {
|
|
24
|
+
// Initialize NotificationService without subscribing to count changes to prevent re-renders
|
|
25
|
+
React.useEffect(() => {
|
|
26
|
+
const initializeNotifications = async () => {
|
|
27
|
+
if (props.context?.person?.id && props.context?.userChurch?.church?.id) {
|
|
28
|
+
const service = NotificationService.getInstance();
|
|
29
|
+
await service.initialize(props.context);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
initializeNotifications();
|
|
34
|
+
}, [props.context?.person?.id, props.context?.userChurch?.church?.id]);
|
|
35
|
+
|
|
36
|
+
const refresh = React.useCallback(async () => {
|
|
37
|
+
// Direct access to NotificationService for refresh functionality
|
|
38
|
+
await NotificationService.getInstance().refresh();
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
// Memoize userName to prevent recreation
|
|
42
|
+
const userName = React.useMemo(() => {
|
|
43
|
+
if (props.context?.user) {
|
|
44
|
+
return `${props.context.user.firstName} ${props.context.user.lastName}`;
|
|
45
|
+
}
|
|
46
|
+
return '';
|
|
47
|
+
}, [props.context?.user?.firstName, props.context?.user?.lastName]);
|
|
48
|
+
|
|
49
|
+
// Memoize profilePicture URL
|
|
50
|
+
const profilePicture = React.useMemo(() => {
|
|
51
|
+
return PersonHelper.getPhotoUrl(props.context?.person);
|
|
52
|
+
}, [props.context?.person]);
|
|
53
|
+
|
|
54
|
+
// Create a stable context object to prevent UserMenu recreation
|
|
55
|
+
const stableContext = React.useMemo(() => {
|
|
56
|
+
if (!props.context) return undefined;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
user: props.context.user,
|
|
60
|
+
person: props.context.person,
|
|
61
|
+
userChurch: props.context.userChurch,
|
|
62
|
+
userChurches: props.context.userChurches,
|
|
63
|
+
setUser: props.context.setUser,
|
|
64
|
+
setPerson: props.context.setPerson,
|
|
65
|
+
setUserChurch: props.context.setUserChurch,
|
|
66
|
+
setUserChurches: props.context.setUserChurches
|
|
67
|
+
};
|
|
68
|
+
}, [
|
|
69
|
+
props.context?.user?.id,
|
|
70
|
+
props.context?.user?.firstName,
|
|
71
|
+
props.context?.user?.lastName,
|
|
72
|
+
props.context?.person?.id,
|
|
73
|
+
props.context?.userChurch?.church?.id,
|
|
74
|
+
props.context?.userChurches,
|
|
75
|
+
props.context?.setUser,
|
|
76
|
+
props.context?.setPerson,
|
|
77
|
+
props.context?.setUserChurch,
|
|
78
|
+
props.context?.setUserChurches
|
|
79
|
+
]);
|
|
23
80
|
|
|
24
81
|
const CustomAppBar = styled(AppBar)(
|
|
25
82
|
({ theme }) => ({
|
|
26
83
|
zIndex: theme.zIndex.drawer + 1,
|
|
84
|
+
backgroundColor: "var(--c1, #1565C0)",
|
|
27
85
|
transition: theme.transitions.create(["width", "margin"], {
|
|
28
86
|
easing: theme.transitions.easing.sharp,
|
|
29
87
|
duration: theme.transitions.duration.leavingScreen
|
|
@@ -63,21 +121,86 @@ export const SiteHeader = (props:Props) => {
|
|
|
63
121
|
|
|
64
122
|
/*<Typography variant="h6" noWrap>{UserHelper.currentUserChurch?.church?.name || ""}</Typography>*/
|
|
65
123
|
return (<>
|
|
66
|
-
<div style={{
|
|
124
|
+
<div style={{
|
|
125
|
+
'--c1': '#1565C0',
|
|
126
|
+
'--c1d1': '#1358AD',
|
|
127
|
+
'--c1d2': '#114A99',
|
|
128
|
+
'--c1l2': '#568BDA',
|
|
129
|
+
backgroundColor:"var(--c1)",
|
|
130
|
+
color: "#FFF"
|
|
131
|
+
} as React.CSSProperties}>
|
|
67
132
|
<CustomAppBar position="absolute">
|
|
68
|
-
<Toolbar sx={{ pr: "24px", backgroundColor: "var(--c1)" }}>
|
|
133
|
+
<Toolbar sx={{ pr: "24px", backgroundColor: "var(--c1)", minHeight: "64px !important" }}>
|
|
69
134
|
<PrimaryMenu label={props.primaryMenuLabel} menuItems={props.primaryMenuItems} onNavigate={props.onNavigate} />
|
|
70
135
|
<SecondaryMenu label={props.secondaryMenuLabel} menuItems={props.secondaryMenuItems} onNavigate={props.onNavigate} />
|
|
71
136
|
<div style={{ flex: 1 }}>
|
|
72
137
|
<SecondaryMenuAlt label={props.secondaryMenuLabel} menuItems={props.secondaryMenuItems} onNavigate={props.onNavigate} />
|
|
73
138
|
</div>
|
|
74
|
-
{
|
|
75
|
-
|
|
139
|
+
{props.context?.user?.id && (
|
|
140
|
+
<UserMenu
|
|
141
|
+
key="user-menu-stable"
|
|
142
|
+
profilePicture={profilePicture}
|
|
143
|
+
userName={userName}
|
|
144
|
+
userChurches={props.context?.userChurches}
|
|
145
|
+
currentUserChurch={props.context?.userChurch}
|
|
146
|
+
context={stableContext}
|
|
147
|
+
appName={props.appName}
|
|
148
|
+
loadCounts={refresh}
|
|
149
|
+
notificationCounts={{notificationCount: 0, pmCount: 0}}
|
|
150
|
+
onNavigate={props.onNavigate}
|
|
151
|
+
/>
|
|
152
|
+
)}
|
|
153
|
+
{!props.context?.user?.id && <Link href="/login" color="inherit" style={{ textDecoration: "none" }}>Login</Link>}
|
|
76
154
|
<SupportDrawer appName={props.appName} relatedArticles={getRelatedArticles()} />
|
|
77
155
|
</Toolbar>
|
|
78
156
|
</CustomAppBar>
|
|
79
|
-
|
|
157
|
+
<div id="appBarSpacer"></div>
|
|
80
158
|
</div>
|
|
81
159
|
</>
|
|
82
160
|
);
|
|
83
|
-
}
|
|
161
|
+
}, (prevProps, nextProps) => {
|
|
162
|
+
// Custom comparison to prevent unnecessary re-renders
|
|
163
|
+
|
|
164
|
+
// Check if essential props have changed
|
|
165
|
+
if (prevProps.primaryMenuLabel !== nextProps.primaryMenuLabel ||
|
|
166
|
+
prevProps.secondaryMenuLabel !== nextProps.secondaryMenuLabel ||
|
|
167
|
+
prevProps.appName !== nextProps.appName) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Check if menu items arrays have changed (shallow comparison)
|
|
172
|
+
if (prevProps.primaryMenuItems?.length !== nextProps.primaryMenuItems?.length ||
|
|
173
|
+
prevProps.secondaryMenuItems?.length !== nextProps.secondaryMenuItems?.length) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Check if user context has actually changed (deep comparison of essential parts)
|
|
178
|
+
const prevUser = prevProps.context?.user;
|
|
179
|
+
const nextUser = nextProps.context?.user;
|
|
180
|
+
|
|
181
|
+
if (prevUser?.id !== nextUser?.id ||
|
|
182
|
+
prevUser?.firstName !== nextUser?.firstName ||
|
|
183
|
+
prevUser?.lastName !== nextUser?.lastName) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Check if person context has changed
|
|
188
|
+
if (prevProps.context?.person?.id !== nextProps.context?.person?.id) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Check if church context has changed
|
|
193
|
+
if (prevProps.context?.userChurch?.church?.id !== nextProps.context?.userChurch?.church?.id) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Check if onNavigate function reference has changed
|
|
198
|
+
if (prevProps.onNavigate !== nextProps.onNavigate) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// All essential props are the same, skip re-render
|
|
203
|
+
return true;
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
SiteHeader.displayName = 'SiteHeader';
|
|
@@ -33,7 +33,7 @@ export const SupportDrawer = (props: Props) => {
|
|
|
33
33
|
|
|
34
34
|
const loadData = () => {
|
|
35
35
|
if (UserHelper?.currentUserChurch?.church?.id) {
|
|
36
|
-
ApiHelper.get("/settings/public/" + UserHelper.currentUserChurch.church.id, "MembershipApi").then((data) => {
|
|
36
|
+
ApiHelper.get("/settings/public/" + UserHelper.currentUserChurch.church.id, "MembershipApi").then((data: any) => {
|
|
37
37
|
const contactRes = data?.supportContact;
|
|
38
38
|
if (contactRes && contactRes !== "") setSupportContact(contactRes);
|
|
39
39
|
|
package/src/components/index.tsx
CHANGED
|
@@ -8,6 +8,8 @@ export { ImageEditor } from "./ImageEditor";
|
|
|
8
8
|
export { InputBox } from "./InputBox";
|
|
9
9
|
export { Loading } from "./Loading";
|
|
10
10
|
export { Notes } from "./notes/Notes";
|
|
11
|
+
export { PageHeader } from "./PageHeader";
|
|
12
|
+
export { PersonAvatar } from "./PersonAvatar";
|
|
11
13
|
export { QuestionEdit } from "./QuestionEdit";
|
|
12
14
|
export { SmallButton } from "./SmallButton";
|
|
13
15
|
export { SupportModal } from "./SupportModal";
|
|
@@ -3,7 +3,16 @@
|
|
|
3
3
|
import React, { useState, useEffect } from "react"
|
|
4
4
|
import { ApiHelper, Locale, PersonHelper } from "../../helpers"
|
|
5
5
|
import { MessageInterface, UserContextInterface } from "@churchapps/helpers"
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
Box,
|
|
8
|
+
Stack,
|
|
9
|
+
TextField,
|
|
10
|
+
IconButton,
|
|
11
|
+
Paper,
|
|
12
|
+
CircularProgress,
|
|
13
|
+
Avatar
|
|
14
|
+
} from "@mui/material"
|
|
15
|
+
import { Send as SendIcon, Delete as DeleteIcon } from "@mui/icons-material"
|
|
7
16
|
import { ErrorMessages } from "../ErrorMessages"
|
|
8
17
|
import { SmallButton } from "../SmallButton"
|
|
9
18
|
|
|
@@ -22,7 +31,7 @@ export function AddNote({ context, ...props }: Props) {
|
|
|
22
31
|
const headerText = props.messageId ? "Edit note" : "Add a note"
|
|
23
32
|
|
|
24
33
|
useEffect(() => {
|
|
25
|
-
if (props.messageId) ApiHelper.get(`/messages/${props.messageId}`, "MessagingApi").then(n => setMessage(n));
|
|
34
|
+
if (props.messageId) ApiHelper.get(`/messages/${props.messageId}`, "MessagingApi").then((n: any) => setMessage(n));
|
|
26
35
|
else setMessage({ conversationId: props.conversationId, content: "" });
|
|
27
36
|
return () => {
|
|
28
37
|
setMessage(null);
|
|
@@ -38,7 +47,7 @@ export function AddNote({ context, ...props }: Props) {
|
|
|
38
47
|
|
|
39
48
|
const validate = () => {
|
|
40
49
|
const result = [];
|
|
41
|
-
if (!message.content.trim()) result.push(Locale.label("notes.validate.content"));
|
|
50
|
+
if (!message.content.trim()) result.push(Locale.label("notes.validate.content", "Please enter a message"));
|
|
42
51
|
setErrors(result);
|
|
43
52
|
return result.length === 0;
|
|
44
53
|
}
|
|
@@ -58,8 +67,13 @@ export function AddNote({ context, ...props }: Props) {
|
|
|
58
67
|
m.content = "";
|
|
59
68
|
setMessage(m);
|
|
60
69
|
})
|
|
61
|
-
.catch((error) => {
|
|
62
|
-
|
|
70
|
+
.catch((error: any) => {
|
|
71
|
+
console.error("Error saving message:", error);
|
|
72
|
+
if (error?.message === "Forbidden") {
|
|
73
|
+
setErrors(["You can't edit the message sent by others."]);
|
|
74
|
+
} else {
|
|
75
|
+
setErrors([error?.message || "Failed to save message. Please try again."]);
|
|
76
|
+
}
|
|
63
77
|
})
|
|
64
78
|
.finally(() => { setIsSubmitting(false); });
|
|
65
79
|
}
|
|
@@ -75,21 +89,93 @@ export function AddNote({ context, ...props }: Props) {
|
|
|
75
89
|
const image = PersonHelper.getPhotoUrl(context?.person)
|
|
76
90
|
|
|
77
91
|
return (
|
|
78
|
-
|
|
92
|
+
<Box sx={{ width: '100%' }}>
|
|
79
93
|
<ErrorMessages errors={errors} />
|
|
80
|
-
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
|
|
95
|
+
<Paper
|
|
96
|
+
variant="outlined"
|
|
97
|
+
sx={{
|
|
98
|
+
p: 2,
|
|
99
|
+
bgcolor: 'grey.50',
|
|
100
|
+
borderColor: 'grey.300'
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
<Stack direction="row" spacing={2} alignItems="flex-start">
|
|
104
|
+
<Avatar
|
|
105
|
+
src={image}
|
|
106
|
+
alt={context?.person?.name?.display}
|
|
107
|
+
sx={{ width: 48, height: 48 }}
|
|
108
|
+
/>
|
|
109
|
+
|
|
110
|
+
<Box sx={{ flex: 1 }}>
|
|
111
|
+
<TextField
|
|
112
|
+
fullWidth
|
|
113
|
+
multiline
|
|
114
|
+
rows={2}
|
|
115
|
+
name="noteText"
|
|
116
|
+
aria-label={headerText}
|
|
117
|
+
placeholder={props.messageId ? "Edit your message..." : "Type a message..."}
|
|
118
|
+
variant="standard"
|
|
119
|
+
value={message?.content || ''}
|
|
120
|
+
onChange={handleChange}
|
|
121
|
+
disabled={isSubmitting}
|
|
122
|
+
InputProps={{
|
|
123
|
+
disableUnderline: true,
|
|
124
|
+
sx: {
|
|
125
|
+
fontSize: '1rem',
|
|
126
|
+
'& textarea': {
|
|
127
|
+
resize: 'vertical',
|
|
128
|
+
minHeight: '40px'
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}}
|
|
132
|
+
sx={{
|
|
133
|
+
bgcolor: 'white',
|
|
134
|
+
borderRadius: 1,
|
|
135
|
+
p: 1,
|
|
136
|
+
border: '1px solid',
|
|
137
|
+
borderColor: 'grey.300',
|
|
138
|
+
'&:hover': {
|
|
139
|
+
borderColor: 'grey.400'
|
|
140
|
+
},
|
|
141
|
+
'&.Mui-focused': {
|
|
142
|
+
borderColor: 'primary.main'
|
|
143
|
+
}
|
|
144
|
+
}}
|
|
145
|
+
/>
|
|
146
|
+
|
|
147
|
+
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mt: 1, gap: 0.5 }}>
|
|
148
|
+
{deleteFunction && (
|
|
149
|
+
<IconButton
|
|
150
|
+
size="small"
|
|
151
|
+
onClick={deleteFunction}
|
|
152
|
+
disabled={isSubmitting}
|
|
153
|
+
sx={{ color: 'error.main' }}
|
|
154
|
+
>
|
|
155
|
+
<DeleteIcon fontSize="small" />
|
|
156
|
+
</IconButton>
|
|
157
|
+
)}
|
|
158
|
+
<IconButton
|
|
159
|
+
size="small"
|
|
160
|
+
color="primary"
|
|
161
|
+
onClick={handleSave}
|
|
162
|
+
disabled={isSubmitting || !message?.content?.trim()}
|
|
163
|
+
sx={{
|
|
164
|
+
bgcolor: 'primary.main',
|
|
165
|
+
color: 'white',
|
|
166
|
+
'&:hover': { bgcolor: 'primary.dark' },
|
|
167
|
+
'&:disabled': {
|
|
168
|
+
bgcolor: 'action.disabledBackground',
|
|
169
|
+
color: 'action.disabled'
|
|
170
|
+
}
|
|
171
|
+
}}
|
|
172
|
+
>
|
|
173
|
+
{isSubmitting ? <CircularProgress size={18} color="inherit" /> : <SendIcon fontSize="small" />}
|
|
174
|
+
</IconButton>
|
|
175
|
+
</Box>
|
|
176
|
+
</Box>
|
|
91
177
|
</Stack>
|
|
92
|
-
</
|
|
93
|
-
|
|
178
|
+
</Paper>
|
|
179
|
+
</Box>
|
|
94
180
|
);
|
|
95
181
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { Icon, IconButton, Stack, Box } from "@mui/material";
|
|
1
|
+
import { Icon, IconButton, Stack, Box, Typography } from "@mui/material";
|
|
2
2
|
import React, { useState, useEffect } from "react";
|
|
3
3
|
import { DateHelper, PersonHelper } from "../../helpers"
|
|
4
4
|
import { MessageInterface, UserContextInterface } from "@churchapps/helpers"
|
|
5
|
+
import { PersonAvatar } from "../PersonAvatar"
|
|
5
6
|
|
|
6
7
|
interface Props {
|
|
7
8
|
message: MessageInterface;
|
|
@@ -15,33 +16,53 @@ export const Note: React.FC<Props> = (props) => {
|
|
|
15
16
|
useEffect(() => setMessage(props.message), [props.message]);
|
|
16
17
|
|
|
17
18
|
if (message === null) return null;
|
|
18
|
-
const photoUrl = PersonHelper.getPhotoUrl(message.person);
|
|
19
19
|
let datePosted = new Date(message.timeUpdated || message.timeSent);
|
|
20
20
|
const displayDuration = DateHelper.getDisplayDuration(datePosted);
|
|
21
21
|
|
|
22
|
-
const isEdited = message.timeUpdated && message.timeUpdated !== message.timeSent
|
|
22
|
+
const isEdited = message.timeUpdated && message.timeUpdated !== message.timeSent;
|
|
23
23
|
const contents = message.content?.split("\n");
|
|
24
|
+
|
|
24
25
|
return (
|
|
25
|
-
<
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
26
|
+
<Box sx={{ display: 'flex', gap: 2, mb: 2, p: 1, '&:hover': { bgcolor: 'action.hover', borderRadius: 1 } }}>
|
|
27
|
+
<PersonAvatar person={message.person} size="small" />
|
|
28
|
+
<Box sx={{ flex: 1 }}>
|
|
29
|
+
<Stack direction="row" justifyContent="space-between" alignItems="flex-start">
|
|
30
|
+
<Box sx={{ flex: 1 }}>
|
|
31
|
+
<Stack direction="row" alignItems="center" spacing={1} sx={{ mb: 0.5 }}>
|
|
32
|
+
<Typography variant="subtitle2" fontWeight="bold">
|
|
33
|
+
{message.person?.name?.display}
|
|
34
|
+
</Typography>
|
|
35
|
+
<Typography variant="caption" color="text.secondary">
|
|
36
|
+
{displayDuration}
|
|
37
|
+
</Typography>
|
|
38
|
+
{isEdited && (
|
|
39
|
+
<Typography variant="caption" color="text.secondary">
|
|
40
|
+
(edited)
|
|
41
|
+
</Typography>
|
|
42
|
+
)}
|
|
43
|
+
</Stack>
|
|
44
|
+
<Box>
|
|
45
|
+
{contents.map((c, i) => c ? (
|
|
46
|
+
<Typography key={i} variant="body2" sx={{ mb: 0.5 }}>
|
|
47
|
+
{c}
|
|
48
|
+
</Typography>
|
|
49
|
+
) : (
|
|
50
|
+
<Box key={i} sx={{ height: '1em' }} />
|
|
51
|
+
))}
|
|
52
|
+
</Box>
|
|
53
|
+
</Box>
|
|
54
|
+
{(message?.id && message.personId === props.context?.person.id) && (
|
|
55
|
+
<IconButton
|
|
56
|
+
size="small"
|
|
57
|
+
aria-label="editNote"
|
|
58
|
+
onClick={() => props.showEditNote(message.id)}
|
|
59
|
+
sx={{ opacity: 0.7, '&:hover': { opacity: 1 } }}
|
|
60
|
+
>
|
|
61
|
+
<Icon fontSize="small">edit</Icon>
|
|
62
|
+
</IconButton>
|
|
63
|
+
)}
|
|
42
64
|
</Stack>
|
|
43
|
-
|
|
44
65
|
</Box>
|
|
45
|
-
</
|
|
66
|
+
</Box>
|
|
46
67
|
);
|
|
47
68
|
};
|