Migrated to http instance

This commit is contained in:
2024-07-05 10:32:12 +08:00
parent 7b9afb27b8
commit ed9ebb5e29
11 changed files with 318 additions and 299 deletions

View File

@@ -16,11 +16,8 @@ function App() {
<Route element={<HomePage />} path="/" /> <Route element={<HomePage />} path="/" />
<Route element={<SignUpPage />} path="/signup" /> <Route element={<SignUpPage />} path="/signup" />
<Route element={<SignInPage />} path="/signin" /> <Route element={<SignInPage />} path="/signin" />
<Route element={<SpringboardPage />} path="/springboard/:accessToken" /> <Route element={<SpringboardPage />} path="/springboard" />
<Route <Route element={<ManageUserAccountPage />} path="/manage-account" />
element={<ManageUserAccountPage />}
path="/manage-account/:accessToken"
/>
<Route element={<CommunityPage />} path="/community" /> <Route element={<CommunityPage />} path="/community" />
<Route element={<CreatePostPage />} path="/createPost" /> <Route element={<CreatePostPage />} path="/createPost" />

View File

@@ -38,7 +38,8 @@ export default function SignInModule() {
axios axios
.post(config.serverAddress + "/users/login", values) .post(config.serverAddress + "/users/login", values)
.then((response) => { .then((response) => {
navigate("/springboard/" + response.data.accessToken); localStorage.setItem("accessToken", response.data.accessToken);
navigate("/springboard/");
}) })
.catch((error) => { .catch((error) => {
popErrorToast(error); popErrorToast(error);

View File

@@ -1,12 +1,12 @@
import { Button, Checkbox, Link } from "@nextui-org/react"; import { Button, Checkbox, Link } from "@nextui-org/react";
import { Formik, Form, Field, ErrorMessage } from "formik"; import { Formik, Form, Field, ErrorMessage } from "formik";
import * as Yup from "yup"; import * as Yup from "yup";
import axios from "axios";
import config from "../config"; import config from "../config";
import NextUIFormikInput from "./NextUIFormikInput"; import NextUIFormikInput from "./NextUIFormikInput";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { popErrorToast } from "../utilities"; import { popErrorToast } from "../utilities";
import { useState } from "react"; import { useState } from "react";
import instance from "../security/http";
const validationSchema = Yup.object({ const validationSchema = Yup.object({
firstName: Yup.string() firstName: Yup.string()
@@ -61,7 +61,7 @@ export default function SignUpModule() {
const handleSubmit = async (values: any) => { const handleSubmit = async (values: any) => {
try { try {
const response = await axios.post( const response = await instance.post(
config.serverAddress + "/users/register", config.serverAddress + "/users/register",
values values
); );

View File

@@ -1,4 +1,3 @@
import axios from "axios";
import * as Yup from "yup"; import * as Yup from "yup";
import config from "../config"; import config from "../config";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
@@ -19,26 +18,23 @@ import NextUIFormikInput from "./NextUIFormikInput";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import UserProfilePicture from "./UserProfilePicture"; import UserProfilePicture from "./UserProfilePicture";
import { popErrorToast } from "../utilities"; import { popErrorToast } from "../utilities";
import instance from "../security/http";
export default function UpdateAccountModule({ export default function UpdateAccountModule() {
accessToken,
}: {
accessToken: string;
}) {
const navigate = useNavigate(); const navigate = useNavigate();
let [userInformation, setUserInformation] = useState<any>(); let [userInformation, setUserInformation] = useState<any>();
const { isOpen, onOpen, onOpenChange } = useDisclosure(); const { isOpen, onOpen, onOpenChange } = useDisclosure();
useEffect(() => { useEffect(() => {
retrieveUserInformation(accessToken!) retrieveUserInformation()
.then((response) => { .then((response) => {
setUserInformation(response); setUserInformation(response);
}) })
.catch(() => { .catch(() => {
navigate("/springboard/" + accessToken); navigate("/springboard/");
}); });
}, [accessToken]); }, []);
const validationSchema = Yup.object({ const validationSchema = Yup.object({
firstName: Yup.string() firstName: Yup.string()
@@ -70,17 +66,12 @@ export default function UpdateAccountModule({
const handleSubmit = async (values: any) => { const handleSubmit = async (values: any) => {
try { try {
const response = await axios.put( const response = await instance.put(
`${config.serverAddress}/users/individual/${userInformation.id}`, `${config.serverAddress}/users/individual/${userInformation.id}`,
values, values
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
); );
console.log("User updated successfully:", response.data); console.log("User updated successfully:", response.data);
navigate("/springboard/" + accessToken); navigate("/springboard/");
} catch (error) { } catch (error) {
popErrorToast(error); popErrorToast(error);
} }
@@ -103,16 +94,8 @@ export default function UpdateAccountModule({
}; };
const archiveAccount = () => { const archiveAccount = () => {
axios instance
.put( .put(config.serverAddress + "/users/archive/" + userInformation.id)
config.serverAddress + "/users/archive/" + userInformation.id,
null,
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
)
.then(() => { .then(() => {
navigate("/signin"); navigate("/signin");
}) })
@@ -143,7 +126,7 @@ export default function UpdateAccountModule({
<Button <Button
variant="light" variant="light"
onPress={() => { onPress={() => {
navigate("/springboard/" + accessToken); navigate("/springboard/");
}} }}
> >
Cancel Cancel
@@ -200,7 +183,6 @@ export default function UpdateAccountModule({
<div> <div>
<UserProfilePicture <UserProfilePicture
userId={userInformation.id} userId={userInformation.id}
token={accessToken}
editable={true} editable={true}
/> />
</div> </div>

View File

@@ -1,39 +1,27 @@
import axios from "axios";
import React, { useRef, useState } from "react"; import React, { useRef, useState } from "react";
import config from "../config"; import config from "../config";
import { Button, Image } from "@nextui-org/react"; import { Button, Image } from "@nextui-org/react";
import { popErrorToast } from "../utilities"; import { popErrorToast } from "../utilities";
import instance from "../security/http";
export default function UserProfilePicture({ export default function UserProfilePicture({
userId, userId,
token,
editable, editable,
}: { }: {
userId: string; userId: string;
token: string;
editable: boolean; editable: boolean;
}) { }) {
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const uploadProfileImage = async ( const uploadProfileImage = async (userId: string, file: File) => {
userId: string,
file: File,
token: string
) => {
const formData = new FormData(); const formData = new FormData();
formData.append("image", file); formData.append("image", file);
try { try {
const response = await axios.put( const response = await instance.put(
`${config.serverAddress}/users/profile-image/${userId}`, `${config.serverAddress}/users/profile-image/${userId}`,
formData, formData
{
headers: {
"Content-Type": "multipart/form-data",
Authorization: `Bearer ${token}`,
},
}
); );
return response.data; return response.data;
} catch (error) { } catch (error) {
@@ -57,7 +45,7 @@ export default function UserProfilePicture({
const uploadAndHandleSubmit = async (file: File) => { const uploadAndHandleSubmit = async (file: File) => {
setLoading(true); setLoading(true);
try { try {
await uploadProfileImage(userId, file, token); await uploadProfileImage(userId, file);
} catch (error) { } catch (error) {
} finally { } finally {
setLoading(false); setLoading(false);

View File

@@ -1,26 +1,34 @@
import DefaultLayout from '../layouts/default'; import DefaultLayout from "../layouts/default";
import { Button, Link } from "@nextui-org/react"; import { Button, Link } from "@nextui-org/react";
import { Formik, Form } from "formik"; import { Formik, Form } from "formik";
import * as Yup from 'yup'; import * as Yup from "yup";
import axios from "axios"; import { useEffect, useState } from "react";
import { useEffect, useState } from 'react'; import { useParams } from "react-router-dom";
import { useParams } from 'react-router-dom'; import { useNavigate } from "react-router-dom";
import { useNavigate } from 'react-router-dom'; import NextUIFormikInput from "../components/NextUIFormikInput";
import NextUIFormikInput from '../components/NextUIFormikInput'; import NextUIFormikTextarea from "../components/NextUIFormikTextarea";
import NextUIFormikTextarea from '../components/NextUIFormikTextarea'; import config from "../config";
import config from '../config'; import instance from "../security/http";
const validationSchema = Yup.object({ const validationSchema = Yup.object({
title: Yup.string().trim() title: Yup.string()
.min(3, 'Title must be at least 3 characters') .trim()
.max(200, 'Title must be at most 200 characters') .min(3, "Title must be at least 3 characters")
.matches(/^[a-zA-Z0-9\s]+$/, "Title can only contain letters, numbers, and spaces") .max(200, "Title must be at most 200 characters")
.required('Title is required'), .matches(
content: Yup.string().trim() /^[a-zA-Z0-9\s]+$/,
.min(3, 'Content must be at least 3 characters') "Title can only contain letters, numbers, and spaces"
.max(500, 'Content must be at most 500 characters') )
.matches(/^[a-zA-Z0-9,\s!"'-]*$/, 'Only letters, numbers, commas, spaces, exclamation marks, quotations, and common symbols are allowed') .required("Title is required"),
.required('Content is required') content: Yup.string()
.trim()
.min(3, "Content must be at least 3 characters")
.max(500, "Content must be at most 500 characters")
.matches(
/^[a-zA-Z0-9,\s!"'-]*$/,
"Only letters, numbers, commas, spaces, exclamation marks, quotations, and common symbols are allowed"
)
.required("Content is required"),
}); });
function editPost() { function editPost() {
@@ -35,32 +43,38 @@ function editPost() {
const [post, setPost] = useState({ const [post, setPost] = useState({
title: "", title: "",
content: "" content: "",
}); });
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
useEffect(() => { useEffect(() => {
axios.get(config.serverAddress + `/post/${id}`).then((res) => { instance.get(config.serverAddress + `/post/${id}`).then((res) => {
setPost(res.data); setPost(res.data);
setLoading(false); setLoading(false);
}); });
}, [id]); }, [id]);
const handleSubmit = async (values: any, { setSubmitting, resetForm , setFieldError }: any) => { const handleSubmit = async (
values: any,
{ setSubmitting, resetForm, setFieldError }: any
) => {
try { try {
const response = await axios.put(config.serverAddress + `/post/${id}`, values); // Assuming an API route const response = await instance.put(
config.serverAddress + `/post/${id}`,
values
); // Assuming an API route
if (response.status === 200) { if (response.status === 200) {
console.log('Post updated successfully:', response.data); console.log("Post updated successfully:", response.data);
resetForm(); // Clear form after successful submit resetForm(); // Clear form after successful submit
navigate("/community"); navigate("/community");
} else { } else {
console.error('Error updating post:', response.statusText); console.error("Error updating post:", response.statusText);
} }
} catch (error: any) { } catch (error: any) {
if (error.response && error.response.data && error.response.data.field) { if (error.response && error.response.data && error.response.data.field) {
setFieldError(error.response.data.field, error.response.data.error); setFieldError(error.response.data.field, error.response.data.error);
} else { } else {
console.error('Unexpected error:', error); console.error("Unexpected error:", error);
} }
} finally { } finally {
setSubmitting(false); setSubmitting(false);
@@ -77,18 +91,18 @@ function editPost() {
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="1.5"
stroke="black" stroke="black"
className="size-5"> className="size-5"
>
<path <path
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
d="M9 15 3 9m0 0 6-6M3 9h12a6 6 0 0 1 0 12h-3" /> d="M9 15 3 9m0 0 6-6M3 9h12a6 6 0 0 1 0 12h-3"
/>
</svg> </svg>
</Link> </Link>
</section> </section>
<section className="w-8/12 mx-auto p-5 bg-red-100 border border-none rounded-2xl"> <section className="w-8/12 mx-auto p-5 bg-red-100 border border-none rounded-2xl">
{ {!loading && (
!loading && (
<Formik <Formik
initialValues={post} initialValues={post}
validationSchema={validationSchema} validationSchema={validationSchema}
@@ -136,11 +150,10 @@ function editPost() {
</Form> </Form>
)} )}
</Formik> </Formik>
) )}
}
</section> </section>
</DefaultLayout> </DefaultLayout>
); );
} }
export default editPost export default editPost;

View File

@@ -1,15 +1,19 @@
import { useParams } from "react-router-dom";
import DefaultLayout from "../layouts/default"; import DefaultLayout from "../layouts/default";
import UpdateAccountModule from "../components/UpdateAccountModule"; import UpdateAccountModule from "../components/UpdateAccountModule";
import { useNavigate } from "react-router-dom";
export default function ManageUserAccountPage() { export default function ManageUserAccountPage() {
let { accessToken } = useParams<string>(); // TODO: Replace AT from props with AT from localstorage const navigate = useNavigate();
let accessToken = localStorage.getItem("accessToken");
if (!accessToken) {
navigate("/signin");
}
return ( return (
<DefaultLayout> <DefaultLayout>
<div> <div>
<div className="p-8 flex flex-col gap-8"> <div className="p-8 flex flex-col gap-8">
<UpdateAccountModule accessToken={accessToken!} /> <UpdateAccountModule />
</div> </div>
</div> </div>
</DefaultLayout> </DefaultLayout>

View File

@@ -1,9 +1,17 @@
import { Card, CardBody, Table, TableBody, TableCell, TableColumn, TableHeader, TableRow } from '@nextui-org/react'; import {
Card,
CardBody,
Table,
TableBody,
TableCell,
TableColumn,
TableHeader,
TableRow,
} from "@nextui-org/react";
import DefaultLayout from "../layouts/default"; import DefaultLayout from "../layouts/default";
import { useEffect, useState } from 'react'; import { useEffect, useState } from "react";
import axios from 'axios'; import config from "../config";
import config from '../config'; import instance from "../security/http";
interface Schedule { interface Schedule {
id: number; id: number;
@@ -14,11 +22,11 @@ interface Schedule {
} }
export default function SchedulePage() { export default function SchedulePage() {
const [scheduleList, setScheduleList] = useState<Schedule[]>([]); const [scheduleList, setScheduleList] = useState<Schedule[]>([]);
useEffect(() => { useEffect(() => {
axios.get(config.serverAddress + '/schedule') instance
.get(config.serverAddress + "/schedule")
.then((res) => { .then((res) => {
const schedules = res.data.map((schedule: Schedule) => ({ const schedules = res.data.map((schedule: Schedule) => ({
...schedule, ...schedule,
@@ -27,7 +35,7 @@ export default function SchedulePage() {
setScheduleList(schedules); setScheduleList(schedules);
}) })
.catch((err) => { .catch((err) => {
console.error('Error fetching schedules:', err); console.error("Error fetching schedules:", err);
}); });
}, []); }, []);
@@ -47,8 +55,17 @@ export default function SchedulePage() {
<TableBody> <TableBody>
{scheduleList.map((schedule) => ( {scheduleList.map((schedule) => (
<TableRow key={schedule.id}> <TableRow key={schedule.id}>
<TableCell>{((schedule.dateTime as unknown) as Date).toLocaleDateString()}</TableCell> <TableCell>
<TableCell>{((schedule.dateTime as unknown) as Date).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</TableCell> {(
schedule.dateTime as unknown as Date
).toLocaleDateString()}
</TableCell>
<TableCell>
{(schedule.dateTime as unknown as Date).toLocaleTimeString(
[],
{ hour: "2-digit", minute: "2-digit" }
)}
</TableCell>
<TableCell>{schedule.location}</TableCell> <TableCell>{schedule.location}</TableCell>
<TableCell>{schedule.postalCode}</TableCell> <TableCell>{schedule.postalCode}</TableCell>
<TableCell>{schedule.status}</TableCell> <TableCell>{schedule.status}</TableCell>
@@ -102,7 +119,6 @@ export default function SchedulePage() {
</div> </div>
</div> </div>
</div> </div>
</section> </section>
</DefaultLayout> </DefaultLayout>
); );

View File

@@ -1,4 +1,4 @@
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import DefaultLayout from "../layouts/default"; import DefaultLayout from "../layouts/default";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Button, Card, Link } from "@nextui-org/react"; import { Button, Card, Link } from "@nextui-org/react";
@@ -9,13 +9,15 @@ import { retrieveUserInformation } from "../security/users";
import UserProfilePicture from "../components/UserProfilePicture"; import UserProfilePicture from "../components/UserProfilePicture";
export default function SpringboardPage() { export default function SpringboardPage() {
let { accessToken } = useParams<string>(); // TODO: Replace AT from props with AT from localstorage const navigate = useNavigate();
let accessToken = localStorage.getItem("accessToken");
if (!accessToken) {
navigate("/signin");
}
let [userInformation, setUserInformation] = useState<any>(); let [userInformation, setUserInformation] = useState<any>();
let [accountUnavailable, setAccountUnavaliable] = useState(false); let [accountUnavailable, setAccountUnavaliable] = useState(false);
let timeOfDay = getTimeOfDay(); let timeOfDay = getTimeOfDay();
const navigate = useNavigate();
let greeting = ""; let greeting = "";
if (timeOfDay === 0) { if (timeOfDay === 0) {
greeting = "Good morning"; greeting = "Good morning";
@@ -26,11 +28,11 @@ export default function SpringboardPage() {
} }
useEffect(() => { useEffect(() => {
retrieveUserInformation(accessToken!) retrieveUserInformation()
.then((response) => { .then((response) => {
setUserInformation(response); setUserInformation(response);
}) })
.catch((error) => { .catch((_) => {
setAccountUnavaliable(true); setAccountUnavaliable(true);
}); });
return; return;
@@ -60,7 +62,7 @@ export default function SpringboardPage() {
</div> </div>
} }
onPress={() => { onPress={() => {
navigate("/manage-account/" + accessToken); navigate("/manage-account/");
}} }}
> >
Manage your account Manage your account
@@ -68,7 +70,6 @@ export default function SpringboardPage() {
</div> </div>
<UserProfilePicture <UserProfilePicture
userId={userInformation.id} userId={userInformation.id}
token={accessToken!}
editable={false} editable={false}
/> />
</div> </div>
@@ -77,25 +78,21 @@ export default function SpringboardPage() {
title="Community Forums" title="Community Forums"
subtitle="Be involved in discussions among your neighbourhood" subtitle="Be involved in discussions among your neighbourhood"
linkToPage="" linkToPage=""
></SpringboardButton> ></SpringboardButton>
<SpringboardButton <SpringboardButton
title="Events" title="Events"
subtitle="Participate in exciting upcoming events around Singapore" subtitle="Participate in exciting upcoming events around Singapore"
linkToPage="" linkToPage=""
></SpringboardButton> ></SpringboardButton>
<SpringboardButton <SpringboardButton
title="Home Bill Contest" title="Home Bill Contest"
subtitle="Save resources, win vouchers!" subtitle="Save resources, win vouchers!"
linkToPage="" linkToPage=""
></SpringboardButton> ></SpringboardButton>
<SpringboardButton <SpringboardButton
title="Karang Guni Scheduling" title="Karang Guni Scheduling"
subtitle="Arrange doorstep sales for your old gears with Karang Guni" subtitle="Arrange doorstep sales for your old gears with Karang Guni"
linkToPage="/schedule" linkToPage="/schedule"
></SpringboardButton> ></SpringboardButton>
</div> </div>
<div className="w-full h-[600px] bg-red-500"></div> <div className="w-full h-[600px] bg-red-500"></div>

View File

@@ -0,0 +1,29 @@
import axios from "axios";
import config from "../config";
const instance = axios.create({
baseURL: config.serverAddress,
});
// Add a request interceptor
instance.interceptors.request.use(
function (config) {
console.log("yessss");
// Do something before request is sent
let accessToken = localStorage.getItem("accessToken");
if (accessToken) {
config.headers["Authorization"] = `Bearer ${accessToken}`;
}
if (config.data && config.data.user) {
delete config.data.user;
}
return config;
},
function (error) {
// Do something with request error
localStorage.clear();
return Promise.reject(error);
}
);
export default instance;

View File

@@ -1,20 +1,12 @@
import axios, { AxiosError } from "axios"; import { AxiosError } from "axios";
import config from "../config"; import config from "../config";
import instance from "./http";
export async function retrieveUserInformation(accessToken: string) { export async function retrieveUserInformation() {
try { try {
let userId = await axios.get(`${config.serverAddress}/users/auth`, { let userId = await instance.get(`${config.serverAddress}/users/auth`);
headers: { let userInformation = await instance.get(
Authorization: `Bearer ${accessToken}`, `${config.serverAddress}/users/individual/${userId.data.id}`
},
});
let userInformation = await axios.get(
`${config.serverAddress}/users/individual/${userId.data.id}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
); );
return userInformation.data; return userInformation.data;
} catch (error) { } catch (error) {