From db9549abddb4fad0999c9d8c69c4591592552185 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 6 May 2023 11:40:06 +0800 Subject: [PATCH 1/9] feat: create settiing page Signed-off-by: neil --- src/common/components/Header/User.tsx | 38 ++++++++++++++++++++ src/common/components/Header/index.tsx | 2 ++ src/modules/settings/ProfileSetting.tsx | 13 +++++++ src/pages/{analyze/index.tsx => analyze.tsx} | 0 src/pages/report.tsx | 26 ++++++++++++++ src/pages/settings.tsx | 27 ++++++++++++++ 6 files changed, 106 insertions(+) create mode 100644 src/common/components/Header/User.tsx create mode 100644 src/modules/settings/ProfileSetting.tsx rename src/pages/{analyze/index.tsx => analyze.tsx} (100%) create mode 100644 src/pages/report.tsx create mode 100644 src/pages/settings.tsx diff --git a/src/common/components/Header/User.tsx b/src/common/components/Header/User.tsx new file mode 100644 index 00000000..e32208e2 --- /dev/null +++ b/src/common/components/Header/User.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { FaUserCircle } from 'react-icons/fa'; + +const User = () => { + const { reload } = useRouter(); + + return ( +
+
+ +
+ +
+
+ +
+ 我的订阅 +
+ + + +
+ 账号设置 +
+ + +
+ 退出 +
+
+
+
+ ); +}; + +export default User; diff --git a/src/common/components/Header/index.tsx b/src/common/components/Header/index.tsx index 0474963d..7b77deb3 100644 --- a/src/common/components/Header/index.tsx +++ b/src/common/components/Header/index.tsx @@ -8,6 +8,7 @@ import MobileHeader from './MobileHeader'; import CommunityDropdown from './CommunityDropdown'; import ChangeLanguage from './ChangeLanguage'; import SubmitYouProject from './SubmitYouProject'; +import User from './User'; const Header: React.FC<{ sticky?: boolean; @@ -60,6 +61,7 @@ const Header: React.FC<{
+
diff --git a/src/modules/settings/ProfileSetting.tsx b/src/modules/settings/ProfileSetting.tsx new file mode 100644 index 00000000..a6131622 --- /dev/null +++ b/src/modules/settings/ProfileSetting.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +const ProfileSetting = () => { + return ( +
+

Profile

+ +
Name
+
+ ); +}; + +export default ProfileSetting; diff --git a/src/pages/analyze/index.tsx b/src/pages/analyze.tsx similarity index 100% rename from src/pages/analyze/index.tsx rename to src/pages/analyze.tsx diff --git a/src/pages/report.tsx b/src/pages/report.tsx new file mode 100644 index 00000000..b94abde4 --- /dev/null +++ b/src/pages/report.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { GetServerSideProps } from 'next'; +import { useTranslation } from 'react-i18next'; +import Header from '@common/components/Header'; +import Banner from '@modules/submitProject/Misc/Banner'; +import getLocalesFile from '@common/utils/getLocalesFile'; + +export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { + return { + props: { + ...(await getLocalesFile(req.cookies, [])), + }, + }; +}; + +const Report = () => { + const { t } = useTranslation(); + return ( + <> +
+ + + ); +}; + +export default Report; diff --git a/src/pages/settings.tsx b/src/pages/settings.tsx new file mode 100644 index 00000000..3be25515 --- /dev/null +++ b/src/pages/settings.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { GetServerSideProps } from 'next'; +import { useTranslation } from 'react-i18next'; +import Header from '@common/components/Header'; +import Banner from '@modules/submitProject/Misc/Banner'; +import getLocalesFile from '@common/utils/getLocalesFile'; + +export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { + return { + props: { + ...(await getLocalesFile(req.cookies, [])), + }, + }; +}; + +const Settings = () => { + const { t } = useTranslation(); + return ( + <> +
+ +
+ + ); +}; + +export default Settings; From 8a0f42c7a9b90a91d214e114350551bc9bd2d5f5 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 6 May 2023 18:01:54 +0800 Subject: [PATCH 2/9] feat: profile setting page Signed-off-by: neil --- package.json | 1 + src/common/components/Input.tsx | 4 +- src/common/components/Layout/Center.tsx | 11 ++-- src/modules/settings/ProfileSetting.tsx | 74 +++++++++++++++++++++++-- src/pages/settings.tsx | 3 +- yarn.lock | 12 ++++ 6 files changed, 92 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 9849264c..124092bc 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "qrcode": "^1.5.1", "query-string": "^7.1.1", "react": "18.2.0", + "react-cropper": "^2.3.3", "react-datepicker": "^4.11.0", "react-dom": "18.2.0", "react-error-boundary": "^3.1.4", diff --git a/src/common/components/Input.tsx b/src/common/components/Input.tsx index eed1c1a4..839bbdd5 100644 --- a/src/common/components/Input.tsx +++ b/src/common/components/Input.tsx @@ -10,7 +10,7 @@ const Input = forwardRef< onBlur?: (e: FocusEvent) => void; defaultValue?: string; className?: string; - placeholder: string; + placeholder?: string; error?: boolean; disabled?: boolean; } @@ -42,7 +42,7 @@ const Input = forwardRef< disabled={disabled} className={classnames( className, - 'daisy-input-bordered daisy-input h-12 flex-1 border-2 px-4 text-base outline-none', + 'daisy-input-bordered daisy-input h-12 w-full flex-1 border-2 px-4 text-base outline-none', [error ? 'border-red-500' : 'border-black'] )} /> diff --git a/src/common/components/Layout/Center.tsx b/src/common/components/Layout/Center.tsx index 2ab82c2b..5d590e24 100644 --- a/src/common/components/Layout/Center.tsx +++ b/src/common/components/Layout/Center.tsx @@ -1,12 +1,13 @@ import React, { PropsWithChildren } from 'react'; import classnames from 'classnames'; -const Center: React.FC> = ({ - children, - className, -}) => { +const Center: React.FC< + PropsWithChildren<{ className?: string; widthClassName?: string }> +> = ({ children, className, widthClassName = 'w-[1200px]' }) => { return ( -
+
{children}
); diff --git a/src/modules/settings/ProfileSetting.tsx b/src/modules/settings/ProfileSetting.tsx index a6131622..d20cafa6 100644 --- a/src/modules/settings/ProfileSetting.tsx +++ b/src/modules/settings/ProfileSetting.tsx @@ -1,13 +1,77 @@ import React from 'react'; +import Image from 'next/image'; +import Center from '@common/components/Layout/Center'; +import Input from '@common/components/Input'; +import Button from '@common/components/Button'; -const ProfileSetting = () => { +const ProfileForm = () => { + return ( +
+
+

Profile

+ +
+
Name
+ +
+ +
+
Email
+ +
+ + +
+ +
+
Avatar
+
+ avatar +
+
+
+ ); +}; + +const OAuthList = () => { return ( -
-

Profile

- -
Name
+
+
Connected Accounts
+
+ Connect multiple accounts to your user and sign in with any of them +
+
+
+
+
+
+
+
); }; +const DeleteAccount = () => { + return ( + <> +
Delete account
+
+ Once you delete your account, there is no going back. Please be certain + when taking this action. +
+ + + ); +}; + +const ProfileSetting = () => { + return ( +
+ + + +
+ ); +}; + export default ProfileSetting; diff --git a/src/pages/settings.tsx b/src/pages/settings.tsx index 3be25515..3584f7f8 100644 --- a/src/pages/settings.tsx +++ b/src/pages/settings.tsx @@ -3,6 +3,7 @@ import { GetServerSideProps } from 'next'; import { useTranslation } from 'react-i18next'; import Header from '@common/components/Header'; import Banner from '@modules/submitProject/Misc/Banner'; +import ProfileSetting from '@modules/settings/ProfileSetting'; import getLocalesFile from '@common/utils/getLocalesFile'; export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { @@ -19,7 +20,7 @@ const Settings = () => { <>
-
+ ); }; diff --git a/yarn.lock b/yarn.lock index 8388764c..3723b51c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4093,6 +4093,11 @@ create-require@^1.1.0: resolved "https://registry.npmmirror.com/create-require/-/create-require-1.1.1.tgz" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +cropperjs@^1.5.13: + version "1.5.13" + resolved "https://registry.npmmirror.com/cropperjs/-/cropperjs-1.5.13.tgz#eb1682f01d17c70ed5244317091d745c9a249ef8" + integrity sha512-by7jKAo73y5/Do0K6sxdTKHgndY0NMjG2bEdgeJxycbcmHuCiMXqw8sxy5C5Y5WTOTcDGmbT7Sr5CgKOXR06OA== + cross-env@^7.0.3: version "7.0.3" resolved "https://registry.npmmirror.com/cross-env/-/cross-env-7.0.3.tgz" @@ -8476,6 +8481,13 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-cropper@^2.3.3: + version "2.3.3" + resolved "https://registry.npmmirror.com/react-cropper/-/react-cropper-2.3.3.tgz#aced0d045d9fd56168ba4d17287a40c287754dee" + integrity sha512-zghiEYkUb41kqtu+2jpX2Ntigf+Jj1dF9ew4lAobPzI2adaPE31z0p+5TcWngK6TvmWQUwK3lj4G+NDh1PDQ1w== + dependencies: + cropperjs "^1.5.13" + react-datepicker@^4.11.0: version "4.11.0" resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.11.0.tgz#40e73b4729a284ed206fdb322b8e84eb566e11a3" From 2091c2bcf7cb19e55afec5a776821f96903797d2 Mon Sep 17 00:00:00 2001 From: neil Date: Mon, 8 May 2023 18:44:02 +0800 Subject: [PATCH 3/9] feat: settings page Signed-off-by: neil --- src/modules/settings/OAuthProviders.tsx | 51 +++++++++++++++++++ src/modules/settings/ProfileSetting.tsx | 66 ++++++++++--------------- yarn.lock | 12 +++++ 3 files changed, 88 insertions(+), 41 deletions(-) create mode 100644 src/modules/settings/OAuthProviders.tsx diff --git a/src/modules/settings/OAuthProviders.tsx b/src/modules/settings/OAuthProviders.tsx new file mode 100644 index 00000000..6e34cd47 --- /dev/null +++ b/src/modules/settings/OAuthProviders.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import classnames from 'classnames'; +import { SiGitee, SiGithub } from 'react-icons/si'; +import Button from '@common/components/Button'; + +const providers = [ + { + name: 'GitHub', + desc: 'Can be used to submit project after binding', + icon: , + }, + { + name: 'Gitee', + desc: 'Can be used to submit project after binding', + icon: , + }, +]; + +const OAuthList = () => { + return ( +
+
Connected Accounts
+
+ Connect multiple accounts to your user and sign in with any of them +
+
+ {providers.map((provider) => { + return ( +
+
{provider.icon}
+
+
{provider.name}
+
{provider.desc}
+
+
+ +
+
+ ); + })} +
+
+ ); +}; + +export default OAuthList; diff --git a/src/modules/settings/ProfileSetting.tsx b/src/modules/settings/ProfileSetting.tsx index d20cafa6..17c68971 100644 --- a/src/modules/settings/ProfileSetting.tsx +++ b/src/modules/settings/ProfileSetting.tsx @@ -3,51 +3,35 @@ import Image from 'next/image'; import Center from '@common/components/Layout/Center'; import Input from '@common/components/Input'; import Button from '@common/components/Button'; +import OAuthProviders from './OAuthProviders'; const ProfileForm = () => { return ( -
-
-

Profile

- -
-
Name
- -
- -
-
Email
- -
- - -
- -
-
Avatar
-
- avatar + <> +

Profile

+
+
+
+
Name
+ +
+ +
+
Email
+ +
+ +
-
-
- ); -}; -const OAuthList = () => { - return ( -
-
Connected Accounts
-
- Connect multiple accounts to your user and sign in with any of them -
-
-
-
-
-
+
+
Avatar
+
+ avatar +
-
+ ); }; @@ -59,16 +43,16 @@ const DeleteAccount = () => { Once you delete your account, there is no going back. Please be certain when taking this action.
- + ); }; const ProfileSetting = () => { return ( -
+
- +
); diff --git a/yarn.lock b/yarn.lock index 003bb263..563af8fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6675,6 +6675,11 @@ create-require@^1.1.0: resolved "https://registry.npmmirror.com/create-require/-/create-require-1.1.1.tgz" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +cropperjs@^1.5.13: + version "1.5.13" + resolved "https://registry.npmmirror.com/cropperjs/-/cropperjs-1.5.13.tgz#eb1682f01d17c70ed5244317091d745c9a249ef8" + integrity sha512-by7jKAo73y5/Do0K6sxdTKHgndY0NMjG2bEdgeJxycbcmHuCiMXqw8sxy5C5Y5WTOTcDGmbT7Sr5CgKOXR06OA== + cross-env@^7.0.3: version "7.0.3" resolved "https://registry.npmmirror.com/cross-env/-/cross-env-7.0.3.tgz" @@ -12340,6 +12345,13 @@ react-colorful@^5.1.2: resolved "https://registry.npmmirror.com/react-colorful/-/react-colorful-5.6.1.tgz#7dc2aed2d7c72fac89694e834d179e32f3da563b" integrity sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw== +react-cropper@^2.3.3: + version "2.3.3" + resolved "https://registry.npmmirror.com/react-cropper/-/react-cropper-2.3.3.tgz#aced0d045d9fd56168ba4d17287a40c287754dee" + integrity sha512-zghiEYkUb41kqtu+2jpX2Ntigf+Jj1dF9ew4lAobPzI2adaPE31z0p+5TcWngK6TvmWQUwK3lj4G+NDh1PDQ1w== + dependencies: + cropperjs "^1.5.13" + react-datepicker@^4.11.0: version "4.11.0" resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.11.0.tgz#40e73b4729a284ed206fdb322b8e84eb566e11a3" From ef51aa829fc777482e9fba496f8249396e4dfaab Mon Sep 17 00:00:00 2001 From: neil Date: Wed, 10 May 2023 18:51:14 +0800 Subject: [PATCH 4/9] feat: add pages Signed-off-by: neil --- src/pages/{analyze.tsx => analyze/index.tsx} | 0 src/pages/auth/email/verify/failed.tsx | 7 +++++++ src/pages/auth/email/verify/success.tsx | 7 +++++++ src/pages/{settings.tsx => settings/profile.tsx} | 0 src/pages/{report.tsx => settings/subscribe.tsx} | 4 ++-- 5 files changed, 16 insertions(+), 2 deletions(-) rename src/pages/{analyze.tsx => analyze/index.tsx} (100%) create mode 100644 src/pages/auth/email/verify/failed.tsx create mode 100644 src/pages/auth/email/verify/success.tsx rename src/pages/{settings.tsx => settings/profile.tsx} (100%) rename src/pages/{report.tsx => settings/subscribe.tsx} (91%) diff --git a/src/pages/analyze.tsx b/src/pages/analyze/index.tsx similarity index 100% rename from src/pages/analyze.tsx rename to src/pages/analyze/index.tsx diff --git a/src/pages/auth/email/verify/failed.tsx b/src/pages/auth/email/verify/failed.tsx new file mode 100644 index 00000000..f3a2c54e --- /dev/null +++ b/src/pages/auth/email/verify/failed.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +const Failed = () => { + return
; +}; + +export default Failed; diff --git a/src/pages/auth/email/verify/success.tsx b/src/pages/auth/email/verify/success.tsx new file mode 100644 index 00000000..8d84dd3f --- /dev/null +++ b/src/pages/auth/email/verify/success.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +const Success = () => { + return
; +}; + +export default Success; diff --git a/src/pages/settings.tsx b/src/pages/settings/profile.tsx similarity index 100% rename from src/pages/settings.tsx rename to src/pages/settings/profile.tsx diff --git a/src/pages/report.tsx b/src/pages/settings/subscribe.tsx similarity index 91% rename from src/pages/report.tsx rename to src/pages/settings/subscribe.tsx index b94abde4..46f0cd8a 100644 --- a/src/pages/report.tsx +++ b/src/pages/settings/subscribe.tsx @@ -13,7 +13,7 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { }; }; -const Report = () => { +const Subscribe = () => { const { t } = useTranslation(); return ( <> @@ -23,4 +23,4 @@ const Report = () => { ); }; -export default Report; +export default Subscribe; From 518f92f3f2dee457aa93092ef2bec5c520037b3e Mon Sep 17 00:00:00 2001 From: neil Date: Thu, 11 May 2023 14:40:43 +0800 Subject: [PATCH 5/9] feat: fetch user info --- src/common/components/Header/User.tsx | 6 +- src/modules/auth/UserInfoContext.tsx | 41 ------------ src/modules/auth/UserInfoStore.tsx | 67 +++++++++++++++++++ .../submitProject/Form/SelectRepoSource.tsx | 15 +++-- .../submitProject/FormCommunity/index.tsx | 8 ++- .../submitProject/FormSingleRepo/index.tsx | 7 +- .../submitProject/Misc/AddSelectPopover.tsx | 5 +- src/modules/submitProject/Misc/Auth.tsx | 20 +++--- src/modules/submitProject/Misc/FillItem.tsx | 5 +- .../submitProject/Misc/InputUrlField.tsx | 5 +- .../submitProject/RepoSelect/index.tsx | 9 +-- src/pages/_app.tsx | 2 + src/pages/auth/signin.tsx | 5 +- src/pages/submit-your-project/community.tsx | 5 +- src/pages/submit-your-project/index.tsx | 5 +- 15 files changed, 123 insertions(+), 82 deletions(-) delete mode 100644 src/modules/auth/UserInfoContext.tsx create mode 100644 src/modules/auth/UserInfoStore.tsx diff --git a/src/common/components/Header/User.tsx b/src/common/components/Header/User.tsx index e32208e2..6ef975d8 100644 --- a/src/common/components/Header/User.tsx +++ b/src/common/components/Header/User.tsx @@ -7,20 +7,20 @@ const User = () => { const { reload } = useRouter(); return ( -
+
- +
我的订阅
- +
账号设置
diff --git a/src/modules/auth/UserInfoContext.tsx b/src/modules/auth/UserInfoContext.tsx deleted file mode 100644 index 97b29982..00000000 --- a/src/modules/auth/UserInfoContext.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React, { createContext, useContext, PropsWithChildren } from 'react'; -import client from '@graphql/client'; -import { useUserinfoQuery, UserinfoQuery } from '@graphql/generated'; -import { getAuthProvider } from '@common/utils/cookie'; - -const UserContext = createContext(undefined); - -export const UserInfoProvider: React.FC = ({ children }) => { - const { data, isLoading } = useUserinfoQuery(client); - if (isLoading) { - return null; - } - - return {children}; -}; - -export const useUserInfo = () => { - const ctx = useContext(UserContext); - - let user; - const provider = getAuthProvider(); - if (provider) { - user = ctx?.currentUser?.loginBinds?.find( - (bindInfo) => bindInfo.provider === provider - ); - } else { - user = ctx?.currentUser?.loginBinds?.[0]; - } - - if (user) { - user = { - ...user, - // todo Let the backend modify - // The naming of the returned fields in the interface data is reversed. - account: user?.nickname, - nickname: user?.account, - }; - } - - return { user, ...ctx }; -}; diff --git a/src/modules/auth/UserInfoStore.tsx b/src/modules/auth/UserInfoStore.tsx new file mode 100644 index 00000000..6836930d --- /dev/null +++ b/src/modules/auth/UserInfoStore.tsx @@ -0,0 +1,67 @@ +import React, { PropsWithChildren, useEffect } from 'react'; +import client from '@graphql/client'; +import { proxy, useSnapshot } from 'valtio'; +import { useUserinfoQuery, UserinfoQuery } from '@graphql/generated'; +import { getAuthProvider } from '@common/utils/cookie'; + +type LoginBind = NonNullable< + NonNullable['loginBinds'] +>[0]; + +export const UserInfoFetcher: React.FC = ({ children }) => { + const { data, isLoading } = useUserinfoQuery(client, {}); + + useEffect(() => { + setUserInfo(data); + }, [data]); + + useEffect(() => { + serUserLoading(isLoading); + }, [isLoading]); + + return null; +}; + +export const userInfoStore = proxy<{ + loading: boolean; + user: LoginBind | null; + currentUser: UserinfoQuery['currentUser'] | null; +}>({ + loading: true, + user: null, + currentUser: null, +}); + +export const setUserInfo = (res?: UserinfoQuery) => { + let user; + const provider = getAuthProvider(); + if (provider) { + user = res?.currentUser?.loginBinds?.find( + (bindInfo) => bindInfo.provider === provider + ); + } else { + user = res?.currentUser?.loginBinds?.[0]; + } + + if (user) { + user = { + ...user, + // todo Let the backend modify + // The naming of the returned fields in the interface data is reversed. + account: user?.nickname, + nickname: user?.account, + }; + userInfoStore.user = user; + } + userInfoStore.currentUser = res?.currentUser || null; +}; + +export const serUserLoading = (loading: boolean) => { + userInfoStore.loading = loading; +}; + +export const resetUserInfo = () => { + userInfoStore.loading = false; + userInfoStore.user = null; + userInfoStore.currentUser = null; +}; diff --git a/src/modules/submitProject/Form/SelectRepoSource.tsx b/src/modules/submitProject/Form/SelectRepoSource.tsx index d51580a3..2fe0290f 100644 --- a/src/modules/submitProject/Form/SelectRepoSource.tsx +++ b/src/modules/submitProject/Form/SelectRepoSource.tsx @@ -4,7 +4,8 @@ import Image from 'next/image'; import { AiFillCaretDown, AiOutlinePlus } from 'react-icons/ai'; import { useClickAway, useSessionStorage } from 'react-use'; import { useQuery } from '@tanstack/react-query'; -import { useUserInfo } from '@modules/auth/UserInfoContext'; +import { useSnapshot } from 'valtio'; +import { userInfoStore } from '@modules/auth/UserInfoStore'; import { getOrganizations } from '@modules/submitProject/api'; const SourceItem: React.FC<{ @@ -32,7 +33,7 @@ const SourceItem: React.FC<{ interface Item { avatar_url: string; login: string; - user: boolean; + isUser: boolean; } const SelectRepoSource: React.FC< @@ -45,7 +46,7 @@ const SelectRepoSource: React.FC< const [open, setOpen] = useState(false); const ref = useRef(null); - const { user } = useUserInfo(); + const { user } = useSnapshot(userInfoStore); const nickname = user?.nickname!; const account = user?.account!; const provider = user?.provider!; @@ -62,7 +63,11 @@ const SelectRepoSource: React.FC< const options: Item[] = React.useMemo(() => { const items = data?.data?.map((item) => { - return { login: item.login, avatar_url: item.avatar_url, user: false }; + return { + login: item.login, + avatar_url: item.avatar_url, + isUser: false, + }; }) || []; return [ @@ -70,7 +75,7 @@ const SelectRepoSource: React.FC< { login: nickname, avatar_url: avatarUrl!, - user: true, + isUser: true, }, ]; }, [data, nickname, avatarUrl]); diff --git a/src/modules/submitProject/FormCommunity/index.tsx b/src/modules/submitProject/FormCommunity/index.tsx index e1b5fe81..9ea127bd 100644 --- a/src/modules/submitProject/FormCommunity/index.tsx +++ b/src/modules/submitProject/FormCommunity/index.tsx @@ -2,6 +2,7 @@ import React, { useRef, useState } from 'react'; import { useCreateProjectTaskMutation } from '@graphql/generated'; import client from '@graphql/client'; import uniq from 'lodash/uniq'; +import { useSnapshot } from 'valtio'; import { useSessionStorage } from 'react-use'; import Select from '@common/components/Select'; import Button from '@common/components/Button'; @@ -9,14 +10,15 @@ import SwitchToSingleRepo from './SwitchToSingleRepo'; import SoftwareArtifactRepository from './SoftwareArtifactRepository'; import GovernanceRepository from './GovernanceRepository'; import { fillHttps, getRepoName } from '@common/utils'; -import { useUserInfo } from '@modules/auth/UserInfoContext'; +import { userInfoStore } from '@modules/auth/UserInfoStore'; import Message from '@modules/submitProject/Misc/Message'; import { useTranslation } from 'react-i18next'; const FormCommunity = () => { const { t } = useTranslation(); - const { user } = useUserInfo(); + const { user } = useSnapshot(userInfoStore); const account = user!.account; + const provider = user!.provider; const [communityName, setCommunityName] = useState(''); const [sarUrls, setSarUrls] = useSessionStorage( @@ -48,7 +50,7 @@ const FormCommunity = () => { const handleSubmit = () => { const common = { - origin: user!.provider as string, + origin: provider as string, }; const projectName = communityName || options[0]; mutate({ diff --git a/src/modules/submitProject/FormSingleRepo/index.tsx b/src/modules/submitProject/FormSingleRepo/index.tsx index 91431662..36afb4f1 100644 --- a/src/modules/submitProject/FormSingleRepo/index.tsx +++ b/src/modules/submitProject/FormSingleRepo/index.tsx @@ -8,7 +8,8 @@ import SelectLike from '@common/components/SelectLike'; import Input from '@common/components/Input'; import Button from '@common/components/Button'; import Message from '@modules/submitProject/Misc/Message'; -import { useUserInfo } from '@modules/auth/UserInfoContext'; +import { useSnapshot } from 'valtio'; +import { userInfoStore } from '@modules/auth/UserInfoStore'; import { fillHttps } from '@common/utils'; import SwitchToCommunity from './SwitchToCommunity'; import RepoSelect from '../RepoSelect'; @@ -16,7 +17,7 @@ import { getUrlReg } from '../Misc'; const FormSingleRepo = () => { const { t } = useTranslation(); - const { user } = useUserInfo(); + const { user } = useSnapshot(userInfoStore); const provider = user?.provider || 'github'; const [formType, setFormType] = useState<'select' | 'input'>('input'); @@ -47,7 +48,7 @@ const FormSingleRepo = () => { const reportUrl = data?.createRepoTask?.reportUrl; const onSubmit: SubmitHandler<{ url?: string }> = (data) => { - const common = { origin: user?.provider as string }; + const common = { origin: provider }; const urls = [data.url, selectVal].map(fillHttps).filter(Boolean); mutate({ ...common, repoUrls: urls }); }; diff --git a/src/modules/submitProject/Misc/AddSelectPopover.tsx b/src/modules/submitProject/Misc/AddSelectPopover.tsx index c13ae796..f76fc684 100644 --- a/src/modules/submitProject/Misc/AddSelectPopover.tsx +++ b/src/modules/submitProject/Misc/AddSelectPopover.tsx @@ -4,7 +4,8 @@ import { AiFillGithub, AiOutlineLink, AiOutlinePlus } from 'react-icons/ai'; import classnames from 'classnames'; import { SiGitee } from 'react-icons/si'; import { useTranslation } from 'react-i18next'; -import { useUserInfo } from '@modules/auth/UserInfoContext'; +import { useSnapshot } from 'valtio'; +import { userInfoStore } from '@modules/auth/UserInfoStore'; export const getIcons = (type: string) => { switch (type) { @@ -25,7 +26,7 @@ const AddSelectPopover: React.FC<{ onClick: (e: React.MouseEvent) => void; }> = ({ className, onSelect, onClick, open, onClose }) => { const { t } = useTranslation(); - const { user } = useUserInfo(); + const { user } = useSnapshot(userInfoStore); const provider = user?.provider!; const ref = useRef(null); diff --git a/src/modules/submitProject/Misc/Auth.tsx b/src/modules/submitProject/Misc/Auth.tsx index 81b72a19..19d94df6 100644 --- a/src/modules/submitProject/Misc/Auth.tsx +++ b/src/modules/submitProject/Misc/Auth.tsx @@ -1,22 +1,26 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import Image from 'next/image'; import client from '@graphql/client'; import { useRouter } from 'next/router'; +import { useSnapshot } from 'valtio'; import { useTranslation } from 'react-i18next'; -import { useUserInfo } from '@modules/auth/UserInfoContext'; +import { userInfoStore } from '@modules/auth/UserInfoStore'; import { useSignOutMutation } from '@graphql/generated'; const Auth: React.FC = () => { const { t } = useTranslation(); const router = useRouter(); - const { user } = useUserInfo(); - const isLogin = Boolean(user); + const { user, loading } = useSnapshot(userInfoStore); + const hasLoggedIn = Boolean(user); const mutation = useSignOutMutation(client); - if (!isLogin) { - router.push('/auth/signin'); - return null; - } + useEffect(() => { + if (!loading && !hasLoggedIn) { + router.push('/auth/signin'); + } + }, [loading, hasLoggedIn, router]); + + if (!hasLoggedIn) return null; return ( <> diff --git a/src/modules/submitProject/Misc/FillItem.tsx b/src/modules/submitProject/Misc/FillItem.tsx index c45a79b1..f70629b0 100644 --- a/src/modules/submitProject/Misc/FillItem.tsx +++ b/src/modules/submitProject/Misc/FillItem.tsx @@ -1,7 +1,8 @@ import React from 'react'; import { AiFillGithub, AiOutlineClose } from 'react-icons/ai'; import { SiGitee } from 'react-icons/si'; -import { useUserInfo } from '@modules/auth/UserInfoContext'; +import { useSnapshot } from 'valtio'; +import { userInfoStore } from '@modules/auth/UserInfoStore'; export const getIcons = (type: string) => { switch (type) { @@ -18,7 +19,7 @@ const FillItem: React.FC<{ url: string; onDelete: (v: string) => void }> = ({ url, onDelete, }) => { - const { user } = useUserInfo(); + const { user } = useSnapshot(userInfoStore); const provider = user?.provider!; return ( diff --git a/src/modules/submitProject/Misc/InputUrlField.tsx b/src/modules/submitProject/Misc/InputUrlField.tsx index bbd3a5f1..fffe824e 100644 --- a/src/modules/submitProject/Misc/InputUrlField.tsx +++ b/src/modules/submitProject/Misc/InputUrlField.tsx @@ -9,7 +9,8 @@ import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; import Input from '@common/components/Input'; import { AiOutlineClose } from 'react-icons/ai'; -import { useUserInfo } from '@modules/auth/UserInfoContext'; +import { useSnapshot } from 'valtio'; +import { userInfoStore } from '@modules/auth/UserInfoStore'; import { getUrlReg } from '../Misc'; interface Props { @@ -26,7 +27,7 @@ const InputUrlField = forwardRef( ({ onClose, onPressEnter }, ref) => { const { t } = useTranslation(); const inputRef = useRef(null); - const { user } = useUserInfo(); + const { user } = useSnapshot(userInfoStore); const provider = user?.provider!; const [value, setValue] = useState(''); diff --git a/src/modules/submitProject/RepoSelect/index.tsx b/src/modules/submitProject/RepoSelect/index.tsx index bcad8f0b..aee92cf5 100644 --- a/src/modules/submitProject/RepoSelect/index.tsx +++ b/src/modules/submitProject/RepoSelect/index.tsx @@ -10,7 +10,8 @@ import { useDebounce } from 'ahooks'; import Input from '@common/components/Input'; import { CgSpinner } from 'react-icons/cg'; import SelectRepoSource from '@modules/submitProject/Form/SelectRepoSource'; -import { useUserInfo } from '@modules/auth/UserInfoContext'; +import { userInfoStore } from '@modules/auth/UserInfoStore'; +import { useSnapshot } from 'valtio'; import RepoItem from './RepoItem'; import Loading from './Loading'; import { useTranslation } from 'react-i18next'; @@ -20,7 +21,7 @@ const RepoSelect: React.FC<{ onConfirm: (val: string) => void }> = ({ onConfirm, }) => { const { t } = useTranslation(); - const { user } = useUserInfo(); + const { user } = useSnapshot(userInfoStore); const nickname = user?.nickname!; const account = user?.account!; const provider = user?.provider!; @@ -30,7 +31,7 @@ const RepoSelect: React.FC<{ onConfirm: (val: string) => void }> = ({ const [org, setOrg] = useState({ login: nickname!, avatar_url: user?.avatarUrl!, - user: true, + isUser: true, }); const [repoList, setRepoList] = useState([]); @@ -40,7 +41,7 @@ const RepoSelect: React.FC<{ onConfirm: (val: string) => void }> = ({ const { isLoading, isFetching, isError, error } = useQuery( ['getRepos', account, page, { org }], () => { - if (org.user) { + if (org.isUser) { return getRepos(provider)({ username: account, page }); } return getOrgRepos(provider)({ diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 1f44cb00..55c54f9d 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -6,6 +6,7 @@ import { appWithTranslation } from 'next-i18next'; import i18nextConfig from 'next-i18next.config.js'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { UserInfoFetcher } from '@modules/auth/UserInfoStore'; import { useAppGA, GAScripts } from '@common/lib/ga'; import { browserLanguageDetectorAndReload } from '@common/utils/getLocale'; @@ -69,6 +70,7 @@ function MyApp({ color="#3A5BEF" options={{ showSpinner: false }} /> + diff --git a/src/pages/auth/signin.tsx b/src/pages/auth/signin.tsx index 6a98ff5e..19996e7e 100644 --- a/src/pages/auth/signin.tsx +++ b/src/pages/auth/signin.tsx @@ -3,7 +3,6 @@ import { GetServerSidePropsContext } from 'next'; import { useTranslation } from 'next-i18next'; import Header from '@common/components/Header'; import Banner from '@modules/submitProject/Misc/Banner'; -import { UserInfoProvider } from '@modules/auth/UserInfoContext'; import LoginPage from '@modules/auth/LoginPage'; import getLocalesFile from '@common/utils/getLocalesFile'; @@ -19,11 +18,11 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { const SignIn: React.FC = () => { const { t } = useTranslation(); return ( - + <>
- + ); }; diff --git a/src/pages/submit-your-project/community.tsx b/src/pages/submit-your-project/community.tsx index bade2600..691783b6 100644 --- a/src/pages/submit-your-project/community.tsx +++ b/src/pages/submit-your-project/community.tsx @@ -4,7 +4,6 @@ import Banner from '@modules/submitProject/Misc/Banner'; import SubmitProject from '@modules/submitProject'; import FormCommunity from '@modules/submitProject/FormCommunity'; import { GetServerSidePropsContext } from 'next'; -import { UserInfoProvider } from '@modules/auth/UserInfoContext'; import getLocalesFile from '@common/utils/getLocalesFile'; import { useTranslation } from 'react-i18next'; @@ -20,13 +19,13 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { const SubmitYourProject: React.FC = () => { const { t } = useTranslation(); return ( - + <>
- + ); }; diff --git a/src/pages/submit-your-project/index.tsx b/src/pages/submit-your-project/index.tsx index efa24201..f2c20b22 100644 --- a/src/pages/submit-your-project/index.tsx +++ b/src/pages/submit-your-project/index.tsx @@ -4,7 +4,6 @@ import Header from '@common/components/Header'; import Banner from '@modules/submitProject/Misc/Banner'; import SubmitProject from '@modules/submitProject'; import FormSingleRepo from '@modules/submitProject/FormSingleRepo'; -import { UserInfoProvider } from '@modules/auth/UserInfoContext'; import getLocalesFile from '@common/utils/getLocalesFile'; import { useTranslation } from 'react-i18next'; @@ -20,13 +19,13 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { const SubmitYourProject = () => { const { t } = useTranslation(); return ( - + <>
- + ); }; From 6904c6269667a48897bf98ca2e39cf629b716938 Mon Sep 17 00:00:00 2001 From: neil Date: Thu, 11 May 2023 16:52:09 +0800 Subject: [PATCH 6/9] feat: auth redirect Signed-off-by: neil --- next.config.js | 6 ++- src/common/components/Header/User.tsx | 59 +++++++++++++++------ src/modules/auth/LoginCard.tsx | 37 ++++++++++--- src/modules/auth/UserInfoFetcher.ts | 20 +++++++ src/modules/auth/UserInfoStore.tsx | 21 ++------ src/modules/auth/useAuthRedirect.ts | 15 ++++++ src/modules/submitProject/Misc/Auth.tsx | 8 +-- src/pages/_app.tsx | 2 +- src/pages/auth/signin.tsx | 4 -- src/pages/settings/profile.tsx | 2 + src/pages/settings/subscribe.tsx | 2 + src/pages/submit-your-project/community.tsx | 2 + src/pages/submit-your-project/index.tsx | 5 +- 13 files changed, 130 insertions(+), 53 deletions(-) create mode 100644 src/modules/auth/UserInfoFetcher.ts create mode 100644 src/modules/auth/useAuthRedirect.ts diff --git a/next.config.js b/next.config.js index 7c72be5d..98f2793f 100644 --- a/next.config.js +++ b/next.config.js @@ -22,7 +22,11 @@ const nextConfig = { includePaths: [path.join(__dirname, 'src/styles')], }, images: { - domains: ['portrait.gitee.com', 'avatars.githubusercontent.com'], + domains: [ + 'portrait.gitee.com', + 'foruda.gitee.com', + 'avatars.githubusercontent.com', + ], }, webpack(config) { config.module.rules.push({ diff --git a/src/common/components/Header/User.tsx b/src/common/components/Header/User.tsx index 6ef975d8..95398c96 100644 --- a/src/common/components/Header/User.tsx +++ b/src/common/components/Header/User.tsx @@ -1,33 +1,62 @@ import React from 'react'; import Link from 'next/link'; import { useRouter } from 'next/router'; -import { FaUserCircle } from 'react-icons/fa'; +import Image from 'next/image'; +import { AiOutlineUser } from 'react-icons/ai'; +import { MdOutlineLogout } from 'react-icons/md'; +import client from '@graphql/client'; +import { useSnapshot } from 'valtio'; +import { useSignOutMutation } from '@graphql/generated'; +import { resetUserInfo, userInfoStore } from '@modules/auth/UserInfoStore'; const User = () => { - const { reload } = useRouter(); + const router = useRouter(); + const mutation = useSignOutMutation(client); + const { user } = useSnapshot(userInfoStore); + + if (!user) { + return ( + + 登录 + + ); + } return ( -
-
- +
+
+
-
+
- -
- 我的订阅 -
- + {/**/} + {/*
*/} + {/* 我的订阅*/} + {/*
*/} + {/**/} -
- 账号设置 +
+ 账号设置
-
- 退出 +
{ + mutation.mutate( + {}, + { + onSuccess: () => { + resetUserInfo(); + router.push('/auth/signin'); + }, + } + ); + }} + > + 退出
diff --git a/src/modules/auth/LoginCard.tsx b/src/modules/auth/LoginCard.tsx index 1af41c03..115bb6b6 100644 --- a/src/modules/auth/LoginCard.tsx +++ b/src/modules/auth/LoginCard.tsx @@ -1,13 +1,24 @@ import React from 'react'; +import { useRouter } from 'next/router'; import { useTranslation } from 'next-i18next'; import classnames from 'classnames'; import Image from 'next/image'; +import client from '@graphql/client'; +import { useSignOutMutation } from '@graphql/generated'; import { setCallbackUrl, setAuthProvider } from '@common/utils/cookie'; const LoginCard: React.FC<{ provider: { id: string; name: string } }> = ({ provider, }) => { + const router = useRouter(); const { t } = useTranslation(); + const mutation = useSignOutMutation(client); + + const redirectTo = React.useMemo(() => { + const rt = router.query.redirect_to; + return typeof rt === 'string' ? rt : '/settings/profile'; + }, [router.query.redirect_to]); + if (provider.id === 'github') { return (
= ({ 'bg-black' )} onClick={async () => { - setAuthProvider('github'); - setCallbackUrl('/submit-your-project'); - window.location.href = '/users/auth/github'; + mutation.mutate( + {}, + { + onSuccess: () => { + setAuthProvider('github'); + setCallbackUrl(redirectTo); + window.location.href = '/users/auth/github'; + }, + } + ); }} >
@@ -44,9 +62,16 @@ const LoginCard: React.FC<{ provider: { id: string; name: string } }> = ({ 'bg-[#d9001a]' )} onClick={async () => { - setAuthProvider('gitee'); - setCallbackUrl('/submit-your-project'); - window.location.href = '/users/auth/gitee'; + mutation.mutate( + {}, + { + onSuccess: () => { + setAuthProvider('gitee'); + setCallbackUrl(redirectTo); + window.location.href = '/users/auth/gitee'; + }, + } + ); }} >
diff --git a/src/modules/auth/UserInfoFetcher.ts b/src/modules/auth/UserInfoFetcher.ts new file mode 100644 index 00000000..76c21eb7 --- /dev/null +++ b/src/modules/auth/UserInfoFetcher.ts @@ -0,0 +1,20 @@ +import React, { PropsWithChildren, useEffect } from 'react'; +import client from '@graphql/client'; +import { useUserinfoQuery } from '@graphql/generated'; +import { serUserLoading, setUserInfo } from './UserInfoStore'; + +const UserInfoFetcher: React.FC = ({ children }) => { + const { data, isLoading } = useUserinfoQuery(client, {}); + + useEffect(() => { + setUserInfo(data); + }, [data]); + + useEffect(() => { + serUserLoading(isLoading); + }, [isLoading]); + + return null; +}; + +export default UserInfoFetcher; diff --git a/src/modules/auth/UserInfoStore.tsx b/src/modules/auth/UserInfoStore.tsx index 6836930d..f988afa8 100644 --- a/src/modules/auth/UserInfoStore.tsx +++ b/src/modules/auth/UserInfoStore.tsx @@ -1,27 +1,12 @@ -import React, { PropsWithChildren, useEffect } from 'react'; -import client from '@graphql/client'; -import { proxy, useSnapshot } from 'valtio'; -import { useUserinfoQuery, UserinfoQuery } from '@graphql/generated'; +import React from 'react'; +import { proxy } from 'valtio'; +import { UserinfoQuery } from '@graphql/generated'; import { getAuthProvider } from '@common/utils/cookie'; type LoginBind = NonNullable< NonNullable['loginBinds'] >[0]; -export const UserInfoFetcher: React.FC = ({ children }) => { - const { data, isLoading } = useUserinfoQuery(client, {}); - - useEffect(() => { - setUserInfo(data); - }, [data]); - - useEffect(() => { - serUserLoading(isLoading); - }, [isLoading]); - - return null; -}; - export const userInfoStore = proxy<{ loading: boolean; user: LoginBind | null; diff --git a/src/modules/auth/useAuthRedirect.ts b/src/modules/auth/useAuthRedirect.ts new file mode 100644 index 00000000..fd4aeec5 --- /dev/null +++ b/src/modules/auth/useAuthRedirect.ts @@ -0,0 +1,15 @@ +import router from 'next/router'; +import { useSnapshot } from 'valtio'; +import { userInfoStore } from './UserInfoStore'; + +const useAuthRedirect = (options?: { redirectTo?: string }) => { + const { user, loading } = useSnapshot(userInfoStore); + if (!loading && !user) { + let redirectTo = options?.redirectTo ?? window.location.pathname; + router.replace( + `/auth/signin?redirect_to=${encodeURIComponent(redirectTo)}` + ); + } +}; + +export default useAuthRedirect; diff --git a/src/modules/submitProject/Misc/Auth.tsx b/src/modules/submitProject/Misc/Auth.tsx index 19d94df6..c46fcfd2 100644 --- a/src/modules/submitProject/Misc/Auth.tsx +++ b/src/modules/submitProject/Misc/Auth.tsx @@ -10,16 +10,10 @@ import { useSignOutMutation } from '@graphql/generated'; const Auth: React.FC = () => { const { t } = useTranslation(); const router = useRouter(); - const { user, loading } = useSnapshot(userInfoStore); + const { user } = useSnapshot(userInfoStore); const hasLoggedIn = Boolean(user); const mutation = useSignOutMutation(client); - useEffect(() => { - if (!loading && !hasLoggedIn) { - router.push('/auth/signin'); - } - }, [loading, hasLoggedIn, router]); - if (!hasLoggedIn) return null; return ( diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 55c54f9d..26f08df3 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -6,7 +6,7 @@ import { appWithTranslation } from 'next-i18next'; import i18nextConfig from 'next-i18next.config.js'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; -import { UserInfoFetcher } from '@modules/auth/UserInfoStore'; +import UserInfoFetcher from '@modules/auth/UserInfoFetcher'; import { useAppGA, GAScripts } from '@common/lib/ga'; import { browserLanguageDetectorAndReload } from '@common/utils/getLocale'; diff --git a/src/pages/auth/signin.tsx b/src/pages/auth/signin.tsx index 19996e7e..9b9e18f5 100644 --- a/src/pages/auth/signin.tsx +++ b/src/pages/auth/signin.tsx @@ -1,8 +1,6 @@ import React from 'react'; import { GetServerSidePropsContext } from 'next'; import { useTranslation } from 'next-i18next'; -import Header from '@common/components/Header'; -import Banner from '@modules/submitProject/Misc/Banner'; import LoginPage from '@modules/auth/LoginPage'; import getLocalesFile from '@common/utils/getLocalesFile'; @@ -19,8 +17,6 @@ const SignIn: React.FC = () => { const { t } = useTranslation(); return ( <> -
- ); diff --git a/src/pages/settings/profile.tsx b/src/pages/settings/profile.tsx index 3584f7f8..d58a614f 100644 --- a/src/pages/settings/profile.tsx +++ b/src/pages/settings/profile.tsx @@ -3,6 +3,7 @@ import { GetServerSideProps } from 'next'; import { useTranslation } from 'react-i18next'; import Header from '@common/components/Header'; import Banner from '@modules/submitProject/Misc/Banner'; +import useAuthRedirect from '@modules/auth/useAuthRedirect'; import ProfileSetting from '@modules/settings/ProfileSetting'; import getLocalesFile from '@common/utils/getLocalesFile'; @@ -16,6 +17,7 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { const Settings = () => { const { t } = useTranslation(); + useAuthRedirect(); return ( <>
diff --git a/src/pages/settings/subscribe.tsx b/src/pages/settings/subscribe.tsx index 46f0cd8a..022260ae 100644 --- a/src/pages/settings/subscribe.tsx +++ b/src/pages/settings/subscribe.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { GetServerSideProps } from 'next'; import { useTranslation } from 'react-i18next'; +import useAuthRedirect from '@modules/auth/useAuthRedirect'; import Header from '@common/components/Header'; import Banner from '@modules/submitProject/Misc/Banner'; import getLocalesFile from '@common/utils/getLocalesFile'; @@ -15,6 +16,7 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { const Subscribe = () => { const { t } = useTranslation(); + useAuthRedirect(); return ( <>
diff --git a/src/pages/submit-your-project/community.tsx b/src/pages/submit-your-project/community.tsx index 691783b6..063869db 100644 --- a/src/pages/submit-your-project/community.tsx +++ b/src/pages/submit-your-project/community.tsx @@ -3,6 +3,7 @@ import Header from '@common/components/Header'; import Banner from '@modules/submitProject/Misc/Banner'; import SubmitProject from '@modules/submitProject'; import FormCommunity from '@modules/submitProject/FormCommunity'; +import useAuthRedirect from '@modules/auth/useAuthRedirect'; import { GetServerSidePropsContext } from 'next'; import getLocalesFile from '@common/utils/getLocalesFile'; import { useTranslation } from 'react-i18next'; @@ -18,6 +19,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { const SubmitYourProject: React.FC = () => { const { t } = useTranslation(); + useAuthRedirect(); return ( <>
diff --git a/src/pages/submit-your-project/index.tsx b/src/pages/submit-your-project/index.tsx index f2c20b22..588043c7 100644 --- a/src/pages/submit-your-project/index.tsx +++ b/src/pages/submit-your-project/index.tsx @@ -1,5 +1,6 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { GetServerSidePropsContext } from 'next'; +import useAuthRedirect from '@modules/auth/useAuthRedirect'; import Header from '@common/components/Header'; import Banner from '@modules/submitProject/Misc/Banner'; import SubmitProject from '@modules/submitProject'; @@ -18,6 +19,8 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { const SubmitYourProject = () => { const { t } = useTranslation(); + useAuthRedirect(); + return ( <>
From 240fdb680303f5395d963e821793d95c9cf4cb23 Mon Sep 17 00:00:00 2001 From: neil Date: Thu, 11 May 2023 18:21:59 +0800 Subject: [PATCH 7/9] feat: login page Signed-off-by: neil --- i18n/en/common.json | 6 +- i18n/zh/common.json | 5 +- src/common/components/Header/User.tsx | 23 ++++--- .../auth/{LoginCard.tsx => LoginItems.tsx} | 69 +++++++------------ src/modules/auth/LoginPage.tsx | 34 --------- src/modules/submitProject/Misc/Auth.tsx | 3 +- src/pages/auth/login/index.tsx | 65 ----------------- src/pages/auth/signin.tsx | 49 +++++++++++-- 8 files changed, 91 insertions(+), 163 deletions(-) rename src/modules/auth/{LoginCard.tsx => LoginItems.tsx} (50%) delete mode 100644 src/modules/auth/LoginPage.tsx delete mode 100644 src/pages/auth/login/index.tsx diff --git a/i18n/en/common.json b/i18n/en/common.json index cbb07ea5..a28b7f1a 100644 --- a/i18n/en/common.json +++ b/i18n/en/common.json @@ -46,5 +46,9 @@ }, "next": "Next", "know_more": "Know more", - "ago": "ago" + "ago": "ago", + "signin": "Signin", + "subscribe": "My Subscribe", + "profile_setting": "Account Settings", + "signout": "SignOut" } diff --git a/i18n/zh/common.json b/i18n/zh/common.json index 7f2b7470..4212ae4a 100644 --- a/i18n/zh/common.json +++ b/i18n/zh/common.json @@ -46,5 +46,8 @@ }, "next": "下一个", "know_more": "了解更多", - "ago": "前" + "ago": "前", + "signin": "登录", + "profile_setting": "账号设置", + "signout": "退出" } diff --git a/src/common/components/Header/User.tsx b/src/common/components/Header/User.tsx index 95398c96..5119a007 100644 --- a/src/common/components/Header/User.tsx +++ b/src/common/components/Header/User.tsx @@ -8,8 +8,10 @@ import client from '@graphql/client'; import { useSnapshot } from 'valtio'; import { useSignOutMutation } from '@graphql/generated'; import { resetUserInfo, userInfoStore } from '@modules/auth/UserInfoStore'; +import { useTranslation } from 'react-i18next'; const User = () => { + const { t } = useTranslation(); const router = useRouter(); const mutation = useSignOutMutation(client); const { user } = useSnapshot(userInfoStore); @@ -17,7 +19,7 @@ const User = () => { if (!user) { return ( - 登录 + {t('common:signin')} ); } @@ -28,22 +30,23 @@ const User = () => {
-
+
{/**/} - {/*
*/} - {/* 我的订阅*/} - {/*
*/} + {/* */} + {/* {t('common:subscribe')}*/} + {/* */} {/**/} -
- 账号设置 -
+ + + {t('common:profile_setting')} +
{ mutation.mutate( {}, @@ -56,7 +59,7 @@ const User = () => { ); }} > - 退出 + {t('common:signout')}
diff --git a/src/modules/auth/LoginCard.tsx b/src/modules/auth/LoginItems.tsx similarity index 50% rename from src/modules/auth/LoginCard.tsx rename to src/modules/auth/LoginItems.tsx index 115bb6b6..4c5a63a8 100644 --- a/src/modules/auth/LoginCard.tsx +++ b/src/modules/auth/LoginItems.tsx @@ -1,15 +1,12 @@ import React from 'react'; import { useRouter } from 'next/router'; -import { useTranslation } from 'next-i18next'; -import classnames from 'classnames'; import Image from 'next/image'; +import { useTranslation } from 'next-i18next'; import client from '@graphql/client'; import { useSignOutMutation } from '@graphql/generated'; import { setCallbackUrl, setAuthProvider } from '@common/utils/cookie'; -const LoginCard: React.FC<{ provider: { id: string; name: string } }> = ({ - provider, -}) => { +const LoginItems: React.FC = () => { const router = useRouter(); const { t } = useTranslation(); const mutation = useSignOutMutation(client); @@ -19,13 +16,10 @@ const LoginCard: React.FC<{ provider: { id: string; name: string } }> = ({ return typeof rt === 'string' ? rt : '/settings/profile'; }, [router.query.redirect_to]); - if (provider.id === 'github') { - return ( + return ( + <>
{ mutation.mutate( {}, @@ -39,28 +33,17 @@ const LoginCard: React.FC<{ provider: { id: string; name: string } }> = ({ ); }} > -
- {'github'} -
-
- {t('submit_project:sign_in_with_github')} -
+ {'github'} + {t('submit_project:continue_with_github')}
- ); - } - if (provider.id === 'gitee') { - return (
{ mutation.mutate( {}, @@ -74,22 +57,16 @@ const LoginCard: React.FC<{ provider: { id: string; name: string } }> = ({ ); }} > -
- {'gitee'} -
-

- {t('submit_project:sign_in_with_gitee')} -

+ {'gitee'} + {t('submit_project:continue_with_gitee')}
- ); - } - - return null; + + ); }; -export default LoginCard; +export default LoginItems; diff --git a/src/modules/auth/LoginPage.tsx b/src/modules/auth/LoginPage.tsx deleted file mode 100644 index 58dde0b9..00000000 --- a/src/modules/auth/LoginPage.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React, { useEffect } from 'react'; -import { useTranslation } from 'next-i18next'; -import { FiAlertCircle } from 'react-icons/fi'; -import { useRouter } from 'next/router'; -import { oauthProvider } from '@common/constant'; -import LoginCard from './LoginCard'; - -const LoginPage: React.FC = () => { - const { t } = useTranslation(); - const router = useRouter(); - const error = router.query.error; - - return ( -
-

- {t('submit_project:please_select_the_platform_where_your_project_is_h')} -

-
-
- {Object.values(oauthProvider).map((provider) => { - return ; - })} -
-
- {error && ( -

- {error} -

- )} -
- ); -}; - -export default LoginPage; diff --git a/src/modules/submitProject/Misc/Auth.tsx b/src/modules/submitProject/Misc/Auth.tsx index c46fcfd2..12fd0a00 100644 --- a/src/modules/submitProject/Misc/Auth.tsx +++ b/src/modules/submitProject/Misc/Auth.tsx @@ -4,7 +4,7 @@ import client from '@graphql/client'; import { useRouter } from 'next/router'; import { useSnapshot } from 'valtio'; import { useTranslation } from 'react-i18next'; -import { userInfoStore } from '@modules/auth/UserInfoStore'; +import { resetUserInfo, userInfoStore } from '@modules/auth/UserInfoStore'; import { useSignOutMutation } from '@graphql/generated'; const Auth: React.FC = () => { @@ -54,6 +54,7 @@ const Auth: React.FC = () => { {}, { onSuccess: () => { + resetUserInfo(); router.push('/auth/signin'); }, } diff --git a/src/pages/auth/login/index.tsx b/src/pages/auth/login/index.tsx deleted file mode 100644 index 9e201889..00000000 --- a/src/pages/auth/login/index.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react'; -import Copyright from '@modules/auth/components/Copyright'; -import LogoHeader from '@modules/auth/components/LogoHeader'; -import Image from 'next/image'; -import { useTranslation } from 'next-i18next'; -import getLocalesFile from '@common/utils/getLocalesFile'; -import { GetServerSidePropsContext } from 'next'; - -export async function getServerSideProps(context: GetServerSidePropsContext) { - const { req } = context; - return { - props: { - ...(await getLocalesFile(req.cookies, ['submit_project'])), - }, - }; -} -const Login: React.FC = () => { - const { t } = useTranslation(); - - return ( -
- -
-

- {t('submit_project:welcome_to')} -

-
- {'github'} - - {t('submit_project:continue_with_github')} - -
-
- {'gitee'} - - {t('submit_project:continue_with_gitee')} - -
-
- {t('submit_project:by_creating_an_account')} - - {t('submit_project:terms_of_use')} - - {t('submit_project:as_well_as')} - - {t('submit_project:non_active_account_processing_specification')} - -
-
- -
- ); -}; - -export default Login; diff --git a/src/pages/auth/signin.tsx b/src/pages/auth/signin.tsx index 9b9e18f5..9b35d12d 100644 --- a/src/pages/auth/signin.tsx +++ b/src/pages/auth/signin.tsx @@ -1,7 +1,12 @@ import React from 'react'; -import { GetServerSidePropsContext } from 'next'; +import { useRouter } from 'next/router'; import { useTranslation } from 'next-i18next'; -import LoginPage from '@modules/auth/LoginPage'; +import Link from 'next/link'; +import { GetServerSidePropsContext } from 'next'; +import { FiAlertCircle } from 'react-icons/fi'; +import Copyright from '@modules/auth/components/Copyright'; +import LogoHeader from '@modules/auth/components/LogoHeader'; +import LoginItems from '@modules/auth/LoginItems'; import getLocalesFile from '@common/utils/getLocalesFile'; export async function getServerSideProps(context: GetServerSidePropsContext) { @@ -15,10 +20,44 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { const SignIn: React.FC = () => { const { t } = useTranslation(); + const router = useRouter(); + const error = router.query.error; + return ( - <> - - +
+ {error && ( +

+ {error} +

+ )} + + +
+

+ {t('submit_project:welcome_to')} +

+ + + +
+ {t('submit_project:by_creating_an_account')} + + + {t('submit_project:terms_of_use')} + + + + {t('submit_project:as_well_as')} + + + + {t('submit_project:non_active_account_processing_specification')} + + +
+
+ +
); }; From 1c6713b3d8d76a52e569c7b25e0b8b4d285afc6a Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 12 May 2023 15:54:49 +0800 Subject: [PATCH 8/9] feat: user profile update Signed-off-by: neil --- i18n/en/common.json | 6 +- i18n/en/setting.json | 23 ++ i18n/en/submit_project.json | 2 +- i18n/zh/common.json | 6 +- i18n/zh/setting.json | 23 ++ i18n/zh/submit_project.json | 2 +- src/common/components/Button.tsx | 1 + src/common/components/Header/User.tsx | 2 +- src/common/components/Input.tsx | 2 - src/graphql/generated.ts | 312 ++++++++++++++++++ src/graphql/mutation.graphql | 25 ++ src/graphql/query.graphql | 6 + src/modules/auth/UserInfoFetcher.ts | 21 +- src/modules/auth/UserInfoStore.tsx | 37 ++- src/modules/auth/components/LogoHeader.tsx | 10 +- src/modules/auth/useAuthRedirect.ts | 4 +- src/modules/settings/OAuthProviders.tsx | 51 --- src/modules/settings/ProfileSetting.tsx | 61 ---- .../settings/profile/DeleteAccount.tsx | 88 +++++ src/modules/settings/profile/OAuthList.tsx | 113 +++++++ src/modules/settings/profile/ProfileForm.tsx | 114 +++++++ src/modules/settings/profile/index.tsx | 17 + .../submitProject/Form/SelectRepoSource.tsx | 2 +- .../submitProject/FormCommunity/index.tsx | 2 +- .../submitProject/FormSingleRepo/index.tsx | 2 +- .../submitProject/Misc/AddSelectPopover.tsx | 2 +- src/modules/submitProject/Misc/Auth.tsx | 2 +- src/modules/submitProject/Misc/FillItem.tsx | 2 +- .../submitProject/Misc/InputUrlField.tsx | 2 +- .../submitProject/RepoSelect/index.tsx | 2 +- src/pages/auth/signin.tsx | 2 +- src/pages/settings/profile.tsx | 7 +- 32 files changed, 802 insertions(+), 149 deletions(-) create mode 100644 i18n/en/setting.json create mode 100644 i18n/zh/setting.json delete mode 100644 src/modules/settings/OAuthProviders.tsx delete mode 100644 src/modules/settings/ProfileSetting.tsx create mode 100644 src/modules/settings/profile/DeleteAccount.tsx create mode 100644 src/modules/settings/profile/OAuthList.tsx create mode 100644 src/modules/settings/profile/ProfileForm.tsx create mode 100644 src/modules/settings/profile/index.tsx diff --git a/i18n/en/common.json b/i18n/en/common.json index a28b7f1a..905e5208 100644 --- a/i18n/en/common.json +++ b/i18n/en/common.json @@ -50,5 +50,9 @@ "signin": "Signin", "subscribe": "My Subscribe", "profile_setting": "Account Settings", - "signout": "SignOut" + "signout": "SignOut", + "btn": { + "save": "Save", + "cancel": "Cancel" + } } diff --git a/i18n/en/setting.json b/i18n/en/setting.json new file mode 100644 index 00000000..3609efa0 --- /dev/null +++ b/i18n/en/setting.json @@ -0,0 +1,23 @@ +{ + "profile": { + "avatar": "Avatar", + "userinfo": "Profile", + "account_settings": "Account Settings", + "form": { + "name": "name", + "name_placeholder": "Please enter your name", + "email": "email", + "email_placeholder": "Please enter your email address", + "error_require": "{{field}} is a required field", + "error_name_max_len": "nickname cannot exceed {{length}} characters", + "error_email_format": "email format is incorrect" + }, + "connected_accounts": "Connected Accounts", + "connected": "Connected", + "disconnect": "Disconnect", + "connect_multiple_accounts_to_your_user_and_sign_in": "Connect multiple accounts to your user and sign in with any of them", + "delete_account": "Delete account", + "delete_account_btn": "Delete account", + "delete_account_warning": " Once you delete your account, there is no going back. Please be certain when taking this action." + } +} diff --git a/i18n/en/submit_project.json b/i18n/en/submit_project.json index 625c0578..ca2eb68b 100644 --- a/i18n/en/submit_project.json +++ b/i18n/en/submit_project.json @@ -48,7 +48,7 @@ "by_creating_an_account": "By creating an account, you agree to the", "as_well_as": "as well as", "terms_of_use": " Terms of Use ", - "non_active_account_processing_specification": " Non-active Account Processing Specification.", + "privacy_policy": " Non-active Account Processing Specification.", "email_verification_successful": "Email verification successful", "the_email_address": "The email address", "has_been_successfully": "has been successfully bound to your OSS Compass account", diff --git a/i18n/zh/common.json b/i18n/zh/common.json index 4212ae4a..d03e90eb 100644 --- a/i18n/zh/common.json +++ b/i18n/zh/common.json @@ -49,5 +49,9 @@ "ago": "前", "signin": "登录", "profile_setting": "账号设置", - "signout": "退出" + "signout": "退出", + "btn": { + "save": "保存", + "cancel": "取消" + } } diff --git a/i18n/zh/setting.json b/i18n/zh/setting.json new file mode 100644 index 00000000..37df55e1 --- /dev/null +++ b/i18n/zh/setting.json @@ -0,0 +1,23 @@ +{ + "profile": { + "avatar": "头像", + "userinfo": "用户信息", + "account_settings": "账号设置", + "form": { + "name": "昵称", + "name_placeholder": "请输入昵称", + "email": "邮箱", + "email_placeholder": "请输入邮箱地址", + "error_require": "{{field}}不能为空", + "error_name_max_len": "昵称长度不能超过{{length}}", + "error_email_format": "邮件格式不正确" + }, + "connected_accounts": "第三方绑定", + "connected": "绑定", + "disconnect": "解绑", + "connect_multiple_accounts_to_your_user_and_sign_in": "绑定第三方账号,即可使用任何一个账户进行登录。", + "delete_account": "删除账号", + "delete_account_btn": "删除我的账号", + "delete_account_warning": "删除账户后,就无法进行撤销。在执行此操作时,请确保您已经仔细考虑过。" + } +} diff --git a/i18n/zh/submit_project.json b/i18n/zh/submit_project.json index 54811528..54e6f2c6 100644 --- a/i18n/zh/submit_project.json +++ b/i18n/zh/submit_project.json @@ -48,7 +48,7 @@ "by_creating_an_account": "要创建一个帐户,您同意", "as_well_as": "以及", "terms_of_use": "使用条款", - "non_active_account_processing_specification": "非活跃帐户处理规范。", + "privacy_policy": "隐私协议", "email_verification_successful": "邮件验证成功", "the_email_address": "已成功将邮箱地址", "has_been_successfully": "与您的 OSS Compass 帐户绑定", diff --git a/src/common/components/Button.tsx b/src/common/components/Button.tsx index b0af23f2..af61f0d0 100644 --- a/src/common/components/Button.tsx +++ b/src/common/components/Button.tsx @@ -21,6 +21,7 @@ const buttonVariants = cva( secondary: 'border border-black text-black font-bold hover:bg-gray-100', danger: 'border-2 border-[#CC0000] text-[#CC0000] font-bold hover:bg-red-600/5', + text: '', }, size: { lg: 'text-base px-10 py-3', diff --git a/src/common/components/Header/User.tsx b/src/common/components/Header/User.tsx index 5119a007..8595b776 100644 --- a/src/common/components/Header/User.tsx +++ b/src/common/components/Header/User.tsx @@ -14,7 +14,7 @@ const User = () => { const { t } = useTranslation(); const router = useRouter(); const mutation = useSignOutMutation(client); - const { user } = useSnapshot(userInfoStore); + const { providerUser: user } = useSnapshot(userInfoStore); if (!user) { return ( diff --git a/src/common/components/Input.tsx b/src/common/components/Input.tsx index 839bbdd5..50626e56 100644 --- a/src/common/components/Input.tsx +++ b/src/common/components/Input.tsx @@ -8,7 +8,6 @@ const Input = forwardRef< value?: string; onChange?: (e: ChangeEvent) => void; onBlur?: (e: FocusEvent) => void; - defaultValue?: string; className?: string; placeholder?: string; error?: boolean; @@ -18,7 +17,6 @@ const Input = forwardRef< const { name, className, - defaultValue, value, onChange, onBlur, diff --git a/src/graphql/generated.ts b/src/graphql/generated.ts index 3de1fd67..d153b41e 100644 --- a/src/graphql/generated.ts +++ b/src/graphql/generated.ts @@ -480,6 +480,27 @@ export type MetricStat = { median?: Maybe; }; +/** Autogenerated input type of ModifyUser */ +export type ModifyUserInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** user email */ + email: Scalars['String']; + /** user name */ + name: Scalars['String']; +}; + +/** Autogenerated return type of ModifyUser */ +export type ModifyUserPayload = { + __typename?: 'ModifyUserPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** Errors encountered during execution of the mutation. */ + errors?: Maybe>; + message?: Maybe; + status: Scalars['String']; +}; + export type Mutation = { __typename?: 'Mutation'; /** Submit a community analysis task */ @@ -488,8 +509,14 @@ export type Mutation = { createRepoTask?: Maybe; /** Destroy user */ destroyUser?: Maybe; + /** Modify user */ + modifyUser?: Maybe; + /** Send email verify */ + sendEmailVerify?: Maybe; /** Sign out */ signOut?: Maybe; + /** User unbind */ + userUnbind?: Maybe; }; export type MutationCreateProjectTaskArgs = { @@ -500,6 +527,18 @@ export type MutationCreateRepoTaskArgs = { input: CreateRepoTaskInput; }; +export type MutationModifyUserArgs = { + input: ModifyUserInput; +}; + +export type MutationSendEmailVerifyArgs = { + input: SendEmailVerifyInput; +}; + +export type MutationUserUnbindArgs = { + input: UserUnbindInput; +}; + export type ProjectCompletionRow = { __typename?: 'ProjectCompletionRow'; /** metric model object identification */ @@ -682,6 +721,23 @@ export type Repo = { watchersCount?: Maybe; }; +/** Autogenerated input type of SendEmailVerify */ +export type SendEmailVerifyInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; +}; + +/** Autogenerated return type of SendEmailVerify */ +export type SendEmailVerifyPayload = { + __typename?: 'SendEmailVerifyPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** Errors encountered during execution of the mutation. */ + errors?: Maybe>; + message?: Maybe; + status: Scalars['String']; +}; + export type StarterProjectHealthMetric = { __typename?: 'StarterProjectHealthMetric'; /** the smallest number of people that make 50% of contributions */ @@ -740,7 +796,30 @@ export type Trending = { export type User = { __typename?: 'User'; + email: Scalars['String']; + emailVerified: Scalars['Boolean']; + id: Scalars['Int']; loginBinds?: Maybe>; + name: Scalars['String']; +}; + +/** Autogenerated input type of UserUnbind */ +export type UserUnbindInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** provider name */ + provider: Scalars['String']; +}; + +/** Autogenerated return type of UserUnbind */ +export type UserUnbindPayload = { + __typename?: 'UserUnbindPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** Errors encountered during execution of the mutation. */ + errors?: Maybe>; + message?: Maybe; + status: Scalars['String']; }; export type CreateRepoTaskMutationVariables = Exact<{ @@ -776,6 +855,44 @@ export type CreateProjectTaskMutation = { } | null; }; +export type ModifyUserMutationVariables = Exact<{ + name: Scalars['String']; + email: Scalars['String']; +}>; + +export type ModifyUserMutation = { + __typename?: 'Mutation'; + modifyUser?: { + __typename?: 'ModifyUserPayload'; + message?: string | null; + status: string; + } | null; +}; + +export type UserUnbindMutationVariables = Exact<{ + provider: Scalars['String']; +}>; + +export type UserUnbindMutation = { + __typename?: 'Mutation'; + userUnbind?: { + __typename?: 'UserUnbindPayload'; + message?: string | null; + status: string; + } | null; +}; + +export type SendEmailVerifyMutationVariables = Exact<{ [key: string]: never }>; + +export type SendEmailVerifyMutation = { + __typename?: 'Mutation'; + sendEmailVerify?: { + __typename?: 'SendEmailVerifyPayload'; + message?: string | null; + status: string; + } | null; +}; + export type SignOutMutationVariables = Exact<{ [key: string]: never }>; export type SignOutMutation = { @@ -783,12 +900,23 @@ export type SignOutMutation = { signOut?: boolean | null; }; +export type DeleteUserMutationVariables = Exact<{ [key: string]: never }>; + +export type DeleteUserMutation = { + __typename?: 'Mutation'; + destroyUser?: boolean | null; +}; + export type UserinfoQueryVariables = Exact<{ [key: string]: never }>; export type UserinfoQuery = { __typename?: 'Query'; currentUser?: { __typename?: 'User'; + id: number; + name: string; + email: string; + emailVerified: boolean; loginBinds?: Array<{ __typename?: 'LoginBind'; account?: string | null; @@ -1510,6 +1638,144 @@ useCreateProjectTaskMutation.fetcher = ( variables, headers ); +export const ModifyUserDocument = /*#__PURE__*/ ` + mutation modifyUser($name: String!, $email: String!) { + modifyUser(input: {name: $name, email: $email}) { + message + status + } +} + `; +export const useModifyUserMutation = ( + client: GraphQLClient, + options?: UseMutationOptions< + ModifyUserMutation, + TError, + ModifyUserMutationVariables, + TContext + >, + headers?: RequestInit['headers'] +) => + useMutation< + ModifyUserMutation, + TError, + ModifyUserMutationVariables, + TContext + >( + ['modifyUser'], + (variables?: ModifyUserMutationVariables) => + fetcher( + client, + ModifyUserDocument, + variables, + headers + )(), + options + ); +useModifyUserMutation.fetcher = ( + client: GraphQLClient, + variables: ModifyUserMutationVariables, + headers?: RequestInit['headers'] +) => + fetcher( + client, + ModifyUserDocument, + variables, + headers + ); +export const UserUnbindDocument = /*#__PURE__*/ ` + mutation userUnbind($provider: String!) { + userUnbind(input: {provider: $provider}) { + message + status + } +} + `; +export const useUserUnbindMutation = ( + client: GraphQLClient, + options?: UseMutationOptions< + UserUnbindMutation, + TError, + UserUnbindMutationVariables, + TContext + >, + headers?: RequestInit['headers'] +) => + useMutation< + UserUnbindMutation, + TError, + UserUnbindMutationVariables, + TContext + >( + ['userUnbind'], + (variables?: UserUnbindMutationVariables) => + fetcher( + client, + UserUnbindDocument, + variables, + headers + )(), + options + ); +useUserUnbindMutation.fetcher = ( + client: GraphQLClient, + variables: UserUnbindMutationVariables, + headers?: RequestInit['headers'] +) => + fetcher( + client, + UserUnbindDocument, + variables, + headers + ); +export const SendEmailVerifyDocument = /*#__PURE__*/ ` + mutation sendEmailVerify { + sendEmailVerify(input: {}) { + message + status + } +} + `; +export const useSendEmailVerifyMutation = < + TError = unknown, + TContext = unknown +>( + client: GraphQLClient, + options?: UseMutationOptions< + SendEmailVerifyMutation, + TError, + SendEmailVerifyMutationVariables, + TContext + >, + headers?: RequestInit['headers'] +) => + useMutation< + SendEmailVerifyMutation, + TError, + SendEmailVerifyMutationVariables, + TContext + >( + ['sendEmailVerify'], + (variables?: SendEmailVerifyMutationVariables) => + fetcher( + client, + SendEmailVerifyDocument, + variables, + headers + )(), + options + ); +useSendEmailVerifyMutation.fetcher = ( + client: GraphQLClient, + variables?: SendEmailVerifyMutationVariables, + headers?: RequestInit['headers'] +) => + fetcher( + client, + SendEmailVerifyDocument, + variables, + headers + ); export const SignOutDocument = /*#__PURE__*/ ` mutation signOut { signOut @@ -1547,9 +1813,55 @@ useSignOutMutation.fetcher = ( variables, headers ); +export const DeleteUserDocument = /*#__PURE__*/ ` + mutation deleteUser { + destroyUser +} + `; +export const useDeleteUserMutation = ( + client: GraphQLClient, + options?: UseMutationOptions< + DeleteUserMutation, + TError, + DeleteUserMutationVariables, + TContext + >, + headers?: RequestInit['headers'] +) => + useMutation< + DeleteUserMutation, + TError, + DeleteUserMutationVariables, + TContext + >( + ['deleteUser'], + (variables?: DeleteUserMutationVariables) => + fetcher( + client, + DeleteUserDocument, + variables, + headers + )(), + options + ); +useDeleteUserMutation.fetcher = ( + client: GraphQLClient, + variables?: DeleteUserMutationVariables, + headers?: RequestInit['headers'] +) => + fetcher( + client, + DeleteUserDocument, + variables, + headers + ); export const UserinfoDocument = /*#__PURE__*/ ` query userinfo { currentUser { + id + name + email + emailVerified loginBinds { account avatarUrl diff --git a/src/graphql/mutation.graphql b/src/graphql/mutation.graphql index 48f23177..d36e020b 100644 --- a/src/graphql/mutation.graphql +++ b/src/graphql/mutation.graphql @@ -26,6 +26,31 @@ mutation createProjectTask( } } +mutation modifyUser($name: String!, $email: String!) { + modifyUser(input: { name: $name, email: $email }) { + message + status + } +} + +mutation userUnbind($provider: String!) { + userUnbind(input: { provider: $provider }) { + message + status + } +} + +mutation sendEmailVerify { + sendEmailVerify(input: {}) { + message + status + } +} + mutation signOut { signOut } + +mutation deleteUser { + destroyUser +} diff --git a/src/graphql/query.graphql b/src/graphql/query.graphql index d73f7a75..9a1b34f3 100644 --- a/src/graphql/query.graphql +++ b/src/graphql/query.graphql @@ -1,5 +1,9 @@ query userinfo { currentUser { + id + name + email + emailVerified loginBinds { account avatarUrl @@ -373,6 +377,7 @@ query summary($start: ISO8601DateTime, $end: ISO8601DateTime) { # } # } } + query labMetric( $label: String! $level: String = "repo" @@ -404,6 +409,7 @@ query labMetric( type } } + fragment metricStat on MetricStat { mean median diff --git a/src/modules/auth/UserInfoFetcher.ts b/src/modules/auth/UserInfoFetcher.ts index 76c21eb7..f4acb5e0 100644 --- a/src/modules/auth/UserInfoFetcher.ts +++ b/src/modules/auth/UserInfoFetcher.ts @@ -1,10 +1,27 @@ import React, { PropsWithChildren, useEffect } from 'react'; import client from '@graphql/client'; +import { useEventEmitter } from 'ahooks'; import { useUserinfoQuery } from '@graphql/generated'; -import { serUserLoading, setUserInfo } from './UserInfoStore'; +import { + serUserLoading, + setUserInfo, + userInfoStore, + UserEventType, + userEvent, +} from './UserInfoStore'; const UserInfoFetcher: React.FC = ({ children }) => { - const { data, isLoading } = useUserinfoQuery(client, {}); + const { data, isLoading, refetch } = useUserinfoQuery(client, {}); + + const event$ = useEventEmitter(); + event$.useSubscription((e) => { + if (e === userEvent.REFRESH) { + refetch(); + } + }); + useEffect(() => { + userInfoStore.event$ = event$; + }, [event$]); useEffect(() => { setUserInfo(data); diff --git a/src/modules/auth/UserInfoStore.tsx b/src/modules/auth/UserInfoStore.tsx index f988afa8..7876fb02 100644 --- a/src/modules/auth/UserInfoStore.tsx +++ b/src/modules/auth/UserInfoStore.tsx @@ -1,43 +1,54 @@ import React from 'react'; -import { proxy } from 'valtio'; +import { proxy, ref } from 'valtio'; import { UserinfoQuery } from '@graphql/generated'; import { getAuthProvider } from '@common/utils/cookie'; +import { EventEmitter } from 'ahooks/lib/useEventEmitter'; type LoginBind = NonNullable< NonNullable['loginBinds'] >[0]; +export const userEvent = { + REFRESH: 'refresh' as const, +}; + +export type UserEventType = typeof userEvent[keyof typeof userEvent]; + export const userInfoStore = proxy<{ loading: boolean; - user: LoginBind | null; + providerUser: LoginBind | null; currentUser: UserinfoQuery['currentUser'] | null; + event$: EventEmitter | null; }>({ loading: true, - user: null, + providerUser: null, currentUser: null, + event$: null, }); export const setUserInfo = (res?: UserinfoQuery) => { - let user; + let providerUser; + const provider = getAuthProvider(); if (provider) { - user = res?.currentUser?.loginBinds?.find( + providerUser = res?.currentUser?.loginBinds?.find( (bindInfo) => bindInfo.provider === provider ); } else { - user = res?.currentUser?.loginBinds?.[0]; + providerUser = res?.currentUser?.loginBinds?.[0]; } - if (user) { - user = { - ...user, + if (providerUser) { + providerUser = { + ...providerUser, // todo Let the backend modify // The naming of the returned fields in the interface data is reversed. - account: user?.nickname, - nickname: user?.account, + account: providerUser?.nickname, + nickname: providerUser?.account, }; - userInfoStore.user = user; } + + userInfoStore.providerUser = providerUser || null; userInfoStore.currentUser = res?.currentUser || null; }; @@ -47,6 +58,6 @@ export const serUserLoading = (loading: boolean) => { export const resetUserInfo = () => { userInfoStore.loading = false; - userInfoStore.user = null; + userInfoStore.providerUser = null; userInfoStore.currentUser = null; }; diff --git a/src/modules/auth/components/LogoHeader.tsx b/src/modules/auth/components/LogoHeader.tsx index e1ba8641..bb0742b6 100644 --- a/src/modules/auth/components/LogoHeader.tsx +++ b/src/modules/auth/components/LogoHeader.tsx @@ -1,10 +1,18 @@ import React from 'react'; +import router from 'next/router'; import Logo from '@public/images/logos/compass-logo.svg'; const LogoHeader = () => { return (
- +
{ + router.push('/'); + }} + > + +
); }; diff --git a/src/modules/auth/useAuthRedirect.ts b/src/modules/auth/useAuthRedirect.ts index fd4aeec5..759fa8c2 100644 --- a/src/modules/auth/useAuthRedirect.ts +++ b/src/modules/auth/useAuthRedirect.ts @@ -3,8 +3,8 @@ import { useSnapshot } from 'valtio'; import { userInfoStore } from './UserInfoStore'; const useAuthRedirect = (options?: { redirectTo?: string }) => { - const { user, loading } = useSnapshot(userInfoStore); - if (!loading && !user) { + const { currentUser, loading } = useSnapshot(userInfoStore); + if (!loading && !currentUser) { let redirectTo = options?.redirectTo ?? window.location.pathname; router.replace( `/auth/signin?redirect_to=${encodeURIComponent(redirectTo)}` diff --git a/src/modules/settings/OAuthProviders.tsx b/src/modules/settings/OAuthProviders.tsx deleted file mode 100644 index 6e34cd47..00000000 --- a/src/modules/settings/OAuthProviders.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; -import classnames from 'classnames'; -import { SiGitee, SiGithub } from 'react-icons/si'; -import Button from '@common/components/Button'; - -const providers = [ - { - name: 'GitHub', - desc: 'Can be used to submit project after binding', - icon: , - }, - { - name: 'Gitee', - desc: 'Can be used to submit project after binding', - icon: , - }, -]; - -const OAuthList = () => { - return ( -
-
Connected Accounts
-
- Connect multiple accounts to your user and sign in with any of them -
-
- {providers.map((provider) => { - return ( -
-
{provider.icon}
-
-
{provider.name}
-
{provider.desc}
-
-
- -
-
- ); - })} -
-
- ); -}; - -export default OAuthList; diff --git a/src/modules/settings/ProfileSetting.tsx b/src/modules/settings/ProfileSetting.tsx deleted file mode 100644 index 17c68971..00000000 --- a/src/modules/settings/ProfileSetting.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react'; -import Image from 'next/image'; -import Center from '@common/components/Layout/Center'; -import Input from '@common/components/Input'; -import Button from '@common/components/Button'; -import OAuthProviders from './OAuthProviders'; - -const ProfileForm = () => { - return ( - <> -

Profile

-
-
-
-
Name
- -
- -
-
Email
- -
- - -
- -
-
Avatar
-
- avatar -
-
-
- - ); -}; - -const DeleteAccount = () => { - return ( - <> -
Delete account
-
- Once you delete your account, there is no going back. Please be certain - when taking this action. -
- - - ); -}; - -const ProfileSetting = () => { - return ( -
- - - -
- ); -}; - -export default ProfileSetting; diff --git a/src/modules/settings/profile/DeleteAccount.tsx b/src/modules/settings/profile/DeleteAccount.tsx new file mode 100644 index 00000000..efbe19b0 --- /dev/null +++ b/src/modules/settings/profile/DeleteAccount.tsx @@ -0,0 +1,88 @@ +import React, { useEffect } from 'react'; +import router from 'next/router'; +import client from '@graphql/client'; +import { useTranslation } from 'next-i18next'; +import { useDeleteUserMutation } from '@graphql/generated'; +import Button from '@common/components/Button'; +import { userInfoStore, userEvent } from '@modules/auth/UserInfoStore'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; + +const DeleteAccount = () => { + const { t } = useTranslation(); + const mutation = useDeleteUserMutation(client); + const [open, setOpen] = React.useState(false); + + const handleClose = () => { + setOpen(false); + }; + + return ( + <> +
+ {t('setting:profile.delete_account')} +
+
{t('setting:profile.delete_account_warning')}
+ + + + + {t('setting:profile.delete_account')} + + + + {t('setting:profile.delete_account_warning')} + + + + + + + + + ); +}; + +export default DeleteAccount; diff --git a/src/modules/settings/profile/OAuthList.tsx b/src/modules/settings/profile/OAuthList.tsx new file mode 100644 index 00000000..6269da33 --- /dev/null +++ b/src/modules/settings/profile/OAuthList.tsx @@ -0,0 +1,113 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useSnapshot } from 'valtio'; +import { SiGitee, SiGithub } from 'react-icons/si'; +import Button from '@common/components/Button'; +import { userInfoStore, userEvent } from '@modules/auth/UserInfoStore'; +import client from '@graphql/client'; +import { UserinfoQuery, useUserUnbindMutation } from '@graphql/generated'; +import { setCallbackUrl } from '@common/utils/cookie'; + +const findBindInfo = ( + provider: string, + currentUser: DeepReadonly +) => { + if (currentUser?.loginBinds) { + return currentUser?.loginBinds.find((i) => { + return i.provider === provider; + }); + } + return null; +}; + +const OAuthList = () => { + const { t } = useTranslation(); + const mutation = useUserUnbindMutation(client); + const { currentUser } = useSnapshot(userInfoStore); + + const providers = [ + { + name: 'GitHub', + id: 'github', + desc: 'Can be used to submit project after binding', + icon: , + }, + { + name: 'Gitee', + id: 'gitee', + desc: 'Can be used to submit project after binding', + icon: , + }, + ]; + + return ( +
+
+ {t('setting:profile.connected_accounts')} +
+
+ {t( + 'setting:profile.connect_multiple_accounts_to_your_user_and_sign_in' + )} +
+
+ {providers.map((provider) => { + const bindInfo = findBindInfo(provider.id, currentUser); + return ( +
+
{provider.icon}
+
+
{provider.name}
+ {bindInfo ? ( +
+ {bindInfo.nickname} +
+ ) : ( +
{provider.desc}
+ )} +
+
+ {bindInfo ? ( + + ) : ( + + )} +
+
+ ); + })} +
+
+ ); +}; + +export default OAuthList; diff --git a/src/modules/settings/profile/ProfileForm.tsx b/src/modules/settings/profile/ProfileForm.tsx new file mode 100644 index 00000000..a97de712 --- /dev/null +++ b/src/modules/settings/profile/ProfileForm.tsx @@ -0,0 +1,114 @@ +import React, { useEffect } from 'react'; +import Image from 'next/image'; +import { useSnapshot } from 'valtio'; +import { useTranslation } from 'react-i18next'; +import { useForm, SubmitHandler } from 'react-hook-form'; +import { userInfoStore } from '@modules/auth/UserInfoStore'; +import Input from '@common/components/Input'; +import Button from '@common/components/Button'; + +interface IFormInput { + name: string; + email: string; +} + +const ProfileForm = () => { + const { t } = useTranslation(); + const { currentUser, providerUser } = useSnapshot(userInfoStore); + const name = currentUser?.name; + const email = currentUser?.email; + + const { + setValue, + register, + handleSubmit, + formState: { errors }, + } = useForm(); + const onSubmit: SubmitHandler = (data) => { + console.log(data); + }; + + useEffect(() => { + if (name) setValue('name', name); + if (email) setValue('email', email); + }, [name, email, setValue]); + + return ( +
+

+ {t('setting:profile.userinfo')} +

+
+
+
+
+ {t('setting:profile.form.name')} +
+ + {errors['name']?.message && ( +

{errors['name'].message}

+ )} +
+ +
+
+ {t('setting:profile.form.email')} +
+ + {errors['email']?.message && ( +

{errors['email'].message}

+ )} +
+ + +
+ +
+
{t('setting:profile.avatar')}
+
+ {providerUser?.avatarUrl ? ( + avatar + ) : null} +
+
+
+
+ ); +}; + +export default ProfileForm; diff --git a/src/modules/settings/profile/index.tsx b/src/modules/settings/profile/index.tsx new file mode 100644 index 00000000..d1363315 --- /dev/null +++ b/src/modules/settings/profile/index.tsx @@ -0,0 +1,17 @@ +import React, { useEffect } from 'react'; +import Center from '@common/components/Layout/Center'; +import ProfileForm from './ProfileForm'; +import OAuthList from './OAuthList'; +import DeleteAccount from './DeleteAccount'; + +const ProfileSetting = () => { + return ( +
+ + + +
+ ); +}; + +export default ProfileSetting; diff --git a/src/modules/submitProject/Form/SelectRepoSource.tsx b/src/modules/submitProject/Form/SelectRepoSource.tsx index 2fe0290f..fd7cfba8 100644 --- a/src/modules/submitProject/Form/SelectRepoSource.tsx +++ b/src/modules/submitProject/Form/SelectRepoSource.tsx @@ -46,7 +46,7 @@ const SelectRepoSource: React.FC< const [open, setOpen] = useState(false); const ref = useRef(null); - const { user } = useSnapshot(userInfoStore); + const { providerUser: user } = useSnapshot(userInfoStore); const nickname = user?.nickname!; const account = user?.account!; const provider = user?.provider!; diff --git a/src/modules/submitProject/FormCommunity/index.tsx b/src/modules/submitProject/FormCommunity/index.tsx index 9ea127bd..e1969af0 100644 --- a/src/modules/submitProject/FormCommunity/index.tsx +++ b/src/modules/submitProject/FormCommunity/index.tsx @@ -16,7 +16,7 @@ import { useTranslation } from 'react-i18next'; const FormCommunity = () => { const { t } = useTranslation(); - const { user } = useSnapshot(userInfoStore); + const { providerUser: user } = useSnapshot(userInfoStore); const account = user!.account; const provider = user!.provider; diff --git a/src/modules/submitProject/FormSingleRepo/index.tsx b/src/modules/submitProject/FormSingleRepo/index.tsx index 36afb4f1..8b66c213 100644 --- a/src/modules/submitProject/FormSingleRepo/index.tsx +++ b/src/modules/submitProject/FormSingleRepo/index.tsx @@ -17,7 +17,7 @@ import { getUrlReg } from '../Misc'; const FormSingleRepo = () => { const { t } = useTranslation(); - const { user } = useSnapshot(userInfoStore); + const { providerUser: user } = useSnapshot(userInfoStore); const provider = user?.provider || 'github'; const [formType, setFormType] = useState<'select' | 'input'>('input'); diff --git a/src/modules/submitProject/Misc/AddSelectPopover.tsx b/src/modules/submitProject/Misc/AddSelectPopover.tsx index f76fc684..50d36634 100644 --- a/src/modules/submitProject/Misc/AddSelectPopover.tsx +++ b/src/modules/submitProject/Misc/AddSelectPopover.tsx @@ -26,7 +26,7 @@ const AddSelectPopover: React.FC<{ onClick: (e: React.MouseEvent) => void; }> = ({ className, onSelect, onClick, open, onClose }) => { const { t } = useTranslation(); - const { user } = useSnapshot(userInfoStore); + const { providerUser: user } = useSnapshot(userInfoStore); const provider = user?.provider!; const ref = useRef(null); diff --git a/src/modules/submitProject/Misc/Auth.tsx b/src/modules/submitProject/Misc/Auth.tsx index 12fd0a00..abd70606 100644 --- a/src/modules/submitProject/Misc/Auth.tsx +++ b/src/modules/submitProject/Misc/Auth.tsx @@ -10,7 +10,7 @@ import { useSignOutMutation } from '@graphql/generated'; const Auth: React.FC = () => { const { t } = useTranslation(); const router = useRouter(); - const { user } = useSnapshot(userInfoStore); + const { providerUser: user } = useSnapshot(userInfoStore); const hasLoggedIn = Boolean(user); const mutation = useSignOutMutation(client); diff --git a/src/modules/submitProject/Misc/FillItem.tsx b/src/modules/submitProject/Misc/FillItem.tsx index f70629b0..d8518b76 100644 --- a/src/modules/submitProject/Misc/FillItem.tsx +++ b/src/modules/submitProject/Misc/FillItem.tsx @@ -19,7 +19,7 @@ const FillItem: React.FC<{ url: string; onDelete: (v: string) => void }> = ({ url, onDelete, }) => { - const { user } = useSnapshot(userInfoStore); + const { providerUser: user } = useSnapshot(userInfoStore); const provider = user?.provider!; return ( diff --git a/src/modules/submitProject/Misc/InputUrlField.tsx b/src/modules/submitProject/Misc/InputUrlField.tsx index fffe824e..cf363193 100644 --- a/src/modules/submitProject/Misc/InputUrlField.tsx +++ b/src/modules/submitProject/Misc/InputUrlField.tsx @@ -27,7 +27,7 @@ const InputUrlField = forwardRef( ({ onClose, onPressEnter }, ref) => { const { t } = useTranslation(); const inputRef = useRef(null); - const { user } = useSnapshot(userInfoStore); + const { providerUser: user } = useSnapshot(userInfoStore); const provider = user?.provider!; const [value, setValue] = useState(''); diff --git a/src/modules/submitProject/RepoSelect/index.tsx b/src/modules/submitProject/RepoSelect/index.tsx index aee92cf5..b466f879 100644 --- a/src/modules/submitProject/RepoSelect/index.tsx +++ b/src/modules/submitProject/RepoSelect/index.tsx @@ -21,7 +21,7 @@ const RepoSelect: React.FC<{ onConfirm: (val: string) => void }> = ({ onConfirm, }) => { const { t } = useTranslation(); - const { user } = useSnapshot(userInfoStore); + const { providerUser: user } = useSnapshot(userInfoStore); const nickname = user?.nickname!; const account = user?.account!; const provider = user?.provider!; diff --git a/src/pages/auth/signin.tsx b/src/pages/auth/signin.tsx index 9b35d12d..d97f51c5 100644 --- a/src/pages/auth/signin.tsx +++ b/src/pages/auth/signin.tsx @@ -51,7 +51,7 @@ const SignIn: React.FC = () => { - {t('submit_project:non_active_account_processing_specification')} + {t('submit_project:privacy_policy')}
diff --git a/src/pages/settings/profile.tsx b/src/pages/settings/profile.tsx index d58a614f..91e7cafb 100644 --- a/src/pages/settings/profile.tsx +++ b/src/pages/settings/profile.tsx @@ -4,13 +4,13 @@ import { useTranslation } from 'react-i18next'; import Header from '@common/components/Header'; import Banner from '@modules/submitProject/Misc/Banner'; import useAuthRedirect from '@modules/auth/useAuthRedirect'; -import ProfileSetting from '@modules/settings/ProfileSetting'; +import ProfileSetting from '@modules/settings/profile'; import getLocalesFile from '@common/utils/getLocalesFile'; export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { return { props: { - ...(await getLocalesFile(req.cookies, [])), + ...(await getLocalesFile(req.cookies, ['setting'])), }, }; }; @@ -18,10 +18,11 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { const Settings = () => { const { t } = useTranslation(); useAuthRedirect(); + return ( <>
- + ); From 612f885b89e7c31d2d8abeedaf904e51010323df Mon Sep 17 00:00:00 2001 From: neil Date: Mon, 15 May 2023 10:51:15 +0800 Subject: [PATCH 9/9] feat: userinfo loading Signed-off-by: neil --- i18n/en/common.json | 3 +- i18n/en/setting.json | 7 +- i18n/en/submit_project.json | 3 +- i18n/zh/common.json | 3 +- i18n/zh/setting.json | 7 +- package.json | 1 + src/common/components/Button.tsx | 36 +++++--- src/common/components/Header/User.tsx | 6 +- src/modules/auth/AuthRequire.tsx | 58 ++++++++++++ src/modules/auth/UserInfoStore.tsx | 30 ------ src/modules/auth/useAuthRedirect.ts | 15 --- src/modules/auth/useProviderInfo.tsx | 64 +++++++++++++ .../settings/profile/DeleteAccount.tsx | 2 +- src/modules/settings/profile/OAuthList.tsx | 57 +++++++----- src/modules/settings/profile/ProfileForm.tsx | 91 ++++++++++++++----- src/modules/settings/subscribe/index.tsx | 36 ++++++++ .../submitProject/Form/SelectRepoSource.tsx | 6 +- .../submitProject/FormCommunity/index.tsx | 5 +- .../submitProject/FormSingleRepo/index.tsx | 5 +- .../submitProject/Misc/AddSelectPopover.tsx | 5 +- src/modules/submitProject/Misc/Auth.tsx | 39 +++----- .../submitProject/Misc/Banner/index.tsx | 12 +-- src/modules/submitProject/Misc/FillItem.tsx | 5 +- .../submitProject/Misc/InputUrlField.tsx | 5 +- .../submitProject/RepoSelect/index.tsx | 5 +- src/pages/_app.tsx | 12 +++ src/pages/auth/signin.tsx | 3 +- src/pages/settings/profile.tsx | 26 +++++- src/pages/settings/subscribe.tsx | 13 ++- src/pages/submit-your-project/community.tsx | 11 ++- src/pages/submit-your-project/index.tsx | 11 ++- yarn.lock | 12 +++ 32 files changed, 410 insertions(+), 184 deletions(-) create mode 100644 src/modules/auth/AuthRequire.tsx delete mode 100644 src/modules/auth/useAuthRedirect.ts create mode 100644 src/modules/auth/useProviderInfo.tsx create mode 100644 src/modules/settings/subscribe/index.tsx diff --git a/i18n/en/common.json b/i18n/en/common.json index 905e5208..cafebcc0 100644 --- a/i18n/en/common.json +++ b/i18n/en/common.json @@ -53,6 +53,7 @@ "signout": "SignOut", "btn": { "save": "Save", - "cancel": "Cancel" + "cancel": "Cancel", + "func_disabled": "The feature is temporarily disabled" } } diff --git a/i18n/en/setting.json b/i18n/en/setting.json index 3609efa0..7f1190f7 100644 --- a/i18n/en/setting.json +++ b/i18n/en/setting.json @@ -7,6 +7,7 @@ "name": "name", "name_placeholder": "Please enter your name", "email": "email", + "email_ques_icon": "The email is used to receive project update notifications.", "email_placeholder": "Please enter your email address", "error_require": "{{field}} is a required field", "error_name_max_len": "nickname cannot exceed {{length}} characters", @@ -18,6 +19,10 @@ "connect_multiple_accounts_to_your_user_and_sign_in": "Connect multiple accounts to your user and sign in with any of them", "delete_account": "Delete account", "delete_account_btn": "Delete account", - "delete_account_warning": " Once you delete your account, there is no going back. Please be certain when taking this action." + "delete_account_warning": " Once you delete your account, there is no going back. Please be certain when taking this action.", + "can_be_used_to_submit_project_after_binding": "Can be used to submit project after binding", + "verified": "Verified", + "unverified_yet": "Unverified yet,", + "resend_verification_email": "resend verification email" } } diff --git a/i18n/en/submit_project.json b/i18n/en/submit_project.json index ca2eb68b..b3fb7e81 100644 --- a/i18n/en/submit_project.json +++ b/i18n/en/submit_project.json @@ -6,7 +6,8 @@ "submit_your_project": "Submit your project", "single_repository": "Single repository", "your_project_hosting_on": "Your project hosting on", - "logout": "Logout", + "switch_gitee": "switch to Gitee account", + "switch_github": "switch to GitHub account", "select_your_own_repository_on": "Select your own repository on {{providerName}}", "type_the_address_of_any_repository": "Type the address of any repository", "pick_your_own_repository_on": "Pick your own repository on {{providerName}}", diff --git a/i18n/zh/common.json b/i18n/zh/common.json index d03e90eb..8efc30a4 100644 --- a/i18n/zh/common.json +++ b/i18n/zh/common.json @@ -52,6 +52,7 @@ "signout": "退出", "btn": { "save": "保存", - "cancel": "取消" + "cancel": "取消", + "func_disabled": "该功能临时关闭" } } diff --git a/i18n/zh/setting.json b/i18n/zh/setting.json index 37df55e1..23ac6e01 100644 --- a/i18n/zh/setting.json +++ b/i18n/zh/setting.json @@ -7,6 +7,7 @@ "name": "昵称", "name_placeholder": "请输入昵称", "email": "邮箱", + "email_ques_icon": "该电子邮件用于接收项目更新通知", "email_placeholder": "请输入邮箱地址", "error_require": "{{field}}不能为空", "error_name_max_len": "昵称长度不能超过{{length}}", @@ -18,6 +19,10 @@ "connect_multiple_accounts_to_your_user_and_sign_in": "绑定第三方账号,即可使用任何一个账户进行登录。", "delete_account": "删除账号", "delete_account_btn": "删除我的账号", - "delete_account_warning": "删除账户后,就无法进行撤销。在执行此操作时,请确保您已经仔细考虑过。" + "delete_account_warning": "删除账户后,就无法进行撤销。在执行此操作时,请确保您已经仔细考虑过。", + "can_be_used_to_submit_project_after_binding": "绑定后可用于提交项目", + "verified": "已验证", + "unverified_yet": "未验证,", + "resend_verification_email": "发送验证邮件" } } diff --git a/package.json b/package.json index 84380497..1b009cb1 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "react-dom": "18.2.0", "react-error-boundary": "^3.1.4", "react-hook-form": "^7.36.0", + "react-hot-toast": "^2.4.1", "react-hotkeys-hook": "^3.4.7", "react-i18next": "^12.0.0", "react-icons": "^4.4.0", diff --git a/src/common/components/Button.tsx b/src/common/components/Button.tsx index af61f0d0..9615bd90 100644 --- a/src/common/components/Button.tsx +++ b/src/common/components/Button.tsx @@ -1,4 +1,4 @@ -import React, { PropsWithChildren } from 'react'; +import React, { ReactNode, forwardRef } from 'react'; import classnames from 'classnames'; import { cva, type VariantProps } from 'class-variance-authority'; import { twMerge } from 'tailwind-merge'; @@ -38,17 +38,22 @@ const buttonVariants = cva( interface ButtonVariants extends VariantProps {} -const Button: React.FC> = ({ - children, - disabled = false, - loading = false, - type = 'button', - intent, - size, - className, - onClick, - ...props -}) => { +const Button = forwardRef< + HTMLButtonElement, + ButtonProps & ButtonVariants & { children?: ReactNode | undefined } +>((props, ref) => { + const { + children, + disabled = false, + loading = false, + type = 'button', + intent, + size, + className, + onClick, + ...restProps + } = props; + const cls = classnames( buttonVariants({ intent, size }), { 'opacity-50 cursor-not-allowed hover:opacity-50': disabled }, @@ -57,6 +62,7 @@ const Button: React.FC> = ({ return ( ); -}; +}); + +Button.displayName = 'Button'; export default Button; diff --git a/src/common/components/Header/User.tsx b/src/common/components/Header/User.tsx index 8595b776..8a86a4fe 100644 --- a/src/common/components/Header/User.tsx +++ b/src/common/components/Header/User.tsx @@ -5,16 +5,16 @@ import Image from 'next/image'; import { AiOutlineUser } from 'react-icons/ai'; import { MdOutlineLogout } from 'react-icons/md'; import client from '@graphql/client'; -import { useSnapshot } from 'valtio'; import { useSignOutMutation } from '@graphql/generated'; -import { resetUserInfo, userInfoStore } from '@modules/auth/UserInfoStore'; +import { resetUserInfo } from '@modules/auth/UserInfoStore'; import { useTranslation } from 'react-i18next'; +import useProviderInfo from '@modules/auth/useProviderInfo'; const User = () => { const { t } = useTranslation(); const router = useRouter(); const mutation = useSignOutMutation(client); - const { providerUser: user } = useSnapshot(userInfoStore); + const { providerUser: user } = useProviderInfo(); if (!user) { return ( diff --git a/src/modules/auth/AuthRequire.tsx b/src/modules/auth/AuthRequire.tsx new file mode 100644 index 00000000..7b546737 --- /dev/null +++ b/src/modules/auth/AuthRequire.tsx @@ -0,0 +1,58 @@ +import React, { PropsWithChildren } from 'react'; +import router from 'next/router'; +import { useSnapshot } from 'valtio'; +import { userInfoStore } from './UserInfoStore'; + +interface Props { + className?: string; + loadingUi?: React.ReactNode; + redirectTo?: string; +} + +const AuthRequire: React.FC> = ({ + children, + className, + loadingUi, + redirectTo, +}) => { + const { currentUser, loading } = useSnapshot(userInfoStore); + + if (!loading && !currentUser) { + let redirectUrl = redirectTo ?? window.location.pathname; + router.replace( + `/auth/signin?redirect_to=${encodeURIComponent(redirectUrl)}` + ); + } + + if (loading && loadingUi) { + return <>{loadingUi}; + } + + if (loading) { + return ( +
+
+
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+
+
+ ); + } + + return <>{children}; +}; + +export default AuthRequire; diff --git a/src/modules/auth/UserInfoStore.tsx b/src/modules/auth/UserInfoStore.tsx index 7876fb02..16c09ceb 100644 --- a/src/modules/auth/UserInfoStore.tsx +++ b/src/modules/auth/UserInfoStore.tsx @@ -1,13 +1,8 @@ import React from 'react'; import { proxy, ref } from 'valtio'; import { UserinfoQuery } from '@graphql/generated'; -import { getAuthProvider } from '@common/utils/cookie'; import { EventEmitter } from 'ahooks/lib/useEventEmitter'; -type LoginBind = NonNullable< - NonNullable['loginBinds'] ->[0]; - export const userEvent = { REFRESH: 'refresh' as const, }; @@ -16,39 +11,15 @@ export type UserEventType = typeof userEvent[keyof typeof userEvent]; export const userInfoStore = proxy<{ loading: boolean; - providerUser: LoginBind | null; currentUser: UserinfoQuery['currentUser'] | null; event$: EventEmitter | null; }>({ loading: true, - providerUser: null, currentUser: null, event$: null, }); export const setUserInfo = (res?: UserinfoQuery) => { - let providerUser; - - const provider = getAuthProvider(); - if (provider) { - providerUser = res?.currentUser?.loginBinds?.find( - (bindInfo) => bindInfo.provider === provider - ); - } else { - providerUser = res?.currentUser?.loginBinds?.[0]; - } - - if (providerUser) { - providerUser = { - ...providerUser, - // todo Let the backend modify - // The naming of the returned fields in the interface data is reversed. - account: providerUser?.nickname, - nickname: providerUser?.account, - }; - } - - userInfoStore.providerUser = providerUser || null; userInfoStore.currentUser = res?.currentUser || null; }; @@ -58,6 +29,5 @@ export const serUserLoading = (loading: boolean) => { export const resetUserInfo = () => { userInfoStore.loading = false; - userInfoStore.providerUser = null; userInfoStore.currentUser = null; }; diff --git a/src/modules/auth/useAuthRedirect.ts b/src/modules/auth/useAuthRedirect.ts deleted file mode 100644 index 759fa8c2..00000000 --- a/src/modules/auth/useAuthRedirect.ts +++ /dev/null @@ -1,15 +0,0 @@ -import router from 'next/router'; -import { useSnapshot } from 'valtio'; -import { userInfoStore } from './UserInfoStore'; - -const useAuthRedirect = (options?: { redirectTo?: string }) => { - const { currentUser, loading } = useSnapshot(userInfoStore); - if (!loading && !currentUser) { - let redirectTo = options?.redirectTo ?? window.location.pathname; - router.replace( - `/auth/signin?redirect_to=${encodeURIComponent(redirectTo)}` - ); - } -}; - -export default useAuthRedirect; diff --git a/src/modules/auth/useProviderInfo.tsx b/src/modules/auth/useProviderInfo.tsx new file mode 100644 index 00000000..4b16cc7e --- /dev/null +++ b/src/modules/auth/useProviderInfo.tsx @@ -0,0 +1,64 @@ +import React, { useEffect } from 'react'; +import { useSnapshot } from 'valtio'; +import { useToggle } from 'ahooks'; +import { UserinfoQuery } from '@graphql/generated'; +import { getAuthProvider, setAuthProvider } from '@common/utils/cookie'; +import { userInfoStore } from '@modules/auth/UserInfoStore'; +import { ReadonlyDeep } from 'type-fest'; + +type LoginBinds = ReadonlyDeep< + NonNullable['loginBinds'] +>; + +function findSpecifyProvider({ + loginBinds, + provider, +}: { + loginBinds?: LoginBinds; + provider?: string; +}) { + let providerUser; + + if (provider && loginBinds && loginBinds.length > 1) { + providerUser = loginBinds?.find( + (bindInfo) => bindInfo.provider === provider + ); + } else { + providerUser = loginBinds?.[0]; + } + + if (providerUser) { + providerUser = { + ...providerUser, + // todo Let the backend modify + // The naming of the returned fields in the interface data is reversed. + account: providerUser?.nickname, + nickname: providerUser?.account, + }; + } + + return providerUser || null; +} + +const toggleProviders = ['github', 'gitee']; +const getAnother = (p?: string) => toggleProviders.filter((i) => i !== p)[0]; + +const useProviderInfo = () => { + const { currentUser: user } = useSnapshot(userInfoStore); + + const login = getAuthProvider() || 'github'; + const [provider, { toggle }] = useToggle(login, getAnother(login)); + + useEffect(() => { + setAuthProvider(provider); + }, [provider]); + + const showUser = findSpecifyProvider({ + provider: provider, + loginBinds: user?.loginBinds, + }); + + return { providerUser: showUser, loginBinds: user?.loginBinds, toggle }; +}; + +export default useProviderInfo; diff --git a/src/modules/settings/profile/DeleteAccount.tsx b/src/modules/settings/profile/DeleteAccount.tsx index efbe19b0..7e03bdea 100644 --- a/src/modules/settings/profile/DeleteAccount.tsx +++ b/src/modules/settings/profile/DeleteAccount.tsx @@ -53,7 +53,7 @@ const DeleteAccount = () => { {t('setting:profile.delete_account_warning')} - + + ); +}; + +const OAuthList = () => { + const { t } = useTranslation(); const { currentUser } = useSnapshot(userInfoStore); const providers = [ { name: 'GitHub', id: 'github', - desc: 'Can be used to submit project after binding', + desc: t('setting:profile.can_be_used_to_submit_project_after_binding'), icon: , }, { name: 'Gitee', id: 'gitee', - desc: 'Can be used to submit project after binding', + desc: t('setting:profile.can_be_used_to_submit_project_after_binding'), icon: , }, ]; @@ -71,24 +103,7 @@ const OAuthList = () => {
{bindInfo ? ( - + ) : (
-
- {t('setting:profile.form.email')} +
+
+ {t('setting:profile.form.email')} + {t('setting:profile.form.email_ques_icon')}} + placement="right" + > + + + + +
+ + {currentUser?.emailVerified ? null : ( +
+ {t('setting:profile.unverified_yet')} +
{}} + > + {t('setting:profile.resend_verification_email')} +
+
+ )} +
+
+ + + {currentUser?.emailVerified ? ( + inputEmail === email ? ( +
+ {t('setting:profile.verified')} +
+ ) : null + ) : null}
- {errors['email']?.message && (

{errors['email'].message}

)}
- + {t('common:btn.func_disabled')}} + placement="right" + > + +
diff --git a/src/modules/settings/subscribe/index.tsx b/src/modules/settings/subscribe/index.tsx new file mode 100644 index 00000000..b72259c1 --- /dev/null +++ b/src/modules/settings/subscribe/index.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { SiGitee, SiGithub } from 'react-icons/si'; +import Center from '@common/components/Layout/Center'; +import Button from '@common/components/Button'; + +const Subscribe = () => { + return ( +
+
+
My subscriptions
+
+ +
+
+ +
+
+
+ +
+
+
+
+
+ Updated on 2023-04-28 +
+
+
Unsubscribe
+
+
+ ); +}; + +export default Subscribe; diff --git a/src/modules/submitProject/Form/SelectRepoSource.tsx b/src/modules/submitProject/Form/SelectRepoSource.tsx index fd7cfba8..d22b58dc 100644 --- a/src/modules/submitProject/Form/SelectRepoSource.tsx +++ b/src/modules/submitProject/Form/SelectRepoSource.tsx @@ -4,9 +4,8 @@ import Image from 'next/image'; import { AiFillCaretDown, AiOutlinePlus } from 'react-icons/ai'; import { useClickAway, useSessionStorage } from 'react-use'; import { useQuery } from '@tanstack/react-query'; -import { useSnapshot } from 'valtio'; -import { userInfoStore } from '@modules/auth/UserInfoStore'; import { getOrganizations } from '@modules/submitProject/api'; +import useProviderInfo from '@modules/auth/useProviderInfo'; const SourceItem: React.FC<{ className?: string; @@ -45,8 +44,7 @@ const SelectRepoSource: React.FC< > = ({ className, onChange, value }) => { const [open, setOpen] = useState(false); const ref = useRef(null); - - const { providerUser: user } = useSnapshot(userInfoStore); + const { providerUser: user } = useProviderInfo(); const nickname = user?.nickname!; const account = user?.account!; const provider = user?.provider!; diff --git a/src/modules/submitProject/FormCommunity/index.tsx b/src/modules/submitProject/FormCommunity/index.tsx index e1969af0..16acb271 100644 --- a/src/modules/submitProject/FormCommunity/index.tsx +++ b/src/modules/submitProject/FormCommunity/index.tsx @@ -2,7 +2,6 @@ import React, { useRef, useState } from 'react'; import { useCreateProjectTaskMutation } from '@graphql/generated'; import client from '@graphql/client'; import uniq from 'lodash/uniq'; -import { useSnapshot } from 'valtio'; import { useSessionStorage } from 'react-use'; import Select from '@common/components/Select'; import Button from '@common/components/Button'; @@ -10,13 +9,13 @@ import SwitchToSingleRepo from './SwitchToSingleRepo'; import SoftwareArtifactRepository from './SoftwareArtifactRepository'; import GovernanceRepository from './GovernanceRepository'; import { fillHttps, getRepoName } from '@common/utils'; -import { userInfoStore } from '@modules/auth/UserInfoStore'; +import useProviderInfo from '@modules/auth/useProviderInfo'; import Message from '@modules/submitProject/Misc/Message'; import { useTranslation } from 'react-i18next'; const FormCommunity = () => { const { t } = useTranslation(); - const { providerUser: user } = useSnapshot(userInfoStore); + const { providerUser: user } = useProviderInfo(); const account = user!.account; const provider = user!.provider; diff --git a/src/modules/submitProject/FormSingleRepo/index.tsx b/src/modules/submitProject/FormSingleRepo/index.tsx index 8b66c213..a29d8795 100644 --- a/src/modules/submitProject/FormSingleRepo/index.tsx +++ b/src/modules/submitProject/FormSingleRepo/index.tsx @@ -8,8 +8,7 @@ import SelectLike from '@common/components/SelectLike'; import Input from '@common/components/Input'; import Button from '@common/components/Button'; import Message from '@modules/submitProject/Misc/Message'; -import { useSnapshot } from 'valtio'; -import { userInfoStore } from '@modules/auth/UserInfoStore'; +import useProviderInfo from '@modules/auth/useProviderInfo'; import { fillHttps } from '@common/utils'; import SwitchToCommunity from './SwitchToCommunity'; import RepoSelect from '../RepoSelect'; @@ -17,7 +16,7 @@ import { getUrlReg } from '../Misc'; const FormSingleRepo = () => { const { t } = useTranslation(); - const { providerUser: user } = useSnapshot(userInfoStore); + const { providerUser: user } = useProviderInfo(); const provider = user?.provider || 'github'; const [formType, setFormType] = useState<'select' | 'input'>('input'); diff --git a/src/modules/submitProject/Misc/AddSelectPopover.tsx b/src/modules/submitProject/Misc/AddSelectPopover.tsx index 50d36634..67356c07 100644 --- a/src/modules/submitProject/Misc/AddSelectPopover.tsx +++ b/src/modules/submitProject/Misc/AddSelectPopover.tsx @@ -4,8 +4,7 @@ import { AiFillGithub, AiOutlineLink, AiOutlinePlus } from 'react-icons/ai'; import classnames from 'classnames'; import { SiGitee } from 'react-icons/si'; import { useTranslation } from 'react-i18next'; -import { useSnapshot } from 'valtio'; -import { userInfoStore } from '@modules/auth/UserInfoStore'; +import useProviderInfo from '@modules/auth/useProviderInfo'; export const getIcons = (type: string) => { switch (type) { @@ -26,7 +25,7 @@ const AddSelectPopover: React.FC<{ onClick: (e: React.MouseEvent) => void; }> = ({ className, onSelect, onClick, open, onClose }) => { const { t } = useTranslation(); - const { providerUser: user } = useSnapshot(userInfoStore); + const { providerUser: user } = useProviderInfo(); const provider = user?.provider!; const ref = useRef(null); diff --git a/src/modules/submitProject/Misc/Auth.tsx b/src/modules/submitProject/Misc/Auth.tsx index abd70606..129c9ba6 100644 --- a/src/modules/submitProject/Misc/Auth.tsx +++ b/src/modules/submitProject/Misc/Auth.tsx @@ -1,18 +1,13 @@ import React, { useEffect } from 'react'; import Image from 'next/image'; -import client from '@graphql/client'; -import { useRouter } from 'next/router'; -import { useSnapshot } from 'valtio'; import { useTranslation } from 'react-i18next'; -import { resetUserInfo, userInfoStore } from '@modules/auth/UserInfoStore'; -import { useSignOutMutation } from '@graphql/generated'; +import useProviderInfo from '@modules/auth/useProviderInfo'; const Auth: React.FC = () => { const { t } = useTranslation(); - const router = useRouter(); - const { providerUser: user } = useSnapshot(userInfoStore); + const { providerUser: user, loginBinds, toggle } = useProviderInfo(); const hasLoggedIn = Boolean(user); - const mutation = useSignOutMutation(client); + const bindLen = loginBinds?.length; if (!hasLoggedIn) return null; @@ -46,23 +41,17 @@ const Auth: React.FC = () => {
-
- +
+ {bindLen && bindLen > 1 ? ( + + ) : null}
diff --git a/src/modules/submitProject/Misc/Banner/index.tsx b/src/modules/submitProject/Misc/Banner/index.tsx index 6d3b2908..c7ba0bf9 100644 --- a/src/modules/submitProject/Misc/Banner/index.tsx +++ b/src/modules/submitProject/Misc/Banner/index.tsx @@ -10,12 +10,12 @@ const Banner: React.FC<{ content: string }> = ({ content }) => { style.headerBgLine )} > - {/**/} +
{content}
diff --git a/src/modules/submitProject/Misc/FillItem.tsx b/src/modules/submitProject/Misc/FillItem.tsx index d8518b76..0f1053da 100644 --- a/src/modules/submitProject/Misc/FillItem.tsx +++ b/src/modules/submitProject/Misc/FillItem.tsx @@ -1,8 +1,7 @@ import React from 'react'; import { AiFillGithub, AiOutlineClose } from 'react-icons/ai'; import { SiGitee } from 'react-icons/si'; -import { useSnapshot } from 'valtio'; -import { userInfoStore } from '@modules/auth/UserInfoStore'; +import useProviderInfo from '@modules/auth/useProviderInfo'; export const getIcons = (type: string) => { switch (type) { @@ -19,7 +18,7 @@ const FillItem: React.FC<{ url: string; onDelete: (v: string) => void }> = ({ url, onDelete, }) => { - const { providerUser: user } = useSnapshot(userInfoStore); + const { providerUser: user } = useProviderInfo(); const provider = user?.provider!; return ( diff --git a/src/modules/submitProject/Misc/InputUrlField.tsx b/src/modules/submitProject/Misc/InputUrlField.tsx index cf363193..4e46e812 100644 --- a/src/modules/submitProject/Misc/InputUrlField.tsx +++ b/src/modules/submitProject/Misc/InputUrlField.tsx @@ -9,8 +9,7 @@ import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; import Input from '@common/components/Input'; import { AiOutlineClose } from 'react-icons/ai'; -import { useSnapshot } from 'valtio'; -import { userInfoStore } from '@modules/auth/UserInfoStore'; +import useProviderInfo from '@modules/auth/useProviderInfo'; import { getUrlReg } from '../Misc'; interface Props { @@ -27,7 +26,7 @@ const InputUrlField = forwardRef( ({ onClose, onPressEnter }, ref) => { const { t } = useTranslation(); const inputRef = useRef(null); - const { providerUser: user } = useSnapshot(userInfoStore); + const { providerUser: user } = useProviderInfo(); const provider = user?.provider!; const [value, setValue] = useState(''); diff --git a/src/modules/submitProject/RepoSelect/index.tsx b/src/modules/submitProject/RepoSelect/index.tsx index b466f879..ea1881be 100644 --- a/src/modules/submitProject/RepoSelect/index.tsx +++ b/src/modules/submitProject/RepoSelect/index.tsx @@ -10,8 +10,7 @@ import { useDebounce } from 'ahooks'; import Input from '@common/components/Input'; import { CgSpinner } from 'react-icons/cg'; import SelectRepoSource from '@modules/submitProject/Form/SelectRepoSource'; -import { userInfoStore } from '@modules/auth/UserInfoStore'; -import { useSnapshot } from 'valtio'; +import useProviderInfo from '@modules/auth/useProviderInfo'; import RepoItem from './RepoItem'; import Loading from './Loading'; import { useTranslation } from 'react-i18next'; @@ -21,7 +20,7 @@ const RepoSelect: React.FC<{ onConfirm: (val: string) => void }> = ({ onConfirm, }) => { const { t } = useTranslation(); - const { providerUser: user } = useSnapshot(userInfoStore); + const { providerUser: user } = useProviderInfo(); const nickname = user?.nickname!; const account = user?.account!; const provider = user?.provider!; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 26f08df3..202dc191 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -3,6 +3,7 @@ import Head from 'next/head'; import dynamic from 'next/dynamic'; import App, { AppContext, AppProps } from 'next/app'; import { appWithTranslation } from 'next-i18next'; +import { Toaster } from 'react-hot-toast'; import i18nextConfig from 'next-i18next.config.js'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; @@ -70,6 +71,17 @@ function MyApp({ color="#3A5BEF" options={{ showSpinner: false }} /> + diff --git a/src/pages/auth/signin.tsx b/src/pages/auth/signin.tsx index d97f51c5..a6d620e4 100644 --- a/src/pages/auth/signin.tsx +++ b/src/pages/auth/signin.tsx @@ -25,13 +25,14 @@ const SignIn: React.FC = () => { return (
+ + {error && (

{error}

)} -

{t('submit_project:welcome_to')} diff --git a/src/pages/settings/profile.tsx b/src/pages/settings/profile.tsx index 91e7cafb..14ed74aa 100644 --- a/src/pages/settings/profile.tsx +++ b/src/pages/settings/profile.tsx @@ -1,9 +1,11 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { GetServerSideProps } from 'next'; import { useTranslation } from 'react-i18next'; +import { useRouter } from 'next/router'; +import { toast } from 'react-hot-toast'; import Header from '@common/components/Header'; import Banner from '@modules/submitProject/Misc/Banner'; -import useAuthRedirect from '@modules/auth/useAuthRedirect'; +import AuthRequire from '@modules/auth/AuthRequire'; import ProfileSetting from '@modules/settings/profile'; import getLocalesFile from '@common/utils/getLocalesFile'; @@ -16,14 +18,30 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { }; const Settings = () => { + const { query } = useRouter(); const { t } = useTranslation(); - useAuthRedirect(); + + useEffect(() => { + let t: number; + if (query.error) { + t = window.setTimeout(() => { + toast.error((t) => <>{query.error}, { + position: 'top-center', + }); + }, 400); + } + return () => { + t && clearTimeout(t); + }; + }, [query.error]); return ( <>
- + + + ); }; diff --git a/src/pages/settings/subscribe.tsx b/src/pages/settings/subscribe.tsx index 022260ae..83a7ec1e 100644 --- a/src/pages/settings/subscribe.tsx +++ b/src/pages/settings/subscribe.tsx @@ -1,9 +1,10 @@ import React from 'react'; import { GetServerSideProps } from 'next'; import { useTranslation } from 'react-i18next'; -import useAuthRedirect from '@modules/auth/useAuthRedirect'; +import AuthRequire from '@modules/auth/AuthRequire'; import Header from '@common/components/Header'; import Banner from '@modules/submitProject/Misc/Banner'; +import Subscribe from '@modules/settings/subscribe'; import getLocalesFile from '@common/utils/getLocalesFile'; export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { @@ -14,15 +15,17 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { }; }; -const Subscribe = () => { +const SubscribePage = () => { const { t } = useTranslation(); - useAuthRedirect(); return ( <>
- + + + + ); }; -export default Subscribe; +export default SubscribePage; diff --git a/src/pages/submit-your-project/community.tsx b/src/pages/submit-your-project/community.tsx index 063869db..0fb2804f 100644 --- a/src/pages/submit-your-project/community.tsx +++ b/src/pages/submit-your-project/community.tsx @@ -3,7 +3,7 @@ import Header from '@common/components/Header'; import Banner from '@modules/submitProject/Misc/Banner'; import SubmitProject from '@modules/submitProject'; import FormCommunity from '@modules/submitProject/FormCommunity'; -import useAuthRedirect from '@modules/auth/useAuthRedirect'; +import AuthRequire from '@modules/auth/AuthRequire'; import { GetServerSidePropsContext } from 'next'; import getLocalesFile from '@common/utils/getLocalesFile'; import { useTranslation } from 'react-i18next'; @@ -19,14 +19,15 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { const SubmitYourProject: React.FC = () => { const { t } = useTranslation(); - useAuthRedirect(); return ( <>
- - - + + + + + ); }; diff --git a/src/pages/submit-your-project/index.tsx b/src/pages/submit-your-project/index.tsx index 588043c7..dfcc5306 100644 --- a/src/pages/submit-your-project/index.tsx +++ b/src/pages/submit-your-project/index.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from 'react'; import { GetServerSidePropsContext } from 'next'; -import useAuthRedirect from '@modules/auth/useAuthRedirect'; +import AuthRequire from '@modules/auth/AuthRequire'; import Header from '@common/components/Header'; import Banner from '@modules/submitProject/Misc/Banner'; import SubmitProject from '@modules/submitProject'; @@ -19,15 +19,16 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { const SubmitYourProject = () => { const { t } = useTranslation(); - useAuthRedirect(); return ( <>
- - - + + + + + ); }; diff --git a/yarn.lock b/yarn.lock index e78c1060..bc3eb926 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8700,6 +8700,11 @@ globby@^11.0.1, globby@^11.0.2, globby@^11.0.3, globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" +goober@^2.1.10: + version "2.1.13" + resolved "https://registry.npmmirror.com/goober/-/goober-2.1.13.tgz#e3c06d5578486212a76c9eba860cbc3232ff6d7c" + integrity sha512-jFj3BQeleOoy7t93E9rZ2de+ScC4lQICLwiAQmKMg9F6roKGaLSHoCDYKkWlSafg138jejvq/mTdvmnwDQgqoQ== + gopd@^1.0.1: version "1.0.1" resolved "https://registry.npmmirror.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" @@ -12357,6 +12362,13 @@ react-hook-form@^7.36.0: resolved "https://registry.npmmirror.com/react-hook-form/-/react-hook-form-7.38.0.tgz" integrity sha512-gxWW1kMeru9xR1GoR+Iw4hA+JBOM3SHfr4DWCUKY0xc7Vv1MLsF109oHtBeWl9shcyPFx67KHru44DheN0XY5A== +react-hot-toast@^2.4.1: + version "2.4.1" + resolved "https://registry.npmmirror.com/react-hot-toast/-/react-hot-toast-2.4.1.tgz#df04295eda8a7b12c4f968e54a61c8d36f4c0994" + integrity sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ== + dependencies: + goober "^2.1.10" + react-hotkeys-hook@^3.4.7: version "3.4.7" resolved "https://registry.npmmirror.com/react-hotkeys-hook/-/react-hotkeys-hook-3.4.7.tgz"