Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
E
exam_12_Tsoy_Danil
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Цой Данил
exam_12_Tsoy_Danil
Commits
bba25780
Commit
bba25780
authored
Apr 15, 2023
by
Цой Данил
💬
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added basic front to show all data
parent
e3b30264
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
897 additions
and
180 deletions
+897
-180
fixtures.ts
backend/src/fixtures.ts
+0
-5
tsconfig.json
backend/tsconfig.json
+104
-103
.env
frontend/.env
+1
-0
App.tsx
frontend/src/App.tsx
+21
-26
Header.tsx
frontend/src/components/Header/Header.tsx
+0
-42
Layout.tsx
frontend/src/components/Layout/Layout.tsx
+1
-2
IPhotoBlockProps.ts
frontend/src/components/PhotoBlock/IPhotoBlockProps.ts
+8
-0
PhotoBlock.tsx
frontend/src/components/PhotoBlock/PhotoBlock.tsx
+50
-0
IPhotoUserGalleryBlockProps.ts
...ents/PhotoUserGalleryBlock/IPhotoUserGalleryBlockProps.ts
+8
-0
PhotoUserGalleryBlock.module.css
...ts/PhotoUserGalleryBlock/PhotoUserGalleryBlock.module.css
+0
-0
PhotoUserGalleryBlock.tsx
...omponents/PhotoUserGalleryBlock/PhotoUserGalleryBlock.tsx
+53
-0
AddForm.module.css
frontend/src/containers/AddForm/AddForm.module.css
+32
-0
AddForm.tsx
frontend/src/containers/AddForm/AddForm.tsx
+112
-0
AuthorizeForm.module.css
...end/src/containers/AuthorizeForm/AuthorizeForm.module.css
+98
-0
AuthorizeForm.tsx
frontend/src/containers/AuthorizeForm/AuthorizeForm.tsx
+118
-0
ErrorPage.tsx
frontend/src/containers/ErrorPage/ErrorPage.tsx
+34
-0
MainPage.module.css
frontend/src/containers/MainPage/MainPage.module.css
+10
-0
MainPage.tsx
frontend/src/containers/MainPage/MainPage.tsx
+110
-0
UserGallery.tsx
frontend/src/containers/UserGallery/UserGallery.tsx
+135
-0
EStatuses.ts
frontend/src/enum/EStatuses.ts
+2
-2
No files found.
backend/src/fixtures.ts
View file @
bba25780
...
@@ -49,11 +49,6 @@ export default db.once('open', async () => {
...
@@ -49,11 +49,6 @@ export default db.once('open', async () => {
title
:
'Commander Ledros'
,
title
:
'Commander Ledros'
,
photo
:
'ledros.jpg'
photo
:
'ledros.jpg'
},
},
{
user
:
userOne
.
_id
,
title
:
'Commander Ledros'
,
photo
:
'ledros.jpg'
},
{
{
user
:
userTwo
.
_id
,
user
:
userTwo
.
_id
,
title
:
'Quinn and Valor'
,
title
:
'Quinn and Valor'
,
...
...
backend/tsconfig.json
View file @
bba25780
This diff is collapsed.
Click to expand it.
frontend/.env
0 → 100644
View file @
bba25780
VITE_BASE_URL=http://localhost:8000/
\ No newline at end of file
frontend/src/App.tsx
View file @
bba25780
import
{
useState
}
from
'react'
import
React
,
{
useState
}
from
'react'
import
reactLogo
from
'./assets/react.svg'
import
reactLogo
from
'./assets/react.svg'
import
viteLogo
from
'/vite.svg'
import
viteLogo
from
'/vite.svg'
import
'./App.css'
import
'./App.css'
import
{
Route
,
Routes
}
from
'react-router-dom'
import
Layout
from
'./components/Layout/Layout'
import
PrivateRoute
from
'./utils/PrivateRoute'
import
ErrorPage
from
'./containers/ErrorPage/ErrorPage'
import
AuthorizeForm
from
'./containers/AuthorizeForm/AuthorizeForm'
import
MainPage
from
'./containers/MainPage/MainPage'
import
UserGallery
from
'./containers/UserGallery/UserGallery'
import
AddForm
from
'./containers/AddForm/AddForm'
function
App
()
{
const
App
:
React
.
FunctionComponent
=
():
React
.
ReactElement
=>
{
const
[
count
,
setCount
]
=
useState
(
0
)
return
(
return
(
<
div
className=
"App"
>
<
Routes
>
<
div
>
<
Route
element=
{
<
Layout
/>
}
>
<
a
href=
"https://vitejs.dev"
target=
"_blank"
>
<
Route
path=
'/'
element=
{
<
MainPage
/>
}
/>
<
img
src=
{
viteLogo
}
className=
"logo"
alt=
"Vite logo"
/>
<
Route
path=
'/user/:id'
element=
{
<
UserGallery
/>
}
/>
</
a
>
<
Route
path=
'/authorize'
element=
{
<
AuthorizeForm
/>
}
/>
<
a
href=
"https://reactjs.org"
target=
"_blank"
>
<
Route
element=
{
<
PrivateRoute
/>
}
>
<
img
src=
{
reactLogo
}
className=
"logo react"
alt=
"React logo"
/>
<
Route
path=
'/add-photo'
element=
{
<
AddForm
/>
}
/>
</
a
>
</
Route
>
</
div
>
<
Route
path=
'*'
element=
{
<
ErrorPage
/>
}
/>
<
h1
>
Vite + React
</
h1
>
</
Route
>
<
div
className=
"card"
>
</
Routes
>
<
button
onClick=
{
()
=>
setCount
((
count
)
=>
count
+
1
)
}
>
count is
{
count
}
</
button
>
<
p
>
Edit
<
code
>
src/App.tsx
</
code
>
and save to test HMR
</
p
>
</
div
>
<
p
className=
"read-the-docs"
>
Click on the Vite and React logos to learn more
</
p
>
</
div
>
)
)
}
}
...
...
frontend/src/components/Header/Header.tsx
View file @
bba25780
...
@@ -71,48 +71,6 @@ const Header: React.FunctionComponent = (): React.ReactElement => {
...
@@ -71,48 +71,6 @@ const Header: React.FunctionComponent = (): React.ReactElement => {
<
h4
style=
{
{
margin
:
0
,
color
:
'black'
,
marginRight
:
'40px'
}
}
>
<
h4
style=
{
{
margin
:
0
,
color
:
'black'
,
marginRight
:
'40px'
}
}
>
<
span
style=
{
{
fontWeight
:
'normal'
,
marginRight
:
'10px'
}
}
>
Hello,
</
span
><
NavLink
to=
{
`/user/${user?._id}`
}
>
{
user
?.
username
}
</
NavLink
>
<
span
style=
{
{
fontWeight
:
'normal'
,
marginRight
:
'10px'
}
}
>
Hello,
</
span
><
NavLink
to=
{
`/user/${user?._id}`
}
>
{
user
?.
username
}
</
NavLink
>
</
h4
>
</
h4
>
<
Button
ref=
{
anchorRef
}
id=
"composition-button"
aria
-
controls=
{
open
?
'composition-menu'
:
undefined
}
aria
-
expanded=
{
open
?
'true'
:
undefined
}
aria
-
haspopup=
"true"
onClick=
{
handleToggle
}
>
Navigate menu
</
Button
>
{
/* <Popper
open={open}
anchorEl={anchorRef.current}
role={undefined}
placement="bottom-start"
transition
>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin:
placement === 'bottom-start' ? 'left top' : 'left bottom',
}}
>
<Paper
style={{position: 'relative', zIndex: 1000, fontWeight: 'bold'}}
>
<ClickAwayListener onClickAway={handleClose}>
<MenuList
autoFocusItem={open}
id="composition-menu"
aria-labelledby="composition-button"
onKeyDown={handleListKeyDown}
>
<MenuItem onClick={()=>{navigateToPage('/add-photo')}}>Add new photo</MenuItem>
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper> */
}
<
button
className=
{
styles
.
Header_button
}
onClick=
{
logoutHandler
}
>
Logout
</
button
>
<
button
className=
{
styles
.
Header_button
}
onClick=
{
logoutHandler
}
>
Logout
</
button
>
</
div
>
:
</
div
>
:
<
div
>
<
div
>
...
...
frontend/src/components/Layout/Layout.tsx
View file @
bba25780
import
React
from
"react"
;
import
React
from
"react"
;
import
{
Outlet
}
from
"react-router-dom"
;
import
{
Outlet
}
from
"react-router-dom"
;
import
Header
from
"../Header/Header"
;
import
Header
from
"../Header/Header"
;
import
styles
from
'./Layout.module.css'
const
Layout
:
React
.
FunctionComponent
=
():
React
.
ReactElement
=>
{
const
Layout
:
React
.
FunctionComponent
=
():
React
.
ReactElement
=>
{
return
(
return
(
<
div
className=
{
styles
.
Layout
}
>
<
div
>
<
Header
/>
<
Header
/>
<
main
>
<
main
>
<
Outlet
/>
<
Outlet
/>
...
...
frontend/src/components/PhotoBlock/IPhotoBlockProps.ts
0 → 100644
View file @
bba25780
import
{
MouseEventHandler
}
from
"react"
;
import
IPhoto
from
"../../interfaces/IPhoto"
;
export
default
interface
IPhotoBlockProps
{
photo
:
IPhoto
showFullImage
:
MouseEventHandler
<
any
>
goToUserPage
:
MouseEventHandler
<
HTMLButtonElement
>
}
\ No newline at end of file
frontend/src/components/PhotoBlock/PhotoBlock.tsx
0 → 100644
View file @
bba25780
import
*
as
React
from
'react'
;
import
Card
from
'@mui/material/Card'
;
import
CardContent
from
'@mui/material/CardContent'
;
import
CardMedia
from
'@mui/material/CardMedia'
;
import
Typography
from
'@mui/material/Typography'
;
import
{
Button
,
CardActionArea
,
CardActions
}
from
'@mui/material'
;
import
IPhotoBlockProps
from
'./IPhotoBlockProps'
;
import
image_not_found
from
'../../assets/image_not_found.png'
import
{
shallowEqual
,
useSelector
}
from
'react-redux'
;
import
{
AppState
}
from
'../../store/store'
;
const
PhotoBlock
:
React
.
FunctionComponent
<
IPhotoBlockProps
>
=
(
props
):
React
.
ReactElement
=>
{
const
{
user
}
=
useSelector
((
state
:
AppState
)
=>
state
.
users
,
shallowEqual
)
return
(
<
div
style=
{
{
margin
:
'10px'
}
}
>
<
Card
sx=
{
{
maxWidth
:
400
}
}
>
<
CardActionArea
onClick=
{
props
.
showFullImage
}
>
<
CardMedia
component=
"img"
width=
"350"
image=
{
import
.
meta
.
env
.
VITE_BASE_URL
+
'uploads/'
+
props
.
photo
.
photo
}
onError
=
{(
e
)
=
>
{
e
.
currentTarget
.
src
=
image_not_found
}
}
alt=
{
props
.
photo
.
title
+
'image'
}
/
>
<
CardContent
style=
{
{
margin
:
'5px'
,
padding
:
'5px'
,
marginBottom
:
0
}
}
>
<
Typography
gutterBottom
variant=
"h5"
component=
"div"
style=
{
{
marginBottom
:
0
}
}
>
{
props
.
photo
.
title
}
</
Typography
>
</
CardContent
>
</
CardActionArea
>
<
CardActions
>
<
Button
onClick=
{
props
.
goToUserPage
}
size=
"small"
color=
"primary"
>
{
user
?.
username
===
props
.
photo
.
user
.
username
?
'My Photo'
:
props
.
photo
.
user
.
username
}
</
Button
>
</
CardActions
>
</
Card
>
</
div
>
);
}
export default PhotoBlock
\ No newline at end of file
frontend/src/components/PhotoUserGalleryBlock/IPhotoUserGalleryBlockProps.ts
0 → 100644
View file @
bba25780
import
{
MouseEventHandler
}
from
"react"
;
import
IPhoto
from
"../../interfaces/IPhoto"
;
export
default
interface
IPhotoUserGalleryBlockProps
{
photo
:
IPhoto
showFullImage
:
MouseEventHandler
<
any
>
deletePhoto
:
MouseEventHandler
<
HTMLButtonElement
>
}
\ No newline at end of file
frontend/src/components/PhotoUserGalleryBlock/PhotoUserGalleryBlock.module.css
0 → 100644
View file @
bba25780
frontend/src/components/PhotoUserGalleryBlock/PhotoUserGalleryBlock.tsx
0 → 100644
View file @
bba25780
import
*
as
React
from
'react'
;
import
Card
from
'@mui/material/Card'
;
import
CardContent
from
'@mui/material/CardContent'
;
import
CardMedia
from
'@mui/material/CardMedia'
;
import
Typography
from
'@mui/material/Typography'
;
import
{
Button
,
CardActionArea
,
CardActions
}
from
'@mui/material'
;
import
image_not_found
from
'../../assets/image_not_found.png'
import
{
shallowEqual
,
useSelector
}
from
'react-redux'
;
import
{
AppState
}
from
'../../store/store'
;
import
IPhotoUserGalleryBlockProps
from
'./IPhotoUserGalleryBlockProps'
;
const
PhotoUserGalleryBlock
:
React
.
FunctionComponent
<
IPhotoUserGalleryBlockProps
>
=
(
props
):
React
.
ReactElement
=>
{
const
{
user
}
=
useSelector
((
state
:
AppState
)
=>
state
.
users
,
shallowEqual
)
return
(
<
div
style=
{
{
margin
:
'10px'
}
}
>
<
Card
sx=
{
{
maxWidth
:
350
}
}
>
<
CardActionArea
onClick=
{
props
.
showFullImage
}
>
<
CardMedia
component=
"img"
width=
"200"
image=
{
import
.
meta
.
env
.
VITE_BASE_URL
+
'uploads/'
+
props
.
photo
.
photo
}
onError
=
{(
e
)
=
>
{
e
.
currentTarget
.
src
=
image_not_found
}
}
alt=
{
props
.
photo
.
title
+
'image'
}
/
>
<
CardContent
style=
{
{
margin
:
'5px'
,
padding
:
'5px'
,
marginBottom
:
0
}
}
>
<
Typography
gutterBottom
variant=
"h5"
component=
"div"
style=
{
{
marginBottom
:
0
}
}
>
{
props
.
photo
.
title
}
</
Typography
>
</
CardContent
>
</
CardActionArea
>
{
user
?.
_id
===
props
.
photo
.
user
.
_id
?
<
CardActions
>
<
Button
onClick=
{
props
.
deletePhoto
}
variant=
"outlined"
color=
"error"
>
Delete
</
Button
>
</
CardActions
>
:
null
}
</
Card
>
</
div
>
);
}
export default PhotoUserGalleryBlock
\ No newline at end of file
frontend/src/containers/AddForm/AddForm.module.css
0 → 100644
View file @
bba25780
.file_input
{
color
:
white
;
border-bottom
:
1px
solid
red
;
max-width
:
200px
;
cursor
:
pointer
;
transition
:
0.2s
;
border-radius
:
5px
;
color
:
black
;
}
.file_input
:hover
{
color
:
rgb
(
255
,
255
,
255
);
background-color
:
rgb
(
0
,
0
,
0
);
color
:
white
;
}
.filename
{
background-color
:
black
;
color
:
white
;
}
.add_btn
{
max-width
:
200px
;
width
:
100%
;
height
:
50px
;
border-radius
:
7px
;
margin-top
:
35px
;
transition
:
0.2s
;
cursor
:
pointer
;
font-weight
:
bold
;
}
.add_btn
:hover
{
background
:
rgb
(
38
,
164
,
38
);
}
\ No newline at end of file
frontend/src/containers/AddForm/AddForm.tsx
0 → 100644
View file @
bba25780
import
{
shallowEqual
}
from
"react-redux"
import
{
Button
,
TextField
}
from
'@mui/material'
;
import
Box
from
'@mui/material/Box'
;
import
Modal
from
'@mui/material/Modal'
;
import
{
AppDispatch
,
AppState
,
useAppDispatch
}
from
"../../store/store"
import
{
useSelector
}
from
"react-redux"
import
Preloader
from
"../../components/UI/Preloader/Preloader"
import
{
ChangeEvent
,
FormEvent
,
useEffect
,
useState
}
from
"react"
import
IPhotoDto
from
"../../interfaces/IPhotoDto"
import
{
addPhoto
}
from
"../../store/photos/photos.slice"
import
styles
from
'./AddForm.module.css'
const
AddForm
:
React
.
FunctionComponent
=
():
React
.
ReactElement
=>
{
const
{
loadingPhotos
}
=
useSelector
((
state
:
AppState
)
=>
state
.
photos
,
shallowEqual
)
const
[
buttonDisabled
,
setButtonDisabled
]
=
useState
<
boolean
>
(
true
)
const
[
fileName
,
setFileName
]
=
useState
<
string
>
(
''
)
const
[
photoDto
,
setPhotoDto
]
=
useState
<
IPhotoDto
>
({
title
:
''
,
photo
:
undefined
})
const
dispatch
:
AppDispatch
=
useAppDispatch
()
const
inputHandler
=
(
e
:
ChangeEvent
<
HTMLInputElement
>
):
void
=>
{
setPhotoDto
(
prevState
=>
{
return
{...
prevState
,
[
e
.
target
.
name
]:
e
.
target
.
value
}
})
}
const
inputFileHandler
=
(
e
:
ChangeEvent
<
HTMLInputElement
>
):
void
=>
{
setPhotoDto
(
prevState
=>
{
return
{...
prevState
,
photo
:
e
.
target
.
files
?
e
.
target
.
files
[
0
]
:
undefined
}
})
setFileName
(
e
.
target
.
files
&&
e
.
target
.
files
[
0
]
?
e
.
target
.
files
[
0
].
name
:
''
)
}
const
checkButton
=
()
=>
{
if
(
photoDto
.
title
.
trim
()
===
''
||
!
photoDto
.
photo
){
setButtonDisabled
(
true
)
}
else
{
setButtonDisabled
(
false
)
}
}
const
submitHandler
=
(
e
:
FormEvent
):
void
=>
{
e
.
preventDefault
()
const
formData
=
new
FormData
()
Object
.
keys
(
photoDto
).
forEach
((
key
:
string
)
=>
{
//@ts-ignore
formData
.
append
(
key
,
photoDto
[
key
])
})
dispatch
(
addPhoto
(
formData
))
setPhotoDto
({
title
:
''
,
photo
:
undefined
})
setFileName
(
''
)
}
useEffect
(()
=>
{
checkButton
()
},[
photoDto
])
return
(
<
div
>
{
loadingPhotos
?
<
Preloader
/>
:
null
}
<
h1
>
Add new photo
</
h1
>
<
form
onSubmit=
{
submitHandler
}
style=
{
{
margin
:
'10px'
,
padding
:
'20px'
,
background
:
'#e0e0e0'
,
borderRadius
:
'5px'
,
display
:
'flex'
,
flexDirection
:
'column'
}
}
>
<
TextField
id=
"outlined-basic"
label=
"Photo title"
variant=
"outlined"
style=
{
{
marginBottom
:
'20px'
}
}
value=
{
photoDto
.
title
}
name=
'title'
onChange=
{
inputHandler
}
autoComplete=
'off'
/>
<
label
style=
{
{
height
:
'auto'
,
margin
:
'10px'
,
maxWidth
:
'30%'
,
display
:
'flex'
,
flexDirection
:
'column'
}
}
>
<
input
style=
{
{
display
:
"none"
}
}
name=
{
'photo'
}
type=
"file"
placeholder=
"Image"
accept=
".png, .jpg, .jpeg"
onChange=
{
inputFileHandler
}
/>
<
h1
className=
{
styles
.
file_input
}
style=
{
{
margin
:
'0'
}
}
>
Choose file
</
h1
>
<
span
className=
{
styles
.
filename
}
>
{
fileName
}
</
span
>
</
label
>
<
button
disabled=
{
buttonDisabled
}
className=
{
styles
.
add_btn
}
>
SEND
</
button
>
</
form
>
</
div
>
)
}
export
default
AddForm
\ No newline at end of file
frontend/src/containers/AuthorizeForm/AuthorizeForm.module.css
0 → 100644
View file @
bba25780
.AuthorizeForm
{
max-width
:
600px
;
margin
:
0
auto
;
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
margin-top
:
100px
;
}
.AuthorizeForm
p
{
margin
:
10px
0
;
}
.AuthorizeForm
form
{
width
:
90%
;
min-height
:
300px
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
padding
:
20px
;
background
:
rgba
(
83
,
26
,
136
,
0.659
);
border-radius
:
5px
;
}
.login_input
{
width
:
80%
;
height
:
40px
;
padding
:
5px
;
border-radius
:
7px
;
border
:
none
;
margin-bottom
:
20px
;
font-family
:
'Kanit'
,
sans-serif
;
}
.toggle
{
cursor
:
pointer
;
display
:
inline-block
;
}
.toggleSwitch
{
margin-top
:
10px
;
display
:
inline-block
;
background
:
#ccc
;
border-radius
:
16px
;
width
:
58px
;
height
:
32px
;
position
:
relative
;
vertical-align
:
middle
;
transition
:
background
0.25s
;
cursor
:
pointer
;
}
.toggleSwitch
:before
,
.toggleSwitch
:after
{
content
:
""
;
}
.toggleSwitch
:before
{
display
:
block
;
background
:
linear-gradient
(
to
bottom
,
#fff
0%
,
#eee
100%
);
border-radius
:
50%
;
box-shadow
:
0
0
0
1px
rgba
(
0
,
0
,
0
,
0.25
);
width
:
24px
;
height
:
24px
;
position
:
absolute
;
top
:
4px
;
left
:
4px
;
transition
:
left
0.25s
;
}
.toggle
:hover
.toggleSwitch
:before
{
background
:
linear-gradient
(
to
bottom
,
#fff
0%
,
#fff
100%
);
box-shadow
:
0
0
0
1px
rgba
(
0
,
0
,
0
,
0.5
);
}
.toggleCheckbox
:checked
+
.toggleSwitch
{
background
:
#0d893f
;
}
.toggleCheckbox
:checked
+
.toggleSwitch
:before
{
left
:
30px
;
}
.toggleCheckbox
{
position
:
absolute
;
visibility
:
hidden
;
}
.toggleLabel
{
margin-left
:
5px
;
position
:
relative
;
top
:
2px
;
}
.login_btn
{
margin
:
0
auto
;
margin-top
:
30px
;
width
:
170px
;
height
:
50px
;
}
.login_btn
:hover
{
background-color
:
#56c080
;
border
:
none
;
}
frontend/src/containers/AuthorizeForm/AuthorizeForm.tsx
0 → 100644
View file @
bba25780
import
{
FormEvent
,
useEffect
,
useState
}
from
'react'
import
styles
from
'./AuthorizeForm.module.css'
import
IUserCreateDto
from
'../../interfaces/IUserCreateDto'
import
{
AppDispatch
,
AppState
,
useAppDispatch
}
from
'../../store/store'
import
{
useLocation
,
useNavigate
}
from
'react-router-dom'
import
{
shallowEqual
,
useSelector
}
from
'react-redux'
import
Preloader
from
'../../components/UI/Preloader/Preloader'
import
{
createUser
,
hideMessage
,
loginUser
}
from
'../../store/user/user.slice'
import
Alert
from
'@mui/material/Alert'
;
const
AuthorizeForm
:
React
.
FunctionComponent
=
():
React
.
ReactElement
=>
{
const
navigate
=
useNavigate
()
const
dispatch
:
AppDispatch
=
useAppDispatch
()
const
{
isAuth
,
messageUser
,
loadingUser
}
=
useSelector
((
state
:
AppState
)
=>
state
.
users
,
shallowEqual
)
const
location
=
useLocation
()
const
[
userValues
,
setUserValues
]
=
useState
<
IUserCreateDto
>
({
username
:
''
,
password
:
''
})
const
[
buttonDisabled
,
setButtonDisabled
]
=
useState
<
boolean
>
(
true
)
const
[
isLoginUser
,
setIsLoginUser
]
=
useState
<
boolean
>
(
true
)
const
inputHandler
=
(
e
:
React
.
ChangeEvent
<
HTMLInputElement
>
):
void
=>
{
setUserValues
(
prevState
=>
{
return
{...
prevState
,
[
e
.
target
.
name
]:
e
.
target
.
value
}
})
}
const
toggleChangeHandler
=
()
=>
{
setIsLoginUser
(
!
isLoginUser
)
}
const
submitHandler
=
async
(
e
:
FormEvent
)
=>
{
e
.
preventDefault
()
if
(
isLoginUser
){
await
dispatch
(
createUser
(
userValues
))
}
else
{
await
dispatch
(
loginUser
(
userValues
))
}
}
useEffect
(()
=>
{
checkButton
()
},[
userValues
])
useEffect
(()
=>
{
if
(
isAuth
){
navigate
(
location
.
state
?.
from
?
location
.
state
.
from
:
'/'
)
}
},
[
isAuth
])
const
checkButton
=
()
=>
{
if
(
userValues
.
username
.
trim
()
===
''
||
userValues
.
password
.
trim
()
===
''
){
setButtonDisabled
(
true
)
}
else
{
setButtonDisabled
(
false
)
}
}
useEffect
(()
=>
{
dispatch
(
hideMessage
())
},
[])
return
(
<
div
className=
{
styles
.
AuthorizeForm
}
>
{
loadingUser
?
<
Preloader
/>
:
null
}
<
form
onSubmit=
{
submitHandler
}
>
{
messageUser
.
trim
()
!==
''
?
<
Alert
variant=
"filled"
severity=
"error"
>
{
messageUser
}
</
Alert
>
:
null
}
<
p
>
{
isLoginUser
?
'Login user'
:
'Authorize user'
}
</
p
>
<
p
>
Username:
</
p
>
<
input
className=
{
styles
.
login_input
}
placeholder=
'username...'
name=
{
'username'
}
autoComplete=
'off'
value=
{
userValues
.
username
}
onChange=
{
inputHandler
}
/>
<
p
>
Password:
</
p
>
<
input
className=
{
styles
.
login_input
}
placeholder=
'password...'
name=
{
'password'
}
type=
'password'
autoComplete=
'off'
value=
{
userValues
.
password
}
onChange=
{
inputHandler
}
/>
<
label
style=
{
{
display
:
'flex'
,
alignItems
:
'center'
}
}
>
<
input
className=
{
styles
.
toggleCheckbox
}
type=
"checkbox"
checked=
{
isLoginUser
}
onChange=
{
toggleChangeHandler
}
/>
<
div
className=
{
styles
.
toggleSwitch
}
></
div
>
<
span
className=
{
styles
.
toggleLabel
}
>
New user?
</
span
>
</
label
>
<
button
className=
{
styles
.
login_btn
}
disabled=
{
buttonDisabled
}
>
Authorize
</
button
>
</
form
>
</
div
>
)
}
export
default
AuthorizeForm
frontend/src/containers/ErrorPage/ErrorPage.tsx
0 → 100644
View file @
bba25780
import
React
from
'react'
;
import
{
Box
,
Button
,
Typography
}
from
'@mui/material'
;
import
{
useNavigate
}
from
'react-router-dom'
;
const
ErrorPage
:
React
.
FunctionComponent
=
():
React
.
ReactElement
=>
{
const
navigate
=
useNavigate
()
const
goToHome
=
()
=>
{
navigate
(
'/'
)
}
return
(
<
Box
sx=
{
{
display
:
'flex'
,
justifyContent
:
'center'
,
alignItems
:
'center'
,
flexDirection
:
'column'
,
minHeight
:
'100vh'
}
}
>
<
Typography
variant=
"h1"
style=
{
{
color
:
'white'
}
}
>
404
</
Typography
>
<
Typography
variant=
"h6"
style=
{
{
color
:
'white'
}
}
>
The page you’re looking for doesn’t exist.
</
Typography
>
<
Button
onClick=
{
goToHome
}
variant=
"contained"
>
Back Home
</
Button
>
</
Box
>
);
}
export
default
ErrorPage
\ No newline at end of file
frontend/src/containers/MainPage/MainPage.module.css
0 → 100644
View file @
bba25780
.main_page_container
{
max-width
:
1400px
;
width
:
100%
;
margin
:
0
auto
;
padding
:
20px
;
margin-top
:
100px
;
display
:
flex
;
justify-content
:
space-between
;
flex-wrap
:
wrap
;
}
\ No newline at end of file
frontend/src/containers/MainPage/MainPage.tsx
0 → 100644
View file @
bba25780
import
{
useEffect
,
useState
}
from
'react'
import
{
AppDispatch
,
AppState
,
useAppDispatch
}
from
'../../store/store'
import
styles
from
'./MainPage.module.css'
import
{
getAllPhotos
,
setTargetedUser
}
from
'../../store/photos/photos.slice'
import
{
shallowEqual
,
useSelector
}
from
'react-redux'
import
IPhoto
from
'../../interfaces/IPhoto'
import
PhotoBlock
from
'../../components/PhotoBlock/PhotoBlock'
import
Backdrop
from
'@mui/material/Backdrop'
;
import
Box
from
'@mui/material/Box'
;
import
Modal
from
'@mui/material/Modal'
;
import
Fade
from
'@mui/material/Fade'
;
import
{
useNavigate
}
from
'react-router-dom'
import
{
CardMedia
}
from
'@mui/material'
import
image_not_found
from
'../../assets/image_not_found.png'
import
IUser
from
'../../interfaces/IUser'
import
Preloader
from
'../../components/UI/Preloader/Preloader'
const
modalStyles
=
{
position
:
'absolute'
as
'absolute'
,
top
:
'50%'
,
left
:
'50%'
,
transform
:
'translate(-50%, -50%)'
,
width
:
700
,
bgcolor
:
'background.paper'
,
border
:
'2px solid #000'
,
boxShadow
:
24
,
p
:
1
,
};
const
MainPage
:
React
.
FunctionComponent
=
():
React
.
ReactElement
=>
{
const
[
open
,
setOpen
]
=
useState
<
boolean
>
(
false
);
const
[
fullImageSrc
,
setFullImageSrc
]
=
useState
<
string
>
(
''
)
const
handleClose
=
()
=>
setOpen
(
false
);
const
dispatch
:
AppDispatch
=
useAppDispatch
()
const
{
photos
,
loadingPhotos
}
=
useSelector
((
state
:
AppState
)
=>
state
.
photos
,
shallowEqual
)
const
navigate
=
useNavigate
()
const
goToUserPageHandler
=
(
user
:
IUser
)
=>
{
dispatch
(
setTargetedUser
(
user
))
navigate
(
`/user/
${
user
.
_id
}
`
)
}
const
showFullImageHandler
=
(
src
:
string
)
=>
{
setFullImageSrc
(
src
)
setOpen
(
true
)
}
useEffect
(()
=>
{
dispatch
(
getAllPhotos
())
},
[])
return
(
<
div
className=
{
styles
.
main_page_container
}
>
{
loadingPhotos
?
<
Preloader
/>
:
null
}
<
Modal
aria
-
labelledby=
"transition-modal-title"
aria
-
describedby=
"transition-modal-description"
open=
{
open
}
onClose=
{
handleClose
}
closeAfterTransition
slots=
{
{
backdrop
:
Backdrop
}
}
slotProps=
{
{
backdrop
:
{
timeout
:
500
,
},
}
}
>
<
Fade
in=
{
open
}
>
<
Box
sx=
{
modalStyles
}
>
<
CardMedia
component=
"img"
style=
{
{
maxWidth
:
'100%'
,
height
:
'auto'
,
maxHeight
:
'70vh'
}
}
image=
{
import
.
meta
.
env
.
VITE_BASE_URL
+
'uploads/'
+
fullImageSrc
}
alt=
"full image"
onError
=
{(
e
)
=
>
{
e
.
currentTarget
.
src
=
image_not_found
}
}
/
>
</
Box
>
</
Fade
>
</
Modal
>
{
photos
.
length
?
photos
.
map
((
photo
:
IPhoto
)
=>
{
return
<
PhotoBlock
key=
{
photo
.
_id
}
photo=
{
photo
}
goToUserPage=
{
()
=>
{
goToUserPageHandler
(
photo
.
user
)}
}
showFullImage=
{
()
=>
{
showFullImageHandler
(
photo
.
photo
)}
}
/>
}):
<
h1
>
Gallery
is
empty
<
/h1
>
}
</
div
>
)
}
export default MainPage
\ No newline at end of file
frontend/src/containers/UserGallery/UserGallery.tsx
0 → 100644
View file @
bba25780
import
{
useEffect
,
useState
}
from
'react'
import
{
AppDispatch
,
AppState
,
useAppDispatch
}
from
'../../store/store'
import
styles
from
'./MainPage.module.css'
import
{
deletePhotoById
,
getAllPhotos
,
getPhotosByUserId
,
setTargetedUser
}
from
'../../store/photos/photos.slice'
import
{
shallowEqual
,
useSelector
}
from
'react-redux'
import
IPhoto
from
'../../interfaces/IPhoto'
import
PhotoBlock
from
'../../components/PhotoBlock/PhotoBlock'
import
Backdrop
from
'@mui/material/Backdrop'
;
import
Box
from
'@mui/material/Box'
;
import
Modal
from
'@mui/material/Modal'
;
import
Fade
from
'@mui/material/Fade'
;
import
{
useNavigate
,
useParams
}
from
'react-router-dom'
import
{
Button
,
CardMedia
}
from
'@mui/material'
import
image_not_found
from
'../../assets/image_not_found.png'
import
IUser
from
'../../interfaces/IUser'
import
PhotoUserGalleryBlock
from
'../../components/PhotoUserGalleryBlock/PhotoUserGalleryBlock'
const
modalStyles
=
{
position
:
'absolute'
as
'absolute'
,
top
:
'50%'
,
left
:
'50%'
,
transform
:
'translate(-50%, -50%)'
,
width
:
700
,
bgcolor
:
'background.paper'
,
border
:
'2px solid #000'
,
boxShadow
:
24
,
p
:
1
,
};
export
const
UserGallery
:
React
.
FunctionComponent
=
():
React
.
ReactElement
=>
{
const
[
open
,
setOpen
]
=
useState
<
boolean
>
(
false
);
const
[
fullImageSrc
,
setFullImageSrc
]
=
useState
<
string
>
(
''
)
const
handleClose
=
()
=>
setOpen
(
false
);
const
params
=
useParams
()
const
dispatch
:
AppDispatch
=
useAppDispatch
()
const
{
photosByUser
,
targetedUser
}
=
useSelector
((
state
:
AppState
)
=>
state
.
photos
,
shallowEqual
)
const
{
user
}
=
useSelector
((
state
:
AppState
)
=>
state
.
users
,
shallowEqual
)
const
navigate
=
useNavigate
()
const
showFullImageHandler
=
(
src
:
string
)
=>
{
setFullImageSrc
(
src
)
setOpen
(
true
)
}
const
deletePhotoHandler
=
(
id
:
string
)
=>
{
dispatch
(
deletePhotoById
(
id
))
}
const
goToAddForm
=
()
=>
{
navigate
(
'/add-photo'
)
}
useEffect
(()
=>
{
if
(
params
.
id
)
dispatch
(
getPhotosByUserId
(
params
.
id
))
},
[])
return
(
<
div
style=
{
{
maxWidth
:
'1400px'
,
width
:
'100%'
,
margin
:
'0 auto'
}
}
>
<
Modal
aria
-
labelledby=
"transition-modal-title"
aria
-
describedby=
"transition-modal-description"
open=
{
open
}
onClose=
{
handleClose
}
closeAfterTransition
slots=
{
{
backdrop
:
Backdrop
}
}
slotProps=
{
{
backdrop
:
{
timeout
:
500
,
},
}
}
>
<
Fade
in=
{
open
}
>
<
Box
sx=
{
modalStyles
}
>
<
CardMedia
component=
"img"
style=
{
{
maxWidth
:
'100%'
,
height
:
'auto'
,
maxHeight
:
'70vh'
}
}
image=
{
import
.
meta
.
env
.
VITE_BASE_URL
+
'uploads/'
+
fullImageSrc
}
alt=
"full image"
onError
=
{(
e
)
=
>
{
e
.
currentTarget
.
src
=
image_not_found
}
}
/
>
</
Box
>
</
Fade
>
</
Modal
>
<
div
style=
{
{
display
:
'flex'
,
justifyContent
:
'space-between'
,
alignItems
:
'center'
}
}
>
<
h1
>
{
targetedUser
.
_id
===
user
?.
_id
?
'This is your page'
:
`${targetedUser.username}'s gallery`
}
</
h1
>
{
targetedUser
.
_id
===
user
?.
_id
?
<
Button
variant=
"contained"
size=
'medium'
onClick=
{
goToAddForm
}
>
Add Photo
</
Button
>
:
null
}
</
div
>
<
div
style=
{
{
display
:
'flex'
,
justifyContent
:
'space-between'
,
flexWrap
:
'wrap'
}
}
>
{
photosByUser
&&
photosByUser
.
length
?
photosByUser
.
map
((
photo
:
IPhoto
)
=>
{
return
<
PhotoUserGalleryBlock
key=
{
photo
.
_id
}
photo=
{
photo
}
showFullImage=
{
()
=>
showFullImageHandler
(
photo
.
photo
)
}
deletePhoto=
{
()
=>
{
deletePhotoHandler
(
photo
.
_id
)}
}
/>
}):
<
h1
>
Gallery
is
empty
<
/h1
>
}
</
div
>
</
div
>
)
}
export default UserGallery
\ No newline at end of file
frontend/src/enum/EStatuses.ts
View file @
bba25780
export
enum
EStatuses
{
export
enum
EStatuses
{
SUCCESS
=
'S
uccess
'
,
SUCCESS
=
'S
UCCESS
'
,
FAILURE
=
'F
ailure
'
FAILURE
=
'F
AILURE
'
}
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment