Migrated to http instance
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,8 +119,7 @@ export default function SchedulePage() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
</DefaultLayout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
29
client/src/security/http.ts
Normal file
29
client/src/security/http.ts
Normal 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;
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user