Ver a proveniência

[feature] update axios

feature/axios_provider
jason.lam há 4 meses
ascendente
cometimento
e0e1eef6d4
9 ficheiros alterados com 192 adições e 34 eliminações
  1. +2
    -1
      .env.development
  2. +2
    -1
      .env.production
  3. +38
    -0
      src/app/(main)/axios/AxiosProvider.tsx
  4. +56
    -0
      src/app/(main)/axios/axiosInstance.ts
  5. +21
    -18
      src/app/(main)/layout.tsx
  6. +55
    -13
      src/components/ItemsSearch/ItemsSearch.tsx
  7. +7
    -1
      src/components/LoginPage/LoginForm.tsx
  8. +10
    -0
      src/components/SearchResults/SearchResults.tsx
  9. +1
    -0
      src/config/api.ts

+ 2
- 1
.env.development Ver ficheiro

@@ -1,2 +1,3 @@
API_URL=http://localhost:8090/api
NEXTAUTH_SECRET=secret
NEXTAUTH_SECRET=secret
NEXT_PUBLIC_API_URL=http://localhost:8090/api

+ 2
- 1
.env.production Ver ficheiro

@@ -1,3 +1,4 @@
API_URL=http://localhost:8090/api
NEXTAUTH_SECRET=secret
NEXTAUTH_URL=https://fpsms-uat.2fi-solutions.com
NEXTAUTH_URL=https://fpsms-uat.2fi-solutions.com
NEXT_PUBLIC_API_URL=http://localhost:8090/api

+ 38
- 0
src/app/(main)/axios/AxiosProvider.tsx Ver ficheiro

@@ -0,0 +1,38 @@
"use client";
import React, {createContext, useContext, useEffect, useState} from 'react';
import axiosInstance, {SetupAxiosInterceptors} from './axiosInstance';

const AxiosContext = createContext(axiosInstance);
const TokenContext = createContext({
setAccessToken: (token: string | null) => {},
});

export const AxiosProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [accessToken, setAccessToken] = useState<string | null>(localStorage.getItem("accessToken"));

useEffect(() => {
if (accessToken) {
axiosInstance.defaults.headers.Authorization = `Bearer ${accessToken}`;
SetupAxiosInterceptors(accessToken);
console.log("[debug] Updated accessToken:", accessToken);
}
}, [accessToken]);

return (
<AxiosContext.Provider value={axiosInstance}>
<TokenContext.Provider value={{ setAccessToken }}>
{children}
</TokenContext.Provider>
</AxiosContext.Provider>
);
};

// Custom hook to use Axios instance
export const useAxios = () => {
return useContext(AxiosContext);
};

// Custom hook to manage access token
export const useToken = () => {
return useContext(TokenContext);
};

+ 56
- 0
src/app/(main)/axios/axiosInstance.ts Ver ficheiro

@@ -0,0 +1,56 @@
import axios from 'axios';

const axiosInstance = axios.create({
baseURL: process.env.API_URL,
});

// Clear existing interceptors to prevent multiple registrations
const clearInterceptors = () => {
const requestInterceptor = axiosInstance.interceptors.request.use();
const responseInterceptor = axiosInstance.interceptors.response.use();

if (requestInterceptor) {
axiosInstance.interceptors.request.eject(requestInterceptor);
}

if (responseInterceptor) {
axiosInstance.interceptors.response.eject(responseInterceptor);
}
};

export const SetupAxiosInterceptors = (inputToken: string | null) => {
console.log("[debug] set up interceptors", inputToken);

// Clear existing interceptors
clearInterceptors();

// Request interceptor
axiosInstance.interceptors.request.use(
(config) => {
// Use the token passed in or retrieve from localStorage
const token = inputToken ?? localStorage.getItem('accessToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);

console.log("[debug] set up interceptors2", inputToken);

// Response interceptor
axiosInstance.interceptors.response.use(
(response) => {
return response;
},
(error) => {
console.error('API Error:', error.response?.data || error);
return Promise.reject(error);
}
);
};

export default axiosInstance;

+ 21
- 18
src/app/(main)/layout.tsx Ver ficheiro

@@ -6,6 +6,7 @@ import Box from "@mui/material/Box";
import { NAVIGATION_CONTENT_WIDTH } from "@/config/uiConfig";
import Stack from "@mui/material/Stack";
import Breadcrumb from "@/components/Breadcrumb";
import {AxiosProvider} from "@/app/(main)/axios/AxiosProvider";

export default async function MainLayout({
children,
@@ -19,23 +20,25 @@ export default async function MainLayout({
}

return (
<>
<AppBar
profileName={session.user.name!}
avatarImageSrc={session.user.image || undefined}
/>
<Box
component="main"
sx={{
marginInlineStart: { xs: 0, xl: NAVIGATION_CONTENT_WIDTH },
padding: { xs: "1rem", sm: "1.5rem", lg: "3rem" },
}}
>
<Stack spacing={2}>
<Breadcrumb />
{children}
</Stack>
</Box>
</>
<AxiosProvider>
<>
<AppBar
profileName={session.user.name!}
avatarImageSrc={session.user.image || undefined}
/>
<Box
component="main"
sx={{
marginInlineStart: { xs: 0, xl: NAVIGATION_CONTENT_WIDTH },
padding: { xs: "1rem", sm: "1.5rem", lg: "3rem" },
}}
>
<Stack spacing={2}>
<Breadcrumb />
{children}
</Stack>
</Box>
</>
</AxiosProvider>
);
}

+ 55
- 13
src/components/ItemsSearch/ItemsSearch.tsx Ver ficheiro

@@ -1,14 +1,17 @@
"use client";

import { useCallback, useMemo, useState } from "react";
import {useCallback, useEffect, useMemo, useState} from "react";
import SearchBox, { Criterion } from "../SearchBox";
import { ItemsResult } from "@/app/api/settings/item";
import {fetchAllItemsByPage, ItemsResult} from "@/app/api/settings/item";
import { useTranslation } from "react-i18next";
import SearchResults, { Column } from "../SearchResults";
import { EditNote } from "@mui/icons-material";
import { useRouter, useSearchParams } from "next/navigation";
import { GridDeleteIcon } from "@mui/x-data-grid";
import { TypeEnum } from "@/app/utils/typeEnum";
import axios from "axios";
import {BASE_API_URL, NEXT_PUBLIC_API_URL} from "@/config/api";
import axiosInstance from "@/app/(main)/axios/axiosInstance";

type Props = {
items: ItemsResult[];
@@ -20,6 +23,11 @@ const ItemsSearch: React.FC<Props> = ({ items }) => {
const [filteredItems, setFilteredItems] = useState<ItemsResult[]>(items);
const { t } = useTranslation("items");
const router = useRouter();
const [filterObj, setFilterObj] = useState();
const [pagingController, setPagingController] = useState({
pageNum: 1,
pageSize: 10,
})

const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => {
@@ -70,27 +78,61 @@ const ItemsSearch: React.FC<Props> = ({ items }) => {
[filteredItems]
);

const onReset = useCallback(() => {
setFilteredItems(items);
}, [items]);
useEffect(() => {
if (filterObj) {
refetchData(filterObj);
}
}, [filterObj, pagingController]);

const refetchData = async (filterObj: SearchQuery) => {
// Make sure the API endpoint is correct
const params ={
pageNum: pagingController.pageNum,
pageSize: pagingController.pageSize,
...filterObj,
}
try {
console.log("[debug] axiosInstance", axiosInstance)
const response = await axiosInstance.get<ItemsResult[]>(`${NEXT_PUBLIC_API_URL}/items/getRecordByPage`, { params });
console.log("[debug] resposne", response)
setFilteredItems(response.data.records);
return response; // Return the data from the response
} catch (error) {
console.error('Error fetching items:', error);
throw error; // Rethrow the error for further handling
}
};

const onReset = useCallback(() => {
setFilteredItems(items);
}, [items]);

return (
<>
<SearchBox
criteria={searchCriteria}
onSearch={(query) => {
setFilteredItems(
items.filter((pm) => {
return (
pm.code.toLowerCase().includes(query.code.toLowerCase()) &&
pm.name.toLowerCase().includes(query.name.toLowerCase())
);
// setFilteredItems(
// items.filter((pm) => {
// return (
// pm.code.toLowerCase().includes(query.code.toLowerCase()) &&
// pm.name.toLowerCase().includes(query.name.toLowerCase())
// );
// })
// );
// @ts-ignore
setFilterObj({
...query
})
);
}}
onReset={onReset}
/>
<SearchResults<ItemsResult> items={filteredItems} columns={columns} />
<SearchResults<ItemsResult>
items={filteredItems}
columns={columns}
setPagingController={setPagingController}
pagingController={pagingController}
/>
</>
);
};


+ 7
- 1
src/components/LoginPage/LoginForm.tsx Ver ficheiro

@@ -12,6 +12,8 @@ import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { SubmitHandler, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import {SetupAxiosInterceptors} from "@/app/(main)/axios/axiosInstance";
import {useToken} from "@/app/(main)/axios/AxiosProvider";

type LoginFields = {
username: string;
@@ -47,6 +49,7 @@ const LoginForm: React.FC = () => {
const [serverError, setServerError] = useState<string>();

const router = useRouter();
const { setAccessToken } = useToken();

const onSubmit: SubmitHandler<LoginFields> = async (data) => {
const res = await signIn("credentials", {
@@ -62,7 +65,10 @@ const LoginForm: React.FC = () => {

// set auth to local storage
const session = await getSession() as SessionWithAbilities

// @ts-ignore
window.localStorage.setItem("accessToken", session?.accessToken)
setAccessToken(session?.accessToken);
SetupAxiosInterceptors(session?.accessToken);
// console.log(session)
window.localStorage.setItem("abilities", JSON.stringify(session?.abilities))



+ 10
- 0
src/components/SearchResults/SearchResults.tsx Ver ficheiro

@@ -48,6 +48,8 @@ function SearchResults<T extends ResultWithId>({
items,
columns,
noWrapper,
pagingController,
setPagingController
}: Props<T>) {
const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(10);
@@ -57,6 +59,10 @@ function SearchResults<T extends ResultWithId>({
newPage,
) => {
setPage(newPage);
setPagingController({
...pagingController,
pageNum: newPage,
})
};

const handleChangeRowsPerPage: TablePaginationProps["onRowsPerPageChange"] = (
@@ -64,6 +70,10 @@ function SearchResults<T extends ResultWithId>({
) => {
setRowsPerPage(+event.target.value);
setPage(0);
setPagingController({
...pagingController,
pageNum: +event.target.value,
})
};

const table = (


+ 1
- 0
src/config/api.ts Ver ficheiro

@@ -1,2 +1,3 @@
export const BASE_API_URL = `${process.env.API_URL}`;
export const LOGIN_API_PATH = `${BASE_API_URL}/login`;
export const NEXT_PUBLIC_API_URL= `${process.env.NEXT_PUBLIC_API_URL}`;

Carregando…
Cancelar
Guardar