Skip to content
Open
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
6 changes: 6 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,9 @@ export const BROKEN = 'broken';
export const LOCKED = 'locked';

export const MANUAL = 'manual';

export enum AgreementGated {
UPLOAD = 'upload',
UPLOAD_VIDEOS = 'upload.videos',
UPLOAD_FILES = 'upload.files',
}
5 changes: 5 additions & 0 deletions src/course-outline/page-alerts/PageAlerts.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import PropTypes from 'prop-types';
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link, useNavigate } from 'react-router-dom';
import { AlertAgreementGatedFeature } from '@src/generic/agreement-gated-feature';
import { AgreementGated } from '../../constants';
import CourseOutlinePageAlertsSlot from '../../plugin-slots/CourseOutlinePageAlertsSlot';
import advancedSettingsMessages from '../../advanced-settings/messages';
import { OutOfSyncAlert } from '../../course-libraries/OutOfSyncAlert';
Expand Down Expand Up @@ -438,6 +440,9 @@ const PageAlerts = ({
{conflictingFilesPasteAlert()}
{newFilesPasteAlert()}
{renderOutOfSyncAlert()}
<AlertAgreementGatedFeature
gatingTypes={[AgreementGated.UPLOAD, AgreementGated.UPLOAD_VIDEOS, AgreementGated.UPLOAD_FILES]}
/>
<CourseOutlinePageAlertsSlot />
</>
);
Expand Down
22 changes: 22 additions & 0 deletions src/data/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,25 @@ export async function getPreviewModulestoreMigration(
const { data } = await client.get(getPreviewModulestoreMigrationUrl(), { params });
return camelCaseObject(data);
}

export const getUserAgreementRecordApi = (agreementType: string) => `${getConfig().LMS_BASE_URL}/api/agreements/v1/agreement_record/${agreementType}`;

export async function getUserAgreementRecord(agreementType: string) {
const client = getAuthenticatedHttpClient();
const { data } = await client.get(getUserAgreementRecordApi(agreementType));
return camelCaseObject(data);
}

export async function updateUserAgreementRecord(agreementType: string) {
const client = getAuthenticatedHttpClient();
const { data } = await client.post(getUserAgreementRecordApi(agreementType));
return camelCaseObject(data);
}

export const getUserAgreementApi = (agreementType: string) => `${getConfig().LMS_BASE_URL}/api/agreements/v1/agreement/${agreementType}/`;

export async function getUserAgreement(agreementType: string) {
const client = getAuthenticatedHttpClient();
const { data } = await client.get(getUserAgreementApi(agreementType));
return camelCaseObject(data);
}
63 changes: 55 additions & 8 deletions src/data/apiHooks.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import {
skipToken, useMutation, useQuery, useQueryClient,
} from '@tanstack/react-query';
import { getConfig } from '@edx/frontend-platform';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { UserAgreement, UserAgreementRecord } from '@src/data/types';
import { libraryAuthoringQueryKeys } from '@src/library-authoring/data/apiHooks';
import {
getWaffleFlags,
waffleFlagDefaults,
bulkModulestoreMigrate,
getModulestoreMigrationStatus,
skipToken, useMutation, useQueries, useQuery, useQueryClient, UseQueryOptions,
} from '@tanstack/react-query';
import {
BulkMigrateRequestData,
bulkModulestoreMigrate,
getCourseDetails,
getPreviewModulestoreMigration,
getModulestoreMigrationStatus,
getPreviewModulestoreMigration, getUserAgreement,
getUserAgreementRecord,
getWaffleFlags, updateUserAgreementRecord,
waffleFlagDefaults,
} from './api';
import { RequestStatus, RequestStatusType } from './constants';

Expand Down Expand Up @@ -130,3 +133,47 @@ export const useCourseDetails = (courseId: string) => {
status,
};
};

export const getGatingAgreementTypes = (gatingTypes: string[]): string[] => (
[...new Set(
gatingTypes
.flatMap(gatingType => getConfig().AGREEMENT_GATING?.[gatingType])
.filter(item => Boolean(item)),
)]
);

export const useUserAgreementRecord = (agreementType:string) => (
useQuery<UserAgreementRecord, Error>({
queryKey: ['agreement-record', agreementType],
queryFn: () => getUserAgreementRecord(agreementType),
retry: false,
})
);

export const useUserAgreementRecords = (agreementTypes:string[]) => (
useQueries({
queries: agreementTypes.map<UseQueryOptions<UserAgreementRecord, Error>>(agreementType => ({
queryKey: ['agreement-record', agreementType],
queryFn: () => getUserAgreementRecord(agreementType),
retry: false,
})),
})
);

export const useUserAgreementRecordUpdater = (agreementType:string) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async () => updateUserAgreementRecord(agreementType),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['agreement-record', agreementType] });
},
});
};

export const useUserAgreement = (agreementType:string) => (
useQuery<UserAgreement, Error>({
queryKey: ['agreements', agreementType],
queryFn: () => getUserAgreement(agreementType),
retry: false,
})
);
16 changes: 16 additions & 0 deletions src/data/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,19 @@ export type SelectionState = {
sectionId?: string;
subsectionId?: string;
};

export interface UserAgreementRecord {
username: string;
agreementType: string;
acceptedAt: string | null;
isCurrent: boolean;
}

export interface UserAgreement {
type: string;
name: string;
summary: string;
hasText: boolean;
url: string;
updated: string;
}
47 changes: 25 additions & 22 deletions src/files-and-videos/files-page/CourseFilesTable.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useIntl } from '@edx/frontend-platform/i18n';
import { CheckboxFilter } from '@openedx/paragon';
import { AgreementGated, UPLOAD_FILE_MAX_SIZE } from '@src/constants';
import {
addAssetFile,
deleteAssetFile,
Expand All @@ -20,13 +21,13 @@ import {
FileTable,
ThumbnailColumn,
} from '@src/files-and-videos/generic';
import { GatedComponentWrapper } from '@src/generic/agreement-gated-feature';
import { useModels } from '@src/generic/model-store';
import { DeprecatedReduxState } from '@src/store';
import { getFileSizeToClosestByte } from '@src/utils';
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { UPLOAD_FILE_MAX_SIZE } from '@src/constants';

export const CourseFilesTable = () => {
const intl = useIntl();
Expand Down Expand Up @@ -159,26 +160,28 @@ export const CourseFilesTable = () => {
return null;
}
return (
<>
<FileTable
{...{
courseId,
data,
handleAddFile,
handleDeleteFile,
handleDownloadFile,
handleLockFile,
handleUsagePaths,
handleErrorReset,
handleFileOrder,
tableColumns,
maxFileSize,
thumbnailPreview,
infoModalSidebar,
files: assets,
}}
/>
<FileValidationModal {...{ handleFileOverwrite }} />
</>
<GatedComponentWrapper gatingTypes={[AgreementGated.UPLOAD, AgreementGated.UPLOAD_FILES]}>
<>
<FileTable
{...{
courseId,
data,
handleAddFile,
handleDeleteFile,
handleDownloadFile,
handleLockFile,
handleUsagePaths,
handleErrorReset,
handleFileOrder,
tableColumns,
maxFileSize,
thumbnailPreview,
infoModalSidebar,
files: assets,
}}
/>
<FileValidationModal {...{ handleFileOverwrite }} />
</>
</GatedComponentWrapper>
);
};
7 changes: 6 additions & 1 deletion src/files-and-videos/files-page/FilesPage.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useIntl } from '@edx/frontend-platform/i18n';

import { Container } from '@openedx/paragon';
import { useEffect } from 'react';
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';

import { useCourseAuthoringContext } from '@src/CourseAuthoringContext';
Expand All @@ -10,6 +10,8 @@ import Placeholder from '@src/editors/Placeholder';
import { RequestStatus } from '@src/data/constants';
import getPageHeadTitle from '@src/generic/utils';
import EditFileAlertsSlot from '@src/plugin-slots/EditFileAlertsSlot';
import { AlertAgreementGatedFeature } from '@src/generic/agreement-gated-feature';
import { AgreementGated } from '@src/constants';

import { EditFileErrors } from '../generic';
import { fetchAssets, resetErrors } from './data/thunks';
Expand Down Expand Up @@ -55,6 +57,9 @@ const FilesPage = () => {
updateFileStatus={updateAssetStatus}
loadingStatus={loadingStatus}
/>
<AlertAgreementGatedFeature
gatingTypes={[AgreementGated.UPLOAD, AgreementGated.UPLOAD_FILES]}
/>
<EditFileAlertsSlot />
<div className="h2">
{intl.formatMessage(messages.heading)}
Expand Down
56 changes: 30 additions & 26 deletions src/files-and-videos/videos-page/CourseVideosTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useIntl } from '@edx/frontend-platform/i18n';
import {
ActionRow, Button, CheckboxFilter, useToggle,
} from '@openedx/paragon';
import { AgreementGated } from '@src/constants';
import { RequestStatus } from '@src/data/constants';
import {
ActiveColumn,
Expand Down Expand Up @@ -29,6 +30,7 @@ import messages from '@src/files-and-videos/videos-page/messages';
import TranscriptSettings from '@src/files-and-videos/videos-page/transcript-settings';
import UploadModal from '@src/files-and-videos/videos-page/upload-modal';
import VideoThumbnail from '@src/files-and-videos/videos-page/VideoThumbnail';
import { GatedComponentWrapper } from '@src/generic/agreement-gated-feature';
import { useModels } from '@src/generic/model-store';
import { DeprecatedReduxState } from '@src/store';
import React, { useEffect, useRef } from 'react';
Expand Down Expand Up @@ -224,23 +226,24 @@ export const CourseVideosTable = () => {
];

return (
<>
<ActionRow>
<ActionRow.Spacer />
{isVideoTranscriptEnabled ? (
<Button
variant="link"
size="sm"
onClick={() => {
openTranscriptSettings();
handleErrorReset({ errorType: 'transcript' });
}}
>
{intl.formatMessage(messages.transcriptSettingsButtonLabel)}
</Button>
) : null}
</ActionRow>
{
<GatedComponentWrapper gatingTypes={[AgreementGated.UPLOAD, AgreementGated.UPLOAD_VIDEOS]}>
<>
<ActionRow>
<ActionRow.Spacer />
{isVideoTranscriptEnabled ? (
<Button
variant="link"
size="sm"
onClick={() => {
openTranscriptSettings();
handleErrorReset({ errorType: 'transcript' });
}}
>
{intl.formatMessage(messages.transcriptSettingsButtonLabel)}
</Button>
) : null}
</ActionRow>
{
loadingStatus !== RequestStatus.FAILED && (
<>
{isVideoTranscriptEnabled && (
Expand Down Expand Up @@ -275,14 +278,15 @@ export const CourseVideosTable = () => {
</>
)
}
<UploadModal
{...{
isUploadTrackerOpen,
currentUploadingIdsRef: uploadingIdsRef.current,
handleUploadCancel,
addVideoStatus,
}}
/>
</>
<UploadModal
{...{
isUploadTrackerOpen,
currentUploadingIdsRef: uploadingIdsRef.current,
handleUploadCancel,
addVideoStatus,
}}
/>
</>
</GatedComponentWrapper>
);
};
7 changes: 6 additions & 1 deletion src/files-and-videos/videos-page/VideosPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { useEffect } from 'react';
import { AgreementGated } from '@src/constants';
import { AlertAgreementGatedFeature } from '@src/generic/agreement-gated-feature';
import React, { useEffect } from 'react';
import { Helmet } from 'react-helmet';
import { useDispatch, useSelector } from 'react-redux';

Expand Down Expand Up @@ -57,6 +59,9 @@ const VideosPage = () => {
updateFileStatus={updateVideoStatus}
loadingStatus={loadingStatus}
/>
<AlertAgreementGatedFeature
gatingTypes={[AgreementGated.UPLOAD, AgreementGated.UPLOAD_VIDEOS]}
/>
<EditVideoAlertsSlot />
<h2>{intl.formatMessage(messages.heading)}</h2>
<CourseVideosSlot />
Expand Down
Loading