Skip to content

Conversation

@github-actions
Copy link
Contributor

This is an automated pull request to merge mariano/make-portal-great-again into dev.
It was created by the [Auto Pull Request] action.

@vercel
Copy link

vercel bot commented Dec 18, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
app Ready Ready Preview, Comment Jan 1, 2026 10:10pm
portal Error Error Jan 1, 2026 10:10pm

@cursor
Copy link

cursor bot commented Dec 18, 2025

PR Summary

Modernizes authentication and the employee portal, with API endpoints to support it.

  • Auth (API): Adds canonical Better Auth instance at /api/auth/*, JWT issuance/verification, JwtAuthGuard, and MeController (GET /v1/me/organizations).
  • People/Policies (API): New people/me endpoints (member info, training video list/complete). Adds policy acknowledgement endpoints (single/bulk) and signed PDF URL retrieval; policies now include displayFormat and pdfUrl.
  • Portal (UI & data): Migrates to @trycompai/ui-shadcn; rewrites login/OTP, header/layout, and user menu. Introduces client-side SWR hooks and schemas for memberships and employee dashboard (policies, device agent status, training videos). Implements policy PDF viewer and policy accept flows.
  • App integration: Client auth targets API base URL with credentials; SOA/portal settings fetch JWT via API token endpoint; env requires NEXT_PUBLIC_API_URL.
  • Misc: Adjusts build/lint scripts, Next transpile config, and minor formatting/logging fixes.

Written by Cursor Bugbot for commit 4cee4f1. This will update automatically on new commits. Configure here.

@graphite-app graphite-app bot requested a review from Marfuen December 18, 2025 15:40
@graphite-app
Copy link

graphite-app bot commented Dec 18, 2025

Graphite Automations

"Auto-assign PRs to Author" took an action on this PR • (12/18/25)

1 reviewer was added to this PR based on Mariano Fuentes's automation.

return true;
} catch (error) {
throw new UnauthorizedException('Invalid or expired JWT token');
}
Copy link

Choose a reason for hiding this comment

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

JWT guard swallows specific auth error messages

The catch block at the end of handleJwtAuth catches all errors including the specific UnauthorizedException instances thrown within the try block (e.g., "BETTER_AUTH_URL not configured", "missing user information", "User does not have access to organization"). These informative error messages are discarded and replaced with a generic "Invalid or expired JWT token" message, making it very difficult to diagnose authentication failures in production or during development.

Fix in Cursor Fix in Web

const created = await db.employeeTrainingVideoCompletion.create({
data: { memberId: member.id, videoId, completedAt: new Date() },
});
return { success: true, data: created };
Copy link

Choose a reason for hiding this comment

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

Race condition causes unhandled error in video completion

The completeTrainingVideo method has a race condition between the findFirst check and the create call. The database schema has a @@unique([memberId, videoId]) constraint on EmployeeTrainingVideoCompletion. If two concurrent requests attempt to complete the same video (e.g., user double-clicks, or same page open in multiple tabs), both requests may find no existing record, and both will try to create one. The second create will fail with an unhandled unique constraint violation, resulting in a 500 error to the user. Using upsert or catching the unique constraint error would prevent this.

Fix in Cursor Fix in Web

onNext,
allVideosCompleted,
isMarkingComplete,
onWatchAgain,
Copy link

Choose a reason for hiding this comment

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

isRewatching state persists incorrectly across different videos

The isRewatching state is initialized once and never resets when the video prop changes. When a user clicks "Watch Again" on a completed video, isRewatching becomes true and stays true even when navigating to a different video in the carousel. This causes the "Video Completed" overlay to not appear for subsequent completed videos, since the condition isCompleted && !isRewatching will be false. The state needs to reset when the video changes, such as with a useEffect that resets isRewatching to false when video.youtubeId changes.

Fix in Cursor Fix in Web

await db.policy.update({
where: { id: policyId },
data: { signedBy: { push: member.id } },
});
Copy link

Choose a reason for hiding this comment

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

Race condition allows duplicate entries in policy signatures

The acknowledgePolicy and acknowledgePolicies methods have a TOCTOU (time-of-check-time-of-use) race condition. The check at line 54 (policy.signedBy.includes(member.id)) and the subsequent push update at lines 58-60 are not atomic. If two concurrent requests occur for the same member and policy, both may pass the check before either completes the update, resulting in duplicate member.id entries in the signedBy array. While this doesn't break functionality (the includes check still works), it causes data inconsistency. The same pattern exists in acknowledgePolicies at lines 89-98.

Additional Locations (1)

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants