Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 29 additions & 48 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,40 @@
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import MuiThemeProvider from './shared/components/molecules/theme';
import A2ZNotifications from './shared/components/molecules/notification';
import Navbar from './shared/components/organisms/navbar';
import { memo, useEffect, useState, useMemo } from 'react';
import { Provider } from 'jotai';
import { setupTokenRefresh } from './shared/utils/api-interceptor';
import { useEffect } from 'react';
import Home from './modules/home';
import UserAuthForm from './modules/user-auth-form';
import Editor from './modules/editor';
import PageNotFound from './modules/404';
import Search from './modules/search';
import Profile from './modules/profile';
import Project from './modules/project';
import Sidebar from './shared/components/organisms/sidebar';
import ChangePassword from './modules/change-password';
import ManageProjects from './modules/manage-projects';
import EditProfile from './modules/edit-profile';
import Notifications from './modules/notification';
import { AppUnProtectedRoutes } from './modules/app/routes';
import { AppProtectedRoutes } from './modules/app/routes/auth-routes';
import useScrollbar from './shared/components/atoms/scrollbar';
import { useAuth } from './shared/hooks/use-auth';

const App = memo(() => {
const { GlobalScrollbar } = useScrollbar();
const [cacheKey, setCacheKey] = useState<string>('');
const { token } = useAuth();
const isAuth = useMemo(() => !!token, [token]);

function App() {
useEffect(() => {
setupTokenRefresh();
}, []);

return (
<MuiThemeProvider>
<BrowserRouter>
<Routes>
<Route path="/" element={<Navbar />}>
<Route index element={<Home />} />
<Route path="login" element={<UserAuthForm type="login" />} />
<Route path="signup" element={<UserAuthForm type="signup" />} />
<Route path="search/:query" element={<Search />} />
<Route path="user/:username" element={<Profile />} />
<Route path="project/:project_id" element={<Project />} />

<Route path="dashboard" element={<Sidebar />}>
<Route path="projects" element={<ManageProjects />} />
<Route path="notifications" element={<Notifications />} />
</Route>
<Route path="settings" element={<Sidebar />}>
<Route path="edit-profile" element={<EditProfile />} />
<Route path="change-password" element={<ChangePassword />} />
</Route>
</Route>

<Route path="/editor" element={<Editor />} />
<Route path="/editor/:project_id" element={<Editor />} />
useEffect(() => {
setCacheKey(Date.now().toString());
}, [isAuth]);
Comment on lines +19 to +21
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using Date.now().toString() as a cache key causes the Jotai Provider to remount and reset all global state every time isAuth changes. This will cause unnecessary re-renders and loss of state. Consider using a more stable approach, such as a simple counter or only updating the key when truly necessary, rather than on every auth state change.

Copilot uses AI. Check for mistakes.

<Route path="*" element={<PageNotFound />} />
</Routes>
if (!isAuth) {
return (
<>
<GlobalScrollbar />
<AppUnProtectedRoutes />
</>
);
}

<A2ZNotifications />
</BrowserRouter>
</MuiThemeProvider>
return (
<Provider key={cacheKey}>
<GlobalScrollbar />
<AppProtectedRoutes />
</Provider>
);
}
});

Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The memo wrapper on the App component doesn't have a display name set, which makes debugging in React DevTools more difficult. Add a display name using App.displayName = 'App'; after the component declaration to improve the debugging experience.

Suggested change
App.displayName = 'App';

Copilot uses AI. Check for mistakes.
export default App;
7 changes: 7 additions & 0 deletions client/src/config/env.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
import packageDetails from '../../package.json';

export const appVersion = packageDetails.version;

export const ORGANISATION_NAME =
import.meta.env.VITE_ORGANISATION_NAME || 'Code A2Z';

// Load environment variables from .env file
export const VITE_SERVER_DOMAIN =
import.meta.env.VITE_SERVER_DOMAIN || 'https://code-a2z-server.vercel.app'; // https://code-a2z.onrender.com for production
Expand Down
19 changes: 18 additions & 1 deletion client/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
import createCache from '@emotion/cache';
import { CacheProvider } from '@emotion/react';
import MuiThemeProvider from './shared/components/molecules/theme';
import { BrowserRouter } from 'react-router-dom';
import A2ZNotifications from './shared/components/molecules/notification';

const cache = createCache({
key: 'myapp',
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Emotion cache key is set to 'myapp' which is generic and doesn't match the project name. According to Emotion documentation, the cache key should be descriptive of your application. Consider changing this to something more specific like 'code-a2z' to match the project identity and the nonce value.

Suggested change
key: 'myapp',
key: 'code-a2z',

Copilot uses AI. Check for mistakes.
nonce: 'code-a2z-css', // 🔑 must match the CSP nonce
});

createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
<CacheProvider value={cache}>
<MuiThemeProvider>
<BrowserRouter>
<A2ZNotifications />
<App />
</BrowserRouter>
</MuiThemeProvider>
</CacheProvider>
</StrictMode>
);
63 changes: 63 additions & 0 deletions client/src/modules/app/components/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
import { lazy } from 'react';
import Header from '../../../shared/components/organisms/header';
import { HEADER_HEIGHT } from '../../../shared/components/organisms/header/constants';
import Sidebar from '../../../shared/components/organisms/sidebar';
import { SIDEBAR_WIDTH } from '../../../shared/components/organisms/sidebar/constants';
import { Routes } from 'react-router-dom';
import getRoutesV1 from '../routes/auth-routes/v1';

export const LoginLazyComponent = lazy(() => import('../../user-auth-form/v1'));
export const HomePageLazyComponent = lazy(() => import('../../home/v1'));
export const SettingsPageLazyComponent = lazy(
() => import('../../settings/v1')
);

export const AppLayout = () => {
return (
<>
<Header />
<div
css={css`
height: calc(100vh - ${HEADER_HEIGHT}px);
`}
>
<div
css={css`
height: 100%;
display: flex;
flex: 1;
position: relative;
`}
>
<Sidebar />
<div
css={css`
height: 100%;
width: 100%;
min-width: 100%;
max-width: 100%;
flex: 1;
padding-left: ${SIDEBAR_WIDTH}px;
overflow: hidden;
`}
>
<div
css={css`
height: 100%;
transition: height 500ms ease-in-out;
width: 100%;
min-width: 100%;
max-width: 100%;
overflow: hidden;
`}
>
<Routes>{getRoutesV1()}</Routes>
</div>
</div>
</div>
</div>
</>
);
};
10 changes: 10 additions & 0 deletions client/src/modules/app/routes/auth-routes/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Route, Routes } from 'react-router-dom';
import { AppLayout } from '../../components';

export function AppProtectedRoutes() {
return (
<Routes>
<Route key={'/*'} path={'/*'} element={<AppLayout />} />
</Routes>
);
}
44 changes: 44 additions & 0 deletions client/src/modules/app/routes/auth-routes/v1/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Suspense } from 'react';
import { Navigate, Route } from 'react-router-dom';
import { ROUTES_V1 } from '../../constants/routes';
import { LOADING } from '../../constants';
import Loader from '../../../../../shared/components/molecules/loader';
import {
HomePageLazyComponent,
SettingsPageLazyComponent,
} from '../../../components';

export default function getRoutesV1() {
const routes = [
<Route
key={ROUTES_V1.HOME}
path={ROUTES_V1.HOME}
element={
<Suspense fallback={<Loader size={32} secondary={LOADING} />}>
<HomePageLazyComponent />
</Suspense>
}
/>,
<Route
key={ROUTES_V1.SETTINGS}
path={ROUTES_V1.SETTINGS}
element={
<Suspense fallback={<Loader size={32} secondary={LOADING} />}>
<SettingsPageLazyComponent />
</Suspense>
}
/>,
];

if (routes.length) {
routes.push(
<Route
key="*"
path="*"
element={<Navigate to={ROUTES_V1.HOME} replace />}
/>
);
}
Comment on lines +33 to +41
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition if (routes.length) on line 33 is unnecessary since the routes array is hardcoded with 2 elements and will always have a length greater than 0. This check doesn't add any value and should be removed to simplify the code.

Suggested change
if (routes.length) {
routes.push(
<Route
key="*"
path="*"
element={<Navigate to={ROUTES_V1.HOME} replace />}
/>
);
}
routes.push(
<Route
key="*"
path="*"
element={<Navigate to={ROUTES_V1.HOME} replace />}
/>
);

Copilot uses AI. Check for mistakes.

return routes;
}
1 change: 1 addition & 0 deletions client/src/modules/app/routes/constants/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const LOADING = 'Loading...';
9 changes: 9 additions & 0 deletions client/src/modules/app/routes/constants/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export enum ROUTES_V1 {
HOME = '/v1/home',
SETTINGS = '/v1/settings',
}

export enum ROUTES_PAGE_V1 {
HOME = 'home',
SETTINGS = 'settings',
}
20 changes: 20 additions & 0 deletions client/src/modules/app/routes/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Suspense } from 'react';
import { Route, Routes } from 'react-router-dom';
import Loader from '../../../shared/components/molecules/loader';
import { LOADING } from './constants';
import { LoginLazyComponent } from '../components';

export function AppUnProtectedRoutes() {
return (
<Routes>
<Route
path="*"
element={
<Suspense fallback={<Loader size={32} secondary={LOADING} />}>
<LoginLazyComponent />
</Suspense>
}
/>
</Routes>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
import { Link } from 'react-router-dom';
import { Avatar, Box, Chip, Stack, Typography, useTheme } from '@mui/material';
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
import { useA2ZTheme } from '../../../shared/hooks/use-theme';
import { getDay } from '../../../shared/utils/date';
import { useA2ZTheme } from '../../../../shared/hooks/use-theme';
import { getDay } from '../../../../shared/utils/date';
import {
defaultDarkThumbnail,
defaultLightThumbnail,
} from '../../editor/constants';
import { getAllProjectsResponse } from '../../../infra/rest/apis/project/typing';
} from '../../../editor/constants';
import { getAllProjectsResponse } from '../../../../infra/rest/apis/project/typing';

const BannerProjectCard = ({
project,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Link } from 'react-router-dom';
import { Avatar, Box, Typography, Stack, useTheme } from '@mui/material';
import { getTrendingProjectsResponse } from '../../../infra/rest/apis/project/typing';
import { getDay } from '../../../shared/utils/date';
import { getTrendingProjectsResponse } from '../../../../infra/rest/apis/project/typing';
import { getDay } from '../../../../shared/utils/date';

interface NoBannerProjectCardProps {
project: getTrendingProjectsResponse;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
getAllProjects,
getTrendingProjects,
searchProjects,
} from '../../../infra/rest/apis/project';
} from '../../../../infra/rest/apis/project';

const useHome = () => {
const setProjects = useSetAtom(HomePageProjectsAtom);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Box, Stack } from '@mui/material';
import A2ZTypography from '../../shared/components/atoms/typography';
import A2ZTypography from '../../../shared/components/atoms/typography';
import TrendingUpIcon from '@mui/icons-material/TrendingUp';
import { categories } from './constants';
import { CategoryButton } from './components/category-button';
import InPageNavigation from '../../shared/components/molecules/page-navigation';
import InPageNavigation from '../../../shared/components/molecules/page-navigation';
import NoBannerProjectCard from './components/no-banner-project';
import { useAtom, useAtomValue } from 'jotai';
import {
Expand All @@ -12,11 +12,11 @@ import {
HomePageTrendingProjectsAtom,
} from './states';
import BannerProjectCard from './components/banner-project-card';
import NoDataMessageBox from '../../shared/components/atoms/no-data-msg';
import NoDataMessageBox from '../../../shared/components/atoms/no-data-msg';
import {
BannerSkeleton,
NoBannerSkeleton,
} from '../../shared/components/atoms/skeleton';
} from '../../../shared/components/atoms/skeleton';
import { useEffect } from 'react';
import useHome from './hooks';
import { Virtuoso } from 'react-virtuoso';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { atom } from 'jotai';
import {
getAllProjectsResponse,
getTrendingProjectsResponse,
} from '../../../infra/rest/apis/project/typing';
} from '../../../../infra/rest/apis/project/typing';

export const HomePageStateAtom = atom<string>('home');

Expand Down
2 changes: 1 addition & 1 deletion client/src/modules/profile/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { userProfile } from '../../../infra/rest/apis/user';
import { useParams } from 'react-router-dom';
import { useSetAtom } from 'jotai';
import { ProfileAtom } from '../states';
import useHome from '../../home/hooks';
import useHome from '../../home/v1/hooks';

const useProfile = () => {
const { username } = useParams();
Expand Down
6 changes: 3 additions & 3 deletions client/src/modules/profile/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { useParams } from 'react-router-dom';
import InPageNavigation from '../../shared/components/molecules/page-navigation';
import NoDataMessage from '../../shared/components/atoms/no-data-msg';
import { useAtom, useAtomValue } from 'jotai';
import { HomePageProjectsAtom } from '../home/states';
import BannerProjectCard from '../home/components/banner-project-card';
import { HomePageProjectsAtom } from '../home/v1/states';
import BannerProjectCard from '../home/v1/components/banner-project-card';
import { ProfileAtom } from './states';
import { Virtuoso } from 'react-virtuoso';
import { BannerSkeleton } from '../../shared/components/atoms/skeleton';
import { UserAtom } from '../../infra/states/user';
import { Avatar, Box, CircularProgress } from '@mui/material';
import useHome from '../home/hooks';
import useHome from '../home/v1/hooks';
import AboutUser from './components/about-user';
import A2ZTypography from '../../shared/components/atoms/typography';
import Button from '../../shared/components/atoms/button';
Expand Down
2 changes: 1 addition & 1 deletion client/src/modules/project/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useCallback, useState } from 'react';
import { getProjectById } from '../../../infra/rest/apis/project';
import { SelectedProjectAtom } from '../states';
import { useSetAtom } from 'jotai';
import useHome from '../../home/hooks';
import useHome from '../../home/v1/hooks';
import useCommentsWrapper from '../../../shared/components/organisms/comments-wrapper/hooks';

const useProject = () => {
Expand Down
Loading
Loading