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,16 +16,13 @@ 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" />
<Route element={<EditPostPage/>} path="/editPost/:id" />
<Route element={<SchedulePage/>} path="/schedule"/>
<Route element={<EditPostPage />} path="/editPost/:id" />
<Route element={<SchedulePage />} path="/schedule" />
</Routes>
);
}

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,146 +1,159 @@
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() {
const { id } = useParams();
const navigate = useNavigate();
const { id } = useParams();
const navigate = useNavigate();
// const initialValues = {
// title: '',
// content: '',
// tags: ''
// };
// const initialValues = {
// title: '',
// content: '',
// tags: ''
// };
const [post, setPost] = useState({
title: "",
content: ""
const [post, setPost] = useState({
title: "",
content: "",
});
const [loading, setLoading] = useState(true);
useEffect(() => {
instance.get(config.serverAddress + `/post/${id}`).then((res) => {
setPost(res.data);
setLoading(false);
});
const [loading, setLoading] = useState(true);
}, [id]);
useEffect(() => {
axios.get(config.serverAddress + `/post/${id}`).then((res) => {
setPost(res.data);
setLoading(false);
});
}, [id]);
const handleSubmit = async (
values: any,
{ setSubmitting, resetForm, setFieldError }: any
) => {
try {
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);
resetForm(); // Clear form after successful submit
navigate("/community");
} else {
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);
}
} finally {
setSubmitting(false);
}
};
const handleSubmit = async (values: any, { setSubmitting, resetForm , setFieldError }: any) => {
try {
const response = await axios.put(config.serverAddress + `/post/${id}`, values); // Assuming an API route
if (response.status === 200) {
console.log('Post updated successfully:', response.data);
resetForm(); // Clear form after successful submit
navigate("/community");
} else {
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);
}
} finally {
setSubmitting(false);
}
};
return (
<DefaultLayout>
<section className="w-8/12 mx-auto">
<Link href="/community">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="black"
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" />
</svg>
</Link>
</section>
<section className="w-8/12 mx-auto p-5 bg-red-100 border border-none rounded-2xl">
{
!loading && (
<Formik
initialValues={post}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{({ isValid, dirty, isSubmitting }) => (
<Form className="flex flex-col gap-5">
<div>
<NextUIFormikInput
label="Title"
name="title"
type="text"
placeholder="Enter your post title"
labelPlacement="inside"
/>
</div>
<div className="text-sm">
<p>Image</p>
</div>
<div>
<NextUIFormikTextarea
label="Content"
name="content"
placeholder="Write your post content here"
/>
</div>
<div>
<NextUIFormikInput
label="Tags (Optional)"
name="tags"
type="text"
placeholder="Enter tags"
labelPlacement="inside"
/>
</div>
<div className="flex flex-row-reverse border">
<Button
type="submit"
className="bg-red-color text-white text-xl w-1/6"
disabled={!isValid || !dirty || isSubmitting}
>
<p>Update</p>
</Button>
</div>
</Form>
)}
</Formik>
)
}
</section>
</DefaultLayout>
);
return (
<DefaultLayout>
<section className="w-8/12 mx-auto">
<Link href="/community">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="black"
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"
/>
</svg>
</Link>
</section>
<section className="w-8/12 mx-auto p-5 bg-red-100 border border-none rounded-2xl">
{!loading && (
<Formik
initialValues={post}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{({ isValid, dirty, isSubmitting }) => (
<Form className="flex flex-col gap-5">
<div>
<NextUIFormikInput
label="Title"
name="title"
type="text"
placeholder="Enter your post title"
labelPlacement="inside"
/>
</div>
<div className="text-sm">
<p>Image</p>
</div>
<div>
<NextUIFormikTextarea
label="Content"
name="content"
placeholder="Write your post content here"
/>
</div>
<div>
<NextUIFormikInput
label="Tags (Optional)"
name="tags"
type="text"
placeholder="Enter tags"
labelPlacement="inside"
/>
</div>
<div className="flex flex-row-reverse border">
<Button
type="submit"
className="bg-red-color text-white text-xl w-1/6"
disabled={!isValid || !dirty || isSubmitting}
>
<p>Update</p>
</Button>
</div>
</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,83 +35,91 @@ export default function SchedulePage() {
setScheduleList(schedules);
})
.catch((err) => {
console.error('Error fetching schedules:', err);
console.error("Error fetching schedules:", err);
});
}, []);
}, []);
return (
<DefaultLayout>
<section className="flex flex-col items-center justify-center gap-4 py-8 md:py-10">
<h1>Karang Guni Schedule</h1>
<div className="flex flex-col gap-8">
<Table>
<TableHeader>
<TableColumn>Date</TableColumn>
<TableColumn>Time</TableColumn>
<TableColumn>Location</TableColumn>
<TableColumn>Postal Code</TableColumn>
<TableColumn>Status</TableColumn>
</TableHeader>
<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.location}</TableCell>
<TableCell>{schedule.postalCode}</TableCell>
<TableCell>{schedule.status}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<div>
return (
<DefaultLayout>
<section className="flex flex-col items-center justify-center gap-4 py-8 md:py-10">
<h1>Karang Guni Schedule</h1>
<div className="flex flex-col gap-8">
<Table>
<TableHeader>
<TableColumn>Date</TableColumn>
<TableColumn>Time</TableColumn>
<TableColumn>Location</TableColumn>
<TableColumn>Postal Code</TableColumn>
<TableColumn>Status</TableColumn>
</TableHeader>
<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.location}</TableCell>
<TableCell>{schedule.postalCode}</TableCell>
<TableCell>{schedule.status}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<div>
<div>
<div className="flex flex-row gap-20 ">
<div>
<div className="flex flex-row gap-20 ">
<div>
<Card>
<CardBody>
<p className="text-lg">Paper</p>
<p className="text-xl">$0.05 to 0.20/KG</p>
<ul>
<li>Cardboard ($0.20/kg)</li>
<li>Newspaper and B&W ($0.11/kg)</li>
<li>Mix paper ($0.05/kg)</li>
</ul>
</CardBody>
</Card>
</div>
<div>
<Card>
<CardBody>
<p className="text-lg">Paper</p>
<p className="text-xl">$0.05 to 0.20/KG</p>
<ul>
<li>Cardboard ($0.20/kg)</li>
<li>Newspaper and B&W ($0.11/kg)</li>
<li>Mix paper ($0.05/kg)</li>
</ul>
</CardBody>
</Card>
</div>
<div>
<Card>
<CardBody>
<p className="text-lg">Paper</p>
<p className="text-xl">$0.05 to 0.20/KG</p>
<ul>
<li>Cardboard ($0.20/kg)</li>
<li>Newspaper and B&W ($0.11/kg)</li>
<li>Mix paper ($0.05/kg)</li>
</ul>
</CardBody>
</Card>
</div>
</div>
<Card>
<CardBody>
<p className="text-lg">Paper</p>
<p className="text-xl">$0.05 to 0.20/KG</p>
<ul>
<li>Cardboard ($0.20/kg)</li>
<li>Newspaper and B&W ($0.11/kg)</li>
<li>Mix paper ($0.05/kg)</li>
</ul>
</CardBody>
</Card>
</div>
<div>
<Card>
<CardBody>
<p className="text-lg">Paper</p>
<p className="text-xl">$0.05 to 0.20/KG</p>
<ul>
<li>Cardboard ($0.20/kg)</li>
<li>Newspaper and B&W ($0.11/kg)</li>
<li>Mix paper ($0.05/kg)</li>
</ul>
</CardBody>
</Card>
</div>
<div>
<Card>
<CardBody>
<p className="text-lg">Paper</p>
<p className="text-xl">$0.05 to 0.20/KG</p>
<ul>
<li>Cardboard ($0.20/kg)</li>
<li>Newspaper and B&W ($0.11/kg)</li>
<li>Mix paper ($0.05/kg)</li>
</ul>
</CardBody>
</Card>
</div>
</div>
</div>
</section>
</DefaultLayout>
);
}
</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
<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) {