From 343547bfe48470c5739457b3cd19c55ca202e414 Mon Sep 17 00:00:00 2001 From: MarioBatalha Date: Mon, 31 Mar 2025 01:37:40 +0100 Subject: [PATCH 01/12] =?UTF-8?q?=E2=9C=A8=20feat:=20sign=20in=20call=20en?= =?UTF-8?q?dpoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- constants/global.ts | 2 + views/sign-in/sign-in-button.tsx | 133 ++++++++++++++++++------------- 2 files changed, 78 insertions(+), 57 deletions(-) create mode 100644 constants/global.ts diff --git a/constants/global.ts b/constants/global.ts new file mode 100644 index 0000000..267d2fb --- /dev/null +++ b/constants/global.ts @@ -0,0 +1,2 @@ +export const BASE_URL = + 'https://apiauthmemezfun-staging.up.railway.app/api/v1/auth/'; diff --git a/views/sign-in/sign-in-button.tsx b/views/sign-in/sign-in-button.tsx index 1a0219c..f383248 100644 --- a/views/sign-in/sign-in-button.tsx +++ b/views/sign-in/sign-in-button.tsx @@ -1,18 +1,22 @@ import { Button, Div } from '@stylin.js/elements'; +import { useRouter } from 'next/router'; import { FC } from 'react'; import { useFormContext } from 'react-hook-form'; import DialogCountdown from '@/components/dialog/dialog-countdown'; import { LoaderSVG } from '@/components/svg'; import { useConnectModal } from '@/components/wallet-button/wallet-button.hook'; +import { RoutesEnum } from '@/constants'; +import { BASE_URL } from '@/constants/global'; import { useDialog } from '@/hooks/use-dialog'; import { SignInFormProps } from './sign-in.types'; const SignInButton: FC = () => { - const { setValue, trigger } = useFormContext(); + const { setValue, trigger, getValues } = useFormContext(); const { dialog, handleClose } = useDialog(); const handleOpenConnectModal = useConnectModal(); + const { push } = useRouter(); const handleCreateProfile = () => { return new Promise((resolve, reject) => { @@ -34,65 +38,80 @@ const SignInButton: FC = () => { fieldsToValidate = ['username', 'password']; const isValid = await trigger(fieldsToValidate); - if (isValid) { - await dialog.promise(handleCreateProfile(), { - success: () => ({ - title: 'Sign-in successful', - button: ( - - ), - ghostButton: ( - - ), - message: 'Now connect your wallet do see all the function', - }), - loading: () => ({ - Icon: , - title: 'Signing...', - message: 'Please wait...', - }), - error: (e) => ({ - title: 'Oops! You can not sign in!', - button: { label: 'Try again', onClick: handleClose }, - message: ( - e.message || - 'Make sure you fill every field correctly and try again.' - ).replace('Invariant failed: ', ''), - ghostButton: { - label: 'Do not want to try again!', - onClick: handleClose, - }, + try { + fetch(`${BASE_URL}/sign-in`, { + method: 'POST', + body: JSON.stringify({ + username: `${getValues('username')}`, + password: `${getValues('password')}`, }), + }).then((res) => { + if (res.ok) { + if (isValid) { + dialog.promise(handleCreateProfile(), { + success: () => ({ + title: 'Sign-in successful', + button: ( + + ), + ghostButton: ( + + ), + message: 'Now connect your wallet do see all the function', + }), + loading: () => ({ + Icon: , + title: 'Signing...', + message: 'Please wait...', + }), + error: (e) => ({ + title: 'Oops! You can not sign in!', + button: { label: 'Try again', onClick: handleClose }, + message: ( + e.message || + 'Make sure you fill every field correctly and try again.' + ).replace('Invariant failed: ', ''), + ghostButton: { + label: 'Do not want to try again!', + onClick: handleClose, + }, + }), + }); + } + push(RoutesEnum.Profile); + } + if (!isValid) return; }); + } catch (error) { + console.error('Error _> ', error); } - if (!isValid) return; }; return ( From 9b29747ab5582d63ce654f4b94e20bbc9eb18733 Mon Sep 17 00:00:00 2001 From: MarioBatalha Date: Mon, 31 Mar 2025 20:48:00 +0100 Subject: [PATCH 02/12] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20sign=20i?= =?UTF-8?q?n=20call=20endpoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 3 +- components/profile/menu-list.tsx | 13 +- constants/global.ts | 2 +- views/sign-in/sign-in-button.tsx | 190 ++++++++++++++------------- views/sign-in/sign-in-validations.ts | 2 +- 5 files changed, 117 insertions(+), 93 deletions(-) diff --git a/.env b/.env index 572274e..7498483 100644 --- a/.env +++ b/.env @@ -1 +1,2 @@ -NEXT_PUBLIC_NETWORK=testnet \ No newline at end of file +NEXT_PUBLIC_NETWORK=testnet +BASE_URL=https://apiauthmemezfun-staging.up.railway.app/api/v1/auth \ No newline at end of file diff --git a/components/profile/menu-list.tsx b/components/profile/menu-list.tsx index 7798888..51b0492 100644 --- a/components/profile/menu-list.tsx +++ b/components/profile/menu-list.tsx @@ -1,8 +1,12 @@ import { useDisconnectWallet } from '@mysten/dapp-kit'; import { Div } from '@stylin.js/elements'; +import { useRouter } from 'next/router'; import { not } from 'ramda'; import { FC, useState } from 'react'; +import { RoutesEnum } from '@/constants'; +import { BASE_URL } from '@/constants/global'; + import { DocIDSVG, ExplorerSVG, @@ -23,6 +27,13 @@ const MenuList: FC = () => { const [isAccountMenuOpen, setIsAccountMenuOpen] = useState(false); const [isExplorerMenuOpen, setIsExplorerMenuOpen] = useState(false); const { mutate: disconnectWallet } = useDisconnectWallet(); + const { push } = useRouter(); + + const handleDisconnectWallet = () => { + disconnectWallet(); + fetch(`${BASE_URL}/sign-out`).then((res) => res.json()); + push(RoutesEnum.Home); + }; return (
@@ -58,7 +69,7 @@ const MenuList: FC = () => { Icon={LogoutSVG} title="Disconnect" color="#E85965" - onClick={() => disconnectWallet()} + onClick={handleDisconnectWallet} />
); diff --git a/constants/global.ts b/constants/global.ts index 267d2fb..09eca03 100644 --- a/constants/global.ts +++ b/constants/global.ts @@ -1,2 +1,2 @@ export const BASE_URL = - 'https://apiauthmemezfun-staging.up.railway.app/api/v1/auth/'; + 'https://apiauthmemezfun-staging.up.railway.app/api/v1/auth'; diff --git a/views/sign-in/sign-in-button.tsx b/views/sign-in/sign-in-button.tsx index f383248..7bf93c7 100644 --- a/views/sign-in/sign-in-button.tsx +++ b/views/sign-in/sign-in-button.tsx @@ -1,6 +1,6 @@ -import { Button, Div } from '@stylin.js/elements'; +import { Button, Div, P } from '@stylin.js/elements'; import { useRouter } from 'next/router'; -import { FC } from 'react'; +import { FC, useState } from 'react'; import { useFormContext } from 'react-hook-form'; import DialogCountdown from '@/components/dialog/dialog-countdown'; @@ -13,109 +13,121 @@ import { useDialog } from '@/hooks/use-dialog'; import { SignInFormProps } from './sign-in.types'; const SignInButton: FC = () => { - const { setValue, trigger, getValues } = useFormContext(); + const [isError, setIsError] = useState({ status: false, message: '' }); + const { trigger, getValues } = useFormContext(); const { dialog, handleClose } = useDialog(); const handleOpenConnectModal = useConnectModal(); const { push } = useRouter(); - const handleCreateProfile = () => { - return new Promise((resolve, reject) => { - setTimeout(() => { - const isSuccess = Math.random() > 0.5; - if (isSuccess) { - setValue('success', true); - resolve('success'); - return; - } - reject('Error'); - }, 1000); - }); - }; - - const handleSignIn = async () => { + const handleCreateProfile = async () => { let fieldsToValidate: (keyof SignInFormProps)[] = []; fieldsToValidate = ['username', 'password']; const isValid = await trigger(fieldsToValidate); + const username = getValues('username'); + const password = getValues('password'); + + console.log('Base URL _>', BASE_URL); try { - fetch(`${BASE_URL}/sign-in`, { - method: 'POST', - body: JSON.stringify({ - username: `${getValues('username')}`, - password: `${getValues('password')}`, - }), - }).then((res) => { - if (res.ok) { - if (isValid) { - dialog.promise(handleCreateProfile(), { - success: () => ({ - title: 'Sign-in successful', - button: ( - - ), - ghostButton: ( - - ), - message: 'Now connect your wallet do see all the function', - }), - loading: () => ({ - Icon: , - title: 'Signing...', - message: 'Please wait...', - }), - error: (e) => ({ - title: 'Oops! You can not sign in!', - button: { label: 'Try again', onClick: handleClose }, - message: ( - e.message || - 'Make sure you fill every field correctly and try again.' - ).replace('Invariant failed: ', ''), - ghostButton: { - label: 'Do not want to try again!', - onClick: handleClose, - }, - }), - }); + const requestBody = { + username, + password, + }; + if (isValid) { + fetch(`${BASE_URL}/sign-in`, { + method: 'POST', + mode: 'cors', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(requestBody), + }).then((res) => { + console.log('Res _> ', res); + if (res.ok) { + push(RoutesEnum.Profile); } - push(RoutesEnum.Profile); - } - if (!isValid) return; - }); + }); + } + if (!isValid) return; } catch (error) { - console.error('Error _> ', error); + setIsError({ status: true, message: 'User does not exist' }); + console.log('Error _> ', error); } }; + const handleSignIn = async () => { + dialog.promise(handleCreateProfile(), { + success: () => ({ + title: 'Sign-in successful', + button: ( + + ), + ghostButton: ( + + ), + message: 'Now connect your wallet do see all the function', + }), + loading: () => ({ + Icon: , + title: 'Signing...', + message: 'Please wait...', + }), + error: (e) => ({ + title: 'Oops! You can not sign in!', + button: { label: 'Try again', onClick: handleClose }, + message: ( + e.message || 'Make sure you fill every field correctly and try again.' + ).replace('Invariant failed: ', ''), + ghostButton: { + label: 'Do not want to try again!', + onClick: handleClose, + }, + }), + }); + }; + return ( -
+
+ {isError.status && ( +

+ {isError.message} +

+ )}
- - +
+ + +
{ - const [isError, setIsError] = useState({ status: false, message: '' }); - const { trigger, getValues } = useFormContext(); + const { set: setCookie } = useCookie(MEMEZ_FUN_TOKEN_AUTH); + const { getValues, handleSubmit } = useFormContext(); const { dialog, handleClose } = useDialog(); const handleOpenConnectModal = useConnectModal(); - const { push } = useRouter(); const handleCreateProfile = async () => { - let fieldsToValidate: (keyof SignInFormProps)[] = []; - - fieldsToValidate = ['username', 'password']; - const isValid = await trigger(fieldsToValidate); - const username = getValues('username'); - const password = getValues('password'); - - try { - const requestBody = { - username, - password, - }; - if (isValid) { - fetch(`${BASE_URL}/sign-in`, { - method: 'POST', - mode: 'cors', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(requestBody), - }).then((res) => { - if (res.ok) { - push(RoutesEnum.Profile); - } - }); + const { username, password } = getValues(); + const body = JSON.stringify({ + username, + password, + }); + return await fetch(`${process.env.NEXT_PUBLIC_AUTH_URL}/sign-in`, { + method: 'POST', + mode: 'cors', + headers: { + 'Content-Type': 'application/json', + }, + body, + }).then(async (res) => { + const response = await res.json(); + if (!res.ok) { + throw new Error(`${response.error}: ${response.message}`); } - if (!isValid) return; - } catch (error) { - setIsError({ status: true, message: 'User does not exist' }); - console.log('Error _> ', error); - } + setCookie(response.accessToken); + return; + }); }; - const handleSignIn = async () => { + const handleSignIn = () => { dialog.promise(handleCreateProfile(), { success: () => ({ title: 'Sign-in successful', @@ -120,11 +107,6 @@ const SignInButton: FC = () => { flexDirection="column" justifyContent="center" > - {isError.status && ( -

- {isError.message} -

- )}
{ const wallets = useWallets(); - const { push } = useRouter(); const { mutateAsync } = useConnectWallet(); const { dialog, handleClose } = useDialog(); @@ -19,11 +16,6 @@ const ConnectModal: FC = () => { await mutateAsync({ wallet }); }; - const handleSignInNavigation = () => { - push(RoutesEnum.SignIn); - handleClose(); - }; - const handleConnect = (wallet: WalletWithRequiredFeatures) => { dialog.promise(connectWallet(wallet), { success: () => ({ @@ -83,60 +75,15 @@ const ConnectModal: FC = () => { width={['100vw', '33.25rem']} borderRadius={['1rem 1rem 0 0', '1rem']} > -

- {wallets ? 'Select Your Wallet' : 'Go to sign in'} -

- {wallets ? ( -
    - {wallets.map((wallet) => ( -
  • handleConnect(wallet)} - > -
    - {wallet.name} -

    {wallet.name}

    -
    -

    Installed

    -
  • - ))} -
- ) : ( -
    +

    Select Your Wallet

    +
      + {wallets.map((wallet) => (
    • { border="1px solid transparent" justifyContent="space-between" nHover={{ borderColor: '#F5B72280' }} - onClick={handleSignInNavigation} + onClick={() => handleConnect(wallet)} > - Sign in +
      + {wallet.name} +

      {wallet.name}

      +
      +

      Installed

    • -
    - )} + ))} +
); }; diff --git a/components/wallet-button/index.tsx b/components/wallet-button/index.tsx index 1390f85..7fd5e10 100644 --- a/components/wallet-button/index.tsx +++ b/components/wallet-button/index.tsx @@ -1,19 +1,38 @@ -import { useCurrentAccount } from '@mysten/dapp-kit'; +import { useCurrentAccount, useSignPersonalMessage } from '@mysten/dapp-kit'; import { Button, Div } from '@stylin.js/elements'; -import { useRouter } from 'next/router'; -import { FC } from 'react'; +import { FC, useEffect } from 'react'; +import { useLocalStorage } from 'usehooks-ts'; import { WalletSVG } from '@/components/svg'; -import { Routes, RoutesEnum } from '@/constants'; import ConnectedModal from './connected-modal'; +import { useConnectModal } from './wallet-button.hook'; const WalletButton: FC = () => { + const handleOpenConnectModal = useConnectModal(); + const signMessage = useSignPersonalMessage(); + const [signedMessage, setSignedMessage] = useLocalStorage<{ + signature: string; + bytes: string; + } | null>('ww-signed-messages', null); const currentAccount = useCurrentAccount(); - const { push } = useRouter(); - if (currentAccount) return ; + const handleConnectWallet = () => { + handleOpenConnectModal(); + }; + + useEffect(() => { + if (signedMessage || !currentAccount) return; + signMessage + .mutateAsync({ + message: new TextEncoder().encode( + 'Please sign this to make sure verify your identity in our services.' + ), + }) + .then((response) => setSignedMessage(response)); + }, [currentAccount]); + if (currentAccount) return ; return ( - ), - ghostButton: ( - - ), - }), - loading: () => ({ - Icon: , - title: 'Creating...', - message: 'Creating an account...', - }), - error: (e) => ({ - title: 'Oops! You can not create an account!', - button: { label: 'Try again', onClick: handleClose }, - message: ( - e.message || 'Make sure you fill every field correctly and try again.' - ).replace('Invariant failed: ', ''), - ghostButton: { - label: 'Do not want to try again!', - onClick: handleClose, - }, - }), - }); - }; - - return ( -
- -
- ); -}; - -export default CreateProfileButton; diff --git a/views/create-profile/create-profile-validation.ts b/views/create-profile/create-profile-validation.ts deleted file mode 100644 index 0654f75..0000000 --- a/views/create-profile/create-profile-validation.ts +++ /dev/null @@ -1,17 +0,0 @@ -import * as yup from 'yup'; - -export const CreateProfileValidationSchema = yup.object({ - firstName: yup.string().required('First name is required'), - lastName: yup.string().required('Last name is required'), - username: yup.string().required('Username is required'), - email: yup - .string() - .email('Invalid email format') - .required('Email is required'), - imageUrl: yup.string().required('Avatar is required'), - password: yup.string().required('Password is required'), - description: yup - .string() - .min(80, 'Must be at least 80 characters') - .required('Description is required'), -}); diff --git a/views/create-profile/create-profile.types.ts b/views/create-profile/create-profile.types.ts deleted file mode 100644 index dad9624..0000000 --- a/views/create-profile/create-profile.types.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface CreateProfileFormProps { - email: string; - imageUrl: string; - username: string; - firstName: string; - lastName: string; - password: string; - description: string; -} diff --git a/views/create-profile/index.tsx b/views/create-profile/index.tsx deleted file mode 100644 index 9837c0d..0000000 --- a/views/create-profile/index.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { Div, H1, P } from '@stylin.js/elements'; -import { FC } from 'react'; -import { useFormContext } from 'react-hook-form'; - -import InputField from '@/components/input-field'; -import Layout from '@/components/layout'; -import UploadImage from '@/components/upload-image'; - -import { CreateProfileFormProps } from './create-profile.types'; -import CreateProfileButton from './create-profile-button'; - -const CreateProfile: FC = () => { - const { - register, - formState: { errors }, - } = useFormContext(); - - return ( - -
-
-

- Create profile -

-
-
-
-
-

- Basic Details -

- - - - - - - -
-
- -
-
-
- ); -}; - -export default CreateProfile; diff --git a/views/home/index.tsx b/views/home/index.tsx index 4c2683d..43e91bc 100644 --- a/views/home/index.tsx +++ b/views/home/index.tsx @@ -16,7 +16,7 @@ import { CARDS } from './card.data'; const Home: FC = () => { const { push } = useRouter(); - const handleCreateCoinButtonClick = () => push(Routes[RoutesEnum.CreateCoin]); + const handleCreateCoinButtonClick = () => push(Routes[RoutesEnum.Home]); return ( diff --git a/views/profile/edit-profile-modal/edit-profile-modal-button.tsx b/views/profile/edit-profile-modal/edit-profile-modal-button.tsx index 845c95a..fa0ecbb 100644 --- a/views/profile/edit-profile-modal/edit-profile-modal-button.tsx +++ b/views/profile/edit-profile-modal/edit-profile-modal-button.tsx @@ -7,7 +7,7 @@ import DialogCountdown from '@/components/dialog/dialog-countdown'; import { LoaderSVG } from '@/components/svg'; import { Routes, RoutesEnum } from '@/constants'; import { useDialog } from '@/hooks/use-dialog'; -import { CreateProfileFormProps } from '@/views/create-profile/create-profile.types'; +import { CreateProfileFormProps } from '@/interface'; const EditProfileModalButton: FC = () => { const { trigger } = useFormContext(); diff --git a/views/profile/edit-profile-modal/edit-profile-modal.types.ts b/views/profile/edit-profile-modal/edit-profile-modal.types.ts index d7b1d1c..c5f9739 100644 --- a/views/profile/edit-profile-modal/edit-profile-modal.types.ts +++ b/views/profile/edit-profile-modal/edit-profile-modal.types.ts @@ -1,4 +1,5 @@ export interface IEditProfileForm { + name: string; imageUrl: string; username: string; description: string; diff --git a/views/profile/edit-profile-modal/edit-profile.validations.ts b/views/profile/edit-profile-modal/edit-profile.validations.ts index a3f3f51..fbd4efd 100644 --- a/views/profile/edit-profile-modal/edit-profile.validations.ts +++ b/views/profile/edit-profile-modal/edit-profile.validations.ts @@ -1,6 +1,7 @@ import * as yup from 'yup'; export const editProfileValidationSchema = yup.object().shape({ + name: yup.string().required('Name is required'), username: yup.string().required('Username is required'), imageUrl: yup.string().required('Image is required'), description: yup.string().required('Description is required'), diff --git a/views/profile/edit-profile-modal/index.tsx b/views/profile/edit-profile-modal/index.tsx index 478c2fb..4e4d716 100644 --- a/views/profile/edit-profile-modal/index.tsx +++ b/views/profile/edit-profile-modal/index.tsx @@ -47,6 +47,13 @@ const EditProfileModal = () => { status={errors.imageUrl && 'error'} description={errors.imageUrl?.message} /> + { + const currentAccount = useCurrentAccount(); const clipBoardSuccessMessage = 'Address copied to the clipboard'; + const emailVerified = true; return (
{
- - Name + + name -
+
- Username + username -
- -
+ {emailVerified && ( +
+ +
+ )}
@@ -62,16 +75,21 @@ const UserInfo: FC = () => { px="0.5rem" py="0.25rem" display="flex" + width="8rem" color="#E4E7EB" gap="0.625rem" + cursor="pointer" fontSize="0.75rem" alignItems="center" - borderRadius="0.75rem" textAlign="center" + borderRadius="0.75rem" + justifyContent="center" transition="all 300ms ease-in-out" nHover={{ transform: 'scale(1.05)', color: '#F5B722' }} > - 0x2::sui::SUI + + {formatAddress(currentAccount?.address || '')} +
{ - const currentAccount = useCurrentAccount(); - const { push } = useRouter(); - - if (currentAccount) push('/'); - - return ( - -
-
-

- Sign in -

-

- Welcome back. Select method to log in -

-
-
-
- - - -
-

- Don‘t have an account?{' '} - { - push(Routes[RoutesEnum.CreateProfile]); - }} - > - Create a Profile - -

-
-
-
-
- ); -}; - -export default SignIn; diff --git a/views/sign-in/sign-in-button.tsx b/views/sign-in/sign-in-button.tsx deleted file mode 100644 index 09f5998..0000000 --- a/views/sign-in/sign-in-button.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import { useDisconnectWallet } from '@mysten/dapp-kit'; -import { Button, Div } from '@stylin.js/elements'; -import { useRouter } from 'next/router'; -import { FC } from 'react'; -import { useFormContext } from 'react-hook-form'; - -import DialogCountdown from '@/components/dialog/dialog-countdown'; -import { LoaderSVG } from '@/components/svg'; -import { useConnectModal } from '@/components/wallet-button/wallet-button.hook'; -import { MEMEZ_FUN_TOKEN_AUTH, RoutesEnum } from '@/constants'; -import { useCookie } from '@/hooks/use-cookie'; -import { useDialog } from '@/hooks/use-dialog'; - -import { SignInFormProps } from './sign-in.types'; - -const SignInButton: FC = () => { - const { dialog, handleClose } = useDialog(); - const handleOpenConnectModal = useConnectModal(); - const { mutate: disconnectWallet } = useDisconnectWallet(); - const { set: setCookie } = useCookie(MEMEZ_FUN_TOKEN_AUTH); - const { getValues, handleSubmit } = useFormContext(); - const { push } = useRouter(); - - const scheduleTimeToDisconnect = () => { - const oneHour = 60 * 60 * 1000; - const disconnectTimer = setTimeout(() => { - disconnectWallet(); - push(RoutesEnum.SignIn); - }, oneHour); - - return disconnectTimer; - }; - - const onSignIn = async () => { - const { username, password } = getValues(); - const body = JSON.stringify({ - username, - password, - }); - return await fetch(`${process.env.NEXT_PUBLIC_AUTH_URL}/sign-in`, { - method: 'POST', - mode: 'cors', - headers: { - 'Content-Type': 'application/json', - }, - body, - }).then(async (res) => { - const response = await res.json(); - scheduleTimeToDisconnect(); - if (!res.ok) { - throw new Error(`${response.error}: ${response.message}`); - } - setCookie(response.accessToken); - return; - }); - }; - - const handleSignIn = () => { - dialog.promise(onSignIn(), { - success: () => ({ - title: 'Sign-in successful', - button: ( - - ), - ghostButton: ( - - ), - message: 'Now connect your wallet do see all the function', - }), - loading: () => ({ - Icon: , - title: 'Signing...', - message: 'Please wait...', - }), - error: (e) => ({ - title: 'Oops! You can not sign in!', - button: { label: 'Try again', onClick: handleClose }, - message: ( - e.message || 'Make sure you fill every field correctly and try again.' - ).replace('Invariant failed: ', ''), - ghostButton: { - label: 'Do not want to try again!', - onClick: handleClose, - }, - }), - }); - }; - - return ( -
- -
- ); -}; - -export default SignInButton; diff --git a/views/sign-in/sign-in-form.tsx b/views/sign-in/sign-in-form.tsx deleted file mode 100644 index 46933f0..0000000 --- a/views/sign-in/sign-in-form.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { Button, Div, Hr, P, Span } from '@stylin.js/elements'; -import { FC } from 'react'; -import { useFormContext } from 'react-hook-form'; - -import InputField from '@/components/input-field'; -import { XSVG } from '@/components/svg'; - -import { SignInFormProps } from './sign-in.types'; - -const SignInForm: FC = () => { - const { - register, - formState: { errors }, - } = useFormContext(); - return ( -
-
- -
-
-

Or

-
-
-
- - -
-
-
- ); -}; - -export default SignInForm; diff --git a/views/sign-in/sign-in-validations.ts b/views/sign-in/sign-in-validations.ts deleted file mode 100644 index 1e9b224..0000000 --- a/views/sign-in/sign-in-validations.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as yup from 'yup'; - -export const SignInValidationSchema = yup.object({ - username: yup.string().required('Username is required'), - password: yup - .string() - .required('Password is required') - .min(6, 'Must be at least 8 characters') - .matches(/[A-Z]/, 'Must contain an uppercase letter') - .matches(/[0-9]/, 'Must contain a number'), -}); diff --git a/views/sign-in/sign-in.types.ts b/views/sign-in/sign-in.types.ts deleted file mode 100644 index 644a574..0000000 --- a/views/sign-in/sign-in.types.ts +++ /dev/null @@ -1,27 +0,0 @@ -export interface CreateProfileProps { - avatar: string; - userName: string; - description: string; -} - -export interface SignInProps { - username: string; - password: string; -} - -export enum SignInStepEnum { - CreateProfileProps, - SignInProps, -} - -export interface SignInHeaderProps { - title: string; - description?: string; - step: SignInStepEnum; -} - -export interface SignInFormProps { - username: string; - password: string; - success?: boolean; -} From 1ff066da2ed10efb1770a2fbc3c158e15eb96f0d Mon Sep 17 00:00:00 2001 From: KipandaJr Date: Thu, 10 Apr 2025 23:38:32 +0100 Subject: [PATCH 10/12] :construction: feat: wip --- components/profile/menu-list.tsx | 9 ++ components/wallet-button/index.tsx | 135 +++++++++++++++++++++++++---- hooks/use-cookie/index.ts | 2 + 3 files changed, 129 insertions(+), 17 deletions(-) diff --git a/components/profile/menu-list.tsx b/components/profile/menu-list.tsx index 4aa0a4c..a6a7199 100644 --- a/components/profile/menu-list.tsx +++ b/components/profile/menu-list.tsx @@ -2,6 +2,9 @@ import { useDisconnectWallet } from '@mysten/dapp-kit'; import { Div } from '@stylin.js/elements'; import { not } from 'ramda'; import { FC, useState } from 'react'; +import { useLocalStorage } from 'usehooks-ts'; + +import { MEMEZ_FUN_TOKEN_AUTH } from '@/constants'; import { DocIDSVG, @@ -24,7 +27,13 @@ const MenuList: FC = () => { const [isAccountMenuOpen, setIsAccountMenuOpen] = useState(false); const [isExplorerMenuOpen, setIsExplorerMenuOpen] = useState(false); + const [, setSignedPM] = useLocalStorage<{ + signature: string; + message: string; + cookies?: unknown; + }>(MEMEZ_FUN_TOKEN_AUTH, { signature: '', message: '' }); const handleDisconnectWallet = () => { + setSignedPM({ signature: '', message: '' }); disconnectWallet(); }; diff --git a/components/wallet-button/index.tsx b/components/wallet-button/index.tsx index 7fd5e10..1b9c27f 100644 --- a/components/wallet-button/index.tsx +++ b/components/wallet-button/index.tsx @@ -1,38 +1,139 @@ -import { useCurrentAccount, useSignPersonalMessage } from '@mysten/dapp-kit'; -import { Button, Div } from '@stylin.js/elements'; -import { FC, useEffect } from 'react'; +import { + useCurrentAccount, + useDisconnectWallet, + useSignPersonalMessage, +} from '@mysten/dapp-kit'; +import { Button, Div, Img } from '@stylin.js/elements'; +import { not } from 'ramda'; +import { FC, useEffect, useState } from 'react'; import { useLocalStorage } from 'usehooks-ts'; +import { v4 } from 'uuid'; -import { WalletSVG } from '@/components/svg'; +import { LoaderSVG, MemeZLogoSVG, WalletSVG } from '@/components/svg'; +import { MEMEZ_FUN_TOKEN_AUTH } from '@/constants'; +import { useDialog } from '@/hooks/use-dialog'; import ConnectedModal from './connected-modal'; import { useConnectModal } from './wallet-button.hook'; const WalletButton: FC = () => { const handleOpenConnectModal = useConnectModal(); - const signMessage = useSignPersonalMessage(); - const [signedMessage, setSignedMessage] = useLocalStorage<{ - signature: string; - bytes: string; - } | null>('ww-signed-messages', null); const currentAccount = useCurrentAccount(); + const { mutate: disconnectWallet } = useDisconnectWallet(); + const [signing, setSigning] = useState(false); + const { dialog, handleClose } = useDialog(); + const { mutate: signPersonalMessage } = useSignPersonalMessage(); + const [signedPM, setSignedPM] = useLocalStorage<{ + signature: string; + message: string; + }>(MEMEZ_FUN_TOKEN_AUTH, { signature: '', message: '' }); const handleConnectWallet = () => { handleOpenConnectModal(); }; useEffect(() => { - if (signedMessage || !currentAccount) return; - signMessage - .mutateAsync({ - message: new TextEncoder().encode( - 'Please sign this to make sure verify your identity in our services.' - ), - }) - .then((response) => setSignedMessage(response)); + if (signedPM.signature || signedPM.message || !currentAccount) return; + + const newMessage = v4(); + + signPersonalMessage( + { + message: new TextEncoder().encode(newMessage), + }, + { + onSuccess: (result) => { + setSigning(not); + setSignedPM({ + signature: result.signature, + message: newMessage, + }); + }, + onError: () => disconnectWallet(), + } + ); }, [currentAccount]); + useEffect(() => { + if (!signedPM.signature || !signedPM.message || !currentAccount || !signing) + return; + const signIn = async () => { + await fetch(`${process.env.NEXT_PUBLIC_BASE_URL!}/auth/sign-in`, { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + message: signedPM.message, + signature: signedPM.signature, + address: currentAccount.address, + }), + }) + .then(async (res) => { + if (!res.ok) { + disconnectWallet(); + throw new Error('signin error'); + } + + console.log(res.headers.getSetCookie(), '>>>res'); + }) + .catch(() => { + disconnectWallet(); + setSignedPM({ + signature: '', + message: '', + }); + }); + setSigning(not); + }; + dialog.promise(signIn(), { + success: () => ({ + timeout: 10000, + title: 'Sign in!', + button: { label: 'Continue browsing', onClick: handleClose }, + message: 'Signin successfully', + Icon: ( +
+ +
+ ), + }), + loading: () => ({ + Icon: , + title: 'Signing...', + message: 'Hang tight! Signing. Please wait', + }), + error: (e) => ({ + title: 'Oops! You could not Connect!', + button: { label: 'Try again', onClick: () => signIn() }, + message: + e.message || + 'Sigin-in failed. Try to refresh the page, double-check your inputs, or reconnect your wallet.', + ghostButton: { + label: 'Do not want to connect my wallet', + onClick: handleClose, + }, + Icon: ( + Error + ), + }), + }); + }, [signing]); + if (currentAccount) return ; + return (