* feat: improve i18n support as a whole
- Remove dayjs in favor of /helpers/datetime.ts, which uses
Intl.DateTimeFormat and Date. Dayjs is not exactly i18n friendly
and has several locale related opened issues.
- Move/refactor date/time code from /helpers/utils.ts to
/helpers/datetime.ts.
- Fix Daily Review weekday not changing according to selected date.
- Localize Daily review weekday and month.
- Load i18n listed strings from /locales/{locale}.json in a dynamic way.
This makes much easier to add new locales, by just adding a properly
named json file and listing it only in /web/src/i18n.ts and
/api/user_setting.go.
- Fallback languages are now set in /web/src/i18n.ts.
- Full language codes are now preffered, but they fallback to 2-letter
codes when not available.
- The locale dropdown is now populated dynamically from the available
locales. Locale names are populated by the browser via
Intl.DisplayNames(locale).
- /web/src/i18n.ts now exports a type TLocale from availableLocales
array. This is used only by findNearestLanguageMatch(). As I was unable
to use this type in ".d.ts" files, I switched the Locale type from
/web/src/types/i18n.d.ts to string.
- Move pretty much all hardcoded text strings to i18n strings.
- Add pt-BR translation.
- Remove site.ts and move its content to a i18n string.
- Rename zh.json to zh-Hans.json to get the correct language name on
selector dropdown.
- Remove pt_BR.json and replace with pt-BR.json.
- Some minor layout spacing fixes to accommodate larger texts.
- Improve some error messages.
* Delete .yarnrc.yml
* Delete package-lock.json
* fix: 158:28 error Insert `⏎` prettier/prettier
110 lines
3.1 KiB
TypeScript
110 lines
3.1 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { toast } from "react-hot-toast";
|
|
import { useTranslation } from "react-i18next";
|
|
import { useUserStore } from "@/store/module";
|
|
import Icon from "./Icon";
|
|
import { generateDialog } from "./Dialog";
|
|
|
|
type Props = DialogProps;
|
|
|
|
const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
|
|
const { t } = useTranslation();
|
|
const userStore = useUserStore();
|
|
const [newPassword, setNewPassword] = useState("");
|
|
const [newPasswordAgain, setNewPasswordAgain] = useState("");
|
|
|
|
useEffect(() => {
|
|
// do nth
|
|
}, []);
|
|
|
|
const handleCloseBtnClick = () => {
|
|
destroy();
|
|
};
|
|
|
|
const handleNewPasswordChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const text = e.target.value as string;
|
|
setNewPassword(text);
|
|
};
|
|
|
|
const handleNewPasswordAgainChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const text = e.target.value as string;
|
|
setNewPasswordAgain(text);
|
|
};
|
|
|
|
const handleSaveBtnClick = async () => {
|
|
if (newPassword === "" || newPasswordAgain === "") {
|
|
toast.error(t("message.fill-all"));
|
|
return;
|
|
}
|
|
|
|
if (newPassword !== newPasswordAgain) {
|
|
toast.error(t("message.new-password-not-match"));
|
|
setNewPasswordAgain("");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const user = userStore.getState().user as User;
|
|
await userStore.patchUser({
|
|
id: user.id,
|
|
password: newPassword,
|
|
});
|
|
toast.success(t("message.password-changed"));
|
|
handleCloseBtnClick();
|
|
} catch (error: any) {
|
|
console.error(error);
|
|
toast.error(error.response.data.message);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<div className="dialog-header-container !w-64">
|
|
<p className="title-text">{t("setting.account-section.change-password")}</p>
|
|
<button className="btn close-btn" onClick={handleCloseBtnClick}>
|
|
<Icon.X />
|
|
</button>
|
|
</div>
|
|
<div className="dialog-content-container">
|
|
<p className="text-sm mb-1">{t("auth.new-password")}</p>
|
|
<input
|
|
type="password"
|
|
autoComplete="new-password"
|
|
className="input-text"
|
|
placeholder={t("auth.new-password")}
|
|
value={newPassword}
|
|
onChange={handleNewPasswordChanged}
|
|
/>
|
|
<p className="text-sm mb-1 mt-2">{t("auth.repeat-new-password")}</p>
|
|
<input
|
|
type="password"
|
|
autoComplete="new-password"
|
|
className="input-text"
|
|
placeholder={t("auth.repeat-new-password")}
|
|
value={newPasswordAgain}
|
|
onChange={handleNewPasswordAgainChanged}
|
|
/>
|
|
<div className="mt-4 w-full flex flex-row justify-end items-center space-x-2">
|
|
<span className="btn-text" onClick={handleCloseBtnClick}>
|
|
{t("common.cancel")}
|
|
</span>
|
|
<span className="btn-primary" onClick={handleSaveBtnClick}>
|
|
{t("common.save")}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
function showChangePasswordDialog() {
|
|
generateDialog(
|
|
{
|
|
className: "change-password-dialog",
|
|
dialogName: "change-password-dialog",
|
|
},
|
|
ChangePasswordDialog
|
|
);
|
|
}
|
|
|
|
export default showChangePasswordDialog;
|