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

parent 43a90b80
......@@ -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>
......
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);
}
},
};
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="#"
......
......@@ -4,7 +4,7 @@ import Header from '../Header';
const Layout: React.FunctionComponent = (): React.ReactElement => {
return (
<div className="container mx-auto px-96 pt-4">
<div className="container mx-auto pt-4">
<Header />
<Outlet />
</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';
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>
);
})
......
......@@ -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);
......
......@@ -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);
});
},
});
......
......@@ -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;
......@@ -2,5 +2,6 @@ export default interface IArtist {
name: string;
photo: string;
info: string;
_id: string;
_id?: string;
published: boolean;
}
......@@ -3,4 +3,5 @@ export default interface IUser {
password?: string;
token?: 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