Skip to content
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
10 changes: 10 additions & 0 deletions src/App.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import AuthGuard from '@/pages/Guards/AuthGuard';
import SignInPage from '@/pages/SignInPage/SignInPage';
import Navbar from './components/Navbar/Navbar.component';
import RoleGuard from './pages/Guards/RoleGuard';
import ResetPage from './pages/CallbackPage/CallbackPage';

function App() {
return (
Expand All@@ -17,6 +18,15 @@ function App() {
/>
}
/>
<Route
path={UNAUTH_ROUTES.CALLBACK}
element={
<AuthGuard
authComponent={<Navigate to={AUTH_ROUTES.HOME} />}
unAuthComponent={<ResetPage />}
/>
}
/>
<Route
path={AUTH_ROUTES.HOME}
element={
Expand Down
59 changes: 59 additions & 0 deletions src/components/Breadcrumbs/Breadcrumbs.component.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
import { AUTH_ROUTES } from '@/constants/routes';
import { Fragment } from 'react';
import { Link } from 'react-router-dom';

const Breadcrumbs = ({
path,
pathArr,
shared = false,
trashed = false,
}: {
path: string;
pathArr: string[];
shared?: boolean;
trashed?: boolean;
}) => {
return (
<div className='flex gap-2'>
{shared && (
<>
<span>/</span>
<Link to={AUTH_ROUTES.DRIVE_SHARED}>Shared</Link>
</>
)}
{path === '/' ? (
<>
<span>/</span>
<span>{trashed ? 'Trashed' : 'Home'}</span>
</>
) : (
pathArr.map((item, index) => {
const link = item ? pathArr.slice(0, index + 1).join('/') : '/';
console.log(item);
return (
<Fragment key={link}>
{!shared || link !== '/' ? <span>/</span> : null}
{index === pathArr.length - 1 ? (
<span>{item}</span>
) : (
<Link
to={`${
shared
? AUTH_ROUTES.DRIVE_SHARED
: trashed
? AUTH_ROUTES.DRIVE_TRASH
: AUTH_ROUTES.DRIVE
}?path=${encodeURIComponent(link)}`}
>
{item || (trashed ? 'Trashed' : 'Home')}
</Link>
)}
</Fragment>
);
})
)}
</div>
);
};

export default Breadcrumbs;
62 changes: 62 additions & 0 deletions src/components/Drive/CreateFileModal.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
import { FormEvent } from 'react';
import InputWithLabel from '../InputWithLabel/InputWithLabel.component';
import FileInput from '../FileInput/FileInput.component';
import { Button } from 'primereact/button';
import { fileSizeFormatter } from '@/utils/formatter';
import { useRef } from 'react';

const CreateFileModal = ({
onCreateFile,
handleClose,
setFile,
file,
}: {
onCreateFile: (e: FormEvent<HTMLFormElement>) => void;
handleClose: () => void;
setFile: (value: File) => void;
file: File | null;
}) => {
const ref = useRef<HTMLInputElement>(null);
return (
<form
className='bg-neutral-800 rounded-lg p-5 w-[50vw]'
onSubmit={onCreateFile}
onClick={(e) => e.stopPropagation()}
>
<h2 className='title'>Creating folder</h2>
<InputWithLabel
wrapperClassName='mt-5'
label='File name'
id='fileName'
name='fileName'
ref={ref}
/>
<FileInput
name='file'
id='file'
setFiles={(f) => {
setFile(f);
if (!ref.current) return;
ref.current.value = f.name;
}}
/>
{file && (
<div className='mt-5'>
Selected: {file.name} ({fileSizeFormatter(file.size)})
</div>
)}
<div className='flex justify-end mt-5'>
<Button
label='Cancel'
className='h-11 rounded-lg mr-3 btn-outlined !border-red-600'
onClick={handleClose}
severity='danger'
outlined
/>
<Button label='Create' className='h-11 rounded-lg' />
</div>
</form>
);
};

export default CreateFileModal;
39 changes: 39 additions & 0 deletions src/components/Drive/CreateFolderModal.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
import { Button } from 'primereact/button';
import InputWithLabel from '../InputWithLabel/InputWithLabel.component';
import { FormEvent } from 'react';

const CreateFolderModal = ({
onCreateFolder,
handleClose,
}: {
onCreateFolder: (e: FormEvent<HTMLFormElement>) => void;
handleClose: () => void;
}) => {
return (
<form
className='bg-neutral-800 rounded-lg p-5 w-[50vw]'
onSubmit={onCreateFolder}
onClick={(e) => e.stopPropagation()}
>
<h2 className='title'>Creating folder</h2>
<InputWithLabel
wrapperClassName='mt-5'
label='Folder name'
id='folderName'
name='folderName'
/>
<div className='flex justify-end'>
<Button
label='Cancel'
className='mt-5 h-11 rounded-lg mr-3 btn-outlined !border-red-600'
onClick={handleClose}
severity='danger'
outlined
/>
<Button label='Create' className='mt-5 h-11 rounded-lg' />
</div>
</form>
);
};

export default CreateFolderModal;
69 changes: 69 additions & 0 deletions src/components/Drive/DetailInfo.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
import { IDrive } from '@/types/item';
import clsx from 'clsx';
import { PrimeIcons } from 'primereact/api';
import { TextOverflow } from '.';
import { fileSizeFormatter } from '@/utils/formatter';

const DetailInfo = ({
showInfo,
setShowInfo,
}: {
showInfo: IDrive | null;
setShowInfo: (value: IDrive | null) => void;
}) => {
return (
<div
className={clsx(
'card transition-all h-max min-h-[500px] ml-5 sticky top-5',
showInfo ? 'w-[400px] opacity-100' : 'w-0 overflow-hidden p-0 opacity-0'
)}
>
<div
className='text-right hover:text-red-500 cursor-pointer'
onClick={() => setShowInfo(null)}
>
<i className={PrimeIcons.TIMES} />
</div>
{showInfo && (
<>
<h3 className='text-xl font-semibold whitespace-nowrap'>{showInfo.name}</h3>
<div className='flex items-center justify-center mt-3 bg-neutral-700 p-3 rounded-lg'>
<i
className={clsx(
'text-[80px]',
showInfo.isDirectory ? PrimeIcons.FOLDER : PrimeIcons.FILE
)}
/>
</div>
<h3 className='text-xl font-semibold whitespace-nowrap mt-5'>
{showInfo.isDirectory ? 'Folder' : 'File'} information
</h3>
<div className='grid grid-cols-2 mt-3 gap-y-2 gap-x-10'>
<div>Sizes</div>
<div className='text-right col-span-1'>
{fileSizeFormatter(showInfo.sizeInBytes || 0)}
</div>
<div>Location</div>
<div className='text-right col-span-1'>{showInfo.path}</div>
<div>Owner</div>
<TextOverflow>{showInfo.owner.email}</TextOverflow>
<div>Created</div>
<div className='text-right col-span-1'>
{new Date(showInfo.created).toLocaleDateString()}
</div>
<div>Created by</div>
<TextOverflow>{showInfo.owner.email}</TextOverflow>
<div>Last modified</div>
<div className='text-right col-span-1'>
{showInfo.lastModified
? new Date(showInfo.lastModified).toLocaleDateString()
: 'Never'}
</div>
</div>
</>
)}
</div>
);
};

export default DetailInfo;
48 changes: 48 additions & 0 deletions src/components/Drive/File.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
import { IDrive } from '@/types/item';
import clsx from 'clsx';
import { PrimeIcons } from 'primereact/api';
import { MouseEvent } from 'react';

const File = ({
// shared = false,
file,
onContextMenu,
showInfo,
}: // trashed = false,
{
trashed?: boolean;
shared?: boolean;
file: IDrive;
showInfo?: (file: IDrive | null) => void;
onContextMenu: (value: string, e: MouseEvent, type: 'file') => void;
}) => {
return (
<>
<div
onContextMenu={(e) => {
onContextMenu(file.id, e, 'file');
}}
onClick={() => {
showInfo && showInfo(file);
}}
className='group w-full cursor-pointer max-w-[160px]'
>
<div className='flex flex-col items-center p-3 rounded-lg bg-neutral-900'>
<div className='p-3 bg-neutral-800 rounded-lg transition-colors flex items-center justify-center w-full aspect-square'>
<i
className={clsx(
PrimeIcons.FILE,
'text-[100px] group-hover:text-primary transition-colors'
)}
/>
</div>
<h3 className='text-center text-lg font-medium mt-2 max-w-full break-words'>
{file.name}
</h3>
</div>
</div>
</>
);
};

export default File;
85 changes: 85 additions & 0 deletions src/components/Drive/Folder.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
import { AUTH_ROUTES } from '@/constants/routes';
import { IDrive } from '@/types/item';
import clsx from 'clsx';
import { PrimeIcons } from 'primereact/api';
import { useState, MouseEvent, useRef } from 'react';
import { useNavigate } from 'react-router';

const Folder = ({
currentPath,
folder,
shared = false,
trashed = false,
showInfo,
onContextMenu,
}: {
currentPath: string;
folder: IDrive;
shared?: boolean;
trashed?: boolean;
showInfo?: (folder: IDrive | null) => void;
onContextMenu: (value: string, e: MouseEvent, type: 'folder') => void;
}) => {
const [hover, setHover] = useState(false);
const navigate = useNavigate();

const link = `${
shared ? AUTH_ROUTES.DRIVE_SHARED : trashed ? AUTH_ROUTES.DRIVE_TRASH : AUTH_ROUTES.DRIVE
}?path=${encodeURIComponent(currentPath)}${encodeURIComponent(
`${shared ? folder.id : `/${folder.name}`}`
)}`;

const doubleClick = useRef(false);

const handleDoubleClick = () => {
if (doubleClick.current) {
showInfo && showInfo(null);
navigate(link);
} else {
showInfo && showInfo(folder);
doubleClick.current = true;
setTimeout(() => {
doubleClick.current = false;
}, 300);
}
};

return (
<>
<div
onClick={handleDoubleClick}
className={clsx('group w-full cursor-pointer max-w-[160px]')}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
onContextMenu={(e) => {
onContextMenu(folder.id, e, 'folder');
}}
>
<div className='flex flex-col items-center p-3 rounded-lg bg-neutral-900'>
<div className='p-3 bg-neutral-800 rounded-lg transition-colors aspect-square w-full flex items-center justify-center'>
{hover ? (
<i
className={clsx(
PrimeIcons.FOLDER_OPEN,
'text-[100px] group-hover:text-primary transition-colors'
)}
/>
) : (
<i
className={clsx(
PrimeIcons.FOLDER,
'text-[100px] group-hover:text-primary transition-colors'
)}
/>
)}
</div>
<h3 className='text-center text-lg font-medium mt-2 max-w-full break-words'>
{folder.name}
</h3>
</div>
</div>
</>
);
};

export default Folder;
Loading