diff --git a/package-lock.json b/package-lock.json
index c160b94..cfc4eb1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,6 +23,7 @@
"axios": "^1.6.8",
"framer-motion": "^11.0.8",
"markdown-to-jsx": "^7.4.1",
+ "notistack": "^3.0.1",
"nprogress": "^0.2.0",
"react": "^18.2.0",
"react-avatar-editor": "^13.0.2",
@@ -3504,6 +3505,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/goober": {
+ "version": "2.1.14",
+ "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz",
+ "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==",
+ "peerDependencies": {
+ "csstype": "^3.0.10"
+ }
+ },
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@@ -3946,6 +3955,35 @@
"node": ">=0.10.0"
}
},
+ "node_modules/notistack": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/notistack/-/notistack-3.0.1.tgz",
+ "integrity": "sha512-ntVZXXgSQH5WYfyU+3HfcXuKaapzAJ8fBLQ/G618rn3yvSzEbnOB8ZSOwhX+dAORy/lw+GC2N061JA0+gYWTVA==",
+ "dependencies": {
+ "clsx": "^1.1.0",
+ "goober": "^2.0.33"
+ },
+ "engines": {
+ "node": ">=12.0.0",
+ "npm": ">=6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/notistack"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/notistack/node_modules/clsx": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
+ "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/nprogress": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz",
@@ -7210,6 +7248,12 @@
"slash": "^3.0.0"
}
},
+ "goober": {
+ "version": "2.1.14",
+ "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz",
+ "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==",
+ "requires": {}
+ },
"graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@@ -7539,6 +7583,22 @@
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
},
+ "notistack": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/notistack/-/notistack-3.0.1.tgz",
+ "integrity": "sha512-ntVZXXgSQH5WYfyU+3HfcXuKaapzAJ8fBLQ/G618rn3yvSzEbnOB8ZSOwhX+dAORy/lw+GC2N061JA0+gYWTVA==",
+ "requires": {
+ "clsx": "^1.1.0",
+ "goober": "^2.0.33"
+ },
+ "dependencies": {
+ "clsx": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
+ "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
+ }
+ }
+ },
"nprogress": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz",
diff --git a/package.json b/package.json
index 7df2ec0..8917389 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,7 @@
"axios": "^1.6.8",
"framer-motion": "^11.0.8",
"markdown-to-jsx": "^7.4.1",
+ "notistack": "^3.0.1",
"nprogress": "^0.2.0",
"react": "^18.2.0",
"react-avatar-editor": "^13.0.2",
diff --git a/src/app.tsx b/src/app.tsx
index 161e9b0..8f90578 100644
--- a/src/app.tsx
+++ b/src/app.tsx
@@ -5,6 +5,7 @@ import { AppNavBar, AppFooter } from 'component'
import { Fallback } from 'page'
import { AuthProvider } from 'auth/auth-provider'
import { SettingProvider } from 'component/setting'
+import { SnackProvider } from 'hook'
import ErrorBoundary from 'util/error-boundary'
import Router from 'route'
import withRoot from 'withroot'
@@ -22,11 +23,13 @@ function App() {
-
- }>
-
-
-
+
+
+ }>
+
+
+
+
diff --git a/src/auth/auth-provider.tsx b/src/auth/auth-provider.tsx
index aa5fc31..0f1fad9 100644
--- a/src/auth/auth-provider.tsx
+++ b/src/auth/auth-provider.tsx
@@ -1,14 +1,15 @@
-import { LOCAL_STORAGE } from 'constant'
+import { COLOR, LOCAL_STORAGE } from 'constant'
import React, { FC, createContext, useEffect, useState, useMemo, useCallback } from 'react'
import { createAsyncThunk } from '@reduxjs/toolkit'
import localStorageSpace from 'util/local-storage-space'
import storage from 'redux-persist/lib/storage'
import { setInitial, setCredential } from 'store/slice/auth'
import { useLoginMutation, useLogoutMutation } from 'store/slice/auth/endpoint'
+import { useSelector, dispatch } from 'store'
import { isValidToken, setSession } from 'auth/utility'
import { AuthPath } from 'route/path'
import { RESPONSE } from 'constant'
-import { useSelector, dispatch } from 'store'
+import { snack } from 'hook'
export const AuthContext = createContext<{
isAuthenticated: boolean
@@ -32,30 +33,10 @@ export const AuthProvider: FC = ({ children }) => {
// const [isAuthenticated, setIsAuthenticated] = useState(false)
const { user, isAuthenticated } = useSelector((state: RootState) => state.auth || {})
const [logoutCall] = useLogoutMutation()
- const [loginCall, { isLoading }] = useLoginMutation()
+ const [loginCall, { isLoading, error }] = useLoginMutation()
const storageAvailable = useMemo(() => localStorageSpace(), [])
- const token = storageAvailable ? localStorage.getItem(LOCAL_STORAGE.TOKEN) : ''
-
- if (token && isValidToken(token)) {
- setSession(token)
- }
-
- // useEffect(() => {
- // const expirationTime = localStorage.getItem('expirationTime')
- // const user = JSON.parse(localStorage.getItem(LOCAL_STORAGE.USER) || '{}')
-
- // if (user.user !== null) {
- // dispatch(setCredential({ user: null, isAuthenticated: false, token: null, isInitialized: false }))
- // localStorage.removeItem('expirationTime')
- // } else {
- // console.log('user', user)
- // setIsAuthenticated(false)
- // dispatch(setCredential({ user: null, isAuthenticated: false, token: null, isInitialized: false }))
- // }
- // }, [dispatch])
-
const initialize = useCallback(async () => {
try {
const token = storageAvailable ? localStorage.getItem(LOCAL_STORAGE.TOKEN) : ''
@@ -116,13 +97,26 @@ export const AuthProvider: FC = ({ children }) => {
const login = useCallback(
async (credentials: { email: string; password: string; name?: string; token?: string }) => {
const { email, password } = credentials
- const res = await loginCall({
+
+ if (error) {
+ snack(error || RESPONSE.error.INVALID_CREDENTIAL)
+ dispatch(setCredential({ isAuthenticated: false, ...(credentials || {}) }))
+ throw new Error(RESPONSE.error.INVALID_CREDENTIAL)
+ }
+
+ const res = (await loginCall({
email,
password
- }).unwrap()
+ }).unwrap()) as any
- dispatch(setCredential({ isAuthenticated: true, ...(res || {}) }))
- // setIsAuthenticated(true)
+ if (res && res.success && res.user) {
+ snack(RESPONSE.success.LOGIN, { variant: COLOR.SUCCESS })
+ dispatch(setCredential({ isAuthenticated: true, ...res }))
+ } else {
+ snack(RESPONSE.error.LOGIN_UNABLE, { variant: COLOR.ERROR })
+ dispatch(setCredential({ isAuthenticated: false, ...(res || {}) }))
+ throw new Error(RESPONSE.error.INVALID_CREDENTIAL)
+ }
},
[dispatch]
)
diff --git a/src/component/navbar/account-popover.tsx b/src/component/navbar/account-popover.tsx
index 14a486b..03f2916 100644
--- a/src/component/navbar/account-popover.tsx
+++ b/src/component/navbar/account-popover.tsx
@@ -3,6 +3,7 @@ import { useNavigate, Link as RouterLink } from 'react-router-dom'
import { alpha, useTheme } from '@mui/material/styles'
import { Box, Divider, Dialog, Typography, Stack, MenuItem, Link } from '@mui/material'
import { useAuthContext } from 'auth'
+import { useSnackbar } from 'hook/use-snack'
import { DefaultAvatar, Avatar } from 'component/avatar'
import { MenuPopover } from 'component/menu-popover'
import { MotionButton } from 'component/motion'
@@ -22,7 +23,7 @@ export default function AccountPopover({ user }: { user: any }) {
const email = user?.email
const displayName = user?.firstname + ' ' + user?.lastname
- // const { enqueueSnackbar } = useSnackbar()
+ const { enqueueSnackbar: snack } = useSnackbar()
const { themeMode, themeStretch, themeContrast, onResetSetting } = useSettingContext()
@@ -39,8 +40,10 @@ export default function AccountPopover({ user }: { user: any }) {
if (logout) {
logout()
}
+ snack('Logout Successful')
handleClosePopover()
} catch (error) {
+ snack('Unable to logout', { variant: 'error' })
console.error(error)
// enqueueSnackbar('Unable to logout', { variant: 'error' })
}
diff --git a/src/config/icon-directory.ts b/src/config/icon-directory.ts
index 91fa2c6..4b136e6 100644
--- a/src/config/icon-directory.ts
+++ b/src/config/icon-directory.ts
@@ -20,8 +20,11 @@ function _getWebIcon(icon: string) {
const ICON_WEB = {
ALERT_OUTLINE: _getWebIcon('alert-triangle-outline'),
ARROW_FORWARD: _getWebIcon('arrow-ios-forward'),
+ CHEVRON_RIGHT: _getWebIcon('chevron-right-outline'),
CLOSE: _getWebIcon('close-fill'),
+ CHECKMARK_CIRCLE: _getWebIcon('checkmark-circle-outline'),
CONTRAST_BOX: _getWebIcon('contrast-box'),
+ ERROR_OUTLINE: _getWebIcon('alert-circle-outline'),
ERROR: _getWebIcon('alert-circle'),
INFO: _getWebIcon('info'),
@@ -63,9 +66,12 @@ export enum ICON_WEB_NAME {
// @web
ALERT_OUTLINE = 'ALERT_OUTLINE',
ARROW_FORWARD = 'ARROW_FORWARD',
+ CHECKMARK_CIRCLE = 'CHECKMARK_CIRCLE',
+ CHEVRON_RIGHT = 'CHEVRON_RIGHT',
CLOSE = 'CLOSE',
CONTRAST_BOX = 'CONTRAST_BOX',
ERROR = 'ERROR',
+ ERROR_OUTLINE = 'ERROR_OUTLINE',
EYE_OFF = 'EYE_OFF',
EYE_HIDE = 'EYE_HIDE',
INFO = 'INFO',
diff --git a/src/constant/response.ts b/src/constant/response.ts
index 7b2c15d..fc14eaa 100644
--- a/src/constant/response.ts
+++ b/src/constant/response.ts
@@ -57,6 +57,7 @@ const RESPONSE = {
INVALID_TOKEN: 'Invalid token',
NOT_OWNER: (user: string, course: string) => `User ${user} is unauthorized to update course ${course}`,
ROLE_NOT_ALLOWED: (data: string) => `Current role ${data} is unauthorized to access this route`,
+ LOGIN_UNABLE: 'Unable to login',
parseErr: (err: any) => `Error parsing JSON: ${err}`,
NotInstance: 'This class cannot be instantiated',
PASSWORD_MATCH: 'Passwords do not match',
diff --git a/src/constant/size.ts b/src/constant/size.ts
index da272ca..e7b91d1 100644
--- a/src/constant/size.ts
+++ b/src/constant/size.ts
@@ -1,7 +1,7 @@
export enum SIZE {
SMALL = 'small',
MEDIUM = 'medium',
- LARGE = 'large',
+ LARGE = 'large'
}
type SizeMapType = 'small' | 'medium' | 'large'
@@ -9,7 +9,7 @@ type SizeMapType = 'small' | 'medium' | 'large'
export const SIZE_MAP: Record = {
[SIZE.SMALL]: 'small',
[SIZE.MEDIUM]: 'medium',
- [SIZE.LARGE]: 'large',
+ [SIZE.LARGE]: 'large'
}
export type SizeType = {
@@ -25,5 +25,5 @@ export const SIZE_TYPE: SizeType = {
sm: 'sm',
md: 'md',
lg: 'lg',
- xl: 'xl',
+ xl: 'xl'
}
diff --git a/src/hook/index.ts b/src/hook/index.ts
index e572931..524b422 100644
--- a/src/hook/index.ts
+++ b/src/hook/index.ts
@@ -1,3 +1,4 @@
+export { default as useLocalStorage } from './use-local-storage'
+export * from './use-snack'
export { useResponsive } from './use-responsive'
export * from './use-icon'
-export { default as useLocalStorage } from './use-local-storage'
diff --git a/src/hook/use-snack/index.ts b/src/hook/use-snack/index.ts
new file mode 100644
index 0000000..1c0631e
--- /dev/null
+++ b/src/hook/use-snack/index.ts
@@ -0,0 +1,3 @@
+export * from 'notistack'
+export { enqueueSnackbar as snack } from 'notistack'
+export { default as SnackProvider } from './use-snack'
diff --git a/src/hook/use-snack/style.tsx b/src/hook/use-snack/style.tsx
new file mode 100644
index 0000000..e98c303
--- /dev/null
+++ b/src/hook/use-snack/style.tsx
@@ -0,0 +1,42 @@
+import { m } from 'framer-motion'
+import { Box } from '@mui/material'
+import { styled } from '@mui/material/styles'
+import { MaterialDesignContent } from 'notistack'
+
+export const SSnackContent = styled(MaterialDesignContent)(({ theme }) => ({
+ '&.notistack-MuiContent': {
+ backgroundColor: theme.palette.common.white,
+ display: 'flex',
+ flexDirection: 'row',
+ objectFit: 'cover',
+ justifyContent: 'space-evenly',
+ fontSize: theme.typography.overline.fontSize,
+ color: theme.palette.common.black,
+ padding: theme.spacing(2)
+ },
+ '&.notistack-MuiContent-error': {
+ backgroundColor: theme.palette.error.dark,
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ color: theme.palette.common.white,
+ padding: 0
+ },
+ '&.notistack-MuiContent-success': {
+ flexDirection: 'row',
+ padding: 0
+ },
+ '&.notistack-MuiContent-warning': {
+ backgroundColor: theme.palette.common.white,
+ flexDirection: 'row',
+ padding: 0
+ }
+}))
+
+export const SSnackIconMDiv = styled(Box)(({ theme, color }) => ({
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ color: theme.palette.grey[500],
+ width: 30,
+ height: 30
+}))
diff --git a/src/hook/use-snack/use-snack.tsx b/src/hook/use-snack/use-snack.tsx
new file mode 100644
index 0000000..35d50c2
--- /dev/null
+++ b/src/hook/use-snack/use-snack.tsx
@@ -0,0 +1,68 @@
+import { Fragment } from 'react'
+import { m, AnimatePresence } from 'framer-motion'
+import { SnackbarProvider as NotistackProvider, useSnackbar } from 'notistack'
+import { Slide, IconButton } from '@mui/material'
+import { useIcon, ICON_NAME } from 'hook'
+import { ICON_WEB_NAME } from 'config'
+import { KEY, COLOR as COLOR_CONSTANT } from 'constant'
+import { SSnackContent, SSnackIconMDiv } from './style'
+import { Iconify } from 'component/iconify'
+
+interface SnackProviderProps {
+ children: React.ReactNode
+}
+
+export default function SnackProvider({ children }: SnackProviderProps) {
+ const { closeSnackbar } = useSnackbar()
+
+ const onClose = (key: any) => () => {
+ closeSnackbar(key)
+ }
+
+ const { Icon, iconSrc: closeSrc } = useIcon(ICON_NAME.CHEVRON_RIGHT)
+
+ return (
+
+ ,
+ success: ,
+ warning: ,
+ error:
+ }}
+ action={(key) => (
+
+
+
+ )}>
+ {children}
+
+
+ )
+}
+
+interface SnackIconProps {
+ icon: ICON_WEB_NAME
+ color: any
+}
+
+function SnackIcon({ icon, color }: SnackIconProps) {
+ const { Icon, iconSrc } = useIcon(icon)
+ return (
+
+
+
+ )
+}
diff --git a/src/page/auth/log-in/log-in.tsx b/src/page/auth/log-in/log-in.tsx
index 3d3b601..b604ec5 100644
--- a/src/page/auth/log-in/log-in.tsx
+++ b/src/page/auth/log-in/log-in.tsx
@@ -1,19 +1,19 @@
import { useState, useEffect, Fragment, ChangeEvent, BaseSyntheticEvent } from 'react'
import { useNavigate } from 'react-router-dom'
-import { useDispatch, useSelector } from 'react-redux'
+import { useSelector } from 'react-redux'
import * as Yup from 'yup'
+import { useAuthContext } from 'auth'
import { useForm, Controller } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers/yup'
import { FormField, FormProvider } from 'component/form'
-import { useAuthContext } from 'auth'
import { Box, FormControlLabel, Checkbox } from '@mui/material'
import { AppForm, FormButtonRedir, email, required } from 'component/form'
import { MotionContainer } from 'component/motion'
import { Meta } from 'component/meta'
-import { Snack } from 'component/snack'
import { AuthBranding } from 'section/auth'
-import { AuthPath, RootPath } from 'route/path'
+import { AuthPath } from 'route/path'
import { FORM } from 'section/auth'
+import { useSnackbar } from 'hook/use-snack'
import { LABEL, KEY, LOCAL_STORAGE, RESPONSE, COLOR } from 'constant'
import withRoot from 'withroot'
@@ -22,6 +22,7 @@ function LogIn() {
const [remember, setRemember] = useState(false)
const { login } = useAuthContext()
const navigate = useNavigate()
+ const { enqueueSnackbar: snack } = useSnackbar()
const { isAuthenticated } = useSelector((state: { auth: { isAuthenticated: boolean } }) => state.auth)
const loginSchema = Yup.object().shape({
@@ -59,29 +60,38 @@ function LogIn() {
const onSubmit = async (data: any) => {
try {
- console.log('data : ', data)
if (login) {
await login(data)
- }
+ // navigate(RootPath.ROOT_PARAM)
- if (remember) {
- const userInfo = {
- email: data.email,
- remember
- }
+ if (remember) {
+ const userInfo = {
+ email: data.email,
+ remember
+ }
- localStorage.setItem(LOCAL_STORAGE.USER_INFO, JSON.stringify(userInfo))
+ localStorage.setItem(LOCAL_STORAGE.USER_INFO, JSON.stringify(userInfo))
+ } else {
+ localStorage.removeItem(LOCAL_STORAGE.USER_INFO)
+ }
} else {
- localStorage.removeItem(LOCAL_STORAGE.USER_INFO)
- }
-
- if (isAuthenticated) {
- navigate(RootPath.ROOT_PARAM)
+ snack(RESPONSE.error.INVALID_CREDENTIAL, {
+ variant: COLOR.ERROR
+ })
+ throw new Error(RESPONSE.error.INVALID_CREDENTIAL)
}
} catch (error: any) {
console.error('error : ', error || '')
reset()
- setError(KEY.EMAIL, { message: error.message })
+ if (error.message === RESPONSE.error.INVALID_CREDENTIAL) {
+ setError(KEY.EMAIL, { message: RESPONSE.error.EMAIL_INVALID })
+ setError(KEY.PASSWORD, { message: RESPONSE.error.PASSWORD_INVALID })
+ } else {
+ snack(error.message, {
+ variant: COLOR.ERROR
+ })
+ setError(KEY.EMAIL, { message: error.message })
+ }
}
}
@@ -90,22 +100,18 @@ function LogIn() {
-
+
+
+
-
+
-
-
+
+
+
+
+
+
{
return (
diff --git a/src/theme/palette.ts b/src/theme/palette.ts
index 4a6f91b..42bd919 100644
--- a/src/theme/palette.ts
+++ b/src/theme/palette.ts
@@ -1,5 +1,6 @@
import { alpha } from '@mui/material/styles'
import { KEY } from 'constant'
+import { COMMON as COMMON_COLOR } from './theme'
const BRAND = {
background: '#63738114',
@@ -28,9 +29,19 @@ const PRIMARY = {
contrastText: '#F2EED8'
}
+const ERROR = {
+ lighter: '#FCEBEB',
+ light: '#E45D5D',
+ main: '#DD3535',
+ dark: '#9B2525',
+ darker: '#581515',
+ contrastText: '#F2EED8'
+}
+
const COMMON = {
- common: { black: '#000', white: '#F2EED8' },
+ common: COMMON_COLOR,
primary: PRIMARY,
+ error: ERROR,
grey: GREY,
divider: alpha(GREY[500], 0.24),
action: {
diff --git a/src/theme/theme.ts b/src/theme/theme.ts
index 958aa07..7514d3f 100644
--- a/src/theme/theme.ts
+++ b/src/theme/theme.ts
@@ -1,5 +1,5 @@
import { createTheme } from '@mui/material/styles'
-import { green, grey, red } from '@mui/material/colors'
+import { blue, green, grey, red } from '@mui/material/colors'
import { Theme } from '@mui/material/styles'
import ComponentOverride from './override'
import { ASSET } from 'config'
@@ -11,41 +11,41 @@ const rawTheme = createTheme({
primary: {
light: '#D3D3D3',
main: '#000009',
- dark: '#1E1E1f',
+ dark: '#1E1E1f'
},
secondary: {
light: '#FFF5F8',
main: '#FFD500',
- dark: '#E62958',
+ dark: '#E62958'
},
warning: {
light: '#FFF3E0',
main: '#FFC071',
- dark: '#FFB25E',
+ dark: '#FFB25E'
},
error: {
light: red[50],
main: red[500],
- dark: red[700],
+ dark: red[700]
},
success: {
light: green[50],
main: green[500],
- dark: green[700],
+ dark: green[700]
},
text: {
primary: '#172B4D',
- secondary: '#6B778C',
- },
+ secondary: '#6B778C'
+ }
},
typography: {
fontFamily: "'Poppins', sans-serif",
fontSize: 14,
fontWeightLight: 300,
- fontWeightRegular: 400,
+ fontWeightRegular: 400
},
shape: {
- borderRadius: 2,
+ borderRadius: 2
},
components: {
MuiButton: {
@@ -53,18 +53,18 @@ const rawTheme = createTheme({
root: {
boxShadow: 'none',
'&:hover': {
- boxShadow: 'none',
- },
- },
- },
- },
- },
+ boxShadow: 'none'
+ }
+ }
+ }
+ }
+ }
})
const fontHeader = {
color: rawTheme.palette.text.primary,
fontWeight: rawTheme.typography.fontWeightRegular,
- fontFamily: 'Poppins, sans-serif',
+ fontFamily: 'Poppins, sans-serif'
}
const BRAND = {
@@ -72,15 +72,19 @@ const BRAND = {
secondary: '#FFD500',
warning: '#FFC071',
error: red[500],
- success: green[500],
+ success: green[500]
}
-const COMMON = {
+export const COMMON = {
light: '#D3D3D3',
main: '#D9D9D9',
+ yellow: '#FFD500',
+ blue: blue[500],
+ red: red[500],
+ green: green[500],
dark: '#1E1E1F',
black: '#000000',
- white: '#FFFFFF',
+ white: '#F2EED8'
}
const theme = {
@@ -93,15 +97,15 @@ const theme = {
placeholder: grey[200],
light: '#F5F5F5',
main: '#D4D3D3',
- dark: '#D9D9D9',
+ dark: '#D9D9D9'
},
common: COMMON,
brand: BRAND,
mode: 'light',
- backgroundImage: ASSET.PATTERN_BG,
+ backgroundImage: ASSET.PATTERN_BG
},
shape: {
- borderRadius: 2,
+ borderRadius: 2
},
typography: {
...rawTheme.typography,
@@ -110,58 +114,69 @@ const theme = {
...rawTheme.typography.h1,
...fontHeader,
letterSpacing: 0,
- fontSize: 80,
+ fontSize: 80
},
h1: {
...rawTheme.typography.h1,
...fontHeader,
letterSpacing: 0,
- fontSize: 60,
+ fontSize: 60
},
h2: {
...rawTheme.typography.h2,
...fontHeader,
- fontSize: 48,
+ fontSize: 48
},
h3: {
...rawTheme.typography.h3,
...fontHeader,
- fontSize: 42,
+ fontSize: 42
},
h4: {
...rawTheme.typography.h4,
...fontHeader,
- fontSize: 36,
+ fontSize: 36
},
h5: {
...rawTheme.typography.h5,
fontSize: 20,
- fontWeight: rawTheme.typography.fontWeightLight,
+ fontWeight: rawTheme.typography.fontWeightLight
},
h6: {
...rawTheme.typography.h6,
...fontHeader,
- fontSize: 18,
+ fontSize: 18
},
h7: {
...rawTheme.typography.h6,
...fontHeader,
- fontSize: 15,
+ fontSize: 15
},
subtitle1: {
...rawTheme.typography.subtitle1,
- fontSize: 18,
+ fontSize: 18
+ },
+ overline: {
+ ...rawTheme.typography.overline,
+ fontSize: 16,
+ textTransform: 'uppercase'
+ },
+ body0: {
+ ...rawTheme.typography.body2,
+ fontWeight: rawTheme.typography.fontWeightRegular,
+ fontSize: 20,
+ textTransform: 'uppercase'
},
body1: {
...rawTheme.typography.body2,
fontWeight: rawTheme.typography.fontWeightRegular,
- fontSize: 16,
+ fontSize: 16
},
body2: {
...rawTheme.typography.body1,
- fontSize: 14,
- },
- },
+ fontSize: 14
+ }
+ }
}
ComponentOverride(theme as ThemeType)
diff --git a/src/theme/typography.ts b/src/theme/typography.ts
new file mode 100644
index 0000000..cf530ef
--- /dev/null
+++ b/src/theme/typography.ts
@@ -0,0 +1,130 @@
+import { SizeType } from 'constant'
+
+export function remToPx(value: any) {
+ return Math.round(parseFloat(value) * 16)
+}
+
+export function pxToRem(value: any) {
+ return `${value / 16}rem`
+}
+
+export function responsiveFontSizes({ xs, sm, md, lg, xl }: ISize) {
+ return {
+ '@media (min-width:600px)': {
+ fontSize: pxToRem(sm)
+ },
+ '@media (min-width:900px)': {
+ fontSize: pxToRem(md)
+ },
+ '@media (min-width:1200px)': {
+ fontSize: pxToRem(lg)
+ }
+ }
+}
+
+const FONT_PRIMARY = 'Yantramanav, Arimo, Calibri'
+const FONT_SECONDARY = 'Arimo'
+
+const typography = {
+ fontFamily: FONT_PRIMARY,
+ fontWeightLight: 200,
+ fontWeightRegular: 400,
+ fontWeightMedium: 600,
+ fontWeightBold: 700,
+ h0: {
+ fontWeight: 700,
+ lineHeight: 1.5,
+ fontSize: pxToRem(48),
+ ...responsiveFontSizes({ sm: 60, md: 72, lg: 120 })
+ },
+ h1: {
+ fontWeight: 800,
+ lineHeight: 80 / 64,
+ fontSize: pxToRem(40),
+ ...responsiveFontSizes({ sm: 52, md: 58, lg: 64 })
+ },
+ h2: {
+ fontWeight: 800,
+ lineHeight: 64 / 48,
+ fontSize: pxToRem(32),
+ ...responsiveFontSizes({ sm: 48, md: 50, lg: 52 })
+ },
+ h3: {
+ fontWeight: 700,
+ lineHeight: 1.5,
+ fontSize: pxToRem(24),
+ ...responsiveFontSizes({ sm: 26, md: 30, lg: 32 })
+ },
+ h4: {
+ fontWeight: 700,
+ lineHeight: 1.5,
+ fontSize: pxToRem(20),
+ ...responsiveFontSizes({ sm: 20, md: 24, lg: 24 })
+ },
+ h5: {
+ fontWeight: 700,
+ lineHeight: 1.5,
+ fontSize: pxToRem(18),
+ ...responsiveFontSizes({ sm: 19, md: 20, lg: 20 })
+ },
+ h6: {
+ fontWeight: 700,
+ lineHeight: 28 / 18,
+ fontSize: pxToRem(17),
+ ...responsiveFontSizes({ sm: 18, md: 18, lg: 18 })
+ },
+ subtitle0: {
+ fontFamily: FONT_SECONDARY,
+ fontWeight: 600,
+ lineHeight: 1.5,
+ fontSize: pxToRem(24)
+ },
+ subtitle1: {
+ fontFamily: FONT_SECONDARY,
+ fontWeight: 600,
+ lineHeight: 1.5,
+ fontSize: pxToRem(16)
+ },
+ subtitle2: {
+ fontFamily: FONT_SECONDARY,
+ fontWeight: 600,
+ lineHeight: 22 / 14,
+ fontSize: pxToRem(14)
+ },
+ body1: {
+ fontFamily: FONT_SECONDARY,
+ lineHeight: 1.5,
+ fontSize: pxToRem(18)
+ },
+ body2: {
+ fontFamily: FONT_SECONDARY,
+ lineHeight: 22 / 14,
+ fontSize: pxToRem(14)
+ },
+ caption: {
+ fontFamily: FONT_SECONDARY,
+ lineHeight: 1.5,
+ fontSize: pxToRem(12)
+ },
+ overline: {
+ fontWeight: 700,
+ lineHeight: 1.5,
+ fontSize: pxToRem(12),
+ textTransform: 'uppercase'
+ },
+ overline2: {
+ fontWeight: 700,
+ lineHeight: 1.5,
+ fontSize: pxToRem(16),
+ textTransform: 'uppercase'
+ },
+ button: {
+ fontFamily: FONT_SECONDARY,
+ fontWeight: 700,
+ lineHeight: 24 / 14,
+ fontSize: pxToRem(14),
+ textTransform: 'capitalize'
+ }
+}
+
+export default typography
diff --git a/src/types/global.d.ts b/src/types/global.d.ts
index be01789..f2fdf6f 100644
--- a/src/types/global.d.ts
+++ b/src/types/global.d.ts
@@ -87,6 +87,14 @@ declare global {
message?: string
}
+ interface ISize {
+ xs?: number
+ sm: number
+ md: number
+ lg: number
+ xl?: number
+ }
+
type VERTICAL = KEY.TOP | KEY.CENTER | KEY.BOTTOM
type HORIZONTAL = KEY.LEFT | KEY.CENTER | KEY.RIGHT
type COLOR = 'default' | 'inherit' | 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'