Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
00f0347
Add personal user cohorts API and hook
Codebmk May 27, 2026
6347d46
Merge branch 'staging' into personal-devices-claim
Codebmk May 27, 2026
4d1b052
Filter personal cohorts and update device count
Codebmk May 27, 2026
2002065
Show Device Visibility and support personal cohorts
Codebmk May 27, 2026
8634d6a
Add personal cohort scope & refine loading logic
Codebmk May 27, 2026
2f46697
Refactor claim device modal into steps
Codebmk May 27, 2026
f6a7ea7
Add onboarding checklist to Home page
Codebmk May 28, 2026
cab848e
Add device choice dialog and update checklist
Codebmk May 28, 2026
f7a0a78
Disable claim/import in onboarding checklist
Codebmk May 28, 2026
f6de946
Merge branch 'staging' into personal-devices-claim
Codebmk May 28, 2026
b73f216
Remove collapse and dismiss from onboarding checklist
Codebmk May 28, 2026
5b0eb72
Add guided/fast claim modes & refactor modal
Codebmk May 28, 2026
890afdb
Merge branch 'staging' into personal-devices-claim
Codebmk May 29, 2026
e08d051
Merge branch 'staging' into personal-devices-claim
Codebmk May 31, 2026
5154530
Improve Personal Workspace & Banner
Codebmk May 31, 2026
582e6a6
Add an inline "Administrator" pill tag next to "Vertex" in the logo area
Codebmk May 31, 2026
856d447
Remove "Download for Windows" button from the admin panel sidebar
Codebmk May 31, 2026
8a5b118
Add the active organisation name as a subtitle beneath "Vertex" in th…
Codebmk May 31, 2026
d0e7cca
conditional rendering of admin pill
Codebmk May 31, 2026
e5b86a0
Change label from device management to home
Codebmk May 31, 2026
af3a556
fix font application
Codebmk May 31, 2026
1d0ec32
add cohort assignment under step and add mark as done action button
Codebmk May 31, 2026
117fea2
make data sharing toggle buttons for private cohorts more visible
Codebmk May 31, 2026
a1a62c2
fix slide to network visibility component
Codebmk May 31, 2026
84b9fed
fix spacing
Codebmk May 31, 2026
98a59c0
fix visibility of banner
Codebmk May 31, 2026
63e449d
add assigned orgs to cohorts card under cohort details with unassign …
Codebmk May 31, 2026
0eb7892
remove obsolete organization setup banner
Codebmk May 31, 2026
f13fb84
fix modal close behavior
Codebmk May 31, 2026
76f4e49
make org list scrollable
Codebmk May 31, 2026
d47007f
preserve onboarding completion status
Codebmk May 31, 2026
787059d
enhance success state display
Codebmk May 31, 2026
0489cf6
refactor import device modal
Codebmk May 31, 2026
09e21a6
fix cohort listing
Codebmk May 31, 2026
1dd762a
Merge branch 'staging' into personal-devices-claim
Codebmk May 31, 2026
e59c070
add method selection step, refresh devices
Codebmk May 31, 2026
4080f25
refactor: enhance device import and cohort creation dialogs with pres…
Codebmk Jun 1, 2026
8de0393
fix loading issue
Codebmk Jun 2, 2026
6748baf
Rename 'group' to 'cohort' and support imports
Codebmk Jun 2, 2026
d1f560a
Invalidate queries on device/cohort updates
Codebmk Jun 2, 2026
8246297
Trigger onSuccess after cohort assignment
Codebmk Jun 2, 2026
93faaf9
fix bulk import bug
Codebmk Jun 3, 2026
ed92614
handle all three user contexts
Codebmk Jun 3, 2026
f7e9c89
display bulk import results
Codebmk Jun 3, 2026
2629f17
Add typing, fixes & read-only onboarding UI
Codebmk Jun 3, 2026
9c9b312
Handle cohort verification errors
Codebmk Jun 3, 2026
0f87158
sort the organizations alphabetically by their title
Codebmk Jun 3, 2026
c8f03fe
Update ConfirmationStep.tsx
Codebmk Jun 3, 2026
cd48f64
place import cohort as first option
Codebmk Jun 3, 2026
771c6df
Update claim-device-modal.tsx
Codebmk Jun 3, 2026
5d1ed1e
Update changelog.md
Codebmk Jun 3, 2026
400a901
Merge branch 'staging' into personal-devices-claim
Codebmk Jun 3, 2026
9ea2963
Update changelog.md
Codebmk Jun 3, 2026
b5e7fe3
Don’t turn fetch failures into the empty state
Codebmk Jun 3, 2026
a6c6957
Remove set-visibility from autoSteps
Codebmk Jun 3, 2026
f67b63c
Use the session fallback for claim mutations too.
Codebmk Jun 3, 2026
1f19d80
Comment out QR-scan method in MethodSelectStep
Codebmk Jun 3, 2026
7884739
Dynamic imports; improve QR scanner error UI
Codebmk Jun 3, 2026
9b6de8b
Use dynamic imports for import steps
Codebmk Jun 3, 2026
6ccabc3
Validate QR code fields in claim util
Codebmk Jun 3, 2026
b1ead7b
Gate the unassign control behind an explicit update permission.
Codebmk Jun 3, 2026
8b849df
Don’t hide the required network field unless a value is guaranteed.
Codebmk Jun 3, 2026
ec8bc09
Reset bulkResults when the dialog state is cleared.
Codebmk Jun 3, 2026
60ca82b
Pass the created device’s _id into onSuccess, not its name.
Codebmk Jun 3, 2026
b06cc5b
Align ImportDeviceFormData with EXPECTED_FIELDS to prevent field-mapp…
Codebmk Jun 3, 2026
78a5c48
Update network-visibility-card.tsx
Codebmk Jun 3, 2026
3cba223
Add StepLoader to dynamic imports
Codebmk Jun 3, 2026
45f6236
Set analytics base URL by environment
Codebmk Jun 3, 2026
34ef4a8
Remove Claim page and rename CTAs to 'Add AirQo Device'
Codebmk Jun 3, 2026
177c68d
Open Claim flow as modal instead of navigating
Codebmk Jun 3, 2026
a7086fb
Update changelog.md
Codebmk Jun 3, 2026
ff9c6d5
Increase idle session timeout to 6 hours
Codebmk Jun 3, 2026
4dca181
Persist cookie consent across sessions
Codebmk Jun 3, 2026
2b5d5e8
Refactor cohort orgs UI and add cohort groups API
Codebmk Jun 3, 2026
6ffd763
Add smart Save flow; update device changelog
Codebmk Jun 3, 2026
d542b31
fix lint errors
Codebmk Jun 3, 2026
8a5021d
Gate both claim CTAs with PERMISSIONS.DEVICE.CLAIM.
Codebmk Jun 3, 2026
f12f460
Refresh the deployable device list after an in-place claim.
Codebmk Jun 3, 2026
0c36256
Use a production-safe fallback when NEXT_PUBLIC_ENV is missing.
Codebmk Jun 3, 2026
76bb974
Update cohort-organizations-card.tsx
Codebmk Jun 3, 2026
9a60dd2
Update cohort-organizations-card.tsx
Codebmk Jun 3, 2026
c39061f
Update cohort-organizations-card.tsx
Codebmk Jun 3, 2026
9919300
Update device onboarding copy
Codebmk Jun 3, 2026
47a7f73
Add tooltip to unassign switch and rename columns
Codebmk Jun 3, 2026
e5c1693
Replace 'Claim' with 'Add AirQo device'
Codebmk Jun 3, 2026
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
18 changes: 13 additions & 5 deletions src/vertex/app/(authenticated)/admin/cohorts/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ import ReusableButton from "@/components/shared/button/ReusableButton";
import { UnassignCohortDevicesDialog } from "@/components/features/cohorts/unassign-cohort-devices";
import CohortMeasurementsApiCard from "@/components/features/cohorts/cohort-measurements-api-card";
import { usePageTitle } from "@/context/page-title-context";
import { CohortOrganizationsCard } from "@/components/features/cohorts/cohort-organizations-card";

// Loading skeleton for content grid
const ContentGridSkeleton = () => (
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-6 items-start">
{[...Array(4)].map((_, i) => (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mt-6 items-start">
{[...Array(3)].map((_, i) => (
<div key={i} className="h-48 bg-gray-200 rounded-lg animate-pulse" />
))}
</div>
Expand All @@ -31,7 +32,9 @@ export default function CohortDetailsPage() {
const cohortId = params?.id as string;

const { data: cohort, isLoading, error } = useCohortDetails(cohortId);

usePageTitle({ title: cohort?.name || "Cohort Details", section: "Cohorts" });

const canUpdateDevice = usePermission(PERMISSIONS.DEVICE.UPDATE);
const [cohortDetails, setCohortDetails] = useState<{
name: string;
Expand Down Expand Up @@ -103,9 +106,9 @@ export default function CohortDetailsPage() {
{String((error as Error)?.message || "Unknown error")}
</div>
) : (
<div className="flex flex-col gap-6">
{/* Cohort basic info card */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mt-6 items-start">
<div className="flex flex-col">
{/* Cards section */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 my-6 items-start">
<CohortDetailsCard
name={cohort?.name || ""}
id={cohort?._id || ""}
Expand All @@ -115,6 +118,11 @@ export default function CohortDetailsPage() {
cohort_tags={cohort?.cohort_tags}
/>
<CohortMeasurementsApiCard cohortId={cohortId} />
<CohortOrganizationsCard
cohortId={cohortId}
cohortName={cohort?.name || ""}
canUnassign={canUpdateDevice}
/>
</div>

{/* Devices list */}
Expand Down
73 changes: 45 additions & 28 deletions src/vertex/app/(authenticated)/cohorts/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@

import { useMemo } from "react";
import { useRouter } from "next/navigation";
import { useSession } from "next-auth/react";
import { format } from 'date-fns';
import { Badge } from "@/components/ui/badge";
import ReusableTable, { TableColumn } from "@/components/shared/table/ReusableTable";
import { useCohorts, useGroupCohorts } from "@/core/hooks/useCohorts";
import { useCohorts, useGroupCohorts, usePersonalUserCohorts } from "@/core/hooks/useCohorts";
import { Cohort } from "@/app/types/cohorts";
import { useServerSideTableState } from "@/core/hooks/useServerSideTableState";
import { RouteGuard } from "@/components/layout/accessConfig/route-guard";
import { PERMISSIONS } from "@/core/permissions/constants";
import { useUserContext } from "@/core/hooks/useUserContext";
import { useAppSelector } from "@/core/redux/hooks";
import CohortsEmptyState from "@/components/features/cohorts/cohorts-empty-state";

type CohortRow = {
Expand All @@ -23,33 +25,49 @@ type CohortRow = {

export default function CohortsPage() {
const router = useRouter();

const { data: session } = useSession();
const user = useAppSelector((state) => state.user.userDetails);

const {
pagination, setPagination,
searchTerm, setSearchTerm,
sorting, setSorting
} = useServerSideTableState({ initialPageSize: 25 });

const { userDetails: user, isExternalOrg, activeGroup } = useUserContext();
const { isExternalOrg, activeGroup, userScope } = useUserContext();

// 1. Fetch group cohort IDs if external org
const { data: groupCohortIds, isLoading: isLoadingGroupCohorts } = useGroupCohorts(
activeGroup?._id,
{ enabled: isExternalOrg && !!activeGroup?._id }
);
const isPersonalScope = userScope === 'personal';
const userId = (session?.user as { id?: string })?.id || user?._id;

// --- Personal scope: use personal user cohorts API ---
const {
data: personalCohortIds = [],
isLoading: isLoadingPersonalCohorts,
} = usePersonalUserCohorts(userId, {
enabled: isPersonalScope && !!userId,
});

// --- Org scope: use group cohorts ---
const {
data: groupCohortIds,
isLoading: isLoadingGroupCohorts,
} = useGroupCohorts(activeGroup?._id, {
enabled: isExternalOrg && !!activeGroup?._id && !isPersonalScope,
});

// 2. Determine target IDs based on scope
// Resolve effective cohort IDs based on scope
const targetCohortIds = useMemo(() => {
if (isExternalOrg) {
return groupCohortIds || [];
}
// Personal scope
return user?.cohort_ids || [];
}, [isExternalOrg, groupCohortIds, user?.cohort_ids]);
if (isPersonalScope) return personalCohortIds;
if (isExternalOrg) return groupCohortIds || [];
return [];
}, [isPersonalScope, personalCohortIds, isExternalOrg, groupCohortIds]);

const isResolvingIds =
(isPersonalScope && isLoadingPersonalCohorts) ||
(isExternalOrg && !isPersonalScope && isLoadingGroupCohorts);

// 3. Conditional Fetching
const hasIdsToFetch = targetCohortIds && targetCohortIds.length > 0;
const shouldFetchCohorts = hasIdsToFetch && !(isExternalOrg && isLoadingGroupCohorts);
const hasIdsToFetch = targetCohortIds.length > 0;
const shouldFetchCohorts = hasIdsToFetch && !isResolvingIds;

const { cohorts, meta, isFetching: isFetchingCohorts, error } = useCohorts(
{
Expand All @@ -65,13 +83,12 @@ export default function CohortsPage() {

const pageCount = meta?.totalPages ?? 0;

const isDeterminingIds = isExternalOrg && isLoadingGroupCohorts;
const isLoading = isExternalOrg ? (isLoadingGroupCohorts || isFetchingCohorts) : isFetchingCohorts;
const showEmptyState = !isLoading && (!hasIdsToFetch || (cohorts && cohorts.length === 0));

const tableLoading = isDeterminingIds || (hasIdsToFetch && isFetchingCohorts);

const tableLoading = isResolvingIds || (hasIdsToFetch && isFetchingCohorts);
const displayError = (!hasIdsToFetch && !tableLoading) ? null : error;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
const showEmptyState =
!tableLoading &&
!displayError &&
(!hasIdsToFetch || (cohorts && cohorts.length === 0));

const rows: CohortRow[] = useMemo(() => (cohorts || []).map((c: Cohort) => ({
...c,
Expand Down Expand Up @@ -118,7 +135,7 @@ export default function CohortsPage() {
<RouteGuard permission={PERMISSIONS.DEVICE.VIEW}>
<CohortsEmptyState />
</RouteGuard>
)
);
}

return (
Expand All @@ -133,13 +150,13 @@ export default function CohortsPage() {

<div>
<ReusableTable
title="Your Cohorts"
title={isPersonalScope ? "My Cohorts" : "Your Cohorts"}
data={rows}
columns={columns}
loading={tableLoading}
onRowClick={(item: unknown) => {
const row = item as CohortRow;
if (row?.id) router.push(`/cohorts/${row.id}`)
if (row?.id) router.push(`/cohorts/${row.id}`);
}}
emptyState={displayError ? (displayError.message || "Unable to load cohorts") : "No cohorts available"}
serverSidePagination
Expand All @@ -156,4 +173,4 @@ export default function CohortsPage() {
</div>
</RouteGuard>
);
}
}
195 changes: 0 additions & 195 deletions src/vertex/app/(authenticated)/devices/claim/page.tsx

This file was deleted.

Loading
Loading