First Commit

This commit is contained in:
cesnimda
2026-03-21 11:55:27 +01:00
commit 2e8a29b4d0
1757 changed files with 166084 additions and 0 deletions
@@ -0,0 +1,18 @@
// @mui
import { useTheme } from '@mui/material/styles';
// @types
/*************************** IMAGE - TYPE IDENTIFY ***************************/
function isImageComponentProps(value) {
return value.light !== undefined && value.dark !== undefined;
}
/*************************** COMMON - IMAGE PATH ***************************/
export default function GetImagePath(image) {
const theme = useTheme();
return isImageComponentProps(image) ? image[theme.palette.mode] : image;
}
@@ -0,0 +1,66 @@
// material-ui
import { alpha } from '@mui/material/styles';
/**
* Converts a hex color string to an RGB channel string ("r g b").
*
* @param hex - The hex color string (e.g. "#C8FAD6", "#FFF", "#FF00FFAA").
* @returns The RGB channel string (e.g. "200 250 214").
* @throws {Error} If the input is not a valid hex color.
*/
export function hexToRgbChannel(hex) {
let cleaned = hex.replace(/^#/, '');
if (cleaned.length === 3) {
cleaned = cleaned
.split('')
.map((c) => c + c)
.join('');
}
if (cleaned.length === 4) {
cleaned = cleaned
.split('')
.map((c) => c + c)
.join('');
}
if (cleaned.length !== 6 && cleaned.length !== 8) {
throw new Error(`Invalid hex color: ${hex}`);
}
const r = parseInt(cleaned.substring(0, 2), 16);
const g = parseInt(cleaned.substring(2, 4), 16);
const b = parseInt(cleaned.substring(4, 6), 16);
return `${r} ${g} ${b}`;
}
export function extendPaletteWithChannels(palette) {
const result = { ...palette };
Object.entries(palette).forEach(([k, v]) => {
if (typeof v === 'string' && v.startsWith('#')) {
result[`${k}Channel`] = hexToRgbChannel(v);
} else if (typeof v === 'object' && v !== null) {
result[k] = extendPaletteWithChannels(v);
}
});
return result;
}
export function withAlpha(color, opacity) {
// Case 1: normal color (hex, rgb, hsl…)
if (/^#|rgb|hsl|color/i.test(color)) {
return alpha(color, opacity);
}
// Case 2: CSS Var: var(--mui-palette-xxx) or var(--palette-xxx, #hex)
if (color.startsWith('var(')) {
// inject "Channel" *before the closing parenthesis of the var name only*
return color.replace(/(--[a-zA-Z0-9-]+)(.*)\)/, `$1Channel$2)`).replace(/^var\((.+)\)$/, `rgba(var($1) / ${opacity})`);
}
// Fallback
return color;
}
@@ -0,0 +1,10 @@
// @project
import { withAlpha } from '@/utils/colorUtils';
/*************************** COMMON - FOCUS STYLE ***************************/
export function generateFocusStyle(color) {
return {
boxShadow: `0px 0px 0px 3px ${withAlpha(color, 0.2)}`
};
}
@@ -0,0 +1,40 @@
/*************************** CARD RADIUS - STYLES ***************************/
export function getRadiusStyles(radius, ...corners) {
const borderRadiusStyles = {
'& .MuiPaper-root': {}
};
corners.forEach((corner) => {
switch (corner) {
case 'topLeft':
borderRadiusStyles.borderTopLeftRadius = radius;
borderRadiusStyles['& .MuiPaper-root'] = {
borderTopLeftRadius: radius
};
break;
case 'topRight':
borderRadiusStyles.borderTopRightRadius = radius;
borderRadiusStyles['& .MuiPaper-root'] = {
borderTopRightRadius: radius
};
break;
case 'bottomLeft':
borderRadiusStyles.borderBottomLeftRadius = radius;
borderRadiusStyles['& .MuiPaper-root'] = {
borderBottomLeftRadius: radius
};
break;
case 'bottomRight':
borderRadiusStyles.borderBottomRightRadius = radius;
borderRadiusStyles['& .MuiPaper-root'] = {
borderBottomRightRadius: radius
};
break;
default:
break;
}
});
return borderRadiusStyles;
}
@@ -0,0 +1,70 @@
import { useNavigate, useLocation, useParams } from 'react-router-dom';
export const useRouter = () => {
const navigate = useNavigate();
const location = useLocation();
const params = useParams();
const push = (path) => {
try {
const appOrigin = window.location.origin;
const url = new URL(path, appOrigin);
if (url.origin === appOrigin) {
// Internal route: use SPA navigation
navigate(url.pathname + url.search + url.hash);
} else {
// External URL: full page reload
window.location.href = path;
}
} catch {
// Fallback: assume it's relative
navigate(path);
}
};
const replace = (path) => {
try {
const appOrigin = window.location.origin;
const url = new URL(path, appOrigin);
if (url.origin === appOrigin) {
navigate(url.pathname + url.search + url.hash, { replace: true });
} else {
window.location.replace(path);
}
} catch {
navigate(path, { replace: true });
}
};
return {
pathname: location.pathname,
query: Object.fromEntries(new URLSearchParams(location.search)),
asPath: location.pathname + location.search,
push,
replace,
back: () => navigate(-1),
params
};
};
export const usePathname = () => {
const location = useLocation();
return location.pathname;
};
export const useSearchParams = () => {
const location = useLocation();
const searchParams = new URLSearchParams(location.search);
const navigate = useNavigate();
return {
get: (key) => searchParams.get(key),
set: (key, value) => {
const newParams = new URLSearchParams(searchParams);
newParams.set(key, value);
navigate(`${location.pathname}?${newParams.toString()}`);
}
};
};
@@ -0,0 +1,73 @@
export const emailSchema = {
required: 'Email is required',
pattern: { value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i, message: 'Invalid email address' }
};
export const passwordSchema = {
required: 'Password is required',
minLength: { value: 8, message: 'Password must be at least 8 characters' },
validate: {
noSpaces: (value) => !/\s/.test(value) || 'Password cannot contain spaces',
hasUpperCase: (value) => /[A-Z]/.test(value) || 'Password must have at least one uppercase letter',
hasNumber: (value) => /[0-9]/.test(value) || 'Password must have at least one number',
hasSpecialChar: (value) => /[!@#$%^&*(),.?":{}|<>]/.test(value) || 'Password must have at least one special character'
}
};
export const firstNameSchema = {
required: 'First name is required',
pattern: { value: /^[a-zA-Z\s]+$/, message: 'Invalid first name' },
validate: {
trim: (value) => {
const trimmedValue = value.trim();
return trimmedValue.length > 0 || 'First name cannot be empty or contain only spaces';
}
},
onBlur: (e) => {
e.target.value = e.target.value.trim();
}
};
export const lastNameSchema = {
required: 'Last name is required',
pattern: { value: /^[a-zA-Z\s]+$/, message: 'Invalid last name' },
validate: {
trim: (value) => {
const trimmedValue = value.trim();
return trimmedValue.length > 0 || 'Last name cannot be empty or contain only spaces';
}
},
onBlur: (e) => {
e.target.value = e.target.value.trim();
}
};
export const usernameSchema = {
required: 'Username is required',
pattern: {
value: /^[a-zA-Z0-9._]+$/, // Alphanumeric, underscores, and dots
message: 'Username can only contain letters, numbers, dots, and underscores'
},
validate: {
trim: (value) => {
const trimmedValue = value.trim();
return trimmedValue.length > 0 || 'Username cannot be empty or contain only spaces';
},
noSpaces: (value) => {
return !/\s/.test(value) || 'Username cannot contain spaces';
}
},
onBlur: (e) => {
e.target.value = e.target.value.trim();
}
};
export const contactSchema = {
required: 'Contact number is required',
pattern: { value: /^[0-9()\-\.]{7,15}$/, message: 'Invalid contact number' }
};
export const otpSchema = {
required: 'OTP is required',
minLength: { value: 6, message: 'OTP must be exactly 6 characters' }
};