created share modal

This commit is contained in:
subnub
2024-07-19 02:36:38 -04:00
parent c0a9e17330
commit 1502edde70
11 changed files with 372 additions and 14 deletions
+6 -6
View File
@@ -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);
+6 -3
View File
@@ -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;
+1 -1
View 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",
+4 -2
View File
@@ -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) => {
+15
View File
@@ -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`, {
+6 -2
View File
@@ -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 = () => {
+6
View File
@@ -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}>
+7
View File
@@ -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 />
+260
View File
@@ -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;
+39
View File
@@ -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;
};
+22
View File
@@ -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;