added working ticket frontend

This commit is contained in:
QkoSad
2022-12-10 17:21:26 +02:00
parent 885cdfa7fe
commit 475f924381
7 changed files with 210 additions and 32 deletions
+5 -1
View File
@@ -7,7 +7,8 @@ import Home from "./pages/Home";
import Login from "./pages/Login";
import Register from "./pages/Register";
import NewTicket from "./pages/NewTicket";
import Tickets from "./pages/Ticket";
import Tickets from "./pages/Tickets";
import Ticket from "./pages/Ticket";
function App() {
return (
@@ -25,6 +26,9 @@ function App() {
<Route path="/tickets" element={<PrivateRoute />}>
<Route path="/tickets" element={<Tickets />} />
</Route>
<Route path="/ticket/:ticketId" element={<PrivateRoute />}>
<Route path="/ticket/:ticketId" element={<Ticket />} />
</Route>
</Routes>
</div>
</Router>
+15
View File
@@ -0,0 +1,15 @@
import { Link } from "react-router-dom";
function TicketItem({ ticket }) {
return (
<div className="ticket">
<div>{new Date(ticket.createdAt).toLocaleString("en-US")}</div>
<div>{ticket.product}</div>
<div className={`status status-${ticket.status}`}>{ticket.status}</div>
<Link to={`/ticket/${ticket._id}`} className="btn btn-reverse btn-sm">
View
</Link>
</div>
);
}
export default TicketItem;
+29 -2
View File
@@ -19,12 +19,39 @@ const getTickets = async (token) => {
Authorization: `Bearer ${token}`,
},
};
const response = await axios.get(API_URL, config);
return response.data;
};
const ticketService = { createTicket, getTickets };
const closeTicket = async (ticketId, token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
const response = await axios.put(
API_URL + ticketId,
{ status: "closed" },
config
);
return response.data;
};
const getTicket = async (ticketId, token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
const response = await axios.get(API_URL + ticketId, config);
return response.data;
};
const ticketService = { getTicket, createTicket, getTickets, closeTicket };
export default ticketService;
+58 -1
View File
@@ -42,10 +42,49 @@ export const ticketSlice = createSlice({
state.isLoading = false;
state.isError = true;
state.message = action.payload;
})
.addCase(getTicket.pending, (state) => {
state.isLoading = true;
})
.addCase(getTicket.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.ticket = action.payload;
})
.addCase(getTicket.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
})
.addCase(closeTicket.fulfilled, (state, action) => {
state.isLoading = false;
state.tickets.map((ticket) =>
ticket._id === action.payload._id
? (ticket.status = "closed")
: ticket
);
});
},
});
export const closeTicket = createAsyncThunk(
"tickets/close",
async (ticketId, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await ticketService.closeTicket(ticketId, token);
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
return thunkAPI.rejectWithValue(message);
}
}
);
export const getTickets = createAsyncThunk(
"tickets/getAll",
async (_, thunkAPI) => {
@@ -69,7 +108,25 @@ export const createTicket = createAsyncThunk(
async (ticketData, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await ticketService(ticketData, token);
return await ticketService.createTicket(ticketData, token);
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
return thunkAPI.rejectWithValue(message);
}
}
);
export const getTicket = createAsyncThunk(
"tickets/get",
async (ticketId, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await ticketService.getTicket(ticketId, token);
} catch (error) {
const message =
(error.response &&
+15 -11
View File
@@ -9,7 +9,7 @@ import { BackButton } from "../components/BackButton";
function NewTicket() {
const { user } = useSelector((state) => state.auth);
const { isLoading, isError, isSuccess, message } = useSelector(
(state) => state.ticket
(state) => state.tickets
);
const [name, setName] = useState(user.name);
@@ -20,26 +20,30 @@ function NewTicket() {
const dispatch = useDispatch();
const navigate = useNavigate();
useEffect(()=>{
if(isError) toast.error(message)
useEffect(() => {
if (isError) toast.error(message);
if(isSuccess){
dispatch(reset())
navigate('/tickets')
if (isSuccess) {
dispatch(reset());
navigate("/tickets");
}
dispatch(reset())
},[dispatch,isError,isSuccess,navigate,message])
dispatch(reset());
}, [dispatch, isError, isSuccess, navigate, message]);
const onSubmit = (e) => {
e.preventDefault();
dispatch(createTicket({product,description}))
if (product === "") {
dispatch(createTicket({ product:'iPhone', description }));
} else {
dispatch(createTicket({ product, description }));
}
};
if(isLoading) return <Spinner/>
if (isLoading) return <Spinner />;
return (
<>
<BackButton url='/' />
<BackButton url="/" />
<section className="heading">
<h1>Create New Ticket</h1>
<p> Please fill out the form below</p>
+45 -17
View File
@@ -1,28 +1,56 @@
import { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { getTickets, reset } from "../features/tickets/ticketSlice";
import { closeTicket, getTicket, reset } from "../features/tickets/ticketSlice";
import { useNavigate, useParams } from "react-router-dom";
import Spinner from "../components/Spinner";
import { BackButton } from "../components/BackButton";
import { toast } from "react-toastify";
function Tickets() {
const { tickets, isLoading, isSuccess } = useSelector(
function Ticket() {
const { ticket, isLoading, isSuccess, isError, message } = useSelector(
(state) => state.tickets
);
const { ticketId } = useParams();
const dispatch = useDispatch();
useEffect(() => {
return () => {
if (isSuccess) {
dispatch(reset());
}
};
}, [dispatch, isSuccess]);
useEffect(() => {
dispatch(getTickets());
}, [dispatch]);
const navigate = useNavigate();
if (isLoading) {
return <Spinner />;
const onTicketClose =()=>{
dispatch(closeTicket(ticketId))
toast.success('Ticket closed')
navigate('/tickets')
}
return <h1>Head</h1>;
useEffect(() => {
if (isError) toast.error(message);
dispatch(getTicket(ticketId));
}, [isError, message, ticketId]);
if (isLoading) return <Spinner />;
if (isError) return <h3>Something went wrong</h3>;
return (
<div className="ticket-page">
<header className="ticket-header">
<BackButton url="/tickets" />
<h2>
Ticket ID: {ticket._id}
<span className={`status status-${ticket.status}`}>
{ticket.status}
</span>
</h2>
<h3>
Date Submitted: {new Date(ticket.createdAt).toLocaleString("en-US")}
</h3>
<h3>Product: {ticket.product}</h3>
<hr />
<div className="ticket-desc">
<h3>Description of Issue</h3>
<p>{ticket.description}</p>
</div>
</header>
{ticket.status !== "closed" && (
<button className="btn btn-block btn-danger"onClick={onTicketClose}>Close Ticket</button>
)}
</div>
);
}
export default Tickets;
export default Ticket;
+43
View File
@@ -0,0 +1,43 @@
import { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { getTickets, reset } from "../features/tickets/ticketSlice";
import Spinner from "../components/Spinner";
import { BackButton } from "../components/BackButton";
import TicketItem from "../components/TicketItem";
function Tickets() {
const { tickets, isLoading, isSuccess } = useSelector(
(state) => state.tickets
);
const dispatch = useDispatch();
useEffect(() => {
return () => {
if (isSuccess) {
dispatch(reset());
}
};
}, [dispatch, isSuccess]);
useEffect(() => {
dispatch(getTickets());
}, [dispatch]);
if (isLoading) {
return <Spinner />;
}
return <>
<BackButton url='/'/>
<h1>Tickets</h1>
<div className="tickets">
<div className="ticket-heading">
<div>Date</div>
<div>Product</div>
<div>Status</div>
<div></div>
</div>
{tickets.map((ticket)=>(
<TicketItem key={ticket._id} ticket={ticket}/>
))}
</div>
</>
}
export default Tickets;