Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
H
hw87AlenBolatov
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
Болатов Ален
hw87AlenBolatov
Commits
140b0572
Commit
140b0572
authored
Apr 10, 2023
by
Болатов Ален
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
#14
added admin and user roles and showing unpublished only to admins
parent
43a90b80
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
242 additions
and
29 deletions
+242
-29
App.tsx
frontend/src/App.tsx
+3
-1
artistApi.ts
frontend/src/api/artistApi.ts
+14
-0
Header.tsx
frontend/src/components/Header.tsx
+31
-3
Layout.tsx
frontend/src/components/Layout/Layout.tsx
+1
-1
AddArtist.tsx
frontend/src/containers/AddArtist.tsx
+99
-0
HomePage.tsx
frontend/src/containers/HomePage.tsx
+46
-19
Login.tsx
frontend/src/containers/Login.tsx
+0
-1
artistSlice.ts
frontend/src/features/artist/artistSlice.ts
+29
-1
userSlice.ts
frontend/src/features/user/userSlice.ts
+16
-2
IArtist.ts
frontend/src/interfaces/IArtist.ts
+2
-1
IUser.ts
frontend/src/interfaces/IUser.ts
+1
-0
No files found.
frontend/src/App.tsx
View file @
140b0572
...
...
@@ -6,8 +6,9 @@ import Albums from './containers/Albums';
import
Tracks
from
'./containers/Tracks'
;
import
Login
from
'./containers/Login'
;
import
TrackHistory
from
'./containers/TrackHistory'
;
import
AddArtist
from
'./containers/AddArtist'
;
const
App
=
()
=>
{
const
App
:
React
.
FunctionComponent
=
():
React
.
ReactElement
=>
{
return
(
<
BrowserRouter
>
<
Routes
>
...
...
@@ -17,6 +18,7 @@ const App = () => {
<
Route
path=
"tracks"
element=
{
<
Tracks
/>
}
/>
<
Route
path=
"login"
element=
{
<
Login
/>
}
/>
<
Route
path=
"track-history"
element=
{
<
TrackHistory
/>
}
/>
<
Route
path=
"add-artist"
element=
{
<
AddArtist
/>
}
/>
</
Route
>
</
Routes
>
</
BrowserRouter
>
...
...
frontend/src/api/artistApi.ts
View file @
140b0572
import
IArtist
from
'../interfaces/IArtist'
;
import
{
artistInstance
}
from
'./instances'
;
export
const
artistApi
=
{
...
...
@@ -9,4 +10,17 @@ export const artistApi = {
console
.
log
(
err
);
}
},
add
:
async
(
newArtist
:
FormData
)
=>
{
try
{
const
response
=
await
artistInstance
.
post
(
''
,
{
method
:
'post'
,
data
:
newArtist
,
headers
:
{
'Content-Type'
:
'multipart/form-data'
},
});
return
response
.
data
;
}
catch
(
err
:
unknown
)
{
console
.
log
(
err
);
}
},
};
frontend/src/components/Header.tsx
View file @
140b0572
import
React
from
'react'
;
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
Link
,
useNavigate
}
from
'react-router-dom'
;
import
{
useAppDispatch
,
useAppSelector
}
from
'../store/hooks'
;
import
{
logout
,
setLogOut
}
from
'../features/user/userSlice'
;
import
{
logout
,
setLogOut
,
setUserLoggedIn
,
setUserRole
,
}
from
'../features/user/userSlice'
;
import
IUser
from
'../interfaces/IUser'
;
const
Header
=
()
=>
{
const
navigate
=
useNavigate
();
const
dispatch
=
useAppDispatch
();
const
{
userLoggedIn
}
=
useAppSelector
((
state
)
=>
state
.
user
);
const
{
userLoggedIn
,
userRole
}
=
useAppSelector
((
state
)
=>
state
.
user
);
const
handleLogOut
=
()
=>
{
const
token
:
string
|
null
=
window
.
sessionStorage
.
getItem
(
'token'
);
dispatch
(
setLogOut
());
dispatch
(
setUserRole
(
'User'
));
if
(
token
)
{
dispatch
(
logout
(
token
));
}
navigate
(
'/'
);
};
useEffect
(()
=>
{
const
loggedInUser
=
localStorage
.
getItem
(
'user'
);
if
(
loggedInUser
)
{
dispatch
(
setUserLoggedIn
(
true
));
const
foundUser
:
IUser
=
JSON
.
parse
(
loggedInUser
);
dispatch
(
setUserRole
(
foundUser
.
role
));
}
},
[
dispatch
]);
return
(
<
div
>
{
!
userLoggedIn
?
(
...
...
@@ -33,6 +48,19 @@ const Header = () => {
>
Track History
</
Link
>
<
Link
className=
"bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
to=
{
'add-artist'
}
>
Add Artist
</
Link
>
<
Link
className=
"bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
to=
{
'/'
}
>
HomePage
</
Link
>
<
a
onClick=
{
handleLogOut
}
href=
"#"
...
...
frontend/src/components/Layout/Layout.tsx
View file @
140b0572
...
...
@@ -4,7 +4,7 @@ import Header from '../Header';
const
Layout
:
React
.
FunctionComponent
=
():
React
.
ReactElement
=>
{
return
(
<
div
className=
"container mx-auto p
x-96 p
t-4"
>
<
div
className=
"container mx-auto pt-4"
>
<
Header
/>
<
Outlet
/>
</
div
>
...
...
frontend/src/containers/AddArtist.tsx
0 → 100644
View file @
140b0572
import
React
,
{
ChangeEvent
,
useState
}
from
'react'
;
import
IArtist
from
'../interfaces/IArtist'
;
import
{
useAppDispatch
}
from
'../store/hooks'
;
import
{
addArtist
}
from
'../features/artist/artistSlice'
;
const
AddArtist
:
React
.
FunctionComponent
=
():
React
.
ReactElement
=>
{
const
[
newArtist
,
setNewArtist
]
=
useState
<
IArtist
>
({
info
:
''
,
name
:
''
,
}
as
IArtist
);
const
[
file
,
setFile
]
=
useState
<
File
>
();
const
dispatch
=
useAppDispatch
();
const
handleChange
=
(
e
:
ChangeEvent
<
HTMLInputElement
|
HTMLTextAreaElement
>
)
=>
setNewArtist
((
prevState
)
=>
({
...
prevState
,
[
e
.
target
.
name
]:
e
.
target
.
value
,
}));
const
fileHandler
=
(
event
:
React
.
FormEvent
)
=>
{
const
files
=
(
event
.
target
as
HTMLInputElement
).
files
;
if
(
files
&&
files
.
length
>
0
)
{
setFile
(
files
[
0
]);
}
};
const
handleSubmit
=
(
e
:
React
.
FormEvent
)
=>
{
e
.
preventDefault
();
const
artist
=
new
FormData
();
artist
.
append
(
'name'
,
newArtist
.
name
);
artist
.
append
(
'info'
,
newArtist
.
info
);
console
.
log
(
artist
);
artist
.
append
(
'photo'
,
file
!
);
dispatch
(
addArtist
(
artist
));
};
return
(
<
form
encType=
"multipart/form-data"
onSubmit=
{
handleSubmit
}
>
<
label
htmlFor=
"small-input"
className=
"block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>
Small input
</
label
>
<
input
required
onChange=
{
handleChange
}
value=
{
newArtist
.
name
}
placeholder=
"Artist"
name=
"name"
type=
"text"
id=
"small-input"
className=
"block w-full p-2 text-gray-900 border border-gray-300 rounded-lg bg-gray-50 sm:text-xs focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 "
/>
<
label
htmlFor=
"message"
className=
"block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>
Your message
</
label
>
<
textarea
required
onChange=
{
handleChange
}
value=
{
newArtist
.
info
}
id=
"message"
name=
"info"
className=
"block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder=
"Info about artist"
></
textarea
>
<
label
className=
"block mb-2 text-sm font-medium text-gray-900 dark:text-white"
htmlFor=
"file_input"
>
Upload file
</
label
>
<
input
required
onChange=
{
fileHandler
}
className=
"block w-full text-sm text-gray-900 border border-gray-300 rounded-lg cursor-pointer bg-gray-50 dark:text-gray-400 focus:outline-none dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400"
aria
-
describedby=
"file_input_help"
id=
"file_input"
type=
"file"
/>
<
p
className=
"mt-1 text-sm text-gray-500 dark:text-gray-300"
id=
"file_input_help"
>
SVG, PNG, JPG or GIF (MAX. 800x400px).
</
p
>
<
button
type=
"submit"
>
Submit
</
button
>
</
form
>
);
};
export
default
AddArtist
;
frontend/src/containers/HomePage.tsx
View file @
140b0572
...
...
@@ -3,22 +3,17 @@ import {getArtists} from '../features/artist/artistSlice';
import
{
useAppDispatch
,
useAppSelector
}
from
'../store/hooks'
;
import
{
useNavigate
}
from
'react-router-dom'
;
import
Preloader
from
'../components/UI/Preloader'
;
import
{
setUserLoggedIn
}
from
'../features/user/userSlice'
;
const
HomePage
:
React
.
FunctionComponent
=
():
React
.
ReactElement
=>
{
const
{
loading
,
artists
}
=
useAppSelector
((
state
)
=>
state
.
artist
);
const
{
userRole
}
=
useAppSelector
((
state
)
=>
state
.
user
);
const
dispatch
=
useAppDispatch
();
const
navigate
=
useNavigate
();
useEffect
(()
=>
{
dispatch
(
getArtists
());
const
loggedInUser
=
localStorage
.
getItem
(
'user'
);
if
(
loggedInUser
)
{
const
foundUser
=
JSON
.
parse
(
loggedInUser
);
dispatch
(
setUserLoggedIn
(
true
));
}
},
[
dispatch
]);
},
[
dispatch
,
navigate
,
userRole
]);
return
(
<
div
>
...
...
@@ -30,18 +25,50 @@ const HomePage: React.FunctionComponent = (): React.ReactElement => {
artists
.
map
((
artist
)
=>
{
return
(
<
div
key=
{
artist
.
_id
}
>
<
img
onClick=
{
()
=>
navigate
(
{
pathname
:
'/albums'
,
search
:
`?artist=${artist._id}`
},
{
state
:
{
artist
:
artist
.
name
}}
)
}
className=
"w-56 h-56 object-cover "
src=
{
`${import.meta.env.VITE_MY_URL}/${artist.photo}`
}
alt=
{
artist
.
name
}
/>
<
span
>
{
artist
.
name
}
</
span
>
{
userRole
===
'Admin'
?
(
<>
<
img
onClick=
{
()
=>
navigate
(
{
pathname
:
'/albums'
,
search
:
`?artist=${artist._id}`
,
},
{
state
:
{
artist
:
artist
.
name
}}
)
}
className=
"w-56 h-56 object-cover "
src=
{
`${import.meta.env.VITE_MY_URL}/${artist.photo}`
}
alt=
{
artist
.
name
}
/>
<
span
className=
"block"
>
{
artist
.
name
}
</
span
>
{
!
artist
.
published
&&
<
p
>
Unpublished
</
p
>
}
{
artist
.
published
?
(
<
button
>
Delete
</
button
>
)
:
(
<
button
>
Publish
</
button
>
)
}
</>
)
:
artist
.
published
?
(
<>
<
img
onClick=
{
()
=>
navigate
(
{
pathname
:
'/albums'
,
search
:
`?artist=${artist._id}`
,
},
{
state
:
{
artist
:
artist
.
name
}}
)
}
className=
"w-56 h-56 object-cover "
src=
{
`${import.meta.env.VITE_MY_URL}/${artist.photo}`
}
alt=
{
artist
.
name
}
/>
<
span
className=
"block"
>
{
artist
.
name
}
</
span
>
<
button
>
Delete
</
button
>
</>
)
:
null
}
</
div
>
);
})
...
...
frontend/src/containers/Login.tsx
View file @
140b0572
...
...
@@ -6,7 +6,6 @@ import {useAppDispatch, useAppSelector} from '../store/hooks';
const
Login
=
()
=>
{
const
dispatch
=
useAppDispatch
();
const
navigate
=
useNavigate
();
const
[
user
,
setUser
]
=
useState
({}
as
IUser
);
const
{
userLoggedIn
}
=
useAppSelector
((
state
)
=>
state
.
user
);
...
...
frontend/src/features/artist/artistSlice.ts
View file @
140b0572
...
...
@@ -3,21 +3,39 @@ import type {PayloadAction} from '@reduxjs/toolkit';
import
{
RootState
}
from
'../../store/store'
;
import
IArtist
from
'../../interfaces/IArtist'
;
import
{
artistApi
}
from
'../../api/artistApi'
;
import
axios
from
'axios'
;
interface
ArtistState
{
artists
:
IArtist
[];
loading
:
boolean
;
unpublishedArtists
:
IArtist
[];
}
const
initialState
:
ArtistState
=
{
artists
:
[],
loading
:
false
,
unpublishedArtists
:
[],
};
export
const
getArtists
=
createAsyncThunk
(
'getArtists'
,
async
()
=>
{
return
await
artistApi
.
get
();
});
export
const
addArtist
=
createAsyncThunk
(
'addArtist'
,
async
(
newArtist
:
FormData
)
=>
{
const
response
=
await
axios
({
method
:
'post'
,
url
:
`
${
import
.
meta
.
env
.
VITE_MY_URL
}
/artists`
,
data
:
newArtist
,
headers
:
{
'Content-Type'
:
`multipart/form-data; `
,
},
});
return
response
.
data
;
}
);
export
const
artistSlice
=
createSlice
({
name
:
'artist'
,
initialState
,
...
...
@@ -36,7 +54,17 @@ export const artistSlice = createSlice({
state
.
artists
=
payload
;
state
.
loading
=
false
;
}
);
)
.
addCase
(
addArtist
.
pending
,
(
state
,
action
)
=>
{
state
.
loading
=
true
;
})
.
addCase
(
addArtist
.
rejected
,
(
state
,
action
)
=>
{
state
.
loading
=
false
;
})
.
addCase
(
addArtist
.
fulfilled
,
(
state
,
action
)
=>
{
state
.
loading
=
false
;
console
.
log
(
action
.
payload
);
});
},
});
...
...
frontend/src/features/user/userSlice.ts
View file @
140b0572
...
...
@@ -7,12 +7,14 @@ interface UserState {
user
:
IUser
;
loading
:
boolean
;
userLoggedIn
:
boolean
;
userRole
:
string
;
}
const
initialState
:
UserState
=
{
user
:
{}
as
IUser
,
loading
:
false
,
userLoggedIn
:
false
,
userRole
:
'User'
,
};
export
const
createUser
=
createAsyncThunk
(
...
...
@@ -67,6 +69,10 @@ export const userSlice = createSlice({
setLogOut
:
(
state
)
=>
{
state
.
userLoggedIn
=
false
;
localStorage
.
setItem
(
'user'
,
''
);
localStorage
.
setItem
(
'userRole'
,
'User'
);
},
setUserRole
:
(
state
,
action
)
=>
{
state
.
userRole
=
action
.
payload
;
},
},
extraReducers
:
(
builder
)
=>
{
...
...
@@ -93,7 +99,12 @@ export const userSlice = createSlice({
state
,
{
payload
,
}:
PayloadAction
<
{
token
:
string
;
username
:
string
;
id
:
string
}
>
}:
PayloadAction
<
{
token
:
string
;
username
:
string
;
id
:
string
;
role
:
string
;
}
>
)
=>
{
state
.
loading
=
false
;
state
.
userLoggedIn
=
true
;
...
...
@@ -102,8 +113,11 @@ export const userSlice = createSlice({
id
:
payload
.
id
,
username
:
payload
.
username
,
token
:
payload
.
token
,
role
:
payload
.
role
,
};
state
.
userRole
=
payload
.
role
;
localStorage
.
setItem
(
'user'
,
JSON
.
stringify
(
user
));
localStorage
.
setItem
(
'userRole'
,
payload
.
role
);
}
)
.
addCase
(
logout
.
fulfilled
,
(
state
,
action
)
=>
{
...
...
@@ -118,6 +132,6 @@ export const userSlice = createSlice({
},
});
export
const
{
setUserLoggedIn
,
setLogOut
}
=
userSlice
.
actions
;
export
const
{
setUserLoggedIn
,
setLogOut
,
setUserRole
}
=
userSlice
.
actions
;
export
default
userSlice
.
reducer
;
frontend/src/interfaces/IArtist.ts
View file @
140b0572
...
...
@@ -2,5 +2,6 @@ export default interface IArtist {
name
:
string
;
photo
:
string
;
info
:
string
;
_id
:
string
;
_id
?:
string
;
published
:
boolean
;
}
frontend/src/interfaces/IUser.ts
View file @
140b0572
...
...
@@ -3,4 +3,5 @@ export default interface IUser {
password
?:
string
;
token
?:
string
;
id
?:
string
;
role
?:
string
;
}
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