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
a9b2cf17
Commit
a9b2cf17
authored
Apr 10, 2023
by
Болатов Ален
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
added album
parent
140b0572
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
598 additions
and
46 deletions
+598
-46
App.tsx
frontend/src/App.tsx
+4
-0
Header.tsx
frontend/src/components/Header.tsx
+12
-0
AddAlbum.tsx
frontend/src/containers/AddAlbum.tsx
+129
-0
AddArtist.tsx
frontend/src/containers/AddArtist.tsx
+5
-2
AddTrack.tsx
frontend/src/containers/AddTrack.tsx
+108
-0
Albums.tsx
frontend/src/containers/Albums.tsx
+62
-17
HomePage.tsx
frontend/src/containers/HomePage.tsx
+34
-6
Tracks.tsx
frontend/src/containers/Tracks.tsx
+57
-13
albumSlice.ts
frontend/src/features/album/albumSlice.ts
+73
-1
artistSlice.ts
frontend/src/features/artist/artistSlice.ts
+47
-1
trackSlice.ts
frontend/src/features/track/trackSlice.ts
+56
-1
userSlice.ts
frontend/src/features/user/userSlice.ts
+5
-1
IAlbum.ts
frontend/src/interfaces/IAlbum.ts
+3
-2
ITrack.ts
frontend/src/interfaces/ITrack.ts
+3
-2
No files found.
frontend/src/App.tsx
View file @
a9b2cf17
...
...
@@ -7,6 +7,8 @@ import Tracks from './containers/Tracks';
import
Login
from
'./containers/Login'
;
import
TrackHistory
from
'./containers/TrackHistory'
;
import
AddArtist
from
'./containers/AddArtist'
;
import
AddAlbum
from
'./containers/AddAlbum'
;
import
AddTrack
from
'./containers/AddTrack'
;
const
App
:
React
.
FunctionComponent
=
():
React
.
ReactElement
=>
{
return
(
...
...
@@ -19,6 +21,8 @@ const App: React.FunctionComponent = (): React.ReactElement => {
<
Route
path=
"login"
element=
{
<
Login
/>
}
/>
<
Route
path=
"track-history"
element=
{
<
TrackHistory
/>
}
/>
<
Route
path=
"add-artist"
element=
{
<
AddArtist
/>
}
/>
<
Route
path=
"add-album"
element=
{
<
AddAlbum
/>
}
/>
<
Route
path=
"add-track"
element=
{
<
AddTrack
/>
}
/>
</
Route
>
</
Routes
>
</
BrowserRouter
>
...
...
frontend/src/components/Header.tsx
View file @
a9b2cf17
...
...
@@ -54,6 +54,18 @@ const Header = () => {
>
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=
{
'add-album'
}
>
Add Album
</
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-track'
}
>
Add Track
</
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=
{
'/'
}
...
...
frontend/src/containers/AddAlbum.tsx
0 → 100644
View file @
a9b2cf17
import
React
,
{
ChangeEvent
,
Fragment
,
useEffect
,
useState
}
from
'react'
;
import
IAlbum
from
'../interfaces/IAlbum'
;
import
{
useAppDispatch
,
useAppSelector
}
from
'../store/hooks'
;
import
{
useNavigate
}
from
'react-router-dom'
;
import
{
getArtists
}
from
'../features/artist/artistSlice'
;
import
{
addAlbum
}
from
'../features/album/albumSlice'
;
const
AddAlbum
:
React
.
FunctionComponent
=
():
React
.
ReactElement
=>
{
const
[
newAlbum
,
setNewAlbum
]
=
useState
<
IAlbum
>
({
name
:
''
,
year
:
''
,
}
as
IAlbum
);
const
[
file
,
setFile
]
=
useState
<
File
>
();
const
dispatch
=
useAppDispatch
();
const
navigate
=
useNavigate
();
const
{
artists
}
=
useAppSelector
((
state
)
=>
state
.
artist
);
let
[
artist
,
setArtist
]
=
useState
<
string
>
(
''
);
useEffect
(()
=>
{
dispatch
(
getArtists
());
},
[
dispatch
]);
const
handleChange
=
(
e
:
ChangeEvent
<
HTMLInputElement
|
HTMLTextAreaElement
>
)
=>
setNewAlbum
((
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
=
async
(
e
:
React
.
FormEvent
)
=>
{
e
.
preventDefault
();
const
album
=
new
FormData
();
album
.
append
(
'name'
,
newAlbum
.
name
);
album
.
append
(
'year'
,
newAlbum
.
year
);
album
.
append
(
'image'
,
file
!
);
album
.
append
(
'artist'
,
artist
);
await
dispatch
(
addAlbum
(
album
));
navigate
(
'/'
);
};
return
(
<
form
className=
"pt-10"
encType=
"multipart/form-data"
onSubmit=
{
handleSubmit
}
>
<
select
required
onChange=
{
(
e
:
React
.
ChangeEvent
<
HTMLSelectElement
>
)
=>
setArtist
(
e
.
target
.
value
)
}
>
<
option
value=
""
>
Select Artist
</
option
>
{
artists
.
map
((
artist
)
=>
(
<
Fragment
key=
{
artist
.
_id
}
>
{
artist
.
published
?
(
<
option
value=
{
artist
.
_id
}
>
{
artist
.
name
}
</
option
>
)
:
null
}
</
Fragment
>
))
}
</
select
>
<
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=
{
newAlbum
.
name
}
placeholder=
"Album"
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
>
<
input
type=
"number"
required
onChange=
{
handleChange
}
value=
{
newAlbum
.
year
}
id=
"message"
name=
"year"
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"
placeholder=
"Year"
></
input
>
<
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
AddAlbum
;
frontend/src/containers/AddArtist.tsx
View file @
a9b2cf17
...
...
@@ -2,6 +2,7 @@ import React, {ChangeEvent, useState} from 'react';
import
IArtist
from
'../interfaces/IArtist'
;
import
{
useAppDispatch
}
from
'../store/hooks'
;
import
{
addArtist
}
from
'../features/artist/artistSlice'
;
import
{
useNavigate
}
from
'react-router-dom'
;
const
AddArtist
:
React
.
FunctionComponent
=
():
React
.
ReactElement
=>
{
const
[
newArtist
,
setNewArtist
]
=
useState
<
IArtist
>
({
...
...
@@ -10,6 +11,7 @@ const AddArtist: React.FunctionComponent = (): React.ReactElement => {
}
as
IArtist
);
const
[
file
,
setFile
]
=
useState
<
File
>
();
const
dispatch
=
useAppDispatch
();
const
navigate
=
useNavigate
();
const
handleChange
=
(
e
:
ChangeEvent
<
HTMLInputElement
|
HTMLTextAreaElement
>
...
...
@@ -27,7 +29,7 @@ const AddArtist: React.FunctionComponent = (): React.ReactElement => {
}
};
const
handleSubmit
=
(
e
:
React
.
FormEvent
)
=>
{
const
handleSubmit
=
async
(
e
:
React
.
FormEvent
)
=>
{
e
.
preventDefault
();
const
artist
=
new
FormData
();
artist
.
append
(
'name'
,
newArtist
.
name
);
...
...
@@ -35,7 +37,8 @@ const AddArtist: React.FunctionComponent = (): React.ReactElement => {
console
.
log
(
artist
);
artist
.
append
(
'photo'
,
file
!
);
dispatch
(
addArtist
(
artist
));
await
dispatch
(
addArtist
(
artist
));
navigate
(
'/'
);
};
return
(
<
form
encType=
"multipart/form-data"
onSubmit=
{
handleSubmit
}
>
...
...
frontend/src/containers/AddTrack.tsx
0 → 100644
View file @
a9b2cf17
import
React
,
{
ChangeEvent
,
Fragment
,
useEffect
,
useState
}
from
'react'
;
import
ITrack
from
'../interfaces/ITrack'
;
import
{
useAppDispatch
,
useAppSelector
}
from
'../store/hooks'
;
import
{
useNavigate
,
useParams
}
from
'react-router-dom'
;
import
{
getArtists
}
from
'../features/artist/artistSlice'
;
// import {addTrack} from '../features/track/trackSlice';
import
{
getAlbums
}
from
'../features/album/albumSlice'
;
import
{
addTrack
}
from
'../features/track/trackSlice'
;
const
AddTrack
:
React
.
FunctionComponent
=
():
React
.
ReactElement
=>
{
const
[
newTrack
,
setNewTrack
]
=
useState
<
ITrack
>
({
name
:
''
,
duration
:
''
,
}
as
ITrack
);
const
dispatch
=
useAppDispatch
();
const
navigate
=
useNavigate
();
const
{
albums
}
=
useAppSelector
((
state
)
=>
state
.
album
);
let
[
album
,
setAlbum
]
=
useState
<
string
>
(
''
);
useEffect
(()
=>
{
dispatch
(
getAlbums
());
},
[
dispatch
]);
const
handleChange
=
(
e
:
ChangeEvent
<
HTMLInputElement
|
HTMLTextAreaElement
>
)
=>
setNewTrack
((
prevState
)
=>
({
...
prevState
,
[
e
.
target
.
name
]:
e
.
target
.
value
,
}));
const
handleSubmit
=
async
(
e
:
React
.
FormEvent
)
=>
{
e
.
preventDefault
();
const
data
:
ITrack
=
{
album
,
duration
:
newTrack
.
duration
,
name
:
newTrack
.
name
,
};
console
.
log
(
data
);
await
dispatch
(
addTrack
(
data
));
navigate
(
'/'
);
};
return
(
<
form
className=
"pt-10"
encType=
"multipart/form-data"
onSubmit=
{
handleSubmit
}
>
<
select
required
onChange=
{
(
e
:
React
.
ChangeEvent
<
HTMLSelectElement
>
)
=>
setAlbum
(
e
.
target
.
value
)
}
>
<
option
value=
""
>
Select Album
</
option
>
{
albums
.
map
((
album
)
=>
(
<
Fragment
key=
{
album
.
_id
}
>
{
album
.
published
?
(
<
option
value=
{
album
.
_id
}
>
{
album
.
name
}
</
option
>
)
:
null
}
</
Fragment
>
))
}
</
select
>
<
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=
{
newTrack
.
name
}
placeholder=
"Track"
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
>
<
input
type=
"number"
required
onChange=
{
handleChange
}
value=
{
newTrack
.
duration
}
id=
"message"
name=
"duration"
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"
placeholder=
"Duration"
></
input
>
<
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
AddTrack
;
frontend/src/containers/Albums.tsx
View file @
a9b2cf17
import
React
,
{
useEffect
}
from
'react'
;
import
{
getAlbumsByQueryParams
}
from
'../features/album/albumSlice'
;
import
React
,
{
Fragment
,
useEffect
,
useState
}
from
'react'
;
import
{
deleteAlbum
,
getAlbumsByQueryParams
,
publishAlbum
,
}
from
'../features/album/albumSlice'
;
import
{
useAppDispatch
,
useAppSelector
}
from
'../store/hooks'
;
import
{
useLocation
}
from
'react-router-dom'
;
import
{
useNavigate
}
from
'react-router-dom'
;
import
IUser
from
'../interfaces/IUser'
;
const
Albums
:
React
.
FunctionComponent
=
():
React
.
ReactElement
=>
{
const
{
albums
}
=
useAppSelector
((
state
)
=>
state
.
album
);
...
...
@@ -11,11 +16,30 @@ const Albums: React.FunctionComponent = (): React.ReactElement => {
const
artist
=
params
.
get
(
'artist'
);
const
{
state
}
=
useLocation
();
const
navigate
=
useNavigate
();
const
[
role
,
setRole
]
=
useState
<
string
>
(
'User'
);
useEffect
(()
=>
{
const
userRole
=
localStorage
.
getItem
(
'userRole'
);
if
(
userRole
)
{
setRole
(
userRole
);
}
dispatch
(
getAlbumsByQueryParams
(
artist
!
));
},
[
dispatch
]);
const
handlePublish
=
async
(
id
:
string
)
=>
{
await
dispatch
(
publishAlbum
(
id
));
await
dispatch
(
getAlbumsByQueryParams
(
artist
!
));
};
const
handleDelete
=
async
(
id
:
string
)
=>
{
const
user
=
localStorage
.
getItem
(
'user'
);
if
(
user
)
{
const
foundUser
:
IUser
=
JSON
.
parse
(
user
);
await
dispatch
(
deleteAlbum
({
id
,
token
:
foundUser
.
token
!
}));
}
await
dispatch
(
getAlbumsByQueryParams
(
artist
!
));
};
return
(
<
div
>
<
h2
className=
"mt-0 mb-2 text-4xl font-medium leading-tight "
>
Albums
</
h2
>
...
...
@@ -24,21 +48,42 @@ const Albums: React.FunctionComponent = (): React.ReactElement => {
{
albums
.
length
?
(
albums
.
map
((
album
)
=>
{
return
(
<
div
key=
{
album
.
_id
}
>
<
img
onClick=
{
()
=>
navigate
(
{
pathname
:
'/tracks'
,
search
:
`?album=${album._id}`
},
{
state
:
{
album
:
album
.
name
,
artist
:
state
.
artist
}}
)
}
className=
"w-48 h-48 object-cover "
src=
{
`${import.meta.env.VITE_MY_URL}/${album.image}`
}
alt=
{
album
.
name
}
/>
<
p
>
{
album
.
name
}
</
p
>
<
p
>
{
album
.
year
}
</
p
>
</
div
>
<
Fragment
key=
{
album
.
_id
}
>
{
role
===
'User'
&&
!
album
.
published
?
null
:
(
<
div
>
<
img
onClick=
{
()
=>
navigate
(
{
pathname
:
'/tracks'
,
search
:
`?album=${album._id}`
},
{
state
:
{
album
:
album
.
name
,
artist
:
state
.
artist
}}
)
}
className=
"w-48 h-48 object-cover "
src=
{
`${import.meta.env.VITE_MY_URL}/${album.image}`
}
alt=
{
album
.
name
}
/>
<
p
>
{
album
.
name
}
</
p
>
<
p
>
{
album
.
year
}
</
p
>
{
role
===
'Admin'
?
(
<
div
>
<
button
onClick=
{
()
=>
handleDelete
(
album
.
_id
||
''
)
}
>
Delete
</
button
>
{
!
album
.
published
&&
(
<>
<
p
>
Unpublished
</
p
>
<
button
onClick=
{
()
=>
handlePublish
(
album
.
_id
||
''
)
}
>
Publish
</
button
>
</>
)
}
</
div
>
)
:
null
}
</
div
>
)
}
</
Fragment
>
);
})
)
:
(
...
...
frontend/src/containers/HomePage.tsx
View file @
a9b2cf17
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
getArtists
}
from
'../features/artist/artistSlice'
;
import
{
deleteArtist
,
getArtists
,
publishArtist
,
}
from
'../features/artist/artistSlice'
;
import
{
useAppDispatch
,
useAppSelector
}
from
'../store/hooks'
;
import
{
useNavigate
}
from
'react-router-dom'
;
import
Preloader
from
'../components/UI/Preloader'
;
import
IUser
from
'../interfaces/IUser'
;
const
HomePage
:
React
.
FunctionComponent
=
():
React
.
ReactElement
=>
{
const
{
loading
,
artists
}
=
useAppSelector
((
state
)
=>
state
.
artist
);
...
...
@@ -11,6 +16,19 @@ const HomePage: React.FunctionComponent = (): React.ReactElement => {
const
dispatch
=
useAppDispatch
();
const
navigate
=
useNavigate
();
const
handleDelete
=
async
(
id
:
string
)
=>
{
const
user
=
localStorage
.
getItem
(
'user'
);
if
(
user
)
{
const
foundUser
:
IUser
=
JSON
.
parse
(
user
);
await
dispatch
(
deleteArtist
({
id
,
token
:
foundUser
.
token
!
}));
}
await
dispatch
(
getArtists
());
};
const
handlePublish
=
async
(
id
:
string
)
=>
{
await
dispatch
(
publishArtist
(
id
));
await
dispatch
(
getArtists
());
};
useEffect
(()
=>
{
dispatch
(
getArtists
());
},
[
dispatch
,
navigate
,
userRole
]);
...
...
@@ -43,10 +61,16 @@ const HomePage: React.FunctionComponent = (): React.ReactElement => {
/>
<
span
className=
"block"
>
{
artist
.
name
}
</
span
>
{
!
artist
.
published
&&
<
p
>
Unpublished
</
p
>
}
{
artist
.
published
?
(
<
button
>
Delete
</
button
>
)
:
(
<
button
>
Publish
</
button
>
<
button
className=
"block"
onClick=
{
()
=>
handleDelete
(
artist
.
_id
||
''
)
}
>
Delete
</
button
>
{
!
artist
.
published
&&
(
<
button
onClick=
{
()
=>
handlePublish
(
artist
.
_id
||
''
)
}
>
Publish
</
button
>
)
}
</>
)
:
artist
.
published
?
(
...
...
@@ -66,7 +90,11 @@ const HomePage: React.FunctionComponent = (): React.ReactElement => {
alt=
{
artist
.
name
}
/>
<
span
className=
"block"
>
{
artist
.
name
}
</
span
>
<
button
>
Delete
</
button
>
{
userRole
===
'Admin'
&&
(
<
button
onClick=
{
()
=>
handleDelete
(
artist
.
_id
||
''
)
}
>
Delete
</
button
>
)
}
</>
)
:
null
}
</
div
>
...
...
frontend/src/containers/Tracks.tsx
View file @
a9b2cf17
import
React
,
{
useEffect
}
from
'react'
;
import
React
,
{
Fragment
,
useEffect
,
useState
}
from
'react'
;
import
{
useLocation
}
from
'react-router-dom'
;
import
{
addTrackHistory
}
from
'../features/track-history/trackHistorySlice'
;
import
{
getTracksByQuery
}
from
'../features/track/trackSlice'
;
import
{
deleteTrack
,
getTracksByQuery
,
publishTrack
,
}
from
'../features/track/trackSlice'
;
import
ITrackHistoryDto
from
'../interfaces/ITrackHistoryDto'
;
import
{
useAppDispatch
,
useAppSelector
}
from
'../store/hooks'
;
import
IUser
from
'../interfaces/IUser'
;
const
Tracks
:
React
.
FunctionComponent
=
():
React
.
ReactElement
=>
{
const
{
state
}
=
useLocation
();
...
...
@@ -12,8 +17,13 @@ const Tracks: React.FunctionComponent = (): React.ReactElement => {
const
params
=
new
URLSearchParams
(
window
.
location
.
search
);
const
album
=
params
.
get
(
'album'
);
const
{
userLoggedIn
}
=
useAppSelector
((
state
)
=>
state
.
user
);
const
[
role
,
setRole
]
=
useState
<
string
>
(
'User'
);
useEffect
(()
=>
{
const
userRole
=
localStorage
.
getItem
(
'userRole'
);
if
(
userRole
)
{
setRole
(
userRole
);
}
dispatch
(
getTracksByQuery
(
album
!
));
},
[
dispatch
]);
...
...
@@ -27,6 +37,20 @@ const Tracks: React.FunctionComponent = (): React.ReactElement => {
await
dispatch
(
addTrackHistory
(
data
));
};
const
handleDelete
=
async
(
id
:
string
)
=>
{
const
user
=
localStorage
.
getItem
(
'user'
);
if
(
user
)
{
const
foundUser
:
IUser
=
JSON
.
parse
(
user
);
await
dispatch
(
deleteTrack
({
id
,
token
:
foundUser
.
token
!
}));
}
await
dispatch
(
getTracksByQuery
(
album
!
));
};
const
handlePublish
=
async
(
id
:
string
)
=>
{
await
dispatch
(
publishTrack
(
id
));
await
dispatch
(
getTracksByQuery
(
album
!
));
};
return
(
<
div
>
<
h2
className=
"mt-0 mb-2 text-4xl font-medium leading-tight "
>
Tracks
</
h2
>
...
...
@@ -36,17 +60,37 @@ const Tracks: React.FunctionComponent = (): React.ReactElement => {
{
tracks
.
length
?
(
tracks
.
map
((
track
)
=>
{
return
(
<
div
onClick=
{
()
=>
userLoggedIn
?
handleClick
(
track
.
_id
)
:
undefined
}
className=
"border border-solid color border-black p-4"
key=
{
track
.
_id
}
>
<
p
>
Track number:
{
track
.
seq
.
toString
()
}
</
p
>
<
p
>
Track name:
{
track
.
name
}
</
p
>
<
p
>
Track duration:
{
track
.
duration
}
</
p
>
</
div
>
<
Fragment
key=
{
track
.
_id
}
>
{
role
===
'User'
&&
!
track
.
published
?
null
:
(
<
div
onClick=
{
()
=>
userLoggedIn
?
handleClick
(
track
.
_id
!
)
:
undefined
}
className=
"border border-solid color border-black p-4"
>
<
p
>
Track number:
{
track
.
seq
!
.
toString
()
}
</
p
>
<
p
>
Track name:
{
track
.
name
}
</
p
>
<
p
>
Track duration:
{
track
.
duration
}
</
p
>
{
role
===
'Admin'
?
(
<
div
>
<
button
onClick=
{
()
=>
handleDelete
(
track
.
_id
||
''
)
}
>
Delete
</
button
>
{
!
track
.
published
&&
(
<>
<
p
>
Unpublished
</
p
>
<
button
onClick=
{
()
=>
handlePublish
(
track
.
_id
||
''
)
}
>
Publish
</
button
>
</>
)
}
</
div
>
)
:
null
}
</
div
>
)
}
</
Fragment
>
);
})
)
:
(
...
...
frontend/src/features/album/albumSlice.ts
View file @
a9b2cf17
...
...
@@ -31,6 +31,50 @@ export const getAlbumsByQueryParams = createAsyncThunk(
}
);
export
const
addAlbum
=
createAsyncThunk
(
'addAlbum'
,
async
(
newAlbum
:
FormData
)
=>
{
const
response
=
await
axios
({
method
:
'post'
,
url
:
`
${
import
.
meta
.
env
.
VITE_MY_URL
}
/albums`
,
data
:
newAlbum
,
headers
:
{
'Content-Type'
:
`multipart/form-data; `
,
},
});
return
response
.
data
;
}
);
export
const
publishAlbum
=
createAsyncThunk
(
'publishAlbum'
,
async
(
id
:
string
)
=>
{
try
{
const
response
=
await
axios
.
post
(
`
${
import
.
meta
.
env
.
VITE_MY_URL
}
/albums/
${
id
}
/publish`
);
return
response
.
data
;
}
catch
(
err
)
{
console
.
log
(
err
);
}
}
);
export
const
deleteAlbum
=
createAsyncThunk
(
'deletAlbum'
,
async
(
data
:
{
id
:
string
;
token
:
string
})
=>
{
const
response
=
await
axios
.
delete
(
`
${
import
.
meta
.
env
.
VITE_MY_URL
}
/albums/
${
data
.
id
}
`
,
{
headers
:
{
Authorization
:
data
.
token
,
},
}
);
return
response
.
data
;
}
);
export
const
albumSlice
=
createSlice
({
name
:
'artist'
,
initialState
,
...
...
@@ -48,7 +92,35 @@ export const albumSlice = createSlice({
(
state
,
{
payload
}:
PayloadAction
<
IAlbum
[]
>
)
=>
{
state
.
albums
=
payload
;
}
);
)
.
addCase
(
addAlbum
.
pending
,
(
state
,
action
)
=>
{
state
.
loading
=
true
;
})
.
addCase
(
addAlbum
.
rejected
,
(
state
,
action
)
=>
{
state
.
loading
=
false
;
})
.
addCase
(
addAlbum
.
fulfilled
,
(
state
,
action
)
=>
{
state
.
loading
=
false
;
})
.
addCase
(
publishAlbum
.
pending
,
(
state
,
action
)
=>
{
state
.
loading
=
true
;
})
.
addCase
(
publishAlbum
.
rejected
,
(
state
,
action
)
=>
{
state
.
loading
=
false
;
})
.
addCase
(
publishAlbum
.
fulfilled
,
(
state
,
action
)
=>
{
state
.
loading
=
false
;
})
.
addCase
(
getAlbums
.
pending
,
(
state
,
action
)
=>
{
state
.
loading
=
true
;
})
.
addCase
(
getAlbums
.
rejected
,
(
state
,
action
)
=>
{
state
.
loading
=
false
;
})
.
addCase
(
getAlbums
.
fulfilled
,
(
state
,
action
)
=>
{
state
.
loading
=
false
;
state
.
albums
=
action
.
payload
;
});
},
});
...
...
frontend/src/features/artist/artistSlice.ts
View file @
a9b2cf17
...
...
@@ -36,6 +36,35 @@ export const addArtist = createAsyncThunk(
}
);
export
const
deleteArtist
=
createAsyncThunk
(
'deleteArtist'
,
async
(
data
:
{
id
:
string
;
token
:
string
})
=>
{
const
response
=
await
axios
.
delete
(
`
${
import
.
meta
.
env
.
VITE_MY_URL
}
/artists/
${
data
.
id
}
`
,
{
headers
:
{
Authorization
:
data
.
token
,
},
}
);
return
response
.
data
;
}
);
export
const
publishArtist
=
createAsyncThunk
(
'publishArtist'
,
async
(
id
:
string
)
=>
{
try
{
const
response
=
await
axios
.
post
(
`
${
import
.
meta
.
env
.
VITE_MY_URL
}
/artists/
${
id
}
/publish`
);
return
response
.
data
;
}
catch
(
err
)
{
console
.
log
(
err
);
}
}
);
export
const
artistSlice
=
createSlice
({
name
:
'artist'
,
initialState
,
...
...
@@ -63,7 +92,24 @@ export const artistSlice = createSlice({
})
.
addCase
(
addArtist
.
fulfilled
,
(
state
,
action
)
=>
{
state
.
loading
=
false
;
console
.
log
(
action
.
payload
);
})
.
addCase
(
deleteArtist
.
pending
,
(
state
,
action
)
=>
{
state
.
loading
=
true
;
})
.
addCase
(
deleteArtist
.
rejected
,
(
state
,
action
)
=>
{
state
.
loading
=
false
;
})
.
addCase
(
deleteArtist
.
fulfilled
,
(
state
,
action
)
=>
{
state
.
loading
=
false
;
})
.
addCase
(
publishArtist
.
pending
,
(
state
,
action
)
=>
{
state
.
loading
=
true
;
})
.
addCase
(
publishArtist
.
rejected
,
(
state
,
action
)
=>
{
state
.
loading
=
false
;
})
.
addCase
(
publishArtist
.
fulfilled
,
(
state
,
action
)
=>
{
state
.
loading
=
false
;
});
},
});
...
...
frontend/src/features/track/trackSlice.ts
View file @
a9b2cf17
...
...
@@ -26,6 +26,43 @@ export const getTracksByQuery = createAsyncThunk(
}
);
export
const
addTrack
=
createAsyncThunk
(
'addTrack'
,
async
(
data
:
ITrack
)
=>
{
const
response
=
await
axios
.
post
(
`
${
import
.
meta
.
env
.
VITE_MY_URL
}
/tracks`
,
data
);
return
response
.
data
;
});
export
const
publishTrack
=
createAsyncThunk
(
'publishTrack'
,
async
(
id
:
string
)
=>
{
try
{
const
response
=
await
axios
.
post
(
`
${
import
.
meta
.
env
.
VITE_MY_URL
}
/tracks/
${
id
}
/publish`
);
return
response
.
data
;
}
catch
(
err
)
{
console
.
log
(
err
);
}
}
);
export
const
deleteTrack
=
createAsyncThunk
(
'deletTrack'
,
async
(
data
:
{
id
:
string
;
token
:
string
})
=>
{
const
response
=
await
axios
.
delete
(
`
${
import
.
meta
.
env
.
VITE_MY_URL
}
/tracks/
${
data
.
id
}
`
,
{
headers
:
{
Authorization
:
data
.
token
,
},
}
);
return
response
.
data
;
}
);
export
const
trackSlice
=
createSlice
({
name
:
'track'
,
initialState
,
...
...
@@ -43,7 +80,25 @@ export const trackSlice = createSlice({
(
state
,
{
payload
}:
PayloadAction
<
ITrack
[]
>
)
=>
{
state
.
tracks
=
payload
;
}
);
)
.
addCase
(
addTrack
.
pending
,
(
state
,
action
)
=>
{
state
.
loading
=
true
;
})
.
addCase
(
addTrack
.
rejected
,
(
state
,
action
)
=>
{
state
.
loading
=
false
;
})
.
addCase
(
addTrack
.
fulfilled
,
(
state
,
action
)
=>
{
state
.
loading
=
false
;
})
.
addCase
(
publishTrack
.
pending
,
(
state
,
action
)
=>
{
state
.
loading
=
true
;
})
.
addCase
(
publishTrack
.
rejected
,
(
state
,
action
)
=>
{
state
.
loading
=
false
;
})
.
addCase
(
publishTrack
.
fulfilled
,
(
state
,
action
)
=>
{
state
.
loading
=
false
;
});
},
});
...
...
frontend/src/features/user/userSlice.ts
View file @
a9b2cf17
...
...
@@ -74,6 +74,9 @@ export const userSlice = createSlice({
setUserRole
:
(
state
,
action
)
=>
{
state
.
userRole
=
action
.
payload
;
},
setUser
:
(
state
,
action
)
=>
{
state
.
user
=
action
.
payload
;
},
},
extraReducers
:
(
builder
)
=>
{
builder
...
...
@@ -132,6 +135,7 @@ export const userSlice = createSlice({
},
});
export
const
{
setUserLoggedIn
,
setLogOut
,
setUserRole
}
=
userSlice
.
actions
;
export
const
{
setUserLoggedIn
,
setLogOut
,
setUserRole
,
setUser
}
=
userSlice
.
actions
;
export
default
userSlice
.
reducer
;
frontend/src/interfaces/IAlbum.ts
View file @
a9b2cf17
...
...
@@ -3,7 +3,8 @@ import IArtist from './IArtist';
export
default
interface
IAlbum
{
name
:
string
;
artist
:
IArtist
;
year
:
number
;
year
:
string
;
image
:
string
;
_id
:
string
;
_id
?:
string
;
published
:
boolean
;
}
frontend/src/interfaces/ITrack.ts
View file @
a9b2cf17
...
...
@@ -2,6 +2,7 @@ export default interface ITrack {
name
:
string
;
album
:
string
;
duration
:
string
;
seq
:
Number
;
_id
:
string
;
seq
?:
Number
;
_id
?:
string
;
published
?:
boolean
;
}
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