fix(i18n): localize discovery and accounts
This commit is contained in:
@@ -4,6 +4,7 @@ import { Box, Button, Checkbox, Chip, Divider, FormControlLabel, Paper, Stack, T
|
||||
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
|
||||
|
||||
import { api, getApiErrorMessage } from "../api";
|
||||
import { useI18n } from "../i18n/I18nProvider";
|
||||
import { useToast } from "../toast";
|
||||
import type { GmailStatus, ImapStatus, MicrosoftGraphStatus } from "../types";
|
||||
|
||||
@@ -13,6 +14,7 @@ import type { GmailStatus, ImapStatus, MicrosoftGraphStatus } from "../types";
|
||||
// form submitted to POST /api/imap/connect, which verifies the connection before storing it.
|
||||
export default function EmailProviderConnections() {
|
||||
const { toast } = useToast();
|
||||
const { t } = useI18n();
|
||||
|
||||
const [gmailStatus, setGmailStatus] = useState<GmailStatus | null>(null);
|
||||
const [microsoftStatus, setMicrosoftStatus] = useState<MicrosoftGraphStatus | null>(null);
|
||||
@@ -59,32 +61,32 @@ export default function EmailProviderConnections() {
|
||||
const data = event.data as { source?: string; status?: string; message?: string };
|
||||
if (data?.source === "jobtracker-gmail-oauth") {
|
||||
if (data.status === "connected") {
|
||||
toast(data.message || "Gmail connected.", "success");
|
||||
toast(data.message || t("emailConnectionsGmailConnected"), "success");
|
||||
void loadGmailStatus();
|
||||
} else {
|
||||
toast(data.message || "Gmail connection failed.", "error");
|
||||
toast(data.message || t("emailConnectionsGmailFailed"), "error");
|
||||
}
|
||||
} else if (data?.source === "jobtracker-microsoft-oauth") {
|
||||
if (data.status === "connected") {
|
||||
toast(data.message || "Outlook connected.", "success");
|
||||
toast(data.message || t("emailConnectionsOutlookConnected"), "success");
|
||||
void loadMicrosoftStatus();
|
||||
} else {
|
||||
toast(data.message || "Outlook connection failed.", "error");
|
||||
toast(data.message || t("emailConnectionsOutlookFailed"), "error");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("message", onMessage);
|
||||
return () => window.removeEventListener("message", onMessage);
|
||||
}, [loadGmailStatus, loadMicrosoftStatus, toast]);
|
||||
}, [loadGmailStatus, loadMicrosoftStatus, t, toast]);
|
||||
|
||||
const connectViaPopup = async (connectUrlPath: string, popupName: string, providerLabel: string) => {
|
||||
try {
|
||||
const res = await api.get<{ url: string }>(connectUrlPath);
|
||||
const popup = window.open(res.data.url, popupName, "width=620,height=760,resizable=yes,scrollbars=yes");
|
||||
if (!popup) toast("Your browser blocked the connect popup. Allow popups and try again.", "error");
|
||||
if (!popup) toast(t("emailConnectionsPopupBlocked"), "error");
|
||||
} catch (error) {
|
||||
toast(getApiErrorMessage(error, `Failed to start ${providerLabel} connection.`), "error");
|
||||
toast(getApiErrorMessage(error, t("emailConnectionsStartFailed", { provider: providerLabel })), "error");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -92,15 +94,15 @@ export default function EmailProviderConnections() {
|
||||
try {
|
||||
await api.delete(path);
|
||||
await reload();
|
||||
toast(`${providerLabel} disconnected.`, "success");
|
||||
toast(t("emailConnectionsDisconnected", { provider: providerLabel }), "success");
|
||||
} catch (error) {
|
||||
toast(getApiErrorMessage(error, `Failed to disconnect ${providerLabel}.`), "error");
|
||||
toast(getApiErrorMessage(error, t("emailConnectionsDisconnectFailed", { provider: providerLabel })), "error");
|
||||
}
|
||||
};
|
||||
|
||||
const connectImap = async () => {
|
||||
if (!imapForm.host.trim() || !imapForm.username.trim() || !imapForm.password) {
|
||||
toast("Host, username, and password are required.", "error");
|
||||
toast(t("emailConnectionsRequired"), "error");
|
||||
return;
|
||||
}
|
||||
setImapConnecting(true);
|
||||
@@ -108,9 +110,9 @@ export default function EmailProviderConnections() {
|
||||
await api.post("/imap/connect", imapForm);
|
||||
setImapForm((prev) => ({ ...prev, password: "" }));
|
||||
await loadImapStatus();
|
||||
toast("IMAP account connected.", "success");
|
||||
toast(t("emailConnectionsImapConnected"), "success");
|
||||
} catch (error) {
|
||||
toast(getApiErrorMessage(error, "Failed to connect IMAP account."), "error");
|
||||
toast(getApiErrorMessage(error, t("emailConnectionsImapFailed")), "error");
|
||||
} finally {
|
||||
setImapConnecting(false);
|
||||
}
|
||||
@@ -118,9 +120,9 @@ export default function EmailProviderConnections() {
|
||||
|
||||
return (
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Typography sx={{ fontWeight: 950, mb: 0.5 }}>Linked email accounts</Typography>
|
||||
<Typography sx={{ fontWeight: 950, mb: 0.5 }}>{t("emailConnectionsTitle")}</Typography>
|
||||
<Typography sx={{ color: "text.secondary", mb: 2 }}>
|
||||
Connect a mailbox so recruiter correspondence can be linked to jobs automatically.
|
||||
{t("emailConnectionsBody")}
|
||||
</Typography>
|
||||
|
||||
<Stack spacing={2}>
|
||||
@@ -146,7 +148,7 @@ export default function EmailProviderConnections() {
|
||||
|
||||
<Box>
|
||||
<ProviderRow
|
||||
label="Other (IMAP)"
|
||||
label={t("emailConnectionsOtherImap")}
|
||||
connected={Boolean(imapStatus?.connected)}
|
||||
address={imapStatus?.username ?? null}
|
||||
onDisconnect={() => void disconnect("/imap/connection", loadImapStatus, "IMAP")}
|
||||
@@ -155,28 +157,28 @@ export default function EmailProviderConnections() {
|
||||
<Box sx={{ mt: 1.5, display: "grid", gap: 1.25, gridTemplateColumns: { xs: "1fr", sm: "2fr 1fr" }, maxWidth: 520 }}>
|
||||
<TextField
|
||||
size="small"
|
||||
label="IMAP host"
|
||||
label={t("emailConnectionsImapHost")}
|
||||
placeholder="imap.example.com"
|
||||
value={imapForm.host}
|
||||
onChange={(e) => setImapForm((prev) => ({ ...prev, host: e.target.value }))}
|
||||
/>
|
||||
<TextField
|
||||
size="small"
|
||||
label="Port"
|
||||
label={t("emailConnectionsPort")}
|
||||
type="number"
|
||||
value={imapForm.port}
|
||||
onChange={(e) => setImapForm((prev) => ({ ...prev, port: Number(e.target.value) || 993 }))}
|
||||
/>
|
||||
<TextField
|
||||
size="small"
|
||||
label="Username"
|
||||
label={t("emailConnectionsUsername")}
|
||||
value={imapForm.username}
|
||||
onChange={(e) => setImapForm((prev) => ({ ...prev, username: e.target.value }))}
|
||||
sx={{ gridColumn: "1 / -1" }}
|
||||
/>
|
||||
<TextField
|
||||
size="small"
|
||||
label="Password"
|
||||
label={t("emailConnectionsPassword")}
|
||||
type="password"
|
||||
value={imapForm.password}
|
||||
onChange={(e) => setImapForm((prev) => ({ ...prev, password: e.target.value }))}
|
||||
@@ -185,7 +187,7 @@ export default function EmailProviderConnections() {
|
||||
<FormControlLabel
|
||||
sx={{ gridColumn: "1 / -1" }}
|
||||
control={<Checkbox checked={imapForm.useSsl} onChange={(e) => setImapForm((prev) => ({ ...prev, useSsl: e.target.checked }))} />}
|
||||
label="Use SSL/TLS"
|
||||
label={t("emailConnectionsUseSsl")}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
@@ -193,7 +195,7 @@ export default function EmailProviderConnections() {
|
||||
disabled={imapConnecting}
|
||||
sx={{ gridColumn: "1 / -1", justifySelf: "start" }}
|
||||
>
|
||||
{imapConnecting ? "Connecting…" : "Connect IMAP account"}
|
||||
{imapConnecting ? t("emailConnectionsConnecting") : t("emailConnectionsConnectImap")}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
@@ -216,6 +218,7 @@ function ProviderRow({
|
||||
onConnect?: () => void;
|
||||
onDisconnect: () => void;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
return (
|
||||
<Stack direction="row" alignItems="center" justifyContent="space-between" flexWrap="wrap" gap={1}>
|
||||
<Box>
|
||||
@@ -226,17 +229,17 @@ function ProviderRow({
|
||||
icon={<CheckCircleIcon fontSize="small" />}
|
||||
color="success"
|
||||
variant="outlined"
|
||||
label={address || "Connected"}
|
||||
label={address || t("emailConnectionsConnected")}
|
||||
sx={{ mt: 0.5 }}
|
||||
/>
|
||||
) : (
|
||||
<Typography variant="caption" sx={{ color: "text.secondary" }}>Not connected</Typography>
|
||||
<Typography variant="caption" sx={{ color: "text.secondary" }}>{t("emailConnectionsNotConnected")}</Typography>
|
||||
)}
|
||||
</Box>
|
||||
{connected ? (
|
||||
<Button size="small" variant="outlined" color="error" onClick={onDisconnect}>Disconnect</Button>
|
||||
<Button size="small" variant="outlined" color="error" onClick={onDisconnect}>{t("emailConnectionsDisconnect")}</Button>
|
||||
) : (
|
||||
onConnect && <Button size="small" variant="outlined" onClick={onConnect}>Connect</Button>
|
||||
onConnect && <Button size="small" variant="outlined" onClick={onConnect}>{t("emailConnectionsConnect")}</Button>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user