Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ANT-2726 - Link with gaia #56

Merged
merged 20 commits into from
Feb 10, 2025
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"i18next": "^23.14.0",
"mutative": "^1.0.6",
"postcss": "^8.4.39",
"oidc-client-ts": "^3.1.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^15.0.3",
Expand Down
69 changes: 42 additions & 27 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

import { Suspense } from 'react';
import { Suspense, useEffect } from 'react';
import { Route, Routes } from 'react-router-dom';
import './App.css';
import ThemeHandler from './components/common/handler/ThemeHandler';
import PegaseStar from './components/pegase/star/PegaseStar';
import { UserContext } from '@/store/contexts/UserContext';
import { UserSettingsContext } from '@/store/contexts/UserSettingsContext.tsx';
import { THEME_COLOR } from '@/shared/types';
import { menuBottomData, menuTopData } from './routes';
import { PegaseToastContainer } from './shared/notification/containers';
Expand All @@ -19,36 +19,51 @@ import { RdsNavbar } from 'rte-design-system-react';
import { navBarConfig } from '@/shared/const/navBarConfig';
import { useTranslation } from 'react-i18next';
import { translateMenuItemLabel } from '@/shared/utils/textUtils.ts';
import { PEGASE_NAVBAR_ID } from '@/shared/constants.ts';
import UserProvider from '@/store/contexts/UserProvider.tsx';
import { AuthService } from '@/shared/services/authService.ts';

function App() {
const { t } = useTranslation();

useEffect(() => {
const handleAuth = async () => {
if (window.location.href.includes('code=')) {
await AuthService.handleCallback();
window.location.replace('/'); // Redirect to home page after login
}
};
void handleAuth();
}, []);

return (
<div className="flex h-screen w-screen dark:bg-gray-900 dark:text-gray-200">
<UserContext.Provider initialState={{ theme: THEME_COLOR.LIGHT }}>
<ThemeHandler />
<PegaseToastContainer />
<RdsNavbar
id={'main-nav-bar'}
topItems={translateMenuItemLabel(menuTopData, t)}
bottomItems={translateMenuItemLabel(menuBottomData, t)}
headerLink={'/'}
config={navBarConfig}
/>
<div className="flex h-full w-full flex-col">
<PegaseStar />
<Suspense>
<Routes>
<Route path="/study/:studyName" element={<StudyDetails />} />
<Route path="/project/:projectName" element={<ProjectDetails />} />
{Object.entries([...menuBottomData, ...menuTopData]).map(([key, route]) => (
<Route key={key} path={route.path} Component={route.component} />
))}
</Routes>
</Suspense>
</div>
</UserContext.Provider>
</div>
<UserProvider initialValue={{ user: null }}>
<div className="flex h-screen w-screen dark:bg-gray-900 dark:text-gray-200">
<UserSettingsContext.Provider initialState={{ theme: THEME_COLOR.LIGHT }}>
<ThemeHandler />
<PegaseToastContainer />
<RdsNavbar
id={PEGASE_NAVBAR_ID}
topItems={translateMenuItemLabel(menuTopData, t)}
bottomItems={translateMenuItemLabel(menuBottomData, t)}
headerLink={'/'}
config={navBarConfig}
/>
<div className="flex h-full w-full flex-col">
<PegaseStar />
<Suspense>
<Routes>
<Route path="/study/:studyName" element={<StudyDetails />} />
<Route path="/project/:projectName" element={<ProjectDetails />} />
{Object.entries([...menuBottomData, ...menuTopData]).map(([key, route]) => (
<Route key={key} path={route.path} Component={route.component} />
))}
</Routes>
</Suspense>
</div>
</UserSettingsContext.Provider>
</div>
</UserProvider>
);
}

Expand Down
4 changes: 2 additions & 2 deletions src/components/common/handler/ThemeHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

import { UserContext } from '@/store/contexts/UserContext';
import { UserSettingsContext } from '@/store/contexts/UserSettingsContext.tsx';
import usePrevious from '@/hooks/common/usePrevious';
import { THEME_COLOR } from '@/shared/types';
import { useEffect } from 'react';

const ThemeHandler = () => {
const darkThemeMq = window.matchMedia('(prefers-color-scheme: dark)');
const themeColor = UserContext.useStore((store) => store.theme);
const themeColor = UserSettingsContext.useStore((store) => store.theme);
const previousTheme = usePrevious(themeColor, undefined);
useEffect(() => {
if (previousTheme) {
Expand Down
10 changes: 5 additions & 5 deletions src/components/common/handler/test/ThemeHandler.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import { render } from '@testing-library/react';
import { describe, expect, it, Mock, vi } from 'vitest';
import { THEME_COLOR } from '@/shared/types';
import ThemeHandler from '../ThemeHandler';
import { UserContext } from '@/store/contexts/UserContext';
import { UserSettingsContext } from '@/store/contexts/UserSettingsContext.tsx';
import usePrevious from '@/hooks/common/usePrevious';

// Mocking the UserContext and usePrevious hook
vi.mock('@/store/contexts/UserContext', () => ({
UserContext: {
// Mocking the UserSettingsContext and usePrevious hook
vi.mock('@/store/contexts/UserSettingsContext', () => ({
UserSettingsContext: {
useStore: vi.fn(),
},
}));
Expand All @@ -23,7 +23,7 @@ vi.mock('@/hooks/common/usePrevious', () => ({
}));

describe('ThemeHandler', () => {
const mockUseStore = UserContext.useStore as Mock;
const mockUseStore = UserSettingsContext.useStore as Mock;
const mockUsePrevious = usePrevious as Mock;

beforeEach(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

import { User } from '@/shared/types/common/User.type';
import StdAvatar from '../stdAvatar/StdAvatar';
import { AvatarSize } from '../stdAvatar/StdAvatar';
import { UserInfo } from '@/shared/types/common/User.type';
import StdAvatar, { AvatarSize } from '../stdAvatar/StdAvatar';
import { classBuilder as groupContainerClassBuilder } from './avatarGroupClassBuilder';
import { getColor, getUserFullname, getUserInitials, splitUserList } from './avatarTools';
import { useRdsId } from 'rte-design-system-react';

type StdAvatarGroup = {
users: User[];
users: UserInfo[];
avatarSize: AvatarSize;
id?: string;
};
Expand Down
12 changes: 6 additions & 6 deletions src/components/common/layout/stdAvatarGroup/avatarTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,41 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

import { User } from '@/shared/types/common/User.type';
import { UserInfo } from '@/shared/types/common/User.type';

export const AVATAR_COLORS = ['green', 'purple', 'blue', 'pink', 'gray', 'orange'] as const;
const USER_SEPARATOR = ' - ';
const MAX_USER_CHIP = 3;

export const splitUserList = (users: User[]) => {
export const splitUserList = (users: UserInfo[]) => {
if (users.length <= MAX_USER_CHIP) {
return users;
}
const [firstUser, secondUser, ...otherUsers] = users;
return [firstUser, secondUser, otherUsers];
};

export const getInitials = (user: User) => {
export const getInitials = (user: UserInfo) => {
const [firstName, lastName = ''] = user.fullname.split(' ');
return lastName.charAt(0) + firstName.charAt(0);
};

export const getUserInitials = (users: User | User[]) => {
export const getUserInitials = (users: UserInfo | UserInfo[]) => {
if (!Array.isArray(users)) {
return getInitials(users);
}
return `+${users.length}`;
};

export const getUserFullname = (users: User | User[]) => {
export const getUserFullname = (users: UserInfo | UserInfo[]) => {
if (!Array.isArray(users)) {
return users.fullname;
}
return users.map((user) => user.fullname).join(USER_SEPARATOR);
};

//assign a random color from COLORS
export const getColor = (users: User | User[]) => {
export const getColor = (users: UserInfo | UserInfo[]) => {
if (!Array.isArray(users)) {
return AVATAR_COLORS[
users.fullname.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0) % AVATAR_COLORS.length
Expand Down
8 changes: 7 additions & 1 deletion src/envVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@

type EnvVariableType = {
VITE_BACK_END_BASE_URL: string;
VITE_OAUTH2_CLIENT_ID: string;
VITE_OAUTH2_REDIRECT_URL: string;
VITE_OAUTH2_AUTHORITY: string;
};

// Environment Variable Template to Be Replaced at Runtime
export const envVariables: EnvVariableType = {
VITE_BACK_END_BASE_URL: '${URL_BACKEND}',
VITE_OAUTH2_CLIENT_ID: '${PEGASE_OAUTH2_CLIENT_ID}',
VITE_OAUTH2_REDIRECT_URL: '${PEGASE_OAUTH2_REDIRECT_URL}',
VITE_OAUTH2_AUTHORITY: '${PEGASE_OAUTH2_AUTHORITY}',
};
export const getEnvVariables = (key: keyof EnvVariableType) =>
export const getEnvVariables = (key: keyof EnvVariableType): string =>
envVariables[key].startsWith('$') ? (import.meta.env[key] as string) : envVariables[key];
37 changes: 18 additions & 19 deletions src/hooks/test/useFetchProjectList.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,22 @@ vi.mock('@/envVariables', () => ({

describe('useFetchProjectList', () => {
beforeEach(() => {
global.fetch = vi.fn(() =>
Promise.resolve({
json: () =>
Promise.resolve({
content: [
{
projectId: '1',
name: 'Project 1',
tags: ['Tag1', 'Tag2'],
creationDate: '2023-10-01',
createdBy: 'User A',
},
],
totalElements: 1,
}),
}),
) as unknown as typeof fetch;
global.fetch = vi.fn().mockResolvedValueOnce({
ok: true,
json: async () =>
Promise.resolve({
content: [
{
projectId: '1',
name: 'Project 1',
tags: ['Tag1', 'Tag2'],
creationDate: '2023-10-01',
createdBy: 'User A',
},
],
totalElements: 1,
}),
});
});

afterEach(() => {
Expand Down Expand Up @@ -60,15 +59,15 @@ describe('useFetchProjectList', () => {
renderHook(() => useFetchProjectList('test', 0, 9));

await waitFor(() => {
expect(global.fetch).toHaveBeenCalledWith('https://mockapi.com/v1/project/search?page=1&size=9&search=test');
expect(global.fetch).toHaveBeenCalledWith('https://mockapi.com/v1/project/search?page=1&size=9&search=test', {});
});
});

it('fetches projects with pagination', async () => {
renderHook(() => useFetchProjectList('', 1, 9));

await waitFor(() => {
expect(global.fetch).toHaveBeenCalledWith('https://mockapi.com/v1/project/search?page=2&size=9&search=');
expect(global.fetch).toHaveBeenCalledWith('https://mockapi.com/v1/project/search?page=2&size=9&search=', {});
});
});
});
9 changes: 6 additions & 3 deletions src/hooks/test/useStudyTableDisplay.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ vi.mock('@/envVariables', () => ({
describe('useStudyTableDisplay', () => {
beforeEach(() => {
global.fetch = vi.fn();
vi.restoreAllMocks();
});

afterEach(() => {
Expand Down Expand Up @@ -48,11 +49,11 @@ describe('useStudyTableDisplay', () => {

global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: async () => mockResponse,
json: async () => Promise.resolve(mockResponse),
});

const { result } = renderHook(() =>
useStudyTableDisplay({ searchTerm: 'test', sortBy: { status: 'desc' }, reloadStudies: true }),
useStudyTableDisplay({ searchTerm: 'test', sortBy: { status: 'desc' }, reloadStudies: false }),
);
await waitFor(() => {
expect(result.current.rows).toHaveLength(2);
Expand All @@ -61,16 +62,18 @@ describe('useStudyTableDisplay', () => {
//expect(global.fetch).toHaveBeenCalledTimes(1); TODO: ANT-2719
expect(global.fetch).toHaveBeenCalledWith(
'https://mockapi.com/v1/study/search?page=1&size=9&projectId=&search=test&sortColumn=status&sortDirection=desc',
{},
);
});

await act(async () => {
renderHook(() => useStudyTableDisplay({ searchTerm: 'mouad', sortBy: { project: 'asc' }, reloadStudies: true }));
renderHook(() => useStudyTableDisplay({ searchTerm: 'mouad', sortBy: { project: 'asc' }, reloadStudies: false }));
});

//expect(global.fetch).toHaveBeenCalledTimes(1); TODO: ANT-2719
expect(global.fetch).toHaveBeenCalledWith(
'https://mockapi.com/v1/study/search?page=1&size=9&projectId=&search=mouad&sortColumn=project&sortDirection=asc',
{},
);
});

Expand Down
29 changes: 18 additions & 11 deletions src/hooks/useFetchProjectList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,31 @@ import { ProjectActionType, ProjectInfo } from '@/shared/types/pegase/Project.ty
import { fetchProjectFromSearchTerm } from '@/shared/services/projectService.ts';
import { PROJECT_ACTION } from '@/shared/enum/project.ts';
import { useProjectDispatch } from '@/store/contexts/ProjectContext.tsx';
import { PaginatedResponse } from '@/shared/types';

export const useFetchProjectList = (searchTerm: string, current: number, intervalSize: number) => {
const [projects, setProjects] = useState<ProjectInfo[]>([]);
const [count, setCount] = useState(0);
const dispatch = useProjectDispatch();

const fetchProjects = useCallback(
async (searchTerm: string, current: number, intervalSize: number) => {
fetchProjectFromSearchTerm(searchTerm, current, intervalSize)
.then((json) => {
dispatch?.({
type: PROJECT_ACTION.INIT_PROJECT_LIST,
payload: json.content,
} as ProjectActionType);
setProjects(json.content);
setCount(json.totalElements);
})
.catch((error) => console.error(error));
async (term: string, currentPage: number, size: number) => {
try {
const { content, totalElements } = (await fetchProjectFromSearchTerm(
term,
currentPage,
size,
)) as PaginatedResponse<ProjectInfo>;

dispatch?.({
type: PROJECT_ACTION.INIT_PROJECT_LIST,
payload: content,
} as ProjectActionType);
setProjects(content);
setCount(totalElements);
} catch (error) {
console.error(error);
}
},
[current, intervalSize, searchTerm],
);
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/useHandlePinnedProjectList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { useCallback, useEffect } from 'react';
import { ProjectActionType } from '@/shared/types/pegase/Project.type';
import { ProjectActionType, ProjectInfo } from '@/shared/types/pegase/Project.type';
import { fetchPinnedProjects, pinProject, unpinProject } from '@/shared/services/pinnedProjectService';
import { v4 as uuidv4 } from 'uuid';
import { dismissToast, notifyToast, NotifyWithActionProps } from '@/shared/notification/notification.tsx';
Expand All @@ -20,7 +20,7 @@ export const useHandlePinnedProjectList = () => {

const getPinnedProjects = useCallback(async () => {
try {
const projects = await fetchPinnedProjects(userId);
const projects = (await fetchPinnedProjects(userId)) as ProjectInfo[];
if (projects?.length) {
dispatch?.({
type: PROJECT_ACTION.INIT_PINNED_PROJECT_LIST,
Expand Down
Loading
Loading