added working ticket frontend
This commit is contained in:
+5
-1
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user