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
11 changes: 8 additions & 3 deletions apps/backend/src/applications/applications.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ export class ApplicationsService {
const allApplicationsDto = await Promise.all(
applications.map(async (app) => {
const ratings = this.calculateAllRatings(app.reviews);
const stageProgress = this.determineStageProgress(app, app.reviews);
const stageProgress = this.determineStageProgress(app, app.reviews);
const assignedRecruiters =
await this.getAssignedRecruitersForApplication(app);

Expand Down Expand Up @@ -500,7 +500,10 @@ export class ApplicationsService {
* submitted a review for that stage. If no recruiters are assigned, the
* stage remains PENDING even if admins or others submit reviews.
*/
private determineStageProgress(app: Application, reviews: any[]): StageProgress {
private determineStageProgress(
app: Application,
reviews: any[],
): StageProgress {
const stage = app.stage;

// Terminal stages are always completed
Expand Down Expand Up @@ -530,7 +533,9 @@ export class ApplicationsService {
reviewerIdsForStage.has(id),
);

return allAssignedReviewed ? StageProgress.COMPLETED : StageProgress.PENDING;
return allAssignedReviewed
? StageProgress.COMPLETED
: StageProgress.PENDING;
}

/**
Expand Down
8 changes: 7 additions & 1 deletion apps/backend/src/reviews/review.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ export class Review {
@JoinColumn({ name: 'applicationId' })
application: Application;

@ManyToOne(() => User, (user) => user.id)
// The relationship to User
@ManyToOne(() => User, { nullable: false })
@JoinColumn({ name: 'reviewerId' })
reviewer: User;

// The foreign key column
@Column({ nullable: false })
reviewerId: number;

@Column({ type: 'double precision', nullable: false })
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/testing/factories/user.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const defaultUser: User = {
team: null,
role: null,
applications: [],
review: null,
reviews: [],
};

export const userFactory = (user: Partial<User> = {}): User =>
Expand Down
4 changes: 2 additions & 2 deletions apps/backend/src/users/user.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,6 @@ export class User {
@OneToMany(() => Application, (application) => application.user)
applications: Application[];

@OneToMany(() => Review, (review) => review.reviewerId)
review: Review;
@OneToMany(() => Review, (review) => review.reviewer)
reviews: Review[];
}
8 changes: 6 additions & 2 deletions apps/frontend/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,19 @@ import AdminRoutes from '@features/auth/components/AdminRoutes';
import HomePage from '@shared/pages/HomePage';

export const App: React.FC = () => {
const [token, setToken] = useState<string>('');
const [token, setToken] = useState<string>(() => {
const storedToken = localStorage.getItem('token');
return storedToken ? JSON.parse(storedToken) : '';
});

return (
<LoginContext.Provider value={{ setToken, token }}>
<BrowserRouter>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/home" element={<HomePage />} />

<Route element={<ProtectedRoutes token={token} />}>
<Route element={<ProtectedRoutes />}>
<Route element={<AdminRoutes />}>
<Route path="/" element={<ApplicationsPage />} />
<Route path="/applications" element={<ApplicationsPage />} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ const IndividualApplicationDetails = ({
accessToken,
onRefreshApplication,
}: IndividualApplicationDetailsProps) => {
console.log('Full selectedApplication:', selectedApplication);
console.log('Reviews array:', selectedApplication.reviews);

// Lighter purple accent tuned to match Figma palette
const ACCENT = '#9B6CFF';
// Assigned recruiters are managed by the AssignedRecruiters child component
Expand Down Expand Up @@ -90,6 +93,12 @@ const IndividualApplicationDetails = ({
}

try {
if (currentUser == null) {
throw new Error(
'the current user should not be null when trying to submit a review',
);
}

// Submit review
if (reviewRating && trimmedComment) {
await apiClient.submitReview(accessToken, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,23 @@ export default function LoginPage() {
);

// Keep backward compatibility - store access token for existing code
sessionStorage.setItem(
localStorage.setItem(
'token',
JSON.stringify(tokenResponse.access_token),
);

setToken(tokenResponse.access_token);

// Redirect to dashboard after successful login
navigate('/');
navigate('/', { replace: true });
} catch (error) {
console.error('Error fetching token:', error);
// Redirect to home page on error
navigate('/home');
navigate('/home', { replace: true });
}
} else {
// No auth code - redirect to home page
navigate('/home');
navigate('/home', { replace: true });
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Navigate, Outlet } from 'react-router-dom';
import useLoginContext from '@features/auth/components/LoginPage/useLoginContext';

/**
* ProtectedRoutes renders the children components only
* if the user is authenticated (i.e if an access token exists).
* If the user is not authenticated, it redirects to the login page.
*/
function ProtectedRoutes({ token }: { token: string }) {
return token ? <Outlet /> : <Navigate to="/login" />;
function ProtectedRoutes() {
const { token } = useLoginContext();
return token ? <Outlet /> : <Navigate to="/home" replace />;
}

export default ProtectedRoutes;
12 changes: 6 additions & 6 deletions apps/frontend/src/shared/hooks/useAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const verifier = CognitoJwtVerifier.create({
/**
* Custom hook to manage authentication state
*
* Checks if user has a valid token in sessionStorage and verifies it.
* Checks if user has a valid token in localStorage and verifies it.
* Returns authentication state and sign out handler.
*/
export const useAuth = () => {
Expand All @@ -21,17 +21,17 @@ export const useAuth = () => {

useEffect(() => {
const checkAuth = async () => {
const sessionToken = sessionStorage.getItem('token');
const localToken = localStorage.getItem('token');

if (sessionToken) {
if (localToken) {
try {
const token = JSON.parse(sessionToken);
const token = JSON.parse(localToken);
await verifier.verify(token);
setToken(token);
setIsAuthenticated(true);
} catch (error) {
console.log('Error verifying token:', error);
sessionStorage.removeItem('token');
localStorage.removeItem('token');
setIsAuthenticated(false);
}
} else {
Expand All @@ -45,7 +45,7 @@ export const useAuth = () => {
}, [setToken]);

const signOut = () => {
sessionStorage.removeItem('token');
localStorage.removeItem('token');
localStorage.removeItem('auth_tokens');
setToken('');
setIsAuthenticated(false);
Expand Down
Loading
Loading