Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
105 commits
Select commit Hold shift + click to select a range
80b339a
Merge pull request #610 from CodebilityDev/dev
deocagunot Jul 14, 2026
6d5d1b1
Merge pull request #616 from CodebilityDev/dev
deocagunot Jul 28, 2026
61a5129
Merge pull request #619 from CodebilityDev/dev
deocagunot Jul 28, 2026
9d32cbe
fix(codevs): show accepted developers on Be a Member
deocagunot Jul 29, 2026
38b931f
Merge pull request #625 from CodebilityDev/hotfix/be-a-member-filter
deocagunot Aug 13, 2026
d1374d9
fix(2fa): remove non-functional backup recovery code UI
deocagunot Aug 13, 2026
4c8e21d
Merge pull request #628 from CodebilityDev/hotfix/2fa-remove-fake-rec…
deocagunot Aug 13, 2026
ff427b0
pull master to dev
DavidK4leido5 Aug 22, 2026
66f155f
fix client fetching using useEffect
DavidK4leido5 Aug 22, 2026
f40a05d
deleted md
DavidK4leido5 Aug 22, 2026
74ff4bb
fix topbar navigation now only loads the dynamic components without e…
DavidK4leido5 Aug 24, 2026
0e9277c
remove useEffect for animateCountUp use animate from framermotion
DavidK4leido5 Aug 24, 2026
cbbabb7
Features.tsx checked
DavidK4leido5 Aug 24, 2026
a12fdaa
fix metrics animation for WhyChooseUs section. Created variant Animat…
DavidK4leido5 Aug 24, 2026
f39e404
Admins Done, cache the data from supabase as well as baked the genera…
DavidK4leido5 Aug 24, 2026
0536d39
done codebility admins
DavidK4leido5 Aug 25, 2026
b998191
fix landing intern
DavidK4leido5 Aug 25, 2026
ba37f5c
fix errors
DavidK4leido5 Aug 25, 2026
a88b5ed
fix intern section
DavidK4leido5 Aug 25, 2026
4dd04f6
fix intern section loading
DavidK4leido5 Aug 25, 2026
017be5f
fix build errors
DavidK4leido5 Aug 25, 2026
8f5e2fd
fix caching method. only one source of truth
DavidK4leido5 Aug 25, 2026
aacd26d
fix caching
DavidK4leido5 Aug 25, 2026
62c2476
replace useEffects. Fix animation
DavidK4leido5 Aug 26, 2026
53f58cc
floating particles use useAnimationFrame, reduce-motion, smaller scre…
DavidK4leido5 Aug 26, 2026
20dc317
fix ai-integration page hero image, added linear fade
DavidK4leido5 Aug 26, 2026
c7058b9
services page refactor and optimize queries
DavidK4leido5 Aug 26, 2026
818eda3
fix urls
DavidK4leido5 Aug 26, 2026
0ec7229
adjust skeleton
DavidK4leido5 Aug 26, 2026
892af4b
fix image loader blocking the page elements to render
DavidK4leido5 Aug 27, 2026
8805478
images are blocking full page load
DavidK4leido5 Aug 27, 2026
bfb9f8e
progressive enhancement nightmare
DavidK4leido5 Aug 27, 2026
201f814
fix codevs page. From Dynamic to Static
DavidK4leido5 Aug 30, 2026
115317a
hire-a-codev
DavidK4leido5 Aug 30, 2026
02420c8
hire-codevs
DavidK4leido5 Aug 30, 2026
1094b6e
profiles page generate static params
DavidK4leido5 Aug 30, 2026
44dd412
kanban board updates RSC
DavidK4leido5 Sep 1, 2026
dbc312e
realtime updates using supabase
DavidK4leido5 Sep 1, 2026
3490367
realtime updates for tasks done
DavidK4leido5 Sep 1, 2026
eeb7ffc
initial pass, restruturing
DavidK4leido5 Sep 1, 2026
510ccca
phase 2 done
DavidK4leido5 Sep 1, 2026
b9c6701
phase 3 done
DavidK4leido5 Sep 1, 2026
45c14a9
phase 4 done
DavidK4leido5 Sep 1, 2026
f6e5324
phase 6 done
DavidK4leido5 Sep 1, 2026
9ade8d7
fix typescripts
DavidK4leido5 Sep 1, 2026
fe7a3de
reorganize lib
DavidK4leido5 Sep 1, 2026
4137700
build error fix.
DavidK4leido5 Sep 1, 2026
1e5c7f4
heavy progressive enhancements
DavidK4leido5 Sep 1, 2026
611ec57
added skeleton loaders for our services
DavidK4leido5 Sep 2, 2026
7e80c35
same pattern applied to all public pages
DavidK4leido5 Sep 2, 2026
0ab4ff6
fix centering
DavidK4leido5 Sep 2, 2026
c9fd1da
fix
DavidK4leido5 Sep 7, 2026
6e3dc40
fix home page
DavidK4leido5 Sep 7, 2026
94431d2
navigation fix portal
DavidK4leido5 Sep 14, 2026
18634bd
fix navigations unintended rerenders
DavidK4leido5 Sep 14, 2026
57f06f2
probing
DavidK4leido5 Sep 14, 2026
5746a8a
layout refactor, modal provider refactor, providers refactor
DavidK4leido5 Sep 16, 2026
0691c0e
perf(home): remove MUI, add route skeletons, stream layout, seed feeds
DavidK4leido5 Sep 16, 2026
7f2ed4e
fix
DavidK4leido5 Sep 16, 2026
ced3e28
perf(overflow): resolve mount data on the server, drop per-card like …
DavidK4leido5 Sep 16, 2026
70ec514
perf(settings/services): move catalog data to the server render
DavidK4leido5 Sep 16, 2026
48a15ea
fix(settings/services): mark route dynamic after moving fetch to the…
DavidK4leido5 Sep 16, 2026
2c189fc
perf(my-team): stop ChecklistStatusBanner refetching member data
DavidK4leido5 Sep 16, 2026
785cae2
perf(my-team): fetch member points in one round instead of two
DavidK4leido5 Sep 16, 2026
1508386
perf(settings/profile): dedupe the profile-points fetch across 7 com…
DavidK4leido5 Sep 16, 2026
c0da6c4
Complete loading.tsx coverage across all in-scope routes
DavidK4leido5 Sep 16, 2026
38002e2
fix(settings/profile): pass codev id to ContactInfo, Photo and Skills
DavidK4leido5 Sep 21, 2026
0d060f6
fix phase 2
DavidK4leido5 Sep 21, 2026
0a79c78
fix teams and overflow path
DavidK4leido5 Sep 21, 2026
54af45f
fix overflow page
DavidK4leido5 Sep 21, 2026
2784bd3
perf(p9): server-paginate in-house and add shared list primitives
DavidK4leido5 Sep 21, 2026
f8ec607
perf(p9): server-paginate the interns grid
DavidK4leido5 Sep 21, 2026
367898c
perf(p9): drop client-init effects and dead shared nav
DavidK4leido5 Sep 21, 2026
be79003
perf(p9): stop per-card badge queries, delete confirmed dead code
DavidK4leido5 Sep 21, 2026
b942674
perf(p9): convert my-team fetch-on-mount effects to queries
DavidK4leido5 Sep 21, 2026
a33b6e5
perf(p9): convert profile-points and my-team detail fetches to queries
DavidK4leido5 Sep 21, 2026
a354cc5
perf(p9): query-ify attendance grid and compact member points
DavidK4leido5 Sep 21, 2026
9b9539e
perf(p9): convert feeds and dashboard effects to queries
DavidK4leido5 Sep 21, 2026
3f754a6
perf(p9): query-ify overflow comments, top solvers and internal projects
DavidK4leido5 Sep 21, 2026
6c0b34d
perf(p9): query-ify announcement modal
DavidK4leido5 Sep 21, 2026
e205fc6
perf(p9): query-ify account username, profile, hire and admin-controls
DavidK4leido5 Sep 21, 2026
90530c5
perf(p9): query-ify certificate preview, 2FA and meeting attendance
DavidK4leido5 Sep 21, 2026
86c86f4
perf(p9): finish the effect sweep (84 -> 4, all justified)
DavidK4leido5 Sep 21, 2026
1ff09e3
perf(p9): revalidate after hire and attendance mutations
DavidK4leido5 Sep 21, 2026
ad6a0e8
perf(p9): drop the one-off revalidate helper
DavidK4leido5 Sep 21, 2026
800e950
fix
DavidK4leido5 Sep 21, 2026
fc8e0a4
fix: revalidate the Router Cache after every mutation
DavidK4leido5 Sep 21, 2026
cd7da67
fix: set immediatelyRender on every Tiptap editor
DavidK4leido5 Sep 21, 2026
6a05a22
fix: use maybeSingle for the team lead lookup
DavidK4leido5 Sep 21, 2026
d92d578
chore(p9): add diagnostics for the landing and member queries
DavidK4leido5 Sep 21, 2026
e1c6aca
chore(p9): add the announcement modal probe
DavidK4leido5 Sep 21, 2026
bd1b57b
fix(p10): seed initialData only on its own query key
DavidK4leido5 Sep 21, 2026
00e032e
perf(p10): server-paginate /home/projects
DavidK4leido5 Sep 21, 2026
9a920a5
perf(p10): server-paginate /home/clients
DavidK4leido5 Sep 21, 2026
b5a3c6d
perf(p10): server-paginate /home/tasks
DavidK4leido5 Sep 21, 2026
3e8b675
perf(p10): server-paginate /home/applicants and debounce its search
DavidK4leido5 Sep 21, 2026
cb05bf3
perf(p10): select explicit columns on the settings list pages
DavidK4leido5 Sep 21, 2026
cae5330
perf(p10): select explicit columns for the member checklist list
DavidK4leido5 Sep 21, 2026
fd8309f
perf(p10): server-render the certificate preview, server-back overflo…
DavidK4leido5 Sep 21, 2026
622dce1
fix(p10): revalidate the kanban board after every mutation
DavidK4leido5 Sep 21, 2026
2203130
perf(p10): stop client-fetching positions, narrow profile list columns
DavidK4leido5 Sep 21, 2026
a735347
fix(p10): remove undefined status from TaskCard
DavidK4leido5 Sep 21, 2026
0bc1e1b
updated
DavidK4leido5 Sep 21, 2026
2c8e9ed
almost done
DavidK4leido5 Sep 21, 2026
1bce811
updates
DavidK4leido5 Sep 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,6 @@ distapps/bot/.env
.env
apps/codebility/public/assets/onboardingvideos/*.mp4
.planning/
.cursor/
docs/agents
skills-lock.json
16 changes: 15 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,4 +199,18 @@ All database migrations are located in `apps/codebility/supabase/migrations/`

**Legacy Migrations:**
The `migration-scripts/` folder in the root contains deprecated migration tools and scripts.
Do not add new migrations there. See `migration-scripts/DEPRECATION_NOTICE.md` for details.
Do not add new migrations there. See `migration-scripts/DEPRECATION_NOTICE.md` for details.

## Agent skills

### Issue tracker

Issues and specs live in GitHub Issues for `CodebilityDev/codebility-plus`. See `docs/agents/issue-tracker.md`.

### Triage labels

Five canonical triage roles mapped to GitHub label strings. See `docs/agents/triage-labels.md`.

### Domain docs

Single-context layout: root `CONTEXT.md` and `docs/adr/`. See `docs/agents/domain.md`.
2 changes: 1 addition & 1 deletion apps/codebility/__tests__/hooks/useLeaderboard.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { renderHook, waitFor } from "@testing-library/react";
import { useLeaderboard } from "@/hooks/useLeaderboard";
import { useLeaderboard } from "@/hooks/leaderboard/useLeaderboard";

// Mock the API calls
global.fetch = jest.fn();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { createClientServerComponent } from "@/utils/supabase/server";
import { revalidatePath } from "next/cache";
import { requireUser } from "@/lib/server/auth-guard";

export const updatePassword = async (formData: FormData) => {
const email = formData.get("email") as string;
Expand Down Expand Up @@ -112,7 +113,12 @@ export const getUsernameData = async (userId: string) => {
};

// Update username
export const updateUsername = async (userId: string, newUsername: string) => {
export const updateUsername = async (newUsername: string) => {
// Identity from the session. This previously took a `userId` parameter and
// wrote to that row unchecked, so any caller could rename another user.
const { user } = await requireUser();
const userId = user.id;

const supabase = await createClientServerComponent();

// 1. Validate username format
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Import your actual server utility file relative to the utils folder mapping
import { createClientServerComponent } from "@/utils/supabase/server";
import { revalidatePath } from "next/cache";
import { appointmentStatusSchema } from "./types";
import { appointmentStatusSchema } from "@/app/home/admin-controls/appointments/types";

/**
* Server Action to securely update the status text field of a client appointment.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { createClientServerComponent } from "@/utils/supabase/server";
import { revalidatePath } from "next/cache";
import { getCurrentWeekStart } from "./utils";
import { getCurrentWeekStart } from "@/app/home/admin-controls/client-tracker/utils";

export interface ClientOutreach {
id: string;
Expand Down Expand Up @@ -206,8 +206,7 @@ export async function getAdminOutreachHistory(

let query = supabase
.from('client_outreach')
.select('*')
.eq('admin_id', adminId)
.select('id, admin_id, client_name, client_email, client_company, job_link, outreach_date, notes, conversation_image, week_start, created_at')
.order('outreach_date', { ascending: false });

if (weekStart) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import { createClientServerComponent } from "@/utils/supabase/server";
import { revalidatePath } from "next/cache";
import type { TicketSupport, TicketStatus, TicketPriority } from "./types";
import { requireRole } from "@/lib/server/auth-guard";
import type { TicketSupport, TicketStatus, TicketPriority } from "@/app/home/admin-controls/ticket-support/types";

export async function getTickets(): Promise<TicketSupport[]> {
const supabase = await createClientServerComponent();
Expand Down Expand Up @@ -69,6 +70,10 @@ export async function updateTicketStatus(
ticketId: string,
status: TicketStatus
) {
// Ticket administration is gated by the `applicants` permission, matching the
// route permission middleware applies to /home/admin-controls.
await requireRole("applicants");

const supabase = await createClientServerComponent();

const { data, error, count } = await supabase
Expand Down Expand Up @@ -97,6 +102,8 @@ export async function updateTicketPriority(
ticketId: string,
priority: TicketPriority
) {
await requireRole("applicants");

const supabase = await createClientServerComponent();

const { data, error } = await supabase
Expand Down Expand Up @@ -125,6 +132,8 @@ export async function updateTicketAssignment(
ticketId: string,
assignedToId: string | null
) {
await requireRole("applicants");

const supabase = await createClientServerComponent();

const { data, error } = await supabase
Expand All @@ -150,6 +159,8 @@ export async function updateTicketAssignment(
}

export async function deleteTicket(ticketId: string) {
await requireRole("applicants");

const supabase = await createClientServerComponent();

// First verify the ticket exists
Expand Down Expand Up @@ -182,6 +193,8 @@ export async function deleteTicket(ticketId: string) {
}

export async function archiveTicket(ticketId: string) {
await requireRole("applicants");

const supabase = await createClientServerComponent();

const { data, error } = await supabase
Expand All @@ -202,6 +215,8 @@ export async function archiveTicket(ticketId: string) {
}

export async function unarchiveTicket(ticketId: string) {
await requireRole("applicants");

const supabase = await createClientServerComponent();

const { data, error } = await supabase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,42 @@

import { createClientServerComponent } from "@/utils/supabase/server";
import { revalidatePath } from "next/cache";
import { requireUser } from "@/lib/server/auth-guard";

/**
* Onboarding progress is self-service, so a write is only allowed against the
* signed-in user's own `applicant` row.
*
* These actions used to take `applicantId` and write to it unchecked, so any
* caller could mark another applicant's videos complete or overwrite their quiz
* and commitment results.
*/
async function assertOwnApplicant(
supabase: Awaited<ReturnType<typeof createClientServerComponent>>,
applicantId: string,
) {
const { user } = await requireUser();

const { data } = await supabase
.from("applicant")
.select("codev_id")
.eq("id", applicantId)
.maybeSingle();

if (!data || data.codev_id !== user.id) {
throw new Error("Forbidden");
}
}

export async function getOnboardingProgress(applicantId: string) {
try {
const supabase = await createClientServerComponent();

await assertOwnApplicant(supabase, applicantId);

const { data, error } = await supabase
.from("onboarding_videos")
.select("*")
.select("id, applicant_id, video_number, completed, completed_at")
.eq("applicant_id", applicantId)
.order("video_number", { ascending: true });

Expand Down Expand Up @@ -56,10 +84,12 @@ export async function updateVideoProgress({
try {
const supabase = await createClientServerComponent();

await assertOwnApplicant(supabase, applicantId);

// Check if record exists
const { data: existingRecord } = await supabase
.from("onboarding_videos")
.select("*")
.select("id")
.eq("applicant_id", applicantId)
.eq("video_number", videoNumber)
.single();
Expand Down Expand Up @@ -119,6 +149,8 @@ export async function saveQuizProgress({
try {
const supabase = await createClientServerComponent();

await assertOwnApplicant(supabase, applicantId);

const { error } = await supabase
.from("applicant")
.update({
Expand Down Expand Up @@ -159,6 +191,8 @@ export async function saveQuizAndCommitment({
try {
const supabase = await createClientServerComponent();

await assertOwnApplicant(supabase, applicantId);

const { error } = await supabase
.from("applicant")
.update({
Expand All @@ -185,6 +219,20 @@ export async function saveQuizAndCommitment({

export async function completeOnboarding(codevId: string, newStatus: string = "waitlist") {
try {
// Self only, and the status is constrained: the parameter used to accept any
// string, so a caller could set their own application_status to "passed"
// and skip the pipeline.
const { user } = await requireUser();

if (user.id !== codevId) {
throw new Error("Forbidden");
}

const ALLOWED_STATUSES = ["onboarding", "waitlist"];
if (!ALLOWED_STATUSES.includes(newStatus)) {
throw new Error("Forbidden");
}

const supabase = await createClientServerComponent();

// Update codev status to waitlist (or specified status)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@

"use server";


import { createClientServerComponent } from "@/utils/supabase/server";
import { revalidatePath } from "next/cache";
import { requireUser } from "@/lib/server/auth-guard";

/**
* Applicant pipeline actions are self-service: an applicant moves their own
* record through testing/onboarding/waitlist.
*
* `codevId` used to be trusted from the caller, so any caller could set another
* user's `application_status` (e.g. push themselves or someone else to
* "waitlist"). The codev row written is now always the signed-in user; the
* caller's `codevId` argument must match it.
*/
async function requireSelfCodev(codevId: string): Promise<string> {
const { user } = await requireUser();

if (user.id !== codevId) {
throw new Error("Forbidden");
}

return user.id;
}


export async function applicantTakeTest({
Expand All @@ -14,6 +33,7 @@ export async function applicantTakeTest({
codevId: string;
}) {
try {
const selfCodevId = await requireSelfCodev(codevId);
const supabase = await createClientServerComponent();

const { data, error } = await supabase
Expand All @@ -36,7 +56,7 @@ export async function applicantTakeTest({
application_status: "testing",
updated_at: new Date(),
})
.eq("id", codevId);
.eq("id", selfCodevId);

if (codevError) {
console.error("Error updating codev test:", codevError);
Expand All @@ -49,12 +69,34 @@ export async function applicantTakeTest({
}
}

/**
* Confirms an `applicant` row belongs to the signed-in user before it is
* written. `applicantId` arrives from the client, so without this any caller
* could edit another applicant's fork URL or join flags.
*/
async function assertOwnApplicant(
supabase: Awaited<ReturnType<typeof createClientServerComponent>>,
applicantId: string,
codevId: string,
) {
const { data } = await supabase
.from("applicant")
.select("codev_id")
.eq("id", applicantId)
.maybeSingle();

if (!data || data.codev_id !== codevId) {
throw new Error("Forbidden");
}
}

export async function applicantMoveToOnboard({
codevId,
}: {
codevId: string;
}) {
try {
const selfCodevId = await requireSelfCodev(codevId);
const supabase = await createClientServerComponent();

const { data: codevData, error: codevError } = await supabase
Expand All @@ -63,7 +105,7 @@ export async function applicantMoveToOnboard({
application_status: "onboarding",
updated_at: new Date(),
})
.eq("id", codevId);
.eq("id", selfCodevId);

if (codevError) {
console.error("Error updating codev test:", codevError);
Expand All @@ -84,8 +126,11 @@ export async function applicantSubmitTest({
forkUrl: string;
}) {
try {
const { user } = await requireUser();
const supabase = await createClientServerComponent();

await assertOwnApplicant(supabase, applicantId, user.id);

const { data, error } = await supabase
.from("applicant")
.update({
Expand Down Expand Up @@ -113,8 +158,11 @@ export async function applicantUpdateTestSubmission({
forkUrl: string;
}) {
try {
const { user } = await requireUser();
const supabase = await createClientServerComponent();

await assertOwnApplicant(supabase, applicantId, user.id);

const { data, error } = await supabase
.from("applicant")
.update({
Expand Down Expand Up @@ -144,8 +192,11 @@ export async function applicantUpdateJoinedStatus({
joinedMessenger?: boolean;
}) {
try {
const { user } = await requireUser();
const supabase = await createClientServerComponent();

await assertOwnApplicant(supabase, applicantId, user.id);

const updateData: any = {
updated_at: new Date(),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


import { revalidatePath } from "next/cache";
import { NewApplicantType } from "./types";
import { NewApplicantType } from "@/types/applicants";
import { createAdminClient } from "@/utils/supabase/admin";
import { createClientServerComponent } from "@/utils/supabase/server";
import { invalidateCache } from "@/lib/server/redis-cache";
Expand Down
Loading