Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
A
ajs_22_shop_api
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
Нұрасыл Қайратұлы
ajs_22_shop_api
Commits
c3002714
Commit
c3002714
authored
Sep 12, 2025
by
Ли Джен Сеп
💬
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
less 90
parent
cbfe9973
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
665 additions
and
87 deletions
+665
-87
package.json
client/package.json
+8
-1
pnpm-lock.yaml
client/pnpm-lock.yaml
+28
-0
LoginPage.module.css
client/src/app/(auth)/login/LoginPage.module.css
+121
-0
page.tsx
client/src/app/(auth)/login/page.tsx
+76
-0
RegisterPage.module.css
client/src/app/(auth)/registration/RegisterPage.module.css
+121
-0
page.tsx
client/src/app/(auth)/registration/page.tsx
+90
-0
page.tsx
client/src/app/(products)/page.tsx
+5
-4
AppToolbar.tsx
client/src/components/UI/AppToolbar/AppToolbar.tsx
+105
-27
Products.tsx
client/src/containers/Products/Products.tsx
+43
-48
userStore.ts
client/src/features/userStore.ts
+29
-0
axiosClient.ts
client/src/helpers/axiosClient.ts
+3
-3
package.json
server/package.json
+2
-0
pnpm-lock.yaml
server/pnpm-lock.yaml
+11
-0
auth.controller.ts
server/src/auth/auth.controller.ts
+5
-0
auth.service.ts
server/src/auth/auth.service.ts
+16
-2
user.entity.ts
server/src/user/entities/user.entity.ts
+2
-2
No files found.
client/package.json
View file @
c3002714
...
...
@@ -18,7 +18,8 @@
"next"
:
"15.1.0"
,
"react"
:
"^19.0.0"
,
"react-dom"
:
"^19.0.0"
,
"react-redux"
:
"^9.2.0"
"react-redux"
:
"^9.2.0"
,
"zustand"
:
"^5.0.8"
},
"devDependencies"
:
{
"@eslint/eslintrc"
:
"^3"
,
...
...
@@ -28,5 +29,11 @@
"eslint"
:
"^9"
,
"eslint-config-next"
:
"15.1.0"
,
"typescript"
:
"^5"
},
"pnpm"
:
{
"onlyBuiltDependencies"
:
[
"sharp"
,
"unrs-resolver"
]
}
}
client/pnpm-lock.yaml
View file @
c3002714
...
...
@@ -38,6 +38,9 @@ importers:
react-redux
:
specifier
:
^9.2.0
version
:
9.2.0(@types/react@19.1.10)(react@19.1.1)(redux@5.0.1)
zustand
:
specifier
:
^5.0.8
version
:
5.0.8(@types/react@19.1.10)(immer@10.1.1)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1))
devDependencies
:
'
@eslint/eslintrc'
:
specifier
:
^3
...
...
@@ -1955,6 +1958,24 @@ packages:
resolution
:
{
integrity
:
sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
}
engines
:
{
node
:
'
>=10'
}
zustand@5.0.8
:
resolution
:
{
integrity
:
sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==
}
engines
:
{
node
:
'
>=12.20.0'
}
peerDependencies
:
'
@types/react'
:
'
>=18.0.0'
immer
:
'
>=9.0.6'
react
:
'
>=18.0.0'
use-sync-external-store
:
'
>=1.2.0'
peerDependenciesMeta
:
'
@types/react'
:
optional
:
true
immer
:
optional
:
true
react
:
optional
:
true
use-sync-external-store
:
optional
:
true
snapshots
:
'
@babel/code-frame@7.27.1'
:
...
...
@@ -4128,3 +4149,10 @@ snapshots:
yaml@1.10.2
:
{}
yocto-queue@0.1.0
:
{}
zustand@5.0.8(@types/react@19.1.10)(immer@10.1.1)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1))
:
optionalDependencies
:
'
@types/react'
:
19.1.10
immer
:
10.1.1
react
:
19.1.1
use-sync-external-store
:
1.5.0(react@19.1.1)
client/src/app/(auth)/login/LoginPage.module.css
0 → 100644
View file @
c3002714
.wrapper
{
min-height
:
100
dvh
;
display
:
grid
;
place-items
:
center
;
padding
:
24px
;
background
:
#eaf3fc
;
}
.card
{
width
:
100%
;
max-width
:
420px
;
background
:
#ffffff
;
border
:
1px
solid
#cfe0f5
;
border-radius
:
16px
;
padding
:
24px
;
box-shadow
:
0
6px
20px
rgba
(
0
,
0
,
0
,
0.1
);
}
.title
{
margin
:
0
0
16px
0
;
font-size
:
22px
;
font-weight
:
700
;
color
:
#1e3a8a
;
}
.field
{
margin-bottom
:
12px
;
}
.label
{
display
:
block
;
font-size
:
13px
;
color
:
#1e40af
;
margin-bottom
:
6px
;
}
.input
{
width
:
100%
;
background
:
#f0f6ff
;
border
:
1px
solid
#93c5fd
;
color
:
#1e3a8a
;
border-radius
:
10px
;
padding
:
10px
12px
;
outline
:
none
;
transition
:
border-color
0.15s
ease
,
background-color
0.15s
ease
;
}
.input
:focus
{
border-color
:
#2563eb
;
background-color
:
#e0f0ff
;
}
.button
{
width
:
100%
;
margin-top
:
8px
;
padding
:
12px
;
border
:
none
;
border-radius
:
10px
;
background
:
#2563eb
;
color
:
white
;
font-weight
:
600
;
cursor
:
pointer
;
transition
:
background-color
0.2s
ease
;
}
.button
:hover:not
(
:disabled
)
{
background
:
#1d4ed8
;
}
.button
:disabled
{
opacity
:
0.6
;
cursor
:
not-allowed
;
}
.secondary
{
width
:
100%
;
margin-top
:
8px
;
padding
:
10px
;
border-radius
:
10px
;
background
:
transparent
;
border
:
1px
solid
#93c5fd
;
color
:
#1e40af
;
cursor
:
pointer
;
transition
:
background-color
0.2s
ease
;
}
.secondary
:hover
{
background-color
:
#e0f0ff
;
}
.hint
{
text-align
:
center
;
margin-top
:
14px
;
color
:
#1e3a8a
;
font-size
:
14px
;
}
.link
{
color
:
#2563eb
;
text-decoration
:
none
;
}
.link
:hover
{
text-decoration
:
underline
;
}
.error
{
margin
:
6px
0
0
;
color
:
#dc2626
;
font-size
:
12px
;
}
.saved
{
margin-top
:
16px
;
padding
:
12px
;
border-radius
:
10px
;
background
:
#f0f6ff
;
border
:
1px
solid
#93c5fd
;
color
:
#1e3a8a
;
font-size
:
14px
;
}
client/src/app/(auth)/login/page.tsx
0 → 100644
View file @
c3002714
"use client"
;
import
{
FormEvent
,
useState
}
from
"react"
;
import
styles
from
"./LoginPage.module.css"
;
import
Link
from
"next/link"
;
import
{
useUserStore
}
from
"@/features/userStore"
;
import
{
axiosApiClient
}
from
"@/helpers/axiosClient"
;
import
{
useRouter
}
from
"next/navigation"
;
export
default
function
Page
()
{
const
router
=
useRouter
();
const
{
user
,
setUser
}
=
useUserStore
();
const
[
userName
,
setUserName
]
=
useState
(
user
?.
userName
??
""
);
const
[
password
,
setPassword
]
=
useState
(
""
);
const
onSubmit
=
async
(
e
:
FormEvent
)
=>
{
e
.
preventDefault
();
const
newUser
=
{
userName
,
password
,
};
const
{
data
}
=
await
axiosApiClient
.
post
(
"/auth/login"
,
newUser
);
setUser
(
data
);
router
.
push
(
"/"
);
};
return
(
<
main
className=
{
styles
.
wrapper
}
>
<
form
onSubmit=
{
onSubmit
}
className=
{
styles
.
card
}
noValidate
>
<
h1
className=
{
styles
.
title
}
>
Логин
</
h1
>
<
div
className=
{
styles
.
field
}
>
<
label
className=
{
styles
.
label
}
htmlFor=
"userName"
>
Логин
</
label
>
<
input
id=
"name"
className=
{
styles
.
input
}
value=
{
userName
}
onChange=
{
(
e
)
=>
setUserName
(
e
.
target
.
value
)
}
placeholder=
"name"
/>
{
!
userName
&&
<
p
className=
{
styles
.
error
}
>
Введите логин
</
p
>
}
</
div
>
<
div
className=
{
styles
.
field
}
>
<
label
className=
{
styles
.
label
}
htmlFor=
"password"
>
Пароль
</
label
>
<
input
id=
"password"
type=
"password"
className=
{
styles
.
input
}
value=
{
password
}
onChange=
{
(
e
)
=>
setPassword
(
e
.
target
.
value
)
}
placeholder=
"••••••"
/>
{
password
&&
password
.
length
<
6
&&
<
p
className=
{
styles
.
error
}
>
Минимум 6 символов
</
p
>
}
</
div
>
<
button
className=
{
styles
.
button
}
>
Зарегистрироваться
</
button
>
<
p
className=
{
styles
.
hint
}
>
Уже есть аккаунт?
<
Link
href=
"/login"
className=
{
styles
.
link
}
>
Войти
</
Link
>
</
p
>
</
form
>
</
main
>
);
}
client/src/app/(auth)/registration/RegisterPage.module.css
0 → 100644
View file @
c3002714
.wrapper
{
min-height
:
100
dvh
;
display
:
grid
;
place-items
:
center
;
padding
:
24px
;
background
:
#eaf3fc
;
}
.card
{
width
:
100%
;
max-width
:
420px
;
background
:
#ffffff
;
border
:
1px
solid
#cfe0f5
;
border-radius
:
16px
;
padding
:
24px
;
box-shadow
:
0
6px
20px
rgba
(
0
,
0
,
0
,
0.1
);
}
.title
{
margin
:
0
0
16px
0
;
font-size
:
22px
;
font-weight
:
700
;
color
:
#1e3a8a
;
}
.field
{
margin-bottom
:
12px
;
}
.label
{
display
:
block
;
font-size
:
13px
;
color
:
#1e40af
;
margin-bottom
:
6px
;
}
.input
{
width
:
100%
;
background
:
#f0f6ff
;
border
:
1px
solid
#93c5fd
;
color
:
#1e3a8a
;
border-radius
:
10px
;
padding
:
10px
12px
;
outline
:
none
;
transition
:
border-color
0.15s
ease
,
background-color
0.15s
ease
;
}
.input
:focus
{
border-color
:
#2563eb
;
background-color
:
#e0f0ff
;
}
.button
{
width
:
100%
;
margin-top
:
8px
;
padding
:
12px
;
border
:
none
;
border-radius
:
10px
;
background
:
#2563eb
;
color
:
white
;
font-weight
:
600
;
cursor
:
pointer
;
transition
:
background-color
0.2s
ease
;
}
.button
:hover:not
(
:disabled
)
{
background
:
#1d4ed8
;
}
.button
:disabled
{
opacity
:
0.6
;
cursor
:
not-allowed
;
}
.secondary
{
width
:
100%
;
margin-top
:
8px
;
padding
:
10px
;
border-radius
:
10px
;
background
:
transparent
;
border
:
1px
solid
#93c5fd
;
color
:
#1e40af
;
cursor
:
pointer
;
transition
:
background-color
0.2s
ease
;
}
.secondary
:hover
{
background-color
:
#e0f0ff
;
}
.hint
{
text-align
:
center
;
margin-top
:
14px
;
color
:
#1e3a8a
;
font-size
:
14px
;
}
.link
{
color
:
#2563eb
;
text-decoration
:
none
;
}
.link
:hover
{
text-decoration
:
underline
;
}
.error
{
margin
:
6px
0
0
;
color
:
#dc2626
;
font-size
:
12px
;
}
.saved
{
margin-top
:
16px
;
padding
:
12px
;
border-radius
:
10px
;
background
:
#f0f6ff
;
border
:
1px
solid
#93c5fd
;
color
:
#1e3a8a
;
font-size
:
14px
;
}
client/src/app/(auth)/registration/page.tsx
0 → 100644
View file @
c3002714
"use client"
;
import
{
FormEvent
,
useState
}
from
"react"
;
import
styles
from
"./RegisterPage.module.css"
;
import
Link
from
"next/link"
;
import
{
useUserStore
}
from
"@/features/userStore"
;
import
{
axiosApiClient
}
from
"@/helpers/axiosClient"
;
import
{
useRouter
}
from
"next/navigation"
;
export
default
function
Page
()
{
const
router
=
useRouter
();
const
{
user
,
setUser
}
=
useUserStore
();
const
[
userName
,
setUserName
]
=
useState
(
user
?.
userName
??
""
);
const
[
displayName
,
setDisplayName
]
=
useState
(
user
?.
displayName
??
""
);
const
[
password
,
setPassword
]
=
useState
(
""
);
const
onSubmit
=
async
(
e
:
FormEvent
)
=>
{
e
.
preventDefault
();
const
newUser
=
{
userName
,
displayName
,
password
,
};
const
{
data
}
=
await
axiosApiClient
.
post
(
"/auth/register"
,
newUser
);
setUser
(
data
);
router
.
push
(
"/"
);
};
return
(
<
main
className=
{
styles
.
wrapper
}
>
<
form
onSubmit=
{
onSubmit
}
className=
{
styles
.
card
}
noValidate
>
<
h1
className=
{
styles
.
title
}
>
Регистрация
</
h1
>
<
div
className=
{
styles
.
field
}
>
<
label
className=
{
styles
.
label
}
htmlFor=
"userName"
>
Логин
</
label
>
<
input
id=
"name"
className=
{
styles
.
input
}
value=
{
userName
}
onChange=
{
(
e
)
=>
setUserName
(
e
.
target
.
value
)
}
placeholder=
"name"
/>
{
!
userName
&&
<
p
className=
{
styles
.
error
}
>
Введите логин
</
p
>
}
</
div
>
<
div
className=
{
styles
.
field
}
>
<
label
className=
{
styles
.
label
}
htmlFor=
"displayName"
>
Отображаемое имя
</
label
>
<
input
id=
"displayName"
className=
{
styles
.
input
}
value=
{
displayName
}
onChange=
{
(
e
)
=>
setDisplayName
(
e
.
target
.
value
)
}
placeholder=
"displayName"
/>
</
div
>
<
div
className=
{
styles
.
field
}
>
<
label
className=
{
styles
.
label
}
htmlFor=
"password"
>
Пароль
</
label
>
<
input
id=
"password"
type=
"password"
className=
{
styles
.
input
}
value=
{
password
}
onChange=
{
(
e
)
=>
setPassword
(
e
.
target
.
value
)
}
placeholder=
"••••••"
/>
{
password
&&
password
.
length
<
6
&&
<
p
className=
{
styles
.
error
}
>
Минимум 6 символов
</
p
>
}
</
div
>
<
button
className=
{
styles
.
button
}
>
Зарегистрироваться
</
button
>
<
p
className=
{
styles
.
hint
}
>
Уже есть аккаунт?
<
Link
href=
"/login"
className=
{
styles
.
link
}
>
Войти
</
Link
>
</
p
>
</
form
>
</
main
>
);
}
client/src/app/(products)/page.tsx
View file @
c3002714
import
Products
from
'@/containers/Products/Products'
import
Products
from
"@/containers/Products/Products"
;
const
Page
=
()
=>
{
return
<
Products
/>
}
// return <Products />
return
null
;
};
export
default
Page
export
default
Page
;
client/src/components/UI/AppToolbar/AppToolbar.tsx
View file @
c3002714
'use client'
import
{
AppBar
,
Box
,
styled
,
Toolbar
,
Typography
}
from
'@mui/material'
import
Link
from
'next/link'
const
StyledLink
=
styled
(
Link
)(()
=>
({
color
:
'inherit'
,
textDecoration
:
'none'
,
[
'&:hover'
]:
{
color
:
'inherit'
},
}))
const
AppToolbar
=
()
=>
{
return
(
<>
<
AppBar
position=
{
'fixed'
}
>
<
Toolbar
>
<
Typography
variant=
{
'h6'
}
component=
{
StyledLink
}
href=
{
'/'
}
>
Computer parts shop
</
Typography
>
</
Toolbar
>
</
AppBar
>
<
Box
component=
{
Toolbar
}
marginBottom=
{
2
}
/>
</>
)
}
"use client"
;
import
*
as
React
from
"react"
;
import
Link
from
"next/link"
;
import
{
useRouter
}
from
"next/navigation"
;
import
{
useUserStore
}
from
"@/features/userStore"
;
import
{
AppBar
,
Toolbar
,
Typography
,
Box
,
Button
,
Avatar
,
Menu
,
MenuItem
,
Divider
}
from
"@mui/material"
;
import
KeyboardArrowDownIcon
from
"@mui/icons-material/KeyboardArrowDown"
;
import
{
axiosApiClient
}
from
"@/helpers/axiosClient"
;
import
{
useState
}
from
"react"
;
export
default
function
AppToolbar
()
{
const
router
=
useRouter
();
const
{
user
,
clearUser
}
=
useUserStore
();
const
[
anchorEl
,
setAnchorEl
]
=
useState
<
null
|
HTMLElement
>
(
null
);
const
open
=
Boolean
(
anchorEl
);
const
handleOpen
=
(
e
:
React
.
MouseEvent
<
HTMLElement
>
)
=>
setAnchorEl
(
e
.
currentTarget
);
const
handleClose
=
()
=>
setAnchorEl
(
null
);
const
handleProfile
=
()
=>
{
handleClose
();
router
.
push
(
"/profile"
);
};
export
default
AppToolbar
const
handleLogout
=
async
()
=>
{
await
axiosApiClient
.
get
(
"/auth/logout"
);
handleClose
();
clearUser
();
router
.
push
(
"/login"
);
};
return
(
<
AppBar
position=
"sticky"
color=
"primary"
sx=
{
{
bgcolor
:
"#2563eb"
}
}
>
<
Toolbar
sx=
{
{
maxWidth
:
1120
,
mx
:
"auto"
,
width
:
"100%"
}
}
>
<
Typography
variant=
"h6"
component=
{
Link
}
href=
"/"
style=
{
{
textDecoration
:
"none"
,
color
:
"inherit"
}
}
>
Computer parts shop
</
Typography
>
<
Box
sx=
{
{
flexGrow
:
1
}
}
/>
{
!
user
?
(
<
Button
component=
{
Link
}
href=
"/login"
color=
"inherit"
variant=
"outlined"
sx=
{
{
bgcolor
:
"white"
,
color
:
"#2563eb"
,
borderColor
:
"#dbeafe"
,
"&:hover"
:
{
bgcolor
:
"#e0f2fe"
},
}
}
>
Login
</
Button
>
)
:
(
<>
<
Button
color=
"inherit"
onClick=
{
handleOpen
}
aria
-
haspopup=
"menu"
aria
-
controls=
{
open
?
"user-menu"
:
undefined
}
aria
-
expanded=
{
open
?
"true"
:
undefined
}
startIcon=
{
<
Avatar
sx=
{
{
width
:
28
,
height
:
28
}
}
src=
{
`https://api.dicebear.com/8.x/initials/svg?seed=${encodeURIComponent(
user.userName,
)}`
}
alt=
{
user
.
displayName
??
user
.
userName
}
/>
}
endIcon=
{
<
KeyboardArrowDownIcon
/>
}
sx=
{
{
textTransform
:
"none"
,
fontWeight
:
600
,
bgcolor
:
"#1d4ed8"
,
border
:
"1px solid #93c5fd"
,
px
:
1.25
,
"&:hover"
:
{
bgcolor
:
"#1e40af"
},
}
}
>
{
user
.
displayName
??
user
.
userName
}
</
Button
>
<
Menu
id=
"user-menu"
anchorEl=
{
anchorEl
}
open=
{
open
}
onClose=
{
handleClose
}
transformOrigin=
{
{
vertical
:
"top"
,
horizontal
:
"right"
}
}
slotProps=
{
{
paper
:
{
sx
:
{
mt
:
1
,
minWidth
:
200
}
}
}
}
>
<
MenuItem
onClick=
{
handleProfile
}
>
Профиль
</
MenuItem
>
<
Divider
/>
<
MenuItem
onClick=
{
handleLogout
}
sx=
{
{
color
:
"error.main"
}
}
>
Выйти
</
MenuItem
>
</
Menu
>
</>
)
}
</
Toolbar
>
</
AppBar
>
);
}
client/src/containers/Products/Products.tsx
View file @
c3002714
'use client'
"use client"
;
import
{
fetchProducts
}
from
'@/features/productsSlice'
import
{
useAppDispatch
,
useAppSelector
}
from
'@/store/hooks'
import
{
Button
,
Grid2
,
styled
,
Typography
}
from
'@mui/material'
import
Link
from
'next/link'
import
{
useEffect
}
from
'react'
import
{
shallowEqual
}
from
'react-redux'
import
{
ProductItem
}
from
'./ProductItem'
import
{
fetchProducts
}
from
"@/features/productsSlice"
;
import
{
useAppDispatch
,
useAppSelector
}
from
"@/store/hooks"
;
import
{
Button
,
Grid2
,
styled
,
Typography
}
from
"@mui/material"
;
import
Link
from
"next/link"
;
import
{
useEffect
}
from
"react"
;
import
{
shallowEqual
}
from
"react-redux"
;
import
{
ProductItem
}
from
"./ProductItem"
;
const
StyledLink
=
styled
(
Link
)(()
=>
({
color
:
'inherit'
,
textDecoration
:
'none'
,
[
'&:hover'
]:
{
color
:
'inherit'
},
}))
color
:
"inherit"
,
textDecoration
:
"none"
,
[
"&:hover"
]:
{
color
:
"inherit"
},
}))
;
const
Products
=
()
=>
{
const
dispatch
=
useAppDispatch
()
const
{
products
}
=
useAppSelector
(
state
=>
state
.
products
,
shallowEqual
)
useEffect
(()
=>
{
dispatch
(
fetchProducts
())
},
[
dispatch
])
return
(
<
Grid2
container
direction=
{
'column'
}
spacing=
{
2
}
>
<
Grid2
container
direction=
{
'row'
}
justifyContent=
{
'space-between'
}
alignItems=
{
'center'
}
>
<
Grid2
>
<
Typography
variant=
{
'h4'
}
>
Products
</
Typography
>
</
Grid2
>
<
Grid2
>
<
Button
color=
'primary'
component=
{
StyledLink
}
href=
{
'/newProduct'
}
>
Add product
</
Button
>
</
Grid2
>
</
Grid2
>
<
Grid2
container
direction=
{
'row'
}
spacing=
{
1
}
>
{
products
?.
map
(
item
=>
(
<
ProductItem
key=
{
item
.
id
}
product=
{
item
}
/>
))
}
</
Grid2
>
</
Grid2
>
)
}
export
default
Products
const
dispatch
=
useAppDispatch
();
const
{
products
}
=
useAppSelector
((
state
)
=>
state
.
products
,
shallowEqual
);
console
.
log
(
products
);
useEffect
(()
=>
{
dispatch
(
fetchProducts
());
},
[
dispatch
]);
return
(
<
Grid2
container
direction=
{
"column"
}
spacing=
{
2
}
>
<
Grid2
container
direction=
{
"row"
}
justifyContent=
{
"space-between"
}
alignItems=
{
"center"
}
>
<
Grid2
>
<
Typography
variant=
{
"h4"
}
>
Products
</
Typography
>
</
Grid2
>
<
Grid2
>
<
Button
color=
"primary"
component=
{
StyledLink
}
href=
{
"/newProduct"
}
>
Add product
</
Button
>
</
Grid2
>
</
Grid2
>
<
Grid2
container
direction=
{
"row"
}
spacing=
{
1
}
>
{
products
?.
map
((
item
)
=>
(
<
ProductItem
key=
{
item
.
id
}
product=
{
item
}
/>
))
}
</
Grid2
>
</
Grid2
>
);
};
export
default
Products
;
client/src/features/userStore.ts
0 → 100644
View file @
c3002714
import
{
create
}
from
"zustand"
;
import
{
createJSONStorage
,
persist
}
from
"zustand/middleware"
;
export
type
TUser
=
{
userName
:
string
;
displayName
?:
string
;
token
?:
string
;
};
type
TSlice
=
{
user
?:
TUser
|
null
;
setUser
:
(
u
:
TUser
)
=>
void
;
clearUser
:
()
=>
void
;
};
export
const
useUserStore
=
create
<
TSlice
>
()(
persist
(
(
set
)
=>
({
user
:
null
,
setUser
:
(
u
)
=>
set
({
user
:
u
}),
clearUser
:
()
=>
set
({
user
:
null
}),
}),
{
name
:
"user-store"
,
storage
:
createJSONStorage
(()
=>
localStorage
),
},
),
);
client/src/helpers/axiosClient.ts
View file @
c3002714
import
axios
from
'axios'
import
axios
from
"axios"
;
export
const
axiosApiClient
=
axios
.
create
({
baseURL
:
process
.
env
.
SERVER_URL
,
})
baseURL
:
"http://localhost:8000"
,
})
;
server/package.json
View file @
c3002714
...
...
@@ -30,6 +30,7 @@
"bcrypt"
:
"^6.0.0"
,
"class-transformer"
:
"^0.5.1"
,
"class-validator"
:
"^0.14.2"
,
"lodash"
:
"^4.17.21"
,
"multer"
:
"^2.0.2"
,
"pg"
:
"^8.16.3"
,
"reflect-metadata"
:
"^0.2.2"
,
...
...
@@ -47,6 +48,7 @@
"@types/bcrypt"
:
"^6.0.0"
,
"@types/express"
:
"^5.0.0"
,
"@types/jest"
:
"^29.5.14"
,
"@types/lodash"
:
"^4.17.20"
,
"@types/multer"
:
"^2.0.0"
,
"@types/node"
:
"^22.10.7"
,
"@types/supertest"
:
"^6.0.2"
,
...
...
server/pnpm-lock.yaml
View file @
c3002714
...
...
@@ -38,6 +38,9 @@ importers:
class-validator
:
specifier
:
^0.14.2
version
:
0.14.2
lodash
:
specifier
:
^4.17.21
version
:
4.17.21
multer
:
specifier
:
^2.0.2
version
:
2.0.2
...
...
@@ -84,6 +87,9 @@ importers:
'
@types/jest'
:
specifier
:
^29.5.14
version
:
29.5.14
'
@types/lodash'
:
specifier
:
^4.17.20
version
:
4.17.20
'
@types/multer'
:
specifier
:
^2.0.0
version
:
2.0.0
...
...
@@ -1071,6 +1077,9 @@ packages:
'
@types/json-schema@7.0.15'
:
resolution
:
{
integrity
:
sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
}
'
@types/lodash@4.17.20'
:
resolution
:
{
integrity
:
sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==
}
'
@types/methods@1.1.4'
:
resolution
:
{
integrity
:
sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==
}
...
...
@@ -4708,6 +4717,8 @@ snapshots:
'
@types/json-schema@7.0.15'
:
{}
'
@types/lodash@4.17.20'
:
{}
'
@types/methods@1.1.4'
:
{}
'
@types/mime@1.3.5'
:
{}
...
...
server/src/auth/auth.controller.ts
View file @
c3002714
...
...
@@ -21,4 +21,9 @@ export class AuthController {
secret
(@
Headers
(
'Authorization'
)
token
:
string
)
{
return
this
.
authService
.
secret
(
token
);
}
@
Get
(
'logout'
)
async
logout
(@
Headers
(
'Authorization'
)
authToken
:
string
)
{
await
this
.
authService
.
logout
(
authToken
);
}
}
server/src/auth/auth.service.ts
View file @
c3002714
...
...
@@ -5,6 +5,7 @@ import { User } from 'src/user/entities/user.entity';
import
{
Repository
}
from
'typeorm'
;
import
{
LoginAuthDto
}
from
'./dto/login.dto'
;
import
{
RegisterAuthDto
}
from
'./dto/register.dto'
;
import
{
omit
}
from
'lodash'
;
const
SALT_WORK_FACTOR
=
10
;
...
...
@@ -27,7 +28,10 @@ export class AuthService {
user
.
generateToken
();
return
await
this
.
userRepo
.
save
(
user
);
const
userWithToken
=
await
this
.
userRepo
.
save
(
user
);
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call
return
omit
(
userWithToken
,
'password'
);
}
async
login
(
loginAuthDto
:
LoginAuthDto
)
{
...
...
@@ -40,11 +44,21 @@ export class AuthService {
const
isMatch
=
await
user
.
comparePassword
(
loginAuthDto
.
password
);
if
(
!
isMatch
)
throw
new
Error
(
'Invalid userName or password'
);
return
user
;
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call
return
omit
(
user
,
[
'password'
]);
}
async
secret
(
token
:
string
)
{
const
user
=
await
this
.
userRepo
.
findOneBy
({
token
});
return
user
;
}
async
logout
(
token
:
string
)
{
const
existUser
=
await
this
.
userRepo
.
findOneBy
({
token
});
if
(
!
existUser
)
throw
new
NotFoundException
(
'User not found'
);
existUser
.
token
=
null
;
await
this
.
userRepo
.
update
({
token
},
existUser
);
}
}
server/src/user/entities/user.entity.ts
View file @
c3002714
...
...
@@ -15,8 +15,8 @@ export class User {
@
Column
()
password
:
string
;
@
Column
({
nullable
:
true
})
token
:
string
;
@
Column
({
type
:
'varchar'
,
nullable
:
true
})
token
:
string
|
null
;
async
comparePassword
(
password
:
string
):
Promise
<
boolean
>
{
return
await
bcrypt
.
compare
(
password
,
this
.
password
);
...
...
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