Compare commits

...

11 Commits

67 changed files with 1495 additions and 153 deletions

View File

@@ -1,4 +1,13 @@
import type { User } from '@prisma/client';
// src/app/api/auth/me/route.ts
//
// PURPOSE:
// - T.B.A.
//
// RULES:
// - T.B.A.
//
import type { PartyUser, User } from '@prisma/client';
import type { NextRequest } from 'next/server';
import { headers } from 'next/headers';
@@ -11,9 +20,11 @@ import { getUserById } from 'src/app/services/user.service';
import { createAccessLog } from 'src/app/services/access-log.service';
import { flattenNextjsRequest } from '../sign-in/flattenNextjsRequest';
import { getPartyUserById } from 'src/app/services/party-user.service';
// ----------------------------------------------------------------------
// NOTE: keep this comment to let prisma running on nextjs
// export const runtime = 'edge';
/**
@@ -29,6 +40,7 @@ const INVALID_AUTH_TOKEN = 'Invalid authorization token';
const USER_ID_NOT_FOUND = 'userId not found';
const USER_TOKEN_OK = 'user token check ok';
const AUTHORIZATION_TOKEN_MISSING_OR_INVALID = 'Authorization token missing or invalid';
const USER_BANNED = 'user banned';
export async function GET(req: NextRequest) {
const debug = { 'req.headers': flattenNextjsRequest(req) };
@@ -43,12 +55,17 @@ export async function GET(req: NextRequest) {
const accessToken = `${authorization}`.split(' ')[1];
const data = await verify(accessToken, JWT_SECRET);
console.log(data.userId);
if (data.userId) {
// TODO: remove me
// const currentUser = _users.find((user) => user.id === data.userId);
const currentUser: User | null = await getUserById(data.userId);
const { userId } = data;
let currentUser: User | PartyUser | null = null;
currentUser = await getPartyUserById(userId);
if (!currentUser) {
currentUser = await getUserById(userId);
}
if (!currentUser) {
createAccessLog('', USER_TOKEN_CHECK_FAILED, debug);

View File

@@ -1,11 +1,26 @@
###
# username and password ok
GET http://localhost:7272/api/auth/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWJnbnUyengwMDBjaHEzaGZ3dmtjejlvIiwiaWF0IjoxNzQ4OTY0ODkyLCJleHAiOjE3NTAxNzQ0OTJ9.lo04laCxtm0IVeYaETEV3hXKyDmXPEn7SyWtY2VR4dI
GET http://localhost:7272/api/auth/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWMwdWo4aXgwMDBqM2Y1eWhxc29xMW9wIiwiaWF0IjoxNzUwMjE5NTYyLCJleHAiOjE3NTE0MjkxNjJ9.8gKM2oMquccM_HDEfBAgtapCGf3M1eIp6SZ_knx7d1g
###
# username and password ok
POST http://localhost:7272/api/auth/sign-in
content-type: application/json
{
"email": "demo@minimals.cc",
"password": "@2Minimal"
}
###
# There is no user corresponding to the email address.
POST http://localhost:7272/api/auth/sign-in
content-type: application/json
@@ -15,7 +30,9 @@ content-type: application/json
}
###
# Wrong password
POST http://localhost:7272/api/auth/sign-in
content-type: application/json

View File

@@ -0,0 +1,21 @@
import { logger } from 'src/utils/logger';
import { STATUS, response, handleError } from 'src/utils/response';
import { countTotalEvents } from 'src/app/services/eventItem.service';
// ----------------------------------------------------------------------
/** **************************************
* GET - Events, obsoleted
*************************************** */
export async function GET() {
try {
const numOfEvent = await countTotalEvents();
logger('[Event] list', numOfEvent);
return response({ numOfEvent }, STATUS.OK);
} catch (error) {
return handleError('Event - Get list', error);
}
}

View File

@@ -0,0 +1,3 @@
###
GET http://localhost:7272/api/event/helloworld

View File

@@ -0,0 +1,22 @@
import { logger } from 'src/utils/logger';
import { STATUS, response, handleError } from 'src/utils/response';
// src/app/api/event/list/route.ts
import { countTotalEvents, listEvents } from 'src/app/services/eventItem.service';
// ----------------------------------------------------------------------
/** **************************************
* GET - Events, obsoleted
*************************************** */
export async function GET() {
try {
const numOfEvents = await countTotalEvents();
// logger('[Event] list', numOfEvents.length);
return response({ numOfEvents }, STATUS.OK);
} catch (error) {
return handleError('Event - Get list', error);
}
}

View File

@@ -0,0 +1,3 @@
###
GET http://localhost:7272/api/event/numOfEvent

View File

@@ -0,0 +1,23 @@
//
//
import { logger } from 'src/utils/logger';
import { STATUS, response, handleError } from 'src/utils/response';
import { countTotalEvents } from 'src/app/services/eventItem.service';
// ----------------------------------------------------------------------
/** **************************************
* GET - Events, obsoleted
*************************************** */
export async function GET() {
try {
const numOfEvent = await countTotalEvents();
// logger('[Event] list', numOfEvent);
return response({ numOfEvent }, STATUS.OK);
} catch (error) {
return handleError('Event - Get list', error);
}
}

View File

@@ -0,0 +1,3 @@
###
GET http://localhost:7272/api/party-event/numOfEvent

View File

@@ -7,6 +7,14 @@
// - Log all access attempts (success/failure)
// - Validate token structure and user existence
//
// RULES:
// - Must validate Bearer token format before processing
// - All errors must be logged via access-log service
// - User existence must be verified after token validation
// - Sensitive data must be filtered from responses
// - Mock JWT_SECRET should be replaced in production
// - Debug info should be included in error logs
//
import type { NextRequest } from 'next/server';
import type { PartyUser } from '@prisma/client';
@@ -23,14 +31,6 @@ import { flattenNextjsRequest } from '../sign-in/flattenNextjsRequest';
// ----------------------------------------------------------------------
/**
* This API is used for demo purpose only
* You should use a real database
* You should hash the password before saving to database
* You should not save the password in the database
* You should not expose the JWT_SECRET in the client side
*/
const ERR_USER_TOKEN_CHECK_FAILED = 'user token check failed';
const ERR_INVALID_AUTH_TOKEN = 'Invalid authorization token';
const ERR_USER_ID_NOT_FOUND = 'userId not found';

View File

@@ -21,8 +21,8 @@ POST http://localhost:7272/api/party-user-auth/sign-in
content-type: application/json
{
"email": "demo@minimals.cc",
"password": "@2Minimal"
"email": "party_user0@prisma.io",
"password": "Aa12345678"
}
###

View File

@@ -0,0 +1,6 @@
export const ERR_USER_NOT_FOUND = 'There is no user corresponding to the email address.';
export const ERR_WRONG_PASSWORD = 'Wrong password';
export const LOG_USER_TRIED_LOGIN_WITH_EMAIL = `user tried login with email`;
export const LOG_USER_LOGGED_WITH_WRONG_PASSWORD = 'user logged with wrong password';
export const LOG_ACCESS_GRANTED = 'access granted';
export const LOG_ATTEMPTED_LOGIN_BUT_FAILED = 'attempted login but failed';

View File

@@ -10,9 +10,16 @@ import { createAccessLog } from 'src/app/services/access-log.service';
import prisma from '../../../lib/prisma';
import { flattenNextjsRequest } from './flattenNextjsRequest';
import {
LOG_USER_TRIED_LOGIN_WITH_EMAIL,
ERR_USER_NOT_FOUND,
LOG_USER_LOGGED_WITH_WRONG_PASSWORD,
ERR_WRONG_PASSWORD,
LOG_ACCESS_GRANTED,
LOG_ATTEMPTED_LOGIN_BUT_FAILED,
} from './constants';
// ----------------------------------------------------------------------
/**
* This API is used for demo purpose only
* You should use a real database
@@ -21,9 +28,6 @@ import { flattenNextjsRequest } from './flattenNextjsRequest';
* You should not expose the JWT_SECRET in the client side
*/
const ERR_USER_NOT_FOUND = 'There is no user corresponding to the email address.';
const ERR_WRONG_PASSWORD = 'Wrong password';
export async function POST(req: NextRequest) {
const debug = { 'req.headers': flattenNextjsRequest(req) };
@@ -32,12 +36,12 @@ export async function POST(req: NextRequest) {
const currentUser = await prisma.partyUser.findFirst({ where: { email } });
if (!currentUser) {
await createAccessLog('', `user tried login with email ${email}`, { debug });
await createAccessLog('', LOG_USER_TRIED_LOGIN_WITH_EMAIL, { email, debug });
return response({ message: ERR_USER_NOT_FOUND }, STATUS.UNAUTHORIZED);
}
if (currentUser?.password !== password) {
await createAccessLog(currentUser.id, 'user logged with wrong password', { debug });
await createAccessLog(currentUser.id, LOG_USER_LOGGED_WITH_WRONG_PASSWORD, { debug });
return response({ message: ERR_WRONG_PASSWORD }, STATUS.UNAUTHORIZED);
}
@@ -45,11 +49,11 @@ export async function POST(req: NextRequest) {
expiresIn: JWT_EXPIRES_IN,
});
await createAccessLog(currentUser.id, 'access granted', { debug });
await createAccessLog(currentUser.id, LOG_ACCESS_GRANTED, { debug });
return response({ user: currentUser, accessToken }, STATUS.OK);
} catch (error) {
await createAccessLog('', 'attempted login but failed', { debug, error });
await createAccessLog('', LOG_ATTEMPTED_LOGIN_BUT_FAILED, { debug, error });
return handleError('Auth - Sign in', error);
}

View File

@@ -2,14 +2,19 @@
###
GET http://localhost:7272/api/auth/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWMwdWo4bGwwMDByM2Y1eXhob3JuMW1hIiwiaWF0IjoxNzUwMjE5NTgwLCJleHAiOjE3NTE0MjkxODB9.7BtuIKEvwDcHc5j9JYX0Eb1uB37kFH1Ksx4MTDTtEWQ
###
# username and password ok
POST http://localhost:7272/api/party-user-auth/sign-in
content-type: application/json
{
"email": "demo@minimals.cc",
"password": "@2Minimal"
"email": "party_user0@prisma.io",
"password": "Aa12345678"
}
###

View File

@@ -1,28 +1,23 @@
// src/app/services/AccessLog.service.ts
// src/app/services/access-log.service.ts
//
// PURPOSE:
// Service for handling AccessLog records
// - Core service for audit logging and access tracking
// - Records all authentication attempts and system access
// - Provides query capabilities for audit trails
// - Integrates with Prisma ORM for database operations
//
// RULES:
// - All methods return Promises
// - Input validation should be done at controller level
// - Errors should be propagated to caller
// - All methods return Promises for async operations
// - Input validation must be done at controller level
// - Errors should be propagated to caller with context
// - Audit records should never be modified after creation
// - Sensitive data should be hashed before logging
// - Metadata should be stored as JSON for flexibility
import type { AccessLog } from '@prisma/client';
import prisma from '../lib/prisma';
// type CreateAccessLog = {
// userId?: string;
// message?: string;
// metadata?: Record<string, any>;
// };
// type UpdateAccessLog = {
// status?: number;
// metadata?: object;
// };
async function listAccessLogs(): Promise<AccessLog[]> {
return prisma.accessLog.findMany({
orderBy: { timestamp: 'desc' },

View File

@@ -51,6 +51,17 @@ async function getEventItemById(eventId: string): Promise<EventItem | null> {
return prisma.eventItem.findFirst({ where: { id: eventId } });
}
async function countTotalEvents(): Promise<number> {
try {
const result = await prisma.eventItem.findMany();
console.log({ result });
return result.length;
} catch (error) {
console.log(error);
return -1;
}
}
// async function createNewEvent(createForm: CreateEvent) {
// return prisma.event.create({ data: createForm });
// }
@@ -73,4 +84,5 @@ export {
// deleteEvent,
// createNewEvent,
getEventItemById,
countTotalEvents,
};

View File

@@ -23,7 +23,7 @@
"re:build-npm": "npm run clean && npm install && npm run build",
"tsc:dev": "yarn dev & yarn tsc:watch",
"tsc:print": "npx tsc --showConfig",
"tsc:w": "npx nodemon --delay 1 --ext ts,tsx --exec \"yarn tsc\"",
"tsc:w": "npx nodemon --delay 1 --ext ts,tsx --exec \"yarn tsc || (sleep 10; touch src/app.tsx)\"",
"tsc:watch": "tsc --noEmit --watch",
"tsc": "tsc --noEmit"
},
@@ -147,4 +147,4 @@
"vite": "^6.2.3",
"vite-plugin-checker": "^0.9.1"
}
}
}

View File

@@ -1,5 +1,6 @@
import { useMemo } from 'react';
import { endpoints, fetcher } from 'src/lib/axios';
import { fetcher } from 'src/lib/axios';
import { endpoints } from 'src/lib/endpoints';
import type { IPostItem } from 'src/types/blog';
import type { SWRConfiguration } from 'swr';
import useSWR from 'swr';

View File

@@ -1,5 +1,6 @@
import { useMemo } from 'react';
import axios, { endpoints, fetcher } from 'src/lib/axios';
import axios, { fetcher } from 'src/lib/axios';
import { endpoints } from 'src/lib/endpoints';
import type { ICalendarEvent } from 'src/types/calendar';
import type { SWRConfiguration } from 'swr';
import useSWR, { mutate } from 'swr';

View File

@@ -1,6 +1,7 @@
import { keyBy } from 'es-toolkit';
import { useMemo } from 'react';
import axios, { endpoints, fetcher } from 'src/lib/axios';
import axios, { fetcher } from 'src/lib/axios';
import { endpoints } from 'src/lib/endpoints';
import type { IChatConversation, IChatMessage, IChatParticipant } from 'src/types/chat';
import type { SWRConfiguration } from 'swr';
import useSWR, { mutate } from 'swr';

View File

@@ -1,6 +1,7 @@
// src/actions/invoice.ts
import { useMemo } from 'react';
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
import axiosInstance, { fetcher } from 'src/lib/axios';
import { endpoints } from 'src/lib/endpoints';
import type { IInvoiceItem, SaveInvoiceData } from 'src/types/invoice';
import type { SWRConfiguration } from 'swr';
import useSWR from 'swr';

View File

@@ -1,6 +1,7 @@
import type { UniqueIdentifier } from '@dnd-kit/core';
import { startTransition, useMemo } from 'react';
import axios, { endpoints, fetcher } from 'src/lib/axios';
import axios, { fetcher } from 'src/lib/axios';
import { endpoints } from 'src/lib/endpoints';
import type { IKanban, IKanbanColumn, IKanbanTask } from 'src/types/kanban';
import type { SWRConfiguration } from 'swr';
import useSWR, { mutate } from 'swr';

View File

@@ -1,6 +1,7 @@
import { keyBy } from 'es-toolkit';
import { useMemo } from 'react';
import { endpoints, fetcher } from 'src/lib/axios';
import { fetcher } from 'src/lib/axios';
import { endpoints } from 'src/lib/endpoints';
import type { IMail, IMailLabel } from 'src/types/mail';
import type { SWRConfiguration } from 'swr';
import useSWR from 'swr';

View File

@@ -1,6 +1,7 @@
// src/actions/order.ts
import { useMemo } from 'react';
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
import axiosInstance, { fetcher } from 'src/lib/axios';
import { endpoints } from 'src/lib/endpoints';
import type { IOrderItem } from 'src/types/order';
import type { IProductItem } from 'src/types/product';
import type { SWRConfiguration } from 'swr';

View File

@@ -1,7 +1,8 @@
// src/actions/party-event.ts
//
import { useMemo } from 'react';
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
import axiosInstance, { fetcher } from 'src/lib/axios';
import { endpoints } from 'src/lib/endpoints';
import type { IPartyEventItem } from 'src/types/party-event';
import type { SWRConfiguration } from 'swr';
import useSWR, { mutate } from 'swr';

View File

@@ -1,7 +1,8 @@
// src/actions/party-order.ts
//
import { useMemo } from 'react';
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
import axiosInstance, { fetcher } from 'src/lib/axios';
import { endpoints } from 'src/lib/endpoints';
import type { IPartyOrderItem } from 'src/types/party-order';
import type { SWRConfiguration } from 'swr';
import useSWR, { mutate } from 'swr';

View File

@@ -1,7 +1,8 @@
// src/actions/party-user1.ts
//
import { useMemo } from 'react';
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
import axiosInstance, { fetcher } from 'src/lib/axios';
import { endpoints } from 'src/lib/endpoints';
import type { IPartyUserItem } from 'src/types/party-user';
import type { IProductItem } from 'src/types/product';
import type { SWRConfiguration } from 'swr';

View File

@@ -1,7 +1,8 @@
// src/actions/product.ts
//
import { useMemo } from 'react';
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
import axiosInstance, { fetcher } from 'src/lib/axios';
import { endpoints } from 'src/lib/endpoints';
import type { IProductItem } from 'src/types/product';
import type { SWRConfiguration } from 'swr';
import useSWR, { mutate } from 'swr';

View File

@@ -1,5 +1,6 @@
import { useMemo } from 'react';
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
import axiosInstance, { fetcher } from 'src/lib/axios';
import { endpoints } from 'src/lib/endpoints';
import type { IProductItem } from 'src/types/product';
import { IUserItem } from 'src/types/user';
import type { SWRConfiguration } from 'swr';

View File

@@ -1,4 +1,5 @@
import axios, { endpoints } from 'src/lib/axios';
import axios from 'src/lib/axios';
import { endpoints } from 'src/lib/endpoints';
import { JWT_STORAGE_KEY } from './constant';
import { setSession } from './utils';

View File

@@ -1,6 +1,7 @@
import { useSetState } from 'minimal-shared/hooks';
import { useCallback, useEffect, useMemo } from 'react';
import axios, { endpoints } from 'src/lib/axios';
import axios from 'src/lib/axios';
import { endpoints } from 'src/lib/endpoints';
import type { AuthState } from '../../types';
import { AuthContext } from '../auth-context';
import { JWT_STORAGE_KEY } from './constant';

View File

@@ -0,0 +1,2 @@
export const ERR_ACCESS_TOKEN_NOT_FOUND = `Access token not found in response`;
export const ACCESS_TOKEN_NOT_FOUND_IN_RESPONSE = 'Access token not found in response';

View File

@@ -0,0 +1,85 @@
import axios from 'src/lib/axios';
import { endpoints } from 'src/lib/endpoints';
import { JWT_STORAGE_KEY } from './constant';
import { ACCESS_TOKEN_NOT_FOUND_IN_RESPONSE, ERR_ACCESS_TOKEN_NOT_FOUND } from './ERRORS';
import { setSession } from './utils';
// ----------------------------------------------------------------------
export type SignInParams = {
email: string;
password: string;
};
export type SignUpParams = {
email: string;
password: string;
firstName: string;
lastName: string;
};
/** **************************************
* Sign in
*************************************** */
export const signInWithPassword = async ({ email, password }: SignInParams): Promise<void> => {
try {
const params = { email, password };
const res = await axios.post(endpoints.partyUserAuth.signIn, params);
const { accessToken } = res.data;
if (!accessToken) {
throw new Error(ERR_ACCESS_TOKEN_NOT_FOUND);
}
setSession(accessToken);
} catch (error) {
console.error('Error during sign in:', error);
throw error;
}
};
/** **************************************
* Sign up
*************************************** */
export const signUp = async ({
email,
password,
firstName,
lastName,
}: SignUpParams): Promise<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 @@
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 axios from 'src/lib/axios';
import { paths } from 'src/routes/paths';
import { JWT_STORAGE_KEY } from './constant';
// ----------------------------------------------------------------------
export function jwtDecode(token: string) {
try {
if (!token) return null;
const parts = token.split('.');
if (parts.length < 2) {
throw new Error('Invalid token!');
}
const base64Url = parts[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const decoded = JSON.parse(atob(base64));
return decoded;
} catch (error) {
console.error('Error decoding token:', error);
throw error;
}
}
// ----------------------------------------------------------------------
export function isValidToken(accessToken: string) {
if (!accessToken) {
return false;
}
try {
const decoded = jwtDecode(accessToken);
if (!decoded || !('exp' in decoded)) {
return false;
}
const currentTime = Date.now() / 1000;
return decoded.exp > currentTime;
} catch (error) {
console.error('Error during token validation:', error);
return false;
}
}
// ----------------------------------------------------------------------
export function tokenExpired(exp: number) {
const currentTime = Date.now();
const timeLeft = exp * 1000 - currentTime;
setTimeout(() => {
try {
alert('Token expired!');
sessionStorage.removeItem(JWT_STORAGE_KEY);
window.location.href = paths.auth.jwt.signIn;
} catch (error) {
console.error('Error during token expiration:', error);
throw error;
}
}, timeLeft);
}
// ----------------------------------------------------------------------
const INVALID_ACCESS_TOKEN = 'Invalid access token!';
export async function setSession(accessToken: string | null) {
try {
if (accessToken) {
sessionStorage.setItem(JWT_STORAGE_KEY, accessToken);
axios.defaults.headers.common.Authorization = `Bearer ${accessToken}`;
const decodedToken = jwtDecode(accessToken); // ~3 days by minimals server
if (decodedToken && 'exp' in decodedToken) {
tokenExpired(decodedToken.exp);
} else {
throw new Error(INVALID_ACCESS_TOKEN);
}
} else {
sessionStorage.removeItem(JWT_STORAGE_KEY);
delete axios.defaults.headers.common.Authorization;
}
} catch (error) {
console.error('Error during set session:', error);
throw error;
}
}

View File

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

View File

@@ -0,0 +1,167 @@
import { zodResolver } from '@hookform/resolvers/zod';
import Alert from '@mui/material/Alert';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
import Link from '@mui/material/Link';
import { useBoolean } from 'minimal-shared/hooks';
import React, { useState } from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { Field, Form } from 'src/components/hook-form';
import { Iconify } from 'src/components/iconify';
import { RouterLink } from 'src/routes/components';
import { useRouter } from 'src/routes/hooks';
import { paths } from 'src/routes/paths';
import { z as zod } from 'zod';
import { FormHead } from '../../components/form-head';
import { signInWithPassword } from '../../context/party-user-jwt';
import { useAuthContext } from '../../hooks';
import { getErrorMessage } from '../../utils';
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
export function JwtSignInView(): React.JSX.Element {
const { t } = useTranslation();
const router = useRouter();
const showPassword = useBoolean();
const { checkUserSession } = useAuthContext();
const [errorMessage, setErrorMessage] = useState<string | null>(null);
type SignInSchemaType = zod.infer<typeof SignInSchema>;
const EMAIL_IS_REQUIRED = 'Email is required!';
const EMAIL_MUST_BE_A_VALID_EMAIL_ADDRESS = 'Email must be a valid email address!';
const PASSWORD_IS_REQUIRED = 'Password is required!';
const PASSWORD_MUST_BE_AT_LEAST_6_CHARACTERS = 'Password must be at least 6 characters!';
const SignInSchema = zod.object({
email: zod
.string()
.min(1, { message: EMAIL_IS_REQUIRED })
.email({ message: EMAIL_MUST_BE_A_VALID_EMAIL_ADDRESS }),
password: zod
.string()
.min(1, { message: PASSWORD_IS_REQUIRED })
.min(6, { message: PASSWORD_MUST_BE_AT_LEAST_6_CHARACTERS }),
});
const defaultValues: SignInSchemaType = {
email: 'party_user0@prisma.io',
password: 'Aa12345678',
};
const methods = useForm<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..."
>
{t('sign-in')}
</Button>
</Box>
);
return (
<>
<FormHead
title="Sign in to your account"
description={
<>
{`Dont have an account? `}
<Link component={RouterLink} href={paths.partyUserAuth.jwt.signUp} variant="subtitle2">
{t('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,166 @@
import { zodResolver } from '@hookform/resolvers/zod';
import Alert from '@mui/material/Alert';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
import Link from '@mui/material/Link';
import { useBoolean } from 'minimal-shared/hooks';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { Field, Form } from 'src/components/hook-form';
import { Iconify } from 'src/components/iconify';
import { RouterLink } from 'src/routes/components';
import { useRouter } from 'src/routes/hooks';
import { paths } from 'src/routes/paths';
import { z as zod } from 'zod';
import { FormHead } from '../../components/form-head';
import { SignUpTerms } from '../../components/sign-up-terms';
import { signUp } from '../../context/jwt';
import { useAuthContext } from '../../hooks';
import { getErrorMessage } from '../../utils';
// ----------------------------------------------------------------------
export type SignUpSchemaType = zod.infer<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="Hi New party user, 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

@@ -1,3 +1,18 @@
// src/lib/axios.ts
//
// PURPOSE:
// - Centralized Axios instance configuration
// - Global response error handling
// - Standardized API endpoint definitions
// - Reusable fetcher utility
//
// RULES:
// - All API calls must use this axiosInstance
// - Custom error handling in interceptor
// - Endpoints should be defined here for consistency
// - Fetcher should be used for simple GET requests
//
import type { AxiosRequestConfig } from 'axios';
import axios from 'axios';
import { CONFIG } from 'src/global-config';
@@ -27,94 +42,3 @@ export const fetcher = async (args: string | [string, AxiosRequestConfig]) => {
throw error;
}
};
// ----------------------------------------------------------------------
export const endpoints = {
chat: '/api/chat',
kanban: '/api/kanban',
calendar: '/api/calendar',
auth: {
me: '/api/auth/me',
signIn: '/api/auth/sign-in',
signUp: '/api/auth/sign-up',
},
mail: {
list: '/api/mail/list',
details: '/api/mail/details',
labels: '/api/mail/labels',
},
post: {
list: '/api/post/list',
details: '/api/post/details',
latest: '/api/post/latest',
search: '/api/post/search',
},
product: {
list: '/api/product/list',
details: '/api/product/details',
search: '/api/product/search',
save: '/api/product/saveProduct',
create: '/api/product/create',
update: '/api/product/update',
delete: '/api/product/delete',
},
user: {
list: '/api/user/list',
profile: '/api/user/profile',
update: '/api/user/update',
settings: '/api/user/settings',
details: '/api/user/details',
},
order: {
list: '/api/order/list',
profile: '/api/order/profile',
update: '/api/order/update',
settings: '/api/order/settings',
details: '/api/order/details',
changeStatus: (orderId: string) => `/api/order/changeStatus?orderId=${orderId}`,
},
invoice: {
list: '/api/invoice/list',
profile: '/api/invoice/profile',
update: '/api/invoice/update',
saveInvoice: (invoiceId: string) => `/api/invoice/saveInvoice?invoiceId=${invoiceId}`,
settings: '/api/invoice/settings',
details: '/api/invoice/details',
changeStatus: (invoiceId: string) => `/api/invoice/changeStatus?invoiceId=${invoiceId}`,
search: '/api/invoice/search',
},
//
//
//
partyEvent: {
list: '/api/party-event/list',
details: '/api/party-event/details',
search: '/api/party-event/search',
create: '/api/party-event/create',
update: '/api/party-event/update',
delete: '/api/party-event/delete',
},
partyOrder: {
create: '/api/party-order/create',
delete: '/api/party-order/delete',
list: '/api/party-order/list',
profile: '/api/party-order/profile',
update: '/api/party-order/update',
settings: '/api/party-order/settings',
details: '/api/party-order/details',
changeStatus: (partyOrderId: string) =>
`/api/party-order/changeStatus?partyOrderId=${partyOrderId}`,
},
partyUser: {
list: '/api/party-user/list',
details: '/api/party-user/details',
search: '/api/party-user/search',
create: '/api/party-user/create',
update: '/api/party-user/update',
delete: '/api/party-user/delete',
//
detailsByPartyUserId: (partyUserId: string) =>
`/api/party-user/details?partyUserId=${partyUserId}`,
},
};

View File

@@ -0,0 +1,96 @@
// ----------------------------------------------------------------------
export const endpoints = {
chat: '/api/chat',
kanban: '/api/kanban',
calendar: '/api/calendar',
auth: {
me: '/api/auth/me',
signIn: '/api/auth/sign-in',
signUp: '/api/auth/sign-up',
},
mail: {
list: '/api/mail/list',
details: '/api/mail/details',
labels: '/api/mail/labels',
},
post: {
list: '/api/post/list',
details: '/api/post/details',
latest: '/api/post/latest',
search: '/api/post/search',
},
product: {
list: '/api/product/list',
details: '/api/product/details',
search: '/api/product/search',
save: '/api/product/saveProduct',
create: '/api/product/create',
update: '/api/product/update',
delete: '/api/product/delete',
},
user: {
list: '/api/user/list',
profile: '/api/user/profile',
update: '/api/user/update',
settings: '/api/user/settings',
details: '/api/user/details',
},
order: {
list: '/api/order/list',
profile: '/api/order/profile',
update: '/api/order/update',
settings: '/api/order/settings',
details: '/api/order/details',
changeStatus: (orderId: string) => `/api/order/changeStatus?orderId=${orderId}`,
},
invoice: {
list: '/api/invoice/list',
profile: '/api/invoice/profile',
update: '/api/invoice/update',
saveInvoice: (invoiceId: string) => `/api/invoice/saveInvoice?invoiceId=${invoiceId}`,
settings: '/api/invoice/settings',
details: '/api/invoice/details',
changeStatus: (invoiceId: string) => `/api/invoice/changeStatus?invoiceId=${invoiceId}`,
search: '/api/invoice/search',
},
//
//
//
partyEvent: {
list: '/api/party-event/list',
details: '/api/party-event/details',
search: '/api/party-event/search',
create: '/api/party-event/create',
update: '/api/party-event/update',
delete: '/api/party-event/delete',
numOfEvent: '/api/party-event/numOfEvent',
},
partyOrder: {
create: '/api/party-order/create',
delete: '/api/party-order/delete',
list: '/api/party-order/list',
profile: '/api/party-order/profile',
update: '/api/party-order/update',
settings: '/api/party-order/settings',
details: '/api/party-order/details',
changeStatus: (partyOrderId: string) =>
`/api/party-order/changeStatus?partyOrderId=${partyOrderId}`,
},
partyUser: {
list: '/api/party-user/list',
details: '/api/party-user/details',
search: '/api/party-user/search',
create: '/api/party-user/create',
update: '/api/party-user/update',
delete: '/api/party-user/delete',
//
detailsByPartyUserId: (partyUserId: string) =>
`/api/party-user/details?partyUserId=${partyUserId}`,
},
partyUserAuth: {
me: '/api/party-user-auth/me',
signIn: '/api/party-user-auth/sign-in',
signUp: '/api/party-user-auth/sign-up',
},
};

View File

@@ -0,0 +1,16 @@
import { AmplifyResetPasswordView } from 'src/auth/view/amplify';
import { CONFIG } from 'src/global-config';
// ----------------------------------------------------------------------
const metadata = { title: `Reset password | Amplify - ${CONFIG.appName}` };
export default function Page() {
return (
<>
<title>{metadata.title}</title>
<AmplifyResetPasswordView />
</>
);
}

View File

@@ -0,0 +1,16 @@
import { AmplifySignInView } from 'src/auth/view/amplify';
import { CONFIG } from 'src/global-config';
// ----------------------------------------------------------------------
const metadata = { title: `Sign in | Amplify - ${CONFIG.appName}` };
export default function Page() {
return (
<>
<title>{metadata.title}</title>
<AmplifySignInView />
</>
);
}

View File

@@ -0,0 +1,16 @@
import { AmplifySignUpView } from 'src/auth/view/amplify';
import { CONFIG } from 'src/global-config';
// ----------------------------------------------------------------------
const metadata = { title: `Sign up | Amplify - ${CONFIG.appName}` };
export default function Page() {
return (
<>
<title>{metadata.title}</title>
<AmplifySignUpView />
</>
);
}

View File

@@ -0,0 +1,16 @@
import { AmplifyUpdatePasswordView } from 'src/auth/view/amplify';
import { CONFIG } from 'src/global-config';
// ----------------------------------------------------------------------
const metadata = { title: `Update password | Amplify - ${CONFIG.appName}` };
export default function Page() {
return (
<>
<title>{metadata.title}</title>
<AmplifyUpdatePasswordView />
</>
);
}

View File

@@ -0,0 +1,16 @@
import { AmplifyVerifyView } from 'src/auth/view/amplify';
import { CONFIG } from 'src/global-config';
// ----------------------------------------------------------------------
const metadata = { title: `Verify | Amplify - ${CONFIG.appName}` };
export default function Page() {
return (
<>
<title>{metadata.title}</title>
<AmplifyVerifyView />
</>
);
}

View File

@@ -0,0 +1,7 @@
import { SplashScreen } from 'src/components/loading-screen';
// ----------------------------------------------------------------------
export default function CallbackPage() {
return <SplashScreen />;
}

View File

@@ -0,0 +1,16 @@
import { Auth0SignInView } from 'src/auth/view/auth0';
import { CONFIG } from 'src/global-config';
// ----------------------------------------------------------------------
const metadata = { title: `Sign in | Auth0 - ${CONFIG.appName}` };
export default function Page() {
return (
<>
<title>{metadata.title}</title>
<Auth0SignInView />
</>
);
}

View File

@@ -0,0 +1,16 @@
import { FirebaseResetPasswordView } from 'src/auth/view/firebase';
import { CONFIG } from 'src/global-config';
// ----------------------------------------------------------------------
const metadata = { title: `Reset password | Firebase - ${CONFIG.appName}` };
export default function Page() {
return (
<>
<title>{metadata.title}</title>
<FirebaseResetPasswordView />
</>
);
}

View File

@@ -0,0 +1,16 @@
import { FirebaseSignInView } from 'src/auth/view/firebase';
import { CONFIG } from 'src/global-config';
// ----------------------------------------------------------------------
const metadata = { title: `Sign in | Firebase - ${CONFIG.appName}` };
export default function Page() {
return (
<>
<title>{metadata.title}</title>
<FirebaseSignInView />
</>
);
}

View File

@@ -0,0 +1,16 @@
import { FirebaseSignUpView } from 'src/auth/view/firebase';
import { CONFIG } from 'src/global-config';
// ----------------------------------------------------------------------
const metadata = { title: `Sign up | Firebase - ${CONFIG.appName}` };
export default function Page() {
return (
<>
<title>{metadata.title}</title>
<FirebaseSignUpView />
</>
);
}

View File

@@ -0,0 +1,16 @@
import { FirebaseVerifyView } from 'src/auth/view/firebase';
import { CONFIG } from 'src/global-config';
// ----------------------------------------------------------------------
const metadata = { title: `Verify | Firebase - ${CONFIG.appName}` };
export default function Page() {
return (
<>
<title>{metadata.title}</title>
<FirebaseVerifyView />
</>
);
}

View File

@@ -0,0 +1,16 @@
import { JwtSignInView } from 'src/auth/view/party-user-jwt';
import { CONFIG } from 'src/global-config';
// ----------------------------------------------------------------------
const metadata = { title: `Sign in | Jwt - ${CONFIG.appName}` };
export default function Page() {
return (
<>
<title>{metadata.title}</title>
<JwtSignInView />
</>
);
}

View File

@@ -0,0 +1,16 @@
import { JwtSignUpView } from 'src/auth/view/party-user-jwt';
import { CONFIG } from 'src/global-config';
// ----------------------------------------------------------------------
const metadata = { title: `Sign up | Jwt - ${CONFIG.appName}` };
export default function Page() {
return (
<>
<title>{metadata.title}</title>
<JwtSignUpView />
</>
);
}

View File

@@ -0,0 +1,16 @@
import { SupabaseResetPasswordView } from 'src/auth/view/supabase';
import { CONFIG } from 'src/global-config';
// ----------------------------------------------------------------------
const metadata = { title: `Reset password | Supabase - ${CONFIG.appName}` };
export default function Page() {
return (
<>
<title>{metadata.title}</title>
<SupabaseResetPasswordView />
</>
);
}

View File

@@ -0,0 +1,16 @@
import { SupabaseSignInView } from 'src/auth/view/supabase';
import { CONFIG } from 'src/global-config';
// ----------------------------------------------------------------------
const metadata = { title: `Sign in | Supabase - ${CONFIG.appName}` };
export default function Page() {
return (
<>
<title>{metadata.title}</title>
<SupabaseSignInView />
</>
);
}

View File

@@ -0,0 +1,16 @@
import { SupabaseSignUpView } from 'src/auth/view/supabase';
import { CONFIG } from 'src/global-config';
// ----------------------------------------------------------------------
const metadata = { title: `Sign up | Supabase - ${CONFIG.appName}` };
export default function Page() {
return (
<>
<title>{metadata.title}</title>
<SupabaseSignUpView />
</>
);
}

View File

@@ -0,0 +1,16 @@
import { SupabaseUpdatePasswordView } from 'src/auth/view/supabase';
import { CONFIG } from 'src/global-config';
// ----------------------------------------------------------------------
const metadata = { title: `Update password | Supabase - ${CONFIG.appName}` };
export default function Page() {
return (
<>
<title>{metadata.title}</title>
<SupabaseUpdatePasswordView />
</>
);
}

View File

@@ -0,0 +1,16 @@
import { SupabaseVerifyView } from 'src/auth/view/supabase';
import { CONFIG } from 'src/global-config';
// ----------------------------------------------------------------------
const metadata = { title: `Verify | Supabase - ${CONFIG.appName}` };
export default function Page() {
return (
<>
<title>{metadata.title}</title>
<SupabaseVerifyView />
</>
);
}

View File

@@ -10,6 +10,8 @@ const ROOTS = {
AUTH: '/auth',
AUTH_DEMO: '/auth-demo',
DASHBOARD: '/dashboard',
//
PARTY_USER_AUTH: '/party-user-auth',
};
// ----------------------------------------------------------------------
@@ -207,4 +209,32 @@ export const paths = {
demo: { edit: `${ROOTS.DASHBOARD}/party-user/${MOCK_ID}/edit` },
},
},
partyUserAuth: {
jwt: {
signIn: `${ROOTS.PARTY_USER_AUTH}/jwt/sign-in`,
signUp: `${ROOTS.PARTY_USER_AUTH}/jwt/sign-up`,
},
//
// amplify: {
// signIn: `${ROOTS.PARTY_USER_AUTH}/amplify/sign-in`,
// verify: `${ROOTS.PARTY_USER_AUTH}/amplify/verify`,
// signUp: `${ROOTS.PARTY_USER_AUTH}/amplify/sign-up`,
// updatePassword: `${ROOTS.PARTY_USER_AUTH}/amplify/update-password`,
// resetPassword: `${ROOTS.PARTY_USER_AUTH}/amplify/reset-password`,
// },
// firebase: {
// signIn: `${ROOTS.PARTY_USER_AUTH}/firebase/sign-in`,
// verify: `${ROOTS.PARTY_USER_AUTH}/firebase/verify`,
// signUp: `${ROOTS.PARTY_USER_AUTH}/firebase/sign-up`,
// resetPassword: `${ROOTS.PARTY_USER_AUTH}/firebase/reset-password`,
// },
// auth0: { signIn: `${ROOTS.PARTY_USER_AUTH}/auth0/sign-in` },
// supabase: {
// signIn: `${ROOTS.PARTY_USER_AUTH}/supabase/sign-in`,
// verify: `${ROOTS.PARTY_USER_AUTH}/supabase/verify`,
// signUp: `${ROOTS.PARTY_USER_AUTH}/supabase/sign-up`,
// updatePassword: `${ROOTS.PARTY_USER_AUTH}/supabase/update-password`,
// resetPassword: `${ROOTS.PARTY_USER_AUTH}/supabase/reset-password`,
// },
},
};

View File

@@ -9,6 +9,7 @@ import { authDemoRoutes } from './auth-demo';
import { componentsRoutes } from './components';
import { dashboardRoutes } from './dashboard';
import { mainRoutes } from './main';
import { partyUserAuthRoutes } from './party-user-auth';
// ----------------------------------------------------------------------
@@ -48,6 +49,9 @@ export const routesSection: RouteObject[] = [
// Components
...componentsRoutes,
// party-user-auth
...partyUserAuthRoutes,
// No match
{ path: '*', element: <Page404 /> },
];

View File

@@ -0,0 +1,282 @@
import { lazy, Suspense } from 'react';
import type { RouteObject } from 'react-router';
import { Outlet } from 'react-router';
import { GuestGuard } from 'src/auth/guard';
import { SplashScreen } from 'src/components/loading-screen';
import { AuthSplitLayout } from 'src/layouts/auth-split';
// ----------------------------------------------------------------------
/** **************************************
* Jwt
*************************************** */
const Jwt = {
SignInPage: lazy(() => import('src/pages/party-user-auth/jwt/sign-in')),
SignUpPage: lazy(() => import('src/pages/party-user-auth/jwt/sign-up')),
};
const authJwt = {
path: 'jwt',
children: [
{
path: 'sign-in',
element: (
<GuestGuard>
<AuthSplitLayout
slotProps={{
section: { title: 'Hi, Welcome back' },
}}
>
<Jwt.SignInPage />
</AuthSplitLayout>
</GuestGuard>
),
},
{
path: 'sign-up',
element: (
<GuestGuard>
<AuthSplitLayout>
<Jwt.SignUpPage />
</AuthSplitLayout>
</GuestGuard>
),
},
],
};
/** **************************************
* Amplify
*************************************** */
const Amplify = {
SignInPage: lazy(() => import('src/pages/auth/amplify/sign-in')),
SignUpPage: lazy(() => import('src/pages/auth/amplify/sign-up')),
VerifyPage: lazy(() => import('src/pages/auth/amplify/verify')),
UpdatePasswordPage: lazy(() => import('src/pages/auth/amplify/update-password')),
ResetPasswordPage: lazy(() => import('src/pages/auth/amplify/reset-password')),
};
const authAmplify = {
path: 'amplify',
children: [
{
path: 'sign-in',
element: (
<GuestGuard>
<AuthSplitLayout
slotProps={{
section: { title: 'Hi, Welcome back' },
}}
>
<Amplify.SignInPage />
</AuthSplitLayout>
</GuestGuard>
),
},
{
path: 'sign-up',
element: (
<GuestGuard>
<AuthSplitLayout>
<Amplify.SignUpPage />
</AuthSplitLayout>
</GuestGuard>
),
},
{
path: 'verify',
element: (
<AuthSplitLayout>
<Amplify.VerifyPage />
</AuthSplitLayout>
),
},
{
path: 'reset-password',
element: (
<AuthSplitLayout>
<Amplify.ResetPasswordPage />
</AuthSplitLayout>
),
},
{
path: 'update-password',
element: (
<AuthSplitLayout>
<Amplify.UpdatePasswordPage />
</AuthSplitLayout>
),
},
],
};
/** **************************************
* Firebase
*************************************** */
const Firebase = {
SignInPage: lazy(() => import('src/pages/auth/firebase/sign-in')),
SignUpPage: lazy(() => import('src/pages/auth/firebase/sign-up')),
VerifyPage: lazy(() => import('src/pages/auth/firebase/verify')),
ResetPasswordPage: lazy(() => import('src/pages/auth/firebase/reset-password')),
};
const authFirebase = {
path: 'firebase',
children: [
{
path: 'sign-in',
element: (
<GuestGuard>
<AuthSplitLayout
slotProps={{
section: { title: 'Hi, Welcome back' },
}}
>
<Firebase.SignInPage />
</AuthSplitLayout>
</GuestGuard>
),
},
{
path: 'sign-up',
element: (
<GuestGuard>
<AuthSplitLayout>
<Firebase.SignUpPage />
</AuthSplitLayout>
</GuestGuard>
),
},
{
path: 'verify',
element: (
<AuthSplitLayout>
<Firebase.VerifyPage />
</AuthSplitLayout>
),
},
{
path: 'reset-password',
element: (
<AuthSplitLayout>
<Firebase.ResetPasswordPage />
</AuthSplitLayout>
),
},
],
};
/** **************************************
* Auth0
*************************************** */
const Auth0 = {
SignInPage: lazy(() => import('src/pages/auth/auth0/sign-in')),
CallbackPage: lazy(() => import('src/pages/auth/auth0/callback')),
};
const authAuth0 = {
path: 'auth0',
children: [
{
path: 'sign-in',
element: (
<GuestGuard>
<AuthSplitLayout
slotProps={{
section: { title: 'Hi, Welcome back' },
}}
>
<Auth0.SignInPage />
</AuthSplitLayout>
</GuestGuard>
),
},
{
path: 'callback',
element: (
<GuestGuard>
<Auth0.CallbackPage />
</GuestGuard>
),
},
],
};
/** **************************************
* Supabase
*************************************** */
const Supabase = {
SignInPage: lazy(() => import('src/pages/auth/supabase/sign-in')),
SignUpPage: lazy(() => import('src/pages/auth/supabase/sign-up')),
VerifyPage: lazy(() => import('src/pages/auth/supabase/verify')),
UpdatePasswordPage: lazy(() => import('src/pages/auth/supabase/update-password')),
ResetPasswordPage: lazy(() => import('src/pages/auth/supabase/reset-password')),
};
const authSupabase = {
path: 'supabase',
children: [
{
path: 'sign-in',
element: (
<GuestGuard>
<AuthSplitLayout
slotProps={{
section: { title: 'Hi, Welcome back' },
}}
>
<Supabase.SignInPage />
</AuthSplitLayout>
</GuestGuard>
),
},
{
path: 'sign-up',
element: (
<GuestGuard>
<AuthSplitLayout>
<Supabase.SignUpPage />
</AuthSplitLayout>
</GuestGuard>
),
},
{
path: 'verify',
element: (
<AuthSplitLayout>
<Supabase.VerifyPage />
</AuthSplitLayout>
),
},
{
path: 'reset-password',
element: (
<AuthSplitLayout>
<Supabase.ResetPasswordPage />
</AuthSplitLayout>
),
},
{
path: 'update-password',
element: (
<AuthSplitLayout>
<Supabase.UpdatePasswordPage />
</AuthSplitLayout>
),
},
],
};
// ----------------------------------------------------------------------
export const partyUserAuthRoutes: RouteObject[] = [
{
path: 'party-user-auth',
element: (
<Suspense fallback={<SplashScreen />}>
<Outlet />
</Suspense>
),
children: [authJwt, authAmplify, authFirebase, authAuth0, authSupabase],
},
];

View File

@@ -83,6 +83,44 @@ export function OverviewAppView() {
/>
</Grid>
<Grid size={{ xs: 12, md: 4 }}>
<AppWidgetSummary
title="Total party-events"
percent={-0.1}
total={4}
chart={{
colors: [theme.palette.error.main],
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug'],
series: [],
}}
/>
</Grid>
<Grid size={{ xs: 12, md: 4 }}>
<AppWidgetSummary
title="Total party-events"
percent={-0.1}
total={5}
chart={{
colors: [theme.palette.error.main],
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug'],
series: [],
}}
/>
</Grid>
<Grid size={{ xs: 12, md: 4 }}>
<AppWidgetSummary
title="Total party-events"
percent={-0.1}
total={6}
chart={{
colors: [theme.palette.error.main],
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug'],
series: [],
}}
/>
</Grid>
<Grid size={{ xs: 12, md: 6, lg: 4 }}>
<AppCurrentDownload
title="Current download"

View File

@@ -36,7 +36,7 @@ import { EmptyContent } from 'src/components/empty-content';
import { Iconify } from 'src/components/iconify';
import { toast } from 'src/components/snackbar';
import { DashboardContent } from 'src/layouts/dashboard';
import { endpoints } from 'src/lib/axios';
import { endpoints } from 'src/lib/endpoints';
import { RouterLink } from 'src/routes/components';
import { paths } from 'src/routes/paths';
import type { IPartyEventItem, IProductTableFilters } from 'src/types/party-event';

View File

@@ -35,7 +35,7 @@ import { EmptyContent } from 'src/components/empty-content';
import { Iconify } from 'src/components/iconify';
import { toast } from 'src/components/snackbar';
import { DashboardContent } from 'src/layouts/dashboard';
import { endpoints } from 'src/lib/axios';
import { endpoints } from 'src/lib/endpoints';
import { RouterLink } from 'src/routes/components';
import { paths } from 'src/routes/paths';
import type { IProductItem, IProductTableFilters } from 'src/types/product';

View File

@@ -32,3 +32,17 @@ A: You only need to update the comment with same format and detail levels of the
Q: which command should i use for check git stage ?
A: `git status . ` to avoid hanging in the terminal
Q: what is the example of `top comment` ?
A: please refer to markdown below:
```markdown
// <file-path, starting from `src`>
//
// PURPOSE:
// - T.B.A. (Clear PURPOSE section with bullet points)
//
// RULES:
// - T.B.A. (Clear RULES section with bullet points)
//
```

View File

@@ -23,7 +23,7 @@
}
],
"settings": {
"editor.fontSize": 15
"editor.fontSize": 18
},
"extensions": {
"recommendations": [

View File

@@ -1,3 +1,8 @@
please read and follow steps in the markdown files in `98_AI_workspace/software-engineer` folder.
just reply `OK` when you done thanks.
---
please list git staged files (`git status .` )
update the comment of the top of the files