diff --git a/bun.lockb b/bun.lockb
index c6f12e2..c63038c 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index 3cf9643..f9e3088 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"@fortawesome/free-brands-svg-icons": "^6.5.1",
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/react-fontawesome": "^0.2.0",
+ "@reduxjs/toolkit": "^2.0.1",
"appwrite": "^13.0.1",
"entgamers-database": "^0.0.5",
"entgamers-panda-preset": "0.1.1",
@@ -27,6 +28,7 @@
"node-appwrite": "^11.1.0",
"react": "18.2.0",
"react-dom": "18.2.0",
+ "react-redux": "^9.0.4",
"sharp": "^0.33.1",
"yup": "^1.3.3"
},
diff --git a/src/app/FeedbackConsumer.tsx b/src/app/FeedbackConsumer.tsx
new file mode 100644
index 0000000..f1b964b
--- /dev/null
+++ b/src/app/FeedbackConsumer.tsx
@@ -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(
+ (
+
+
+
+ {alerts.map((currentAlert) => (
+
+ dispatch(removeAlert(currentAlert.id))}
+ >
+
+
+
+ {currentAlert.title}
+
+
+ {currentAlert.message}
+
+
+ ))}
+
+
+
+ ),
+ document.body,
+ 'alerts'
+ )}
+ >
+ )
+}
+export default FeedbackConsumer
diff --git a/src/app/StateProvider.tsx b/src/app/StateProvider.tsx
new file mode 100644
index 0000000..61ef270
--- /dev/null
+++ b/src/app/StateProvider.tsx
@@ -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 = ({ children }) => {
+ return (
+
+ {children}
+
+ )
+}
+export default StateProvider
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index c0cb7ca..b1207fc 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -10,6 +10,8 @@ import { config } from '@fortawesome/fontawesome-svg-core'
import '@fortawesome/fontawesome-svg-core/styles.css'
import { type Metadata } from 'next'
import { type FC, type ReactNode } from 'react'
+import FeedbackConsumer from './FeedbackConsumer'
+import StateProvider from './StateProvider'
config.autoAddCss = false
@@ -26,16 +28,19 @@ const RootLayout: FC = ({ children }) => {
return (
-
-
- {children}
-
-
+
+
+
+ {children}
+
+
+
+
)
diff --git a/src/hooks/useAppDispatch.ts b/src/hooks/useAppDispatch.ts
new file mode 100644
index 0000000..ed4c48d
--- /dev/null
+++ b/src/hooks/useAppDispatch.ts
@@ -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
diff --git a/src/hooks/useAppSelector.ts b/src/hooks/useAppSelector.ts
new file mode 100644
index 0000000..c774e36
--- /dev/null
+++ b/src/hooks/useAppSelector.ts
@@ -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 = useSelector
diff --git a/src/state/feedbackSlice.ts b/src/state/feedbackSlice.ts
new file mode 100644
index 0000000..b87b5ca
--- /dev/null
+++ b/src/state/feedbackSlice.ts
@@ -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) {
+ return {
+ ...state,
+ alerts: [...state.alerts, action.payload]
+ }
+ },
+ removeAlert (state, action: PayloadAction) {
+ return {
+ ...state,
+ alerts: state.alerts.filter(alert => alert.id !== action.payload)
+ }
+ }
+ }
+})
+
+export const { addAlert, removeAlert } = feedbackSlice.actions
+
+export default feedbackSlice
diff --git a/src/state/store.ts b/src/state/store.ts
new file mode 100644
index 0000000..a31a62a
--- /dev/null
+++ b/src/state/store.ts
@@ -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
+export type AppDispatch = typeof store.dispatch
diff --git a/src/types/feedback.ts b/src/types/feedback.ts
index f827abd..a2e1e25 100644
--- a/src/types/feedback.ts
+++ b/src/types/feedback.ts
@@ -1,4 +1,5 @@
export interface Alert {
+ id: string
title: string
message: string
severity: 'success' | 'info' | 'warning' | 'error'