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 Login from "./pages/Login";
|
||||||
import Register from "./pages/Register";
|
import Register from "./pages/Register";
|
||||||
import NewTicket from "./pages/NewTicket";
|
import NewTicket from "./pages/NewTicket";
|
||||||
import Tickets from "./pages/Ticket";
|
import Tickets from "./pages/Tickets";
|
||||||
|
import Ticket from "./pages/Ticket";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@@ -25,6 +26,9 @@ function App() {
|
|||||||
<Route path="/tickets" element={<PrivateRoute />}>
|
<Route path="/tickets" element={<PrivateRoute />}>
|
||||||
<Route path="/tickets" element={<Tickets />} />
|
<Route path="/tickets" element={<Tickets />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path="/ticket/:ticketId" element={<PrivateRoute />}>
|
||||||
|
<Route path="/ticket/:ticketId" element={<Ticket />} />
|
||||||
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</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}`,
|
Authorization: `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await axios.get(API_URL, config);
|
const response = await axios.get(API_URL, config);
|
||||||
|
|
||||||
return response.data;
|
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;
|
export default ticketService;
|
||||||
|
|||||||
@@ -42,10 +42,49 @@ export const ticketSlice = createSlice({
|
|||||||
state.isLoading = false;
|
state.isLoading = false;
|
||||||
state.isError = true;
|
state.isError = true;
|
||||||
state.message = action.payload;
|
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(
|
export const getTickets = createAsyncThunk(
|
||||||
"tickets/getAll",
|
"tickets/getAll",
|
||||||
async (_, thunkAPI) => {
|
async (_, thunkAPI) => {
|
||||||
@@ -69,7 +108,25 @@ export const createTicket = createAsyncThunk(
|
|||||||
async (ticketData, thunkAPI) => {
|
async (ticketData, thunkAPI) => {
|
||||||
try {
|
try {
|
||||||
const token = thunkAPI.getState().auth.user.token;
|
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) {
|
} catch (error) {
|
||||||
const message =
|
const message =
|
||||||
(error.response &&
|
(error.response &&
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { BackButton } from "../components/BackButton";
|
|||||||
function NewTicket() {
|
function NewTicket() {
|
||||||
const { user } = useSelector((state) => state.auth);
|
const { user } = useSelector((state) => state.auth);
|
||||||
const { isLoading, isError, isSuccess, message } = useSelector(
|
const { isLoading, isError, isSuccess, message } = useSelector(
|
||||||
(state) => state.ticket
|
(state) => state.tickets
|
||||||
);
|
);
|
||||||
|
|
||||||
const [name, setName] = useState(user.name);
|
const [name, setName] = useState(user.name);
|
||||||
@@ -20,26 +20,30 @@ function NewTicket() {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(() => {
|
||||||
if(isError) toast.error(message)
|
if (isError) toast.error(message);
|
||||||
|
|
||||||
if(isSuccess){
|
if (isSuccess) {
|
||||||
dispatch(reset())
|
dispatch(reset());
|
||||||
navigate('/tickets')
|
navigate("/tickets");
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(reset())
|
dispatch(reset());
|
||||||
},[dispatch,isError,isSuccess,navigate,message])
|
}, [dispatch, isError, isSuccess, navigate, message]);
|
||||||
const onSubmit = (e) => {
|
const onSubmit = (e) => {
|
||||||
e.preventDefault();
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<BackButton url='/' />
|
<BackButton url="/" />
|
||||||
<section className="heading">
|
<section className="heading">
|
||||||
<h1>Create New Ticket</h1>
|
<h1>Create New Ticket</h1>
|
||||||
<p> Please fill out the form below</p>
|
<p> Please fill out the form below</p>
|
||||||
|
|||||||
@@ -1,28 +1,56 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
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 Spinner from "../components/Spinner";
|
||||||
import { BackButton } from "../components/BackButton";
|
import { BackButton } from "../components/BackButton";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
function Tickets() {
|
function Ticket() {
|
||||||
const { tickets, isLoading, isSuccess } = useSelector(
|
const { ticket, isLoading, isSuccess, isError, message } = useSelector(
|
||||||
(state) => state.tickets
|
(state) => state.tickets
|
||||||
);
|
);
|
||||||
|
const { ticketId } = useParams();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
useEffect(() => {
|
const navigate = useNavigate();
|
||||||
return () => {
|
|
||||||
if (isSuccess) {
|
|
||||||
dispatch(reset());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [dispatch, isSuccess]);
|
|
||||||
useEffect(() => {
|
|
||||||
dispatch(getTickets());
|
|
||||||
}, [dispatch]);
|
|
||||||
|
|
||||||
if (isLoading) {
|
const onTicketClose =()=>{
|
||||||
return <Spinner />;
|
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