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={<SignUpPage />} path="/signup" />
<Route element={<SignInPage />} path="/signin" />
<Route element={<SpringboardPage />} path="/springboard/:accessToken" />
<Route
element={<ManageUserAccountPage />}
path="/manage-account/:accessToken"
/>
<Route element={<SpringboardPage />} path="/springboard" />
<Route element={<ManageUserAccountPage />} path="/manage-account" />
<Route element={<CommunityPage />} path="/community" />
<Route element={<CreatePostPage />} path="/createPost" />

View File

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

View File

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

View File

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

View File

@@ -1,39 +1,27 @@
import axios from "axios";
import React, { useRef, useState } from "react";
import config from "../config";
import { Button, Image } from "@nextui-org/react";
import { popErrorToast } from "../utilities";
import instance from "../security/http";
export default function UserProfilePicture({
userId,
token,
editable,
}: {
userId: string;
token: string;
editable: boolean;
}) {
const fileInputRef = useRef<HTMLInputElement>(null);
const [loading, setLoading] = useState(false);
const uploadProfileImage = async (
userId: string,
file: File,
token: string
) => {
const uploadProfileImage = async (userId: string, file: File) => {
const formData = new FormData();
formData.append("image", file);
try {
const response = await axios.put(
const response = await instance.put(
`${config.serverAddress}/users/profile-image/${userId}`,
formData,
{
headers: {
"Content-Type": "multipart/form-data",
Authorization: `Bearer ${token}`,
},
}
formData
);
return response.data;
} catch (error) {
@@ -57,7 +45,7 @@ export default function UserProfilePicture({
const uploadAndHandleSubmit = async (file: File) => {
setLoading(true);
try {
await uploadProfileImage(userId, file, token);
await uploadProfileImage(userId, file);
} catch (error) {
} finally {
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 { Formik, Form } from "formik";
import * as Yup from 'yup';
import axios from "axios";
import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import NextUIFormikInput from '../components/NextUIFormikInput';
import NextUIFormikTextarea from '../components/NextUIFormikTextarea';
import config from '../config';
import * as Yup from "yup";
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { useNavigate } from "react-router-dom";
import NextUIFormikInput from "../components/NextUIFormikInput";
import NextUIFormikTextarea from "../components/NextUIFormikTextarea";
import config from "../config";
import instance from "../security/http";
const validationSchema = Yup.object({
title: Yup.string().trim()
.min(3, 'Title must be at least 3 characters')
.max(200, 'Title must be at most 200 characters')
.matches(/^[a-zA-Z0-9\s]+$/, "Title can only contain letters, numbers, and spaces")
.required('Title 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')
title: Yup.string()
.trim()
.min(3, "Title must be at least 3 characters")
.max(200, "Title must be at most 200 characters")
.matches(
/^[a-zA-Z0-9\s]+$/,
"Title can only contain letters, numbers, and spaces"
)
.required("Title 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() {
@@ -35,32 +43,38 @@ function editPost() {
const [post, setPost] = useState({
title: "",
content: ""
content: "",
});
const [loading, setLoading] = useState(true);
useEffect(() => {
axios.get(config.serverAddress + `/post/${id}`).then((res) => {
instance.get(config.serverAddress + `/post/${id}`).then((res) => {
setPost(res.data);
setLoading(false);
});
}, [id]);
const handleSubmit = async (values: any, { setSubmitting, resetForm , setFieldError }: any) => {
const handleSubmit = async (
values: any,
{ setSubmitting, resetForm, setFieldError }: any
) => {
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) {
console.log('Post updated successfully:', response.data);
console.log("Post updated successfully:", response.data);
resetForm(); // Clear form after successful submit
navigate("/community");
} else {
console.error('Error updating post:', response.statusText);
console.error("Error updating post:", response.statusText);
}
} catch (error: any) {
if (error.response && error.response.data && error.response.data.field) {
setFieldError(error.response.data.field, error.response.data.error);
} else {
console.error('Unexpected error:', error);
console.error("Unexpected error:", error);
}
} finally {
setSubmitting(false);
@@ -77,18 +91,18 @@ function editPost() {
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="black"
className="size-5">
className="size-5"
>
<path
stroke-linecap="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>
</Link>
</section>
<section className="w-8/12 mx-auto p-5 bg-red-100 border border-none rounded-2xl">
{
!loading && (
{!loading && (
<Formik
initialValues={post}
validationSchema={validationSchema}
@@ -136,11 +150,10 @@ function editPost() {
</Form>
)}
</Formik>
)
}
)}
</section>
</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 UpdateAccountModule from "../components/UpdateAccountModule";
import { useNavigate } from "react-router-dom";
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 (
<DefaultLayout>
<div>
<div className="p-8 flex flex-col gap-8">
<UpdateAccountModule accessToken={accessToken!} />
<UpdateAccountModule />
</div>
</div>
</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 { useEffect, useState } from 'react';
import axios from 'axios';
import config from '../config';
import { useEffect, useState } from "react";
import config from "../config";
import instance from "../security/http";
interface Schedule {
id: number;
@@ -14,11 +22,11 @@ interface Schedule {
}
export default function SchedulePage() {
const [scheduleList, setScheduleList] = useState<Schedule[]>([]);
useEffect(() => {
axios.get(config.serverAddress + '/schedule')
instance
.get(config.serverAddress + "/schedule")
.then((res) => {
const schedules = res.data.map((schedule: Schedule) => ({
...schedule,
@@ -27,7 +35,7 @@ export default function SchedulePage() {
setScheduleList(schedules);
})
.catch((err) => {
console.error('Error fetching schedules:', err);
console.error("Error fetching schedules:", err);
});
}, []);
@@ -47,8 +55,17 @@ export default function SchedulePage() {
<TableBody>
{scheduleList.map((schedule) => (
<TableRow key={schedule.id}>
<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.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.postalCode}</TableCell>
<TableCell>{schedule.status}</TableCell>
@@ -102,7 +119,6 @@ export default function SchedulePage() {
</div>
</div>
</div>
</section>
</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 { useEffect, useState } from "react";
import { Button, Card, Link } from "@nextui-org/react";
@@ -9,13 +9,15 @@ import { retrieveUserInformation } from "../security/users";
import UserProfilePicture from "../components/UserProfilePicture";
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 [accountUnavailable, setAccountUnavaliable] = useState(false);
let timeOfDay = getTimeOfDay();
const navigate = useNavigate();
let greeting = "";
if (timeOfDay === 0) {
greeting = "Good morning";
@@ -26,11 +28,11 @@ export default function SpringboardPage() {
}
useEffect(() => {
retrieveUserInformation(accessToken!)
retrieveUserInformation()
.then((response) => {
setUserInformation(response);
})
.catch((error) => {
.catch((_) => {
setAccountUnavaliable(true);
});
return;
@@ -60,7 +62,7 @@ export default function SpringboardPage() {
</div>
}
onPress={() => {
navigate("/manage-account/" + accessToken);
navigate("/manage-account/");
}}
>
Manage your account
@@ -68,7 +70,6 @@ export default function SpringboardPage() {
</div>
<UserProfilePicture
userId={userInformation.id}
token={accessToken!}
editable={false}
/>
</div>
@@ -77,25 +78,21 @@ export default function SpringboardPage() {
title="Community Forums"
subtitle="Be involved in discussions among your neighbourhood"
linkToPage=""
></SpringboardButton>
<SpringboardButton
title="Events"
subtitle="Participate in exciting upcoming events around Singapore"
linkToPage=""
></SpringboardButton>
<SpringboardButton
title="Home Bill Contest"
subtitle="Save resources, win vouchers!"
linkToPage=""
></SpringboardButton>
<SpringboardButton
title="Karang Guni Scheduling"
subtitle="Arrange doorstep sales for your old gears with Karang Guni"
linkToPage="/schedule"
></SpringboardButton>
</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 instance from "./http";
export async function retrieveUserInformation(accessToken: string) {
export async function retrieveUserInformation() {
try {
let userId = await axios.get(`${config.serverAddress}/users/auth`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
let userInformation = await axios.get(
`${config.serverAddress}/users/individual/${userId.data.id}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
let userId = await instance.get(`${config.serverAddress}/users/auth`);
let userInformation = await instance.get(
`${config.serverAddress}/users/individual/${userId.data.id}`
);
return userInformation.data;
} catch (error) {