| @@ -1,4 +1,4 @@ | |||
| import { APICommonResponse, ApiId, HttpMethod, request } from "."; | |||
| import { APICommonResponse, ApiId, HttpMethod, makeParam, request } from "."; | |||
| import { getUrl } from "./url"; | |||
| export type Me = { | |||
| @@ -41,3 +41,34 @@ export const logout = async () => { | |||
| }); | |||
| return res; | |||
| }; | |||
| // -------パスワード設定開始--------------- | |||
| export type StartPasswordSettingRequest = { | |||
| email: string; | |||
| }; | |||
| export const startPasswordSetting = async ( | |||
| param: StartPasswordSettingRequest | |||
| ) => { | |||
| const res = await request({ | |||
| url: getUrl(ApiId.PASSWORD_SETTING_START), | |||
| method: HttpMethod.POST, | |||
| data: makeParam(param), | |||
| }); | |||
| return res; | |||
| }; | |||
| // -------パスワード設定認証--------------- | |||
| export type VerifyPasswordSettingRequest = { | |||
| token: string; | |||
| password: string; | |||
| }; | |||
| export const verifyPasswordSetting = async ( | |||
| param: VerifyPasswordSettingRequest | |||
| ) => { | |||
| const res = await request({ | |||
| url: getUrl(ApiId.PASSWORD_SETTING_VERIFY), | |||
| method: HttpMethod.POST, | |||
| data: makeParam(param), | |||
| }); | |||
| return res; | |||
| }; | |||
| @@ -14,6 +14,9 @@ export const ApiId = { | |||
| LOGIN: id++, | |||
| LOGOUT: id++, | |||
| PASSWORD_SETTING_START: id++, | |||
| PASSWORD_SETTING_VERIFY: id++, | |||
| SEASON_TICKET_CONTRACTS: id++, | |||
| PAYMENT_PLANS: id++, | |||
| @@ -6,6 +6,8 @@ const urls = { | |||
| [A.ME]: "me", | |||
| [A.LOGIN]: "login", | |||
| [A.LOGOUT]: "logout", | |||
| [A.PASSWORD_SETTING_START]: "password/setting/start", | |||
| [A.PASSWORD_SETTING_VERIFY]: "password/setting/verify", | |||
| [A.SEASON_TICKET_CONTRACTS]: "season-ticket-contracts", | |||
| [A.PAYMENT_PLANS]: "season-ticket-contract/payment-plans", | |||
| [A.STICKER_RE_ORDER]: "season-ticket-contract/sticker-re-order", | |||
| @@ -73,7 +73,14 @@ export default function Login() { | |||
| <LoadingButton loading={sending} type="submit" variant="contained"> | |||
| ログイン | |||
| </LoadingButton> | |||
| <Button variant="outlined">パスワードを忘れた</Button> | |||
| <Button | |||
| variant="outlined" | |||
| onClick={() => { | |||
| navigateWhenChanged(getPath(PageID.USER_SETTING_PASSWORD_START)); | |||
| }} | |||
| > | |||
| パスワードを忘れた/設定したい | |||
| </Button> | |||
| </Stack> | |||
| </Box> | |||
| </FormProvider> | |||
| @@ -0,0 +1,120 @@ | |||
| import { yupResolver } from "@hookform/resolvers/yup"; | |||
| import { Box, Button, Stack, Typography } from "@mui/material"; | |||
| import { HasChildren } from "@types"; | |||
| import { startPasswordSetting } from "api/auth"; | |||
| import RequireChip from "components/chip/RequireChip"; | |||
| import { FormProvider, RHFTextField } from "components/hook-form"; | |||
| import StackRow from "components/stack/StackRow"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| import useNavigateCustom from "hooks/useNavigateCustom"; | |||
| import { PageID } from "pages"; | |||
| import { useState } from "react"; | |||
| import { useForm } from "react-hook-form"; | |||
| import { getPath } from "routes/path"; | |||
| import { object as YupObject, string as YupString } from "yup"; | |||
| type AreaBoxProps = { | |||
| label: string; | |||
| require?: boolean; | |||
| } & HasChildren; | |||
| function AreaBox({ label, children, require }: AreaBoxProps) { | |||
| return ( | |||
| <Box> | |||
| <StackRow> | |||
| <Typography variant="subtitle1">〇{label}</Typography> | |||
| <RequireChip require={require ?? false} /> | |||
| </StackRow> | |||
| {children} | |||
| </Box> | |||
| ); | |||
| } | |||
| type FormProps = { | |||
| email: string; | |||
| retype_email: string; | |||
| }; | |||
| export default function PasswordSettingStart() { | |||
| const { navigateWhenChanged } = useNavigateCustom(); | |||
| const [done, setDone] = useState<boolean | null>(null); | |||
| const form = useForm<FormProps>({ | |||
| defaultValues: { | |||
| email: "", | |||
| retype_email: "", | |||
| }, | |||
| resolver: yupResolver( | |||
| YupObject().shape({ | |||
| email: YupString().required("必須項目です"), | |||
| retype_email: YupString() | |||
| .required("必須項目です") | |||
| .test("retype", "一致しません", function (value) { | |||
| return value === this.parent.email; | |||
| }), | |||
| }) | |||
| ), | |||
| }); | |||
| const { callAPI: callStartPasswordSetting } = useAPICall({ | |||
| apiMethod: startPasswordSetting, | |||
| backDrop: true, | |||
| form, | |||
| onSuccess: () => { | |||
| setDone(true); | |||
| }, | |||
| onFailed: () => {}, | |||
| }); | |||
| const handleSubmit = (data: FormProps) => { | |||
| callStartPasswordSetting({ ...data }); | |||
| }; | |||
| const toLogin = () => { | |||
| navigateWhenChanged(getPath(PageID.LOGIN)); | |||
| }; | |||
| if (done === true) { | |||
| return ( | |||
| <Box sx={{ p: 3, pt: 5, mx: "auto", maxWidth: 500 }} textAlign="center"> | |||
| <Stack spacing={2}> | |||
| <Box> | |||
| <Typography> | |||
| メールアドレスに設定手続きのご案内を送信しました。 | |||
| </Typography> | |||
| <Typography>ご確認ください。</Typography> | |||
| </Box> | |||
| <Box> | |||
| <Button onClick={toLogin}>ログインへ</Button> | |||
| </Box> | |||
| </Stack> | |||
| </Box> | |||
| ); | |||
| } | |||
| return ( | |||
| <Box sx={{ p: 3, pt: 5, mx: "auto", maxWidth: 500 }} textAlign="center"> | |||
| <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}> | |||
| <Stack spacing={2}> | |||
| <Box> | |||
| <Button onClick={toLogin}>戻る</Button> | |||
| </Box> | |||
| <Box> | |||
| <Typography> | |||
| 登録済みのメールアドレスへ変更用のURLを送信します。 | |||
| </Typography> | |||
| </Box> | |||
| <AreaBox label="Email" require={true}> | |||
| <RHFTextField name="email" /> | |||
| </AreaBox> | |||
| <AreaBox label="Email (確認用)" require={true}> | |||
| <RHFTextField name="retype_email" /> | |||
| </AreaBox> | |||
| <Box> | |||
| <Button type="submit">送信</Button> | |||
| </Box> | |||
| </Stack> | |||
| </FormProvider> | |||
| </Box> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,117 @@ | |||
| import { yupResolver } from "@hookform/resolvers/yup"; | |||
| import { Box, Button, Stack, Typography } from "@mui/material"; | |||
| import { HasChildren } from "@types"; | |||
| import { verifyPasswordSetting } from "api/auth"; | |||
| import RequireChip from "components/chip/RequireChip"; | |||
| import { FormProvider, RHFTextField } from "components/hook-form"; | |||
| import StackRow from "components/stack/StackRow"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| import useNavigateCustom from "hooks/useNavigateCustom"; | |||
| import { PageID } from "pages"; | |||
| import { useState } from "react"; | |||
| import { useForm } from "react-hook-form"; | |||
| import { useParams } from "react-router-dom"; | |||
| import { getPath } from "routes/path"; | |||
| import { object as YupObject, string as YupString } from "yup"; | |||
| type AreaBoxProps = { | |||
| label: string; | |||
| require?: boolean; | |||
| } & HasChildren; | |||
| function AreaBox({ label, children, require }: AreaBoxProps) { | |||
| return ( | |||
| <Box> | |||
| <StackRow> | |||
| <Typography variant="subtitle1">〇{label}</Typography> | |||
| <RequireChip require={require ?? false} /> | |||
| </StackRow> | |||
| {children} | |||
| </Box> | |||
| ); | |||
| } | |||
| type FormProps = { | |||
| password: string; | |||
| retype_password: string; | |||
| }; | |||
| export default function PasswordSettingVerify() { | |||
| const { navigateWhenChanged } = useNavigateCustom(); | |||
| const { token: paramToken } = useParams(); | |||
| const [done, setDone] = useState<boolean | null>(null); | |||
| const form = useForm<FormProps>({ | |||
| defaultValues: { | |||
| password: "", | |||
| retype_password: "", | |||
| }, | |||
| resolver: yupResolver( | |||
| YupObject().shape({ | |||
| password: YupString().required("必須項目です"), | |||
| retype_password: YupString() | |||
| .required("必須項目です") | |||
| .test("retype", "一致しません", function (value) { | |||
| return value === this.parent.password; | |||
| }), | |||
| }) | |||
| ), | |||
| }); | |||
| const { callAPI: callVerifyPasswordSetting } = useAPICall({ | |||
| apiMethod: verifyPasswordSetting, | |||
| backDrop: true, | |||
| form, | |||
| onSuccess: () => { | |||
| setDone(true); | |||
| }, | |||
| onFailed: () => {}, | |||
| }); | |||
| const handleSubmit = (data: FormProps) => { | |||
| if (!paramToken) return; | |||
| callVerifyPasswordSetting({ ...data, token: paramToken }); | |||
| }; | |||
| const toLogin = () => { | |||
| navigateWhenChanged(getPath(PageID.LOGIN)); | |||
| }; | |||
| if (done === true) { | |||
| return ( | |||
| <Box sx={{ p: 3, pt: 5, mx: "auto", maxWidth: 500 }} textAlign="center"> | |||
| <Stack spacing={2}> | |||
| <Box> | |||
| <Typography>パスワードを設定しました</Typography> | |||
| </Box> | |||
| <Box> | |||
| <Button onClick={toLogin}>ログインへ</Button> | |||
| </Box> | |||
| </Stack> | |||
| </Box> | |||
| ); | |||
| } | |||
| return ( | |||
| <Box sx={{ p: 3, pt: 5, mx: "auto", maxWidth: 500 }} textAlign="center"> | |||
| <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}> | |||
| <Stack spacing={2}> | |||
| <Box> | |||
| <Typography> | |||
| 新たに設定するログインパスワードを入力してください | |||
| </Typography> | |||
| </Box> | |||
| <AreaBox label="新しいログインパスワード" require={true}> | |||
| <RHFTextField name="password" type="password" /> | |||
| </AreaBox> | |||
| <AreaBox label="新しいログインパスワード (再入力)" require={true}> | |||
| <RHFTextField name="retype_password" type="password" /> | |||
| </AreaBox> | |||
| <Box> | |||
| <Button type="submit">送信</Button> | |||
| </Box> | |||
| </Stack> | |||
| </FormProvider> | |||
| </Box> | |||
| ); | |||
| } | |||
| @@ -5,6 +5,8 @@ export const PageID = { | |||
| LOGIN: id++, | |||
| LOGOUT: id++, | |||
| USER_CHANGE_EMAIL_VERIFY: id++, | |||
| USER_SETTING_PASSWORD_START: id++, | |||
| USER_SETTING_PASSWORD_VERIFY: id++, | |||
| DASHBOARD_OVERVIEW: id++, | |||
| @@ -35,7 +35,14 @@ const AuthRoutes = (): RouteObject => ({ | |||
| path: getRoute(PageID.USER_CHANGE_EMAIL_VERIFY), | |||
| element: <ChangeEmailVerify />, | |||
| }, | |||
| {}, | |||
| { | |||
| path: getRoute(PageID.USER_SETTING_PASSWORD_START), | |||
| element: <PasswordSettingStart />, | |||
| }, | |||
| { | |||
| path: getRoute(PageID.USER_SETTING_PASSWORD_VERIFY), | |||
| element: <PasswordSettingVerify />, | |||
| }, | |||
| ], | |||
| }); | |||
| @@ -63,6 +70,12 @@ const Logout = Loadable(lazy(() => import("pages/auth/logout"))); | |||
| const ChangeEmailVerify = Loadable( | |||
| lazy(() => import("pages/auth/change-email-verify")) | |||
| ); | |||
| const PasswordSettingStart = Loadable( | |||
| lazy(() => import("pages/auth/password-setting-start")) | |||
| ); | |||
| const PasswordSettingVerify = Loadable( | |||
| lazy(() => import("pages/auth/password-setting-verify")) | |||
| ); | |||
| // その他 --------------------------------- | |||
| @@ -66,6 +66,9 @@ const PATHS = { | |||
| [makePathKey(PageID.LOGIN)]: "/login", | |||
| [makePathKey(PageID.LOGOUT)]: "/logout", | |||
| [makePathKey(PageID.USER_CHANGE_EMAIL_VERIFY)]: "/change/email/verify/:token", | |||
| [makePathKey(PageID.USER_SETTING_PASSWORD_START)]: "/setting/password/start", | |||
| [makePathKey(PageID.USER_SETTING_PASSWORD_VERIFY)]: | |||
| "/setting/password/verify/:token", | |||
| // ダッシュボード---------------- | |||
| ...PATHS_DASHBOARD, | |||