Skip to content

Commit 1b9b9fc

Browse files
2 parents 7731e82 + 890510b commit 1b9b9fc

11 files changed

Lines changed: 631 additions & 110 deletions

File tree

‎apps/codebility/app/applicant/onboarding/_components/OnboardingClient.tsx‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,15 @@ export default function OnboardingClient({
164164
};
165165

166166
const canWatchVideo = (videoNumber: number): boolean => {
167+
// Always allow watching video 1
167168
if (videoNumber === 1) return true;
168-
if (videoNumber === 2) return progress.video1;
169-
if (videoNumber === 3) return progress.video2;
170-
if (videoNumber === 4) return progress.video3;
169+
170+
// For other videos, allow if:
171+
// 1. Previous video is completed (sequential unlock), OR
172+
// 2. This video has already been completed (allow rewatching)
173+
if (videoNumber === 2) return progress.video1 || progress.video2;
174+
if (videoNumber === 3) return progress.video2 || progress.video3;
175+
if (videoNumber === 4) return progress.video3 || progress.video4;
171176
return false;
172177
};
173178

@@ -251,10 +256,10 @@ export default function OnboardingClient({
251256
<div className="flex gap-4">
252257
<Button
253258
onClick={progress.currentVideo === 4 ? handleProceedToQuiz : handleNextVideo}
254-
disabled={false} // TODO: Change back to: !isVideoCompleted(progress.currentVideo)
259+
disabled={!isVideoCompleted(progress.currentVideo)}
255260
className="from-customTeal to-customViolet-100 w-24 bg-gradient-to-r via-customBlue-100"
256261
>
257-
Next
262+
{progress.currentVideo === 4 ? 'Proceed to Quiz' : 'Next'}
258263
</Button>
259264
</div>
260265
</div>

‎apps/codebility/app/applicant/onboarding/_components/VideoPlayer.tsx‎

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,22 @@ export default function VideoPlayer({
2626
const [currentTime, setCurrentTime] = useState(0);
2727
const [duration, setDuration] = useState(0);
2828

29+
const saveVideoProgress = async () => {
30+
const video = videoRef.current;
31+
if (!video) return;
32+
33+
await updateVideoProgress({
34+
applicantId,
35+
videoNumber,
36+
watchedDuration: video.currentTime,
37+
totalDuration: video.duration,
38+
completed: true,
39+
});
40+
41+
// Notify parent component to refresh progress state (to unlock Next button)
42+
onVideoComplete();
43+
};
44+
2945
useEffect(() => {
3046
const video = videoRef.current;
3147
if (!video) return;
@@ -35,10 +51,12 @@ export default function VideoPlayer({
3551
const percentWatched = (video.currentTime / video.duration) * 100;
3652
setProgress(percentWatched);
3753

38-
// Consider video watched if they've seen 90% or more
39-
if (percentWatched >= 90 && !hasWatched) {
54+
// Mark as watched if they've seen 98% or more (to unlock Next button)
55+
// But don't auto-advance - user must click Next manually
56+
if (percentWatched >= 98 && !hasWatched) {
4057
setHasWatched(true);
41-
handleVideoComplete();
58+
// Save progress in background without triggering navigation
59+
saveVideoProgress();
4260
}
4361
};
4462

@@ -62,21 +80,6 @@ export default function VideoPlayer({
6280
};
6381
}, [hasWatched]);
6482

65-
const handleVideoComplete = async () => {
66-
const video = videoRef.current;
67-
if (!video) return;
68-
69-
await updateVideoProgress({
70-
applicantId,
71-
videoNumber,
72-
watchedDuration: video.currentTime,
73-
totalDuration: video.duration,
74-
completed: true,
75-
});
76-
77-
onVideoComplete();
78-
};
79-
8083
const formatTime = (seconds: number) => {
8184
const mins = Math.floor(seconds / 60);
8285
const secs = Math.floor(seconds % 60);

‎apps/codebility/app/applicant/waiting/_components/applicantStep4.tsx‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ export default function ApplicantStep4({ user }: { user: any }) {
9191
</span>
9292
</button>
9393
</Link>
94+
<div className="mt-3 rounded-md border border-green-600 bg-green-950/30 p-2">
95+
<p className="text-xs text-green-300">
96+
<span className="font-bold">After joining:</span> Navigate to the <span className="font-semibold">"Bench"</span> subchat for your next instructions and updates.
97+
</p>
98+
</div>
9499
</div>
95100
</div>
96101

‎apps/codebility/app/home/applicants/_components/applicantLists.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ function ApplicantLists({
8181
}}
8282
className="w-full"
8383
>
84-
<TabsList className="!grid !h-auto w-full max-w-3xl grid-cols-2 gap-1 rounded-lg bg-gray-100 p-1 md:grid-cols-5 md:gap-1 dark:bg-gray-800">
84+
<TabsList className="!grid !h-auto w-full grid-cols-2 gap-1 rounded-lg bg-gray-100 p-1 md:grid-cols-5 md:gap-1 dark:bg-gray-800">
8585
<TabsTrigger
8686
value="applying"
8787
className="!flex !h-auto flex-col gap-1 rounded-md px-3 py-3 text-sm font-medium transition-all data-[state=active]:bg-white data-[state=active]:shadow-sm md:flex-row md:gap-2 md:px-4 md:py-2 dark:data-[state=active]:bg-gray-900"

‎apps/codebility/app/home/in-house/_components/InHouseView.tsx‎

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ export default function InHouseView({ initialData }: InHouseViewProps) {
2222
const [isPending, startTransition] = useTransition();
2323
const stats = getMemberStats(data);
2424

25+
// Sorting state
26+
const [sortConfig, setSortConfig] = useState<{
27+
key: "date_joined" | "display_position" | null;
28+
direction: "asc" | "desc";
29+
}>({
30+
key: null,
31+
direction: "asc",
32+
});
33+
2534
// Filters
2635
const [filters, setFilters] = useState({
2736
status: "",
@@ -97,14 +106,45 @@ export default function InHouseView({ initialData }: InHouseViewProps) {
97106
return true;
98107
});
99108

100-
// Pagination - increased items per page for better table usage
109+
// Sorting logic
110+
const sortedData = [...filteredData].sort((a, b) => {
111+
if (!sortConfig.key) return 0;
112+
113+
if (sortConfig.key === "date_joined") {
114+
const dateA = a.date_joined ? new Date(a.date_joined).getTime() : 0;
115+
const dateB = b.date_joined ? new Date(b.date_joined).getTime() : 0;
116+
117+
return sortConfig.direction === "asc" ? dateA - dateB : dateB - dateA;
118+
}
119+
120+
if (sortConfig.key === "display_position") {
121+
const posA = (a.display_position || "").toLowerCase();
122+
const posB = (b.display_position || "").toLowerCase();
123+
124+
if (posA < posB) return sortConfig.direction === "asc" ? -1 : 1;
125+
if (posA > posB) return sortConfig.direction === "asc" ? 1 : -1;
126+
return 0;
127+
}
128+
129+
return 0;
130+
});
131+
132+
// Handle sort toggle
133+
const handleSort = (key: "date_joined" | "display_position") => {
134+
setSortConfig((prev) => ({
135+
key,
136+
direction: prev.key === key && prev.direction === "asc" ? "desc" : "asc",
137+
}));
138+
};
139+
140+
// Pagination - increased items per page for better table usage (use sortedData)
101141
const {
102142
currentPage,
103143
totalPages,
104144
paginatedData,
105145
handleNextPage,
106146
handlePreviousPage,
107-
} = usePagination(filteredData, 50);
147+
} = usePagination(sortedData, 50);
108148

109149
const sharedProps = {
110150
data: paginatedData,
@@ -175,7 +215,7 @@ export default function InHouseView({ initialData }: InHouseViewProps) {
175215
{/* Table View with Loading State */}
176216
{isPending ? (
177217
<InHouseTableSkeleton rows={10} />
178-
) : filteredData.length === 0 ? (
218+
) : sortedData.length === 0 ? (
179219
<div className="flex min-h-[400px] flex-col items-center justify-center p-8 text-center">
180220
<div className="mb-4 text-4xl">🔍</div>
181221
<h3 className="mb-2 text-lg font-medium text-gray-900 dark:text-white">No members found</h3>
@@ -202,6 +242,8 @@ export default function InHouseView({ initialData }: InHouseViewProps) {
202242
onNextPage: handleNextPage,
203243
onPreviousPage: handlePreviousPage,
204244
}}
245+
sortConfig={sortConfig}
246+
onSort={handleSort}
205247
/>
206248
)}
207249
</div>

‎apps/codebility/app/home/in-house/_components/table/InHouseMobileTable.tsx‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ interface InHouseMobileTableProps {
3434
message: string,
3535
) => Promise<void>;
3636
handleDownloadNda: (codevId: string) => void;
37+
sortConfig?: {
38+
key: "date_joined" | "display_position" | null;
39+
direction: "asc" | "desc";
40+
};
41+
onSort?: (key: "date_joined" | "display_position") => void;
3742
}
3843

3944
const defaultImage = "/assets/svgs/icon-codebility-black.svg";
@@ -97,6 +102,8 @@ export function InHouseMobileTable({
97102
roles,
98103
handleSendNdaEmail,
99104
handleDownloadNda,
105+
sortConfig,
106+
onSort,
100107
}: InHouseMobileTableProps) {
101108
const [editingId, setEditingId] = useState<string | null>(null);
102109

‎apps/codebility/app/home/in-house/_components/table/InHouseTable.tsx‎

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import DefaultPagination from "@/components/ui/pagination";
88
import { pageSize } from "@/constants";
99
import { Codev, InternalStatus } from "@/types/home/codev";
1010
import { createClientClientComponent } from "@/utils/supabase/client";
11-
import { Download, Link2, Mail } from "lucide-react";
11+
import { ArrowUpDown, Download, Link2, Mail } from "lucide-react";
1212
import { toast } from "react-hot-toast";
1313

1414
import { Button } from "@codevs/ui/button";
@@ -61,6 +61,11 @@ interface InHouseTableProps {
6161
onNextPage: () => void;
6262
onPreviousPage: () => void;
6363
};
64+
sortConfig?: {
65+
key: "date_joined" | "display_position" | null;
66+
direction: "asc" | "desc";
67+
};
68+
onSort?: (key: "date_joined" | "display_position") => void;
6469
}
6570

6671
// Default fallback image for team members without avatars
@@ -141,6 +146,8 @@ export function InHouseTable({
141146
onDataChange,
142147
pagination,
143148
onDelete,
149+
sortConfig,
150+
onSort,
144151
}: InHouseTableProps) {
145152
// Supabase client instance for database operations
146153
const [supabase, setSupabase] = useState<any>(null);
@@ -352,6 +359,20 @@ export function InHouseTable({
352359

353360
const handlePreviousPage = useCallback(() => pagination.onPreviousPage(), [pagination]);
354361

362+
/**
363+
* Renders sort indicator for sortable columns
364+
*/
365+
const SortIndicator = ({ columnKey }: { columnKey: "date_joined" | "display_position" }) => {
366+
if (!sortConfig || sortConfig.key !== columnKey) {
367+
return <ArrowUpDown className="ml-1 inline h-3 w-3 opacity-30" />;
368+
}
369+
return (
370+
<span className="ml-1 inline">
371+
{sortConfig.direction === "asc" ? "↑" : "↓"}
372+
</span>
373+
);
374+
};
375+
355376
/**
356377
* Advanced pagination handler for direct page navigation
357378
*/
@@ -387,6 +408,8 @@ export function InHouseTable({
387408
roles={roles}
388409
handleSendNdaEmail={handleSendNdaEmail}
389410
handleDownloadNda={handleDownloadNda}
411+
sortConfig={sortConfig}
412+
onSort={onSort}
390413
/>
391414

392415
{/* Desktop Table Container - Hidden on mobile, visible on XL+ screens */}
@@ -407,6 +430,16 @@ export function InHouseTable({
407430
>
408431
{column.label}
409432
</TableHead>
433+
) : column.key === "display_position" || column.key === "date_joined" ? (
434+
// Sortable columns - Hidden on smaller screens, visible on 2XL+
435+
<TableHead
436+
key={column.key}
437+
className="dark:text-light-900 hidden cursor-pointer px-1 py-2 text-xs font-semibold text-black hover:bg-light-700 dark:hover:bg-dark-400 2xl:table-cell"
438+
onClick={() => onSort?.(column.key as "date_joined" | "display_position")}
439+
>
440+
{column.label}
441+
<SortIndicator columnKey={column.key as "date_joined" | "display_position"} />
442+
</TableHead>
410443
) : (
411444
// Hidden on smaller screens, visible on 2XL+
412445
<TableHead

0 commit comments

Comments
 (0)