init commit,

This commit is contained in:
louiscklaw
2025-05-28 09:55:51 +08:00
commit efe70ceb69
8042 changed files with 951668 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
import type { Theme, SxProps } from '@mui/material/styles';
import Divider from '@mui/material/Divider';
// ----------------------------------------------------------------------
type FormDividerProps = {
sx?: SxProps<Theme>;
label?: React.ReactNode;
};
export function FormDivider({ sx, label = 'OR' }: FormDividerProps) {
return (
<Divider
sx={[
() => ({
my: 3,
typography: 'overline',
color: 'text.disabled',
'&::before, :after': { borderTopStyle: 'dashed' },
}),
...(Array.isArray(sx) ? sx : [sx]),
]}
>
{label}
</Divider>
);
}

View File

@@ -0,0 +1,47 @@
import type { BoxProps } from '@mui/material/Box';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
// ----------------------------------------------------------------------
type FormHeadProps = BoxProps & {
icon?: React.ReactNode;
title: React.ReactNode;
description?: React.ReactNode;
};
export function FormHead({ sx, icon, title, description, ...other }: FormHeadProps) {
return (
<>
{icon && (
<Box component="span" sx={{ mb: 3, mx: 'auto', display: 'inline-flex' }}>
{icon}
</Box>
)}
<Box
sx={[
() => ({
mb: 5,
gap: 1.5,
display: 'flex',
textAlign: 'center',
whiteSpace: 'pre-line',
flexDirection: 'column',
}),
...(Array.isArray(sx) ? sx : [sx]),
]}
{...other}
>
<Typography variant="h5">{title}</Typography>
{description && (
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{description}
</Typography>
)}
</Box>
</>
);
}

View File

@@ -0,0 +1,46 @@
import type { BoxProps } from '@mui/material/Box';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
// ----------------------------------------------------------------------
type FormResendCodeProps = BoxProps & {
value?: number;
disabled?: boolean;
onResendCode?: () => void;
};
export function FormResendCode({
value,
disabled,
onResendCode,
sx,
...other
}: FormResendCodeProps) {
return (
<Box
sx={[
() => ({
mt: 3,
typography: 'body2',
alignSelf: 'center',
}),
...(Array.isArray(sx) ? sx : [sx]),
]}
{...other}
>
{`Dont have a code? `}
<Link
variant="subtitle2"
onClick={onResendCode}
sx={{
cursor: 'pointer',
...(disabled && { color: 'text.disabled', pointerEvents: 'none' }),
}}
>
Resend {disabled && value && value > 0 && `(${value}s)`}
</Link>
</Box>
);
}

View File

@@ -0,0 +1,41 @@
import type { LinkProps } from '@mui/material/Link';
import Link from '@mui/material/Link';
import { RouterLink } from 'src/routes/components';
import { Iconify } from 'src/components/iconify';
// ----------------------------------------------------------------------
type FormReturnLinkProps = LinkProps & {
href: string;
icon?: React.ReactNode;
label?: React.ReactNode;
};
export function FormReturnLink({ sx, href, label, icon, children, ...other }: FormReturnLinkProps) {
return (
<Link
component={RouterLink}
href={href}
color="inherit"
variant="subtitle2"
sx={[
{
mt: 3,
gap: 0.5,
mx: 'auto',
alignItems: 'center',
display: 'inline-flex',
},
...(Array.isArray(sx) ? sx : [sx]),
]}
{...other}
>
{icon || <Iconify width={16} icon="eva:arrow-ios-back-fill" />}
{label || 'Return to sign in'}
{children}
</Link>
);
}

View File

@@ -0,0 +1,46 @@
import type { BoxProps } from '@mui/material/Box';
import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton';
import { Iconify } from 'src/components/iconify';
// ----------------------------------------------------------------------
type FormSocialsProps = BoxProps & {
signInWithGoogle?: () => void;
singInWithGithub?: () => void;
signInWithTwitter?: () => void;
};
export function FormSocials({
sx,
signInWithGoogle,
singInWithGithub,
signInWithTwitter,
...other
}: FormSocialsProps) {
return (
<Box
sx={[
{
gap: 1.5,
display: 'flex',
justifyContent: 'center',
},
...(Array.isArray(sx) ? sx : [sx]),
]}
{...other}
>
<IconButton color="inherit" onClick={signInWithGoogle}>
<Iconify width={22} icon="socials:google" />
</IconButton>
<IconButton color="inherit" onClick={singInWithGithub}>
<Iconify width={22} icon="socials:github" />
</IconButton>
<IconButton color="inherit" onClick={signInWithTwitter}>
<Iconify width={22} icon="socials:twitter" />
</IconButton>
</Box>
);
}

View File

@@ -0,0 +1,35 @@
import type { BoxProps } from '@mui/material/Box';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
// ----------------------------------------------------------------------
export function SignUpTerms({ sx, ...other }: BoxProps) {
return (
<Box
component="span"
sx={[
() => ({
mt: 3,
display: 'block',
textAlign: 'center',
typography: 'caption',
color: 'text.secondary',
}),
...(Array.isArray(sx) ? sx : [sx]),
]}
{...other}
>
{'By signing up, I agree to '}
<Link underline="always" color="text.primary">
Terms of service
</Link>
{' and '}
<Link underline="always" color="text.primary">
Privacy policy
</Link>
.
</Box>
);
}

View File

@@ -0,0 +1,97 @@
import type {
SignUpInput,
SignInInput,
ConfirmSignUpInput,
ResetPasswordInput,
ResendSignUpCodeInput,
ConfirmResetPasswordInput,
} from 'aws-amplify/auth';
import {
signIn as _signIn,
signUp as _signUp,
signOut as _signOut,
confirmSignUp as _confirmSignUp,
resetPassword as _resetPassword,
resendSignUpCode as _resendSignUpCode,
confirmResetPassword as _confirmResetPassword,
} from 'aws-amplify/auth';
// ----------------------------------------------------------------------
export type SignInParams = SignInInput;
export type SignUpParams = SignUpInput & { firstName: string; lastName: string };
export type ResendSignUpCodeParams = ResendSignUpCodeInput;
export type ConfirmSignUpParams = ConfirmSignUpInput;
export type ResetPasswordParams = ResetPasswordInput;
export type ConfirmResetPasswordParams = ConfirmResetPasswordInput;
/** **************************************
* Sign in
*************************************** */
export const signInWithPassword = async ({ username, password }: SignInParams): Promise<void> => {
await _signIn({ username, password });
};
/** **************************************
* Sign up
*************************************** */
export const signUp = async ({
username,
password,
firstName,
lastName,
}: SignUpParams): Promise<void> => {
await _signUp({
username,
password,
options: { userAttributes: { email: username, given_name: firstName, family_name: lastName } },
});
};
/** **************************************
* Confirm sign up
*************************************** */
export const confirmSignUp = async ({
username,
confirmationCode,
}: ConfirmSignUpParams): Promise<void> => {
await _confirmSignUp({ username, confirmationCode });
};
/** **************************************
* Resend code sign up
*************************************** */
export const resendSignUpCode = async ({ username }: ResendSignUpCodeParams): Promise<void> => {
await _resendSignUpCode({ username });
};
/** **************************************
* Sign out
*************************************** */
export const signOut = async (): Promise<void> => {
await _signOut();
};
/** **************************************
* Reset password
*************************************** */
export const resetPassword = async ({ username }: ResetPasswordParams): Promise<void> => {
await _resetPassword({ username });
};
/** **************************************
* Update password
*************************************** */
export const updatePassword = async ({
username,
confirmationCode,
newPassword,
}: ConfirmResetPasswordParams): Promise<void> => {
await _confirmResetPassword({ username, confirmationCode, newPassword });
};

View File

@@ -0,0 +1,99 @@
import { Amplify } from 'aws-amplify';
import { useSetState } from 'minimal-shared/hooks';
import { useMemo, useEffect, useCallback } from 'react';
import { fetchAuthSession, fetchUserAttributes } from 'aws-amplify/auth';
import axios from 'src/lib/axios';
import { CONFIG } from 'src/global-config';
import { AuthContext } from '../auth-context';
import type { AuthState } from '../../types';
// ----------------------------------------------------------------------
/**
* NOTE:
* We only build demo at basic level.
* Customer will need to do some extra handling yourself if you want to extend the logic and other features...
*/
/**
* Docs:
* https://docs.amplify.aws/react/build-a-backend/auth/manage-user-session/
*/
Amplify.configure({
Auth: {
Cognito: {
userPoolId: CONFIG.amplify.userPoolId,
userPoolClientId: CONFIG.amplify.userPoolWebClientId,
},
},
});
// ----------------------------------------------------------------------
type Props = {
children: React.ReactNode;
};
export function AuthProvider({ children }: Props) {
const { state, setState } = useSetState<AuthState>({ user: null, loading: true });
const checkUserSession = useCallback(async () => {
try {
const authSession = (await fetchAuthSession({ forceRefresh: true })).tokens;
if (authSession) {
const userAttributes = await fetchUserAttributes();
const accessToken = authSession.accessToken.toString();
setState({ user: { ...authSession, ...userAttributes }, loading: false });
axios.defaults.headers.common.Authorization = `Bearer ${accessToken}`;
} else {
setState({ user: null, loading: false });
delete axios.defaults.headers.common.Authorization;
}
} catch (error) {
console.error(error);
setState({ user: null, loading: false });
}
}, [setState]);
useEffect(() => {
checkUserSession();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// ----------------------------------------------------------------------
const checkAuthenticated = state.user ? 'authenticated' : 'unauthenticated';
const status = state.loading ? 'loading' : checkAuthenticated;
const memoizedValue = useMemo(
() => ({
user: state.user
? {
...state.user,
id: state.user?.sub,
accessToken: state.user?.accessToken?.toString(),
displayName:
state.user?.given_name &&
state.user?.family_name &&
`${state.user?.given_name} ${state.user?.family_name}`,
role: state.user?.role ?? 'admin',
}
: null,
checkUserSession,
loading: status === 'loading',
authenticated: status === 'authenticated',
unauthenticated: status === 'unauthenticated',
}),
[checkUserSession, state.user, status]
);
return <AuthContext value={memoizedValue}>{children}</AuthContext>;
}

View File

@@ -0,0 +1,3 @@
export * from './action';
export * from './auth-provider';

View File

@@ -0,0 +1,7 @@
import { createContext } from 'react';
import type { AuthContextValue } from '../types';
// ----------------------------------------------------------------------
export const AuthContext = createContext<AuthContextValue | undefined>(undefined);

View File

@@ -0,0 +1,94 @@
import type { AppState } from '@auth0/auth0-react';
import { useAuth0, Auth0Provider } from '@auth0/auth0-react';
import { useMemo, useState, useEffect, useCallback } from 'react';
import axios from 'src/lib/axios';
import { CONFIG } from 'src/global-config';
import { AuthContext } from '../auth-context';
// ----------------------------------------------------------------------
type Props = {
children: React.ReactNode;
};
export function AuthProvider({ children }: Props) {
const { domain, clientId, callbackUrl } = CONFIG.auth0;
const onRedirectCallback = useCallback((appState?: AppState) => {
window.location.replace(appState?.returnTo || window.location.pathname);
}, []);
if (!(domain && clientId && callbackUrl)) {
return null;
}
return (
<Auth0Provider
domain={domain}
clientId={clientId}
authorizationParams={{ redirect_uri: callbackUrl }}
onRedirectCallback={onRedirectCallback}
cacheLocation="localstorage"
>
<AuthProviderContainer>{children}</AuthProviderContainer>
</Auth0Provider>
);
}
// ----------------------------------------------------------------------
function AuthProviderContainer({ children }: Props) {
const { user, isLoading, isAuthenticated, getAccessTokenSilently } = useAuth0();
const [accessToken, setAccessToken] = useState<string | null>(null);
const getAccessToken = useCallback(async () => {
try {
if (isAuthenticated) {
const token = await getAccessTokenSilently();
setAccessToken(token);
axios.defaults.headers.common.Authorization = `Bearer ${token}`;
} else {
setAccessToken(null);
delete axios.defaults.headers.common.Authorization;
}
} catch (error) {
console.error(error);
}
}, [getAccessTokenSilently, isAuthenticated]);
useEffect(() => {
getAccessToken();
}, [getAccessToken]);
// ----------------------------------------------------------------------
const checkAuthenticated = isAuthenticated ? 'authenticated' : 'unauthenticated';
const status = isLoading ? 'loading' : checkAuthenticated;
const memoizedValue = useMemo(
() => ({
user: user
? {
...user,
id: user?.sub,
accessToken,
displayName: user?.name,
photoURL: user?.picture,
role: user?.role ?? 'admin',
}
: null,
loading: status === 'loading',
authenticated: status === 'authenticated',
unauthenticated: status === 'unauthenticated',
}),
[accessToken, status, user]
);
return <AuthContext value={memoizedValue}>{children}</AuthContext>;
}

View File

@@ -0,0 +1 @@
export * from './auth-provider';

View File

@@ -0,0 +1,110 @@
import { doc, setDoc, collection } from 'firebase/firestore';
import {
signOut as _signOut,
signInWithPopup as _signInWithPopup,
GoogleAuthProvider as _GoogleAuthProvider,
GithubAuthProvider as _GithubAuthProvider,
TwitterAuthProvider as _TwitterAuthProvider,
sendEmailVerification as _sendEmailVerification,
sendPasswordResetEmail as _sendPasswordResetEmail,
signInWithEmailAndPassword as _signInWithEmailAndPassword,
createUserWithEmailAndPassword as _createUserWithEmailAndPassword,
} from 'firebase/auth';
import { AUTH, FIRESTORE } from 'src/lib/firebase';
// ----------------------------------------------------------------------
export type SignInParams = {
email: string;
password: string;
};
export type SignUpParams = {
email: string;
password: string;
firstName: string;
lastName: string;
};
export type ForgotPasswordParams = {
email: string;
};
/** **************************************
* Sign in
*************************************** */
export const signInWithPassword = async ({ email, password }: SignInParams): Promise<void> => {
try {
await _signInWithEmailAndPassword(AUTH, email, password);
const user = AUTH.currentUser;
if (!user?.emailVerified) {
throw new Error('Email not verified!');
}
} catch (error) {
console.error('Error during sign in with password:', error);
throw error;
}
};
export const signInWithGoogle = async (): Promise<void> => {
const provider = new _GoogleAuthProvider();
await _signInWithPopup(AUTH, provider);
};
export const signInWithGithub = async (): Promise<void> => {
const provider = new _GithubAuthProvider();
await _signInWithPopup(AUTH, provider);
};
export const signInWithTwitter = async (): Promise<void> => {
const provider = new _TwitterAuthProvider();
await _signInWithPopup(AUTH, provider);
};
/** **************************************
* Sign up
*************************************** */
export const signUp = async ({
email,
password,
firstName,
lastName,
}: SignUpParams): Promise<void> => {
try {
const newUser = await _createUserWithEmailAndPassword(AUTH, email, password);
/*
* (1) If skip emailVerified
* Remove : await _sendEmailVerification(newUser.user);
*/
await _sendEmailVerification(newUser.user);
const userProfile = doc(collection(FIRESTORE, 'users'), newUser.user?.uid);
await setDoc(userProfile, {
uid: newUser.user?.uid,
email,
displayName: `${firstName} ${lastName}`,
});
} catch (error) {
console.error('Error during sign up:', error);
throw error;
}
};
/** **************************************
* Sign out
*************************************** */
export const signOut = async (): Promise<void> => {
await _signOut(AUTH);
};
/** **************************************
* Reset password
*************************************** */
export const sendPasswordResetEmail = async ({ email }: ForgotPasswordParams): Promise<void> => {
await _sendPasswordResetEmail(AUTH, email);
};

View File

@@ -0,0 +1,89 @@
import { doc, getDoc } from 'firebase/firestore';
import { onAuthStateChanged } from 'firebase/auth';
import { useSetState } from 'minimal-shared/hooks';
import { useMemo, useEffect, useCallback } from 'react';
import axios from 'src/lib/axios';
import { AUTH, FIRESTORE } from 'src/lib/firebase';
import { AuthContext } from '../auth-context';
import type { AuthState } from '../../types';
// ----------------------------------------------------------------------
/**
* NOTE:
* We only build demo at basic level.
* Customer will need to do some extra handling yourself if you want to extend the logic and other features...
*/
type Props = {
children: React.ReactNode;
};
export function AuthProvider({ children }: Props) {
const { state, setState } = useSetState<AuthState>({ user: null, loading: true });
const checkUserSession = useCallback(async () => {
try {
onAuthStateChanged(AUTH, async (user: AuthState['user']) => {
if (user && user.emailVerified) {
/*
* (1) If skip emailVerified
* Remove the condition (if/else) : user.emailVerified
*/
const userProfile = doc(FIRESTORE, 'users', user.uid);
const docSnap = await getDoc(userProfile);
const profileData = docSnap.data();
const { accessToken } = user;
setState({ user: { ...user, ...profileData }, loading: false });
axios.defaults.headers.common.Authorization = `Bearer ${accessToken}`;
} else {
setState({ user: null, loading: false });
delete axios.defaults.headers.common.Authorization;
}
});
} catch (error) {
console.error(error);
setState({ user: null, loading: false });
}
}, [setState]);
useEffect(() => {
checkUserSession();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// ----------------------------------------------------------------------
const checkAuthenticated = state.user ? 'authenticated' : 'unauthenticated';
const status = state.loading ? 'loading' : checkAuthenticated;
const memoizedValue = useMemo(
() => ({
user: state.user
? {
...state.user,
id: state.user?.uid,
accessToken: state.user?.accessToken,
displayName: state.user?.displayName,
photoURL: state.user?.photoURL,
role: state.user?.role ?? 'admin',
}
: null,
checkUserSession,
loading: status === 'loading',
authenticated: status === 'authenticated',
unauthenticated: status === 'unauthenticated',
}),
[checkUserSession, state.user, status]
);
return <AuthContext value={memoizedValue}>{children}</AuthContext>;
}

View File

@@ -0,0 +1,3 @@
export * from './action';
export * from './auth-provider';

View File

@@ -0,0 +1,84 @@
import axios, { endpoints } from 'src/lib/axios';
import { setSession } from './utils';
import { JWT_STORAGE_KEY } from './constant';
// ----------------------------------------------------------------------
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<void> => {
try {
const params = { email, password };
const res = await axios.post(endpoints.auth.signIn, params);
const { accessToken } = res.data;
if (!accessToken) {
throw new Error('Access token not found in response');
}
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<void> => {
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<void> => {
try {
await setSession(null);
} catch (error) {
console.error('Error during sign out:', error);
throw error;
}
};

View File

@@ -0,0 +1,71 @@
import { useSetState } from 'minimal-shared/hooks';
import { useMemo, useEffect, useCallback } from 'react';
import axios, { endpoints } from 'src/lib/axios';
import { JWT_STORAGE_KEY } from './constant';
import { AuthContext } from '../auth-context';
import { setSession, isValidToken } from './utils';
import type { AuthState } from '../../types';
// ----------------------------------------------------------------------
/**
* NOTE:
* We only build demo at basic level.
* Customer will need to do some extra handling yourself if you want to extend the logic and other features...
*/
type Props = {
children: React.ReactNode;
};
export function AuthProvider({ children }: Props) {
const { state, setState } = useSetState<AuthState>({ user: null, loading: true });
const checkUserSession = useCallback(async () => {
try {
const accessToken = sessionStorage.getItem(JWT_STORAGE_KEY);
if (accessToken && isValidToken(accessToken)) {
setSession(accessToken);
const res = await axios.get(endpoints.auth.me);
const { user } = res.data;
setState({ user: { ...user, accessToken }, loading: false });
} else {
setState({ user: null, loading: false });
}
} catch (error) {
console.error(error);
setState({ user: null, loading: false });
}
}, [setState]);
useEffect(() => {
checkUserSession();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// ----------------------------------------------------------------------
const checkAuthenticated = state.user ? 'authenticated' : 'unauthenticated';
const status = state.loading ? 'loading' : checkAuthenticated;
const memoizedValue = useMemo(
() => ({
user: state.user ? { ...state.user, role: state.user?.role ?? 'admin' } : null,
checkUserSession,
loading: status === 'loading',
authenticated: status === 'authenticated',
unauthenticated: status === 'unauthenticated',
}),
[checkUserSession, state.user, status]
);
return <AuthContext value={memoizedValue}>{children}</AuthContext>;
}

View File

@@ -0,0 +1 @@
export const JWT_STORAGE_KEY = 'jwt_access_token';

View File

@@ -0,0 +1,7 @@
export * from './utils';
export * from './action';
export * from './constant';
export * from './auth-provider';

View File

@@ -0,0 +1,94 @@
import { paths } from 'src/routes/paths';
import axios from 'src/lib/axios';
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);
}
// ----------------------------------------------------------------------
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;
}
}

View File

@@ -0,0 +1,138 @@
import type {
AuthError,
AuthResponse,
UserResponse,
AuthTokenResponsePassword,
SignInWithPasswordCredentials,
SignUpWithPasswordCredentials,
} from '@supabase/supabase-js';
import { paths } from 'src/routes/paths';
import { supabase } from 'src/lib/supabase';
// ----------------------------------------------------------------------
export type SignInParams = {
email: string;
password: string;
options?: SignInWithPasswordCredentials['options'];
};
export type SignUpParams = {
email: string;
password: string;
firstName: string;
lastName: string;
options?: SignUpWithPasswordCredentials['options'];
};
export type ResetPasswordParams = {
email: string;
options?: {
redirectTo?: string;
captchaToken?: string;
};
};
export type UpdatePasswordParams = {
password: string;
options?: {
emailRedirectTo?: string | undefined;
};
};
/** **************************************
* Sign in
*************************************** */
export const signInWithPassword = async ({
email,
password,
}: SignInParams): Promise<AuthTokenResponsePassword> => {
const { data, error } = await supabase.auth.signInWithPassword({ email, password });
if (error) {
console.error(error);
throw error;
}
return { data, error };
};
/** **************************************
* Sign up
*************************************** */
export const signUp = async ({
email,
password,
firstName,
lastName,
}: SignUpParams): Promise<AuthResponse> => {
const { data, error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${window.location.origin}${paths.dashboard.root}`,
data: { display_name: `${firstName} ${lastName}` },
},
});
if (error) {
console.error(error);
throw error;
}
if (!data?.user?.identities?.length) {
throw new Error('This user already exists');
}
return { data, error };
};
/** **************************************
* Sign out
*************************************** */
export const signOut = async (): Promise<{
error: AuthError | null;
}> => {
const { error } = await supabase.auth.signOut();
if (error) {
console.error(error);
throw error;
}
return { error };
};
/** **************************************
* Reset password
*************************************** */
export const resetPassword = async ({
email,
}: ResetPasswordParams): Promise<{ data: {}; error: null } | { data: null; error: AuthError }> => {
const { data, error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${window.location.origin}${paths.auth.supabase.updatePassword}`,
});
if (error) {
console.error(error);
throw error;
}
return { data, error };
};
/** **************************************
* Update password
*************************************** */
export const updatePassword = async ({ password }: UpdatePasswordParams): Promise<UserResponse> => {
const { data, error } = await supabase.auth.updateUser({ password });
if (error) {
console.error(error);
throw error;
}
return { data, error };
};

View File

@@ -0,0 +1,85 @@
import { useSetState } from 'minimal-shared/hooks';
import { useMemo, useEffect, useCallback } from 'react';
import axios from 'src/lib/axios';
import { supabase } from 'src/lib/supabase';
import { AuthContext } from '../auth-context';
import type { AuthState } from '../../types';
// ----------------------------------------------------------------------
/**
* NOTE:
* We only build demo at basic level.
* Customer will need to do some extra handling yourself if you want to extend the logic and other features...
*/
type Props = {
children: React.ReactNode;
};
export function AuthProvider({ children }: Props) {
const { state, setState } = useSetState<AuthState>({ user: null, loading: true });
const checkUserSession = useCallback(async () => {
try {
const {
data: { session },
error,
} = await supabase.auth.getSession();
if (error) {
setState({ user: null, loading: false });
console.error(error);
throw error;
}
if (session) {
const accessToken = session?.access_token;
setState({ user: { ...session, ...session?.user }, loading: false });
axios.defaults.headers.common.Authorization = `Bearer ${accessToken}`;
} else {
setState({ user: null, loading: false });
delete axios.defaults.headers.common.Authorization;
}
} catch (error) {
console.error(error);
setState({ user: null, loading: false });
}
}, [setState]);
useEffect(() => {
checkUserSession();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// ----------------------------------------------------------------------
const checkAuthenticated = state.user ? 'authenticated' : 'unauthenticated';
const status = state.loading ? 'loading' : checkAuthenticated;
const memoizedValue = useMemo(
() => ({
user: state.user
? {
...state.user,
id: state.user?.id,
accessToken: state.user?.access_token,
displayName: state.user?.user_metadata.display_name,
role: state.user?.role ?? 'admin',
}
: null,
checkUserSession,
loading: status === 'loading',
authenticated: status === 'authenticated',
unauthenticated: status === 'unauthenticated',
}),
[checkUserSession, state.user, status]
);
return <AuthContext value={memoizedValue}>{children}</AuthContext>;
}

View File

@@ -0,0 +1,3 @@
export * from './action';
export * from './auth-provider';

View File

@@ -0,0 +1,68 @@
import { useState, useEffect } from 'react';
import { paths } from 'src/routes/paths';
import { useRouter, usePathname } from 'src/routes/hooks';
import { CONFIG } from 'src/global-config';
import { SplashScreen } from 'src/components/loading-screen';
import { useAuthContext } from '../hooks';
// ----------------------------------------------------------------------
type AuthGuardProps = {
children: React.ReactNode;
};
const signInPaths = {
jwt: paths.auth.jwt.signIn,
auth0: paths.auth.auth0.signIn,
amplify: paths.auth.amplify.signIn,
firebase: paths.auth.firebase.signIn,
supabase: paths.auth.supabase.signIn,
};
export function AuthGuard({ children }: AuthGuardProps) {
const router = useRouter();
const pathname = usePathname();
const { authenticated, loading } = useAuthContext();
const [isChecking, setIsChecking] = useState(true);
const createRedirectPath = (currentPath: string) => {
const queryString = new URLSearchParams({ returnTo: pathname }).toString();
return `${currentPath}?${queryString}`;
};
const checkPermissions = async (): Promise<void> => {
if (loading) {
return;
}
if (!authenticated) {
const { method } = CONFIG.auth;
const signInPath = signInPaths[method];
const redirectPath = createRedirectPath(signInPath);
router.replace(redirectPath);
return;
}
setIsChecking(false);
};
useEffect(() => {
checkPermissions();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [authenticated, loading]);
if (isChecking) {
return <SplashScreen />;
}
return <>{children}</>;
}

View File

@@ -0,0 +1,51 @@
import { useState, useEffect } from 'react';
import { useSearchParams } from 'src/routes/hooks';
import { CONFIG } from 'src/global-config';
import { SplashScreen } from 'src/components/loading-screen';
import { useAuthContext } from '../hooks';
// ----------------------------------------------------------------------
type GuestGuardProps = {
children: React.ReactNode;
};
export function GuestGuard({ children }: GuestGuardProps) {
const { loading, authenticated } = useAuthContext();
const searchParams = useSearchParams();
const returnTo = searchParams.get('returnTo') || CONFIG.auth.redirectPath;
const [isChecking, setIsChecking] = useState(true);
const checkPermissions = async (): Promise<void> => {
if (loading) {
return;
}
if (authenticated) {
// Redirect authenticated users to the returnTo path
// Using `window.location.href` instead of `router.replace` to avoid unnecessary re-rendering
// that might be caused by the AuthGuard component
window.location.href = returnTo;
return;
}
setIsChecking(false);
};
useEffect(() => {
checkPermissions();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [authenticated, loading]);
if (isChecking) {
return <SplashScreen />;
}
return <>{children}</>;
}

View File

@@ -0,0 +1,5 @@
export * from './auth-guard';
export * from './guest-guard';
export * from './role-based-guard';

View File

@@ -0,0 +1,61 @@
import type { Theme, SxProps } from '@mui/material/styles';
import { m } from 'framer-motion';
import Container from '@mui/material/Container';
import Typography from '@mui/material/Typography';
import { ForbiddenIllustration } from 'src/assets/illustrations';
import { varBounce, MotionContainer } from 'src/components/animate';
// ----------------------------------------------------------------------
/**
* NOTE:
* This component is for reference only.
* You can customize the logic and conditions to better suit your application's requirements.
*/
export type RoleBasedGuardProp = {
sx?: SxProps<Theme>;
currentRole: string;
hasContent?: boolean;
allowedRoles: string | string[];
children: React.ReactNode;
};
export function RoleBasedGuard({
sx,
children,
hasContent,
currentRole,
allowedRoles,
}: RoleBasedGuardProp) {
if (currentRole && allowedRoles && !allowedRoles.includes(currentRole)) {
return hasContent ? (
<Container
component={MotionContainer}
sx={[{ textAlign: 'center' }, ...(Array.isArray(sx) ? sx : [sx])]}
>
<m.div variants={varBounce('in')}>
<Typography variant="h3" sx={{ mb: 2 }}>
Permission denied
</Typography>
</m.div>
<m.div variants={varBounce('in')}>
<Typography sx={{ color: 'text.secondary' }}>
You do not have permission to access this page.
</Typography>
</m.div>
<m.div variants={varBounce('in')}>
<ForbiddenIllustration sx={{ my: { xs: 5, sm: 10 } }} />
</m.div>
</Container>
) : null;
}
return <> {children} </>;
}

View File

@@ -0,0 +1,3 @@
export * from './use-mocked-user';
export * from './use-auth-context';

View File

@@ -0,0 +1,15 @@
import { use } from 'react';
import { AuthContext } from '../context/auth-context';
// ----------------------------------------------------------------------
export function useAuthContext() {
const context = use(AuthContext);
if (!context) {
throw new Error('useAuthContext: Context must be used inside AuthProvider');
}
return context;
}

View File

@@ -0,0 +1,33 @@
import { _mock } from 'src/_mock';
// To get the user from the <AuthContext/>, you can use
// Change:
// import { useMockedUser } from 'src/auth/hooks';
// const { user } = useMockedUser();
// To:
// import { useAuthContext } from 'src/auth/hooks';
// const { user } = useAuthContext();
// ----------------------------------------------------------------------
export function useMockedUser() {
const user = {
id: '8864c717-587d-472a-929a-8e5f298024da-0',
displayName: 'Jaydon Frankie',
email: 'demo@minimals.cc',
photoURL: _mock.image.avatar(24),
phoneNumber: _mock.phoneNumber(1),
country: _mock.countryNames(1),
address: '90210 Broadway Blvd',
state: 'California',
city: 'San Francisco',
zipCode: '94116',
about: 'Praesent turpis. Phasellus viverra nulla ut metus varius laoreet. Phasellus tempus.',
role: 'admin',
isPublic: true,
};
return { user };
}

View File

@@ -0,0 +1,14 @@
export type UserType = Record<string, any> | null;
export type AuthState = {
user: UserType;
loading: boolean;
};
export type AuthContextValue = {
user: UserType;
loading: boolean;
authenticated: boolean;
unauthenticated: boolean;
checkUserSession?: () => Promise<void>;
};

View File

@@ -0,0 +1,20 @@
// ----------------------------------------------------------------------
export function getErrorMessage(error: unknown): string {
if (error instanceof Error) {
return error.message || error.name || 'An error occurred';
}
if (typeof error === 'string') {
return error;
}
if (typeof error === 'object' && error !== null) {
const errorMessage = (error as { message?: string }).message;
if (typeof errorMessage === 'string') {
return errorMessage;
}
}
return `Unknown error: ${error}`;
}

View File

@@ -0,0 +1 @@
export * from './error-message';

View File

@@ -0,0 +1,104 @@
import { z as zod } from 'zod';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import { paths } from 'src/routes/paths';
import { useRouter } from 'src/routes/hooks';
import { PasswordIcon } from 'src/assets/icons';
import { Form, Field } from 'src/components/hook-form';
import { resetPassword } from '../../context/amplify';
import { FormHead } from '../../components/form-head';
import { FormReturnLink } from '../../components/form-return-link';
// ----------------------------------------------------------------------
export type ResetPasswordSchemaType = zod.infer<typeof ResetPasswordSchema>;
export const ResetPasswordSchema = zod.object({
email: zod
.string()
.min(1, { message: 'Email is required!' })
.email({ message: 'Email must be a valid email address!' }),
});
// ----------------------------------------------------------------------
export function AmplifyResetPasswordView() {
const router = useRouter();
const defaultValues: ResetPasswordSchemaType = {
email: '',
};
const methods = useForm<ResetPasswordSchemaType>({
resolver: zodResolver(ResetPasswordSchema),
defaultValues,
});
const {
handleSubmit,
formState: { isSubmitting },
} = methods;
const createRedirectPath = (query: string) => {
const queryString = new URLSearchParams({ email: query }).toString();
return `${paths.auth.amplify.updatePassword}?${queryString}`;
};
const onSubmit = handleSubmit(async (data) => {
try {
await resetPassword({ username: data.email });
const redirectPath = createRedirectPath(data.email);
router.push(redirectPath);
} catch (error) {
console.error(error);
}
});
const renderForm = () => (
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
<Field.Text
autoFocus
name="email"
label="Email address"
placeholder="example@gmail.com"
slotProps={{ inputLabel: { shrink: true } }}
/>
<Button
fullWidth
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Send request..."
>
Send request
</Button>
</Box>
);
return (
<>
<FormHead
icon={<PasswordIcon />}
title="Forgot your password?"
description={`Please enter the email address associated with your account and we'll email you a link to reset your password.`}
/>
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
<FormReturnLink href={paths.auth.amplify.signIn} />
</>
);
}

View File

@@ -0,0 +1,157 @@
import { z as zod } from 'zod';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { useBoolean } from 'minimal-shared/hooks';
import { zodResolver } from '@hookform/resolvers/zod';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Alert from '@mui/material/Alert';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
import { paths } from 'src/routes/paths';
import { useRouter } from 'src/routes/hooks';
import { RouterLink } from 'src/routes/components';
import { Iconify } from 'src/components/iconify';
import { Form, Field } from 'src/components/hook-form';
import { useAuthContext } from '../../hooks';
import { getErrorMessage } from '../../utils';
import { FormHead } from '../../components/form-head';
import { signInWithPassword } from '../../context/amplify';
// ----------------------------------------------------------------------
export type SignInSchemaType = zod.infer<typeof SignInSchema>;
export 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!' }),
});
// ----------------------------------------------------------------------
export function AmplifySignInView() {
const router = useRouter();
const showPassword = useBoolean();
const { checkUserSession } = useAuthContext();
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const defaultValues: SignInSchemaType = {
email: '',
password: '',
};
const methods = useForm<SignInSchemaType>({
resolver: zodResolver(SignInSchema),
defaultValues,
});
const {
handleSubmit,
formState: { isSubmitting },
} = methods;
const onSubmit = handleSubmit(async (data) => {
try {
await signInWithPassword({ username: data.email, password: data.password });
await checkUserSession?.();
router.refresh();
} catch (error) {
console.error(error);
const feedbackMessage = getErrorMessage(error);
setErrorMessage(feedbackMessage);
}
});
const renderForm = () => (
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
<Field.Text name="email" label="Email address" slotProps={{ inputLabel: { shrink: true } }} />
<Box sx={{ gap: 1.5, display: 'flex', flexDirection: 'column' }}>
<Link
component={RouterLink}
href={paths.auth.amplify.resetPassword}
variant="body2"
color="inherit"
sx={{ alignSelf: 'flex-end' }}
>
Forgot password?
</Link>
<Field.Text
name="password"
label="Password"
placeholder="6+ characters"
type={showPassword.value ? 'text' : 'password'}
slotProps={{
inputLabel: { shrink: true },
input: {
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={showPassword.onToggle} edge="end">
<Iconify
icon={showPassword.value ? 'solar:eye-bold' : 'solar:eye-closed-bold'}
/>
</IconButton>
</InputAdornment>
),
},
}}
/>
</Box>
<Button
fullWidth
color="inherit"
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Sign in..."
>
Sign in
</Button>
</Box>
);
return (
<>
<FormHead
title="Sign in to your account"
description={
<>
{`Dont have an account? `}
<Link component={RouterLink} href={paths.auth.amplify.signUp} variant="subtitle2">
Get started
</Link>
</>
}
sx={{ textAlign: { xs: 'center', md: 'left' } }}
/>
{!!errorMessage && (
<Alert severity="error" sx={{ mb: 3 }}>
{errorMessage}
</Alert>
)}
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
</>
);
}

View File

@@ -0,0 +1,173 @@
import { z as zod } from 'zod';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { useBoolean } from 'minimal-shared/hooks';
import { zodResolver } from '@hookform/resolvers/zod';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Alert from '@mui/material/Alert';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
import { paths } from 'src/routes/paths';
import { useRouter } from 'src/routes/hooks';
import { RouterLink } from 'src/routes/components';
import { Iconify } from 'src/components/iconify';
import { Form, Field } from 'src/components/hook-form';
import { getErrorMessage } from '../../utils';
import { signUp } from '../../context/amplify';
import { FormHead } from '../../components/form-head';
import { SignUpTerms } from '../../components/sign-up-terms';
// ----------------------------------------------------------------------
export type SignUpSchemaType = zod.infer<typeof SignUpSchema>;
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 AmplifySignUpView() {
const router = useRouter();
const showPassword = useBoolean();
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const defaultValues: SignUpSchemaType = {
firstName: '',
lastName: '',
email: '',
password: '',
};
const methods = useForm<SignUpSchemaType>({
resolver: zodResolver(SignUpSchema),
defaultValues,
});
const {
handleSubmit,
formState: { isSubmitting },
} = methods;
const createRedirectPath = (query: string) => {
const queryString = new URLSearchParams({ email: query }).toString();
return `${paths.auth.amplify.verify}?${queryString}`;
};
const onSubmit = handleSubmit(async (data) => {
try {
await signUp({
username: data.email,
password: data.password,
firstName: data.firstName,
lastName: data.lastName,
});
const redirectPath = createRedirectPath(data.email);
router.push(redirectPath);
} catch (error) {
console.error(error);
const feedbackMessage = getErrorMessage(error);
setErrorMessage(feedbackMessage);
}
});
const renderForm = () => (
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
<Box
sx={{ display: 'flex', gap: { xs: 3, sm: 2 }, flexDirection: { xs: 'column', sm: 'row' } }}
>
<Field.Text
name="firstName"
label="First name"
slotProps={{ inputLabel: { shrink: true } }}
/>
<Field.Text
name="lastName"
label="Last name"
slotProps={{ inputLabel: { shrink: true } }}
/>
</Box>
<Field.Text name="email" label="Email address" slotProps={{ inputLabel: { shrink: true } }} />
<Field.Text
name="password"
label="Password"
placeholder="6+ characters"
type={showPassword.value ? 'text' : 'password'}
slotProps={{
inputLabel: { shrink: true },
input: {
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={showPassword.onToggle} edge="end">
<Iconify icon={showPassword.value ? 'solar:eye-bold' : 'solar:eye-closed-bold'} />
</IconButton>
</InputAdornment>
),
},
}}
/>
<Button
fullWidth
color="inherit"
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Create account..."
>
Create account
</Button>
</Box>
);
return (
<>
<FormHead
title="Get started absolutely free"
description={
<>
{`Already have an account? `}
<Link component={RouterLink} href={paths.auth.amplify.signIn} variant="subtitle2">
Get started
</Link>
</>
}
sx={{ textAlign: { xs: 'center', md: 'left' } }}
/>
{!!errorMessage && (
<Alert severity="error" sx={{ mb: 3 }}>
{errorMessage}
</Alert>
)}
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
<SignUpTerms />
</>
);
}

View File

@@ -0,0 +1,193 @@
import { z as zod } from 'zod';
import { useCallback } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { useBoolean, useCountdownSeconds } from 'minimal-shared/hooks';
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 { paths } from 'src/routes/paths';
import { useRouter, useSearchParams } from 'src/routes/hooks';
import { SentIcon } from 'src/assets/icons';
import { Iconify } from 'src/components/iconify';
import { Form, Field } from 'src/components/hook-form';
import { FormHead } from '../../components/form-head';
import { FormReturnLink } from '../../components/form-return-link';
import { FormResendCode } from '../../components/form-resend-code';
import { resetPassword, updatePassword } from '../../context/amplify';
// ----------------------------------------------------------------------
export type UpdatePasswordSchemaType = zod.infer<typeof UpdatePasswordSchema>;
export const UpdatePasswordSchema = zod
.object({
code: zod
.string()
.min(1, { message: 'Code is required!' })
.min(6, { message: 'Code must be at least 6 characters!' }),
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!' }),
confirmPassword: zod.string().min(1, { message: 'Confirm password is required!' }),
})
.refine((data) => data.password === data.confirmPassword, {
message: 'Passwords do not match!',
path: ['confirmPassword'],
});
// ----------------------------------------------------------------------
export function AmplifyUpdatePasswordView() {
const router = useRouter();
const searchParams = useSearchParams();
const email = searchParams.get('email');
const showPassword = useBoolean();
const countdown = useCountdownSeconds(5);
const defaultValues: UpdatePasswordSchemaType = {
code: '',
email: email || '',
password: '',
confirmPassword: '',
};
const methods = useForm<UpdatePasswordSchemaType>({
resolver: zodResolver(UpdatePasswordSchema),
defaultValues,
});
const {
watch,
handleSubmit,
formState: { isSubmitting },
} = methods;
const values = watch();
const onSubmit = handleSubmit(async (data) => {
try {
await updatePassword({
username: data.email,
confirmationCode: data.code,
newPassword: data.password,
});
router.push(paths.auth.amplify.signIn);
} catch (error) {
console.error(error);
}
});
const handleResendCode = useCallback(async () => {
if (!countdown.isCounting) {
try {
countdown.reset();
countdown.start();
await resetPassword({ username: values.email });
} catch (error) {
console.error(error);
}
}
}, [countdown, values.email]);
const renderForm = () => (
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
<Field.Text
name="email"
label="Email address"
placeholder="example@gmail.com"
slotProps={{ inputLabel: { shrink: true } }}
disabled
/>
<Field.Code name="code" />
<Field.Text
name="password"
label="Password"
placeholder="6+ characters"
type={showPassword.value ? 'text' : 'password'}
slotProps={{
inputLabel: { shrink: true },
input: {
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={showPassword.onToggle} edge="end">
<Iconify icon={showPassword.value ? 'solar:eye-bold' : 'solar:eye-closed-bold'} />
</IconButton>
</InputAdornment>
),
},
}}
/>
<Field.Text
name="confirmPassword"
label="Confirm new password"
type={showPassword.value ? 'text' : 'password'}
slotProps={{
inputLabel: { shrink: true },
input: {
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={showPassword.onToggle} edge="end">
<Iconify icon={showPassword.value ? 'solar:eye-bold' : 'solar:eye-closed-bold'} />
</IconButton>
</InputAdornment>
),
},
}}
/>
<Button
fullWidth
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Update password..."
>
Update password
</Button>
</Box>
);
return (
<>
<FormHead
icon={<SentIcon />}
title="Request sent successfully!"
description={`We've sent a 6-digit confirmation email to your email. \nPlease enter the code in below box to verify your email.`}
/>
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
<FormResendCode
onResendCode={handleResendCode}
value={countdown.value}
disabled={countdown.isCounting}
/>
<FormReturnLink href={paths.auth.amplify.signIn} />
</>
);
}

View File

@@ -0,0 +1,134 @@
import { z as zod } from 'zod';
import { useCallback } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { useCountdownSeconds } from 'minimal-shared/hooks';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import { paths } from 'src/routes/paths';
import { useRouter, useSearchParams } from 'src/routes/hooks';
import { EmailInboxIcon } from 'src/assets/icons';
import { Form, Field } from 'src/components/hook-form';
import { FormHead } from '../../components/form-head';
import { FormReturnLink } from '../../components/form-return-link';
import { FormResendCode } from '../../components/form-resend-code';
import { confirmSignUp, resendSignUpCode } from '../../context/amplify';
// ----------------------------------------------------------------------
export type VerifySchemaType = zod.infer<typeof VerifySchema>;
export const VerifySchema = zod.object({
code: zod
.string()
.min(1, { message: 'Code is required!' })
.min(6, { message: 'Code must be at least 6 characters!' }),
email: zod
.string()
.min(1, { message: 'Email is required!' })
.email({ message: 'Email must be a valid email address!' }),
});
// ----------------------------------------------------------------------
export function AmplifyVerifyView() {
const router = useRouter();
const searchParams = useSearchParams();
const email = searchParams.get('email');
const countdown = useCountdownSeconds(5);
const defaultValues: VerifySchemaType = {
code: '',
email: email || '',
};
const methods = useForm<VerifySchemaType>({
resolver: zodResolver(VerifySchema),
defaultValues,
});
const {
watch,
handleSubmit,
formState: { isSubmitting },
} = methods;
const values = watch();
const onSubmit = handleSubmit(async (data) => {
try {
await confirmSignUp({ username: data.email, confirmationCode: data.code });
router.push(paths.auth.amplify.signIn);
} catch (error) {
console.error(error);
}
});
const handleResendCode = useCallback(async () => {
if (!countdown.isCounting) {
try {
countdown.reset();
countdown.start();
await resendSignUpCode?.({ username: values.email });
} catch (error) {
console.error(error);
}
}
}, [countdown, values.email]);
const renderForm = () => (
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
<Field.Text
name="email"
label="Email address"
placeholder="example@gmail.com"
slotProps={{ inputLabel: { shrink: true } }}
disabled
/>
<Field.Code name="code" />
<Button
fullWidth
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Verify..."
>
Verify
</Button>
</Box>
);
return (
<>
<FormHead
icon={<EmailInboxIcon />}
title="Please check your email!"
description={`We've emailed a 6-digit confirmation code. \nPlease enter the code in the box below to verify your email.`}
/>
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
<FormResendCode
onResendCode={handleResendCode}
value={countdown.value}
disabled={countdown.isCounting}
/>
<FormReturnLink href={paths.auth.amplify.signIn} />
</>
);
}

View File

@@ -0,0 +1,9 @@
export * from './amplify-verify-view';
export * from './amplify-sign-in-view';
export * from './amplify-sign-up-view';
export * from './amplify-reset-password-view';
export * from './amplify-update-password-view';

View File

@@ -0,0 +1,92 @@
import { z as zod } from 'zod';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import { paths } from 'src/routes/paths';
import { PasswordIcon } from 'src/assets/icons';
import { Form, Field } from 'src/components/hook-form';
import { FormHead } from '../../../components/form-head';
import { FormReturnLink } from '../../../components/form-return-link';
// ----------------------------------------------------------------------
export type ResetPasswordSchemaType = zod.infer<typeof ResetPasswordSchema>;
export const ResetPasswordSchema = zod.object({
email: zod
.string()
.min(1, { message: 'Email is required!' })
.email({ message: 'Email must be a valid email address!' }),
});
// ----------------------------------------------------------------------
export function CenteredResetPasswordView() {
const defaultValues: ResetPasswordSchemaType = {
email: '',
};
const methods = useForm<ResetPasswordSchemaType>({
resolver: zodResolver(ResetPasswordSchema),
defaultValues,
});
const {
handleSubmit,
formState: { isSubmitting },
} = methods;
const onSubmit = handleSubmit(async (data) => {
try {
await new Promise((resolve) => setTimeout(resolve, 500));
console.info('DATA', data);
} catch (error) {
console.error(error);
}
});
const renderForm = () => (
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
<Field.Text
name="email"
label="Email address"
placeholder="example@gmail.com"
autoFocus
slotProps={{ inputLabel: { shrink: true } }}
/>
<Button
fullWidth
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Send request..."
>
Send request
</Button>
</Box>
);
return (
<>
<FormHead
icon={<PasswordIcon />}
title="Forgot your password?"
description={`Please enter the email address associated with your account and we'll email you a link to reset your password.`}
/>
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
<FormReturnLink href={paths.authDemo.centered.signIn} />
</>
);
}

View File

@@ -0,0 +1,147 @@
import { z as zod } from 'zod';
import { useForm } from 'react-hook-form';
import { useBoolean } from 'minimal-shared/hooks';
import { zodResolver } from '@hookform/resolvers/zod';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
import { paths } from 'src/routes/paths';
import { RouterLink } from 'src/routes/components';
import { Iconify } from 'src/components/iconify';
import { Form, Field } from 'src/components/hook-form';
import { AnimateLogoRotate } from 'src/components/animate';
import { FormHead } from '../../../components/form-head';
import { FormSocials } from '../../../components/form-socials';
import { FormDivider } from '../../../components/form-divider';
// ----------------------------------------------------------------------
export type SignInSchemaType = zod.infer<typeof SignInSchema>;
export 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!' }),
});
// ----------------------------------------------------------------------
export function CenteredSignInView() {
const showPassword = useBoolean();
const defaultValues: SignInSchemaType = {
email: '',
password: '',
};
const methods = useForm<SignInSchemaType>({
resolver: zodResolver(SignInSchema),
defaultValues,
});
const {
handleSubmit,
formState: { isSubmitting },
} = methods;
const onSubmit = handleSubmit(async (data) => {
try {
await new Promise((resolve) => setTimeout(resolve, 500));
console.info('DATA', data);
} catch (error) {
console.error(error);
}
});
const renderForm = () => (
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
<Field.Text name="email" label="Email address" slotProps={{ inputLabel: { shrink: true } }} />
<Box sx={{ gap: 1.5, display: 'flex', flexDirection: 'column' }}>
<Link
component={RouterLink}
href={paths.authDemo.centered.resetPassword}
variant="body2"
color="inherit"
sx={{ alignSelf: 'flex-end' }}
>
Forgot password?
</Link>
<Field.Text
name="password"
label="Password"
placeholder="6+ characters"
type={showPassword.value ? 'text' : 'password'}
slotProps={{
inputLabel: { shrink: true },
input: {
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={showPassword.onToggle} edge="end">
<Iconify
icon={showPassword.value ? 'solar:eye-bold' : 'solar:eye-closed-bold'}
/>
</IconButton>
</InputAdornment>
),
},
}}
/>
</Box>
<Button
fullWidth
color="inherit"
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Sign in..."
>
Sign in
</Button>
</Box>
);
return (
<>
<AnimateLogoRotate sx={{ mb: 3, mx: 'auto' }} />
<FormHead
title="Sign in to your account"
description={
<>
{`Dont have an account? `}
<Link component={RouterLink} href={paths.authDemo.centered.signUp} variant="subtitle2">
Get started
</Link>
</>
}
/>
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
<FormDivider />
<FormSocials
signInWithGoogle={() => {}}
singInWithGithub={() => {}}
signInWithTwitter={() => {}}
/>
</>
);
}

View File

@@ -0,0 +1,165 @@
import { z as zod } from 'zod';
import { useForm } from 'react-hook-form';
import { useBoolean } from 'minimal-shared/hooks';
import { zodResolver } from '@hookform/resolvers/zod';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
import { paths } from 'src/routes/paths';
import { RouterLink } from 'src/routes/components';
import { Iconify } from 'src/components/iconify';
import { Form, Field } from 'src/components/hook-form';
import { AnimateLogoRotate } from 'src/components/animate';
import { FormHead } from '../../../components/form-head';
import { FormSocials } from '../../../components/form-socials';
import { FormDivider } from '../../../components/form-divider';
import { SignUpTerms } from '../../../components/sign-up-terms';
// ----------------------------------------------------------------------
export type SignUpSchemaType = zod.infer<typeof SignUpSchema>;
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 CenteredSignUpView() {
const showPassword = useBoolean();
const defaultValues: SignUpSchemaType = {
firstName: '',
lastName: '',
email: '',
password: '',
};
const methods = useForm<SignUpSchemaType>({
resolver: zodResolver(SignUpSchema),
defaultValues,
});
const {
handleSubmit,
formState: { isSubmitting },
} = methods;
const onSubmit = handleSubmit(async (data) => {
try {
await new Promise((resolve) => setTimeout(resolve, 500));
console.info('DATA', data);
} catch (error) {
console.error(error);
}
});
const renderForm = () => (
<Box
sx={{
gap: 3,
display: 'flex',
flexDirection: 'column',
}}
>
<Box
sx={{
display: 'flex',
gap: { xs: 3, sm: 2 },
flexDirection: { xs: 'column', sm: 'row' },
}}
>
<Field.Text
name="firstName"
label="First name"
slotProps={{ inputLabel: { shrink: true } }}
/>
<Field.Text
name="lastName"
label="Last name"
slotProps={{ inputLabel: { shrink: true } }}
/>
</Box>
<Field.Text name="email" label="Email address" slotProps={{ inputLabel: { shrink: true } }} />
<Field.Text
name="password"
label="Password"
placeholder="6+ characters"
type={showPassword.value ? 'text' : 'password'}
slotProps={{
inputLabel: { shrink: true },
input: {
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={showPassword.onToggle} edge="end">
<Iconify icon={showPassword.value ? 'solar:eye-bold' : 'solar:eye-closed-bold'} />
</IconButton>
</InputAdornment>
),
},
}}
/>
<Button
fullWidth
color="inherit"
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Create account..."
>
Create account
</Button>
</Box>
);
return (
<>
<AnimateLogoRotate sx={{ mb: 3, mx: 'auto' }} />
<FormHead
title="Get started absolutely free"
description={
<>
{`Already have an account? `}
<Link component={RouterLink} href={paths.authDemo.centered.signIn} variant="subtitle2">
Get started
</Link>
</>
}
/>
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
<SignUpTerms />
<FormDivider />
<FormSocials
signInWithGoogle={() => {}}
singInWithGithub={() => {}}
signInWithTwitter={() => {}}
/>
</>
);
}

View File

@@ -0,0 +1,155 @@
import { z as zod } from 'zod';
import { useForm } from 'react-hook-form';
import { useBoolean } from 'minimal-shared/hooks';
import { zodResolver } from '@hookform/resolvers/zod';
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 { paths } from 'src/routes/paths';
import { SentIcon } from 'src/assets/icons';
import { Iconify } from 'src/components/iconify';
import { Form, Field } from 'src/components/hook-form';
import { FormHead } from '../../../components/form-head';
import { FormResendCode } from '../../../components/form-resend-code';
import { FormReturnLink } from '../../../components/form-return-link';
// ----------------------------------------------------------------------
export type UpdatePasswordSchemaType = zod.infer<typeof UpdatePasswordSchema>;
export const UpdatePasswordSchema = zod
.object({
code: zod
.string()
.min(1, { message: 'Code is required!' })
.min(6, { message: 'Code must be at least 6 characters!' }),
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!' }),
confirmPassword: zod.string().min(1, { message: 'Confirm password is required!' }),
})
.refine((data) => data.password === data.confirmPassword, {
message: 'Passwords do not match!',
path: ['confirmPassword'],
});
// ----------------------------------------------------------------------
export function CenteredUpdatePasswordView() {
const showPassword = useBoolean();
const defaultValues: UpdatePasswordSchemaType = {
code: '',
email: '',
password: '',
confirmPassword: '',
};
const methods = useForm<UpdatePasswordSchemaType>({
resolver: zodResolver(UpdatePasswordSchema),
defaultValues,
});
const {
handleSubmit,
formState: { isSubmitting },
} = methods;
const onSubmit = handleSubmit(async (data) => {
try {
await new Promise((resolve) => setTimeout(resolve, 500));
console.info('DATA', data);
} catch (error) {
console.error(error);
}
});
const renderForm = () => (
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
<Field.Text
name="email"
label="Email address"
placeholder="example@gmail.com"
slotProps={{ inputLabel: { shrink: true } }}
/>
<Field.Code name="code" />
<Field.Text
name="password"
label="Password"
placeholder="6+ characters"
type={showPassword.value ? 'text' : 'password'}
slotProps={{
inputLabel: { shrink: true },
input: {
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={showPassword.onToggle} edge="end">
<Iconify icon={showPassword.value ? 'solar:eye-bold' : 'solar:eye-closed-bold'} />
</IconButton>
</InputAdornment>
),
},
}}
/>
<Field.Text
name="confirmPassword"
label="Confirm new password"
type={showPassword.value ? 'text' : 'password'}
slotProps={{
inputLabel: { shrink: true },
input: {
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={showPassword.onToggle} edge="end">
<Iconify icon={showPassword.value ? 'solar:eye-bold' : 'solar:eye-closed-bold'} />
</IconButton>
</InputAdornment>
),
},
}}
/>
<Button
fullWidth
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Update password..."
>
Update password
</Button>
</Box>
);
return (
<>
<FormHead
icon={<SentIcon />}
title="Request sent successfully!"
description={`We've sent a 6-digit confirmation email to your email. \nPlease enter the code in below box to verify your email.`}
/>
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
<FormResendCode onResendCode={() => {}} value={0} disabled={false} />
<FormReturnLink href={paths.authDemo.centered.signIn} />
</>
);
}

View File

@@ -0,0 +1,101 @@
import { z as zod } from 'zod';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import { paths } from 'src/routes/paths';
import { EmailInboxIcon } from 'src/assets/icons';
import { Form, Field } from 'src/components/hook-form';
import { FormHead } from '../../../components/form-head';
import { FormResendCode } from '../../../components/form-resend-code';
import { FormReturnLink } from '../../../components/form-return-link';
// ----------------------------------------------------------------------
export type VerifySchemaType = zod.infer<typeof VerifySchema>;
export const VerifySchema = zod.object({
code: zod
.string()
.min(1, { message: 'Code is required!' })
.min(6, { message: 'Code must be at least 6 characters!' }),
email: zod
.string()
.min(1, { message: 'Email is required!' })
.email({ message: 'Email must be a valid email address!' }),
});
// ----------------------------------------------------------------------
export function CenteredVerifyView() {
const defaultValues: VerifySchemaType = {
code: '',
email: '',
};
const methods = useForm<VerifySchemaType>({
resolver: zodResolver(VerifySchema),
defaultValues,
});
const {
handleSubmit,
formState: { isSubmitting },
} = methods;
const onSubmit = handleSubmit(async (data) => {
try {
await new Promise((resolve) => setTimeout(resolve, 500));
console.info('DATA', data);
} catch (error) {
console.error(error);
}
});
const renderForm = () => (
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
<Field.Text
name="email"
label="Email address"
placeholder="example@gmail.com"
slotProps={{ inputLabel: { shrink: true } }}
/>
<Field.Code name="code" />
<Button
fullWidth
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Verify..."
>
Verify
</Button>
</Box>
);
return (
<>
<FormHead
icon={<EmailInboxIcon />}
title="Please check your email!"
description={`We've emailed a 6-digit confirmation code. \nPlease enter the code in the box below to verify your email.`}
/>
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
<FormResendCode onResendCode={() => {}} value={0} disabled={false} />
<FormReturnLink href={paths.authDemo.centered.signIn} />
</>
);
}

View File

@@ -0,0 +1,9 @@
export * from './centered-verify-view';
export * from './centered-sign-in-view';
export * from './centered-sign-up-view';
export * from './centered-reset-password-view';
export * from './centered-update-password-view';

View File

@@ -0,0 +1,9 @@
export * from './split-verify-view';
export * from './split-sign-in-view';
export * from './split-sign-up-view';
export * from './split-reset-password-view';
export * from './split-update-password-view';

View File

@@ -0,0 +1,92 @@
import { z as zod } from 'zod';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import { paths } from 'src/routes/paths';
import { PasswordIcon } from 'src/assets/icons';
import { Form, Field } from 'src/components/hook-form';
import { FormHead } from '../../../components/form-head';
import { FormReturnLink } from '../../../components/form-return-link';
// ----------------------------------------------------------------------
export type ResetPasswordSchemaType = zod.infer<typeof ResetPasswordSchema>;
export const ResetPasswordSchema = zod.object({
email: zod
.string()
.min(1, { message: 'Email is required!' })
.email({ message: 'Email must be a valid email address!' }),
});
// ----------------------------------------------------------------------
export function SplitResetPasswordView() {
const defaultValues: ResetPasswordSchemaType = {
email: '',
};
const methods = useForm<ResetPasswordSchemaType>({
resolver: zodResolver(ResetPasswordSchema),
defaultValues,
});
const {
handleSubmit,
formState: { isSubmitting },
} = methods;
const onSubmit = handleSubmit(async (data) => {
try {
await new Promise((resolve) => setTimeout(resolve, 500));
console.info('DATA', data);
} catch (error) {
console.error(error);
}
});
const renderForm = () => (
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
<Field.Text
autoFocus
name="email"
label="Email address"
placeholder="example@gmail.com"
slotProps={{ inputLabel: { shrink: true } }}
/>
<Button
fullWidth
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Send request..."
>
Send request
</Button>
</Box>
);
return (
<>
<FormHead
icon={<PasswordIcon />}
title="Forgot your password?"
description={`Please enter the email address associated with your account and we'll email you a link to reset your password.`}
/>
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
<FormReturnLink href={paths.authDemo.split.signIn} />
</>
);
}

View File

@@ -0,0 +1,157 @@
import { z as zod } from 'zod';
import { useForm } from 'react-hook-form';
import { useBoolean } from 'minimal-shared/hooks';
import { zodResolver } from '@hookform/resolvers/zod';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
import { paths } from 'src/routes/paths';
import { RouterLink } from 'src/routes/components';
import { Iconify } from 'src/components/iconify';
import { Form, Field } from 'src/components/hook-form';
import { FormHead } from '../../../components/form-head';
import { FormSocials } from '../../../components/form-socials';
import { FormDivider } from '../../../components/form-divider';
// ----------------------------------------------------------------------
export type SignInSchemaType = zod.infer<typeof SignInSchema>;
export 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!' }),
});
// ----------------------------------------------------------------------
export function SplitSignInView() {
const showPassword = useBoolean();
const defaultValues: SignInSchemaType = {
email: '',
password: '',
};
const methods = useForm<SignInSchemaType>({
resolver: zodResolver(SignInSchema),
defaultValues,
});
const {
handleSubmit,
formState: { isSubmitting },
} = methods;
const onSubmit = handleSubmit(async (data) => {
try {
await new Promise((resolve) => setTimeout(resolve, 500));
console.info('DATA', data);
} catch (error) {
console.error(error);
}
});
const renderForm = () => (
<Box
sx={{
gap: 3,
display: 'flex',
flexDirection: 'column',
}}
>
<Field.Text name="email" label="Email address" slotProps={{ inputLabel: { shrink: true } }} />
<Box
sx={{
gap: 1.5,
display: 'flex',
flexDirection: 'column',
}}
>
<Link
component={RouterLink}
href={paths.authDemo.split.resetPassword}
variant="body2"
color="inherit"
sx={{ alignSelf: 'flex-end' }}
>
Forgot password?
</Link>
<Field.Text
name="password"
label="Password"
placeholder="6+ characters"
type={showPassword.value ? 'text' : 'password'}
slotProps={{
inputLabel: { shrink: true },
input: {
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={showPassword.onToggle} edge="end">
<Iconify
icon={showPassword.value ? 'solar:eye-bold' : 'solar:eye-closed-bold'}
/>
</IconButton>
</InputAdornment>
),
},
}}
/>
</Box>
<Button
fullWidth
color="inherit"
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Sign in..."
>
Sign in
</Button>
</Box>
);
return (
<>
<FormHead
title="Sign in to your account"
description={
<>
{`Dont have an account? `}
<Link component={RouterLink} href={paths.authDemo.split.signUp} variant="subtitle2">
Get started
</Link>
</>
}
sx={{ textAlign: { xs: 'center', md: 'left' } }}
/>
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
<FormDivider />
<FormSocials
signInWithGoogle={() => {}}
singInWithGithub={() => {}}
signInWithTwitter={() => {}}
/>
</>
);
}

View File

@@ -0,0 +1,153 @@
import { z as zod } from 'zod';
import { useForm } from 'react-hook-form';
import { useBoolean } from 'minimal-shared/hooks';
import { zodResolver } from '@hookform/resolvers/zod';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
import { paths } from 'src/routes/paths';
import { RouterLink } from 'src/routes/components';
import { Iconify } from 'src/components/iconify';
import { Form, Field } from 'src/components/hook-form';
import { FormHead } from '../../../components/form-head';
import { FormDivider } from '../../../components/form-divider';
import { FormSocials } from '../../../components/form-socials';
import { SignUpTerms } from '../../../components/sign-up-terms';
// ----------------------------------------------------------------------
export type SignUpSchemaType = zod.infer<typeof SignUpSchema>;
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 SplitSignUpView() {
const showPassword = useBoolean();
const defaultValues: SignUpSchemaType = {
firstName: '',
lastName: '',
email: '',
password: '',
};
const methods = useForm<SignUpSchemaType>({
resolver: zodResolver(SignUpSchema),
defaultValues,
});
const {
handleSubmit,
formState: { isSubmitting },
} = methods;
const onSubmit = handleSubmit(async (data) => {
try {
await new Promise((resolve) => setTimeout(resolve, 500));
console.info('DATA', data);
} catch (error) {
console.error(error);
}
});
const renderForm = () => (
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
<Box
sx={{ display: 'flex', gap: { xs: 3, sm: 2 }, flexDirection: { xs: 'column', sm: 'row' } }}
>
<Field.Text
name="firstName"
label="First name"
slotProps={{ inputLabel: { shrink: true } }}
/>
<Field.Text
name="lastName"
label="Last name"
slotProps={{ inputLabel: { shrink: true } }}
/>
</Box>
<Field.Text name="email" label="Email address" slotProps={{ inputLabel: { shrink: true } }} />
<Field.Text
name="password"
label="Password"
placeholder="6+ characters"
type={showPassword.value ? 'text' : 'password'}
slotProps={{
inputLabel: { shrink: true },
input: {
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={showPassword.onToggle} edge="end">
<Iconify icon={showPassword.value ? 'solar:eye-bold' : 'solar:eye-closed-bold'} />
</IconButton>
</InputAdornment>
),
},
}}
/>
<Button
fullWidth
color="inherit"
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Create account..."
>
Create account
</Button>
</Box>
);
return (
<>
<FormHead
title="Get started absolutely free"
description={
<>
{`Already have an account? `}
<Link component={RouterLink} href={paths.authDemo.split.signIn} variant="subtitle2">
Get started
</Link>
</>
}
sx={{ textAlign: { xs: 'center', md: 'left' } }}
/>
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
<SignUpTerms />
<FormDivider />
<FormSocials
signInWithGoogle={() => {}}
singInWithGithub={() => {}}
signInWithTwitter={() => {}}
/>
</>
);
}

View File

@@ -0,0 +1,156 @@
import { z as zod } from 'zod';
import { useForm } from 'react-hook-form';
import { useBoolean } from 'minimal-shared/hooks';
import { zodResolver } from '@hookform/resolvers/zod';
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 { paths } from 'src/routes/paths';
import { SentIcon } from 'src/assets/icons';
import { Iconify } from 'src/components/iconify';
import { Form, Field } from 'src/components/hook-form';
import { FormHead } from '../../../components/form-head';
import { FormResendCode } from '../../../components/form-resend-code';
import { FormReturnLink } from '../../../components/form-return-link';
// ----------------------------------------------------------------------
export type UpdatePasswordSchemaType = zod.infer<typeof UpdatePasswordSchema>;
export const UpdatePasswordSchema = zod
.object({
code: zod
.string()
.min(1, { message: 'Code is required!' })
.min(6, { message: 'Code must be at least 6 characters!' }),
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!' }),
confirmPassword: zod.string().min(1, { message: 'Confirm password is required!' }),
})
.refine((data) => data.password === data.confirmPassword, {
message: 'Passwords do not match!',
path: ['confirmPassword'],
});
// ----------------------------------------------------------------------
export function SplitUpdatePasswordView() {
const showPassword = useBoolean();
const defaultValues: UpdatePasswordSchemaType = {
code: '',
email: '',
password: '',
confirmPassword: '',
};
const methods = useForm<UpdatePasswordSchemaType>({
resolver: zodResolver(UpdatePasswordSchema),
defaultValues,
});
const {
handleSubmit,
formState: { isSubmitting },
} = methods;
const onSubmit = handleSubmit(async (data) => {
try {
await new Promise((resolve) => setTimeout(resolve, 500));
console.info('DATA', data);
} catch (error) {
console.error(error);
}
});
const renderForm = () => (
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
<Field.Text
name="email"
label="Email address"
placeholder="example@gmail.com"
slotProps={{ inputLabel: { shrink: true } }}
/>
<Field.Code name="code" />
<Field.Text
name="password"
label="Password"
placeholder="6+ characters"
type={showPassword.value ? 'text' : 'password'}
slotProps={{
inputLabel: { shrink: true },
input: {
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={showPassword.onToggle} edge="end">
<Iconify icon={showPassword.value ? 'solar:eye-bold' : 'solar:eye-closed-bold'} />
</IconButton>
</InputAdornment>
),
},
}}
/>
<Field.Text
name="confirmPassword"
label="Confirm new password"
type={showPassword.value ? 'text' : 'password'}
slotProps={{
inputLabel: { shrink: true },
input: {
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={showPassword.onToggle} edge="end">
<Iconify icon={showPassword.value ? 'solar:eye-bold' : 'solar:eye-closed-bold'} />
</IconButton>
</InputAdornment>
),
},
}}
/>
<Button
fullWidth
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Update password..."
>
Update password
</Button>
</Box>
);
return (
<>
<FormHead
icon={<SentIcon />}
title="Request sent successfully!"
description={`We've sent a 6-digit confirmation email to your email. \nPlease enter the code in below box to verify your email.`}
/>
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
<FormResendCode onResendCode={() => {}} value={0} disabled={false} />
<FormReturnLink href={paths.authDemo.split.signIn} />
</>
);
}

View File

@@ -0,0 +1,101 @@
import { z as zod } from 'zod';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import { paths } from 'src/routes/paths';
import { EmailInboxIcon } from 'src/assets/icons';
import { Form, Field } from 'src/components/hook-form';
import { FormHead } from '../../../components/form-head';
import { FormReturnLink } from '../../../components/form-return-link';
import { FormResendCode } from '../../../components/form-resend-code';
// ----------------------------------------------------------------------
export type VerifySchemaType = zod.infer<typeof VerifySchema>;
export const VerifySchema = zod.object({
code: zod
.string()
.min(1, { message: 'Code is required!' })
.min(6, { message: 'Code must be at least 6 characters!' }),
email: zod
.string()
.min(1, { message: 'Email is required!' })
.email({ message: 'Email must be a valid email address!' }),
});
// ----------------------------------------------------------------------
export function SplitVerifyView() {
const defaultValues: VerifySchemaType = {
code: '',
email: '',
};
const methods = useForm<VerifySchemaType>({
resolver: zodResolver(VerifySchema),
defaultValues,
});
const {
handleSubmit,
formState: { isSubmitting },
} = methods;
const onSubmit = handleSubmit(async (data) => {
try {
await new Promise((resolve) => setTimeout(resolve, 500));
console.info('DATA', data);
} catch (error) {
console.error(error);
}
});
const renderForm = () => (
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
<Field.Text
name="email"
label="Email address"
placeholder="example@gmail.com"
slotProps={{ inputLabel: { shrink: true } }}
/>
<Field.Code name="code" />
<Button
fullWidth
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Verify..."
>
Verify
</Button>
</Box>
);
return (
<>
<FormHead
icon={<EmailInboxIcon />}
title="Please check your email!"
description={`We've emailed a 6-digit confirmation code. \nPlease enter the code in the box below to verify your email.`}
/>
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
<FormResendCode onResendCode={() => {}} value={0} disabled={false} />
<FormReturnLink href={paths.authDemo.split.signIn} />
</>
);
}

View File

@@ -0,0 +1,99 @@
import { useCallback } from 'react';
import { useAuth0 } from '@auth0/auth0-react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Divider from '@mui/material/Divider';
import Typography from '@mui/material/Typography';
import { useSearchParams } from 'src/routes/hooks';
import { CONFIG } from 'src/global-config';
// ----------------------------------------------------------------------
export function Auth0SignInView() {
const { loginWithPopup, loginWithRedirect } = useAuth0();
const searchParams = useSearchParams();
const returnTo = searchParams.get('returnTo');
const handleSignInWithPopup = useCallback(async () => {
try {
await loginWithPopup();
} catch (error) {
console.error(error);
}
}, [loginWithPopup]);
const handleSignUpWithPopup = useCallback(async () => {
try {
await loginWithPopup({ authorizationParams: { screen_hint: 'signup' } });
} catch (error) {
console.error(error);
}
}, [loginWithPopup]);
const handleSignInWithRedirect = useCallback(async () => {
try {
await loginWithRedirect({ appState: { returnTo: returnTo || CONFIG.auth.redirectPath } });
} catch (error) {
console.error(error);
}
}, [loginWithRedirect, returnTo]);
const handleSignUpWithRedirect = useCallback(async () => {
try {
await loginWithRedirect({
appState: { returnTo: returnTo || CONFIG.auth.redirectPath },
authorizationParams: { screen_hint: 'signup' },
});
} catch (error) {
console.error(error);
}
}, [loginWithRedirect, returnTo]);
return (
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
<Typography variant="h5" sx={{ textAlign: 'center' }}>
Sign in to your account
</Typography>
<Button
fullWidth
color="primary"
size="large"
variant="contained"
onClick={handleSignInWithRedirect}
>
Sign in with Redirect
</Button>
<Button
fullWidth
color="primary"
size="large"
variant="soft"
onClick={handleSignUpWithRedirect}
>
Sign up with Redirect
</Button>
<Divider sx={{ borderStyle: 'dashed' }} />
<Button
fullWidth
color="inherit"
size="large"
variant="contained"
onClick={handleSignInWithPopup}
>
Sign in with Popup
</Button>
<Button fullWidth color="inherit" size="large" variant="soft" onClick={handleSignUpWithPopup}>
Sign up with Popup
</Button>
</Box>
);
}

View File

@@ -0,0 +1 @@
export * from './auth0-sign-in-view';

View File

@@ -0,0 +1,104 @@
import { z as zod } from 'zod';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import { paths } from 'src/routes/paths';
import { useRouter } from 'src/routes/hooks';
import { PasswordIcon } from 'src/assets/icons';
import { Form, Field } from 'src/components/hook-form';
import { FormHead } from '../../components/form-head';
import { sendPasswordResetEmail } from '../../context/firebase';
import { FormReturnLink } from '../../components/form-return-link';
// ----------------------------------------------------------------------
export type ResetPasswordSchemaType = zod.infer<typeof ResetPasswordSchema>;
export const ResetPasswordSchema = zod.object({
email: zod
.string()
.min(1, { message: 'Email is required!' })
.email({ message: 'Email must be a valid email address!' }),
});
// ----------------------------------------------------------------------
export function FirebaseResetPasswordView() {
const router = useRouter();
const defaultValues: ResetPasswordSchemaType = {
email: '',
};
const methods = useForm<ResetPasswordSchemaType>({
resolver: zodResolver(ResetPasswordSchema),
defaultValues,
});
const {
handleSubmit,
formState: { isSubmitting },
} = methods;
const createRedirectPath = (query: string) => {
const queryString = new URLSearchParams({ email: query }).toString();
return `${paths.auth.firebase.verify}?${queryString}`;
};
const onSubmit = handleSubmit(async (data) => {
try {
await sendPasswordResetEmail({ email: data.email });
const redirectPath = createRedirectPath(data.email);
router.push(redirectPath);
} catch (error) {
console.error(error);
}
});
const renderForm = () => (
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
<Field.Text
autoFocus
name="email"
label="Email address"
placeholder="example@gmail.com"
slotProps={{ inputLabel: { shrink: true } }}
/>
<Button
fullWidth
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Send request..."
>
Send request
</Button>
</Box>
);
return (
<>
<FormHead
icon={<PasswordIcon />}
title="Forgot your password?"
description={`Please enter the email address associated with your account and we'll email you a link to reset your password.`}
/>
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
<FormReturnLink href={paths.auth.firebase.signIn} />
</>
);
}

View File

@@ -0,0 +1,196 @@
import { z as zod } from 'zod';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { useBoolean } from 'minimal-shared/hooks';
import { zodResolver } from '@hookform/resolvers/zod';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Alert from '@mui/material/Alert';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
import { paths } from 'src/routes/paths';
import { useRouter } from 'src/routes/hooks';
import { RouterLink } from 'src/routes/components';
import { Iconify } from 'src/components/iconify';
import { Form, Field } from 'src/components/hook-form';
import { useAuthContext } from '../../hooks';
import { getErrorMessage } from '../../utils';
import { FormHead } from '../../components/form-head';
import { FormDivider } from '../../components/form-divider';
import { FormSocials } from '../../components/form-socials';
import {
signInWithGoogle,
signInWithGithub,
signInWithTwitter,
signInWithPassword,
} from '../../context/firebase';
// ----------------------------------------------------------------------
export type SignInSchemaType = zod.infer<typeof SignInSchema>;
export 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!' }),
});
// ----------------------------------------------------------------------
export function FirebaseSignInView() {
const router = useRouter();
const showPassword = useBoolean();
const { checkUserSession } = useAuthContext();
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const defaultValues: SignInSchemaType = {
email: '',
password: '',
};
const methods = useForm<SignInSchemaType>({
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 handleSignInWithGoogle = async () => {
try {
await signInWithGoogle();
} catch (error) {
console.error(error);
}
};
const handleSignInWithGithub = async () => {
try {
await signInWithGithub();
} catch (error) {
console.error(error);
}
};
const handleSignInWithTwitter = async () => {
try {
await signInWithTwitter();
} catch (error) {
console.error(error);
}
};
const renderForm = () => (
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
<Field.Text name="email" label="Email address" slotProps={{ inputLabel: { shrink: true } }} />
<Box sx={{ gap: 1.5, display: 'flex', flexDirection: 'column' }}>
<Link
component={RouterLink}
href={paths.auth.firebase.resetPassword}
variant="body2"
color="inherit"
sx={{ alignSelf: 'flex-end' }}
>
Forgot password?
</Link>
<Field.Text
name="password"
label="Password"
placeholder="6+ characters"
type={showPassword.value ? 'text' : 'password'}
slotProps={{
inputLabel: { shrink: true },
input: {
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={showPassword.onToggle} edge="end">
<Iconify
icon={showPassword.value ? 'solar:eye-bold' : 'solar:eye-closed-bold'}
/>
</IconButton>
</InputAdornment>
),
},
}}
/>
</Box>
<Button
fullWidth
color="inherit"
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Sign in..."
>
Sign in
</Button>
</Box>
);
return (
<>
<FormHead
title="Sign in to your account"
description={
<>
{`Dont have an account? `}
<Link component={RouterLink} href={paths.auth.firebase.signUp} variant="subtitle2">
Get started
</Link>
</>
}
sx={{ textAlign: { xs: 'center', md: 'left' } }}
/>
{!!errorMessage && (
<Alert severity="error" sx={{ mb: 3 }}>
{errorMessage}
</Alert>
)}
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
<FormDivider />
<FormSocials
signInWithGoogle={handleSignInWithGoogle}
singInWithGithub={handleSignInWithGithub}
signInWithTwitter={handleSignInWithTwitter}
/>
</>
);
}

View File

@@ -0,0 +1,212 @@
import { z as zod } from 'zod';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { useBoolean } from 'minimal-shared/hooks';
import { zodResolver } from '@hookform/resolvers/zod';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Alert from '@mui/material/Alert';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
import { paths } from 'src/routes/paths';
import { useRouter } from 'src/routes/hooks';
import { RouterLink } from 'src/routes/components';
import { Iconify } from 'src/components/iconify';
import { Form, Field } from 'src/components/hook-form';
import { getErrorMessage } from '../../utils';
import { FormHead } from '../../components/form-head';
import { FormDivider } from '../../components/form-divider';
import { FormSocials } from '../../components/form-socials';
import { SignUpTerms } from '../../components/sign-up-terms';
import {
signUp,
signInWithGithub,
signInWithGoogle,
signInWithTwitter,
} from '../../context/firebase';
// ----------------------------------------------------------------------
export type SignUpSchemaType = zod.infer<typeof SignUpSchema>;
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 FirebaseSignUpView() {
const router = useRouter();
const showPassword = useBoolean();
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const defaultValues: SignUpSchemaType = {
firstName: '',
lastName: '',
email: '',
password: '',
};
const methods = useForm<SignUpSchemaType>({
resolver: zodResolver(SignUpSchema),
defaultValues,
});
const {
handleSubmit,
formState: { isSubmitting },
} = methods;
const createRedirectPath = (query: string) => {
const queryString = new URLSearchParams({ email: query }).toString();
return `${paths.auth.firebase.verify}?${queryString}`;
};
const onSubmit = handleSubmit(async (data) => {
try {
await signUp({
email: data.email,
password: data.password,
firstName: data.firstName,
lastName: data.lastName,
});
const redirectPath = createRedirectPath(data.email);
router.push(redirectPath);
} catch (error) {
console.error(error);
const feedbackMessage = getErrorMessage(error);
setErrorMessage(feedbackMessage);
}
});
const handleSignInWithGoogle = async () => {
try {
await signInWithGoogle();
} catch (error) {
console.error(error);
}
};
const handleSignInWithGithub = async () => {
try {
await signInWithGithub();
} catch (error) {
console.error(error);
}
};
const handleSignInWithTwitter = async () => {
try {
await signInWithTwitter();
} catch (error) {
console.error(error);
}
};
const renderForm = () => (
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
<Box
sx={{ display: 'flex', gap: { xs: 3, sm: 2 }, flexDirection: { xs: 'column', sm: 'row' } }}
>
<Field.Text
name="firstName"
label="First name"
slotProps={{ inputLabel: { shrink: true } }}
/>
<Field.Text
name="lastName"
label="Last name"
slotProps={{ inputLabel: { shrink: true } }}
/>
</Box>
<Field.Text name="email" label="Email address" slotProps={{ inputLabel: { shrink: true } }} />
<Field.Text
name="password"
label="Password"
placeholder="6+ characters"
type={showPassword.value ? 'text' : 'password'}
slotProps={{
inputLabel: { shrink: true },
input: {
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={showPassword.onToggle} edge="end">
<Iconify icon={showPassword.value ? 'solar:eye-bold' : 'solar:eye-closed-bold'} />
</IconButton>
</InputAdornment>
),
},
}}
/>
<Button
fullWidth
color="inherit"
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Create account..."
>
Create account
</Button>
</Box>
);
return (
<>
<FormHead
title="Get started absolutely free"
description={
<>
{`Already have an account? `}
<Link component={RouterLink} href={paths.auth.firebase.signIn} variant="subtitle2">
Get started
</Link>
</>
}
sx={{ textAlign: { xs: 'center', md: 'left' } }}
/>
{!!errorMessage && (
<Alert severity="error" sx={{ mb: 3 }}>
{errorMessage}
</Alert>
)}
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
<SignUpTerms />
<FormDivider />
<FormSocials
signInWithGoogle={handleSignInWithGoogle}
singInWithGithub={handleSignInWithGithub}
signInWithTwitter={handleSignInWithTwitter}
/>
</>
);
}

View File

@@ -0,0 +1,22 @@
import { paths } from 'src/routes/paths';
import { EmailInboxIcon } from 'src/assets/icons';
import { FormHead } from '../../components/form-head';
import { FormReturnLink } from '../../components/form-return-link';
// ----------------------------------------------------------------------
export function FirebaseVerifyView() {
return (
<>
<FormHead
icon={<EmailInboxIcon />}
title="Please check your email!"
description={`We've emailed a 6-digit confirmation code. \nPlease enter the code in the box below to verify your email.`}
/>
<FormReturnLink href={paths.auth.firebase.signIn} sx={{ mt: 0 }} />
</>
);
}

View File

@@ -0,0 +1,7 @@
export * from './firebase-verify-view';
export * from './firebase-sign-in-view';
export * from './firebase-sign-up-view';
export * from './firebase-reset-password-view';

View File

@@ -0,0 +1,3 @@
export * from './jwt-sign-in-view';
export * from './jwt-sign-up-view';

View File

@@ -0,0 +1,163 @@
import { z as zod } from 'zod';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { useBoolean } from 'minimal-shared/hooks';
import { zodResolver } from '@hookform/resolvers/zod';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Alert from '@mui/material/Alert';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
import { paths } from 'src/routes/paths';
import { useRouter } from 'src/routes/hooks';
import { RouterLink } from 'src/routes/components';
import { Iconify } from 'src/components/iconify';
import { Form, Field } from 'src/components/hook-form';
import { useAuthContext } from '../../hooks';
import { getErrorMessage } from '../../utils';
import { FormHead } from '../../components/form-head';
import { signInWithPassword } from '../../context/jwt';
// ----------------------------------------------------------------------
export type SignInSchemaType = zod.infer<typeof SignInSchema>;
export 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!' }),
});
// ----------------------------------------------------------------------
export function JwtSignInView() {
const router = useRouter();
const showPassword = useBoolean();
const { checkUserSession } = useAuthContext();
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const defaultValues: SignInSchemaType = {
email: 'demo@minimals.cc',
password: '@2Minimal',
};
const methods = useForm<SignInSchemaType>({
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 = () => (
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
<Field.Text name="email" label="Email address" slotProps={{ inputLabel: { shrink: true } }} />
<Box sx={{ gap: 1.5, display: 'flex', flexDirection: 'column' }}>
<Link
component={RouterLink}
href="#"
variant="body2"
color="inherit"
sx={{ alignSelf: 'flex-end' }}
>
Forgot password?
</Link>
<Field.Text
name="password"
label="Password"
placeholder="6+ characters"
type={showPassword.value ? 'text' : 'password'}
slotProps={{
inputLabel: { shrink: true },
input: {
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={showPassword.onToggle} edge="end">
<Iconify
icon={showPassword.value ? 'solar:eye-bold' : 'solar:eye-closed-bold'}
/>
</IconButton>
</InputAdornment>
),
},
}}
/>
</Box>
<Button
fullWidth
color="inherit"
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Sign in..."
>
Sign in
</Button>
</Box>
);
return (
<>
<FormHead
title="Sign in to your account"
description={
<>
{`Dont have an account? `}
<Link component={RouterLink} href={paths.auth.jwt.signUp} variant="subtitle2">
Get started
</Link>
</>
}
sx={{ textAlign: { xs: 'center', md: 'left' } }}
/>
<Alert severity="info" sx={{ mb: 3 }}>
Use <strong>{defaultValues.email}</strong>
{' with password '}
<strong>{defaultValues.password}</strong>
</Alert>
{!!errorMessage && (
<Alert severity="error" sx={{ mb: 3 }}>
{errorMessage}
</Alert>
)}
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
</>
);
}

View File

@@ -0,0 +1,170 @@
import { z as zod } from 'zod';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { useBoolean } from 'minimal-shared/hooks';
import { zodResolver } from '@hookform/resolvers/zod';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Alert from '@mui/material/Alert';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
import { paths } from 'src/routes/paths';
import { useRouter } from 'src/routes/hooks';
import { RouterLink } from 'src/routes/components';
import { Iconify } from 'src/components/iconify';
import { Form, Field } from 'src/components/hook-form';
import { signUp } from '../../context/jwt';
import { useAuthContext } from '../../hooks';
import { getErrorMessage } from '../../utils';
import { FormHead } from '../../components/form-head';
import { SignUpTerms } from '../../components/sign-up-terms';
// ----------------------------------------------------------------------
export type SignUpSchemaType = zod.infer<typeof SignUpSchema>;
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<string | null>(null);
const defaultValues: SignUpSchemaType = {
firstName: 'Hello',
lastName: 'Friend',
email: 'hello@gmail.com',
password: '@2Minimal',
};
const methods = useForm<SignUpSchemaType>({
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 = () => (
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
<Box
sx={{ display: 'flex', gap: { xs: 3, sm: 2 }, flexDirection: { xs: 'column', sm: 'row' } }}
>
<Field.Text
name="firstName"
label="First name"
slotProps={{ inputLabel: { shrink: true } }}
/>
<Field.Text
name="lastName"
label="Last name"
slotProps={{ inputLabel: { shrink: true } }}
/>
</Box>
<Field.Text name="email" label="Email address" slotProps={{ inputLabel: { shrink: true } }} />
<Field.Text
name="password"
label="Password"
placeholder="6+ characters"
type={showPassword.value ? 'text' : 'password'}
slotProps={{
inputLabel: { shrink: true },
input: {
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={showPassword.onToggle} edge="end">
<Iconify icon={showPassword.value ? 'solar:eye-bold' : 'solar:eye-closed-bold'} />
</IconButton>
</InputAdornment>
),
},
}}
/>
<Button
fullWidth
color="inherit"
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Create account..."
>
Create account
</Button>
</Box>
);
return (
<>
<FormHead
title="Get started absolutely free"
description={
<>
{`Already have an account? `}
<Link component={RouterLink} href={paths.auth.jwt.signIn} variant="subtitle2">
Get started
</Link>
</>
}
sx={{ textAlign: { xs: 'center', md: 'left' } }}
/>
{!!errorMessage && (
<Alert severity="error" sx={{ mb: 3 }}>
{errorMessage}
</Alert>
)}
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
<SignUpTerms />
</>
);
}

View File

@@ -0,0 +1,9 @@
export * from './supabase-verify-view';
export * from './supabase-sign-in-view';
export * from './supabase-sign-up-view';
export * from './supabase-reset-password-view';
export * from './supabase-update-password-view';

View File

@@ -0,0 +1,97 @@
import { z as zod } from 'zod';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import { paths } from 'src/routes/paths';
import { useRouter } from 'src/routes/hooks';
import { PasswordIcon } from 'src/assets/icons';
import { Form, Field } from 'src/components/hook-form';
import { FormHead } from '../../components/form-head';
import { resetPassword } from '../../context/supabase';
import { FormReturnLink } from '../../components/form-return-link';
// ----------------------------------------------------------------------
export type ResetPasswordSchemaType = zod.infer<typeof ResetPasswordSchema>;
export const ResetPasswordSchema = zod.object({
email: zod
.string()
.min(1, { message: 'Email is required!' })
.email({ message: 'Email must be a valid email address!' }),
});
// ----------------------------------------------------------------------
export function SupabaseResetPasswordView() {
const router = useRouter();
const defaultValues: ResetPasswordSchemaType = {
email: '',
};
const methods = useForm<ResetPasswordSchemaType>({
resolver: zodResolver(ResetPasswordSchema),
defaultValues,
});
const {
handleSubmit,
formState: { isSubmitting },
} = methods;
const onSubmit = handleSubmit(async (data) => {
try {
await resetPassword({ email: data.email });
router.push(paths.auth.supabase.verify);
} catch (error) {
console.error(error);
}
});
const renderForm = () => (
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
<Field.Text
autoFocus
name="email"
label="Email address"
placeholder="example@gmail.com"
slotProps={{ inputLabel: { shrink: true } }}
/>
<Button
fullWidth
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Send request..."
>
Send request
</Button>
</Box>
);
return (
<>
<FormHead
icon={<PasswordIcon />}
title="Forgot your password?"
description={`Please enter the email address associated with your account and we'll email you a link to reset your password.`}
/>
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
<FormReturnLink href={paths.auth.supabase.signIn} />
</>
);
}

View File

@@ -0,0 +1,157 @@
import { z as zod } from 'zod';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { useBoolean } from 'minimal-shared/hooks';
import { zodResolver } from '@hookform/resolvers/zod';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Alert from '@mui/material/Alert';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
import { paths } from 'src/routes/paths';
import { useRouter } from 'src/routes/hooks';
import { RouterLink } from 'src/routes/components';
import { Iconify } from 'src/components/iconify';
import { Form, Field } from 'src/components/hook-form';
import { useAuthContext } from '../../hooks';
import { getErrorMessage } from '../../utils';
import { FormHead } from '../../components/form-head';
import { signInWithPassword } from '../../context/supabase';
// ----------------------------------------------------------------------
export type SignInSchemaType = zod.infer<typeof SignInSchema>;
export 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!' }),
});
// ----------------------------------------------------------------------
export function SupabaseSignInView() {
const router = useRouter();
const showPassword = useBoolean();
const { checkUserSession } = useAuthContext();
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const defaultValues: SignInSchemaType = {
email: '',
password: '',
};
const methods = useForm<SignInSchemaType>({
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 = () => (
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
<Field.Text name="email" label="Email address" slotProps={{ inputLabel: { shrink: true } }} />
<Box sx={{ gap: 1.5, display: 'flex', flexDirection: 'column' }}>
<Link
component={RouterLink}
href={paths.auth.supabase.resetPassword}
variant="body2"
color="inherit"
sx={{ alignSelf: 'flex-end' }}
>
Forgot password?
</Link>
<Field.Text
name="password"
label="Password"
placeholder="6+ characters"
type={showPassword.value ? 'text' : 'password'}
slotProps={{
inputLabel: { shrink: true },
input: {
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={showPassword.onToggle} edge="end">
<Iconify
icon={showPassword.value ? 'solar:eye-bold' : 'solar:eye-closed-bold'}
/>
</IconButton>
</InputAdornment>
),
},
}}
/>
</Box>
<Button
fullWidth
color="inherit"
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Sign in..."
>
Sign in
</Button>
</Box>
);
return (
<>
<FormHead
title="Sign in to your account"
description={
<>
{`Dont have an account? `}
<Link component={RouterLink} href={paths.auth.supabase.signUp} variant="subtitle2">
Get started
</Link>
</>
}
sx={{ textAlign: { xs: 'center', md: 'left' } }}
/>
{!!errorMessage && (
<Alert severity="error" sx={{ mb: 3 }}>
{errorMessage}
</Alert>
)}
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
</>
);
}

View File

@@ -0,0 +1,166 @@
import { z as zod } from 'zod';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { useBoolean } from 'minimal-shared/hooks';
import { zodResolver } from '@hookform/resolvers/zod';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Alert from '@mui/material/Alert';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
import { paths } from 'src/routes/paths';
import { useRouter } from 'src/routes/hooks';
import { RouterLink } from 'src/routes/components';
import { Iconify } from 'src/components/iconify';
import { Form, Field } from 'src/components/hook-form';
import { getErrorMessage } from '../../utils';
import { signUp } from '../../context/supabase';
import { FormHead } from '../../components/form-head';
import { SignUpTerms } from '../../components/sign-up-terms';
// ----------------------------------------------------------------------
export type SignUpSchemaType = zod.infer<typeof SignUpSchema>;
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 SupabaseSignUpView() {
const router = useRouter();
const showPassword = useBoolean();
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const defaultValues: SignUpSchemaType = {
firstName: '',
lastName: '',
email: '',
password: '',
};
const methods = useForm<SignUpSchemaType>({
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,
});
router.push(paths.auth.supabase.verify);
} catch (error) {
console.error(error);
const feedbackMessage = getErrorMessage(error);
setErrorMessage(feedbackMessage);
}
});
const renderForm = () => (
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
<Box
sx={{ display: 'flex', gap: { xs: 3, sm: 2 }, flexDirection: { xs: 'column', sm: 'row' } }}
>
<Field.Text
name="firstName"
label="First name"
slotProps={{ inputLabel: { shrink: true } }}
/>
<Field.Text
name="lastName"
label="Last name"
slotProps={{ inputLabel: { shrink: true } }}
/>
</Box>
<Field.Text name="email" label="Email address" slotProps={{ inputLabel: { shrink: true } }} />
<Field.Text
name="password"
label="Password"
placeholder="6+ characters"
type={showPassword.value ? 'text' : 'password'}
slotProps={{
inputLabel: { shrink: true },
input: {
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={showPassword.onToggle} edge="end">
<Iconify icon={showPassword.value ? 'solar:eye-bold' : 'solar:eye-closed-bold'} />
</IconButton>
</InputAdornment>
),
},
}}
/>
<Button
fullWidth
color="inherit"
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Create account..."
>
Create account
</Button>
</Box>
);
return (
<>
<FormHead
title="Get started absolutely free"
description={
<>
{`Already have an account? `}
<Link component={RouterLink} href={paths.auth.supabase.signIn} variant="subtitle2">
Get started
</Link>
</>
}
sx={{ textAlign: { xs: 'center', md: 'left' } }}
/>
{!!errorMessage && (
<Alert severity="error" sx={{ mb: 3 }}>
{errorMessage}
</Alert>
)}
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
<SignUpTerms />
</>
);
}

View File

@@ -0,0 +1,149 @@
import { z as zod } from 'zod';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { useBoolean } from 'minimal-shared/hooks';
import { zodResolver } from '@hookform/resolvers/zod';
import Box from '@mui/material/Box';
import Alert from '@mui/material/Alert';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
import { paths } from 'src/routes/paths';
import { useRouter } from 'src/routes/hooks';
import { NewPasswordIcon } from 'src/assets/icons';
import { Iconify } from 'src/components/iconify';
import { Form, Field } from 'src/components/hook-form';
import { getErrorMessage } from '../../utils';
import { FormHead } from '../../components/form-head';
import { updatePassword } from '../../context/supabase';
// ----------------------------------------------------------------------
export type UpdatePasswordSchemaType = zod.infer<typeof UpdatePasswordSchema>;
export const UpdatePasswordSchema = zod
.object({
password: zod
.string()
.min(1, { message: 'Password is required!' })
.min(6, { message: 'Password must be at least 6 characters!' }),
confirmPassword: zod.string().min(1, { message: 'Confirm password is required!' }),
})
.refine((data) => data.password === data.confirmPassword, {
message: 'Passwords do not match!',
path: ['confirmPassword'],
});
// ----------------------------------------------------------------------
export function SupabaseUpdatePasswordView() {
const router = useRouter();
const showPassword = useBoolean();
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const defaultValues: UpdatePasswordSchemaType = {
password: '',
confirmPassword: '',
};
const methods = useForm<UpdatePasswordSchemaType>({
resolver: zodResolver(UpdatePasswordSchema),
defaultValues,
});
const {
handleSubmit,
formState: { isSubmitting },
} = methods;
const onSubmit = handleSubmit(async (data) => {
try {
await updatePassword({ password: data.password });
router.push(paths.dashboard.root);
} catch (error) {
console.error(error);
const feedbackMessage = getErrorMessage(error);
setErrorMessage(feedbackMessage);
}
});
const renderForm = () => (
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
<Field.Text
name="password"
label="Password"
placeholder="6+ characters"
type={showPassword.value ? 'text' : 'password'}
slotProps={{
inputLabel: { shrink: true },
input: {
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={showPassword.onToggle} edge="end">
<Iconify icon={showPassword.value ? 'solar:eye-bold' : 'solar:eye-closed-bold'} />
</IconButton>
</InputAdornment>
),
},
}}
/>
<Field.Text
name="confirmPassword"
label="Confirm password"
type={showPassword.value ? 'text' : 'password'}
slotProps={{
inputLabel: { shrink: true },
input: {
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={showPassword.onToggle} edge="end">
<Iconify icon={showPassword.value ? 'solar:eye-bold' : 'solar:eye-closed-bold'} />
</IconButton>
</InputAdornment>
),
},
}}
/>
<Button
fullWidth
type="submit"
size="large"
variant="contained"
loading={isSubmitting}
loadingIndicator="Update password..."
>
Update password
</Button>
</Box>
);
return (
<>
<FormHead
icon={<NewPasswordIcon />}
title="Update password"
description="Successful updates enable access using the new password."
/>
{!!errorMessage && (
<Alert severity="error" sx={{ mb: 3 }}>
{errorMessage}
</Alert>
)}
<Form methods={methods} onSubmit={onSubmit}>
{renderForm()}
</Form>
</>
);
}

View File

@@ -0,0 +1,22 @@
import { paths } from 'src/routes/paths';
import { EmailInboxIcon } from 'src/assets/icons';
import { FormHead } from '../../components/form-head';
import { FormReturnLink } from '../../components/form-return-link';
// ----------------------------------------------------------------------
export function SupabaseVerifyView() {
return (
<>
<FormHead
icon={<EmailInboxIcon />}
title="Please check your email!"
description={`We've emailed a 6-digit confirmation code. \nPlease enter the code in the box below to verify your email.`}
/>
<FormReturnLink href={paths.auth.supabase.signIn} sx={{ mt: 0 }} />
</>
);
}