From 13c3399a6e7d20704fb86c2fe1064ed913f18eac Mon Sep 17 00:00:00 2001 From: louiscklaw Date: Wed, 18 Jun 2025 13:42:35 +0800 Subject: [PATCH] update, --- 03_source/frontend/src/actions/blog.ts | 3 +- 03_source/frontend/src/actions/calendar.ts | 3 +- 03_source/frontend/src/actions/chat.ts | 3 +- 03_source/frontend/src/actions/invoice.ts | 3 +- 03_source/frontend/src/actions/kanban.ts | 3 +- 03_source/frontend/src/actions/mail.ts | 3 +- 03_source/frontend/src/actions/order.ts | 3 +- 03_source/frontend/src/actions/party-event.ts | 3 +- 03_source/frontend/src/actions/party-order.ts | 3 +- 03_source/frontend/src/actions/party-user.ts | 3 +- 03_source/frontend/src/actions/product.ts | 3 +- 03_source/frontend/src/actions/user.ts | 3 +- .../frontend/src/auth/context/jwt/action.ts | 3 +- .../src/auth/context/jwt/auth-provider.tsx | 3 +- .../src/auth/context/party-user-jwt/ERRORS.ts | 2 + .../src/auth/context/party-user-jwt/action.ts | 85 ++++++ .../auth/context/party-user-jwt/constant.ts | 1 + .../src/auth/context/party-user-jwt/index.ts | 7 + .../src/auth/context/party-user-jwt/utils.ts | 94 ++++++ .../src/auth/view/party-user-jwt/index.ts | 3 + .../view/party-user-jwt/jwt-sign-in-view.tsx | 167 +++++++++++ .../view/party-user-jwt/jwt-sign-up-view.tsx | 166 +++++++++++ .../amplify/reset-password.tsx | 16 + .../pages/party-user-auth/amplify/sign-in.tsx | 16 + .../pages/party-user-auth/amplify/sign-up.tsx | 16 + .../amplify/update-password.tsx | 16 + .../pages/party-user-auth/amplify/verify.tsx | 16 + .../pages/party-user-auth/auth0/callback.tsx | 7 + .../pages/party-user-auth/auth0/sign-in.tsx | 16 + .../firebase/reset-password.tsx | 16 + .../party-user-auth/firebase/sign-in.tsx | 16 + .../party-user-auth/firebase/sign-up.tsx | 16 + .../pages/party-user-auth/firebase/verify.tsx | 16 + .../src/pages/party-user-auth/jwt/sign-in.tsx | 16 + .../src/pages/party-user-auth/jwt/sign-up.tsx | 16 + .../supabase/reset-password.tsx | 16 + .../party-user-auth/supabase/sign-in.tsx | 16 + .../party-user-auth/supabase/sign-up.tsx | 16 + .../supabase/update-password.tsx | 16 + .../pages/party-user-auth/supabase/verify.tsx | 16 + 03_source/frontend/src/routes/paths.ts | 30 ++ .../frontend/src/routes/sections/index.tsx | 4 + .../src/routes/sections/party-user-auth.tsx | 282 ++++++++++++++++++ .../overview/app/view/overview-app-view.tsx | 38 +++ .../view/party-event-list-view.tsx | 2 +- .../product/view/product-list-view.tsx | 2 +- 46 files changed, 1188 insertions(+), 16 deletions(-) create mode 100644 03_source/frontend/src/auth/context/party-user-jwt/ERRORS.ts create mode 100644 03_source/frontend/src/auth/context/party-user-jwt/action.ts create mode 100644 03_source/frontend/src/auth/context/party-user-jwt/constant.ts create mode 100644 03_source/frontend/src/auth/context/party-user-jwt/index.ts create mode 100644 03_source/frontend/src/auth/context/party-user-jwt/utils.ts create mode 100644 03_source/frontend/src/auth/view/party-user-jwt/index.ts create mode 100644 03_source/frontend/src/auth/view/party-user-jwt/jwt-sign-in-view.tsx create mode 100644 03_source/frontend/src/auth/view/party-user-jwt/jwt-sign-up-view.tsx create mode 100644 03_source/frontend/src/pages/party-user-auth/amplify/reset-password.tsx create mode 100644 03_source/frontend/src/pages/party-user-auth/amplify/sign-in.tsx create mode 100644 03_source/frontend/src/pages/party-user-auth/amplify/sign-up.tsx create mode 100644 03_source/frontend/src/pages/party-user-auth/amplify/update-password.tsx create mode 100644 03_source/frontend/src/pages/party-user-auth/amplify/verify.tsx create mode 100644 03_source/frontend/src/pages/party-user-auth/auth0/callback.tsx create mode 100644 03_source/frontend/src/pages/party-user-auth/auth0/sign-in.tsx create mode 100644 03_source/frontend/src/pages/party-user-auth/firebase/reset-password.tsx create mode 100644 03_source/frontend/src/pages/party-user-auth/firebase/sign-in.tsx create mode 100644 03_source/frontend/src/pages/party-user-auth/firebase/sign-up.tsx create mode 100644 03_source/frontend/src/pages/party-user-auth/firebase/verify.tsx create mode 100644 03_source/frontend/src/pages/party-user-auth/jwt/sign-in.tsx create mode 100644 03_source/frontend/src/pages/party-user-auth/jwt/sign-up.tsx create mode 100644 03_source/frontend/src/pages/party-user-auth/supabase/reset-password.tsx create mode 100644 03_source/frontend/src/pages/party-user-auth/supabase/sign-in.tsx create mode 100644 03_source/frontend/src/pages/party-user-auth/supabase/sign-up.tsx create mode 100644 03_source/frontend/src/pages/party-user-auth/supabase/update-password.tsx create mode 100644 03_source/frontend/src/pages/party-user-auth/supabase/verify.tsx create mode 100644 03_source/frontend/src/routes/sections/party-user-auth.tsx diff --git a/03_source/frontend/src/actions/blog.ts b/03_source/frontend/src/actions/blog.ts index ed261d0..f14fe2c 100644 --- a/03_source/frontend/src/actions/blog.ts +++ b/03_source/frontend/src/actions/blog.ts @@ -1,5 +1,6 @@ import { useMemo } from 'react'; -import { endpoints, fetcher } from 'src/lib/axios'; +import { fetcher } from 'src/lib/axios'; +import { endpoints } from 'src/lib/endpoints'; import type { IPostItem } from 'src/types/blog'; import type { SWRConfiguration } from 'swr'; import useSWR from 'swr'; diff --git a/03_source/frontend/src/actions/calendar.ts b/03_source/frontend/src/actions/calendar.ts index b53ed7f..7338cf5 100644 --- a/03_source/frontend/src/actions/calendar.ts +++ b/03_source/frontend/src/actions/calendar.ts @@ -1,5 +1,6 @@ import { useMemo } from 'react'; -import axios, { endpoints, fetcher } from 'src/lib/axios'; +import axios, { fetcher } from 'src/lib/axios'; +import { endpoints } from 'src/lib/endpoints'; import type { ICalendarEvent } from 'src/types/calendar'; import type { SWRConfiguration } from 'swr'; import useSWR, { mutate } from 'swr'; diff --git a/03_source/frontend/src/actions/chat.ts b/03_source/frontend/src/actions/chat.ts index f27b42d..07540fd 100644 --- a/03_source/frontend/src/actions/chat.ts +++ b/03_source/frontend/src/actions/chat.ts @@ -1,6 +1,7 @@ import { keyBy } from 'es-toolkit'; import { useMemo } from 'react'; -import axios, { endpoints, fetcher } from 'src/lib/axios'; +import axios, { fetcher } from 'src/lib/axios'; +import { endpoints } from 'src/lib/endpoints'; import type { IChatConversation, IChatMessage, IChatParticipant } from 'src/types/chat'; import type { SWRConfiguration } from 'swr'; import useSWR, { mutate } from 'swr'; diff --git a/03_source/frontend/src/actions/invoice.ts b/03_source/frontend/src/actions/invoice.ts index a157543..98543bd 100644 --- a/03_source/frontend/src/actions/invoice.ts +++ b/03_source/frontend/src/actions/invoice.ts @@ -1,6 +1,7 @@ // src/actions/invoice.ts import { useMemo } from 'react'; -import axiosInstance, { endpoints, fetcher } from 'src/lib/axios'; +import axiosInstance, { fetcher } from 'src/lib/axios'; +import { endpoints } from 'src/lib/endpoints'; import type { IInvoiceItem, SaveInvoiceData } from 'src/types/invoice'; import type { SWRConfiguration } from 'swr'; import useSWR from 'swr'; diff --git a/03_source/frontend/src/actions/kanban.ts b/03_source/frontend/src/actions/kanban.ts index 10fd970..d92d78b 100644 --- a/03_source/frontend/src/actions/kanban.ts +++ b/03_source/frontend/src/actions/kanban.ts @@ -1,6 +1,7 @@ import type { UniqueIdentifier } from '@dnd-kit/core'; import { startTransition, useMemo } from 'react'; -import axios, { endpoints, fetcher } from 'src/lib/axios'; +import axios, { fetcher } from 'src/lib/axios'; +import { endpoints } from 'src/lib/endpoints'; import type { IKanban, IKanbanColumn, IKanbanTask } from 'src/types/kanban'; import type { SWRConfiguration } from 'swr'; import useSWR, { mutate } from 'swr'; diff --git a/03_source/frontend/src/actions/mail.ts b/03_source/frontend/src/actions/mail.ts index c9f6fe2..b0078fc 100644 --- a/03_source/frontend/src/actions/mail.ts +++ b/03_source/frontend/src/actions/mail.ts @@ -1,6 +1,7 @@ import { keyBy } from 'es-toolkit'; import { useMemo } from 'react'; -import { endpoints, fetcher } from 'src/lib/axios'; +import { fetcher } from 'src/lib/axios'; +import { endpoints } from 'src/lib/endpoints'; import type { IMail, IMailLabel } from 'src/types/mail'; import type { SWRConfiguration } from 'swr'; import useSWR from 'swr'; diff --git a/03_source/frontend/src/actions/order.ts b/03_source/frontend/src/actions/order.ts index 9de89fb..75cb27c 100644 --- a/03_source/frontend/src/actions/order.ts +++ b/03_source/frontend/src/actions/order.ts @@ -1,6 +1,7 @@ // src/actions/order.ts import { useMemo } from 'react'; -import axiosInstance, { endpoints, fetcher } from 'src/lib/axios'; +import axiosInstance, { fetcher } from 'src/lib/axios'; +import { endpoints } from 'src/lib/endpoints'; import type { IOrderItem } from 'src/types/order'; import type { IProductItem } from 'src/types/product'; import type { SWRConfiguration } from 'swr'; diff --git a/03_source/frontend/src/actions/party-event.ts b/03_source/frontend/src/actions/party-event.ts index 530bb1d..44db2a3 100644 --- a/03_source/frontend/src/actions/party-event.ts +++ b/03_source/frontend/src/actions/party-event.ts @@ -1,7 +1,8 @@ // src/actions/party-event.ts // import { useMemo } from 'react'; -import axiosInstance, { endpoints, fetcher } from 'src/lib/axios'; +import axiosInstance, { fetcher } from 'src/lib/axios'; +import { endpoints } from 'src/lib/endpoints'; import type { IPartyEventItem } from 'src/types/party-event'; import type { SWRConfiguration } from 'swr'; import useSWR, { mutate } from 'swr'; diff --git a/03_source/frontend/src/actions/party-order.ts b/03_source/frontend/src/actions/party-order.ts index b5e8227..e68f876 100644 --- a/03_source/frontend/src/actions/party-order.ts +++ b/03_source/frontend/src/actions/party-order.ts @@ -1,7 +1,8 @@ // src/actions/party-order.ts // import { useMemo } from 'react'; -import axiosInstance, { endpoints, fetcher } from 'src/lib/axios'; +import axiosInstance, { fetcher } from 'src/lib/axios'; +import { endpoints } from 'src/lib/endpoints'; import type { IPartyOrderItem } from 'src/types/party-order'; import type { SWRConfiguration } from 'swr'; import useSWR, { mutate } from 'swr'; diff --git a/03_source/frontend/src/actions/party-user.ts b/03_source/frontend/src/actions/party-user.ts index 35cdffc..1b9ccfd 100644 --- a/03_source/frontend/src/actions/party-user.ts +++ b/03_source/frontend/src/actions/party-user.ts @@ -1,7 +1,8 @@ // src/actions/party-user1.ts // import { useMemo } from 'react'; -import axiosInstance, { endpoints, fetcher } from 'src/lib/axios'; +import axiosInstance, { fetcher } from 'src/lib/axios'; +import { endpoints } from 'src/lib/endpoints'; import type { IPartyUserItem } from 'src/types/party-user'; import type { IProductItem } from 'src/types/product'; import type { SWRConfiguration } from 'swr'; diff --git a/03_source/frontend/src/actions/product.ts b/03_source/frontend/src/actions/product.ts index 9dd5304..1ec8d33 100644 --- a/03_source/frontend/src/actions/product.ts +++ b/03_source/frontend/src/actions/product.ts @@ -1,7 +1,8 @@ // src/actions/product.ts // import { useMemo } from 'react'; -import axiosInstance, { endpoints, fetcher } from 'src/lib/axios'; +import axiosInstance, { fetcher } from 'src/lib/axios'; +import { endpoints } from 'src/lib/endpoints'; import type { IProductItem } from 'src/types/product'; import type { SWRConfiguration } from 'swr'; import useSWR, { mutate } from 'swr'; diff --git a/03_source/frontend/src/actions/user.ts b/03_source/frontend/src/actions/user.ts index 41d1a16..97ae488 100644 --- a/03_source/frontend/src/actions/user.ts +++ b/03_source/frontend/src/actions/user.ts @@ -1,5 +1,6 @@ import { useMemo } from 'react'; -import axiosInstance, { endpoints, fetcher } from 'src/lib/axios'; +import axiosInstance, { fetcher } from 'src/lib/axios'; +import { endpoints } from 'src/lib/endpoints'; import type { IProductItem } from 'src/types/product'; import { IUserItem } from 'src/types/user'; import type { SWRConfiguration } from 'swr'; diff --git a/03_source/frontend/src/auth/context/jwt/action.ts b/03_source/frontend/src/auth/context/jwt/action.ts index aea67b9..dc7b1b5 100644 --- a/03_source/frontend/src/auth/context/jwt/action.ts +++ b/03_source/frontend/src/auth/context/jwt/action.ts @@ -1,4 +1,5 @@ -import axios, { endpoints } from 'src/lib/axios'; +import axios from 'src/lib/axios'; +import { endpoints } from 'src/lib/endpoints'; import { JWT_STORAGE_KEY } from './constant'; import { setSession } from './utils'; diff --git a/03_source/frontend/src/auth/context/jwt/auth-provider.tsx b/03_source/frontend/src/auth/context/jwt/auth-provider.tsx index b386e57..e5121d5 100644 --- a/03_source/frontend/src/auth/context/jwt/auth-provider.tsx +++ b/03_source/frontend/src/auth/context/jwt/auth-provider.tsx @@ -1,6 +1,7 @@ import { useSetState } from 'minimal-shared/hooks'; import { useCallback, useEffect, useMemo } from 'react'; -import axios, { endpoints } from 'src/lib/axios'; +import axios from 'src/lib/axios'; +import { endpoints } from 'src/lib/endpoints'; import type { AuthState } from '../../types'; import { AuthContext } from '../auth-context'; import { JWT_STORAGE_KEY } from './constant'; diff --git a/03_source/frontend/src/auth/context/party-user-jwt/ERRORS.ts b/03_source/frontend/src/auth/context/party-user-jwt/ERRORS.ts new file mode 100644 index 0000000..501f66e --- /dev/null +++ b/03_source/frontend/src/auth/context/party-user-jwt/ERRORS.ts @@ -0,0 +1,2 @@ +export const ERR_ACCESS_TOKEN_NOT_FOUND = `Access token not found in response`; +export const ACCESS_TOKEN_NOT_FOUND_IN_RESPONSE = 'Access token not found in response'; diff --git a/03_source/frontend/src/auth/context/party-user-jwt/action.ts b/03_source/frontend/src/auth/context/party-user-jwt/action.ts new file mode 100644 index 0000000..da24123 --- /dev/null +++ b/03_source/frontend/src/auth/context/party-user-jwt/action.ts @@ -0,0 +1,85 @@ +import axios from 'src/lib/axios'; +import { endpoints } from 'src/lib/endpoints'; +import { JWT_STORAGE_KEY } from './constant'; +import { ACCESS_TOKEN_NOT_FOUND_IN_RESPONSE, ERR_ACCESS_TOKEN_NOT_FOUND } from './ERRORS'; +import { setSession } from './utils'; + +// ---------------------------------------------------------------------- + +export type SignInParams = { + email: string; + password: string; +}; + +export type SignUpParams = { + email: string; + password: string; + firstName: string; + lastName: string; +}; + +/** ************************************** + * Sign in + *************************************** */ +export const signInWithPassword = async ({ email, password }: SignInParams): Promise => { + try { + const params = { email, password }; + + const res = await axios.post(endpoints.partyUserAuth.signIn, params); + + const { accessToken } = res.data; + + if (!accessToken) { + throw new Error(ERR_ACCESS_TOKEN_NOT_FOUND); + } + + setSession(accessToken); + } catch (error) { + console.error('Error during sign in:', error); + throw error; + } +}; + +/** ************************************** + * Sign up + *************************************** */ +export const signUp = async ({ + email, + password, + firstName, + lastName, +}: SignUpParams): Promise => { + const params = { + email, + password, + firstName, + lastName, + }; + + try { + const res = await axios.post(endpoints.auth.signUp, params); + + const { accessToken } = res.data; + + if (!accessToken) { + throw new Error(ACCESS_TOKEN_NOT_FOUND_IN_RESPONSE); + } + + sessionStorage.setItem(JWT_STORAGE_KEY, accessToken); + } catch (error) { + console.error('Error during sign up:', error); + throw error; + } +}; + +/** ************************************** + * Sign out + *************************************** */ +export const signOut = async (): Promise => { + try { + await setSession(null); + } catch (error) { + console.error('Error during sign out:', error); + throw error; + } +}; diff --git a/03_source/frontend/src/auth/context/party-user-jwt/constant.ts b/03_source/frontend/src/auth/context/party-user-jwt/constant.ts new file mode 100644 index 0000000..c9cb827 --- /dev/null +++ b/03_source/frontend/src/auth/context/party-user-jwt/constant.ts @@ -0,0 +1 @@ +export const JWT_STORAGE_KEY = 'jwt_access_token'; diff --git a/03_source/frontend/src/auth/context/party-user-jwt/index.ts b/03_source/frontend/src/auth/context/party-user-jwt/index.ts new file mode 100644 index 0000000..a0ae001 --- /dev/null +++ b/03_source/frontend/src/auth/context/party-user-jwt/index.ts @@ -0,0 +1,7 @@ +export * from './utils'; + +export * from './action'; + +export * from './constant'; + +// export * from './auth-provider'; diff --git a/03_source/frontend/src/auth/context/party-user-jwt/utils.ts b/03_source/frontend/src/auth/context/party-user-jwt/utils.ts new file mode 100644 index 0000000..2e9f335 --- /dev/null +++ b/03_source/frontend/src/auth/context/party-user-jwt/utils.ts @@ -0,0 +1,94 @@ +import axios from 'src/lib/axios'; +import { paths } from 'src/routes/paths'; +import { JWT_STORAGE_KEY } from './constant'; + +// ---------------------------------------------------------------------- + +export function jwtDecode(token: string) { + try { + if (!token) return null; + + const parts = token.split('.'); + if (parts.length < 2) { + throw new Error('Invalid token!'); + } + + const base64Url = parts[1]; + const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); + const decoded = JSON.parse(atob(base64)); + + return decoded; + } catch (error) { + console.error('Error decoding token:', error); + throw error; + } +} + +// ---------------------------------------------------------------------- + +export function isValidToken(accessToken: string) { + if (!accessToken) { + return false; + } + + try { + const decoded = jwtDecode(accessToken); + + if (!decoded || !('exp' in decoded)) { + return false; + } + + const currentTime = Date.now() / 1000; + + return decoded.exp > currentTime; + } catch (error) { + console.error('Error during token validation:', error); + return false; + } +} + +// ---------------------------------------------------------------------- + +export function tokenExpired(exp: number) { + const currentTime = Date.now(); + const timeLeft = exp * 1000 - currentTime; + + setTimeout(() => { + try { + alert('Token expired!'); + sessionStorage.removeItem(JWT_STORAGE_KEY); + window.location.href = paths.auth.jwt.signIn; + } catch (error) { + console.error('Error during token expiration:', error); + throw error; + } + }, timeLeft); +} + +// ---------------------------------------------------------------------- + +const INVALID_ACCESS_TOKEN = 'Invalid access token!'; + +export async function setSession(accessToken: string | null) { + try { + if (accessToken) { + sessionStorage.setItem(JWT_STORAGE_KEY, accessToken); + + axios.defaults.headers.common.Authorization = `Bearer ${accessToken}`; + + const decodedToken = jwtDecode(accessToken); // ~3 days by minimals server + + if (decodedToken && 'exp' in decodedToken) { + tokenExpired(decodedToken.exp); + } else { + throw new Error(INVALID_ACCESS_TOKEN); + } + } else { + sessionStorage.removeItem(JWT_STORAGE_KEY); + delete axios.defaults.headers.common.Authorization; + } + } catch (error) { + console.error('Error during set session:', error); + throw error; + } +} diff --git a/03_source/frontend/src/auth/view/party-user-jwt/index.ts b/03_source/frontend/src/auth/view/party-user-jwt/index.ts new file mode 100644 index 0000000..0e2428a --- /dev/null +++ b/03_source/frontend/src/auth/view/party-user-jwt/index.ts @@ -0,0 +1,3 @@ +export * from './jwt-sign-in-view'; + +export * from './jwt-sign-up-view'; diff --git a/03_source/frontend/src/auth/view/party-user-jwt/jwt-sign-in-view.tsx b/03_source/frontend/src/auth/view/party-user-jwt/jwt-sign-in-view.tsx new file mode 100644 index 0000000..035fa42 --- /dev/null +++ b/03_source/frontend/src/auth/view/party-user-jwt/jwt-sign-in-view.tsx @@ -0,0 +1,167 @@ +import { zodResolver } from '@hookform/resolvers/zod'; +import Alert from '@mui/material/Alert'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import IconButton from '@mui/material/IconButton'; +import InputAdornment from '@mui/material/InputAdornment'; +import Link from '@mui/material/Link'; +import { useBoolean } from 'minimal-shared/hooks'; +import React, { useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; +import { Field, Form } from 'src/components/hook-form'; +import { Iconify } from 'src/components/iconify'; +import { RouterLink } from 'src/routes/components'; +import { useRouter } from 'src/routes/hooks'; +import { paths } from 'src/routes/paths'; +import { z as zod } from 'zod'; +import { FormHead } from '../../components/form-head'; +import { signInWithPassword } from '../../context/party-user-jwt'; +import { useAuthContext } from '../../hooks'; +import { getErrorMessage } from '../../utils'; + +// ---------------------------------------------------------------------- + +// ---------------------------------------------------------------------- + +export function JwtSignInView(): React.JSX.Element { + const { t } = useTranslation(); + + const router = useRouter(); + + const showPassword = useBoolean(); + + const { checkUserSession } = useAuthContext(); + + const [errorMessage, setErrorMessage] = useState(null); + + type SignInSchemaType = zod.infer; + + const EMAIL_IS_REQUIRED = 'Email is required!'; + const EMAIL_MUST_BE_A_VALID_EMAIL_ADDRESS = 'Email must be a valid email address!'; + const PASSWORD_IS_REQUIRED = 'Password is required!'; + const PASSWORD_MUST_BE_AT_LEAST_6_CHARACTERS = 'Password must be at least 6 characters!'; + + const SignInSchema = zod.object({ + email: zod + .string() + .min(1, { message: EMAIL_IS_REQUIRED }) + .email({ message: EMAIL_MUST_BE_A_VALID_EMAIL_ADDRESS }), + password: zod + .string() + .min(1, { message: PASSWORD_IS_REQUIRED }) + .min(6, { message: PASSWORD_MUST_BE_AT_LEAST_6_CHARACTERS }), + }); + + const defaultValues: SignInSchemaType = { + email: 'party_user0@prisma.io', + password: 'Aa12345678', + }; + + const methods = useForm({ + resolver: zodResolver(SignInSchema), + defaultValues, + }); + + const { + handleSubmit, + formState: { isSubmitting }, + } = methods; + + const onSubmit = handleSubmit(async (data) => { + try { + await signInWithPassword({ email: data.email, password: data.password }); + await checkUserSession?.(); + + router.refresh(); + } catch (error) { + console.error(error); + const feedbackMessage = getErrorMessage(error); + setErrorMessage(feedbackMessage); + } + }); + + const renderForm = () => ( + + + + + + Forgot password? + + + + + + + + ), + }, + }} + /> + + + + + ); + + return ( + <> + + {`Don’t have an account? `} + + {t('get-started')} + + + } + sx={{ textAlign: { xs: 'center', md: 'left' } }} + /> + + + Use {defaultValues.email} + {' with password '} + {defaultValues.password} + + + {!!errorMessage && ( + + {errorMessage} + + )} + +
+ {renderForm()} +
+ + ); +} diff --git a/03_source/frontend/src/auth/view/party-user-jwt/jwt-sign-up-view.tsx b/03_source/frontend/src/auth/view/party-user-jwt/jwt-sign-up-view.tsx new file mode 100644 index 0000000..a89391e --- /dev/null +++ b/03_source/frontend/src/auth/view/party-user-jwt/jwt-sign-up-view.tsx @@ -0,0 +1,166 @@ +import { zodResolver } from '@hookform/resolvers/zod'; +import Alert from '@mui/material/Alert'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import IconButton from '@mui/material/IconButton'; +import InputAdornment from '@mui/material/InputAdornment'; +import Link from '@mui/material/Link'; +import { useBoolean } from 'minimal-shared/hooks'; +import { useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { Field, Form } from 'src/components/hook-form'; +import { Iconify } from 'src/components/iconify'; +import { RouterLink } from 'src/routes/components'; +import { useRouter } from 'src/routes/hooks'; +import { paths } from 'src/routes/paths'; +import { z as zod } from 'zod'; +import { FormHead } from '../../components/form-head'; +import { SignUpTerms } from '../../components/sign-up-terms'; +import { signUp } from '../../context/jwt'; +import { useAuthContext } from '../../hooks'; +import { getErrorMessage } from '../../utils'; + +// ---------------------------------------------------------------------- + +export type SignUpSchemaType = zod.infer; + +export const SignUpSchema = zod.object({ + firstName: zod.string().min(1, { message: 'First name is required!' }), + lastName: zod.string().min(1, { message: 'Last name is required!' }), + email: zod + .string() + .min(1, { message: 'Email is required!' }) + .email({ message: 'Email must be a valid email address!' }), + password: zod + .string() + .min(1, { message: 'Password is required!' }) + .min(6, { message: 'Password must be at least 6 characters!' }), +}); + +// ---------------------------------------------------------------------- + +export function JwtSignUpView() { + const router = useRouter(); + + const showPassword = useBoolean(); + + const { checkUserSession } = useAuthContext(); + + const [errorMessage, setErrorMessage] = useState(null); + + const defaultValues: SignUpSchemaType = { + firstName: 'Hello', + lastName: 'Friend', + email: 'hello@gmail.com', + password: '@2Minimal', + }; + + const methods = useForm({ + resolver: zodResolver(SignUpSchema), + defaultValues, + }); + + const { + handleSubmit, + formState: { isSubmitting }, + } = methods; + + const onSubmit = handleSubmit(async (data) => { + try { + await signUp({ + email: data.email, + password: data.password, + firstName: data.firstName, + lastName: data.lastName, + }); + await checkUserSession?.(); + + router.refresh(); + } catch (error) { + console.error(error); + const feedbackMessage = getErrorMessage(error); + setErrorMessage(feedbackMessage); + } + }); + + const renderForm = () => ( + + + + + + + + + + + + + + ), + }, + }} + /> + + + + ); + + return ( + <> + + {`Already have an account? `} + + Get started + + + } + sx={{ textAlign: { xs: 'center', md: 'left' } }} + /> + + {!!errorMessage && ( + + {errorMessage} + + )} + +
+ {renderForm()} +
+ + + + ); +} diff --git a/03_source/frontend/src/pages/party-user-auth/amplify/reset-password.tsx b/03_source/frontend/src/pages/party-user-auth/amplify/reset-password.tsx new file mode 100644 index 0000000..6485e59 --- /dev/null +++ b/03_source/frontend/src/pages/party-user-auth/amplify/reset-password.tsx @@ -0,0 +1,16 @@ +import { AmplifyResetPasswordView } from 'src/auth/view/amplify'; +import { CONFIG } from 'src/global-config'; + +// ---------------------------------------------------------------------- + +const metadata = { title: `Reset password | Amplify - ${CONFIG.appName}` }; + +export default function Page() { + return ( + <> + {metadata.title} + + + + ); +} diff --git a/03_source/frontend/src/pages/party-user-auth/amplify/sign-in.tsx b/03_source/frontend/src/pages/party-user-auth/amplify/sign-in.tsx new file mode 100644 index 0000000..8ca9823 --- /dev/null +++ b/03_source/frontend/src/pages/party-user-auth/amplify/sign-in.tsx @@ -0,0 +1,16 @@ +import { AmplifySignInView } from 'src/auth/view/amplify'; +import { CONFIG } from 'src/global-config'; + +// ---------------------------------------------------------------------- + +const metadata = { title: `Sign in | Amplify - ${CONFIG.appName}` }; + +export default function Page() { + return ( + <> + {metadata.title} + + + + ); +} diff --git a/03_source/frontend/src/pages/party-user-auth/amplify/sign-up.tsx b/03_source/frontend/src/pages/party-user-auth/amplify/sign-up.tsx new file mode 100644 index 0000000..08f6931 --- /dev/null +++ b/03_source/frontend/src/pages/party-user-auth/amplify/sign-up.tsx @@ -0,0 +1,16 @@ +import { AmplifySignUpView } from 'src/auth/view/amplify'; +import { CONFIG } from 'src/global-config'; + +// ---------------------------------------------------------------------- + +const metadata = { title: `Sign up | Amplify - ${CONFIG.appName}` }; + +export default function Page() { + return ( + <> + {metadata.title} + + + + ); +} diff --git a/03_source/frontend/src/pages/party-user-auth/amplify/update-password.tsx b/03_source/frontend/src/pages/party-user-auth/amplify/update-password.tsx new file mode 100644 index 0000000..4ae4c77 --- /dev/null +++ b/03_source/frontend/src/pages/party-user-auth/amplify/update-password.tsx @@ -0,0 +1,16 @@ +import { AmplifyUpdatePasswordView } from 'src/auth/view/amplify'; +import { CONFIG } from 'src/global-config'; + +// ---------------------------------------------------------------------- + +const metadata = { title: `Update password | Amplify - ${CONFIG.appName}` }; + +export default function Page() { + return ( + <> + {metadata.title} + + + + ); +} diff --git a/03_source/frontend/src/pages/party-user-auth/amplify/verify.tsx b/03_source/frontend/src/pages/party-user-auth/amplify/verify.tsx new file mode 100644 index 0000000..ac43552 --- /dev/null +++ b/03_source/frontend/src/pages/party-user-auth/amplify/verify.tsx @@ -0,0 +1,16 @@ +import { AmplifyVerifyView } from 'src/auth/view/amplify'; +import { CONFIG } from 'src/global-config'; + +// ---------------------------------------------------------------------- + +const metadata = { title: `Verify | Amplify - ${CONFIG.appName}` }; + +export default function Page() { + return ( + <> + {metadata.title} + + + + ); +} diff --git a/03_source/frontend/src/pages/party-user-auth/auth0/callback.tsx b/03_source/frontend/src/pages/party-user-auth/auth0/callback.tsx new file mode 100644 index 0000000..a7a78fd --- /dev/null +++ b/03_source/frontend/src/pages/party-user-auth/auth0/callback.tsx @@ -0,0 +1,7 @@ +import { SplashScreen } from 'src/components/loading-screen'; + +// ---------------------------------------------------------------------- + +export default function CallbackPage() { + return ; +} diff --git a/03_source/frontend/src/pages/party-user-auth/auth0/sign-in.tsx b/03_source/frontend/src/pages/party-user-auth/auth0/sign-in.tsx new file mode 100644 index 0000000..4be60ab --- /dev/null +++ b/03_source/frontend/src/pages/party-user-auth/auth0/sign-in.tsx @@ -0,0 +1,16 @@ +import { Auth0SignInView } from 'src/auth/view/auth0'; +import { CONFIG } from 'src/global-config'; + +// ---------------------------------------------------------------------- + +const metadata = { title: `Sign in | Auth0 - ${CONFIG.appName}` }; + +export default function Page() { + return ( + <> + {metadata.title} + + + + ); +} diff --git a/03_source/frontend/src/pages/party-user-auth/firebase/reset-password.tsx b/03_source/frontend/src/pages/party-user-auth/firebase/reset-password.tsx new file mode 100644 index 0000000..dcd2d7e --- /dev/null +++ b/03_source/frontend/src/pages/party-user-auth/firebase/reset-password.tsx @@ -0,0 +1,16 @@ +import { FirebaseResetPasswordView } from 'src/auth/view/firebase'; +import { CONFIG } from 'src/global-config'; + +// ---------------------------------------------------------------------- + +const metadata = { title: `Reset password | Firebase - ${CONFIG.appName}` }; + +export default function Page() { + return ( + <> + {metadata.title} + + + + ); +} diff --git a/03_source/frontend/src/pages/party-user-auth/firebase/sign-in.tsx b/03_source/frontend/src/pages/party-user-auth/firebase/sign-in.tsx new file mode 100644 index 0000000..5385c9b --- /dev/null +++ b/03_source/frontend/src/pages/party-user-auth/firebase/sign-in.tsx @@ -0,0 +1,16 @@ +import { FirebaseSignInView } from 'src/auth/view/firebase'; +import { CONFIG } from 'src/global-config'; + +// ---------------------------------------------------------------------- + +const metadata = { title: `Sign in | Firebase - ${CONFIG.appName}` }; + +export default function Page() { + return ( + <> + {metadata.title} + + + + ); +} diff --git a/03_source/frontend/src/pages/party-user-auth/firebase/sign-up.tsx b/03_source/frontend/src/pages/party-user-auth/firebase/sign-up.tsx new file mode 100644 index 0000000..aa5c37f --- /dev/null +++ b/03_source/frontend/src/pages/party-user-auth/firebase/sign-up.tsx @@ -0,0 +1,16 @@ +import { FirebaseSignUpView } from 'src/auth/view/firebase'; +import { CONFIG } from 'src/global-config'; + +// ---------------------------------------------------------------------- + +const metadata = { title: `Sign up | Firebase - ${CONFIG.appName}` }; + +export default function Page() { + return ( + <> + {metadata.title} + + + + ); +} diff --git a/03_source/frontend/src/pages/party-user-auth/firebase/verify.tsx b/03_source/frontend/src/pages/party-user-auth/firebase/verify.tsx new file mode 100644 index 0000000..5c712af --- /dev/null +++ b/03_source/frontend/src/pages/party-user-auth/firebase/verify.tsx @@ -0,0 +1,16 @@ +import { FirebaseVerifyView } from 'src/auth/view/firebase'; +import { CONFIG } from 'src/global-config'; + +// ---------------------------------------------------------------------- + +const metadata = { title: `Verify | Firebase - ${CONFIG.appName}` }; + +export default function Page() { + return ( + <> + {metadata.title} + + + + ); +} diff --git a/03_source/frontend/src/pages/party-user-auth/jwt/sign-in.tsx b/03_source/frontend/src/pages/party-user-auth/jwt/sign-in.tsx new file mode 100644 index 0000000..86c7eae --- /dev/null +++ b/03_source/frontend/src/pages/party-user-auth/jwt/sign-in.tsx @@ -0,0 +1,16 @@ +import { JwtSignInView } from 'src/auth/view/party-user-jwt'; +import { CONFIG } from 'src/global-config'; + +// ---------------------------------------------------------------------- + +const metadata = { title: `Sign in | Jwt - ${CONFIG.appName}` }; + +export default function Page() { + return ( + <> + {metadata.title} + + + + ); +} diff --git a/03_source/frontend/src/pages/party-user-auth/jwt/sign-up.tsx b/03_source/frontend/src/pages/party-user-auth/jwt/sign-up.tsx new file mode 100644 index 0000000..9a53e9e --- /dev/null +++ b/03_source/frontend/src/pages/party-user-auth/jwt/sign-up.tsx @@ -0,0 +1,16 @@ +import { JwtSignUpView } from 'src/auth/view/party-user-jwt'; +import { CONFIG } from 'src/global-config'; + +// ---------------------------------------------------------------------- + +const metadata = { title: `Sign up | Jwt - ${CONFIG.appName}` }; + +export default function Page() { + return ( + <> + {metadata.title} + + + + ); +} diff --git a/03_source/frontend/src/pages/party-user-auth/supabase/reset-password.tsx b/03_source/frontend/src/pages/party-user-auth/supabase/reset-password.tsx new file mode 100644 index 0000000..e969b4c --- /dev/null +++ b/03_source/frontend/src/pages/party-user-auth/supabase/reset-password.tsx @@ -0,0 +1,16 @@ +import { SupabaseResetPasswordView } from 'src/auth/view/supabase'; +import { CONFIG } from 'src/global-config'; + +// ---------------------------------------------------------------------- + +const metadata = { title: `Reset password | Supabase - ${CONFIG.appName}` }; + +export default function Page() { + return ( + <> + {metadata.title} + + + + ); +} diff --git a/03_source/frontend/src/pages/party-user-auth/supabase/sign-in.tsx b/03_source/frontend/src/pages/party-user-auth/supabase/sign-in.tsx new file mode 100644 index 0000000..ae0e2e1 --- /dev/null +++ b/03_source/frontend/src/pages/party-user-auth/supabase/sign-in.tsx @@ -0,0 +1,16 @@ +import { SupabaseSignInView } from 'src/auth/view/supabase'; +import { CONFIG } from 'src/global-config'; + +// ---------------------------------------------------------------------- + +const metadata = { title: `Sign in | Supabase - ${CONFIG.appName}` }; + +export default function Page() { + return ( + <> + {metadata.title} + + + + ); +} diff --git a/03_source/frontend/src/pages/party-user-auth/supabase/sign-up.tsx b/03_source/frontend/src/pages/party-user-auth/supabase/sign-up.tsx new file mode 100644 index 0000000..6afa1fb --- /dev/null +++ b/03_source/frontend/src/pages/party-user-auth/supabase/sign-up.tsx @@ -0,0 +1,16 @@ +import { SupabaseSignUpView } from 'src/auth/view/supabase'; +import { CONFIG } from 'src/global-config'; + +// ---------------------------------------------------------------------- + +const metadata = { title: `Sign up | Supabase - ${CONFIG.appName}` }; + +export default function Page() { + return ( + <> + {metadata.title} + + + + ); +} diff --git a/03_source/frontend/src/pages/party-user-auth/supabase/update-password.tsx b/03_source/frontend/src/pages/party-user-auth/supabase/update-password.tsx new file mode 100644 index 0000000..d0c72c4 --- /dev/null +++ b/03_source/frontend/src/pages/party-user-auth/supabase/update-password.tsx @@ -0,0 +1,16 @@ +import { SupabaseUpdatePasswordView } from 'src/auth/view/supabase'; +import { CONFIG } from 'src/global-config'; + +// ---------------------------------------------------------------------- + +const metadata = { title: `Update password | Supabase - ${CONFIG.appName}` }; + +export default function Page() { + return ( + <> + {metadata.title} + + + + ); +} diff --git a/03_source/frontend/src/pages/party-user-auth/supabase/verify.tsx b/03_source/frontend/src/pages/party-user-auth/supabase/verify.tsx new file mode 100644 index 0000000..87187f1 --- /dev/null +++ b/03_source/frontend/src/pages/party-user-auth/supabase/verify.tsx @@ -0,0 +1,16 @@ +import { SupabaseVerifyView } from 'src/auth/view/supabase'; +import { CONFIG } from 'src/global-config'; + +// ---------------------------------------------------------------------- + +const metadata = { title: `Verify | Supabase - ${CONFIG.appName}` }; + +export default function Page() { + return ( + <> + {metadata.title} + + + + ); +} diff --git a/03_source/frontend/src/routes/paths.ts b/03_source/frontend/src/routes/paths.ts index cdae1c5..44c2a1d 100644 --- a/03_source/frontend/src/routes/paths.ts +++ b/03_source/frontend/src/routes/paths.ts @@ -10,6 +10,8 @@ const ROOTS = { AUTH: '/auth', AUTH_DEMO: '/auth-demo', DASHBOARD: '/dashboard', + // + PARTY_USER_AUTH: '/party-user-auth', }; // ---------------------------------------------------------------------- @@ -207,4 +209,32 @@ export const paths = { demo: { edit: `${ROOTS.DASHBOARD}/party-user/${MOCK_ID}/edit` }, }, }, + partyUserAuth: { + jwt: { + signIn: `${ROOTS.PARTY_USER_AUTH}/jwt/sign-in`, + signUp: `${ROOTS.PARTY_USER_AUTH}/jwt/sign-up`, + }, + // + // amplify: { + // signIn: `${ROOTS.PARTY_USER_AUTH}/amplify/sign-in`, + // verify: `${ROOTS.PARTY_USER_AUTH}/amplify/verify`, + // signUp: `${ROOTS.PARTY_USER_AUTH}/amplify/sign-up`, + // updatePassword: `${ROOTS.PARTY_USER_AUTH}/amplify/update-password`, + // resetPassword: `${ROOTS.PARTY_USER_AUTH}/amplify/reset-password`, + // }, + // firebase: { + // signIn: `${ROOTS.PARTY_USER_AUTH}/firebase/sign-in`, + // verify: `${ROOTS.PARTY_USER_AUTH}/firebase/verify`, + // signUp: `${ROOTS.PARTY_USER_AUTH}/firebase/sign-up`, + // resetPassword: `${ROOTS.PARTY_USER_AUTH}/firebase/reset-password`, + // }, + // auth0: { signIn: `${ROOTS.PARTY_USER_AUTH}/auth0/sign-in` }, + // supabase: { + // signIn: `${ROOTS.PARTY_USER_AUTH}/supabase/sign-in`, + // verify: `${ROOTS.PARTY_USER_AUTH}/supabase/verify`, + // signUp: `${ROOTS.PARTY_USER_AUTH}/supabase/sign-up`, + // updatePassword: `${ROOTS.PARTY_USER_AUTH}/supabase/update-password`, + // resetPassword: `${ROOTS.PARTY_USER_AUTH}/supabase/reset-password`, + // }, + }, }; diff --git a/03_source/frontend/src/routes/sections/index.tsx b/03_source/frontend/src/routes/sections/index.tsx index b6d1b06..bba9daf 100644 --- a/03_source/frontend/src/routes/sections/index.tsx +++ b/03_source/frontend/src/routes/sections/index.tsx @@ -9,6 +9,7 @@ import { authDemoRoutes } from './auth-demo'; import { componentsRoutes } from './components'; import { dashboardRoutes } from './dashboard'; import { mainRoutes } from './main'; +import { partyUserAuthRoutes } from './party-user-auth'; // ---------------------------------------------------------------------- @@ -48,6 +49,9 @@ export const routesSection: RouteObject[] = [ // Components ...componentsRoutes, + // party-user-auth + ...partyUserAuthRoutes, + // No match { path: '*', element: }, ]; diff --git a/03_source/frontend/src/routes/sections/party-user-auth.tsx b/03_source/frontend/src/routes/sections/party-user-auth.tsx new file mode 100644 index 0000000..9de7a0c --- /dev/null +++ b/03_source/frontend/src/routes/sections/party-user-auth.tsx @@ -0,0 +1,282 @@ +import { lazy, Suspense } from 'react'; +import type { RouteObject } from 'react-router'; +import { Outlet } from 'react-router'; +import { GuestGuard } from 'src/auth/guard'; +import { SplashScreen } from 'src/components/loading-screen'; +import { AuthSplitLayout } from 'src/layouts/auth-split'; + +// ---------------------------------------------------------------------- + +/** ************************************** + * Jwt + *************************************** */ +const Jwt = { + SignInPage: lazy(() => import('src/pages/party-user-auth/jwt/sign-in')), + SignUpPage: lazy(() => import('src/pages/party-user-auth/jwt/sign-up')), +}; + +const authJwt = { + path: 'jwt', + children: [ + { + path: 'sign-in', + element: ( + + + + + + ), + }, + { + path: 'sign-up', + element: ( + + + + + + ), + }, + ], +}; + +/** ************************************** + * Amplify + *************************************** */ +const Amplify = { + SignInPage: lazy(() => import('src/pages/auth/amplify/sign-in')), + SignUpPage: lazy(() => import('src/pages/auth/amplify/sign-up')), + VerifyPage: lazy(() => import('src/pages/auth/amplify/verify')), + UpdatePasswordPage: lazy(() => import('src/pages/auth/amplify/update-password')), + ResetPasswordPage: lazy(() => import('src/pages/auth/amplify/reset-password')), +}; + +const authAmplify = { + path: 'amplify', + children: [ + { + path: 'sign-in', + element: ( + + + + + + ), + }, + { + path: 'sign-up', + element: ( + + + + + + ), + }, + { + path: 'verify', + element: ( + + + + ), + }, + { + path: 'reset-password', + element: ( + + + + ), + }, + { + path: 'update-password', + element: ( + + + + ), + }, + ], +}; + +/** ************************************** + * Firebase + *************************************** */ +const Firebase = { + SignInPage: lazy(() => import('src/pages/auth/firebase/sign-in')), + SignUpPage: lazy(() => import('src/pages/auth/firebase/sign-up')), + VerifyPage: lazy(() => import('src/pages/auth/firebase/verify')), + ResetPasswordPage: lazy(() => import('src/pages/auth/firebase/reset-password')), +}; + +const authFirebase = { + path: 'firebase', + children: [ + { + path: 'sign-in', + element: ( + + + + + + ), + }, + { + path: 'sign-up', + element: ( + + + + + + ), + }, + { + path: 'verify', + element: ( + + + + ), + }, + { + path: 'reset-password', + element: ( + + + + ), + }, + ], +}; + +/** ************************************** + * Auth0 + *************************************** */ +const Auth0 = { + SignInPage: lazy(() => import('src/pages/auth/auth0/sign-in')), + CallbackPage: lazy(() => import('src/pages/auth/auth0/callback')), +}; + +const authAuth0 = { + path: 'auth0', + children: [ + { + path: 'sign-in', + element: ( + + + + + + ), + }, + { + path: 'callback', + element: ( + + + + ), + }, + ], +}; + +/** ************************************** + * Supabase + *************************************** */ +const Supabase = { + SignInPage: lazy(() => import('src/pages/auth/supabase/sign-in')), + SignUpPage: lazy(() => import('src/pages/auth/supabase/sign-up')), + VerifyPage: lazy(() => import('src/pages/auth/supabase/verify')), + UpdatePasswordPage: lazy(() => import('src/pages/auth/supabase/update-password')), + ResetPasswordPage: lazy(() => import('src/pages/auth/supabase/reset-password')), +}; + +const authSupabase = { + path: 'supabase', + children: [ + { + path: 'sign-in', + element: ( + + + + + + ), + }, + { + path: 'sign-up', + element: ( + + + + + + ), + }, + { + path: 'verify', + element: ( + + + + ), + }, + { + path: 'reset-password', + element: ( + + + + ), + }, + { + path: 'update-password', + element: ( + + + + ), + }, + ], +}; + +// ---------------------------------------------------------------------- + +export const partyUserAuthRoutes: RouteObject[] = [ + { + path: 'party-user-auth', + element: ( + }> + + + ), + children: [authJwt, authAmplify, authFirebase, authAuth0, authSupabase], + }, +]; diff --git a/03_source/frontend/src/sections/overview/app/view/overview-app-view.tsx b/03_source/frontend/src/sections/overview/app/view/overview-app-view.tsx index 25eae4f..ee02d92 100644 --- a/03_source/frontend/src/sections/overview/app/view/overview-app-view.tsx +++ b/03_source/frontend/src/sections/overview/app/view/overview-app-view.tsx @@ -83,6 +83,44 @@ export function OverviewAppView() { /> + + + + + + + + + + +