Skip to content

Commit

Permalink
feat: layout (#13)
Browse files Browse the repository at this point in the history
* feat(web): add header

* refactor(web): load alert once

* feat(web): add drawer
  • Loading branch information
KazuyaHara authored Oct 25, 2022
1 parent 4848fe6 commit d9e019e
Show file tree
Hide file tree
Showing 14 changed files with 197 additions and 94 deletions.
4 changes: 4 additions & 0 deletions web/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
"lines-between-class-members": ["error", "always", { "exceptAfterSingleLine": true }],
"no-underscore-dangle": ["error", { "allowAfterThis": true }],
"no-unused-vars": "off",
"react/function-component-definition": [
"error",
{ "namedComponents": ["arrow-function", "function-declaration"] }
],
"react/jsx-filename-extension": ["error", { "extensions": [".ts", ".tsx"] }],
"react/require-default-props": [
"error",
Expand Down
1 change: 1 addition & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@emotion/react": "^11.10.4",
"@emotion/styled": "^11.10.4",
"@hookform/resolvers": "^2.9.9",
"@mui/icons-material": "^5.10.9",
"@mui/lab": "^5.0.0-alpha.104",
"@mui/material": "^5.10.10",
"@testing-library/jest-dom": "^5.14.1",
Expand Down
7 changes: 7 additions & 0 deletions web/src/adapters/stores/alert/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { AlertProps, SnackbarProps } from '@mui/material';
import create from 'zustand';

export type AlertState = Pick<AlertProps, 'severity'> &
Pick<SnackbarProps, 'open'> & { message: string };

export const useAlertStore = create<AlertState>(() => ({ message: '' }));
6 changes: 6 additions & 0 deletions web/src/adapters/stores/drawer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { DrawerProps } from '@mui/material';
import create from 'zustand';

export type DrawerState = Pick<DrawerProps, 'open'>;

export const useDrawerStore = create<DrawerState>(() => ({ open: false }));
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 8 additions & 14 deletions web/src/adapters/userInterface/components/atoms/alert/index.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
import React from 'react';

import { Alert as MUIAlert, AlertProps as MUIAlertProps, Snackbar } from '@mui/material';
import { Alert as MUIAlert, Snackbar } from '@mui/material';

export type AlertProps = {
onClose: () => void;
options?: Pick<MUIAlertProps, 'severity'> & { message: string };
};
import { AlertState } from '../../../../stores/alert';

export default function Alert({ onClose, options }: AlertProps) {
export type AlertProps = AlertState & { onClose: () => void };

export default function Alert({ onClose, message, open, severity }: AlertProps) {
return (
<Snackbar
anchorOrigin={{ horizontal: 'right', vertical: 'top' }}
autoHideDuration={5000}
onClose={onClose}
open={Boolean(options)}
open={open}
>
<MUIAlert
onClose={onClose}
severity={options?.severity || 'info'}
variant="filled"
sx={{ width: '100%' }}
>
{options?.message}
<MUIAlert onClose={onClose} severity={severity} variant="filled" sx={{ width: '100%' }}>
{message}
</MUIAlert>
</Snackbar>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';

import { Box, Drawer as MUIDrawer } from '@mui/material';

import { DrawerState } from '../../../../stores/drawer';

type Props = DrawerState & { onClose: () => void };

export const DrawerContent = () => <Box>sidebar</Box>;

export default function Drawer({ onClose, open }: Props) {
return (
<MUIDrawer onClose={onClose} open={open}>
<Box p={3}>
<DrawerContent />
</Box>
</MUIDrawer>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as React from 'react';

import { Menu } from '@mui/icons-material';
import { AppBar, Box, Button, Container, IconButton, Toolbar } from '@mui/material';

import Logo from '../../../assets/logo/black.png';

type Props = { handleDrawerOpen: () => void; handleSignOut: () => void };

export default function Header({ handleDrawerOpen, handleSignOut }: Props) {
return (
<AppBar color="secondary" position="static" sx={{ boxShadow: '0 0 64px 0 rgba(0, 0, 0, .15)' }}>
<Container maxWidth="xl">
<Toolbar disableGutters>
<IconButton
aria-controls="menu-appbar"
aria-haspopup="true"
aria-label="account of current user"
color="primary"
onClick={handleDrawerOpen}
size="large"
sx={{ display: { xs: 'flex', md: 'none' } }}
>
<Menu />
</IconButton>
<Box component="img" height={24} src={Logo} />
<Box flexGrow={1} />
<Button onClick={handleSignOut}>ログアウト</Button>
</Toolbar>
</Container>
</AppBar>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,47 @@ import { use100vh } from 'react-div-100vh';

import useUserUseCase from '../../../../../../application/useCases/user';
import userRepository from '../../../../../repositories/user';
import { useAlertStore } from '../../../../../stores/alert';
import Background from '../../../../assets/background/landing.jpg';
import Logo from '../../../../assets/logo/white.png';
import Alert, { AlertProps } from '../../../atoms/alert';
import Form, { Submit } from '../../../organisms/form/auth/resetPassword';

export default function ResetPassword() {
const height = use100vh();
const [loading, setLoading] = useState(false);
const [alertOptions, setAlertOptions] = useState<AlertProps['options']>();
const { sendPasswordResetEmail } = useUserUseCase(userRepository());

const onSubmit = async ({ email }: Submit) => {
setLoading(true);
await sendPasswordResetEmail(email)
.then(() =>
setAlertOptions({ message: 'パスワード再設定メールを送信しました', severity: 'success' })
useAlertStore.setState({
message: 'パスワード再設定メールを送信しました',
severity: 'success',
})
)
.catch(({ message }: Error) =>
useAlertStore.setState({ message, open: true, severity: 'error' })
)
.catch(({ message }: Error) => setAlertOptions({ message, severity: 'error' }))
.finally(() => setLoading(false));
};

return (
<>
<Alert onClose={() => setAlertOptions(undefined)} options={alertOptions} />
<Box
display="flex"
flexDirection="column"
height={height || '100vh'}
justifyContent="center"
sx={{
backgroundImage: `url(${Background})`,
backgroundPosition: 'center',
backgroundSize: 'cover',
}}
>
<Container maxWidth="xs">
<Box component="img" mx="auto" src={Logo} />
<Form loading={loading} onSubmit={onSubmit} sx={{ mt: 3 }} />
</Container>
</Box>
</>
<Box
display="flex"
flexDirection="column"
height={height || '100vh'}
justifyContent="center"
sx={{
backgroundImage: `url(${Background})`,
backgroundPosition: 'center',
backgroundSize: 'cover',
}}
>
<Container maxWidth="xs">
<Box component="img" mx="auto" src={Logo} />
<Form loading={loading} onSubmit={onSubmit} sx={{ mt: 3 }} />
</Container>
</Box>
);
}
62 changes: 29 additions & 33 deletions web/src/adapters/userInterface/components/pages/landing/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,55 +6,51 @@ import { Link } from 'react-router-dom';

import useUserUseCase from '../../../../../application/useCases/user';
import userRepository from '../../../../repositories/user';
import { useAlertStore } from '../../../../stores/alert';
import Background from '../../../assets/background/landing.jpg';
import Logo from '../../../assets/logo/white.png';
import Alert, { AlertProps } from '../../atoms/alert';
import Form, { Submit } from '../../organisms/form/auth/signin';

export default function Landing() {
const height = use100vh();
const [loading, setLoading] = useState(false);
const [alertOptions, setAlertOptions] = useState<AlertProps['options']>();
const { signIn } = useUserUseCase(userRepository());

const onSubmit = async ({ email, password }: Submit) => {
setLoading(true);
await signIn(email, password).catch(({ message }: Error) => {
setLoading(false);
setAlertOptions({ message, severity: 'error' });
useAlertStore.setState({ message, open: true, severity: 'error' });
});
};

return (
<>
<Alert onClose={() => setAlertOptions(undefined)} options={alertOptions} />
<Box
display="flex"
flexDirection="column"
height={height || '100vh'}
justifyContent="center"
sx={{
backgroundImage: `url(${Background})`,
backgroundPosition: 'center',
backgroundSize: 'cover',
}}
>
<Container maxWidth="xs" sx={{ display: 'flex', flexDirection: 'column' }}>
<Box component="img" mx="auto" src={Logo} />
<Form loading={loading} onSubmit={onSubmit} sx={{ mt: 3 }} />
<Typography
color="white"
component={Link}
mt={3}
mx="auto"
sx={{ textDecoration: 'none' }}
to="/reset-password"
variant="body2"
>
パスワードを再設定する
</Typography>
</Container>
</Box>
</>
<Box
display="flex"
flexDirection="column"
height={height || '100vh'}
justifyContent="center"
sx={{
backgroundImage: `url(${Background})`,
backgroundPosition: 'center',
backgroundSize: 'cover',
}}
>
<Container maxWidth="xs" sx={{ display: 'flex', flexDirection: 'column' }}>
<Box component="img" mx="auto" src={Logo} />
<Form loading={loading} onSubmit={onSubmit} sx={{ mt: 3 }} />
<Typography
color="white"
component={Link}
mt={3}
mx="auto"
sx={{ textDecoration: 'none' }}
to="/reset-password"
variant="body2"
>
パスワードを再設定する
</Typography>
</Container>
</Box>
);
}
9 changes: 9 additions & 0 deletions web/src/adapters/userInterface/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import { BrowserRouter } from 'react-router-dom';
import useUserUseCase from '../../application/useCases/user';
import { TUser } from '../../domains/user';
import userRepository from '../repositories/user';
import { useAlertStore } from '../stores/alert';
import { useAuthStore } from '../stores/authentication';

import Alert from './components/atoms/alert';
import Routes from './routes';
import ThemeProvider from './theme';

export default function UserInterface() {
const { message, open, severity } = useAlertStore();
const { subscribe } = useUserUseCase(userRepository());

useEffect(() => {
Expand All @@ -23,6 +26,12 @@ export default function UserInterface() {
return (
<ThemeProvider>
<BrowserRouter>
<Alert
onClose={() => useAlertStore.setState({ open: false })}
message={message}
open={open}
severity={severity}
/>
<Routes />
</BrowserRouter>
</ThemeProvider>
Expand Down
27 changes: 5 additions & 22 deletions web/src/adapters/userInterface/routes/authenticated.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,11 @@
import React, { useState } from 'react';
import React from 'react';

import { Box, Button, Typography } from '@mui/material';

import useUserUseCase from '../../../application/useCases/user';
import userRepository from '../../repositories/user';
import Alert, { AlertProps } from '../components/atoms/alert';
import { Box, Typography } from '@mui/material';

export default function Authenticated() {
const [alertOptions, setAlertOptions] = useState<AlertProps['options']>();
const { signOut } = useUserUseCase(userRepository());

const handleSignOut = async () => {
await signOut().catch(({ message }: Error) => setAlertOptions({ message, severity: 'error' }));
};

return (
<>
<Alert onClose={() => setAlertOptions(undefined)} options={alertOptions} />
<Box>
<Typography>signed in.</Typography>
<Button onClick={handleSignOut} variant="outlined">
sign out
</Button>
</Box>
</>
<Box>
<Typography>signed in.</Typography>
</Box>
);
}
Loading

0 comments on commit d9e019e

Please sign in to comment.