diff options
author | poeti8 <ezzati.upt@gmail.com> | 2020-09-19 16:32:32 +0300 |
---|---|---|
committer | poeti8 <ezzati.upt@gmail.com> | 2020-09-19 16:32:32 +0300 |
commit | 9e99c2ce51ad10b2c2449e8d673ea855bb52cd11 (patch) | |
tree | 118edcc1efaa3eaddf654a746a1eecda65bf9537 /client | |
parent | f7c191fb630b00b6e243593b9209cfe2b1c215c3 (diff) |
feat: add change email functionality
Diffstat (limited to 'client')
-rw-r--r-- | client/components/AppWrapper.tsx | 8 | ||||
-rw-r--r-- | client/components/Settings/SettingsChangeEmail.tsx | 99 | ||||
-rw-r--r-- | client/components/Settings/SettingsDeleteAccount.tsx | 2 | ||||
-rw-r--r-- | client/components/Settings/SettingsDomain.tsx | 4 | ||||
-rw-r--r-- | client/components/Settings/SettingsPassword.tsx | 2 | ||||
-rw-r--r-- | client/consts/consts.ts | 1 | ||||
-rw-r--r-- | client/pages/_app.tsx | 5 | ||||
-rw-r--r-- | client/pages/settings.tsx | 5 | ||||
-rw-r--r-- | client/pages/verify-email.tsx | 55 |
9 files changed, 173 insertions, 8 deletions
diff --git a/client/components/AppWrapper.tsx b/client/components/AppWrapper.tsx index d7f6963..7da4c39 100644 --- a/client/components/AppWrapper.tsx +++ b/client/components/AppWrapper.tsx @@ -28,11 +28,15 @@ const AppWrapper = ({ children }: { children: any }) => { const loading = useStoreState(s => s.loading.loading); const getSettings = useStoreActions(s => s.settings.getSettings); + const isVerifyEmailPage = + typeof window !== "undefined" && + window.location.pathname.includes("verify-email"); + useEffect(() => { - if (isAuthenticated && !fetched) { + if (isAuthenticated && !fetched && !isVerifyEmailPage) { getSettings().catch(() => logout()); } - }, []); + }, [isVerifyEmailPage]); return ( <Wrapper diff --git a/client/components/Settings/SettingsChangeEmail.tsx b/client/components/Settings/SettingsChangeEmail.tsx new file mode 100644 index 0000000..2099257 --- /dev/null +++ b/client/components/Settings/SettingsChangeEmail.tsx @@ -0,0 +1,99 @@ +import { useFormState } from "react-use-form-state"; +import React, { FC, useState } from "react"; +import { Flex } from "reflexbox"; +import axios from "axios"; + +import { getAxiosConfig } from "../../utils"; +import { useMessage } from "../../hooks"; +import { APIv2 } from "../../consts"; +import { TextInput } from "../Input"; +import Text, { H2 } from "../Text"; +import { Button } from "../Button"; +import { Col } from "../Layout"; +import Icon from "../Icon"; + +const SettingsChangeEmail: FC = () => { + const [loading, setLoading] = useState(false); + const [message, setMessage] = useMessage(5000); + const [formState, { password, email, label }] = useFormState<{ + changeemailpass: string; + changeemailaddress: string; + }>(null, { + withIds: true + }); + + const onSubmit = async e => { + e.preventDefault(); + if (loading) return; + setLoading(true); + try { + const res = await axios.post( + APIv2.AuthChangeEmail, + { + password: formState.values.changeemailpass, + email: formState.values.changeemailaddress + }, + getAxiosConfig() + ); + setMessage(res.data.message, "green"); + } catch (error) { + setMessage(error?.response?.data?.error || "Couldn't send email."); + } + setLoading(false); + }; + + return ( + <Col alignItems="flex-start" maxWidth="100%"> + <H2 mb={4} bold> + Change email address + </H2> + <Col alignItems="flex-start" onSubmit={onSubmit} width={1} as="form"> + <Flex width={1} flexDirection={["column", "row"]}> + <Col mr={[0, 2]} mb={[3, 0]} flex="0 0 auto"> + <Text + {...label("changeemailpass")} + as="label" + mb={[2, 3]} + fontSize={[15, 16]} + bold + > + Password: + </Text> + <TextInput + {...password("changeemailpass")} + placeholder="Password..." + maxWidth="240px" + required + /> + </Col> + <Col ml={[0, 2]} flex="0 0 auto"> + <Text + {...label("changeemailaddress")} + as="label" + mb={[2, 3]} + fontSize={[15, 16]} + bold + > + New email address: + </Text> + <TextInput + {...email("changeemailaddress")} + placeholder="john@examaple.com" + flex="1 1 auto" + maxWidth="240px" + /> + </Col> + </Flex> + <Button type="submit" color="blue" mt={[24, 3]} disabled={loading}> + <Icon name={loading ? "spinner" : "refresh"} mr={2} stroke="white" /> + {loading ? "Sending..." : "Update"} + </Button> + </Col> + <Text fontSize={15} color={message.color} mt={3}> + {message.text} + </Text> + </Col> + ); +}; + +export default SettingsChangeEmail; diff --git a/client/components/Settings/SettingsDeleteAccount.tsx b/client/components/Settings/SettingsDeleteAccount.tsx index 22b7d80..c4a6a4b 100644 --- a/client/components/Settings/SettingsDeleteAccount.tsx +++ b/client/components/Settings/SettingsDeleteAccount.tsx @@ -65,7 +65,7 @@ const SettingsDeleteAccount: FC = () => { fontSize={[15, 16]} bold > - Password + Password: </Text> <RowCenterV as="form" onSubmit={onSubmit}> <TextInput diff --git a/client/components/Settings/SettingsDomain.tsx b/client/components/Settings/SettingsDomain.tsx index 55ca837..ac4de49 100644 --- a/client/components/Settings/SettingsDomain.tsx +++ b/client/components/Settings/SettingsDomain.tsx @@ -135,7 +135,7 @@ const SettingsDomain: FC = () => { fontSize={[15, 16]} bold > - Domain + Domain: </Text> <TextInput {...text("address")} @@ -152,7 +152,7 @@ const SettingsDomain: FC = () => { fontSize={[15, 16]} bold > - Homepage (optional) + Homepage (optional): </Text> <TextInput {...text("homepage")} diff --git a/client/components/Settings/SettingsPassword.tsx b/client/components/Settings/SettingsPassword.tsx index e2d7fda..281843e 100644 --- a/client/components/Settings/SettingsPassword.tsx +++ b/client/components/Settings/SettingsPassword.tsx @@ -55,7 +55,7 @@ const SettingsPassword: FC = () => { fontSize={[15, 16]} bold > - New password + New password: </Text> <Flex as="form" onSubmit={onSubmit}> <TextInput diff --git a/client/consts/consts.ts b/client/consts/consts.ts index 75e5944..bd440ea 100644 --- a/client/consts/consts.ts +++ b/client/consts/consts.ts @@ -19,6 +19,7 @@ export enum APIv2 { AuthRenew = "/api/v2/auth/renew", AuthResetPassword = "/api/v2/auth/reset-password", AuthChangePassword = "/api/v2/auth/change-password", + AuthChangeEmail = "/api/v2/auth/change-email", AuthGenerateApikey = "/api/v2/auth/apikey", Users = "/api/v2/users", Domains = "/api/v2/domains", diff --git a/client/pages/_app.tsx b/client/pages/_app.tsx index 1f7497c..bdc39a6 100644 --- a/client/pages/_app.tsx +++ b/client/pages/_app.tsx @@ -45,8 +45,11 @@ class MyApp extends App<any> { componentDidMount() { const { loading, auth } = this.store.dispatch; const token = cookie.get("token"); + const isVerifyEmailPage = + typeof window !== "undefined" && + window.location.pathname.includes("verify-email"); - if (token) { + if (token && !isVerifyEmailPage) { auth.renew().catch(() => { auth.logout(); }); diff --git a/client/pages/settings.tsx b/client/pages/settings.tsx index aec7008..2d6bdb0 100644 --- a/client/pages/settings.tsx +++ b/client/pages/settings.tsx @@ -2,6 +2,7 @@ import { NextPage } from "next"; import React from "react"; import SettingsDeleteAccount from "../components/Settings/SettingsDeleteAccount"; +import SettingsChangeEmail from "../components/Settings/SettingsChangeEmail"; import SettingsPassword from "../components/Settings/SettingsPassword"; import SettingsDomain from "../components/Settings/SettingsDomain"; import SettingsApi from "../components/Settings/SettingsApi"; @@ -28,9 +29,11 @@ const SettingsPage: NextPage = () => { <Divider mt={4} mb={48} /> <SettingsDomain /> <Divider mt={4} mb={48} /> + <SettingsApi /> + <Divider mt={4} mb={48} /> <SettingsPassword /> <Divider mt={4} mb={48} /> - <SettingsApi /> + <SettingsChangeEmail /> <Divider mt={4} mb={48} /> <SettingsDeleteAccount /> </Col> diff --git a/client/pages/verify-email.tsx b/client/pages/verify-email.tsx new file mode 100644 index 0000000..e1a77b8 --- /dev/null +++ b/client/pages/verify-email.tsx @@ -0,0 +1,55 @@ +import React, { useEffect } from "react"; +import { Flex } from "reflexbox/styled-components"; +import decode from "jwt-decode"; +import { NextPage } from "next"; +import cookie from "js-cookie"; + +import { useStoreActions } from "../store"; +import AppWrapper from "../components/AppWrapper"; +import { H2 } from "../components/Text"; +import { TokenPayload } from "../types"; +import Icon from "../components/Icon"; +import { Colors } from "../consts"; +import Footer from "../components/Footer"; + +interface Props { + token?: string; +} + +const VerifyEmail: NextPage<Props> = ({ token }) => { + const addAuth = useStoreActions(s => s.auth.add); + + useEffect(() => { + if (token) { + cookie.set("token", token, { expires: 7 }); + const decoded: TokenPayload = decode(token); + addAuth(decoded); + } + }, []); + + return ( + <AppWrapper> + <Flex flex="1 1 100%" justifyContent="center" mt={4}> + <Icon + name={token ? "check" : "x"} + size={26} + stroke={token ? Colors.CheckIcon : Colors.TrashIcon} + mr={3} + mt={1} + /> + <H2 textAlign="center" normal> + {token + ? "Email address verified successfully." + : "Couldn't verify the email address."} + </H2> + </Flex> + <Footer /> + </AppWrapper> + ); +}; + +VerifyEmail.getInitialProps = async ctx => { + return { token: (ctx?.req as any)?.token }; +}; + +export default VerifyEmail; |