Skip to content

Enhance Request Feature #3142

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Jun 21, 2025
Merged
Show file tree
Hide file tree
Changes from 8 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
9 changes: 9 additions & 0 deletions apps/db/supabase/migrations/20250620133223_new_migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
create type "public"."FEATURE_FLAG" as enum ('ENABLE_AI', 'ENABLE_EDUCATION', 'ENABLE_CHALLENGES', 'ENABLE_QUIZZES');

drop index if exists "public"."workspace_education_access_requests_unique_pending";

alter table "public"."workspace_education_access_requests" add column "feature" "FEATURE_FLAG" not null default 'ENABLE_EDUCATION'::"FEATURE_FLAG";

CREATE UNIQUE INDEX workspace_education_access_requests_unique_pending ON public.workspace_education_access_requests USING btree (ws_id, feature) WHERE (status = 'pending'::text);


Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import { approvalsColumns } from './columns';
import { FeatureFilter } from './feature-filter';

Check warning on line 4 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx#L4

Added line #L4 was not covered by tests
import { StatusFilter } from './status-filter';
import { CustomDataTable } from '@/components/custom-data-table';
import { Button } from '@tuturuuu/ui/button';
Expand Down Expand Up @@ -51,11 +52,12 @@

// Get current search parameters
const status = searchParams.get('status') || undefined;
const feature = searchParams.get('feature') || undefined;

Check warning on line 55 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx#L55

Added line #L55 was not covered by tests
const q = searchParams.get('q') || undefined;
const page = searchParams.get('page') || '1';
const pageSize = searchParams.get('pageSize') || '10';

// Fetch education access requests
// Fetch feature access requests

Check warning on line 60 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx#L60

Added line #L60 was not covered by tests
const fetchApprovals = async (showRefreshLoader = false) => {
try {
if (showRefreshLoader) {
Expand All @@ -71,9 +73,10 @@
if (page) queryParams.set('page', page);
if (pageSize) queryParams.set('pageSize', pageSize);
if (status && status !== 'all') queryParams.set('status', status);
if (feature && feature !== 'all') queryParams.set('feature', feature);

Check warning on line 76 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx#L76

Added line #L76 was not covered by tests

const response = await fetch(
`/api/v1/admin/education-access-requests?${queryParams.toString()}`,
`/api/v1/admin/feature-requests?${queryParams.toString()}`,

Check warning on line 79 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx#L79

Added line #L79 was not covered by tests
{
method: 'GET',
headers: {
Expand All @@ -92,7 +95,7 @@
const data = await response.json();
setApprovals(data);
} catch (error) {
console.error('Error fetching education access requests:', error);
console.error('Error fetching feature access requests:', error);

Check warning on line 98 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx#L98

Added line #L98 was not covered by tests
const errorMessage =
error instanceof Error ? error.message : 'Failed to fetch requests';
setError(errorMessage);
Expand All @@ -106,15 +109,36 @@
// Fetch data when dependencies change
useEffect(() => {
fetchApprovals();
}, [status, q, page, pageSize]);
}, [status, feature, q, page, pageSize]);

Check warning on line 112 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx#L112

Added line #L112 was not covered by tests

// Handle manual refresh
const handleRefresh = () => {
fetchApprovals(true);
};

// Handle bulk actions

// Get feature statistics
const getFeatureStats = () => {
const stats = {
pending: 0,
approved: 0,
rejected: 0,
total: 0,
};

approvals.data.forEach((request) => {
stats[request.status]++;
stats.total++;
});

return stats;
};

const stats = getFeatureStats();

Check warning on line 139 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx#L119-L139

Added lines #L119 - L139 were not covered by tests
// Show loading state
if (isLoading) {
if (isLoading && !isRefreshing) {

Check warning on line 141 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx#L141

Added line #L141 was not covered by tests
return (
<div className="flex h-64 items-center justify-center">
<div className="flex items-center gap-2 text-muted-foreground">
Expand Down Expand Up @@ -144,33 +168,58 @@
return (
<div className="flex min-h-full w-full flex-col">
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
{isLoading && (
<>
<RefreshCw className="h-4 w-4 animate-spin" />
<span>Loading requests...</span>
</>
)}
{!isLoading && (
<span>
{approvals.count} total request{approvals.count !== 1 ? 's' : ''}
</span>
)}
{isLoading && (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<RefreshCw className="h-4 w-4 animate-spin" />
<span>Loading requests...</span>
</div>
)}
</div>
{/* Statistics and Controls */}
<div className="mb-3 space-y-4">
{/* Statistics Cards */}
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
<div className="rounded-lg border bg-card p-4">
<div className="text-2xl font-bold text-foreground">
{stats.total}
</div>
<div className="text-sm text-muted-foreground">Total Requests</div>
</div>
<div className="rounded-lg border bg-card p-4">
<div className="text-2xl font-bold text-yellow-600">
{stats.pending}
</div>
<div className="text-sm text-muted-foreground">Pending</div>
</div>
<div className="rounded-lg border bg-card p-4">
<div className="text-2xl font-bold text-green-600">
{stats.approved}
</div>
<div className="text-sm text-muted-foreground">Approved</div>
</div>
<div className="rounded-lg border bg-card p-4">
<div className="text-2xl font-bold text-red-600">
{stats.rejected}
</div>
<div className="text-sm text-muted-foreground">Rejected</div>
</div>

Check warning on line 205 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx#L171-L205

Added lines #L171 - L205 were not covered by tests
</div>

<div className="flex items-center gap-4">
<Button
onClick={handleRefresh}
disabled={isLoading || isRefreshing}
variant="outline"
size="sm"
>
<RefreshCw
className={`mr-2 h-4 w-4 ${isRefreshing ? 'animate-spin' : ''}`}
/>
Refresh
</Button>
<div className="flex items-center justify-end gap-4">

Check warning on line 208 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx#L208

Added line #L208 was not covered by tests
<StatusFilter currentStatus={status} />
<FeatureFilter currentFeature={feature} />
</div>

{/* Controls */}
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
{isLoading && (
<>
<RefreshCw className="h-4 w-4 animate-spin" />
<span>Loading requests...</span>
</>
)}
</div>

Check warning on line 222 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/approvals-table.tsx#L210-L222

Added lines #L210 - L222 were not covered by tests
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
import { Badge } from '@tuturuuu/ui/badge';
import { DataTableColumnHeader } from '@tuturuuu/ui/custom/tables/data-table-column-header';
import { BookOpenText, User } from '@tuturuuu/ui/icons';
import {
REQUESTABLE_KEY_TO_FEATURE_FLAG,
getRequestableFeature,
} from '@tuturuuu/utils/feature-flags/requestable-features';

Check warning on line 13 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/columns.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/columns.tsx#L10-L13

Added lines #L10 - L13 were not covered by tests
import { cn } from '@tuturuuu/utils/format';
import moment from 'moment';

Expand Down Expand Up @@ -78,16 +82,59 @@
);
},
},
{
accessorKey: 'feature_requested',
header: ({ column }) => (
<DataTableColumnHeader t={t} column={column} title="Feature Requested" />
),
cell: ({ row }) => {
const featureRequested = row.getValue('feature_requested') as string;

// Try to get the feature configuration
let featureConfig = null;
let FeatureIcon = BookOpenText; // Default icon

// Check if it's a feature flag and get the requestable key
const featureFlag = featureRequested as any;
if (featureFlag) {
// Try to find the requestable key for this feature flag
const requestableKey = Object.entries(
REQUESTABLE_KEY_TO_FEATURE_FLAG
// @ts-ignore
).find(([_key, flag]) => flag === featureFlag)?.[0];

if (requestableKey) {
featureConfig = getRequestableFeature(requestableKey as any);
if (featureConfig?.icon) {
FeatureIcon = featureConfig.icon;
}
}
}

return (
<div className="flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-md bg-dynamic-blue/10">
<FeatureIcon className="h-4 w-4 text-dynamic-blue" />
</div>
<div className="flex flex-col">
<div className="text-sm font-medium text-dynamic-blue">
{featureConfig?.name || featureRequested}
</div>
<div className="text-xs text-muted-foreground">
{featureRequested}
</div>
</div>
</div>
);
},
},

Check warning on line 130 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/columns.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/columns.tsx#L85-L130

Added lines #L85 - L130 were not covered by tests
{
accessorKey: 'request_message',
header: ({ column }) => (
<DataTableColumnHeader t={t} column={column} title="Request Details" />
),
cell: ({ row }) => (
<div className="max-w-64 space-y-1">
<div className="text-sm font-medium text-dynamic-blue">
Education Features
</div>
<div className="max-w-64">

Check warning on line 137 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/columns.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/columns.tsx#L137

Added line #L137 was not covered by tests
<div className="line-clamp-3 text-sm break-words text-muted-foreground">
{row.getValue('request_message')}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'use client';

import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@tuturuuu/ui/select';
import {
getRequestableFeature,
getRequestableFeatureKeys,
} from '@tuturuuu/utils/feature-flags/requestable-features';
import { useRouter, useSearchParams } from 'next/navigation';

interface FeatureFilterProps {
currentFeature?: string;
}

export function FeatureFilter({ currentFeature }: FeatureFilterProps) {
const router = useRouter();
const searchParams = useSearchParams();

const handleFeatureChange = (feature: string) => {
const params = new URLSearchParams(searchParams);

if (feature === 'all') {
params.delete('feature');
} else {
params.set('feature', feature);
}

// Reset to first page when filtering
params.delete('page');

router.push(`?${params.toString()}`);
};

const availableFeatures = getRequestableFeatureKeys();

return (
<Select value={currentFeature || 'all'} onValueChange={handleFeatureChange}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Filter by feature" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Features</SelectItem>
{availableFeatures.map((featureKey) => {
const feature = getRequestableFeature(featureKey);
return (
<SelectItem key={featureKey} value={featureKey}>
{feature.name}
</SelectItem>
);
})}
</SelectContent>
</Select>
);
}

Check warning on line 59 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/feature-filter.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/feature-filter.tsx#L2-L59

Added lines #L2 - L59 were not covered by tests
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
return (
<>
<FeatureSummary
pluralTitle="Education Access Requests"
description="Review and approve education feature requests from workspace creators. Approved requests will automatically enable education features for the respective workspaces."
pluralTitle="Feature Access Requests"
description="Review and approve feature requests from workspace creators. Manage access to AI, Education, Quizzes, and Challenges features. Approved requests will automatically enable features for the respective workspaces."

Check warning on line 35 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/page.tsx#L34-L35

Added lines #L34 - L35 were not covered by tests
/>
<Separator className="my-4" />
<ApprovalsTable />
Expand Down
Loading