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 all 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
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);


67 changes: 66 additions & 1 deletion apps/upskii/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -4340,5 +4340,70 @@
"users": "Users",
"teams": "Teams",
"platform_users": "Platform users"
}
},
"approval-data-table": {
"workspace": "Workspace",
"requested_by": "Requested by",
"feature_requested": "Feature requested",
"request_details": "Request ",
"status": "Status",
"status-pending": "Pending",
"status-approved": "Approved",
"status-rejected": "Rejected",
"requested": "Requested",
"admin_notes": "Admin notes",
"no_notes": "No notes",
"reviewed": "Reviewed",
"reviewed_at": "Reviewed at {date}",
"reviewed_by": "Reviewed by {name}",
"created_at": "Created at",
"id": "ID",
"workspace_name": "Workspace",
"creator_name": "Requested by",
"request_message": "Request details",
"loading": "Loading",
"try-again": "Try Again",
"total-requests": "Total Requests",
"pending": "Pending",
"approved": "Approved",
"rejected": "Rejected",
"all-requests": "All Requests",
"all-features": "All features",
"approveSuccess": "Approved {feature} access for {workspace}. Features will be enabled automatically.",
"approveError": "Failed to approve request",
"rejectError": "Please provide a reason for rejection",
"rejectSuccess": "Rejected {feature} access request for {workspace}",
"detailsTitle": "Feature Request Details",
"requestedBy": "Requested by",
"timeline": "Timeline",
"viewDetails": "View Details",
"approveRequest": "Approve Request",
"rejectRequest": "Reject Request",
"revokeAccess": "Revoke Access",
"adminNotes": "Admin Notes",
"notesBy": "Notes by {name}",
"close": "Close",
"approveTitle": "Approve {feature} access",
"approveOverride": "This will override the previous rejection",
"approveDescription": "This will enable {feature} features for \"{workspace}\" and grant the workspace access to {feature} functionality.",
"adminNotesPlaceholder": "Add any notes about this approval",
"cancel": "Cancel",
"approving": "Approving",
"rejectTitle": "Reject {feature} access",
"rejectDescription": "This will reject the {feature} access request for {workspace}. Please provide a reason for the rejection.",
"reasonForRejection": "Reason for Rejection",
"reasonForRejectionPlaceholder": "Please explain why this request is being rejected",
"rejecting": "Rejecting",
"openMenu": "Open menu",
"filter-by-feature": "Filter by Feature",
"filter-by-status": "Filter by status",
"requested_at": "Requested {date}"
},
"approvals": {
"feature-access-requests": "Feature Requests",
"feature-access-requests-description": "Review and approve feature requests from workspace creators. Approved requests will automatically enable features for the respective workspaces."
},
"request-feature": "Request Feature",
"unlock-education-features": "Unlock Education Features",
"transform-your-workspace": "Transform your workspace with comprehensive learning tools: create\ncourses, design interactive quizzes, track student progress, issue\ncertificates, and leverage AI-powered teaching assistants."
}
90 changes: 88 additions & 2 deletions apps/upskii/messages/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -4128,7 +4128,28 @@
"delete": "Xóa không gian làm việc",
"deleting": "Đang xóa",
"feature-request": {
"title": "Yêu cầu mở khóa tính năng"
"title": "Yêu cầu mở khóa tính năng",
"toasts": {
"error": {
"reason-required": "Vui lòng cung cấp một lý do cho yêu cầu của bạn",
"feature-required": "Vui lòng chọn một tính năng để yêu cầu",
"request-failed": "Không thể gửi yêu cầu. Vui lòng thử lại sau"
},
"success": {
"request-submitted": "Yêu cầu truy cập tính năng được gửi thành công! \nQuản trị viên nền tảng sẽ xem xét yêu cầu của bạn."
}
},
"description": "Yêu cầu truy cập vào các tính năng mới cho không gian của bạn \"{ws}\".",
"description-2": "Quản trị viên nền tảng sẽ xem xét yêu cầu của bạn và phê duyệt quyền truy cập nếu thích hợp.",
"feature-label": "Tính năng",
"feature-placeholder": "Chọn một tính năng",
"no-features": "Tất cả các tính năng có sẵn đã được yêu cầu hoặc bật.",
"reason-label": "Tại sao bạn cần truy cập vào tính năng này? \n*",
"reason-placeholder": "Vui lòng giải thích cách bạn có kế hoạch sử dụng các tính năng {feature} trong không gian làm việc của bạn. \nBao gồm chi tiết về trường hợp sử dụng dự định của bạn, đối tượng mục tiêu và mục tiêu ...",
"reason-description": "Tối thiểu 20 ký tự. \nHãy cụ thể về mục tiêu của bạn.",
"existing-requests-title": "Các yêu cầu hiện tại của bạn",
"submitting": "Đang gửi ...",
"submit": "Gửi yêu cầu"
}
},
"ws-slides": {
Expand Down Expand Up @@ -4319,5 +4340,70 @@
"empty_state_description": "Hoàn thành các khóa học để giành chứng chỉ và thể hiện thành tích của bạn.",
"browse_courses": "Xem các khóa học",
"certified": "Được chứng nhận"
}
},
"approval-data-table": {
"workspace": "Không gian",
"requested_by": "Yêu cầu bởi",
"feature_requested": "Tính năng yêu cầu",
"request_details": "Chi tiết yêu cầu",
"status": "Trạng thái",
"status-pending": "Chưa giải quyết",
"status-approved": "Đã chấp thuận",
"status-rejected": "Đã từ chối",
"requested": "Đã yêu cầu",
"admin_notes": "Ghi chú quản trị viên",
"no_notes": "Không có ghi chú",
"reviewed": "Đã xem xét",
"reviewed_at": "Đã xem xét lúc {date}",
"reviewed_by": "Đã xem xét bởi {name}",
"created_at": "Đã tạo lúc",
"id": "ID",
"workspace_name": "Không gian",
"creator_name": "Yêu cầu bởi",
"request_message": "Chi tiết yêu cầu",
"loading": "Đang tải",
"try-again": "Thử lại",
"total-requests": "Tổng số yêu cầu",
"pending": "Chưa giải quyết",
"approved": "Chấp thuận",
"rejected": "Từ chối",
"all-requests": "Tất cả các yêu cầu",
"all-features": "Tất cả các tính năng",
"approveSuccess": "Đã phê duyệt tính năng {feature} cho {workspace}. \nCác tính năng sẽ được bật tự động.",
"approveError": "Không phê duyệt yêu cầu",
"rejectError": "Vui lòng cung cấp một lý do để từ chối",
"rejectSuccess": "Đã từ chối quyền truy cập tính năng {feature} cho {workspace}",
"detailsTitle": "Chi tiết yêu cầu tính năng",
"requestedBy": "Được yêu cầu bởi",
"timeline": "Dòng thời gian",
"viewDetails": "Xem thông tin",
"approveRequest": "Phê duyệt yêu cầu",
"rejectRequest": "Từ chối yêu cầu",
"revokeAccess": "Thu hồi quyền truy cập",
"adminNotes": "Ghi chú quản trị viên",
"notesBy": "Ghi chú bởi {name}",
"close": "Đóng",
"approveTitle": "Phê duyệt quyền truy cập tính năng {feature} ",
"approveOverride": "Điều này sẽ ghi đè lên quyết định từ chối trước đó",
"approveDescription": "Điều này sẽ kích hoạt các tính năng {feature} cho \"{workspace}\".",
"adminNotesPlaceholder": "Thêm bất kỳ ghi chú nào về sự chấp thuận này",
"cancel": "Hủy bỏ",
"approving": "Đang chấp thuận",
"rejectTitle": "Từ chối quyền truy cập tính năng {feature}",
"rejectDescription": "Điều này sẽ từ chối yêu cầu truy cập các tính năng {feature} cho {workspace}. \nVui lòng cung cấp một lý do cho sự từ chối.",
"reasonForRejection": "Lý do từ chối",
"reasonForRejectionPlaceholder": "Vui lòng giải thích lý do tại sao yêu cầu này đang bị từ chối",
"rejecting": "Đang từ chối",
"openMenu": "Mở menu",
"filter-by-feature": "Bộ lọc theo tính năng",
"filter-by-status": "Lọc theo trạng thái",
"requested_at": "Được yêu cầu {date}"
},
"approvals": {
"feature-access-requests": "Yêu cầu tính năng",
"feature-access-requests-description": "Xem xét và phê duyệt các yêu cầu tính năng từ người tạo không gian làm việc. \nCác yêu cầu được phê duyệt sẽ tự động kích hoạt các tính năng cho các không gian làm việc tương ứng."
},
"request-feature": "Yêu cầu tính năng",
"unlock-education-features": "Mở khóa các tính năng giáo dục",
"transform-your-workspace": "Biến đổi không gian làm việc của bạn với các công cụ học tập toàn diện: Tạo Các khóa học, các câu đố tương tác thiết kế, theo dõi tiến trình của sinh viên, ủy quyền giấy chứng nhận, và tận dụng các trợ lý giảng dạy hỗ trợ AI."
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
'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';
import { AlertCircle, RefreshCw } from '@tuturuuu/ui/icons';
import { useTranslations } from 'next-intl';

Check warning on line 9 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#L9

Added line #L9 was not covered by tests
import { useSearchParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'sonner';
Expand Down Expand Up @@ -49,13 +51,16 @@
totalPages: 1,
});

const t = useTranslations('approval-data-table');

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#L54-L55

Added lines #L54 - L55 were not covered by tests
// Get current search parameters
const status = searchParams.get('status') || undefined;
const feature = searchParams.get('feature') || undefined;

Check warning on line 58 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#L58

Added line #L58 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 63 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#L63

Added line #L63 was not covered by tests
const fetchApprovals = async (showRefreshLoader = false) => {
try {
if (showRefreshLoader) {
Expand All @@ -71,9 +76,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 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

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

Check warning on line 82 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#L82

Added line #L82 was not covered by tests
{
method: 'GET',
headers: {
Expand All @@ -92,7 +98,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 101 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#L101

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

Check warning on line 115 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#L115

Added line #L115 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 142 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#L122-L142

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

Check warning on line 144 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#L144

Added line #L144 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">
<RefreshCw className="h-5 w-5 animate-spin" />
<span>Loading...</span>
<span>{t('loading')}</span>

Check warning on line 149 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#L149

Added line #L149 was not covered by tests
</div>
</div>
);
Expand All @@ -135,7 +162,7 @@
</div>
<Button onClick={handleRefresh} variant="outline" size="sm">
<RefreshCw className="mr-2 h-4 w-4" />
Try Again
{t('try-again')}

Check warning on line 165 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#L165

Added line #L165 was not covered by tests
</Button>
</div>
);
Expand All @@ -144,33 +171,60 @@
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>{t('loading')}</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">
{t('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">{t('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">{t('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">{t('rejected')}</div>
</div>

Check warning on line 210 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#L174-L210

Added lines #L174 - L210 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 213 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#L213

Added line #L213 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>{t('loading')}</span>
</>
)}
</div>

Check warning on line 227 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#L215-L227

Added lines #L215 - L227 were not covered by tests
</div>
</div>

Expand All @@ -180,6 +234,7 @@
approvalsColumns(t, namespace, handleRefresh)
}
count={approvals.count}
namespace="approval-data-table"

Check warning on line 237 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#L237

Added line #L237 was not covered by tests
defaultVisibility={{
id: false,
created_at: false,
Expand Down
Loading