@churchapps/apphelper 0.4.50 → 0.5.1
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.d.ts +1 -1
- package/dist/components/FormCardPayment.d.ts.map +1 -1
- package/dist/components/FormCardPayment.js +27 -10
- package/dist/components/FormCardPayment.js.map +1 -1
- package/dist/components/Loading.js +36 -36
- package/dist/components/notes/Notes.js +27 -27
- package/dist/helpers/UserHelper.d.ts.map +1 -1
- package/dist/helpers/UserHelper.js +4 -1
- package/dist/helpers/UserHelper.js.map +1 -1
- package/dist/helpers/index.d.ts +1 -1
- package/dist/helpers/index.d.ts.map +1 -1
- package/dist/helpers/index.js +1 -1
- package/dist/helpers/index.js.map +1 -1
- package/dist/public/css/cropper.css +309 -309
- package/dist/public/css/styles.css +111 -111
- package/package.json +72 -73
- package/public/css/cropper.css +309 -309
- package/public/css/styles.css +111 -111
- package/public/locales/de.json +269 -269
- package/public/locales/en.json +276 -276
- package/public/locales/es.json +272 -272
- package/public/locales/fr.json +269 -269
- package/public/locales/hi.json +269 -269
- package/public/locales/it.json +269 -269
- package/public/locales/ko.json +269 -269
- package/public/locales/no.json +269 -269
- package/public/locales/pt.json +269 -269
- package/public/locales/ru.json +269 -269
- package/public/locales/tl.json +269 -269
- package/public/locales/zh.json +269 -269
- package/src/components/DisplayBox.tsx +83 -83
- package/src/components/ErrorMessages.tsx +28 -28
- package/src/components/ExportLink.tsx +81 -81
- package/src/components/FloatingSupport.tsx +18 -18
- package/src/components/FormCardPayment.tsx +184 -169
- package/src/components/FormSubmissionEdit.tsx +168 -168
- package/src/components/HelpIcon.tsx +12 -12
- package/src/components/ImageEditor.tsx +161 -161
- package/src/components/InputBox.tsx +96 -96
- package/src/components/Loading.tsx +77 -77
- package/src/components/PageHeader.tsx +110 -110
- package/src/components/PersonAvatar.tsx +77 -77
- package/src/components/QuestionEdit.tsx +99 -99
- package/src/components/SmallButton.tsx +42 -42
- package/src/components/SupportModal.tsx +32 -32
- package/src/components/TabPanel.tsx +28 -28
- package/src/components/gallery/GalleryModal.tsx +173 -173
- package/src/components/gallery/StockPhotos.tsx +95 -95
- package/src/components/gallery/index.ts +1 -1
- package/src/components/header/Banner.tsx +11 -11
- package/src/components/header/PrimaryMenu.tsx +100 -100
- package/src/components/header/SecondaryMenu.tsx +23 -23
- package/src/components/header/SecondaryMenuAlt.tsx +40 -40
- package/src/components/header/SiteHeader.tsx +207 -207
- package/src/components/header/SupportDrawer.tsx +111 -111
- package/src/components/header/index.tsx +2 -2
- package/src/components/index.tsx +20 -20
- package/src/components/notes/AddNote.tsx +180 -180
- package/src/components/notes/Note.tsx +68 -68
- package/src/components/notes/Notes.tsx +208 -208
- package/src/components/notes/index.ts +3 -3
- package/src/components/wrapper/AppList.tsx +19 -19
- package/src/components/wrapper/ChurchList.tsx +154 -154
- package/src/components/wrapper/NavItem.tsx +47 -47
- package/src/components/wrapper/NewPrivateMessage.tsx +253 -253
- package/src/components/wrapper/Notifications.tsx +223 -223
- package/src/components/wrapper/PrivateMessageDetails.tsx +112 -112
- package/src/components/wrapper/PrivateMessages.tsx +576 -576
- package/src/components/wrapper/UserMenu.tsx +383 -383
- package/src/components/wrapper/index.tsx +8 -8
- package/src/helpers/AnalyticsHelper.ts +32 -32
- package/src/helpers/AppearanceHelper.ts +73 -73
- package/src/helpers/ArrayHelper.ts +87 -87
- package/src/helpers/CurrencyHelper.ts +10 -10
- package/src/helpers/DateHelper.ts +104 -104
- package/src/helpers/ErrorHelper.ts +43 -43
- package/src/helpers/EventHelper.ts +49 -49
- package/src/helpers/FileHelper.ts +31 -31
- package/src/helpers/Locale.ts +457 -457
- package/src/helpers/NotificationService.ts +296 -296
- package/src/helpers/PersonHelper.ts +62 -62
- package/src/helpers/SlugHelper.ts +37 -37
- package/src/helpers/SocketHelper.ts +296 -296
- package/src/helpers/UniqueIdHelper.ts +36 -36
- package/src/helpers/UserHelper.ts +107 -104
- package/src/helpers/createEmotionCache.ts +17 -17
- package/src/helpers/index.ts +58 -50
- package/src/hooks/index.ts +3 -3
- package/src/hooks/useMountedState.ts +16 -16
- package/src/hooks/useNotifications.ts +93 -93
- package/src/index.ts +2 -2
- package/src/types/interface-extensions.d.ts +12 -0
- package/tsconfig.json +31 -31
- package/dist/public/locales/de.json +0 -270
- package/dist/public/locales/en.json +0 -277
- package/dist/public/locales/es.json +0 -272
- package/dist/public/locales/fr.json +0 -270
- package/dist/public/locales/hi.json +0 -270
- package/dist/public/locales/it.json +0 -270
- package/dist/public/locales/ko.json +0 -270
- package/dist/public/locales/no.json +0 -270
- package/dist/public/locales/pt.json +0 -270
- package/dist/public/locales/ru.json +0 -270
- package/dist/public/locales/tl.json +0 -270
- package/dist/public/locales/zh.json +0 -270
|
@@ -1,253 +1,253 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
Button,
|
|
5
|
-
TextField,
|
|
6
|
-
Paper,
|
|
7
|
-
Box,
|
|
8
|
-
Typography,
|
|
9
|
-
Stack,
|
|
10
|
-
IconButton,
|
|
11
|
-
List,
|
|
12
|
-
ListItem,
|
|
13
|
-
ListItemAvatar,
|
|
14
|
-
ListItemText,
|
|
15
|
-
ListItemButton,
|
|
16
|
-
InputAdornment,
|
|
17
|
-
Divider,
|
|
18
|
-
Skeleton,
|
|
19
|
-
useTheme
|
|
20
|
-
} from "@mui/material";
|
|
21
|
-
import {
|
|
22
|
-
ArrowBack as ArrowBackIcon,
|
|
23
|
-
PersonSearch as PersonSearchIcon
|
|
24
|
-
} from "@mui/icons-material";
|
|
25
|
-
import React, { useEffect, useState } from "react";
|
|
26
|
-
import { ApiHelper, Locale } from "../../helpers";
|
|
27
|
-
import { ConversationInterface, PersonInterface, PrivateMessageInterface, UserContextInterface } from "@churchapps/helpers";
|
|
28
|
-
import { AddNote } from "../notes/AddNote";
|
|
29
|
-
import { PersonAvatar } from "../PersonAvatar";
|
|
30
|
-
|
|
31
|
-
interface Props {
|
|
32
|
-
context: UserContextInterface;
|
|
33
|
-
onSelectMessage: (pm: PrivateMessageInterface) => void
|
|
34
|
-
onBack: () => void
|
|
35
|
-
selectedPerson?: PersonInterface
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export const NewPrivateMessage: React.FC<Props> = (props) => {
|
|
39
|
-
|
|
40
|
-
const [searchText, setSearchText] = React.useState("");
|
|
41
|
-
const [searchResults, setSearchResults] = React.useState([]);
|
|
42
|
-
const [selectedPerson, setSelectedPerson] = React.useState<PersonInterface>(null);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
46
|
-
setSearchText(e.currentTarget.value);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/*
|
|
50
|
-
const handleKeyDown = (e: React.KeyboardEvent<any>) => {
|
|
51
|
-
//if (e.key === "Enter") { e.preventDefault(); handleSubmit(null); }
|
|
52
|
-
}
|
|
53
|
-
*/
|
|
54
|
-
|
|
55
|
-
const handlePersonSelected = async (person: PersonInterface) => {
|
|
56
|
-
try {
|
|
57
|
-
const existing: PrivateMessageInterface = await ApiHelper.get("/privateMessages/existing/" + person.id, "MessagingApi");
|
|
58
|
-
if (existing?.id) {
|
|
59
|
-
existing.person = person;
|
|
60
|
-
props.onSelectMessage(existing);
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
} catch (error) {
|
|
64
|
-
// No existing conversation found, continue to create new one
|
|
65
|
-
console.log("No existing conversation found for person:", person.id);
|
|
66
|
-
}
|
|
67
|
-
setSelectedPerson(person);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const handleNoteAdded = () => {
|
|
72
|
-
handlePersonSelected(selectedPerson);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const createConversation = async () => {
|
|
76
|
-
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" }
|
|
77
|
-
const result: ConversationInterface[] = await ApiHelper.post("/conversations", [conv], "MessagingApi");
|
|
78
|
-
|
|
79
|
-
const pm: PrivateMessageInterface = {
|
|
80
|
-
fromPersonId: props.context.person.id,
|
|
81
|
-
toPersonId: selectedPerson.id,
|
|
82
|
-
conversationId: result[0].id
|
|
83
|
-
}
|
|
84
|
-
const privateMessages: PrivateMessageInterface[] = await ApiHelper.post("/privateMessages", [pm], "MessagingApi");
|
|
85
|
-
return privateMessages[0].conversationId;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
useEffect(() => {
|
|
90
|
-
if (props.selectedPerson) handlePersonSelected(props.selectedPerson);
|
|
91
|
-
}, [props.selectedPerson]);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const theme = useTheme();
|
|
95
|
-
const [isSearching, setIsSearching] = useState(false);
|
|
96
|
-
|
|
97
|
-
const handleSearchSubmit = async (e: React.MouseEvent) => {
|
|
98
|
-
if (e !== null) e.preventDefault();
|
|
99
|
-
if (!searchText.trim()) return;
|
|
100
|
-
|
|
101
|
-
setIsSearching(true);
|
|
102
|
-
let term = escape(searchText.trim());
|
|
103
|
-
const data = await ApiHelper.get("/people/search?term=" + term, "MembershipApi");
|
|
104
|
-
setSearchResults(data);
|
|
105
|
-
setIsSearching(false);
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
if (!selectedPerson) return (
|
|
109
|
-
<Paper elevation={0} sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
|
110
|
-
<Box sx={{ p: 2, borderBottom: 1, borderColor: 'divider' }}>
|
|
111
|
-
<Stack direction="row" alignItems="center" spacing={2}>
|
|
112
|
-
<IconButton onClick={props.onBack}>
|
|
113
|
-
<ArrowBackIcon />
|
|
114
|
-
</IconButton>
|
|
115
|
-
<Typography variant="h6" component="h2">
|
|
116
|
-
{Locale.label("wrapper.newPrivateMessage", "New Private Message")}
|
|
117
|
-
</Typography>
|
|
118
|
-
</Stack>
|
|
119
|
-
</Box>
|
|
120
|
-
|
|
121
|
-
<Box sx={{ p: 3 }}>
|
|
122
|
-
<Stack spacing={3}>
|
|
123
|
-
<Box>
|
|
124
|
-
<Typography variant="body1" color="textSecondary" gutterBottom>
|
|
125
|
-
{Locale.label("wrapper.searchForPerson", "Search for a person to message")}
|
|
126
|
-
</Typography>
|
|
127
|
-
<TextField
|
|
128
|
-
fullWidth
|
|
129
|
-
placeholder="Search by name..."
|
|
130
|
-
id="searchText"
|
|
131
|
-
data-testid="search-input"
|
|
132
|
-
value={searchText}
|
|
133
|
-
onChange={handleChange}
|
|
134
|
-
onKeyDown={(e) => {
|
|
135
|
-
e.stopPropagation();
|
|
136
|
-
if (e.key === 'Enter') handleSearchSubmit(null);
|
|
137
|
-
}}
|
|
138
|
-
InputProps={{
|
|
139
|
-
startAdornment: (
|
|
140
|
-
<InputAdornment position="start">
|
|
141
|
-
<PersonSearchIcon color="action" />
|
|
142
|
-
</InputAdornment>
|
|
143
|
-
),
|
|
144
|
-
endAdornment: (
|
|
145
|
-
<InputAdornment position="end">
|
|
146
|
-
<Button
|
|
147
|
-
variant="contained"
|
|
148
|
-
size="small"
|
|
149
|
-
onClick={handleSearchSubmit}
|
|
150
|
-
disabled={!searchText.trim() || isSearching}
|
|
151
|
-
>
|
|
152
|
-
{Locale.label("common.search", "Search")}
|
|
153
|
-
</Button>
|
|
154
|
-
</InputAdornment>
|
|
155
|
-
)
|
|
156
|
-
}}
|
|
157
|
-
sx={{ mt: 1 }}
|
|
158
|
-
/>
|
|
159
|
-
</Box>
|
|
160
|
-
|
|
161
|
-
{isSearching && (
|
|
162
|
-
<Box>
|
|
163
|
-
{[...Array(3)].map((_, index) => (
|
|
164
|
-
<Box key={index} sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
|
165
|
-
<Skeleton variant="circular" width={48} height={48} sx={{ mr: 2 }} />
|
|
166
|
-
<Skeleton variant="text" width="60%" height={24} />
|
|
167
|
-
</Box>
|
|
168
|
-
))}
|
|
169
|
-
</Box>
|
|
170
|
-
)}
|
|
171
|
-
|
|
172
|
-
{!isSearching && searchResults.length > 0 && (
|
|
173
|
-
<Box>
|
|
174
|
-
<Typography variant="subtitle2" color="textSecondary" gutterBottom>
|
|
175
|
-
{searchResults.length} {searchResults.length === 1 ? 'person' : 'people'} found
|
|
176
|
-
</Typography>
|
|
177
|
-
<List sx={{ bgcolor: 'background.paper', borderRadius: 1 }}>
|
|
178
|
-
{searchResults.map((person, index) => (
|
|
179
|
-
<React.Fragment key={person.id}>
|
|
180
|
-
<ListItemButton
|
|
181
|
-
onClick={() => handlePersonSelected(person)}
|
|
182
|
-
sx={{ py: 2 }}
|
|
183
|
-
>
|
|
184
|
-
<ListItemAvatar>
|
|
185
|
-
<PersonAvatar person={person} size="small" />
|
|
186
|
-
</ListItemAvatar>
|
|
187
|
-
<ListItemText
|
|
188
|
-
primary={person.name.display}
|
|
189
|
-
secondary={person.contactInfo?.email || ''}
|
|
190
|
-
/>
|
|
191
|
-
</ListItemButton>
|
|
192
|
-
{index < searchResults.length - 1 && <Divider />}
|
|
193
|
-
</React.Fragment>
|
|
194
|
-
))}
|
|
195
|
-
</List>
|
|
196
|
-
</Box>
|
|
197
|
-
)}
|
|
198
|
-
|
|
199
|
-
{!isSearching && searchText && searchResults.length === 0 && (
|
|
200
|
-
<Box sx={{ textAlign: 'center', py: 4 }}>
|
|
201
|
-
<PersonSearchIcon sx={{ fontSize: 48, color: 'grey.400', mb: 2 }} />
|
|
202
|
-
<Typography variant="h6" color="textSecondary">
|
|
203
|
-
No people found
|
|
204
|
-
</Typography>
|
|
205
|
-
<Typography variant="body2" color="textSecondary">
|
|
206
|
-
Try searching with a different name
|
|
207
|
-
</Typography>
|
|
208
|
-
</Box>
|
|
209
|
-
)}
|
|
210
|
-
</Stack>
|
|
211
|
-
</Box>
|
|
212
|
-
</Paper>
|
|
213
|
-
);
|
|
214
|
-
else {
|
|
215
|
-
return (
|
|
216
|
-
<Paper elevation={0} sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
|
217
|
-
<Box sx={{ p: 2, borderBottom: 1, borderColor: 'divider' }}>
|
|
218
|
-
<Stack direction="row" alignItems="center" spacing={2}>
|
|
219
|
-
<IconButton onClick={props.onBack}>
|
|
220
|
-
<ArrowBackIcon />
|
|
221
|
-
</IconButton>
|
|
222
|
-
<Typography variant="h6" component="h2">
|
|
223
|
-
{Locale.label("wrapper.newPrivateMessage", "New Private Message")}
|
|
224
|
-
</Typography>
|
|
225
|
-
</Stack>
|
|
226
|
-
</Box>
|
|
227
|
-
|
|
228
|
-
<Box sx={{ p: 3 }}>
|
|
229
|
-
<Stack direction="row" spacing={2} alignItems="center" sx={{ mb: 3 }}>
|
|
230
|
-
<PersonAvatar person={selectedPerson} size="medium" />
|
|
231
|
-
<Box>
|
|
232
|
-
<Typography variant="subtitle1" fontWeight="medium">
|
|
233
|
-
{selectedPerson.name.display}
|
|
234
|
-
</Typography>
|
|
235
|
-
{selectedPerson.contactInfo?.email && (
|
|
236
|
-
<Typography variant="body2" color="textSecondary">
|
|
237
|
-
{selectedPerson.contactInfo.email}
|
|
238
|
-
</Typography>
|
|
239
|
-
)}
|
|
240
|
-
</Box>
|
|
241
|
-
</Stack>
|
|
242
|
-
<Divider sx={{ mb: 3 }} />
|
|
243
|
-
<AddNote
|
|
244
|
-
context={props.context}
|
|
245
|
-
conversationId={null}
|
|
246
|
-
onUpdate={handleNoteAdded}
|
|
247
|
-
createConversation={createConversation}
|
|
248
|
-
/>
|
|
249
|
-
</Box>
|
|
250
|
-
</Paper>
|
|
251
|
-
)
|
|
252
|
-
}
|
|
253
|
-
}
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
TextField,
|
|
6
|
+
Paper,
|
|
7
|
+
Box,
|
|
8
|
+
Typography,
|
|
9
|
+
Stack,
|
|
10
|
+
IconButton,
|
|
11
|
+
List,
|
|
12
|
+
ListItem,
|
|
13
|
+
ListItemAvatar,
|
|
14
|
+
ListItemText,
|
|
15
|
+
ListItemButton,
|
|
16
|
+
InputAdornment,
|
|
17
|
+
Divider,
|
|
18
|
+
Skeleton,
|
|
19
|
+
useTheme
|
|
20
|
+
} from "@mui/material";
|
|
21
|
+
import {
|
|
22
|
+
ArrowBack as ArrowBackIcon,
|
|
23
|
+
PersonSearch as PersonSearchIcon
|
|
24
|
+
} from "@mui/icons-material";
|
|
25
|
+
import React, { useEffect, useState } from "react";
|
|
26
|
+
import { ApiHelper, Locale } from "../../helpers";
|
|
27
|
+
import { ConversationInterface, PersonInterface, PrivateMessageInterface, UserContextInterface } from "@churchapps/helpers";
|
|
28
|
+
import { AddNote } from "../notes/AddNote";
|
|
29
|
+
import { PersonAvatar } from "../PersonAvatar";
|
|
30
|
+
|
|
31
|
+
interface Props {
|
|
32
|
+
context: UserContextInterface;
|
|
33
|
+
onSelectMessage: (pm: PrivateMessageInterface) => void
|
|
34
|
+
onBack: () => void
|
|
35
|
+
selectedPerson?: PersonInterface
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const NewPrivateMessage: React.FC<Props> = (props) => {
|
|
39
|
+
|
|
40
|
+
const [searchText, setSearchText] = React.useState("");
|
|
41
|
+
const [searchResults, setSearchResults] = React.useState([]);
|
|
42
|
+
const [selectedPerson, setSelectedPerson] = React.useState<PersonInterface>(null);
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
46
|
+
setSearchText(e.currentTarget.value);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/*
|
|
50
|
+
const handleKeyDown = (e: React.KeyboardEvent<any>) => {
|
|
51
|
+
//if (e.key === "Enter") { e.preventDefault(); handleSubmit(null); }
|
|
52
|
+
}
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
const handlePersonSelected = async (person: PersonInterface) => {
|
|
56
|
+
try {
|
|
57
|
+
const existing: PrivateMessageInterface = await ApiHelper.get("/privateMessages/existing/" + person.id, "MessagingApi");
|
|
58
|
+
if (existing?.id) {
|
|
59
|
+
existing.person = person;
|
|
60
|
+
props.onSelectMessage(existing);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
// No existing conversation found, continue to create new one
|
|
65
|
+
console.log("No existing conversation found for person:", person.id);
|
|
66
|
+
}
|
|
67
|
+
setSelectedPerson(person);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
const handleNoteAdded = () => {
|
|
72
|
+
handlePersonSelected(selectedPerson);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const createConversation = async () => {
|
|
76
|
+
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" }
|
|
77
|
+
const result: ConversationInterface[] = await ApiHelper.post("/conversations", [conv], "MessagingApi");
|
|
78
|
+
|
|
79
|
+
const pm: PrivateMessageInterface = {
|
|
80
|
+
fromPersonId: props.context.person.id,
|
|
81
|
+
toPersonId: selectedPerson.id,
|
|
82
|
+
conversationId: result[0].id
|
|
83
|
+
}
|
|
84
|
+
const privateMessages: PrivateMessageInterface[] = await ApiHelper.post("/privateMessages", [pm], "MessagingApi");
|
|
85
|
+
return privateMessages[0].conversationId;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (props.selectedPerson) handlePersonSelected(props.selectedPerson);
|
|
91
|
+
}, [props.selectedPerson]);
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
const theme = useTheme();
|
|
95
|
+
const [isSearching, setIsSearching] = useState(false);
|
|
96
|
+
|
|
97
|
+
const handleSearchSubmit = async (e: React.MouseEvent) => {
|
|
98
|
+
if (e !== null) e.preventDefault();
|
|
99
|
+
if (!searchText.trim()) return;
|
|
100
|
+
|
|
101
|
+
setIsSearching(true);
|
|
102
|
+
let term = escape(searchText.trim());
|
|
103
|
+
const data = await ApiHelper.get("/people/search?term=" + term, "MembershipApi");
|
|
104
|
+
setSearchResults(data);
|
|
105
|
+
setIsSearching(false);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
if (!selectedPerson) return (
|
|
109
|
+
<Paper elevation={0} sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
|
110
|
+
<Box sx={{ p: 2, borderBottom: 1, borderColor: 'divider' }}>
|
|
111
|
+
<Stack direction="row" alignItems="center" spacing={2}>
|
|
112
|
+
<IconButton onClick={props.onBack}>
|
|
113
|
+
<ArrowBackIcon />
|
|
114
|
+
</IconButton>
|
|
115
|
+
<Typography variant="h6" component="h2">
|
|
116
|
+
{Locale.label("wrapper.newPrivateMessage", "New Private Message")}
|
|
117
|
+
</Typography>
|
|
118
|
+
</Stack>
|
|
119
|
+
</Box>
|
|
120
|
+
|
|
121
|
+
<Box sx={{ p: 3 }}>
|
|
122
|
+
<Stack spacing={3}>
|
|
123
|
+
<Box>
|
|
124
|
+
<Typography variant="body1" color="textSecondary" gutterBottom>
|
|
125
|
+
{Locale.label("wrapper.searchForPerson", "Search for a person to message")}
|
|
126
|
+
</Typography>
|
|
127
|
+
<TextField
|
|
128
|
+
fullWidth
|
|
129
|
+
placeholder="Search by name..."
|
|
130
|
+
id="searchText"
|
|
131
|
+
data-testid="search-input"
|
|
132
|
+
value={searchText}
|
|
133
|
+
onChange={handleChange}
|
|
134
|
+
onKeyDown={(e) => {
|
|
135
|
+
e.stopPropagation();
|
|
136
|
+
if (e.key === 'Enter') handleSearchSubmit(null);
|
|
137
|
+
}}
|
|
138
|
+
InputProps={{
|
|
139
|
+
startAdornment: (
|
|
140
|
+
<InputAdornment position="start">
|
|
141
|
+
<PersonSearchIcon color="action" />
|
|
142
|
+
</InputAdornment>
|
|
143
|
+
),
|
|
144
|
+
endAdornment: (
|
|
145
|
+
<InputAdornment position="end">
|
|
146
|
+
<Button
|
|
147
|
+
variant="contained"
|
|
148
|
+
size="small"
|
|
149
|
+
onClick={handleSearchSubmit}
|
|
150
|
+
disabled={!searchText.trim() || isSearching}
|
|
151
|
+
>
|
|
152
|
+
{Locale.label("common.search", "Search")}
|
|
153
|
+
</Button>
|
|
154
|
+
</InputAdornment>
|
|
155
|
+
)
|
|
156
|
+
}}
|
|
157
|
+
sx={{ mt: 1 }}
|
|
158
|
+
/>
|
|
159
|
+
</Box>
|
|
160
|
+
|
|
161
|
+
{isSearching && (
|
|
162
|
+
<Box>
|
|
163
|
+
{[...Array(3)].map((_, index) => (
|
|
164
|
+
<Box key={index} sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
|
165
|
+
<Skeleton variant="circular" width={48} height={48} sx={{ mr: 2 }} />
|
|
166
|
+
<Skeleton variant="text" width="60%" height={24} />
|
|
167
|
+
</Box>
|
|
168
|
+
))}
|
|
169
|
+
</Box>
|
|
170
|
+
)}
|
|
171
|
+
|
|
172
|
+
{!isSearching && searchResults.length > 0 && (
|
|
173
|
+
<Box>
|
|
174
|
+
<Typography variant="subtitle2" color="textSecondary" gutterBottom>
|
|
175
|
+
{searchResults.length} {searchResults.length === 1 ? 'person' : 'people'} found
|
|
176
|
+
</Typography>
|
|
177
|
+
<List sx={{ bgcolor: 'background.paper', borderRadius: 1 }}>
|
|
178
|
+
{searchResults.map((person, index) => (
|
|
179
|
+
<React.Fragment key={person.id}>
|
|
180
|
+
<ListItemButton
|
|
181
|
+
onClick={() => handlePersonSelected(person)}
|
|
182
|
+
sx={{ py: 2 }}
|
|
183
|
+
>
|
|
184
|
+
<ListItemAvatar>
|
|
185
|
+
<PersonAvatar person={person} size="small" />
|
|
186
|
+
</ListItemAvatar>
|
|
187
|
+
<ListItemText
|
|
188
|
+
primary={person.name.display}
|
|
189
|
+
secondary={person.contactInfo?.email || ''}
|
|
190
|
+
/>
|
|
191
|
+
</ListItemButton>
|
|
192
|
+
{index < searchResults.length - 1 && <Divider />}
|
|
193
|
+
</React.Fragment>
|
|
194
|
+
))}
|
|
195
|
+
</List>
|
|
196
|
+
</Box>
|
|
197
|
+
)}
|
|
198
|
+
|
|
199
|
+
{!isSearching && searchText && searchResults.length === 0 && (
|
|
200
|
+
<Box sx={{ textAlign: 'center', py: 4 }}>
|
|
201
|
+
<PersonSearchIcon sx={{ fontSize: 48, color: 'grey.400', mb: 2 }} />
|
|
202
|
+
<Typography variant="h6" color="textSecondary">
|
|
203
|
+
No people found
|
|
204
|
+
</Typography>
|
|
205
|
+
<Typography variant="body2" color="textSecondary">
|
|
206
|
+
Try searching with a different name
|
|
207
|
+
</Typography>
|
|
208
|
+
</Box>
|
|
209
|
+
)}
|
|
210
|
+
</Stack>
|
|
211
|
+
</Box>
|
|
212
|
+
</Paper>
|
|
213
|
+
);
|
|
214
|
+
else {
|
|
215
|
+
return (
|
|
216
|
+
<Paper elevation={0} sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
|
217
|
+
<Box sx={{ p: 2, borderBottom: 1, borderColor: 'divider' }}>
|
|
218
|
+
<Stack direction="row" alignItems="center" spacing={2}>
|
|
219
|
+
<IconButton onClick={props.onBack}>
|
|
220
|
+
<ArrowBackIcon />
|
|
221
|
+
</IconButton>
|
|
222
|
+
<Typography variant="h6" component="h2">
|
|
223
|
+
{Locale.label("wrapper.newPrivateMessage", "New Private Message")}
|
|
224
|
+
</Typography>
|
|
225
|
+
</Stack>
|
|
226
|
+
</Box>
|
|
227
|
+
|
|
228
|
+
<Box sx={{ p: 3 }}>
|
|
229
|
+
<Stack direction="row" spacing={2} alignItems="center" sx={{ mb: 3 }}>
|
|
230
|
+
<PersonAvatar person={selectedPerson} size="medium" />
|
|
231
|
+
<Box>
|
|
232
|
+
<Typography variant="subtitle1" fontWeight="medium">
|
|
233
|
+
{selectedPerson.name.display}
|
|
234
|
+
</Typography>
|
|
235
|
+
{selectedPerson.contactInfo?.email && (
|
|
236
|
+
<Typography variant="body2" color="textSecondary">
|
|
237
|
+
{selectedPerson.contactInfo.email}
|
|
238
|
+
</Typography>
|
|
239
|
+
)}
|
|
240
|
+
</Box>
|
|
241
|
+
</Stack>
|
|
242
|
+
<Divider sx={{ mb: 3 }} />
|
|
243
|
+
<AddNote
|
|
244
|
+
context={props.context}
|
|
245
|
+
conversationId={null}
|
|
246
|
+
onUpdate={handleNoteAdded}
|
|
247
|
+
createConversation={createConversation}
|
|
248
|
+
/>
|
|
249
|
+
</Box>
|
|
250
|
+
</Paper>
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
}
|