@churchapps/apphelper 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/dist/components/Loading.d.ts +1 -0
  2. package/dist/components/Loading.d.ts.map +1 -1
  3. package/dist/components/Loading.js +1 -45
  4. package/dist/components/Loading.js.map +1 -1
  5. package/dist/components/notes/Notes.js +27 -27
  6. package/dist/helpers/AnalyticsHelper.d.ts.map +1 -1
  7. package/dist/helpers/AnalyticsHelper.js +21 -6
  8. package/dist/helpers/AnalyticsHelper.js.map +1 -1
  9. package/dist/public/css/cropper.css +309 -309
  10. package/dist/public/css/styles.css +111 -111
  11. package/dist/public/locales/de.json +270 -0
  12. package/dist/public/locales/en.json +277 -0
  13. package/dist/public/locales/es.json +272 -0
  14. package/dist/public/locales/fr.json +270 -0
  15. package/dist/public/locales/hi.json +270 -0
  16. package/dist/public/locales/it.json +270 -0
  17. package/dist/public/locales/ko.json +270 -0
  18. package/dist/public/locales/no.json +270 -0
  19. package/dist/public/locales/pt.json +270 -0
  20. package/dist/public/locales/ru.json +270 -0
  21. package/dist/public/locales/tl.json +270 -0
  22. package/dist/public/locales/zh.json +270 -0
  23. package/package.json +72 -72
  24. package/public/css/cropper.css +309 -309
  25. package/public/css/styles.css +111 -111
  26. package/public/locales/de.json +269 -269
  27. package/public/locales/en.json +276 -276
  28. package/public/locales/es.json +272 -272
  29. package/public/locales/fr.json +269 -269
  30. package/public/locales/hi.json +269 -269
  31. package/public/locales/it.json +269 -269
  32. package/public/locales/ko.json +269 -269
  33. package/public/locales/no.json +269 -269
  34. package/public/locales/pt.json +269 -269
  35. package/public/locales/ru.json +269 -269
  36. package/public/locales/tl.json +269 -269
  37. package/public/locales/zh.json +269 -269
  38. package/src/components/DisplayBox.tsx +83 -83
  39. package/src/components/ErrorMessages.tsx +28 -28
  40. package/src/components/ExportLink.tsx +81 -81
  41. package/src/components/FloatingSupport.tsx +18 -18
  42. package/src/components/FormCardPayment.tsx +184 -184
  43. package/src/components/FormSubmissionEdit.tsx +168 -168
  44. package/src/components/HelpIcon.tsx +12 -12
  45. package/src/components/ImageEditor.tsx +161 -161
  46. package/src/components/InputBox.tsx +96 -96
  47. package/src/components/Loading.tsx +31 -77
  48. package/src/components/PageHeader.tsx +110 -110
  49. package/src/components/PersonAvatar.tsx +77 -77
  50. package/src/components/QuestionEdit.tsx +99 -99
  51. package/src/components/SmallButton.tsx +42 -42
  52. package/src/components/SupportModal.tsx +32 -32
  53. package/src/components/TabPanel.tsx +28 -28
  54. package/src/components/gallery/GalleryModal.tsx +173 -173
  55. package/src/components/gallery/StockPhotos.tsx +95 -95
  56. package/src/components/gallery/index.ts +1 -1
  57. package/src/components/header/Banner.tsx +11 -11
  58. package/src/components/header/PrimaryMenu.tsx +100 -100
  59. package/src/components/header/SecondaryMenu.tsx +23 -23
  60. package/src/components/header/SecondaryMenuAlt.tsx +40 -40
  61. package/src/components/header/SiteHeader.tsx +207 -207
  62. package/src/components/header/SupportDrawer.tsx +111 -111
  63. package/src/components/header/index.tsx +2 -2
  64. package/src/components/index.tsx +20 -20
  65. package/src/components/notes/AddNote.tsx +180 -180
  66. package/src/components/notes/Note.tsx +68 -68
  67. package/src/components/notes/Notes.tsx +208 -208
  68. package/src/components/notes/index.ts +3 -3
  69. package/src/components/wrapper/AppList.tsx +19 -19
  70. package/src/components/wrapper/ChurchList.tsx +154 -154
  71. package/src/components/wrapper/NavItem.tsx +47 -47
  72. package/src/components/wrapper/NewPrivateMessage.tsx +253 -253
  73. package/src/components/wrapper/Notifications.tsx +223 -223
  74. package/src/components/wrapper/PrivateMessageDetails.tsx +112 -112
  75. package/src/components/wrapper/PrivateMessages.tsx +576 -576
  76. package/src/components/wrapper/UserMenu.tsx +383 -383
  77. package/src/components/wrapper/index.tsx +8 -8
  78. package/src/helpers/AnalyticsHelper.ts +44 -32
  79. package/src/helpers/AppearanceHelper.ts +73 -73
  80. package/src/helpers/ArrayHelper.ts +87 -87
  81. package/src/helpers/CurrencyHelper.ts +10 -10
  82. package/src/helpers/DateHelper.ts +104 -104
  83. package/src/helpers/ErrorHelper.ts +43 -43
  84. package/src/helpers/EventHelper.ts +49 -49
  85. package/src/helpers/FileHelper.ts +31 -31
  86. package/src/helpers/Locale.ts +457 -457
  87. package/src/helpers/NotificationService.ts +296 -296
  88. package/src/helpers/PersonHelper.ts +62 -62
  89. package/src/helpers/SlugHelper.ts +37 -37
  90. package/src/helpers/SocketHelper.ts +296 -296
  91. package/src/helpers/UniqueIdHelper.ts +36 -36
  92. package/src/helpers/UserHelper.ts +107 -107
  93. package/src/helpers/createEmotionCache.ts +17 -17
  94. package/src/helpers/index.ts +58 -58
  95. package/src/hooks/index.ts +3 -3
  96. package/src/hooks/useMountedState.ts +16 -16
  97. package/src/hooks/useNotifications.ts +93 -93
  98. package/src/index.ts +2 -2
  99. package/src/types/interface-extensions.d.ts +11 -11
  100. package/tsconfig.json +31 -31
@@ -1,208 +1,208 @@
1
- import React from "react";
2
- import { Note } from "./Note";
3
- import { AddNote } from "./AddNote";
4
- import { DisplayBox, Loading } from "../";
5
- import { ApiHelper, ArrayHelper, Locale } from "../../helpers";
6
- import { MessageInterface, UserContextInterface } from "@churchapps/helpers";
7
-
8
- interface Props {
9
- //showEditNote: (messageId?: string) => void;
10
- conversationId: string;
11
- createConversation?: () => Promise<string>;
12
- noDisplayBox?: boolean;
13
- context: UserContextInterface;
14
- maxHeight?: any;
15
- refreshKey?: number;
16
- }
17
-
18
- export function Notes(props: Props) {
19
-
20
- const [messages, setMessages] = React.useState<MessageInterface[]>(null)
21
- const [editMessageId, setEditMessageId] = React.useState(null)
22
- const [isInitialLoad, setIsInitialLoad] = React.useState(true)
23
- const [previousMessageCount, setPreviousMessageCount] = React.useState(0)
24
-
25
- // Add CSS for custom scrollbar styling
26
- React.useEffect(() => {
27
- const styleId = 'notes-scrollbar-styles';
28
- if (!document.getElementById(styleId)) {
29
- const style = document.createElement('style');
30
- style.id = styleId;
31
- style.textContent = `
32
- .notes-scroll-container {
33
- scrollbar-width: thin;
34
- scrollbar-color: rgba(0, 0, 0, 0.3) rgba(0, 0, 0, 0.1);
35
- }
36
- .notes-scroll-container::-webkit-scrollbar {
37
- width: 12px;
38
- background: transparent;
39
- }
40
- .notes-scroll-container::-webkit-scrollbar-track {
41
- background: rgba(0, 0, 0, 0.1);
42
- border-radius: 6px;
43
- margin: 4px;
44
- }
45
- .notes-scroll-container::-webkit-scrollbar-thumb {
46
- background: rgba(0, 0, 0, 0.3);
47
- border-radius: 6px;
48
- border: 2px solid transparent;
49
- background-clip: content-box;
50
- }
51
- .notes-scroll-container::-webkit-scrollbar-thumb:hover {
52
- background: rgba(0, 0, 0, 0.5);
53
- background-clip: content-box;
54
- }
55
- .notes-scroll-container::-webkit-scrollbar-corner {
56
- background: transparent;
57
- }
58
- `;
59
- document.head.appendChild(style);
60
- }
61
- }, []);
62
-
63
- const loadNotes = async () => {
64
- try {
65
- const messages: MessageInterface[] = (props.conversationId) ? await ApiHelper.get("/messages/conversation/" + props.conversationId, "MessagingApi") : [];
66
- if (messages.length > 0) {
67
- const peopleIds = ArrayHelper.getIds(messages, "personId");
68
- const people = await ApiHelper.get("/people/basic?ids=" + peopleIds.join(","), "MembershipApi");
69
- messages.forEach(n => {
70
- n.person = ArrayHelper.getOne(people, "id", n.personId);
71
- })
72
- }
73
- setMessages(messages);
74
- setEditMessageId(null);
75
-
76
- // Mark as no longer initial load after first load
77
- if (isInitialLoad) {
78
- setIsInitialLoad(false);
79
- }
80
- } catch (error) {
81
- console.error("❌ Failed to load messages for conversation:", props.conversationId, error);
82
- // Don't clear messages on error - keep showing existing messages
83
- // Only set isInitialLoad to false if this was the first load attempt
84
- if (isInitialLoad) {
85
- setIsInitialLoad(false);
86
- }
87
- }
88
- };
89
-
90
- const getNotes = () => {
91
- if (!messages) return <Loading />
92
- if (messages.length === 0) return <></>
93
- else {
94
- let noteArray: React.ReactNode[] = [];
95
- for (let i = 0; i < messages.length; i++) noteArray.push(<Note message={messages[i]} key={messages[i].id} showEditNote={setEditMessageId} context={props.context} />);
96
- return noteArray;
97
- }
98
- }
99
-
100
- const getNotesWrapper = () => {
101
- const notes = getNotes();
102
- if (props.maxHeight) {
103
- return (
104
- <div
105
- id="notesScroll"
106
- style={{
107
- flex: 1,
108
- minHeight: 0,
109
- overflowY: "auto",
110
- overflowX: "hidden",
111
- padding: "8px 12px",
112
- scrollBehavior: "smooth",
113
- height: "100%"
114
- }}
115
- className="notes-scroll-container"
116
- data-testid="message-scroll-area"
117
- >
118
- <div style={{
119
- display: "flex",
120
- flexDirection: "column",
121
- gap: "8px",
122
- minHeight: "min-content"
123
- }}>
124
- {notes}
125
- </div>
126
- </div>
127
- );
128
- }
129
- else return notes;
130
- }
131
-
132
- React.useEffect(() => { loadNotes() }, [props.conversationId, props.refreshKey]); //eslint-disable-line
133
-
134
- // Simply reload notes when refreshKey changes
135
- // This is triggered by the parent component when WebSocket messages arrive
136
-
137
- // Auto-scroll to bottom only when new messages are added (not on initial load)
138
- React.useEffect(() => {
139
- if (props.maxHeight && messages?.length > 0 && !isInitialLoad) {
140
- const currentMessageCount = messages.length;
141
-
142
- // Only auto-scroll if messages were added
143
- if (currentMessageCount > previousMessageCount) {
144
- // Use requestAnimationFrame for smoother scrolling
145
- requestAnimationFrame(() => {
146
- const element = window?.document?.getElementById("notesScroll");
147
- if (element) {
148
- element.scrollTop = element.scrollHeight;
149
- }
150
- });
151
- }
152
-
153
- setPreviousMessageCount(currentMessageCount);
154
- } else if (messages?.length > 0 && isInitialLoad) {
155
- // On initial load, just set the previous count without scrolling
156
- setPreviousMessageCount(messages.length);
157
- }
158
- }, [messages, props.maxHeight, isInitialLoad, previousMessageCount]);
159
-
160
- let result = props.maxHeight ? (
161
- <div style={{
162
- height: "100%",
163
- display: "flex",
164
- flexDirection: "column",
165
- overflow: "hidden",
166
- minHeight: 0
167
- }}>
168
- {/* Messages area - scrollable */}
169
- <div style={{
170
- flex: 1,
171
- minHeight: 0,
172
- display: "flex",
173
- flexDirection: "column",
174
- overflow: "hidden"
175
- }}>
176
- {getNotesWrapper()}
177
- </div>
178
-
179
- {/* Input area - always visible at bottom */}
180
- {messages && (
181
- <div style={{
182
- flexShrink: 0,
183
- borderTop: "1px solid #e0e0e0",
184
- backgroundColor: "#fafafa",
185
- padding: "12px",
186
- minHeight: "auto",
187
- maxHeight: "200px"
188
- }}>
189
- <AddNote
190
- context={props.context}
191
- conversationId={props.conversationId}
192
- onUpdate={loadNotes}
193
- createConversation={props.createConversation}
194
- messageId={editMessageId}
195
- />
196
- </div>
197
- )}
198
- </div>
199
- ) : (
200
- <>
201
- {getNotesWrapper()}
202
- {messages && (<AddNote context={props.context} conversationId={props.conversationId} onUpdate={loadNotes} createConversation={props.createConversation} messageId={editMessageId} />)}
203
- </>
204
- );
205
-
206
- if (props.noDisplayBox) return result;
207
- else return (<DisplayBox id="notesBox" data-testid="notes-box" headerIcon="sticky_note_2" headerText={Locale.label("notes.notes", "Notes")}>{result}</DisplayBox>);
208
- };
1
+ import React from "react";
2
+ import { Note } from "./Note";
3
+ import { AddNote } from "./AddNote";
4
+ import { DisplayBox, Loading } from "../";
5
+ import { ApiHelper, ArrayHelper, Locale } from "../../helpers";
6
+ import { MessageInterface, UserContextInterface } from "@churchapps/helpers";
7
+
8
+ interface Props {
9
+ //showEditNote: (messageId?: string) => void;
10
+ conversationId: string;
11
+ createConversation?: () => Promise<string>;
12
+ noDisplayBox?: boolean;
13
+ context: UserContextInterface;
14
+ maxHeight?: any;
15
+ refreshKey?: number;
16
+ }
17
+
18
+ export function Notes(props: Props) {
19
+
20
+ const [messages, setMessages] = React.useState<MessageInterface[]>(null)
21
+ const [editMessageId, setEditMessageId] = React.useState(null)
22
+ const [isInitialLoad, setIsInitialLoad] = React.useState(true)
23
+ const [previousMessageCount, setPreviousMessageCount] = React.useState(0)
24
+
25
+ // Add CSS for custom scrollbar styling
26
+ React.useEffect(() => {
27
+ const styleId = 'notes-scrollbar-styles';
28
+ if (!document.getElementById(styleId)) {
29
+ const style = document.createElement('style');
30
+ style.id = styleId;
31
+ style.textContent = `
32
+ .notes-scroll-container {
33
+ scrollbar-width: thin;
34
+ scrollbar-color: rgba(0, 0, 0, 0.3) rgba(0, 0, 0, 0.1);
35
+ }
36
+ .notes-scroll-container::-webkit-scrollbar {
37
+ width: 12px;
38
+ background: transparent;
39
+ }
40
+ .notes-scroll-container::-webkit-scrollbar-track {
41
+ background: rgba(0, 0, 0, 0.1);
42
+ border-radius: 6px;
43
+ margin: 4px;
44
+ }
45
+ .notes-scroll-container::-webkit-scrollbar-thumb {
46
+ background: rgba(0, 0, 0, 0.3);
47
+ border-radius: 6px;
48
+ border: 2px solid transparent;
49
+ background-clip: content-box;
50
+ }
51
+ .notes-scroll-container::-webkit-scrollbar-thumb:hover {
52
+ background: rgba(0, 0, 0, 0.5);
53
+ background-clip: content-box;
54
+ }
55
+ .notes-scroll-container::-webkit-scrollbar-corner {
56
+ background: transparent;
57
+ }
58
+ `;
59
+ document.head.appendChild(style);
60
+ }
61
+ }, []);
62
+
63
+ const loadNotes = async () => {
64
+ try {
65
+ const messages: MessageInterface[] = (props.conversationId) ? await ApiHelper.get("/messages/conversation/" + props.conversationId, "MessagingApi") : [];
66
+ if (messages.length > 0) {
67
+ const peopleIds = ArrayHelper.getIds(messages, "personId");
68
+ const people = await ApiHelper.get("/people/basic?ids=" + peopleIds.join(","), "MembershipApi");
69
+ messages.forEach(n => {
70
+ n.person = ArrayHelper.getOne(people, "id", n.personId);
71
+ })
72
+ }
73
+ setMessages(messages);
74
+ setEditMessageId(null);
75
+
76
+ // Mark as no longer initial load after first load
77
+ if (isInitialLoad) {
78
+ setIsInitialLoad(false);
79
+ }
80
+ } catch (error) {
81
+ console.error("❌ Failed to load messages for conversation:", props.conversationId, error);
82
+ // Don't clear messages on error - keep showing existing messages
83
+ // Only set isInitialLoad to false if this was the first load attempt
84
+ if (isInitialLoad) {
85
+ setIsInitialLoad(false);
86
+ }
87
+ }
88
+ };
89
+
90
+ const getNotes = () => {
91
+ if (!messages) return <Loading />
92
+ if (messages.length === 0) return <></>
93
+ else {
94
+ let noteArray: React.ReactNode[] = [];
95
+ for (let i = 0; i < messages.length; i++) noteArray.push(<Note message={messages[i]} key={messages[i].id} showEditNote={setEditMessageId} context={props.context} />);
96
+ return noteArray;
97
+ }
98
+ }
99
+
100
+ const getNotesWrapper = () => {
101
+ const notes = getNotes();
102
+ if (props.maxHeight) {
103
+ return (
104
+ <div
105
+ id="notesScroll"
106
+ style={{
107
+ flex: 1,
108
+ minHeight: 0,
109
+ overflowY: "auto",
110
+ overflowX: "hidden",
111
+ padding: "8px 12px",
112
+ scrollBehavior: "smooth",
113
+ height: "100%"
114
+ }}
115
+ className="notes-scroll-container"
116
+ data-testid="message-scroll-area"
117
+ >
118
+ <div style={{
119
+ display: "flex",
120
+ flexDirection: "column",
121
+ gap: "8px",
122
+ minHeight: "min-content"
123
+ }}>
124
+ {notes}
125
+ </div>
126
+ </div>
127
+ );
128
+ }
129
+ else return notes;
130
+ }
131
+
132
+ React.useEffect(() => { loadNotes() }, [props.conversationId, props.refreshKey]); //eslint-disable-line
133
+
134
+ // Simply reload notes when refreshKey changes
135
+ // This is triggered by the parent component when WebSocket messages arrive
136
+
137
+ // Auto-scroll to bottom only when new messages are added (not on initial load)
138
+ React.useEffect(() => {
139
+ if (props.maxHeight && messages?.length > 0 && !isInitialLoad) {
140
+ const currentMessageCount = messages.length;
141
+
142
+ // Only auto-scroll if messages were added
143
+ if (currentMessageCount > previousMessageCount) {
144
+ // Use requestAnimationFrame for smoother scrolling
145
+ requestAnimationFrame(() => {
146
+ const element = window?.document?.getElementById("notesScroll");
147
+ if (element) {
148
+ element.scrollTop = element.scrollHeight;
149
+ }
150
+ });
151
+ }
152
+
153
+ setPreviousMessageCount(currentMessageCount);
154
+ } else if (messages?.length > 0 && isInitialLoad) {
155
+ // On initial load, just set the previous count without scrolling
156
+ setPreviousMessageCount(messages.length);
157
+ }
158
+ }, [messages, props.maxHeight, isInitialLoad, previousMessageCount]);
159
+
160
+ let result = props.maxHeight ? (
161
+ <div style={{
162
+ height: "100%",
163
+ display: "flex",
164
+ flexDirection: "column",
165
+ overflow: "hidden",
166
+ minHeight: 0
167
+ }}>
168
+ {/* Messages area - scrollable */}
169
+ <div style={{
170
+ flex: 1,
171
+ minHeight: 0,
172
+ display: "flex",
173
+ flexDirection: "column",
174
+ overflow: "hidden"
175
+ }}>
176
+ {getNotesWrapper()}
177
+ </div>
178
+
179
+ {/* Input area - always visible at bottom */}
180
+ {messages && (
181
+ <div style={{
182
+ flexShrink: 0,
183
+ borderTop: "1px solid #e0e0e0",
184
+ backgroundColor: "#fafafa",
185
+ padding: "12px",
186
+ minHeight: "auto",
187
+ maxHeight: "200px"
188
+ }}>
189
+ <AddNote
190
+ context={props.context}
191
+ conversationId={props.conversationId}
192
+ onUpdate={loadNotes}
193
+ createConversation={props.createConversation}
194
+ messageId={editMessageId}
195
+ />
196
+ </div>
197
+ )}
198
+ </div>
199
+ ) : (
200
+ <>
201
+ {getNotesWrapper()}
202
+ {messages && (<AddNote context={props.context} conversationId={props.conversationId} onUpdate={loadNotes} createConversation={props.createConversation} messageId={editMessageId} />)}
203
+ </>
204
+ );
205
+
206
+ if (props.noDisplayBox) return result;
207
+ else return (<DisplayBox id="notesBox" data-testid="notes-box" headerIcon="sticky_note_2" headerText={Locale.label("notes.notes", "Notes")}>{result}</DisplayBox>);
208
+ };
@@ -1,3 +1,3 @@
1
- export { Notes } from "./Notes";
2
- export { Note } from "./Note";
3
- export { AddNote } from "./AddNote";
1
+ export { Notes } from "./Notes";
2
+ export { Note } from "./Note";
3
+ export { AddNote } from "./AddNote";
@@ -1,19 +1,19 @@
1
- "use client";
2
-
3
- import React from "react";
4
- import { ApiHelper, UserHelper, CommonEnvironmentHelper, LoginUserChurchInterface } from "@churchapps/helpers";
5
- import { NavItem } from "./NavItem";
6
-
7
- export interface Props { appName: string; currentUserChurch: LoginUserChurchInterface; onNavigate: (url: string) => void; }
8
-
9
- export const AppList: React.FC<Props> = props => {
10
- const jwt = ApiHelper.getConfig("MembershipApi").jwt;
11
- const churchId = UserHelper.currentUserChurch.church.id;
12
- return (
13
- <>
14
- <NavItem url={`${CommonEnvironmentHelper.ChumsRoot}/login?jwt=${jwt}&churchId=${churchId}`} selected={props.appName === "CHUMS"} external={true} label="Chums" icon="logout" onNavigate={props.onNavigate} />
15
- <NavItem url={`${CommonEnvironmentHelper.B1Root.replace("{key}", props.currentUserChurch.church.subDomain)}/login?jwt=${jwt}&churchId=${churchId}`} selected={props.appName === "B1.church"} external={true} label="B1.Church" icon="logout" onNavigate={props.onNavigate} />
16
- <NavItem url={`${CommonEnvironmentHelper.LessonsRoot}/login?jwt=${jwt}&churchId=${churchId}`} selected={props.appName === "Lessons.church"} external={true} label="Lessons.church" icon="logout" onNavigate={props.onNavigate} />
17
- </>
18
- );
19
- }
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { ApiHelper, UserHelper, CommonEnvironmentHelper, LoginUserChurchInterface } from "@churchapps/helpers";
5
+ import { NavItem } from "./NavItem";
6
+
7
+ export interface Props { appName: string; currentUserChurch: LoginUserChurchInterface; onNavigate: (url: string) => void; }
8
+
9
+ export const AppList: React.FC<Props> = props => {
10
+ const jwt = ApiHelper.getConfig("MembershipApi").jwt;
11
+ const churchId = UserHelper.currentUserChurch.church.id;
12
+ return (
13
+ <>
14
+ <NavItem url={`${CommonEnvironmentHelper.ChumsRoot}/login?jwt=${jwt}&churchId=${churchId}`} selected={props.appName === "CHUMS"} external={true} label="Chums" icon="logout" onNavigate={props.onNavigate} />
15
+ <NavItem url={`${CommonEnvironmentHelper.B1Root.replace("{key}", props.currentUserChurch.church.subDomain)}/login?jwt=${jwt}&churchId=${churchId}`} selected={props.appName === "B1.church"} external={true} label="B1.Church" icon="logout" onNavigate={props.onNavigate} />
16
+ <NavItem url={`${CommonEnvironmentHelper.LessonsRoot}/login?jwt=${jwt}&churchId=${churchId}`} selected={props.appName === "Lessons.church"} external={true} label="Lessons.church" icon="logout" onNavigate={props.onNavigate} />
17
+ </>
18
+ );
19
+ }