created share modal
This commit is contained in:
@@ -297,9 +297,9 @@ class FileController {
|
||||
const id = req.params.id;
|
||||
const userID = req.user._id;
|
||||
|
||||
await fileService.removeLink(userID, id);
|
||||
const file = await fileService.removeLink(userID, id);
|
||||
|
||||
res.send();
|
||||
res.send(file);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
console.log("\nRemove Public Link Error File Route:", e.message);
|
||||
@@ -317,9 +317,9 @@ class FileController {
|
||||
const fileID = req.params.id;
|
||||
const userID = req.user._id;
|
||||
|
||||
const token = await fileService.makePublic(userID, fileID);
|
||||
const { file, token } = await fileService.makePublic(userID, fileID);
|
||||
|
||||
res.send(token);
|
||||
res.send({ file, token });
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
console.log("\nMake Public Error File Route:", e.message);
|
||||
@@ -353,9 +353,9 @@ class FileController {
|
||||
const id = req.params.id;
|
||||
const userID = req.user._id;
|
||||
|
||||
const token = await fileService.makeOneTimePublic(userID, id);
|
||||
const { file, token } = await fileService.makeOneTimePublic(userID, id);
|
||||
|
||||
res.send(token);
|
||||
res.send({ file, token });
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
console.log("\nMake One Time Public Link Error File Route:", e.message);
|
||||
|
||||
@@ -26,7 +26,8 @@ class DbUtil {
|
||||
removeLink = async (fileID: string, userID: string) => {
|
||||
const file = await File.findOneAndUpdate(
|
||||
{ _id: new ObjectId(fileID), "metadata.owner": userID },
|
||||
{ $unset: { "metadata.linkType": "", "metadata.link": "" } }
|
||||
{ $unset: { "metadata.linkType": "", "metadata.link": "" } },
|
||||
{ new: true }
|
||||
);
|
||||
|
||||
return file;
|
||||
@@ -35,7 +36,8 @@ class DbUtil {
|
||||
makePublic = async (fileID: string, userID: string, token: string) => {
|
||||
const file = await File.findOneAndUpdate(
|
||||
{ _id: new ObjectId(fileID), "metadata.owner": userID },
|
||||
{ $set: { "metadata.linkType": "public", "metadata.link": token } }
|
||||
{ $set: { "metadata.linkType": "public", "metadata.link": token } },
|
||||
{ new: true }
|
||||
);
|
||||
|
||||
return file;
|
||||
@@ -52,7 +54,8 @@ class DbUtil {
|
||||
makeOneTimePublic = async (fileID: string, userID: string, token: string) => {
|
||||
const file = await File.findOneAndUpdate(
|
||||
{ _id: new ObjectId(fileID), "metadata.owner": userID },
|
||||
{ $set: { "metadata.linkType": "one", "metadata.link": token } }
|
||||
{ $set: { "metadata.linkType": "one", "metadata.link": token } },
|
||||
{ new: true }
|
||||
);
|
||||
|
||||
return file;
|
||||
|
||||
@@ -92,7 +92,7 @@ router.patch("/file-service/restore", auth, fileController.restoreFile);
|
||||
|
||||
router.patch("/file-service/restore-multi", auth, fileController.restoreMulti);
|
||||
|
||||
router.delete("/file-service/remove-link/:id", auth, fileController.removeLink);
|
||||
router.patch("/file-service/remove-link/:id", auth, fileController.removeLink);
|
||||
|
||||
router.delete(
|
||||
"/file-service/remove/token-video/:id",
|
||||
|
||||
@@ -39,6 +39,8 @@ class MongoFileService {
|
||||
const file = await dbUtilsFile.removeLink(fileID, userID);
|
||||
|
||||
if (!file) throw new NotFoundError("Remove Link File Not Found Error");
|
||||
|
||||
return file;
|
||||
};
|
||||
|
||||
makePublic = async (userID: string, fileID: string) => {
|
||||
@@ -48,7 +50,7 @@ class MongoFileService {
|
||||
|
||||
if (!file) throw new NotFoundError("Make Public File Not Found Error");
|
||||
|
||||
return token;
|
||||
return { file, token };
|
||||
};
|
||||
|
||||
getPublicInfo = async (fileID: string, tempToken: string) => {
|
||||
@@ -68,7 +70,7 @@ class MongoFileService {
|
||||
|
||||
if (!file) throw new NotFoundError("Make One Time Public Not Found Error");
|
||||
|
||||
return token;
|
||||
return { file, token };
|
||||
};
|
||||
|
||||
getFileInfo = async (userID: string, fileID: string) => {
|
||||
|
||||
@@ -162,6 +162,21 @@ export const renameFileAPI = async (fileID: string, name: string) => {
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const makePublicAPI = async (fileID: string) => {
|
||||
const response = await axios.patch(`/file-service/make-public/${fileID}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const makeOneTimePublicAPI = async (fileID: string) => {
|
||||
const response = await axios.patch(`/file-service/make-one/${fileID}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const removeLinkAPI = async (fileID: string) => {
|
||||
const response = await axios.patch(`/file-service/remove-link/${fileID}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// DELETE
|
||||
export const deleteFileAPI = async (fileID: string) => {
|
||||
const response = await axios.delete(`/file-service/remove`, {
|
||||
|
||||
@@ -21,7 +21,11 @@ import {
|
||||
} from "../../api/foldersAPI";
|
||||
import { useClickOutOfBounds, useUtils } from "../../hooks/utils";
|
||||
import { useAppDispatch } from "../../hooks/store";
|
||||
import { resetSelected, setMultiSelectMode } from "../../reducers/selected";
|
||||
import {
|
||||
resetSelected,
|
||||
setMultiSelectMode,
|
||||
setShareModal,
|
||||
} from "../../reducers/selected";
|
||||
import TrashIcon from "../../icons/TrashIcon";
|
||||
import MultiSelectIcon from "../../icons/MultiSelectIcon";
|
||||
import RenameIcon from "../../icons/RenameIcon";
|
||||
@@ -195,7 +199,7 @@ const ContextMenu: React.FC<ContextMenuProps> = memo((props) => {
|
||||
|
||||
const openShareItemModal = () => {
|
||||
props.closeContext();
|
||||
dispatch(setShareSelected(props.file));
|
||||
dispatch(setShareModal(props.file!));
|
||||
};
|
||||
|
||||
const downloadItem = () => {
|
||||
|
||||
@@ -49,10 +49,16 @@ const FileInfoPopup = memo(() => {
|
||||
downloadFileAPI(file._id);
|
||||
};
|
||||
|
||||
const outterWrapperClick = (e: any) => {
|
||||
if (e.target.id !== "outer-wrapper" || contextMenuState.selected) return;
|
||||
dispatch(resetPopupSelect());
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-screen h-screen bg-black bg-opacity-80 absolute top-0 left-0 right-0 bottom-0 z-50 flex justify-center items-center flex-col"
|
||||
id="outer-wrapper"
|
||||
onClick={outterWrapperClick}
|
||||
>
|
||||
{contextMenuState.selected && (
|
||||
<div onClick={clickStopPropagation}>
|
||||
|
||||
@@ -10,9 +10,14 @@ import Medias from "../Medias";
|
||||
import { useAppSelector } from "../../hooks/store";
|
||||
import PhotoViewerPopup from "../PhotoViewerPopup";
|
||||
import FileInfoPopup from "../FileInfoPopup";
|
||||
import SharePopup from "../SharePopup";
|
||||
|
||||
const MainSection = memo(() => {
|
||||
const selectedItem = useAppSelector((state) => state.selected.popupModal);
|
||||
const shareModalItem = useAppSelector(
|
||||
(state) => state.selected.shareModal.file
|
||||
);
|
||||
|
||||
const { isMedia } = useUtils();
|
||||
return (
|
||||
<div>
|
||||
@@ -30,6 +35,8 @@ const MainSection = memo(() => {
|
||||
<FileInfoPopup />
|
||||
) : undefined}
|
||||
|
||||
{shareModalItem && <SharePopup />}
|
||||
|
||||
<div className="flex flex-row h-screen w-screen pt-16">
|
||||
<LeftSection />
|
||||
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
import { memo, useEffect, useMemo, useState } from "react";
|
||||
import CloseIcon from "../../icons/CloseIcon";
|
||||
import { useAppDispatch, useAppSelector } from "../../hooks/store";
|
||||
import { getFileColor, getFileExtension } from "../../utils/files";
|
||||
import ActionsIcon from "../../icons/ActionsIcon";
|
||||
import bytes from "bytes";
|
||||
import moment from "moment";
|
||||
import { makePublicPopup, removeLinkPopup } from "../../popups/file";
|
||||
import {
|
||||
makeOneTimePublicAPI,
|
||||
makePublicAPI,
|
||||
removeLinkAPI,
|
||||
} from "../../api/filesAPI";
|
||||
import { useFilesClient } from "../../hooks/files";
|
||||
import {
|
||||
resetShareModal,
|
||||
setMainSelect,
|
||||
setShareModal,
|
||||
} from "../../reducers/selected";
|
||||
import SpinnerPage from "../SpinnerPage";
|
||||
|
||||
const SharePopup = memo(() => {
|
||||
const file = useAppSelector((state) => state.selected.shareModal.file)!;
|
||||
const [updating, setUpdating] = useState(false);
|
||||
const [shareLink, setShareLink] = useState("");
|
||||
const dispatch = useAppDispatch();
|
||||
const { invalidateFilesCache } = useFilesClient();
|
||||
|
||||
const imageColor = useMemo(
|
||||
() => getFileColor(file.filename),
|
||||
[file.filename]
|
||||
);
|
||||
|
||||
const fileExtension = useMemo(
|
||||
() => getFileExtension(file.filename, 3),
|
||||
[file.filename]
|
||||
);
|
||||
|
||||
const formattedDate = useMemo(() => {
|
||||
return moment(file.uploadDate).format("MM/DD/YYYY");
|
||||
}, [file.uploadDate, moment]);
|
||||
|
||||
const fileSize = useMemo(() => {
|
||||
return bytes(file.length);
|
||||
}, [file.length, bytes]);
|
||||
|
||||
const makePublic = async () => {
|
||||
try {
|
||||
const result = await makePublicPopup();
|
||||
if (!result) return;
|
||||
setUpdating(true);
|
||||
const { file: updatedFile } = await makePublicAPI(file._id);
|
||||
dispatch(
|
||||
setMainSelect({
|
||||
file: updatedFile,
|
||||
id: updatedFile._id,
|
||||
type: "file",
|
||||
folder: null,
|
||||
})
|
||||
);
|
||||
dispatch(setShareModal(updatedFile));
|
||||
invalidateFilesCache();
|
||||
} catch (e) {
|
||||
console.log("Error making file public", e);
|
||||
}
|
||||
setUpdating(false);
|
||||
};
|
||||
|
||||
const makeOneTimePublic = async () => {
|
||||
try {
|
||||
const result = await makePublicPopup();
|
||||
if (!result) return;
|
||||
setUpdating(true);
|
||||
const { file: updatedFile } = await makeOneTimePublicAPI(file._id);
|
||||
dispatch(
|
||||
setMainSelect({
|
||||
file: updatedFile,
|
||||
id: updatedFile._id,
|
||||
type: "file",
|
||||
folder: null,
|
||||
})
|
||||
);
|
||||
dispatch(setShareModal(updatedFile));
|
||||
invalidateFilesCache();
|
||||
} catch (e) {
|
||||
console.log("Error making file public", e);
|
||||
}
|
||||
setUpdating(false);
|
||||
};
|
||||
|
||||
const removeLink = async () => {
|
||||
try {
|
||||
const result = await removeLinkPopup();
|
||||
if (!result) return;
|
||||
setUpdating(true);
|
||||
const updatedFile = await removeLinkAPI(file._id);
|
||||
dispatch(
|
||||
setMainSelect({
|
||||
file: updatedFile,
|
||||
id: updatedFile._id,
|
||||
type: "file",
|
||||
folder: null,
|
||||
})
|
||||
);
|
||||
dispatch(setShareModal(updatedFile));
|
||||
invalidateFilesCache();
|
||||
setShareLink("");
|
||||
} catch (e) {
|
||||
console.log("Error removing link", e);
|
||||
}
|
||||
setUpdating(false);
|
||||
};
|
||||
|
||||
const copyLink = () => {
|
||||
navigator.clipboard.writeText(shareLink);
|
||||
};
|
||||
|
||||
const closeShareModal = () => {
|
||||
dispatch(resetShareModal());
|
||||
};
|
||||
|
||||
const outterWrapperClick = (e: any) => {
|
||||
if (e.target.id !== "outer-wrapper") return;
|
||||
closeShareModal();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!file.metadata.link) return;
|
||||
const url = `${window.location.origin}/download-page/${file._id}/${shareLink}`;
|
||||
console.log("url", url);
|
||||
setShareLink(url);
|
||||
}, [file.metadata.link]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-screen h-screen bg-black bg-opacity-80 absolute top-0 left-0 right-0 bottom-0 z-50 flex justify-center items-center flex-col"
|
||||
id="outer-wrapper"
|
||||
onClick={outterWrapperClick}
|
||||
>
|
||||
<div
|
||||
className="absolute top-[20px] flex justify-between w-full"
|
||||
id="actions-wrapper"
|
||||
>
|
||||
<div className="ml-4 flex items-center">
|
||||
<span className="inline-flex items-center mr-[15px] max-w-[27px] min-w-[27px] min-h-[27px] max-h-[27px]">
|
||||
<div
|
||||
className="h-[27px] w-[27px] bg-red-500 rounded-[3px] flex flex-row justify-center items-center"
|
||||
style={{ background: imageColor }}
|
||||
>
|
||||
<span className="font-semibold text-[9.5px] text-white">
|
||||
{fileExtension}
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
<p className="text-md text-white text-ellipsis overflow-hidden max-w-[200px] md:max-w-[600px] whitespace-nowrap">
|
||||
{file.filename}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex mr-4">
|
||||
<div id="action-close-wrapper" onClick={closeShareModal}>
|
||||
<CloseIcon className="pointer text-white w-[25px] h-[25px]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-[400px] p-4 bg-white rounded-md">
|
||||
<p className="text-lg mb-4 text-center">Share file</p>
|
||||
<div className="mt-2 flex justify-between">
|
||||
<span className="text-[#637381] text-[13px] font-normal leading-[20px] min-w-[50px]">
|
||||
Type
|
||||
</span>
|
||||
<span className="text-[#212b36] text-[13px] font-normal leading-[20px]">
|
||||
{fileExtension}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-2 flex justify-between">
|
||||
<span className="text-[#637381] text-[13px] font-normal leading-[20px] min-w-[50px]">
|
||||
Size
|
||||
</span>
|
||||
<span className="text-[#212b36] text-[13px] font-normal leading-[20px]">
|
||||
{fileSize}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-2 flex justify-between">
|
||||
<span className="text-[#637381] text-[13px] font-normal leading-[20px] min-w-[50px]">
|
||||
Created
|
||||
</span>
|
||||
<span className="text-[#212b36] text-[13px] font-normal leading-[20px]">
|
||||
{formattedDate}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-2 flex justify-between">
|
||||
<span className="text-[#637381] text-[13px] font-normal leading-[20px] min-w-[50px]">
|
||||
Access
|
||||
</span>
|
||||
<span className="text-[#212b36] text-[13px] font-normal leading-[20px]">
|
||||
{file.metadata.link ? "Public" : "Private"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{shareLink && (
|
||||
<div className="relative">
|
||||
<input
|
||||
placeholder="Share link"
|
||||
className="w-full h-[48px] pl-[12px] pr-[53px] text-black border border-[#637381] rounded-[5px] outline-none text-[15px] mt-4"
|
||||
value={shareLink}
|
||||
readOnly={true}
|
||||
/>
|
||||
<div className="absolute right-0 top-0 bottom-0 flex items-center justify-center">
|
||||
<a
|
||||
className="text-[#3c85ee] text-[15px] font-medium no-underline mr-2 mt-4 cursor-pointer"
|
||||
onClick={copyLink}
|
||||
>
|
||||
Copy
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!shareLink && (
|
||||
<div className="flex justify-between mt-4">
|
||||
<button
|
||||
className="py-2 px-4 inline-flex items-center justify-center border border-[#3c85ee] rounded-[4px] text-[#3c85ee] text-[15px] font-medium no-underline animate cursor-pointer hover:bg-[#f6f5fd] disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
onClick={makePublic}
|
||||
disabled={updating}
|
||||
>
|
||||
Share Indefinitely
|
||||
</button>
|
||||
<button
|
||||
className="py-2 px-4 inline-flex items-center justify-center border border-[#3c85ee] rounded-[4px] text-[#3c85ee] text-[15px] font-medium no-underline animate cursor-pointer hover:bg-[#f6f5fd] disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
onClick={makeOneTimePublic}
|
||||
disabled={updating}
|
||||
>
|
||||
Single-Use Link
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{shareLink && (
|
||||
<div className="flex justify-center mt-4">
|
||||
<button
|
||||
className="py-2 px-4 inline-flex items-center justify-center border border-[#3c85ee] rounded-[4px] text-[#3c85ee] text-[15px] font-medium no-underline animate cursor-pointer hover:bg-[#f6f5fd] disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
onClick={removeLink}
|
||||
disabled={updating}
|
||||
>
|
||||
Make Private
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{updating && (
|
||||
<div className="flex justify-center items-center mt-4">
|
||||
<SpinnerPage />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default SharePopup;
|
||||
@@ -49,3 +49,42 @@ export const trashItemsPopup = async () => {
|
||||
});
|
||||
return result.value;
|
||||
};
|
||||
|
||||
export const makePublicPopup = async () => {
|
||||
const result = await Swal.fire({
|
||||
title: "Make file public?",
|
||||
text: "This iwill make the file public, anyone with the link will be able to access the file.",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#3085d6",
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: "Yes",
|
||||
});
|
||||
return result.value;
|
||||
};
|
||||
|
||||
export const makeOneTimePublicPopup = async () => {
|
||||
const result = await Swal.fire({
|
||||
title: "Make file public?",
|
||||
text: "This iwill make the file public, anyone with the link will be able to access the file for a single time.",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#3085d6",
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: "Yes",
|
||||
});
|
||||
return result.value;
|
||||
};
|
||||
|
||||
export const removeLinkPopup = async () => {
|
||||
const result = await Swal.fire({
|
||||
title: "Remove link?",
|
||||
text: "This will remove public access to the file.",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#3085d6",
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: "Yes",
|
||||
});
|
||||
return result.value;
|
||||
};
|
||||
|
||||
@@ -20,6 +20,9 @@ export interface SelectedStateType {
|
||||
[key: string]: MainSecionType;
|
||||
};
|
||||
multiSelectCount: number;
|
||||
shareModal: {
|
||||
file: FileInterface | null;
|
||||
};
|
||||
}
|
||||
|
||||
const initialState: SelectedStateType = {
|
||||
@@ -36,6 +39,9 @@ const initialState: SelectedStateType = {
|
||||
multiSelectMode: false,
|
||||
multiSelectMap: {},
|
||||
multiSelectCount: 0,
|
||||
shareModal: {
|
||||
file: null,
|
||||
},
|
||||
};
|
||||
|
||||
const selectedSlice = createSlice({
|
||||
@@ -90,6 +96,20 @@ const selectedSlice = createSlice({
|
||||
file: null,
|
||||
};
|
||||
},
|
||||
setShareModal: (state, action: PayloadAction<FileInterface>) => {
|
||||
state.popupModal = {
|
||||
type: "",
|
||||
file: null,
|
||||
};
|
||||
state.shareModal = {
|
||||
file: action.payload,
|
||||
};
|
||||
},
|
||||
resetShareModal: (state) => {
|
||||
state.shareModal = {
|
||||
file: null,
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -100,6 +120,8 @@ export const {
|
||||
resetMultiSelect,
|
||||
setPopupSelect,
|
||||
resetPopupSelect,
|
||||
setShareModal,
|
||||
resetShareModal,
|
||||
} = selectedSlice.actions;
|
||||
|
||||
export default selectedSlice.reducer;
|
||||
|
||||
Reference in New Issue
Block a user