#14 added admin and user roles and showing unpublished only to admins

parent 43a90b80
...@@ -6,8 +6,9 @@ import Albums from './containers/Albums'; ...@@ -6,8 +6,9 @@ import Albums from './containers/Albums';
import Tracks from './containers/Tracks'; import Tracks from './containers/Tracks';
import Login from './containers/Login'; import Login from './containers/Login';
import TrackHistory from './containers/TrackHistory'; import TrackHistory from './containers/TrackHistory';
import AddArtist from './containers/AddArtist';
const App = () => { const App: React.FunctionComponent = (): React.ReactElement => {
return ( return (
<BrowserRouter> <BrowserRouter>
<Routes> <Routes>
...@@ -17,6 +18,7 @@ const App = () => { ...@@ -17,6 +18,7 @@ const App = () => {
<Route path="tracks" element={<Tracks />} /> <Route path="tracks" element={<Tracks />} />
<Route path="login" element={<Login />} /> <Route path="login" element={<Login />} />
<Route path="track-history" element={<TrackHistory />} /> <Route path="track-history" element={<TrackHistory />} />
<Route path="add-artist" element={<AddArtist />} />
</Route> </Route>
</Routes> </Routes>
</BrowserRouter> </BrowserRouter>
......
import IArtist from '../interfaces/IArtist';
import {artistInstance} from './instances'; import {artistInstance} from './instances';
export const artistApi = { export const artistApi = {
...@@ -9,4 +10,17 @@ export const artistApi = { ...@@ -9,4 +10,17 @@ export const artistApi = {
console.log(err); 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);
}
},
}; };
import React from 'react'; import React, {useEffect, useState} from 'react';
import {Link, useNavigate} from 'react-router-dom'; import {Link, useNavigate} from 'react-router-dom';
import {useAppDispatch, useAppSelector} from '../store/hooks'; 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 Header = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const {userLoggedIn} = useAppSelector((state) => state.user); const {userLoggedIn, userRole} = useAppSelector((state) => state.user);
const handleLogOut = () => { const handleLogOut = () => {
const token: string | null = window.sessionStorage.getItem('token'); const token: string | null = window.sessionStorage.getItem('token');
dispatch(setLogOut()); dispatch(setLogOut());
dispatch(setUserRole('User'));
if (token) { if (token) {
dispatch(logout(token)); dispatch(logout(token));
} }
navigate('/'); navigate('/');
}; };
useEffect(() => {
const loggedInUser = localStorage.getItem('user');
if (loggedInUser) {
dispatch(setUserLoggedIn(true));
const foundUser: IUser = JSON.parse(loggedInUser);
dispatch(setUserRole(foundUser.role));
}
}, [dispatch]);
return ( return (
<div> <div>
{!userLoggedIn ? ( {!userLoggedIn ? (
...@@ -33,6 +48,19 @@ const Header = () => { ...@@ -33,6 +48,19 @@ const Header = () => {
> >
Track History Track History
</Link> </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 <a
onClick={handleLogOut} onClick={handleLogOut}
href="#" href="#"
......
...@@ -4,7 +4,7 @@ import Header from '../Header'; ...@@ -4,7 +4,7 @@ import Header from '../Header';
const Layout: React.FunctionComponent = (): React.ReactElement => { const Layout: React.FunctionComponent = (): React.ReactElement => {
return ( return (
<div className="container mx-auto px-96 pt-4"> <div className="container mx-auto pt-4">
<Header /> <Header />
<Outlet /> <Outlet />
</div> </div>
......
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;
...@@ -3,22 +3,17 @@ import {getArtists} from '../features/artist/artistSlice'; ...@@ -3,22 +3,17 @@ import {getArtists} from '../features/artist/artistSlice';
import {useAppDispatch, useAppSelector} from '../store/hooks'; import {useAppDispatch, useAppSelector} from '../store/hooks';
import {useNavigate} from 'react-router-dom'; import {useNavigate} from 'react-router-dom';
import Preloader from '../components/UI/Preloader'; import Preloader from '../components/UI/Preloader';
import {setUserLoggedIn} from '../features/user/userSlice';
const HomePage: React.FunctionComponent = (): React.ReactElement => { const HomePage: React.FunctionComponent = (): React.ReactElement => {
const {loading, artists} = useAppSelector((state) => state.artist); const {loading, artists} = useAppSelector((state) => state.artist);
const {userRole} = useAppSelector((state) => state.user);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
useEffect(() => { useEffect(() => {
dispatch(getArtists()); dispatch(getArtists());
const loggedInUser = localStorage.getItem('user'); }, [dispatch, navigate, userRole]);
if (loggedInUser) {
const foundUser = JSON.parse(loggedInUser);
dispatch(setUserLoggedIn(true));
}
}, [dispatch]);
return ( return (
<div> <div>
...@@ -30,18 +25,50 @@ const HomePage: React.FunctionComponent = (): React.ReactElement => { ...@@ -30,18 +25,50 @@ const HomePage: React.FunctionComponent = (): React.ReactElement => {
artists.map((artist) => { artists.map((artist) => {
return ( return (
<div key={artist._id}> <div key={artist._id}>
<img {userRole === 'Admin' ? (
onClick={() => <>
navigate( <img
{pathname: '/albums', search: `?artist=${artist._id}`}, onClick={() =>
{state: {artist: artist.name}} navigate(
) {
} pathname: '/albums',
className="w-56 h-56 object-cover " search: `?artist=${artist._id}`,
src={`${import.meta.env.VITE_MY_URL}/${artist.photo}`} },
alt={artist.name} {state: {artist: artist.name}}
/> )
<span>{artist.name}</span> }
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> </div>
); );
}) })
......
...@@ -6,7 +6,6 @@ import {useAppDispatch, useAppSelector} from '../store/hooks'; ...@@ -6,7 +6,6 @@ import {useAppDispatch, useAppSelector} from '../store/hooks';
const Login = () => { const Login = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const navigate = useNavigate();
const [user, setUser] = useState({} as IUser); const [user, setUser] = useState({} as IUser);
const {userLoggedIn} = useAppSelector((state) => state.user); const {userLoggedIn} = useAppSelector((state) => state.user);
......
...@@ -3,21 +3,39 @@ import type {PayloadAction} from '@reduxjs/toolkit'; ...@@ -3,21 +3,39 @@ import type {PayloadAction} from '@reduxjs/toolkit';
import {RootState} from '../../store/store'; import {RootState} from '../../store/store';
import IArtist from '../../interfaces/IArtist'; import IArtist from '../../interfaces/IArtist';
import {artistApi} from '../../api/artistApi'; import {artistApi} from '../../api/artistApi';
import axios from 'axios';
interface ArtistState { interface ArtistState {
artists: IArtist[]; artists: IArtist[];
loading: boolean; loading: boolean;
unpublishedArtists: IArtist[];
} }
const initialState: ArtistState = { const initialState: ArtistState = {
artists: [], artists: [],
loading: false, loading: false,
unpublishedArtists: [],
}; };
export const getArtists = createAsyncThunk('getArtists', async () => { export const getArtists = createAsyncThunk('getArtists', async () => {
return await artistApi.get(); 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({ export const artistSlice = createSlice({
name: 'artist', name: 'artist',
initialState, initialState,
...@@ -36,7 +54,17 @@ export const artistSlice = createSlice({ ...@@ -36,7 +54,17 @@ export const artistSlice = createSlice({
state.artists = payload; state.artists = payload;
state.loading = false; 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);
});
}, },
}); });
......
...@@ -7,12 +7,14 @@ interface UserState { ...@@ -7,12 +7,14 @@ interface UserState {
user: IUser; user: IUser;
loading: boolean; loading: boolean;
userLoggedIn: boolean; userLoggedIn: boolean;
userRole: string;
} }
const initialState: UserState = { const initialState: UserState = {
user: {} as IUser, user: {} as IUser,
loading: false, loading: false,
userLoggedIn: false, userLoggedIn: false,
userRole: 'User',
}; };
export const createUser = createAsyncThunk( export const createUser = createAsyncThunk(
...@@ -67,6 +69,10 @@ export const userSlice = createSlice({ ...@@ -67,6 +69,10 @@ export const userSlice = createSlice({
setLogOut: (state) => { setLogOut: (state) => {
state.userLoggedIn = false; state.userLoggedIn = false;
localStorage.setItem('user', ''); localStorage.setItem('user', '');
localStorage.setItem('userRole', 'User');
},
setUserRole: (state, action) => {
state.userRole = action.payload;
}, },
}, },
extraReducers: (builder) => { extraReducers: (builder) => {
...@@ -93,7 +99,12 @@ export const userSlice = createSlice({ ...@@ -93,7 +99,12 @@ export const userSlice = createSlice({
state, state,
{ {
payload, payload,
}: PayloadAction<{token: string; username: string; id: string}> }: PayloadAction<{
token: string;
username: string;
id: string;
role: string;
}>
) => { ) => {
state.loading = false; state.loading = false;
state.userLoggedIn = true; state.userLoggedIn = true;
...@@ -102,8 +113,11 @@ export const userSlice = createSlice({ ...@@ -102,8 +113,11 @@ export const userSlice = createSlice({
id: payload.id, id: payload.id,
username: payload.username, username: payload.username,
token: payload.token, token: payload.token,
role: payload.role,
}; };
state.userRole = payload.role;
localStorage.setItem('user', JSON.stringify(user)); localStorage.setItem('user', JSON.stringify(user));
localStorage.setItem('userRole', payload.role);
} }
) )
.addCase(logout.fulfilled, (state, action) => { .addCase(logout.fulfilled, (state, action) => {
...@@ -118,6 +132,6 @@ export const userSlice = createSlice({ ...@@ -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; export default userSlice.reducer;
...@@ -2,5 +2,6 @@ export default interface IArtist { ...@@ -2,5 +2,6 @@ export default interface IArtist {
name: string; name: string;
photo: string; photo: string;
info: string; info: string;
_id: string; _id?: string;
published: boolean;
} }
...@@ -3,4 +3,5 @@ export default interface IUser { ...@@ -3,4 +3,5 @@ export default interface IUser {
password?: string; password?: string;
token?: string; token?: string;
id?: string; id?: string;
role?: string;
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment