feat: state redux toolkit & feedback slice
This commit is contained in:
@@ -17,6 +17,7 @@
|
|||||||
"@fortawesome/free-brands-svg-icons": "^6.5.1",
|
"@fortawesome/free-brands-svg-icons": "^6.5.1",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.5.1",
|
"@fortawesome/free-solid-svg-icons": "^6.5.1",
|
||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
|
"@reduxjs/toolkit": "^2.0.1",
|
||||||
"appwrite": "^13.0.1",
|
"appwrite": "^13.0.1",
|
||||||
"entgamers-database": "^0.0.5",
|
"entgamers-database": "^0.0.5",
|
||||||
"entgamers-panda-preset": "0.1.1",
|
"entgamers-panda-preset": "0.1.1",
|
||||||
@@ -27,6 +28,7 @@
|
|||||||
"node-appwrite": "^11.1.0",
|
"node-appwrite": "^11.1.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
|
"react-redux": "^9.0.4",
|
||||||
"sharp": "^0.33.1",
|
"sharp": "^0.33.1",
|
||||||
"yup": "^1.3.3"
|
"yup": "^1.3.3"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
'use client'
|
||||||
|
import IconButton from '@/components/ui/IconButton'
|
||||||
|
import Typography from '@/components/ui/Typography'
|
||||||
|
import { useAppDispatch } from '@/hooks/useAppDispatch'
|
||||||
|
import { useAppSelector } from '@/hooks/useAppSelector'
|
||||||
|
import { removeAlert } from '@/state/feedbackSlice'
|
||||||
|
import { css } from '@/styled-system/css'
|
||||||
|
import { alert } from '@/styled-system/recipes/alert'
|
||||||
|
import { faTimes } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
import { AnimatePresence, motion } from 'framer-motion'
|
||||||
|
import { type FC } from 'react'
|
||||||
|
import { createPortal } from 'react-dom'
|
||||||
|
|
||||||
|
const FeedbackConsumer: FC = () => {
|
||||||
|
const { alerts } = useAppSelector(state => state.feedback)
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{alerts.length > 0 && createPortal(
|
||||||
|
(
|
||||||
|
<AnimatePresence>
|
||||||
|
<motion.div
|
||||||
|
key="alerts"
|
||||||
|
className={css({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 'medium',
|
||||||
|
position: 'fixed',
|
||||||
|
bottom: 'medium',
|
||||||
|
left: 'medium',
|
||||||
|
padding: 'medium',
|
||||||
|
zIndex: 'modalBackdrop',
|
||||||
|
width: 'calc(100vw - 32px)',
|
||||||
|
maxWidth: '400px'
|
||||||
|
})}
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1, y: 0, transition: { duration: 0.3, ease: 'backIn' } }}
|
||||||
|
exit={{ opacity: 0, x: 400, transition: { duration: 0.3, ease: 'backOut' } }}
|
||||||
|
>
|
||||||
|
<AnimatePresence>
|
||||||
|
{alerts.map((currentAlert) => (
|
||||||
|
<motion.div
|
||||||
|
key={currentAlert.id}
|
||||||
|
// This is a workaround for PandaCSS to auto-generate styles and avoid Alerts with non-generated styles. See https://panda-css.com/docs/guides/dynamic-styling#runtime-conditions
|
||||||
|
className={alert({
|
||||||
|
severity: currentAlert.severity === 'success' ? 'success' : currentAlert.severity === 'info' ? 'info' : currentAlert.severity === 'warning' ? 'warning' : currentAlert.severity === 'error' ? 'error' : undefined
|
||||||
|
}).body}
|
||||||
|
initial={{ opacity: 0, y: 50 }}
|
||||||
|
animate={{ opacity: 1, y: 0, transition: { duration: 0.3, ease: 'backIn' } }}
|
||||||
|
exit={{ opacity: 0, x: 400, transition: { duration: 0.3, ease: 'backOut' } }}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
className={alert().closeButton}
|
||||||
|
onClick={() => dispatch(removeAlert(currentAlert.id))}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faTimes} fixedWidth size='sm'/>
|
||||||
|
</IconButton>
|
||||||
|
<Typography variant="h3" component="div">
|
||||||
|
{currentAlert.title}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1">
|
||||||
|
{currentAlert.message}
|
||||||
|
</Typography>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</AnimatePresence>
|
||||||
|
</motion.div>
|
||||||
|
</AnimatePresence>
|
||||||
|
),
|
||||||
|
document.body,
|
||||||
|
'alerts'
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default FeedbackConsumer
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
'use client'
|
||||||
|
import store from '@/state/store'
|
||||||
|
import { type FC, type ReactNode } from 'react'
|
||||||
|
import { Provider } from 'react-redux'
|
||||||
|
|
||||||
|
export interface StateProviderProps {
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const StateProvider: FC<StateProviderProps> = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<Provider
|
||||||
|
store={store}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default StateProvider
|
||||||
+15
-10
@@ -10,6 +10,8 @@ import { config } from '@fortawesome/fontawesome-svg-core'
|
|||||||
import '@fortawesome/fontawesome-svg-core/styles.css'
|
import '@fortawesome/fontawesome-svg-core/styles.css'
|
||||||
import { type Metadata } from 'next'
|
import { type Metadata } from 'next'
|
||||||
import { type FC, type ReactNode } from 'react'
|
import { type FC, type ReactNode } from 'react'
|
||||||
|
import FeedbackConsumer from './FeedbackConsumer'
|
||||||
|
import StateProvider from './StateProvider'
|
||||||
|
|
||||||
config.autoAddCss = false
|
config.autoAddCss = false
|
||||||
|
|
||||||
@@ -26,16 +28,19 @@ const RootLayout: FC<RootLayoutProps> = ({ children }) => {
|
|||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body>
|
<body>
|
||||||
<Header />
|
<StateProvider>
|
||||||
<main
|
<Header />
|
||||||
className={css({
|
<main
|
||||||
paddingBlock: 'medium',
|
className={css({
|
||||||
minHeight: 'calc(100vh - 60px - 72px)'
|
paddingBlock: 'medium',
|
||||||
})}
|
minHeight: 'calc(100vh - 60px - 72px)'
|
||||||
>
|
})}
|
||||||
{children}
|
>
|
||||||
</main>
|
{children}
|
||||||
<Footer />
|
</main>
|
||||||
|
<Footer />
|
||||||
|
<FeedbackConsumer />
|
||||||
|
</StateProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import type { AppDispatch } from '@/state/store'
|
||||||
|
import { useDispatch } from 'react-redux'
|
||||||
|
|
||||||
|
// Use throughout your app instead of plain `useDispatch` and `useSelector`
|
||||||
|
export const useAppDispatch: () => AppDispatch = useDispatch
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import type { RootState } from '@/state/store'
|
||||||
|
import type { TypedUseSelectorHook } from 'react-redux'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
|
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { type Alert } from '@/types/feedback'
|
||||||
|
import { createSlice, type PayloadAction } from '@reduxjs/toolkit'
|
||||||
|
|
||||||
|
interface FeedbackState {
|
||||||
|
alerts: Alert[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: FeedbackState = {
|
||||||
|
alerts: []
|
||||||
|
}
|
||||||
|
|
||||||
|
const feedbackSlice = createSlice({
|
||||||
|
name: 'feedback',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
addAlert (state, action: PayloadAction<Alert>) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
alerts: [...state.alerts, action.payload]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeAlert (state, action: PayloadAction<string>) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
alerts: state.alerts.filter(alert => alert.id !== action.payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const { addAlert, removeAlert } = feedbackSlice.actions
|
||||||
|
|
||||||
|
export default feedbackSlice
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import feedbackSlice from '@/state/feedbackSlice'
|
||||||
|
import { configureStore } from '@reduxjs/toolkit'
|
||||||
|
|
||||||
|
const store = configureStore({
|
||||||
|
reducer: {
|
||||||
|
feedback: feedbackSlice.reducer
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default store
|
||||||
|
|
||||||
|
export type RootState = ReturnType<typeof store.getState>
|
||||||
|
export type AppDispatch = typeof store.dispatch
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export interface Alert {
|
export interface Alert {
|
||||||
|
id: string
|
||||||
title: string
|
title: string
|
||||||
message: string
|
message: string
|
||||||
severity: 'success' | 'info' | 'warning' | 'error'
|
severity: 'success' | 'info' | 'warning' | 'error'
|
||||||
|
|||||||
Reference in New Issue
Block a user