This commit is contained in:
2024-07-29 03:21:29 +02:00
parent 2008888a16
commit 9deff439d7
32 changed files with 448 additions and 236 deletions
+13
View File
@@ -6,6 +6,9 @@
## Tabelle `egb_benutzer`
- Neue Spalten `token` (Auth token): VarChar(36), Nullable, UNIQUE Constraint
- Neue Spalten `tokenExpiry` (Auth token verfall): DateTime, Nullable
- Neue Spalten `refreshToken` (Auth refresh token): VarChar(36), Nullable, UNIQUE Constraint
- Neue Spalten `refreshExpiry` (Auth refresh token verfall): VarChar(36), DateTime
- Abänderung der Spalte `zeitstempel`: Entfernen des `ON UPDATE` (da sonst die Mitgliedszeit beim Ändern der Daten sich ändert)
- Abänderung der Spalte `benutzername`: Non-Nullable gemacht, UNIQUE Constraint
- Abänderung der Spalte `email`: Non-Nullable gemacht, UNIQUE Constraint
@@ -19,6 +22,15 @@
# Notwendige Anpassung für die Verzeichnisstruktur eines anderen Hosters
## HTACCESS
### `.htaccess`
- RewriteBase anpassen
### `react/public/.htaccess`
- RewriteBase anpassen
- **WICHTIG:** React Projekt neu bauen damit die Datei an den korrekten Platz kopiert wird
## PHP
### `classes/Models/User.php`
@@ -28,6 +40,7 @@
- Alle Pfade
## JS
**WICHTIG:** Nach allen Änderungen muss das React Projekt neu gebaut werden
### `react/vite.config.ts`
- `base` Pfad
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-178
View File
File diff suppressed because one or more lines are too long
+178
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
import"./react-C_FdcE2X.js";
+17
View File
@@ -0,0 +1,17 @@
import{r as g,d as R,a as j}from"./react-C_FdcE2X.js";var V={BASE_URL:"/phpCourse/exam/dist",MODE:"production",DEV:!1,PROD:!0,SSR:!1};const y=t=>{let e;const n=new Set,o=(s,d)=>{const c=typeof s=="function"?s(e):s;if(!Object.is(c,e)){const i=e;e=d??(typeof c!="object"||c===null)?c:Object.assign({},e,c),n.forEach(a=>a(e,i))}},r=()=>e,S={setState:o,getState:r,getInitialState:()=>v,subscribe:s=>(n.add(s),()=>n.delete(s)),destroy:()=>{(V?"production":void 0)!=="production"&&console.warn("[DEPRECATED] The `destroy` method will be unsupported in a future version. Instead use unsubscribe function returned by subscribe. Everything will be garbage-collected if store is garbage-collected."),n.clear()}},v=e=t(o,r,S);return S},$=t=>t?y(t):y;var w={exports:{}},b={},D={exports:{}},_={};/**
* @license React
* use-sync-external-store-shim.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var f=g;function I(t,e){return t===e&&(t!==0||1/t===1/e)||t!==t&&e!==e}var A=typeof Object.is=="function"?Object.is:I,C=f.useState,P=f.useEffect,T=f.useLayoutEffect,W=f.useDebugValue;function q(t,e){var n=e(),o=C({inst:{value:n,getSnapshot:e}}),r=o[0].inst,u=o[1];return T(function(){r.value=n,r.getSnapshot=e,m(r)&&u({inst:r})},[t,n,e]),P(function(){return m(r)&&u({inst:r}),t(function(){m(r)&&u({inst:r})})},[t]),W(n),n}function m(t){var e=t.getSnapshot;t=t.value;try{var n=e();return!A(t,n)}catch{return!0}}function z(t,e){return e()}var B=typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"?z:q;_.useSyncExternalStore=f.useSyncExternalStore!==void 0?f.useSyncExternalStore:B;D.exports=_;var F=D.exports;/**
* @license React
* use-sync-external-store-shim/with-selector.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var E=g,L=F;function M(t,e){return t===e&&(t!==0||1/t===1/e)||t!==t&&e!==e}var U=typeof Object.is=="function"?Object.is:M,k=L.useSyncExternalStore,G=E.useRef,H=E.useEffect,J=E.useMemo,K=E.useDebugValue;b.useSyncExternalStoreWithSelector=function(t,e,n,o,r){var u=G(null);if(u.current===null){var l={hasValue:!1,value:null};u.current=l}else l=u.current;u=J(function(){function S(i){if(!v){if(v=!0,s=i,i=o(i),r!==void 0&&l.hasValue){var a=l.value;if(r(a,i))return d=a}return d=i}if(a=d,U(s,i))return a;var h=o(i);return r!==void 0&&r(a,h)?a:(s=i,d=h)}var v=!1,s,d,c=n===void 0?null:n;return[function(){return S(e())},c===null?void 0:function(){return S(c())}]},[e,n,o,r]);var p=k(t,u[0],u[1]);return H(function(){l.hasValue=!0,l.value=p},[p]),K(p),p};w.exports=b;var N=w.exports;const Q=R(N);var O={BASE_URL:"/phpCourse/exam/dist",MODE:"production",DEV:!1,PROD:!0,SSR:!1};const{useDebugValue:X}=j,{useSyncExternalStoreWithSelector:Y}=Q;let x=!1;const Z=t=>t;function tt(t,e=Z,n){(O?"production":void 0)!=="production"&&n&&!x&&(console.warn("[DEPRECATED] Use `createWithEqualityFn` instead of `create` or use `useStoreWithEqualityFn` instead of `useStore`. They can be imported from 'zustand/traditional'. https://github.com/pmndrs/zustand/discussions/1937"),x=!0);const o=Y(t.subscribe,t.getState,t.getServerState||t.getInitialState,e,n);return X(o),o}const et=t=>{(O?"production":void 0)!=="production"&&typeof t!="function"&&console.warn("[DEPRECATED] Passing a vanilla store will be unsupported in a future version. Instead use `import { useStore } from 'zustand'`.");const e=typeof t=="function"?$(t):t,n=(o,r)=>tt(e,o,r);return Object.assign(n,e),n},rt=t=>et;export{rt as c};
+5 -4
View File
@@ -5,11 +5,12 @@
<link rel="icon" type="image/svg+xml" href="/phpCourse/exam/dist/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<script type="module" crossorigin src="/phpCourse/exam/dist/assets/index-CB6IjOSm.js"></script>
<script type="module" crossorigin src="/phpCourse/exam/dist/assets/index-CzVZnOtD.js"></script>
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/react-C_FdcE2X.js">
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/mui-53GMXZgr.js">
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/tanstack-ZGp-Rrdw.js">
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/i18n-DyW0LrNj.js">
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/mui-aBip8mmu.js">
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/tanstack-DojtBDN6.js">
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/zustand-DAXCIHlT.js">
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/i18n-W-kxdzA-.js">
<link rel="stylesheet" crossorigin href="/phpCourse/exam/dist/assets/mui-CKDNpdid.css">
<link rel="stylesheet" crossorigin href="/phpCourse/exam/dist/assets/index-D83Ey19k.css">
</head>
+5 -1
View File
@@ -82,5 +82,9 @@
"Register": "Konto anlegen",
"Confirm header": "Fast geschafft!",
"Confirm mail": "Prüfe dein E-Mail Postfach auf eine Bestätigungsmail.",
"Close": "Schließen"
"Close": "Schließen",
"Dark": "Dunkel",
"Light": "Hell",
"System": "System"
}
+5 -1
View File
@@ -83,5 +83,9 @@
"Register": "Create account",
"Confirm header": "Almost there!",
"Confirm mail": "Check your email for a confirmation mail.",
"Close": "Close"
"Close": "Close",
"Dark": "Dark",
"Light": "Light",
"System": "System"
}
+1 -1
View File
File diff suppressed because one or more lines are too long
-1
View File
@@ -24,7 +24,6 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^15.0.0",
"use-local-storage-state": "^19.3.1",
"zustand": "^4.5.4"
},
"devDependencies": {
-15
View File
@@ -50,9 +50,6 @@ importers:
react-i18next:
specifier: ^15.0.0
version: 15.0.0(i18next@23.12.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
use-local-storage-state:
specifier: ^19.3.1
version: 19.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
zustand:
specifier: ^4.5.4
version: 4.5.4(@types/react@18.3.3)(react@18.3.1)
@@ -1845,13 +1842,6 @@ packages:
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
use-local-storage-state@19.3.1:
resolution: {integrity: sha512-y3Z1dODXvZXZB4qtLDNN8iuXbsYD6TAxz61K58GWB9/yKwrNG9ynI0GzCTHi/Je1rMiyOwMimz0oyFsZn+Kj7Q==}
engines: {node: '>=14'}
peerDependencies:
react: '>=18'
react-dom: '>=18'
use-sync-external-store@1.2.0:
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
peerDependencies:
@@ -3731,11 +3721,6 @@ snapshots:
dependencies:
punycode: 2.3.1
use-local-storage-state@19.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
use-sync-external-store@1.2.0(react@18.3.1):
dependencies:
react: 18.3.1
@@ -82,5 +82,9 @@
"Register": "Konto anlegen",
"Confirm header": "Fast geschafft!",
"Confirm mail": "Prüfe dein E-Mail Postfach auf eine Bestätigungsmail.",
"Close": "Schließen"
"Close": "Schließen",
"Dark": "Dunkel",
"Light": "Hell",
"System": "System"
}
@@ -83,5 +83,9 @@
"Register": "Create account",
"Confirm header": "Almost there!",
"Confirm mail": "Check your email for a confirmation mail.",
"Close": "Close"
"Close": "Close",
"Dark": "Dark",
"Light": "Light",
"System": "System"
}
+28
View File
@@ -0,0 +1,28 @@
import { createTheme, CssBaseline, ThemeProvider, useMediaQuery } from '@mui/material';
import { FC, useMemo } from 'react';
import Router from './router';
import useGuestBookStore from './store/store';
const App: FC = () => {
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
const theme = useGuestBookStore((state) => state.theme);
const themePreset = useMemo(
() =>
createTheme({
palette: {
mode: theme ?? (prefersDarkMode ? 'dark' : 'light'),
},
}),
[theme, prefersDarkMode]
);
return (
<ThemeProvider theme={themePreset}>
<CssBaseline />
<Router />
</ThemeProvider>
);
};
export default App;
+6 -5
View File
@@ -1,6 +1,6 @@
import { createContext, FC, PropsWithChildren, useContext, useEffect, useRef, useState } from 'react';
import useLocalStorageState from 'use-local-storage-state';
import { POST_LIMIT, PROFILE_POST_LIMIT } from '../constanst';
import useGuestBookStore from '../store/store';
import { PostAuth, PostCreate, PostDelete, PostListAuth, PostListNonAuth, PostNew, PostUpdate } from '../types/Post';
import { User, UserCreate, UserImageUpdate, UserUpdate } from '../types/User';
@@ -94,10 +94,11 @@ export const useApi = () => {
export const ApiProvider: FC<PropsWithChildren<Record<string, unknown>>> = ({ children }) => {
const [hasAuth, setHasAuth] = useState(false);
const [authenticatedUser, setAuthenticatedUser] = useState<User>();
const [currentSession, setCurrentSession] = useLocalStorageState<[string | undefined, string | undefined]>(
'egb_session',
{ defaultValue: [undefined, undefined] }
);
const [currentSession, setCurrentSession] = useGuestBookStore((state) => [
state.currentSession,
state.setCurrentSession,
]);
const token = useRef<string | undefined>();
@@ -0,0 +1,34 @@
import { Box, Divider, Grid, Typography } from '@mui/material';
import { FC } from 'react';
const Footer: FC = () => {
return (
<Box
sx={{
marginTop: 2,
display: 'flex',
justifyContent: 'center',
}}
>
<Box sx={{ maxWidth: '800px', flexGrow: 1 }}>
<Grid container spacing={2}>
<Grid item xs={12} sx={{ height: '50px' }} />
<Grid item xs={12}>
<Divider />
</Grid>
<Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center' }}>
<Typography variant="caption">© 2024 Kilian Kurt Hofmann</Typography>
</Grid>
</Grid>
</Box>
</Box>
);
};
export default Footer;
/*
*/
@@ -7,11 +7,7 @@ import { useApi } from '../../../api/Api';
import { Login } from '../../../types/User';
import ErrorComponent from '../../Error/ErrorComponent';
interface Props {
handleClose: () => void;
}
const LoginForm: FC<Props> = ({ handleClose }) => {
const LoginForm: FC = () => {
const [error, setError] = useState();
const { t } = useTranslation();
@@ -27,7 +23,6 @@ const LoginForm: FC<Props> = ({ handleClose }) => {
try {
await Api.logIn(value.email, value.password);
router.invalidate();
handleClose();
//eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (_error: any) {
setError(_error);
@@ -35,6 +35,7 @@ const PostForm: FC = () => {
{
onSuccess: async (data) => {
form.reset();
setCharacterCount(0);
await queryClient.invalidateQueries({ queryKey: ['posts'] });
navigate({ to: '/', search: { page: data.pages - 1 } });
},
+13 -2
View File
@@ -1,4 +1,4 @@
import { AccountCircle, Person, Translate } from '@mui/icons-material';
import { AccountCircle, DarkModeOutlined, LightMode, Person, SettingsBrightness, Translate } from '@mui/icons-material';
import {
AppBar,
Avatar,
@@ -13,7 +13,9 @@ import { Link, useRouterState } from '@tanstack/react-router';
import { cloneElement, FC, ReactElement, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useApi } from '../../api/Api';
import useGuestBookStore from '../../store/store';
import LanguageMenu from '../Menus/Language/LanguageMenu';
import ThemeMenu from '../Menus/Theme/ThemeMenu';
import UserMenu from '../Menus/User/UserMenu';
const ElevationScroll = ({ children }: { children: ReactElement }) => {
@@ -30,14 +32,17 @@ const ElevationScroll = ({ children }: { children: ReactElement }) => {
const Header: FC = () => {
const [anchorUserMenu, setAnchorUserMenu] = useState<null | HTMLElement>(null);
const [anchorLanguageMenu, setAnchorLanguageMenu] = useState<null | HTMLElement>(null);
const [anchorThemeMenu, setAnchorThemeMenu] = useState<null | HTMLElement>(null);
const { t } = useTranslation();
const isLoading = useRouterState({ select: (s) => s.status === 'pending' });
const Api = useApi();
const [theme] = useGuestBookStore((state) => [state.theme, state.setTheme]);
const handleClose = () => {
setAnchorLanguageMenu(null);
setAnchorUserMenu(null);
setAnchorLanguageMenu(null);
setAnchorThemeMenu(null);
};
return (
@@ -56,6 +61,11 @@ const Header: FC = () => {
<IconButton size="large" onClick={(event) => setAnchorLanguageMenu(event.currentTarget)}>
<Translate sx={{ color: 'white' }} />
</IconButton>
<IconButton size="large" onClick={(event) => setAnchorThemeMenu(event.currentTarget)}>
{theme === 'dark' && <DarkModeOutlined />}
{theme === 'light' && <LightMode sx={{ color: 'white' }} />}
{!theme && <SettingsBrightness sx={{ color: 'white' }} />}
</IconButton>
{Api.authenticatedUser ? (
<IconButton onClick={(event) => setAnchorUserMenu(event.currentTarget)} sx={{ p: 0 }}>
<Avatar alt={Api.authenticatedUser.username} src={`${Api.authenticatedUser.image}`}>
@@ -70,6 +80,7 @@ const Header: FC = () => {
</Box>
<LanguageMenu anchorEl={anchorLanguageMenu} handleClose={handleClose} />
<UserMenu anchorEl={anchorUserMenu} handleClose={handleClose} />
<ThemeMenu anchorEl={anchorThemeMenu} handleClose={handleClose} />
</Toolbar>
</AppBar>
<Toolbar />
@@ -0,0 +1,88 @@
import { DarkModeOutlined, LightMode, SettingsBrightness } from '@mui/icons-material';
import { Grid, Menu, MenuItem, Typography } from '@mui/material';
import { FC } from 'react';
import { useTranslation } from 'react-i18next';
import useGuestBookStore from '../../../store/store';
interface Props {
anchorEl: HTMLElement | null;
handleClose: () => void;
}
const ThemeMenu: FC<Props> = ({ anchorEl, handleClose }) => {
const [theme, setTheme] = useGuestBookStore((state) => [state.theme, state.setTheme]);
const { t } = useTranslation();
return (
<Menu
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={Boolean(anchorEl)}
onClose={handleClose}
sx={{
'& .MuiMenu-paper': {
minWidth: '240px',
},
}}
>
<MenuItem
key="dark"
selected={theme === 'dark'}
onClick={() => {
setTheme('dark');
}}
>
<Grid container spacing={2}>
<Grid item xs={2}>
<DarkModeOutlined />
</Grid>
<Grid item xs={10}>
<Typography>{t('Dark')}</Typography>
</Grid>
</Grid>
</MenuItem>
<MenuItem
key="light"
selected={theme === 'light'}
onClick={() => {
setTheme('light');
}}
>
<Grid container spacing={2}>
<Grid item xs={2}>
<LightMode />
</Grid>
<Grid item xs={10}>
<Typography>{t('Light')}</Typography>
</Grid>
</Grid>
</MenuItem>
<MenuItem
key="system"
selected={!theme}
onClick={() => {
setTheme(undefined);
}}
>
<Grid container spacing={2}>
<Grid item xs={2}>
<SettingsBrightness />
</Grid>
<Grid item xs={10}>
<Typography>{t('System')}</Typography>
</Grid>
</Grid>
</MenuItem>
</Menu>
);
};
export default ThemeMenu;
@@ -64,7 +64,7 @@ const UserMenu: FC<Props> = ({ anchorEl, handleClose }) => {
onClick={async () => {
await Api.logOut();
router.invalidate();
_handleClose();
navigate({ to: ROUTES.INDEX });
}}
>
{t('Log out')}
@@ -74,7 +74,7 @@ const UserMenu: FC<Props> = ({ anchorEl, handleClose }) => {
<RegisterDialog open={register} onClose={() => setRegister(false)} />
) : (
<Box>
<LoginForm handleClose={_handleClose} />
<LoginForm />
<Box sx={{ padding: 1 }}>
<Trans i18nKey="Register prompt">
<Typography component="span" />
+3 -2
View File
@@ -27,11 +27,12 @@ import PostEditDialog from '../Dialogs/PostEdit/PostEditDialog';
import ErrorComponent from '../Error/ErrorComponent';
interface Props {
page?: number;
post: PostNonAuth | PostAuth;
disableActions?: boolean;
}
const Post: FC<Props> = ({ post, disableActions }) => {
const Post: FC<Props> = ({ page = 0, post, disableActions }) => {
const [deleteOpen, setDeleteOpen] = useState(false);
const [editOpen, setEditOpen] = useState(false);
//eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -128,7 +129,7 @@ const Post: FC<Props> = ({ post, disableActions }) => {
await queryClient.invalidateQueries({
queryKey: ['posts'],
});
navigate({ to: '/', search: { page: data.pages - 1 } });
if (page >= data.pages) navigate({ to: '/', search: { page: data.pages - 1 } });
},
onError: setError,
});
@@ -33,7 +33,7 @@ const Profile: FC<Props> = ({ user, posts, canEdit }) => {
const { t } = useTranslation();
return (
<Grid container sx={{ justifyContent: 'center' }} spacing={2}>
<Grid container sx={{ justifyContent: 'center', marginTop: 0 }} spacing={2}>
<Grid item>
<Card>
<CardContent>
+2 -2
View File
@@ -3,7 +3,6 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { StrictMode } from 'react';
import ReactDOM from 'react-dom/client';
import ApiProvider from './api/Api';
import Router from './router';
// Import i18n
import './i18n';
@@ -13,6 +12,7 @@ import '@fontsource/roboto/300.css';
import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';
import App from './App';
// Query Client
const queryClient = new QueryClient();
@@ -25,7 +25,7 @@ if (!rootElement.innerHTML) {
<StrictMode>
<QueryClientProvider client={queryClient}>
<ApiProvider>
<Router />
<App />
</ApiProvider>
{process.env.NODE_ENV === 'development' && <ReactQueryDevtools initialIsOpen={false} />}
</QueryClientProvider>
+5 -2
View File
@@ -4,19 +4,22 @@ import { createRootRouteWithContext, ErrorRouteComponent, Outlet, useRouter } fr
import { TanStackRouterDevtools } from '@tanstack/router-devtools';
import { useEffect } from 'react';
import { useApi } from '../api/Api';
import Footer from '../components/Footer/Footer';
import Header from '../components/Header/Header';
const Root = () => {
return (
<>
<Box sx={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
<Header />
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
<Box sx={{ maxWidth: '800px', flexGrow: 1 }}>
<Outlet />
</Box>
</Box>
<Box sx={{ flexGrow: 1 }} />
<Footer />
{process.env.NODE_ENV === 'development' && <TanStackRouterDevtools />}
</>
</Box>
);
};
+2 -2
View File
@@ -36,10 +36,10 @@ const Home = () => {
return (
<>
<Snackbar open={isFetching} message={t('Updating')} />
<Grid container spacing={2}>
<Grid container spacing={2} sx={{ marginTop: 0 }}>
{postsQuery.data.map((post) => (
<Grid item xs={12} key={post.id}>
<Post post={post} />
<Post page={page} post={post} />
</Grid>
))}
<Grid item xs={12}>
+20 -4
View File
@@ -2,13 +2,29 @@ import type {} from '@redux-devtools/extension';
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
interface GuestBookState {}
interface GuestBookState {
theme: 'dark' | 'light' | undefined;
currentSession: [string | undefined, string | undefined];
setTheme: (theme: GuestBookState['theme']) => void;
setCurrentSession: (session: GuestBookState['currentSession']) => void;
}
const useGuestBookStore = create<GuestBookState>()(
devtools(
persist(() => ({}), {
name: 'guestbook-storage',
})
persist(
(set) => ({
theme: undefined,
currentSession: [undefined, undefined],
setTheme: (theme: GuestBookState['theme']) => set(() => ({ theme })),
setCurrentSession: (session: GuestBookState['currentSession']) =>
set(() => ({
currentSession: session,
})),
}),
{
name: 'guestbook-storage',
}
)
)
);