Skip to content

Commit 9d6956e

Browse files
committed
feature: implement versioning limits context and enhance file replacement logic
1 parent 7f94127 commit 9d6956e

File tree

11 files changed

+155
-28
lines changed

11 files changed

+155
-28
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"@iconscout/react-unicons": "^1.1.6",
99
"@internxt/css-config": "1.1.0",
1010
"@internxt/lib": "1.4.1",
11-
"@internxt/sdk": "=1.11.21",
11+
"@internxt/sdk": "=1.11.22",
1212
"@internxt/ui": "0.1.1",
1313
"@phosphor-icons/react": "^2.1.7",
1414
"@popperjs/core": "^2.11.6",

src/app/drive/components/NameCollisionDialog/NameCollisionContainer.tsx

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import workspacesSelectors from 'app/store/slices/workspaces/workspaces.selector
1414
import { uploadFoldersWithManager } from 'app/network/UploadFolderManager';
1515
import replaceFileService from 'views/Drive/services/replaceFile.service';
1616
import { Network, getEnvironmentConfig } from 'app/drive/services/network.service';
17+
import { useVersioningLimits } from 'views/Drive/components/VersionHistory/context/VersioningLimitsContext';
1718

1819
type NameCollisionContainerProps = {
1920
currentFolderId: string;
@@ -45,6 +46,8 @@ const NameCollisionContainer: FC<NameCollisionContainerProps> = ({
4546
() => moveDestinationFolderId ?? currentFolderId,
4647
[moveDestinationFolderId, currentFolderId],
4748
);
49+
const { limits } = useVersioningLimits();
50+
const isVersioningEnabled = limits?.versioning?.enabled ?? false;
4851

4952
const handleNewItems = (files: (File | DriveItemData)[], folders: (IRoot | DriveItemData)[]) => [
5053
...files,
@@ -143,6 +146,27 @@ const NameCollisionContainer: FC<NameCollisionContainerProps> = ({
143146
return await uploadPromise;
144147
};
145148

149+
const replaceFileVersion = async (file: File, itemToReplace: DriveItemData) => {
150+
const newFileId = await uploadFileAndGetFileId(file, itemToReplace);
151+
await replaceFileService.replaceFile(itemToReplace.uuid, {
152+
fileId: newFileId,
153+
size: file.size,
154+
});
155+
};
156+
157+
const trashAndUpload = async (file: File, itemToReplace: DriveItemData) => {
158+
await moveItemsToTrash([itemToReplace]);
159+
await dispatch(
160+
storageThunks.uploadItemsThunk({
161+
files: [file],
162+
parentFolderId: folderId,
163+
options: {
164+
disableDuplicatedNamesCheck: true,
165+
},
166+
}),
167+
);
168+
};
169+
146170
const replaceAndUploadItem = async ({
147171
itemsToReplace,
148172
itemsToUpload,
@@ -156,7 +180,7 @@ const NameCollisionContainer: FC<NameCollisionContainerProps> = ({
156180

157181
if ((itemToUpload as IRoot).fullPathEdited) {
158182
await moveItemsToTrash([itemToReplace]);
159-
uploadFoldersWithManager({
183+
await uploadFoldersWithManager({
160184
payload: [
161185
{
162186
root: { ...(itemToUpload as IRoot) },
@@ -165,20 +189,13 @@ const NameCollisionContainer: FC<NameCollisionContainerProps> = ({
165189
],
166190
selectedWorkspace,
167191
dispatch,
168-
}).then(() => {
169-
dispatch(fetchSortedFolderContentThunk(folderId));
170192
});
171193
} else {
172194
const file = itemToUpload as File;
173-
const newFileId = await uploadFileAndGetFileId(file, itemToReplace);
174-
175-
await replaceFileService.replaceFile(itemToReplace.uuid, {
176-
fileId: newFileId,
177-
size: file.size,
178-
});
179-
180-
dispatch(fetchSortedFolderContentThunk(folderId));
195+
isVersioningEnabled ? await replaceFileVersion(file, itemToReplace) : await trashAndUpload(file, itemToReplace);
181196
}
197+
198+
dispatch(fetchSortedFolderContentThunk(folderId));
182199
}
183200
};
184201

src/services/date.service.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,21 @@ export const formatDefaultDate = (date: Date | string | number, translate: (key:
2828
return dayjs(date).format(`D MMM, YYYY [${translatedAt}] HH:mm`);
2929
};
3030

31+
function getDaysUntilExpiration(expiresAt: Date | string): number {
32+
const expirationDate = dayjs(expiresAt);
33+
const now = dayjs();
34+
const diffInDays = expirationDate.diff(now, 'day', true);
35+
return Math.max(0, Math.ceil(diffInDays));
36+
}
37+
3138
const dateService = {
3239
format,
3340
fromNow,
3441
isDateOneBefore,
3542
getCurrentDate,
3643
getExpirationDate,
3744
formatDefaultDate,
45+
getDaysUntilExpiration,
3846
};
3947

4048
export default dateService;

src/views/Drive/components/VersionHistory/Sidebar.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import errorService from 'services/error.service';
1818
import notificationsService, { ToastType } from 'app/notifications/services/notifications.service';
1919
import { FileVersion, GetFileLimitsResponse } from '@internxt/sdk/dist/drive/storage/types';
2020

21-
type VersionInfo = { id: string; createdAt: string };
21+
type VersionInfo = { id: string; updatedAt: string };
2222

2323
const Sidebar = () => {
2424
const dispatch = useAppDispatch();
@@ -39,14 +39,14 @@ const Sidebar = () => {
3939
const [isBatchDeleteMode, setIsBatchDeleteMode] = useState(false);
4040
const [currentVersion, setCurrentVersion] = useState<VersionInfo>({
4141
id: '',
42-
createdAt: '',
42+
updatedAt: '',
4343
});
4444

4545
useEffect(() => {
4646
if (item) {
4747
setCurrentVersion({
4848
id: item.fileId,
49-
createdAt: item.createdAt,
49+
updatedAt: item.updatedAt,
5050
});
5151
}
5252
}, [item]);
@@ -156,8 +156,8 @@ const Sidebar = () => {
156156
const restoredVersion = await fileVersionService.restoreVersion(item.uuid, versionToRestore.id);
157157

158158
setCurrentVersion({
159-
id: restoredVersion.id,
160-
createdAt: restoredVersion.createdAt,
159+
id: restoredVersion.fileId,
160+
updatedAt: new Date().toISOString(),
161161
});
162162

163163
notificationsService.show({
@@ -213,7 +213,7 @@ const Sidebar = () => {
213213
<VersionHistorySkeleton />
214214
) : (
215215
<>
216-
<CurrentVersionItem key={currentVersion.id} createdAt={currentVersion.createdAt} userName={userName} />
216+
<CurrentVersionItem key={currentVersion.id} createdAt={currentVersion.updatedAt} userName={userName} />
217217

218218
<AutosaveSection
219219
totalVersionsCount={totalVersionsCount}

src/views/Drive/components/VersionHistory/components/VersionItem.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Info, DotsThree } from '@phosphor-icons/react';
22
import { Checkbox, Dropdown, Avatar } from '@internxt/ui';
33
import { useTranslationContext } from 'app/i18n/provider/TranslationProvider';
44
import { useDropdownPositioning, useVersionItemActions } from '../hooks';
5-
import { formatVersionDate } from '../utils';
5+
import { formatVersionDate, getDaysUntilExpiration } from '../utils';
66
import { FileVersion } from '@internxt/sdk/dist/drive/storage/types';
77

88
interface VersionItemProps {
@@ -53,12 +53,16 @@ export const VersionItem = ({ version, userName, isSelected, onSelectionChange }
5353
/>
5454
<div className="flex min-w-0 flex-1 flex-col space-y-1">
5555
<div className="flex items-center justify-between">
56-
<span className="text-base font-semibold text-gray-100">{formatVersionDate(version.createdAt)}</span>
56+
<span className="text-base font-semibold text-gray-100">{formatVersionDate(version.updatedAt)}</span>
5757
</div>
58-
{version.expiresInDays !== undefined && (
58+
{version.expiresAt !== undefined && (
5959
<div className="flex items-center space-x-1 text-[12px] text-red-dark">
6060
<Info size={16} weight="regular" />
61-
<span>{translate('modals.versionHistory.expiresInDays', { days: version.expiresInDays })}</span>
61+
<span>
62+
{translate('modals.versionHistory.expiresInDays', {
63+
days: getDaysUntilExpiration(version.expiresAt),
64+
})}
65+
</span>
6266
</div>
6367
)}
6468
<div className="flex items-center space-x-2 pt-1">
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import React, { createContext, useContext, useEffect, useState, useCallback } from 'react';
2+
import { GetFileLimitsResponse } from '@internxt/sdk/dist/drive/storage/types';
3+
import fileVersionService from '../services/fileVersion.service';
4+
import errorService from 'services/error.service';
5+
6+
interface VersioningLimitsContextValue {
7+
limits: GetFileLimitsResponse | null;
8+
isLoading: boolean;
9+
refetch: () => Promise<void>;
10+
}
11+
12+
export const VersioningLimitsContext = createContext<VersioningLimitsContextValue | undefined>(undefined);
13+
14+
export const VersioningLimitsProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
15+
const [limits, setLimits] = useState<GetFileLimitsResponse | null>(null);
16+
const [isLoading, setIsLoading] = useState(false);
17+
18+
const fetchLimits = useCallback(async () => {
19+
setIsLoading(true);
20+
try {
21+
const response = await fileVersionService.getLimits();
22+
setLimits(response);
23+
} catch (error) {
24+
const castedError = errorService.castError(error);
25+
errorService.reportError(castedError);
26+
} finally {
27+
setIsLoading(false);
28+
}
29+
}, []);
30+
31+
useEffect(() => {
32+
void fetchLimits();
33+
}, [fetchLimits]);
34+
35+
return (
36+
<VersioningLimitsContext.Provider value={{ limits, isLoading, refetch: fetchLimits }}>
37+
{children}
38+
</VersioningLimitsContext.Provider>
39+
);
40+
};
41+
42+
export const useVersioningLimits = (): VersioningLimitsContextValue => {
43+
const context = useContext(VersioningLimitsContext);
44+
if (!context) {
45+
return {
46+
limits: null,
47+
isLoading: false,
48+
refetch: async () => {},
49+
};
50+
}
51+
return context;
52+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
export { useDropdownPositioning } from './useDropdownPositioning';
22
export { useVersionItemActions } from './useVersionItemActions';
3+
export { useVersioningLimits } from '../context/VersioningLimitsContext';
4+
export { useVersionHistoryMenuConfig } from './useVersionHistoryMenuConfig';
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { useSelector } from 'react-redux';
2+
import { useContext } from 'react';
3+
import { useAppDispatch } from 'app/store/hooks';
4+
import { uiActions } from 'app/store/slices/ui';
5+
import workspacesSelectors from 'app/store/slices/workspaces/workspaces.selectors';
6+
import navigationService from 'services/navigation.service';
7+
import { DriveItemData } from 'app/drive/types';
8+
import { VersioningLimitsContext } from '../context/VersioningLimitsContext';
9+
import { isVersioningExtensionAllowed } from '../utils';
10+
import { VersionHistoryMenuConfig } from '../../DriveExplorer/components/DriveItemContextMenu';
11+
12+
export const useVersionHistoryMenuConfig = (selectedItem?: DriveItemData): VersionHistoryMenuConfig => {
13+
const dispatch = useAppDispatch();
14+
const selectedWorkspace = useSelector(workspacesSelectors.getSelectedWorkspace);
15+
const context = useContext(VersioningLimitsContext);
16+
const isVersioningEnabled = context?.limits?.versioning?.enabled ?? false;
17+
const allowedExtension = selectedItem ? isVersioningExtensionAllowed(selectedItem) : true;
18+
19+
return {
20+
locked: !isVersioningEnabled,
21+
allowedExtension,
22+
onLockedClick: () => {
23+
dispatch(uiActions.setIsPreferencesDialogOpen(true));
24+
navigationService.openPreferencesDialog({
25+
section: 'account',
26+
subsection: 'plans',
27+
workspaceUuid: selectedWorkspace?.workspaceUser.workspaceId,
28+
});
29+
},
30+
};
31+
};

src/views/Drive/components/VersionHistory/services/fileVersion.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { SdkFactory } from 'app/core/factory/sdk';
22
import { DownloadManager } from 'app/network/DownloadManager';
33
import { DriveItemData } from 'app/drive/types';
44
import { WorkspaceCredentialsDetails, WorkspaceData } from '@internxt/sdk/dist/workspaces';
5-
import { GetFileLimitsResponse, FileVersion } from '@internxt/sdk/dist/drive/storage/types';
5+
import { GetFileLimitsResponse, FileVersion, RestoreFileVersionResponse } from '@internxt/sdk/dist/drive/storage/types';
66

77
const getStorageClient = () => SdkFactory.getNewApiInstance().createNewStorageClient();
88

@@ -14,7 +14,7 @@ export async function deleteVersion(fileUuid: string, versionId: string): Promis
1414
await getStorageClient().deleteFileVersion(fileUuid, versionId);
1515
}
1616

17-
export async function restoreVersion(fileUuid: string, versionId: string): Promise<FileVersion> {
17+
export async function restoreVersion(fileUuid: string, versionId: string): Promise<RestoreFileVersionResponse> {
1818
return getStorageClient().restoreFileVersion(fileUuid, versionId);
1919
}
2020

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
11
import dateService from 'services/date.service';
2+
import { DriveItemData } from 'app/drive/types';
23

34
export const formatVersionDate = (date: string): string => dateService.format(date, 'MMM D, h:mm A');
5+
6+
const ALLOWED_VERSIONING_EXTENSIONS = ['pdf', 'docx', 'xlsx', 'csv'];
7+
8+
export const isVersioningExtensionAllowed = (item?: Pick<DriveItemData, 'type'> | null): boolean => {
9+
if (!item || !item.type) {
10+
return false;
11+
}
12+
const extension = item.type.toLowerCase();
13+
return ALLOWED_VERSIONING_EXTENSIONS.includes(extension);
14+
};
15+
16+
export const getDaysUntilExpiration = (expiresAt: string): number => dateService.getDaysUntilExpiration(expiresAt);

0 commit comments

Comments
 (0)