@churchapps/apphelper-login 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.
Files changed (52) hide show
  1. package/dist/LoginPage.d.ts +1 -0
  2. package/dist/LoginPage.d.ts.map +1 -1
  3. package/dist/LoginPage.js +14 -3
  4. package/dist/LoginPage.js.map +1 -1
  5. package/dist/LogoutPage.d.ts.map +1 -1
  6. package/dist/LogoutPage.js +1 -1
  7. package/dist/LogoutPage.js.map +1 -1
  8. package/dist/components/Forgot.d.ts.map +1 -1
  9. package/dist/components/Forgot.js +71 -83
  10. package/dist/components/Forgot.js.map +1 -1
  11. package/dist/components/Login.d.ts.map +1 -1
  12. package/dist/components/Login.js +86 -106
  13. package/dist/components/Login.js.map +1 -1
  14. package/dist/components/LoginSetPassword.d.ts.map +1 -1
  15. package/dist/components/LoginSetPassword.js +70 -94
  16. package/dist/components/LoginSetPassword.js.map +1 -1
  17. package/dist/components/Register.d.ts.map +1 -1
  18. package/dist/components/Register.js +104 -132
  19. package/dist/components/Register.js.map +1 -1
  20. package/dist/components/SelectChurchModal.d.ts.map +1 -1
  21. package/dist/components/SelectChurchModal.js +2 -1
  22. package/dist/components/SelectChurchModal.js.map +1 -1
  23. package/dist/components/SelectChurchRegister.d.ts.map +1 -1
  24. package/dist/components/SelectChurchRegister.js +11 -5
  25. package/dist/components/SelectChurchRegister.js.map +1 -1
  26. package/dist/components/SelectChurchSearch.d.ts.map +1 -1
  27. package/dist/components/SelectChurchSearch.js +3 -2
  28. package/dist/components/SelectChurchSearch.js.map +1 -1
  29. package/dist/components/SelectableChurch.js +1 -1
  30. package/dist/components/SelectableChurch.js.map +1 -1
  31. package/dist/helpers/AnalyticsHelper.d.ts.map +1 -1
  32. package/dist/helpers/AnalyticsHelper.js +3 -3
  33. package/dist/helpers/AnalyticsHelper.js.map +1 -1
  34. package/dist/helpers/Locale.d.ts.map +1 -1
  35. package/dist/helpers/Locale.js +9 -11
  36. package/dist/helpers/Locale.js.map +1 -1
  37. package/package.json +98 -57
  38. package/src/LoginPage.tsx +0 -314
  39. package/src/LogoutPage.tsx +0 -43
  40. package/src/components/Forgot.tsx +0 -247
  41. package/src/components/Login.tsx +0 -304
  42. package/src/components/LoginSetPassword.tsx +0 -296
  43. package/src/components/Register.tsx +0 -371
  44. package/src/components/SelectChurchModal.tsx +0 -88
  45. package/src/components/SelectChurchRegister.tsx +0 -88
  46. package/src/components/SelectChurchSearch.tsx +0 -85
  47. package/src/components/SelectableChurch.tsx +0 -114
  48. package/src/helpers/AnalyticsHelper.ts +0 -44
  49. package/src/helpers/Locale.ts +0 -248
  50. package/src/helpers/index.ts +0 -2
  51. package/src/index.ts +0 -11
  52. package/tsconfig.json +0 -30
@@ -1,88 +0,0 @@
1
- "use client";
2
-
3
- import React from "react";
4
- import { ChurchInterface, LoginUserChurchInterface } from "@churchapps/helpers";
5
- import { SelectChurchSearch } from "./SelectChurchSearch";
6
- import { SelectableChurch } from "./SelectableChurch";
7
- import { ErrorMessages } from "@churchapps/apphelper"
8
- import { Dialog, DialogContent, DialogTitle, IconButton, Tooltip, Box } from "@mui/material";
9
- import { Logout } from "@mui/icons-material";
10
- import { Locale } from "../helpers";
11
-
12
- interface Props {
13
- appName: string,
14
- show: boolean,
15
- userChurches?: LoginUserChurchInterface[],
16
- selectChurch: (churchId: string) => void,
17
- registeredChurchCallback?: (church: ChurchInterface) => void,
18
- errors?: string[],
19
- handleRedirect?: (url: string) => void
20
- }
21
-
22
- export const SelectChurchModal: React.FC<Props> = (props) => {
23
- const [showSearch, setShowSearch] = React.useState(false);
24
-
25
- const handleClose = () => {
26
- window.location.reload();
27
- }
28
-
29
- const getContents = () => {
30
- if (showSearch || props.userChurches?.length === 0) return <SelectChurchSearch selectChurch={props.selectChurch} registeredChurchCallback={props.registeredChurchCallback} appName={props.appName} />
31
- else return (<>
32
- {props.userChurches?.map(uc => (<SelectableChurch church={uc.church} selectChurch={props.selectChurch} key={uc.church.id} />))}
33
- <Box sx={{ textAlign: "center", mt: 3 }}>
34
- <button
35
- type="button"
36
- style={{
37
- display: "inline-block",
38
- background: "none",
39
- border: "none",
40
- color: "#3b82f6",
41
- cursor: "pointer",
42
- textDecoration: "none",
43
- fontSize: "1rem",
44
- transition: "all 0.2s ease"
45
- }}
46
- onClick={(e) => { e.preventDefault(); setShowSearch(true); }}
47
- onMouseEnter={(e) => e.currentTarget.style.textDecoration = "underline"}
48
- onMouseLeave={(e) => e.currentTarget.style.textDecoration = "none"}
49
- >
50
- {Locale.label("selectChurch.another")}
51
- </button>
52
- </Box>
53
- </>);
54
- }
55
-
56
- return (
57
- <Dialog
58
- open={props.show}
59
- onClose={handleClose}
60
- aria-labelledby="select-church-title"
61
- aria-describedby="select-church-content"
62
- >
63
- <DialogTitle id="select-church-title" sx={{ fontSize: "1.5rem", fontWeight: 600 }}>
64
- {Locale.label("selectChurch.selectChurch")}
65
- </DialogTitle>
66
- <Tooltip title="Logout" arrow>
67
- <IconButton
68
- sx={{ position: "absolute", right: 8, top: 8 }}
69
- color="error"
70
- aria-label="Logout"
71
- onClick={() => {
72
- // Use handleRedirect function if available, otherwise fallback to window.location
73
- if (props.handleRedirect) {
74
- props.handleRedirect("/logout");
75
- } else {
76
- window.location.href = "/logout";
77
- }
78
- }}>
79
- <Logout />
80
- </IconButton>
81
- </Tooltip>
82
- <DialogContent id="select-church-content" sx={{ width: 700, maxWidth: "100%", px: 2, py: 2 }}>
83
- <ErrorMessages errors={props.errors} />
84
- {getContents()}
85
- </DialogContent>
86
- </Dialog>
87
- );
88
- };
@@ -1,88 +0,0 @@
1
- "use client";
2
-
3
- import React from "react";
4
- import { ApiHelper } from "@churchapps/helpers";
5
- import { Locale } from "../helpers";
6
- import { ChurchInterface, RegisterChurchRequestInterface } from "@churchapps/helpers";
7
- import { ErrorMessages, InputBox } from "@churchapps/apphelper"
8
- import { Grid, TextField } from "@mui/material";
9
-
10
- interface Props {
11
- initialChurchName: string,
12
- registeredChurchCallback?: (church: ChurchInterface) => void,
13
- selectChurch: (churchId: string) => void,
14
- appName: string
15
- }
16
-
17
- export const SelectChurchRegister: React.FC<Props> = (props) => {
18
- const suggestSubDomain = (name: string) => {
19
- let result = name.toLowerCase().replaceAll("christian", "").replaceAll("church", "").replaceAll(" ", "");
20
- return result;
21
- }
22
-
23
- const [church, setChurch] = React.useState<RegisterChurchRequestInterface>({ name: props.initialChurchName, appName: props.appName, subDomain: suggestSubDomain(props.initialChurchName) });
24
- const [errors, setErrors] = React.useState([]);
25
- const [isSubmitting, setIsSubmitting] = React.useState(false);
26
-
27
- const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
28
- const c = { ...church }
29
- switch (e.target.name) {
30
- case "churchName": c.name = e.target.value; break;
31
- case "subDomain": c.subDomain = e.target.value; break;
32
- case "address1": c.address1 = e.target.value; break;
33
- case "address2": c.address2 = e.target.value; break;
34
- case "city": c.city = e.target.value; break;
35
- case "state": c.state = e.target.value; break;
36
- case "zip": c.zip = e.target.value; break;
37
- case "country": c.country = e.target.value; break;
38
- }
39
- setChurch(c);
40
- }
41
-
42
- const validate = () => {
43
- let errors = [];
44
- if (!church.name?.trim()) errors.push(Locale.label("selectChurch.validate.name"));
45
- if (!church.address1?.trim()) errors.push(Locale.label("selectChurch.validate.address"));
46
- if (!church.city?.trim()) errors.push(Locale.label("selectChurch.validate.city"));
47
- if (!church.state?.trim()) errors.push(Locale.label("selectChurch.validate.state"));
48
- if (!church.zip?.trim()) errors.push(Locale.label("selectChurch.validate.zip"));
49
- if (!church.country?.trim()) errors.push(Locale.label("selectChurch.validate.country"));
50
- setErrors(errors);
51
- return errors.length === 0;
52
- }
53
-
54
- const handleSave = () => {
55
- if (validate()) {
56
- setIsSubmitting(true);
57
- const c = { ...church };
58
- if (!c.subDomain) c.subDomain = suggestSubDomain(c.name);
59
- ApiHelper.post("/churches/add", church, "MembershipApi").then(async (resp: any) => {
60
- setIsSubmitting(false);
61
- if (resp.errors !== undefined) setErrors(resp.errors);
62
- else {
63
- if (props.registeredChurchCallback) props.registeredChurchCallback(resp);
64
- props.selectChurch(resp.id);
65
- }
66
- });
67
- }
68
- }
69
-
70
- return (
71
- <InputBox id="churchBox" saveFunction={handleSave} headerText={Locale.label("selectChurch.register")} headerIcon="church" isSubmitting={isSubmitting}>
72
- <ErrorMessages errors={errors} />
73
- <TextField required fullWidth name="churchName" label={Locale.label("selectChurch.name")} value={church.name} onChange={handleChange} />
74
-
75
- <TextField required fullWidth name="address1" label={Locale.label("selectChurch.address1")} value={church.address1} onChange={handleChange} />
76
- <Grid container spacing={3}>
77
- <Grid size={6}><TextField fullWidth name="address2" label={Locale.label("selectChurch.address2")} value={church.address2} onChange={handleChange} /></Grid>
78
- <Grid size={6}><TextField required fullWidth name="city" label={Locale.label("selectChurch.city")} value={church.city} onChange={handleChange} /></Grid>
79
- </Grid>
80
- <Grid container spacing={3}>
81
- <Grid size={6}><TextField required fullWidth name="state" label={Locale.label("selectChurch.state")} value={church.state} onChange={handleChange} /></Grid>
82
- <Grid size={6}><TextField required fullWidth name="zip" label={Locale.label("selectChurch.zip")} value={church.zip} onChange={handleChange} /></Grid>
83
- </Grid>
84
- <TextField required fullWidth name="country" label={Locale.label("selectChurch.country")} value={church.country} onChange={handleChange} />
85
- </InputBox>
86
- );
87
- };
88
-
@@ -1,85 +0,0 @@
1
- "use client";
2
-
3
- import { Button, TextField } from "@mui/material";
4
- import React from "react";
5
- import { ApiHelper } from "@churchapps/helpers";
6
- import { Locale } from "../helpers";
7
- import { ChurchInterface } from "@churchapps/helpers";
8
- import { SelectableChurch } from "./SelectableChurch";
9
- import { SelectChurchRegister } from "./SelectChurchRegister";
10
-
11
- interface Props {
12
- selectChurch: (churchId: string) => void,
13
- registeredChurchCallback?: (church: ChurchInterface) => void,
14
- appName: string
15
- }
16
-
17
- export const SelectChurchSearch: React.FC<Props> = (props) => {
18
- const [searchText, setSearchText] = React.useState("");
19
- const [churches, setChurches] = React.useState<ChurchInterface[]>(null);
20
- const [showRegister, setShowRegister] = React.useState(false);
21
-
22
- const handleSubmit = (e: React.MouseEvent) => {
23
- if (e !== null) e.preventDefault();
24
- let term = searchText.trim();
25
- ApiHelper.post("/churches/search", { name: term }, "MembershipApi").then((data: any) => setChurches(data));
26
- }
27
-
28
- const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => setSearchText(e.currentTarget.value);
29
-
30
- const handleKeyDown = (e: React.KeyboardEvent<any>) => { if (e.key === "Enter") { e.preventDefault(); handleSubmit(null); } }
31
-
32
- const handleRegisterClick = (e: React.MouseEvent) => {
33
- e.preventDefault();
34
- if (window.confirm(Locale.label("selectChurch.confirmRegister"))) {
35
- setShowRegister(true);
36
- }
37
- }
38
-
39
- const getRegisterLink = () => (
40
- <div>
41
- <button
42
- type="button"
43
- style={{
44
- display: "block",
45
- textAlign: "center",
46
- background: "none",
47
- border: "none",
48
- color: "#3b82f6",
49
- cursor: "pointer",
50
- textDecoration: "underline",
51
- width: "100%"
52
- }}
53
- onClick={handleRegisterClick}
54
- >
55
- {Locale.label("selectChurch.register")}
56
- </button>
57
- </div>
58
- )
59
-
60
- const getChurches = () => {
61
- const result: React.ReactElement[] = [];
62
- churches.forEach(church => {
63
- result.push(<SelectableChurch church={church} selectChurch={props.selectChurch} />);
64
- });
65
- result.push(getRegisterLink());
66
- return result;
67
- }
68
-
69
- const getResults = () => {
70
- if (churches === null) return;
71
- else if (churches.length === 0) return <><p>{Locale.label("selectChurch.noMatches")}</p>{getRegisterLink()}</>
72
- else return getChurches();
73
- }
74
-
75
- if (showRegister) return (<SelectChurchRegister selectChurch={props.selectChurch} registeredChurchCallback={props.registeredChurchCallback} appName={props.appName} initialChurchName={searchText} />)
76
- else return (
77
- <>
78
- <TextField fullWidth name="searchText" label="Name" value={searchText} onChange={handleChange} onKeyDown={handleKeyDown}
79
- InputProps={{ endAdornment: <Button variant="contained" id="searchButton" data-testid="search-button" onClick={handleSubmit}>{Locale.label("common.search")}</Button> }}
80
- />
81
- {getResults()}
82
- </>
83
-
84
- );
85
- };
@@ -1,114 +0,0 @@
1
- "use client";
2
-
3
- import { Grid, Paper, Box, Typography } from "@mui/material";
4
- import React from "react";
5
- import { ArrayHelper } from "@churchapps/helpers";
6
- import { ChurchInterface, GenericSettingInterface } from "@churchapps/helpers";
7
- import { LocationOn, Church } from "@mui/icons-material";
8
-
9
- interface Props {
10
- selectChurch: (churchId: string) => void,
11
- church: ChurchInterface
12
- }
13
-
14
- export const SelectableChurch: React.FC<Props> = (props) => {
15
-
16
- let logo: string | null = null;
17
- if (props.church.settings) {
18
- let l: GenericSettingInterface = ArrayHelper.getOne(props.church.settings, "keyName", "logoLight");
19
- if (l?.value) logo = l.value;
20
- }
21
- return (
22
- <Paper
23
- elevation={0}
24
- sx={{
25
- p: 1,
26
- mb: 0.75,
27
- cursor: "pointer",
28
- transition: "all 0.2s ease",
29
- border: "1px solid transparent",
30
- borderRadius: 2,
31
- "&:hover": {
32
- borderColor: "primary.main",
33
- boxShadow: 2,
34
- transform: "translateY(-2px)"
35
- }
36
- }}
37
- onClick={() => props.selectChurch(props.church.id)}
38
- >
39
- <Grid container spacing={2} alignItems="center">
40
- <Grid size={{ xs: 12, sm: 5, md: 5 }}>
41
- <Box
42
- sx={{
43
- display: "flex",
44
- justifyContent: "center",
45
- alignItems: "center",
46
- height: { xs: 60, sm: 80 },
47
- p: 0.5
48
- }}
49
- >
50
- {logo ? (
51
- <img
52
- src={logo}
53
- alt={`${props.church.name} logo`}
54
- style={{
55
- width: "100%",
56
- height: "100%",
57
- objectFit: "contain"
58
- }}
59
- />
60
- ) : (
61
- <Box
62
- sx={{
63
- display: "flex",
64
- alignItems: "center",
65
- justifyContent: "center",
66
- width: "100%",
67
- height: "100%",
68
- backgroundColor: "grey.100",
69
- borderRadius: 2
70
- }}
71
- >
72
- <Church sx={{ fontSize: { xs: 40, sm: 50 }, color: "grey.400" }} />
73
- </Box>
74
- )}
75
- </Box>
76
- </Grid>
77
- <Grid size={{ xs: 12, sm: 7, md: 7 }}>
78
- <Box>
79
- <Typography
80
- variant="h6"
81
- component="h3"
82
- sx={{
83
- color: "primary.main",
84
- fontWeight: 600,
85
- mb: 0.5
86
- }}
87
- >
88
- {props.church.name}
89
- </Typography>
90
- {(props.church.address1 || props.church.city || props.church.state) && (
91
- <Box sx={{ display: "flex", alignItems: "flex-start", color: "text.secondary" }}>
92
- <LocationOn sx={{ fontSize: 18, mr: 0.5, mt: 0.3 }} />
93
- <Box>
94
- {props.church.address1 && (
95
- <Typography variant="body2">
96
- {props.church.address1}
97
- </Typography>
98
- )}
99
- {(props.church.city || props.church.state) && (
100
- <Typography variant="body2">
101
- {props.church.city && props.church.city}
102
- {props.church.city && props.church.state && ", "}
103
- {props.church.state}
104
- </Typography>
105
- )}
106
- </Box>
107
- </Box>
108
- )}
109
- </Box>
110
- </Grid>
111
- </Grid>
112
- </Paper>
113
- );
114
- };
@@ -1,44 +0,0 @@
1
- import ReactGA from "react-ga4";
2
- import { CommonEnvironmentHelper, UserHelper } from "@churchapps/helpers";
3
-
4
- export class AnalyticsHelper {
5
-
6
- static init = () => {
7
- if (CommonEnvironmentHelper.GoogleAnalyticsTag !== "" && typeof(window)!=="undefined") {
8
- try {
9
- ReactGA.initialize([{trackingId: CommonEnvironmentHelper.GoogleAnalyticsTag}]);
10
- AnalyticsHelper.logPageView();
11
- } catch (error) {
12
- console.warn('Analytics initialization failed:', error);
13
- }
14
- }
15
- }
16
-
17
- static logPageView = () => {
18
- if (CommonEnvironmentHelper.GoogleAnalyticsTag !== "" && typeof(window)!=="undefined") {
19
- try {
20
- this.setChurchKey();
21
- ReactGA.send({ hitType: "pageview", page: window.location.pathname + window.location.search });
22
- } catch (error) {
23
- console.warn('Analytics page view logging failed:', error);
24
- }
25
- }
26
- }
27
-
28
- static logEvent = (category: string, action: string, label?:string) => {
29
- if (CommonEnvironmentHelper.GoogleAnalyticsTag !== "" && typeof(window)!=="undefined") {
30
- try {
31
- this.setChurchKey();
32
- ReactGA.event({ category, action, label });
33
- } catch (error) {
34
- console.warn('Analytics event logging failed:', error);
35
- }
36
- }
37
- }
38
-
39
- private static setChurchKey = () => {
40
- const churchKey = UserHelper?.currentUserChurch?.church?.subDomain;
41
- if (churchKey) ReactGA.set({church_key: churchKey });
42
- }
43
-
44
- }
@@ -1,248 +0,0 @@
1
- import i18n from "i18next";
2
- import { initReactI18next } from "react-i18next/initReactI18next";
3
- import LanguageDetector from "i18next-browser-languagedetector";
4
- import Backend from "i18next-chained-backend";
5
-
6
- interface TranslationResources {
7
- [key: string]: {
8
- translation: Record<string, unknown>;
9
- };
10
- }
11
-
12
- interface ExtraLanguageCodes {
13
- [key: string]: string[];
14
- }
15
-
16
- export class Locale {
17
- private static readonly supportedLanguages: string[] = [
18
- "de",
19
- "en",
20
- "es",
21
- "fr",
22
- "hi",
23
- "it",
24
- "ko",
25
- "no",
26
- "pt",
27
- "ru",
28
- "tl",
29
- "zh",
30
- ];
31
- private static readonly extraCodes: ExtraLanguageCodes = { no: ["nb", "nn"] };
32
-
33
- // Hard-coded English fallbacks for when locale files are not available
34
- private static readonly englishFallbacks: Record<string, any> = {
35
- "common": {
36
- "pleaseWait": "Please wait...",
37
- "search": "Search",
38
- "cancel": "Cancel",
39
- "save": "Save",
40
- "delete": "Delete",
41
- "edit": "Edit",
42
- "add": "Add",
43
- "close": "Close",
44
- "date": "Date",
45
- "error": "Error",
46
- "submit": "Submit",
47
- "update": "Update"
48
- },
49
- "login": {
50
- "createAccount": "Create an Account",
51
- "email": "Email",
52
- "expiredLink": "The current link is expired.",
53
- "forgot": "Forgot Password",
54
- "goLogin": "Go to Login",
55
- "login": "Login",
56
- "password": "Password",
57
- "register": "Register",
58
- "registerThankYou": "Thank you for registering! Please check your email to verify your account.",
59
- "requestLink": "Request a new reset link",
60
- "reset": "Reset",
61
- "resetInstructions": "Enter your email address to request a password reset.",
62
- "resetPassword": "Reset Password",
63
- "resetSent": "Password reset email sent!",
64
- "setPassword": "Set Password",
65
- "signIn": "Sign In",
66
- "signInTitle": "Please Sign In",
67
- "verifyPassword": "Verify Password",
68
- "welcomeName": "Welcome back, <b>{}</b>! Please wait while we load your data.",
69
- "welcomeBack": "Welcome back",
70
- "validate": {
71
- "email": "Please enter a valid email address.",
72
- "firstName": "Please enter your first name.",
73
- "invalid": "Invalid login. Please check your email or password.",
74
- "lastName": "Please enter your last name.",
75
- "password": "Please enter a password.",
76
- "passwordLength": "Password must be at least 8 characters long.",
77
- "passwordMatch": "Passwords do not match.",
78
- "selectingChurch": "Error in selecting church. Please verify and try again"
79
- }
80
- },
81
- "selectChurch": {
82
- "address1": "Address Line 1",
83
- "address2": "Address Line 2",
84
- "another": "Choose another church",
85
- "city": "City",
86
- "confirmRegister": "Are you sure you wish to register a new church?",
87
- "country": "Country",
88
- "name": "Church Name",
89
- "noMatches": "No matches found.",
90
- "register": "Register a New Church",
91
- "selectChurch": "Select a Church",
92
- "state": "State / Province",
93
- "zip": "Zip / Postal Code",
94
- "validate": {
95
- "address": "Address cannot be blank.",
96
- "city": "City cannot be blank.",
97
- "country": "Country cannot be blank.",
98
- "name": "Church name cannot be blank.",
99
- "state": "State/Province cannot be blank.",
100
- "zip": "Zip/Postal code cannot be blank."
101
- }
102
- }
103
- };
104
-
105
- static init = async (backends: string[]): Promise<void> => {
106
- const resources: TranslationResources = {};
107
- let langs = ["en"];
108
-
109
- if (typeof navigator !== "undefined") {
110
- const browserLang = navigator.language.split("-")[0];
111
- const mappedLang
112
- = Object.keys(this.extraCodes).find((code) =>
113
- this.extraCodes[code].includes(browserLang),
114
- ) || browserLang;
115
- const notSupported = this.supportedLanguages.indexOf(mappedLang) === -1;
116
- langs = mappedLang === "en" || notSupported ? ["en"] : ["en", mappedLang];
117
- }
118
-
119
- // Load translations for each language
120
- for (const lang of langs) {
121
- resources[lang] = { translation: {} };
122
- try {
123
- for (const backend of backends) {
124
- const url = backend.replace("{{lng}}", lang);
125
- try {
126
- const response = await fetch(url);
127
- if (response.ok) {
128
- const data = await response.json();
129
- resources[lang].translation = this.deepMerge(
130
- resources[lang].translation,
131
- data,
132
- );
133
- }
134
- } catch (error) {
135
- console.warn(`Failed to load translations from ${url}:`, error);
136
- }
137
- }
138
- } catch (error) {
139
- console.warn(`Failed to load translations for language ${lang}:`, error);
140
- }
141
- }
142
-
143
- // Initialize i18n
144
- try {
145
- await i18n
146
- .use(Backend)
147
- .use(LanguageDetector)
148
- .use(initReactI18next)
149
- .init({
150
- resources,
151
- fallbackLng: "en",
152
- debug: false,
153
- interpolation: {
154
- escapeValue: false,
155
- },
156
- detection: {
157
- order: ["navigator"],
158
- caches: ["localStorage"],
159
- },
160
- });
161
- } catch (error) {
162
- console.warn("Failed to initialize i18n:", error);
163
- }
164
- };
165
-
166
- private static deepMerge(
167
- target: Record<string, unknown>,
168
- source: Record<string, unknown>,
169
- ): Record<string, unknown> {
170
- for (const key in source) {
171
- if (this.isObject(source[key])) {
172
- if (!target[key]) Object.assign(target, { [key]: {} });
173
- this.deepMerge(
174
- target[key] as Record<string, unknown>,
175
- source[key] as Record<string, unknown>,
176
- );
177
- } else Object.assign(target, { [key]: source[key] });
178
- }
179
- return target;
180
- }
181
-
182
- private static isObject(obj: unknown): boolean {
183
- return obj !== null && typeof obj === "object" && !Array.isArray(obj);
184
- }
185
-
186
- // Helper method to get value from nested object using dot notation
187
- private static getNestedValue(obj: Record<string, any>, path: string): any {
188
- return path.split('.').reduce((current, key) => {
189
- return current && current[key] !== undefined ? current[key] : undefined;
190
- }, obj);
191
- }
192
-
193
- // New helper method that uses i18n with hard-coded English fallback
194
- static t(key: string, options?: Record<string, unknown>): string {
195
- try {
196
- // Check if i18n is initialized and has the key
197
- if (i18n && i18n.isInitialized && i18n.exists(key)) {
198
- const translation = i18n.t(key, options);
199
- // If translation is not the same as the key, return it
200
- if (translation !== key) {
201
- return translation;
202
- }
203
- }
204
- } catch (error) {
205
- // If i18n fails, fall through to hard-coded fallback
206
- console.warn(`i18n translation failed for key "${key}":`, error);
207
- }
208
-
209
- // Fallback to hard-coded English translations
210
- const fallbackValue = this.getNestedValue(this.englishFallbacks, key);
211
- if (fallbackValue !== undefined) {
212
- // Handle simple string interpolation for options
213
- if (typeof fallbackValue === 'string' && options) {
214
- let result = fallbackValue;
215
- Object.keys(options).forEach(optionKey => {
216
- const placeholder = `{{${optionKey}}}`;
217
- if (result.includes(placeholder)) {
218
- result = result.replace(new RegExp(placeholder, 'g'), String(options[optionKey]));
219
- }
220
- // Also handle {} placeholder for backward compatibility
221
- if (result.includes('{}')) {
222
- result = result.replace('{}', String(options[optionKey]));
223
- }
224
- });
225
- return result;
226
- }
227
- return String(fallbackValue);
228
- }
229
-
230
- // If no fallback found, return the key itself
231
- return key;
232
- }
233
-
234
- // Keep the old method for backward compatibility
235
- static label(key: string): string {
236
- return this.t(key);
237
- }
238
-
239
- // Helper method to check if i18n is initialized
240
- static isInitialized(): boolean {
241
- return i18n && i18n.isInitialized;
242
- }
243
-
244
- // Method to set up basic fallback-only mode (no i18n)
245
- static initFallbackMode(): void {
246
- console.info("Locale: Running in fallback mode with English labels only");
247
- }
248
- }
@@ -1,2 +0,0 @@
1
- export { Locale } from "./Locale";
2
- export { AnalyticsHelper } from "./AnalyticsHelper";
package/src/index.ts DELETED
@@ -1,11 +0,0 @@
1
- export { LoginPage } from "./LoginPage";
2
- export { LogoutPage } from "./LogoutPage";
3
- export { Register } from "./components/Register";
4
- export { Login } from "./components/Login";
5
- export { Forgot } from "./components/Forgot";
6
- export { LoginSetPassword } from "./components/LoginSetPassword";
7
- export { SelectChurchModal } from "./components/SelectChurchModal";
8
- export { SelectChurchRegister } from "./components/SelectChurchRegister";
9
- export { SelectChurchSearch } from "./components/SelectChurchSearch";
10
- export { SelectableChurch } from "./components/SelectableChurch";
11
- export { ErrorMessages } from "@churchapps/apphelper";