mirror of
https://github.com/kyantech/Palmr.git
synced 2025-10-23 06:11:58 +00:00
chore: update configuration files and translations
- Modified ESLint and Next.js configuration files for improved code quality and performance. - Updated package.json and pnpm-lock.yaml to reflect dependency changes and ensure compatibility. - Added new translations for multiple languages, enhancing localization support across the application. - Cleaned up unused or deprecated model files in the HTTP endpoints, streamlining the codebase.
This commit is contained in:
@@ -30,6 +30,7 @@ export default [
|
||||
"react/no-unused-prop-types": "off",
|
||||
"react/require-default-props": "off",
|
||||
"react/no-unescaped-entities": "off",
|
||||
"@next/next/no-img-element": "off",
|
||||
"import/extensions": [
|
||||
"error",
|
||||
"ignorePackages",
|
||||
@@ -63,4 +64,8 @@ export default [
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
},
|
||||
},
|
||||
// Ignore ESLint errors in @/ui directory
|
||||
{
|
||||
ignores: ["src/components/ui/**/*"],
|
||||
},
|
||||
];
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3,12 +3,6 @@ import createNextIntlPlugin from "next-intl/plugin";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
output: "standalone",
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
|
@@ -44,51 +44,51 @@
|
||||
"@radix-ui/react-slot": "^1.1.2",
|
||||
"@radix-ui/react-switch": "^1.1.4",
|
||||
"@radix-ui/react-tabs": "^1.1.12",
|
||||
"@tabler/icons-react": "^3.31.0",
|
||||
"@tabler/icons-react": "^3.34.0",
|
||||
"@types/react-dropzone": "^5.1.0",
|
||||
"axios": "^1.8.4",
|
||||
"axios": "^1.10.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"framer-motion": "^12.6.3",
|
||||
"framer-motion": "^12.20.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jszip": "^3.10.1",
|
||||
"lucide-react": "^0.487.0",
|
||||
"lucide-react": "^0.525.0",
|
||||
"nanoid": "^5.1.5",
|
||||
"next": "15.2.4",
|
||||
"next-intl": "^4.0.2",
|
||||
"next": "15.3.4",
|
||||
"next-intl": "^4.3.1",
|
||||
"next-themes": "^0.4.6",
|
||||
"nookies": "^2.5.2",
|
||||
"react": "^19.1.0",
|
||||
"react-country-flag": "^3.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-hook-form": "^7.55.0",
|
||||
"react-hook-form": "^7.59.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"sonner": "^2.0.3",
|
||||
"tailwind-merge": "^3.1.0",
|
||||
"tw-animate-css": "^1.2.5",
|
||||
"zod": "^3.24.2",
|
||||
"zustand": "^5.0.3"
|
||||
"sonner": "^2.0.5",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tw-animate-css": "^1.3.4",
|
||||
"zod": "^3.25.67",
|
||||
"zustand": "^5.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "3.3.1",
|
||||
"@eslint/js": "9.23.0",
|
||||
"@ianvs/prettier-plugin-sort-imports": "4.4.1",
|
||||
"@tailwindcss/postcss": "4.1.2",
|
||||
"@eslint/js": "9.30.0",
|
||||
"@ianvs/prettier-plugin-sort-imports": "4.4.2",
|
||||
"@tailwindcss/postcss": "4.1.11",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/node": "22.14.0",
|
||||
"@types/react": "19.1.0",
|
||||
"@types/react-dom": "19.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.29.0",
|
||||
"@typescript-eslint/parser": "8.29.0",
|
||||
"eslint": "9.23.0",
|
||||
"eslint-config-next": "15.2.4",
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react-dom": "19.1.6",
|
||||
"@typescript-eslint/eslint-plugin": "8.35.1",
|
||||
"@typescript-eslint/parser": "8.35.1",
|
||||
"eslint": "9.30.0",
|
||||
"eslint-config-next": "15.3.4",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-plugin-prettier": "5.2.6",
|
||||
"prettier": "3.5.3",
|
||||
"eslint-plugin-prettier": "5.5.1",
|
||||
"prettier": "3.6.2",
|
||||
"prettier-plugin-sort-json": "4.1.1",
|
||||
"tailwindcss": "4.1.2",
|
||||
"typescript": "5.8.2"
|
||||
"tailwindcss": "4.1.11",
|
||||
"typescript": "5.8.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
5818
apps/web/pnpm-lock.yaml
generated
5818
apps/web/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
import { motion } from "framer-motion";
|
||||
import { Palmtree } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { HomeHeaderProps } from "../types";
|
||||
import { Palmtree } from "lucide-react";
|
||||
|
||||
const fadeInUpAnimation = {
|
||||
animate: { opacity: 1, y: 0 },
|
||||
@@ -28,8 +28,11 @@ export function HomeHeader({ title }: HomeHeaderProps) {
|
||||
return (
|
||||
<motion.div {...fadeInUpAnimation} className="inline-block max-w-xl text-center justify-center">
|
||||
<div className="flex flex-col gap-8">
|
||||
<motion.span {...titleAnimation} className="text-4xl lg:text-6xl font-extrabold tracking-tight flex mx-auto items-end gap-3">
|
||||
<Palmtree className="h-18 w-18" /> {title}
|
||||
<motion.span
|
||||
{...titleAnimation}
|
||||
className="text-4xl lg:text-6xl font-extrabold tracking-tight flex mx-auto items-end gap-3"
|
||||
>
|
||||
<Palmtree className="h-18 w-18" /> {title}
|
||||
</motion.span>
|
||||
<div className="flex flex-col gap-2">
|
||||
<motion.span
|
||||
|
@@ -26,33 +26,39 @@ export function FileUploadSection({ reverseShare, password, alias, onUploadSucce
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
const validateFileSize = (file: File): string | null => {
|
||||
if (!reverseShare.maxFileSize) return null;
|
||||
const validateFileSize = useCallback(
|
||||
(file: File): string | null => {
|
||||
if (!reverseShare.maxFileSize) return null;
|
||||
|
||||
if (file.size > reverseShare.maxFileSize) {
|
||||
return t("reverseShares.upload.errors.fileTooLarge", {
|
||||
maxSize: formatFileSize(reverseShare.maxFileSize),
|
||||
});
|
||||
}
|
||||
return null;
|
||||
};
|
||||
if (file.size > reverseShare.maxFileSize) {
|
||||
return t("reverseShares.upload.errors.fileTooLarge", {
|
||||
maxSize: formatFileSize(reverseShare.maxFileSize),
|
||||
});
|
||||
}
|
||||
return null;
|
||||
},
|
||||
[reverseShare.maxFileSize, t]
|
||||
);
|
||||
|
||||
const validateFileType = (file: File): string | null => {
|
||||
if (!reverseShare.allowedFileTypes) return null;
|
||||
const validateFileType = useCallback(
|
||||
(file: File): string | null => {
|
||||
if (!reverseShare.allowedFileTypes) return null;
|
||||
|
||||
const allowedTypes = reverseShare.allowedFileTypes.split(",").map((type) => type.trim().toLowerCase());
|
||||
const allowedTypes = reverseShare.allowedFileTypes.split(",").map((type) => type.trim().toLowerCase());
|
||||
|
||||
const fileExtension = file.name.split(".").pop()?.toLowerCase();
|
||||
const fileExtension = file.name.split(".").pop()?.toLowerCase();
|
||||
|
||||
if (fileExtension && !allowedTypes.includes(fileExtension)) {
|
||||
return t("reverseShares.upload.errors.fileTypeNotAllowed", {
|
||||
allowedTypes: reverseShare.allowedFileTypes,
|
||||
});
|
||||
}
|
||||
return null;
|
||||
};
|
||||
if (fileExtension && !allowedTypes.includes(fileExtension)) {
|
||||
return t("reverseShares.upload.errors.fileTypeNotAllowed", {
|
||||
allowedTypes: reverseShare.allowedFileTypes,
|
||||
});
|
||||
}
|
||||
return null;
|
||||
},
|
||||
[reverseShare.allowedFileTypes, t]
|
||||
);
|
||||
|
||||
const validateFileCount = (): string | null => {
|
||||
const validateFileCount = useCallback((): string | null => {
|
||||
if (!reverseShare.maxFiles) return null;
|
||||
|
||||
const totalFiles = files.length + 1 + reverseShare.currentFileCount;
|
||||
@@ -62,11 +68,14 @@ export function FileUploadSection({ reverseShare, password, alias, onUploadSucce
|
||||
});
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}, [reverseShare.maxFiles, reverseShare.currentFileCount, files.length, t]);
|
||||
|
||||
const validateFile = (file: File): string | null => {
|
||||
return validateFileSize(file) || validateFileType(file) || validateFileCount();
|
||||
};
|
||||
const validateFile = useCallback(
|
||||
(file: File): string | null => {
|
||||
return validateFileSize(file) || validateFileType(file) || validateFileCount();
|
||||
},
|
||||
[validateFileSize, validateFileType, validateFileCount]
|
||||
);
|
||||
|
||||
const createFileWithProgress = (file: File): FileWithProgress => ({
|
||||
file,
|
||||
@@ -74,27 +83,30 @@ export function FileUploadSection({ reverseShare, password, alias, onUploadSucce
|
||||
status: FILE_STATUS.PENDING,
|
||||
});
|
||||
|
||||
const processAcceptedFiles = (acceptedFiles: File[]): FileWithProgress[] => {
|
||||
const validFiles: FileWithProgress[] = [];
|
||||
const processAcceptedFiles = useCallback(
|
||||
(acceptedFiles: File[]): FileWithProgress[] => {
|
||||
const validFiles: FileWithProgress[] = [];
|
||||
|
||||
for (const file of acceptedFiles) {
|
||||
const validationError = validateFile(file);
|
||||
if (validationError) {
|
||||
toast.error(validationError);
|
||||
continue;
|
||||
for (const file of acceptedFiles) {
|
||||
const validationError = validateFile(file);
|
||||
if (validationError) {
|
||||
toast.error(validationError);
|
||||
continue;
|
||||
}
|
||||
validFiles.push(createFileWithProgress(file));
|
||||
}
|
||||
validFiles.push(createFileWithProgress(file));
|
||||
}
|
||||
|
||||
return validFiles;
|
||||
};
|
||||
return validFiles;
|
||||
},
|
||||
[validateFile]
|
||||
);
|
||||
|
||||
const onDrop = useCallback(
|
||||
(acceptedFiles: File[]) => {
|
||||
const newFiles = processAcceptedFiles(acceptedFiles);
|
||||
setFiles((previousFiles) => [...previousFiles, ...newFiles]);
|
||||
},
|
||||
[files, reverseShare]
|
||||
[processAcceptedFiles]
|
||||
);
|
||||
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||
@@ -240,8 +252,7 @@ export function FileUploadSection({ reverseShare, password, alias, onUploadSucce
|
||||
|
||||
try {
|
||||
await processAllUploads();
|
||||
} catch (error) {
|
||||
console.error("Upload error:", error);
|
||||
} catch {
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
@@ -31,59 +31,65 @@ export function useReverseShareUpload({ alias }: UseReverseShareUploadProps) {
|
||||
return reverseShareData.currentFileCount >= reverseShareData.maxFiles;
|
||||
};
|
||||
|
||||
const handleErrorResponse = (responseError: any) => {
|
||||
const status = responseError.response?.status;
|
||||
const errorMessage = responseError.response?.data?.error;
|
||||
const handleErrorResponse = useCallback(
|
||||
(responseError: any) => {
|
||||
const status = responseError.response?.status;
|
||||
const errorMessage = responseError.response?.data?.error;
|
||||
|
||||
switch (status) {
|
||||
case HTTP_STATUS.UNAUTHORIZED:
|
||||
if (errorMessage === ERROR_MESSAGES.PASSWORD_REQUIRED) {
|
||||
setIsPasswordModalOpen(true);
|
||||
} else if (errorMessage === ERROR_MESSAGES.INVALID_PASSWORD) {
|
||||
setIsPasswordModalOpen(true);
|
||||
toast.error(t("reverseShares.upload.errors.passwordIncorrect"));
|
||||
}
|
||||
break;
|
||||
switch (status) {
|
||||
case HTTP_STATUS.UNAUTHORIZED:
|
||||
if (errorMessage === ERROR_MESSAGES.PASSWORD_REQUIRED) {
|
||||
setIsPasswordModalOpen(true);
|
||||
} else if (errorMessage === ERROR_MESSAGES.INVALID_PASSWORD) {
|
||||
setIsPasswordModalOpen(true);
|
||||
toast.error(t("reverseShares.upload.errors.passwordIncorrect"));
|
||||
}
|
||||
break;
|
||||
|
||||
case HTTP_STATUS.NOT_FOUND:
|
||||
setError({ type: "notFound" });
|
||||
break;
|
||||
case HTTP_STATUS.NOT_FOUND:
|
||||
setError({ type: "notFound" });
|
||||
break;
|
||||
|
||||
case HTTP_STATUS.FORBIDDEN:
|
||||
setError({ type: "inactive" });
|
||||
break;
|
||||
case HTTP_STATUS.FORBIDDEN:
|
||||
setError({ type: "inactive" });
|
||||
break;
|
||||
|
||||
case HTTP_STATUS.GONE:
|
||||
setError({ type: "expired" });
|
||||
break;
|
||||
case HTTP_STATUS.GONE:
|
||||
setError({ type: "expired" });
|
||||
break;
|
||||
|
||||
default:
|
||||
setError({ type: "generic" });
|
||||
toast.error(t("reverseShares.upload.errors.loadFailed"));
|
||||
break;
|
||||
}
|
||||
};
|
||||
default:
|
||||
setError({ type: "generic" });
|
||||
toast.error(t("reverseShares.upload.errors.loadFailed"));
|
||||
break;
|
||||
}
|
||||
},
|
||||
[t]
|
||||
);
|
||||
|
||||
const loadReverseShare = async (passwordAttempt?: string) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setError({ type: null });
|
||||
const loadReverseShare = useCallback(
|
||||
async (passwordAttempt?: string) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setError({ type: null });
|
||||
|
||||
const response = await getReverseShareForUploadByAlias(
|
||||
alias,
|
||||
passwordAttempt ? { password: passwordAttempt } : undefined
|
||||
);
|
||||
const response = await getReverseShareForUploadByAlias(
|
||||
alias,
|
||||
passwordAttempt ? { password: passwordAttempt } : undefined
|
||||
);
|
||||
|
||||
setReverseShare(response.data.reverseShare);
|
||||
setIsPasswordModalOpen(false);
|
||||
setCurrentPassword(passwordAttempt || "");
|
||||
} catch (responseError: any) {
|
||||
console.error("Failed to load reverse share:", responseError);
|
||||
handleErrorResponse(responseError);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
setReverseShare(response.data.reverseShare);
|
||||
setIsPasswordModalOpen(false);
|
||||
setCurrentPassword(passwordAttempt || "");
|
||||
} catch (responseError: any) {
|
||||
console.error("Failed to load reverse share:", responseError);
|
||||
handleErrorResponse(responseError);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[alias, handleErrorResponse]
|
||||
);
|
||||
|
||||
const handlePasswordSubmit = (passwordValue: string) => {
|
||||
loadReverseShare(passwordValue);
|
||||
@@ -105,7 +111,7 @@ export function useReverseShareUpload({ alias }: UseReverseShareUploadProps) {
|
||||
if (alias) {
|
||||
loadReverseShare();
|
||||
}
|
||||
}, [alias]);
|
||||
}, [alias, loadReverseShare]);
|
||||
|
||||
const isMaxFilesReached = reverseShare ? checkIfMaxFilesReached(reverseShare) : false;
|
||||
const isWeTransferLayout = reverseShare?.pageLayout === "WETRANSFER";
|
||||
|
@@ -8,7 +8,6 @@ import {
|
||||
IconFile,
|
||||
IconFiles,
|
||||
IconLock,
|
||||
IconMail,
|
||||
IconSettings,
|
||||
IconUpload,
|
||||
IconUser,
|
||||
@@ -162,7 +161,7 @@ export function CreateReverseShareModal({
|
||||
const payload = buildPayload(formData);
|
||||
await onCreateReverseShare(payload);
|
||||
form.reset();
|
||||
} catch (error) {
|
||||
} catch {
|
||||
// Error handling is managed by the hook
|
||||
}
|
||||
};
|
||||
|
@@ -73,100 +73,6 @@ interface EditReverseShareModalProps {
|
||||
isUpdating: boolean;
|
||||
}
|
||||
|
||||
export function EditReverseShareModal({
|
||||
reverseShare,
|
||||
isOpen,
|
||||
onClose,
|
||||
onUpdateReverseShare,
|
||||
isUpdating,
|
||||
}: EditReverseShareModalProps) {
|
||||
const t = useTranslations();
|
||||
|
||||
const form = useForm<EditReverseShareFormData>({
|
||||
defaultValues: getFormDefaultValues(),
|
||||
});
|
||||
|
||||
const watchedValues = {
|
||||
hasExpiration: form.watch("hasExpiration"),
|
||||
hasFileLimits: form.watch("hasFileLimits"),
|
||||
hasFieldRequirements: form.watch("hasFieldRequirements"),
|
||||
noFilesLimit: form.watch("noFilesLimit"),
|
||||
noSizeLimit: form.watch("noSizeLimit"),
|
||||
allFileTypes: form.watch("allFileTypes"),
|
||||
hasPassword: form.watch("hasPassword"),
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (reverseShare) {
|
||||
form.reset(mapReverseShareToFormData(reverseShare));
|
||||
}
|
||||
}, [reverseShare, form]);
|
||||
|
||||
const handleSubmit = async (data: EditReverseShareFormData) => {
|
||||
if (!reverseShare) return;
|
||||
|
||||
try {
|
||||
const payload = buildUpdatePayload(data, reverseShare.id);
|
||||
await onUpdateReverseShare(payload);
|
||||
} catch (error) {
|
||||
// Error is handled by the hook
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="sm:max-w-[500px] md:max-w-[650px] max-h-[85vh] overflow-hidden">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<IconEdit size={20} />
|
||||
{t("reverseShares.modals.edit.title")}
|
||||
</DialogTitle>
|
||||
<DialogDescription>{t("reverseShares.modals.edit.description")}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="overflow-y-auto max-h-[calc(85vh-140px)] py-2">
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-6">
|
||||
<BasicInfoSection form={form} t={t} />
|
||||
<Separator />
|
||||
<ExpirationSection form={form} t={t} hasExpiration={watchedValues.hasExpiration} />
|
||||
<Separator />
|
||||
<FileLimitsSection
|
||||
form={form}
|
||||
t={t}
|
||||
hasFileLimits={watchedValues.hasFileLimits}
|
||||
noFilesLimit={watchedValues.noFilesLimit}
|
||||
noSizeLimit={watchedValues.noSizeLimit}
|
||||
allFileTypes={watchedValues.allFileTypes}
|
||||
/>
|
||||
<Separator />
|
||||
<PasswordSection form={form} t={t} hasPassword={watchedValues.hasPassword} />
|
||||
<Separator />
|
||||
<FieldRequirementsSection form={form} t={t} hasFieldRequirements={watchedValues.hasFieldRequirements} />
|
||||
|
||||
<DialogFooter className="gap-2">
|
||||
<Button type="button" variant="outline" onClick={onClose} disabled={isUpdating}>
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button type="submit" disabled={isUpdating}>
|
||||
{isUpdating ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="animate-spin">⠋</div>
|
||||
{t("reverseShares.modals.edit.updating")}
|
||||
</div>
|
||||
) : (
|
||||
t("reverseShares.modals.edit.saveChanges")
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function getFormDefaultValues(): EditReverseShareFormData {
|
||||
return {
|
||||
name: DEFAULT_VALUES.EMPTY_STRING,
|
||||
@@ -190,6 +96,12 @@ function getFormDefaultValues(): EditReverseShareFormData {
|
||||
};
|
||||
}
|
||||
|
||||
function parsePositiveIntegerOrNull(value?: string): number | null {
|
||||
if (!value || value === DEFAULT_VALUES.ZERO_STRING) return null;
|
||||
const parsed = parseInt(value);
|
||||
return parsed > 0 ? parsed : null;
|
||||
}
|
||||
|
||||
function mapReverseShareToFormData(reverseShare: ReverseShare): EditReverseShareFormData {
|
||||
const maxFilesValue = reverseShare.maxFiles?.toString() || DEFAULT_VALUES.ZERO_STRING;
|
||||
const maxFileSizeValue = reverseShare.maxFileSize?.toString() || DEFAULT_VALUES.ZERO_STRING;
|
||||
@@ -259,12 +171,6 @@ function buildUpdatePayload(data: EditReverseShareFormData, id: string): UpdateR
|
||||
return payload;
|
||||
}
|
||||
|
||||
function parsePositiveIntegerOrNull(value?: string): number | null {
|
||||
if (!value || value === DEFAULT_VALUES.ZERO_STRING) return null;
|
||||
const parsed = parseInt(value);
|
||||
return parsed > 0 ? parsed : null;
|
||||
}
|
||||
|
||||
function createToggleButton(isExpanded: boolean, onToggle: () => void, icon: React.ReactNode, label: string) {
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
@@ -721,3 +627,97 @@ function FieldRequirementsSection({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function EditReverseShareModal({
|
||||
reverseShare,
|
||||
isOpen,
|
||||
onClose,
|
||||
onUpdateReverseShare,
|
||||
isUpdating,
|
||||
}: EditReverseShareModalProps) {
|
||||
const t = useTranslations();
|
||||
|
||||
const form = useForm<EditReverseShareFormData>({
|
||||
defaultValues: getFormDefaultValues(),
|
||||
});
|
||||
|
||||
const watchedValues = {
|
||||
hasExpiration: form.watch("hasExpiration"),
|
||||
hasFileLimits: form.watch("hasFileLimits"),
|
||||
hasFieldRequirements: form.watch("hasFieldRequirements"),
|
||||
noFilesLimit: form.watch("noFilesLimit"),
|
||||
noSizeLimit: form.watch("noSizeLimit"),
|
||||
allFileTypes: form.watch("allFileTypes"),
|
||||
hasPassword: form.watch("hasPassword"),
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (reverseShare) {
|
||||
form.reset(mapReverseShareToFormData(reverseShare));
|
||||
}
|
||||
}, [reverseShare, form]);
|
||||
|
||||
const handleSubmit = async (data: EditReverseShareFormData) => {
|
||||
if (!reverseShare) return;
|
||||
|
||||
try {
|
||||
const payload = buildUpdatePayload(data, reverseShare.id);
|
||||
await onUpdateReverseShare(payload);
|
||||
} catch {
|
||||
// Error is handled by the hook
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="sm:max-w-[500px] md:max-w-[650px] max-h-[85vh] overflow-hidden">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<IconEdit size={20} />
|
||||
{t("reverseShares.modals.edit.title")}
|
||||
</DialogTitle>
|
||||
<DialogDescription>{t("reverseShares.modals.edit.description")}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="overflow-y-auto max-h-[calc(85vh-140px)] py-2">
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-6">
|
||||
<BasicInfoSection form={form} t={t} />
|
||||
<Separator />
|
||||
<ExpirationSection form={form} t={t} hasExpiration={watchedValues.hasExpiration} />
|
||||
<Separator />
|
||||
<FileLimitsSection
|
||||
form={form}
|
||||
t={t}
|
||||
hasFileLimits={watchedValues.hasFileLimits}
|
||||
noFilesLimit={watchedValues.noFilesLimit}
|
||||
noSizeLimit={watchedValues.noSizeLimit}
|
||||
allFileTypes={watchedValues.allFileTypes}
|
||||
/>
|
||||
<Separator />
|
||||
<PasswordSection form={form} t={t} hasPassword={watchedValues.hasPassword} />
|
||||
<Separator />
|
||||
<FieldRequirementsSection form={form} t={t} hasFieldRequirements={watchedValues.hasFieldRequirements} />
|
||||
|
||||
<DialogFooter className="gap-2">
|
||||
<Button type="button" variant="outline" onClick={onClose} disabled={isUpdating}>
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button type="submit" disabled={isUpdating}>
|
||||
{isUpdating ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="animate-spin">⠋</div>
|
||||
{t("reverseShares.modals.edit.updating")}
|
||||
</div>
|
||||
) : (
|
||||
t("reverseShares.modals.edit.saveChanges")
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
@@ -23,18 +23,6 @@ export function FileTypesTagsInput({
|
||||
}: FileTypesTagsInputProps) {
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter" || e.key === " " || e.key === "," || e.key === "|" || e.key === "-") {
|
||||
e.preventDefault();
|
||||
addTag();
|
||||
} else if (e.key === "Backspace" && inputValue === "" && value.length > 0) {
|
||||
e.preventDefault();
|
||||
removeTag(value.length - 1);
|
||||
} else if (e.key === ".") {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
const addTag = () => {
|
||||
const newTag = inputValue.trim().toLowerCase();
|
||||
if (newTag && !value.includes(newTag)) {
|
||||
@@ -48,6 +36,18 @@ export function FileTypesTagsInput({
|
||||
onChange(newTags);
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter" || e.key === " " || e.key === "," || e.key === "|" || e.key === "-") {
|
||||
e.preventDefault();
|
||||
addTag();
|
||||
} else if (e.key === "Backspace" && inputValue === "" && value.length > 0) {
|
||||
e.preventDefault();
|
||||
removeTag(value.length - 1);
|
||||
} else if (e.key === ".") {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const sanitizedValue = e.target.value.replace(/\./g, "").toLowerCase();
|
||||
setInputValue(sanitizedValue);
|
||||
|
@@ -71,7 +71,7 @@ export function GenerateAliasModal({
|
||||
try {
|
||||
await onCreateAlias(reverseShare.id, data.alias);
|
||||
onClose();
|
||||
} catch (error) {
|
||||
} catch {
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
|
@@ -61,6 +61,10 @@ interface HoverState {
|
||||
field: string;
|
||||
}
|
||||
|
||||
const getFileNameWithoutExtension = (fileName: string) => {
|
||||
return fileName.replace(/\.[^/.]+$/, "");
|
||||
};
|
||||
|
||||
function useFileEdit() {
|
||||
const [editingFile, setEditingFile] = useState<EditingState | null>(null);
|
||||
const [editValue, setEditValue] = useState("");
|
||||
@@ -110,7 +114,7 @@ const formatFileSize = (sizeString: string) => {
|
||||
const formatDate = (dateString: string) => {
|
||||
try {
|
||||
return format(new Date(dateString), "dd/MM/yyyy HH:mm", { locale: ptBR });
|
||||
} catch (error) {
|
||||
} catch {
|
||||
return "Data inválida";
|
||||
}
|
||||
};
|
||||
@@ -120,10 +124,6 @@ const getFileExtension = (fileName: string) => {
|
||||
return match ? match[0] : "";
|
||||
};
|
||||
|
||||
const getFileNameWithoutExtension = (fileName: string) => {
|
||||
return fileName.replace(/\.[^/.]+$/, "");
|
||||
};
|
||||
|
||||
const getSenderDisplay = (file: ReverseShareFile, t: any) => {
|
||||
if (file.uploaderName && file.uploaderEmail) {
|
||||
return `${file.uploaderName} (${file.uploaderEmail})`;
|
||||
|
@@ -7,7 +7,6 @@ import {
|
||||
IconExternalLink,
|
||||
IconEye,
|
||||
IconFile,
|
||||
IconFileText,
|
||||
IconFileUnknown,
|
||||
IconLink,
|
||||
IconLock,
|
||||
@@ -20,7 +19,7 @@ import {
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -76,11 +75,9 @@ export function ReverseShareCard({
|
||||
}, [editingField]);
|
||||
|
||||
const fileCount = reverseShare.files?.length || 0;
|
||||
const maxFiles = reverseShare.maxFiles || 0;
|
||||
const hasAlias = Boolean(reverseShare.alias?.alias);
|
||||
const isExpired = reverseShare.expiration ? new Date(reverseShare.expiration) < new Date() : false;
|
||||
const hasPassword = reverseShare.hasPassword;
|
||||
const hasLimits = Boolean(reverseShare.maxFiles || reverseShare.maxFileSize);
|
||||
|
||||
const formatFileSize = (sizeInBytes: number) => {
|
||||
if (sizeInBytes === 0) return "0 B";
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
|
||||
@@ -35,7 +35,7 @@ export function useReverseShares() {
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
|
||||
const loadReverseShares = async () => {
|
||||
const loadReverseShares = useCallback(async () => {
|
||||
try {
|
||||
const response = await listUserReverseShares();
|
||||
const allReverseShares = response.data.reverseShares || [];
|
||||
@@ -44,12 +44,12 @@ export function useReverseShares() {
|
||||
);
|
||||
|
||||
setReverseShares(sortedReverseShares);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("reverseShares.errors.loadFailed"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
}, [t]);
|
||||
|
||||
const refreshReverseShare = async (id: string) => {
|
||||
try {
|
||||
@@ -70,7 +70,7 @@ export function useReverseShares() {
|
||||
setReverseShareToViewDetails(updatedReverseShare as ReverseShare);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("reverseShares.errors.loadFailed"));
|
||||
}
|
||||
};
|
||||
@@ -89,9 +89,8 @@ export function useReverseShares() {
|
||||
setReverseShareToGenerateLink(newReverseShare as ReverseShare);
|
||||
|
||||
return newReverseShare;
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("reverseShares.errors.createFailed"));
|
||||
throw error;
|
||||
} finally {
|
||||
setIsCreating(false);
|
||||
}
|
||||
@@ -128,9 +127,8 @@ export function useReverseShares() {
|
||||
}
|
||||
|
||||
toast.success(t("reverseShares.messages.aliasCreated"));
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("reverseShares.errors.aliasCreateFailed"));
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -143,7 +141,7 @@ export function useReverseShares() {
|
||||
|
||||
toast.success(t("reverseShares.messages.deleteSuccess"));
|
||||
setReverseShareToDelete(null);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("reverseShares.errors.deleteFailed"));
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
@@ -164,9 +162,8 @@ export function useReverseShares() {
|
||||
setReverseShareToEdit(null);
|
||||
|
||||
return updatedReverseShare;
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("reverseShares.errors.updateFailed"));
|
||||
throw error;
|
||||
} finally {
|
||||
setIsUpdating(false);
|
||||
}
|
||||
@@ -187,8 +184,8 @@ export function useReverseShares() {
|
||||
}
|
||||
|
||||
return updatedReverseShare;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
} catch {
|
||||
toast.error(t("reverseShares.errors.updateFailed"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -208,9 +205,8 @@ export function useReverseShares() {
|
||||
|
||||
toast.success(t("reverseShares.messages.updateSuccess"));
|
||||
return updatedReverseShare;
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("reverseShares.errors.updateFailed"));
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -232,15 +228,14 @@ export function useReverseShares() {
|
||||
isActive ? t("reverseShares.messages.activateSuccess") : t("reverseShares.messages.deactivateSuccess")
|
||||
);
|
||||
return updatedReverseShare;
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("reverseShares.errors.updateFailed"));
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadReverseShares();
|
||||
}, []);
|
||||
}, [loadReverseShares]);
|
||||
|
||||
useEffect(() => {
|
||||
if (reverseShareToViewDetails) {
|
||||
@@ -249,7 +244,7 @@ export function useReverseShares() {
|
||||
setReverseShareToViewDetails(updatedReverseShare);
|
||||
}
|
||||
}
|
||||
}, [reverseShares, reverseShareToViewDetails?.id]);
|
||||
}, [reverseShares, reverseShareToViewDetails]);
|
||||
|
||||
useEffect(() => {
|
||||
if (reverseShareToViewFiles) {
|
||||
@@ -258,7 +253,7 @@ export function useReverseShares() {
|
||||
setReverseShareToViewFiles(updatedReverseShare);
|
||||
}
|
||||
}
|
||||
}, [reverseShares, reverseShareToViewFiles?.id]);
|
||||
}, [reverseShares, reverseShareToViewFiles]);
|
||||
|
||||
const filteredReverseShares = reverseShares.filter(
|
||||
(reverseShare) => reverseShare.name?.toLowerCase().includes(searchQuery.toLowerCase()) ?? false
|
||||
|
@@ -1 +0,0 @@
|
||||
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
|
@@ -1,51 +1,54 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { getDownloadUrl, getShareByAlias } from "@/http/endpoints";
|
||||
import type { GetShareByAlias200Share } from "@/http/models/getShareByAlias200Share";
|
||||
import type { Share } from "@/http/endpoints/shares/types";
|
||||
|
||||
export function usePublicShare() {
|
||||
const t = useTranslations();
|
||||
const params = useParams();
|
||||
const alias = params?.alias as string;
|
||||
const [share, setShare] = useState<GetShareByAlias200Share | null>(null);
|
||||
const [share, setShare] = useState<Share | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [password, setPassword] = useState("");
|
||||
const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(false);
|
||||
const [isPasswordError, setIsPasswordError] = useState(false);
|
||||
|
||||
const loadShare = async (sharePassword?: string) => {
|
||||
if (!alias) return;
|
||||
const loadShare = useCallback(
|
||||
async (sharePassword?: string) => {
|
||||
if (!alias) return;
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await getShareByAlias(alias, sharePassword ? { password: sharePassword } : undefined);
|
||||
const handleShareError = (error: any) => {
|
||||
if (error.response?.data?.error === "Password required") {
|
||||
setIsPasswordModalOpen(true);
|
||||
setShare(null);
|
||||
} else if (error.response?.data?.error === "Invalid password") {
|
||||
setIsPasswordError(true);
|
||||
toast.error(t("share.errors.invalidPassword"));
|
||||
} else {
|
||||
toast.error(t("share.errors.loadFailed"));
|
||||
}
|
||||
};
|
||||
|
||||
setShare(response.data.share);
|
||||
setIsPasswordModalOpen(false);
|
||||
setIsPasswordError(false);
|
||||
} catch (error: any) {
|
||||
handleShareError(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await getShareByAlias(alias, sharePassword ? { password: sharePassword } : undefined);
|
||||
|
||||
const handleShareError = (error: any) => {
|
||||
if (error.response?.data?.error === "Password required") {
|
||||
setIsPasswordModalOpen(true);
|
||||
setShare(null);
|
||||
} else if (error.response?.data?.error === "Invalid password") {
|
||||
setIsPasswordError(true);
|
||||
toast.error(t("share.errors.invalidPassword"));
|
||||
} else {
|
||||
toast.error(t("share.errors.loadFailed"));
|
||||
}
|
||||
};
|
||||
setShare(response.data.share);
|
||||
setIsPasswordModalOpen(false);
|
||||
setIsPasswordError(false);
|
||||
} catch (error: any) {
|
||||
handleShareError(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[alias, t]
|
||||
);
|
||||
|
||||
const handlePasswordSubmit = async () => {
|
||||
await loadShare(password);
|
||||
@@ -64,7 +67,7 @@ export function usePublicShare() {
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
toast.success(t("share.messages.downloadStarted"));
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("share.errors.downloadFailed"));
|
||||
}
|
||||
};
|
||||
@@ -125,24 +128,9 @@ export function usePublicShare() {
|
||||
}
|
||||
};
|
||||
|
||||
const downloadFile = async (url: string, fileName: string) => {
|
||||
const fileResponse = await fetch(url);
|
||||
const blob = await fileResponse.blob();
|
||||
const objectUrl = window.URL.createObjectURL(blob);
|
||||
|
||||
const link = document.createElement("a");
|
||||
|
||||
link.href = objectUrl;
|
||||
link.download = fileName;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(objectUrl);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadShare();
|
||||
}, [alias]);
|
||||
}, [alias, loadShare]);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { GetShareByAlias200Share } from "@/http/models";
|
||||
import { Share } from "@/http/endpoints/shares/types";
|
||||
|
||||
export interface ShareFile {
|
||||
id: string;
|
||||
@@ -22,7 +22,7 @@ export interface PasswordModalProps {
|
||||
}
|
||||
|
||||
export interface ShareDetailsProps {
|
||||
share: GetShareByAlias200Share;
|
||||
share: Share;
|
||||
onDownload: (objectName: string, fileName: string) => Promise<void>;
|
||||
onBulkDownload?: () => Promise<void>;
|
||||
}
|
||||
|
@@ -1,23 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { useSecureConfigValue } from "@/hooks/use-secure-configs";
|
||||
import { listUserShares, notifyRecipients } from "@/http/endpoints";
|
||||
import { ListUserShares200SharesItem } from "@/http/models/listUserShares200SharesItem";
|
||||
import { Share } from "@/http/endpoints/shares/types";
|
||||
|
||||
export function useShares() {
|
||||
const t = useTranslations();
|
||||
const [shares, setShares] = useState<ListUserShares200SharesItem[]>([]);
|
||||
const [shares, setShares] = useState<Share[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [shareToGenerateLink, setShareToGenerateLink] = useState<ListUserShares200SharesItem | null>(null);
|
||||
const [shareToGenerateLink, setShareToGenerateLink] = useState<Share | null>(null);
|
||||
|
||||
const { value: smtpEnabled } = useSecureConfigValue("smtpEnabled");
|
||||
|
||||
const loadShares = async () => {
|
||||
const loadShares = useCallback(async () => {
|
||||
try {
|
||||
const response = await listUserShares();
|
||||
const allShares = response.data.shares || [];
|
||||
@@ -26,22 +26,22 @@ export function useShares() {
|
||||
);
|
||||
|
||||
setShares(sortedShares);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("shares.errors.loadFailed"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
}, [t]);
|
||||
|
||||
useEffect(() => {
|
||||
loadShares();
|
||||
}, []);
|
||||
}, [loadShares]);
|
||||
|
||||
const filteredShares = shares.filter(
|
||||
(share) => share.name?.toLowerCase().includes(searchQuery.toLowerCase()) ?? false
|
||||
);
|
||||
|
||||
const handleCopyLink = (share: ListUserShares200SharesItem) => {
|
||||
const handleCopyLink = (share: Share) => {
|
||||
if (!share.alias?.alias) return;
|
||||
|
||||
const link = `${window.location.origin}/s/${share.alias.alias}`;
|
||||
@@ -50,7 +50,7 @@ export function useShares() {
|
||||
toast.success(t("shares.messages.linkCopied"));
|
||||
};
|
||||
|
||||
const handleNotifyRecipients = async (share: ListUserShares200SharesItem) => {
|
||||
const handleNotifyRecipients = async (share: Share) => {
|
||||
if (!share.alias?.alias) return;
|
||||
|
||||
const link = `${window.location.origin}/s/${share.alias.alias}`;
|
||||
@@ -58,7 +58,7 @@ export function useShares() {
|
||||
try {
|
||||
await notifyRecipients(share.id, { shareLink: link });
|
||||
toast.success(t("shares.messages.recipientsNotified"));
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("shares.errors.notifyFailed"));
|
||||
}
|
||||
};
|
||||
|
@@ -1,5 +1,3 @@
|
||||
import { ListUserShares200SharesItem } from "@/http/models";
|
||||
|
||||
export interface SharesSearchProps {
|
||||
searchQuery: string;
|
||||
onSearchChange: (value: string) => void;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
export async function GET() {
|
||||
return new NextResponse(
|
||||
JSON.stringify({
|
||||
error: "This endpoint has been deprecated for security reasons. Use secure server actions instead.",
|
||||
|
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { LoadingScreen } from "@/components/layout/loading-screen";
|
||||
@@ -10,7 +10,6 @@ import { getCurrentUser } from "@/http/endpoints";
|
||||
|
||||
export default function OIDCCallbackPage() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const { setUser, setIsAuthenticated, setIsAdmin } = useAuth();
|
||||
const t = useTranslations();
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useFileManager } from "@/hooks/use-file-manager";
|
||||
import { useSecureConfigValue } from "@/hooks/use-secure-configs";
|
||||
import { useShareManager } from "@/hooks/use-share-manager";
|
||||
import { getDiskSpace, listFiles, listUserShares } from "@/http/endpoints";
|
||||
import { ListUserShares200SharesItem } from "@/http/models/listUserShares200SharesItem";
|
||||
import { Share } from "@/http/endpoints/shares/types";
|
||||
|
||||
export function useDashboard() {
|
||||
const t = useTranslations();
|
||||
@@ -33,7 +33,7 @@ export function useDashboard() {
|
||||
const onOpenCreateModal = () => setIsCreateModalOpen(true);
|
||||
const onCloseCreateModal = () => setIsCreateModalOpen(false);
|
||||
|
||||
const loadDashboardData = async () => {
|
||||
const loadDashboardData = useCallback(async () => {
|
||||
try {
|
||||
const loadDiskSpace = async () => {
|
||||
try {
|
||||
@@ -77,12 +77,12 @@ export function useDashboard() {
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
}, [t]);
|
||||
|
||||
const fileManager = useFileManager(loadDashboardData);
|
||||
const shareManager = useShareManager(loadDashboardData);
|
||||
|
||||
const handleCopyLink = (share: ListUserShares200SharesItem) => {
|
||||
const handleCopyLink = (share: Share) => {
|
||||
if (!share.alias?.alias) return;
|
||||
const link = `${window.location.origin}/s/${share.alias.alias}`;
|
||||
|
||||
@@ -92,7 +92,7 @@ export function useDashboard() {
|
||||
|
||||
useEffect(() => {
|
||||
loadDashboardData();
|
||||
}, []);
|
||||
}, [loadDashboardData]);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
|
@@ -115,8 +115,8 @@ export function DashboardModals({ modals, fileManager, shareManager, onSuccess }
|
||||
onClose={() => shareManager.setShareToViewDetails(null)}
|
||||
onUpdateName={shareManager.handleUpdateName}
|
||||
onUpdateDescription={shareManager.handleUpdateDescription}
|
||||
onUpdateSecurity={shareManager.handleUpdateSecurity}
|
||||
onUpdateExpiration={shareManager.handleUpdateExpiration}
|
||||
onUpdateSecurity={async () => shareManager.handleUpdateSecurity(shareManager.shareToViewDetails!)}
|
||||
onUpdateExpiration={async () => shareManager.handleUpdateExpiration(shareManager.shareToViewDetails!)}
|
||||
onGenerateLink={shareManager.handleGenerateLink}
|
||||
onManageFiles={shareManager.setShareToManageFiles}
|
||||
refreshTrigger={shareDetailsRefresh}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { FileManagerHook } from "@/hooks/use-file-manager";
|
||||
import { ShareManagerHook } from "@/hooks/use-share-manager";
|
||||
import { ListUserShares200SharesItem } from "@/http/models/listUserShares200SharesItem";
|
||||
import { Share } from "@/http/endpoints/shares/types";
|
||||
|
||||
export interface RecentFilesProps {
|
||||
files: any[];
|
||||
@@ -10,11 +10,11 @@ export interface RecentFilesProps {
|
||||
}
|
||||
|
||||
export interface RecentSharesProps {
|
||||
shares: ListUserShares200SharesItem[];
|
||||
shares: Share[];
|
||||
shareManager: ShareManagerHook;
|
||||
isCreateModalOpen: boolean;
|
||||
onOpenCreateModal: () => void;
|
||||
onCopyLink: (share: ListUserShares200SharesItem) => void;
|
||||
onCopyLink: (share: Share) => void;
|
||||
}
|
||||
|
||||
export interface StorageUsageProps {
|
||||
|
@@ -19,7 +19,7 @@ export function useFiles() {
|
||||
setClearSelectionCallbackState(() => callback);
|
||||
}, []);
|
||||
|
||||
const loadFiles = async () => {
|
||||
const loadFiles = useCallback(async () => {
|
||||
try {
|
||||
const response = await listFiles();
|
||||
const allFiles = response.data.files || [];
|
||||
@@ -28,19 +28,19 @@ export function useFiles() {
|
||||
);
|
||||
|
||||
setFiles(sortedFiles);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("files.loadError"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
}, [t]);
|
||||
|
||||
const fileManager = useFileManager(loadFiles, clearSelectionCallback);
|
||||
const filteredFiles = files.filter((file) => file.name.toLowerCase().includes(searchQuery.toLowerCase()));
|
||||
|
||||
useEffect(() => {
|
||||
loadFiles();
|
||||
}, []);
|
||||
}, [loadFiles]);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
|
@@ -1,5 +1,3 @@
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { BulkDownloadModal } from "@/components/modals/bulk-download-modal";
|
||||
import { DeleteConfirmationModal } from "@/components/modals/delete-confirmation-modal";
|
||||
import { FileActionsModals } from "@/components/modals/file-actions-modals";
|
||||
@@ -10,8 +8,6 @@ import { UploadFileModal } from "@/components/modals/upload-file-modal";
|
||||
import type { FilesModalsProps } from "../types";
|
||||
|
||||
export function FilesModals({ fileManager, modals, onSuccess }: FilesModalsProps) {
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<>
|
||||
<UploadFileModal isOpen={modals.isUploadModalOpen} onClose={modals.onCloseUploadModal} onSuccess={onSuccess} />
|
||||
|
@@ -14,15 +14,6 @@ export function MultiProviderButtons() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { firstAccess } = useAppInfo();
|
||||
|
||||
useEffect(() => {
|
||||
if (firstAccess) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
loadProviders();
|
||||
}, [firstAccess]);
|
||||
|
||||
const loadProviders = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
@@ -40,6 +31,14 @@ export function MultiProviderButtons() {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
if (firstAccess) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
loadProviders();
|
||||
}, [firstAccess]);
|
||||
|
||||
const handleProviderLogin = (provider: EnabledAuthProvider) => {
|
||||
if (!provider.authUrl) {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useForm } from "react-hook-form";
|
||||
@@ -23,6 +23,7 @@ interface RegisterFormProps {
|
||||
export function RegisterForm({ isVisible, onToggleVisibility }: RegisterFormProps) {
|
||||
const t = useTranslations();
|
||||
const { refreshAppInfo } = useAppInfo();
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const registerSchema = z.object({
|
||||
firstName: z.string().min(1, t("register.validation.firstNameRequired")),
|
||||
@@ -44,6 +45,7 @@ export function RegisterForm({ isVisible, onToggleVisibility }: RegisterFormProp
|
||||
});
|
||||
|
||||
const onSubmit = async (data: z.infer<typeof registerSchema>) => {
|
||||
setError(null);
|
||||
try {
|
||||
await registerUser({
|
||||
...data,
|
||||
@@ -55,15 +57,14 @@ export function RegisterForm({ isVisible, onToggleVisibility }: RegisterFormProp
|
||||
|
||||
await refreshAppInfo();
|
||||
toast.success(t("register.validation.success"));
|
||||
} catch (error) {
|
||||
} catch {
|
||||
setError(t("register.validation.error"));
|
||||
toast.error(t("register.validation.error"));
|
||||
}
|
||||
};
|
||||
|
||||
const renderErrorMessage = () => (
|
||||
<p className="text-destructive text-sm text-center bg-destructive/10 p-2 rounded-md">
|
||||
{t("register.validation.error")}
|
||||
</p>
|
||||
<p className="text-destructive text-sm text-center bg-destructive/10 p-2 rounded-md">{error}</p>
|
||||
);
|
||||
|
||||
const renderForm = () => (
|
||||
@@ -178,6 +179,7 @@ export function RegisterForm({ isVisible, onToggleVisibility }: RegisterFormProp
|
||||
|
||||
return (
|
||||
<>
|
||||
{error && renderErrorMessage()}
|
||||
{renderForm()}
|
||||
<MultiProviderButtons />
|
||||
</>
|
||||
|
@@ -30,7 +30,6 @@ export function useLogin() {
|
||||
useEffect(() => {
|
||||
const errorParam = searchParams.get("error");
|
||||
const messageParam = searchParams.get("message");
|
||||
const providerParam = searchParams.get("provider");
|
||||
|
||||
if (errorParam) {
|
||||
let message: string;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useForm } from "react-hook-form";
|
||||
@@ -53,7 +53,7 @@ export function useProfile() {
|
||||
resolver: zodResolver(passwordSchema),
|
||||
});
|
||||
|
||||
const loadUserData = async () => {
|
||||
const loadUserData = useCallback(async () => {
|
||||
try {
|
||||
const response = await getCurrentUser();
|
||||
|
||||
@@ -64,12 +64,12 @@ export function useProfile() {
|
||||
username: response.data.user.username,
|
||||
email: response.data.user.email,
|
||||
});
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("profile.errors.loadFailed"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
}, [t, profileForm]);
|
||||
|
||||
const onProfileSubmit = async (data: z.infer<typeof profileSchema>) => {
|
||||
const hasChanges = Object.keys(data).some((key) => data[key as keyof typeof data] !== userData[key]);
|
||||
@@ -87,7 +87,7 @@ export function useProfile() {
|
||||
});
|
||||
toast.success(t("profile.messages.updateSuccess"));
|
||||
await loadUserData();
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("profile.errors.updateFailed"));
|
||||
}
|
||||
};
|
||||
@@ -106,7 +106,7 @@ export function useProfile() {
|
||||
});
|
||||
toast.success(t("profile.messages.passwordSuccess"));
|
||||
passwordForm.reset();
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("profile.errors.passwordFailed"));
|
||||
}
|
||||
};
|
||||
@@ -121,7 +121,7 @@ export function useProfile() {
|
||||
setUserData(updatedUser);
|
||||
setUser(updatedUser);
|
||||
toast.success(t("profile.messages.imageSuccess"));
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("profile.errors.imageFailed"));
|
||||
}
|
||||
};
|
||||
@@ -136,14 +136,14 @@ export function useProfile() {
|
||||
setUserData(updatedUser);
|
||||
setUser(updatedUser);
|
||||
toast.success(t("profile.messages.imageRemoved"));
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("profile.errors.imageRemoveFailed"));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadUserData();
|
||||
}, []);
|
||||
}, [loadUserData]);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
|
@@ -32,7 +32,6 @@ export function AuthProvidersSettings() {
|
||||
isDeleting,
|
||||
enabledCount,
|
||||
filteredProviders,
|
||||
loadProviders,
|
||||
updateProvider,
|
||||
addProvider,
|
||||
editProvider,
|
||||
|
@@ -70,6 +70,16 @@ export function EditProviderForm({
|
||||
const [showClientSecret, setShowClientSecret] = useState(false);
|
||||
const isOfficial = provider.isOfficial;
|
||||
|
||||
const updateFormData = (updates: Partial<typeof formData>) => {
|
||||
const newFormData = { ...formData, ...updates };
|
||||
setFormData(newFormData);
|
||||
|
||||
setEditingFormData({
|
||||
...editingFormData,
|
||||
[provider.id]: newFormData,
|
||||
});
|
||||
};
|
||||
|
||||
const detectProviderTypeAndSuggestScopesEdit = (url: string, currentType: string): string[] => {
|
||||
if (!url) return [];
|
||||
|
||||
@@ -122,16 +132,6 @@ export function EditProviderForm({
|
||||
}
|
||||
};
|
||||
|
||||
const updateFormData = (updates: Partial<typeof formData>) => {
|
||||
const newFormData = { ...formData, ...updates };
|
||||
setFormData(newFormData);
|
||||
|
||||
setEditingFormData({
|
||||
...editingFormData,
|
||||
[provider.id]: newFormData,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
onSave(formData);
|
||||
};
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { DropResult } from "@hello-pangea/dnd";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
@@ -37,12 +37,7 @@ export function useAuthProviders() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Load providers on mount
|
||||
useEffect(() => {
|
||||
loadProviders();
|
||||
}, []);
|
||||
|
||||
const loadProviders = async () => {
|
||||
const loadProviders = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await getAllProviders();
|
||||
@@ -59,7 +54,12 @@ export function useAuthProviders() {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
}, [t]);
|
||||
|
||||
// Load providers on mount
|
||||
useEffect(() => {
|
||||
loadProviders();
|
||||
}, [loadProviders]);
|
||||
|
||||
const updateProvider = async (id: string, updates: Partial<AuthProvider>) => {
|
||||
try {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useForm } from "react-hook-form";
|
||||
@@ -42,12 +42,20 @@ export function useSettings() {
|
||||
reload: reloadConfigs,
|
||||
} = useAdminConfigs();
|
||||
|
||||
const groupForms = {
|
||||
general: useForm<GroupFormData>({ resolver: zodResolver(settingsSchema) }),
|
||||
email: useForm<GroupFormData>({ resolver: zodResolver(settingsSchema) }),
|
||||
security: useForm<GroupFormData>({ resolver: zodResolver(settingsSchema) }),
|
||||
storage: useForm<GroupFormData>({ resolver: zodResolver(settingsSchema) }),
|
||||
} as const;
|
||||
const generalForm = useForm<GroupFormData>({ resolver: zodResolver(settingsSchema) });
|
||||
const emailForm = useForm<GroupFormData>({ resolver: zodResolver(settingsSchema) });
|
||||
const securityForm = useForm<GroupFormData>({ resolver: zodResolver(settingsSchema) });
|
||||
const storageForm = useForm<GroupFormData>({ resolver: zodResolver(settingsSchema) });
|
||||
|
||||
const groupForms = useMemo(
|
||||
() => ({
|
||||
general: generalForm,
|
||||
email: emailForm,
|
||||
security: securityForm,
|
||||
storage: storageForm,
|
||||
}),
|
||||
[generalForm, emailForm, securityForm, storageForm]
|
||||
);
|
||||
|
||||
type ValidGroup = keyof typeof groupForms;
|
||||
|
||||
@@ -132,7 +140,7 @@ export function useSettings() {
|
||||
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [configsLoading, adminConfigsList]);
|
||||
}, [configsLoading, adminConfigsList, groupForms]);
|
||||
|
||||
const onGroupSubmit = async (group: ValidGroup, data: GroupFormData) => {
|
||||
try {
|
||||
@@ -164,7 +172,7 @@ export function useSettings() {
|
||||
}
|
||||
|
||||
await refreshAppInfo();
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("settings.errors.updateFailed"));
|
||||
}
|
||||
};
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Resolver, useForm } from "react-hook-form";
|
||||
@@ -10,7 +10,7 @@ import { z } from "zod";
|
||||
import { useAuth } from "@/contexts/auth-context";
|
||||
import { useDisclosure } from "@/hooks/use-disclosure";
|
||||
import { activateUser, deactivateUser, deleteUser, listUsers, registerUser, updateUser } from "@/http/endpoints";
|
||||
import type { ListUsers200Item } from "@/http/models";
|
||||
import { User } from "@/http/endpoints/auth/types";
|
||||
|
||||
const createSchemas = (t: (key: string) => string) => ({
|
||||
userSchema: z.object({
|
||||
@@ -35,12 +35,12 @@ export function useUserManagement() {
|
||||
const t = useTranslations();
|
||||
|
||||
const { userSchema } = createSchemas(t);
|
||||
const [users, setUsers] = useState<ListUsers200Item[]>([]);
|
||||
const [users, setUsers] = useState<User[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [selectedUser, setSelectedUser] = useState<ListUsers200Item | null>(null);
|
||||
const [selectedUser, setSelectedUser] = useState<User | null>(null);
|
||||
const [modalMode, setModalMode] = useState<"create" | "edit">("create");
|
||||
const [deleteModalUser, setDeleteModalUser] = useState<ListUsers200Item | null>(null);
|
||||
const [statusModalUser, setStatusModalUser] = useState<ListUsers200Item | null>(null);
|
||||
const [deleteModalUser, setDeleteModalUser] = useState<User | null>(null);
|
||||
const [statusModalUser, setStatusModalUser] = useState<User | null>(null);
|
||||
|
||||
const { user: currentUser } = useAuth();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
@@ -51,21 +51,21 @@ export function useUserManagement() {
|
||||
resolver: zodResolver(userSchema) as Resolver<UserFormData>,
|
||||
});
|
||||
|
||||
const loadUsers = async () => {
|
||||
const loadUsers = useCallback(async () => {
|
||||
try {
|
||||
const response = await listUsers();
|
||||
|
||||
setUsers(response.data);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("users.errors.loadFailed"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
}, [t]);
|
||||
|
||||
useEffect(() => {
|
||||
loadUsers();
|
||||
}, []);
|
||||
}, [loadUsers]);
|
||||
|
||||
const handleCreateUser = () => {
|
||||
setModalMode("create");
|
||||
@@ -74,7 +74,7 @@ export function useUserManagement() {
|
||||
onOpen();
|
||||
};
|
||||
|
||||
const handleEditUser = (user: ListUsers200Item) => {
|
||||
const handleEditUser = (user: User) => {
|
||||
setModalMode("edit");
|
||||
setSelectedUser(user);
|
||||
formMethods.reset({
|
||||
@@ -109,7 +109,7 @@ export function useUserManagement() {
|
||||
}
|
||||
onClose();
|
||||
loadUsers();
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("users.errors.submitFailed", { mode: t(`users.modes.${modalMode}`) }));
|
||||
}
|
||||
};
|
||||
@@ -122,7 +122,7 @@ export function useUserManagement() {
|
||||
toast.success(t("users.messages.deleteSuccess"));
|
||||
loadUsers();
|
||||
onDeleteModalClose();
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("users.errors.deleteFailed"));
|
||||
}
|
||||
};
|
||||
@@ -140,7 +140,7 @@ export function useUserManagement() {
|
||||
}
|
||||
loadUsers();
|
||||
onStatusModalClose();
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("users.errors.statusUpdateFailed"));
|
||||
}
|
||||
};
|
||||
|
@@ -1,20 +1,20 @@
|
||||
import { UseFormReturn } from "react-hook-form";
|
||||
|
||||
import type { ListUsers200Item } from "@/http/models";
|
||||
import { User } from "@/http/endpoints/auth/types";
|
||||
import { UserFormData } from "../hooks/use-user-management";
|
||||
|
||||
export interface UserActionsDropdownProps {
|
||||
user: ListUsers200Item;
|
||||
user: User;
|
||||
isCurrentUser: boolean;
|
||||
onEdit: (user: ListUsers200Item) => void;
|
||||
onDelete: (user: ListUsers200Item) => void;
|
||||
onToggleStatus: (user: ListUsers200Item) => void;
|
||||
onEdit: (user: User) => void;
|
||||
onDelete: (user: User) => void;
|
||||
onToggleStatus: (user: User) => void;
|
||||
}
|
||||
|
||||
export interface UserDeleteModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
user: ListUsers200Item | null;
|
||||
user: User | null;
|
||||
onConfirm: () => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export interface UserFormModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
modalMode: "create" | "edit";
|
||||
selectedUser: ListUsers200Item | null;
|
||||
selectedUser: User | null;
|
||||
formMethods: UseFormReturn<UserFormData>;
|
||||
onSubmit: (data: UserFormData) => Promise<void>;
|
||||
}
|
||||
@@ -37,9 +37,9 @@ export interface UserManagementModalsProps {
|
||||
isStatusModalOpen: boolean;
|
||||
onStatusModalClose: () => void;
|
||||
};
|
||||
selectedUser: ListUsers200Item | null;
|
||||
deleteModalUser: ListUsers200Item | null;
|
||||
statusModalUser: ListUsers200Item | null;
|
||||
selectedUser: User | null;
|
||||
deleteModalUser: User | null;
|
||||
statusModalUser: User | null;
|
||||
onSubmit: (data: UserFormData) => Promise<void>;
|
||||
onDelete: () => Promise<void>;
|
||||
onToggleStatus: () => Promise<void>;
|
||||
@@ -49,7 +49,7 @@ export interface UserManagementModalsProps {
|
||||
export interface UserStatusModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
user: ListUsers200Item | null;
|
||||
user: User | null;
|
||||
onConfirm: () => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -67,9 +67,9 @@ export interface AuthUser {
|
||||
}
|
||||
|
||||
export interface UsersTableProps {
|
||||
users: ListUsers200Item[];
|
||||
users: User[];
|
||||
currentUser: AuthUser | null;
|
||||
onEdit: (user: ListUsers200Item) => void;
|
||||
onDelete: (user: ListUsers200Item) => void;
|
||||
onToggleStatus: (user: ListUsers200Item) => void;
|
||||
onEdit: (user: User) => void;
|
||||
onDelete: (user: User) => void;
|
||||
onToggleStatus: (user: User) => void;
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { IconPlayerPause, IconPlayerPlay, IconVolume, IconVolume3, IconVolumeOff } from "@tabler/icons-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -21,9 +21,37 @@ export function CustomAudioPlayer({ src }: CustomAudioPlayerProps) {
|
||||
const audioRef = useRef<HTMLAudioElement>(null);
|
||||
const previousVolume = useRef(1);
|
||||
|
||||
const loadAudioData = useCallback(async () => {
|
||||
try {
|
||||
const response = await fetch(src);
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
|
||||
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
||||
|
||||
const channelData = audioBuffer.getChannelData(0);
|
||||
const points = 200;
|
||||
const blockSize = Math.floor(channelData.length / points);
|
||||
const downsampledData = new Float32Array(points);
|
||||
|
||||
for (let i = 0; i < points; i++) {
|
||||
const blockStart = blockSize * i;
|
||||
let sum = 0;
|
||||
for (let j = 0; j < blockSize; j++) {
|
||||
sum += Math.abs(channelData[blockStart + j]);
|
||||
}
|
||||
downsampledData[i] = sum / blockSize;
|
||||
}
|
||||
|
||||
setAudioData(downsampledData);
|
||||
} catch (error) {
|
||||
console.error("Failed to load audio data:", error);
|
||||
setAudioData(null);
|
||||
}
|
||||
}, [src]);
|
||||
|
||||
useEffect(() => {
|
||||
loadAudioData();
|
||||
}, [src]);
|
||||
}, [src, loadAudioData]);
|
||||
|
||||
useEffect(() => {
|
||||
const audio = audioRef.current;
|
||||
@@ -51,34 +79,6 @@ export function CustomAudioPlayer({ src }: CustomAudioPlayerProps) {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const loadAudioData = async () => {
|
||||
try {
|
||||
const response = await fetch(src);
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
|
||||
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
||||
|
||||
const channelData = audioBuffer.getChannelData(0);
|
||||
const points = 200;
|
||||
const blockSize = Math.floor(channelData.length / points);
|
||||
const downsampledData = new Float32Array(points);
|
||||
|
||||
for (let i = 0; i < points; i++) {
|
||||
const blockStart = blockSize * i;
|
||||
let sum = 0;
|
||||
for (let j = 0; j < blockSize; j++) {
|
||||
sum += Math.abs(channelData[blockStart + j]);
|
||||
}
|
||||
downsampledData[i] = sum / blockSize;
|
||||
}
|
||||
|
||||
setAudioData(downsampledData);
|
||||
} catch (error) {
|
||||
console.error("Failed to load audio data:", error);
|
||||
setAudioData(null);
|
||||
}
|
||||
};
|
||||
|
||||
const togglePlayPause = () => {
|
||||
if (!audioRef.current) return;
|
||||
|
||||
|
@@ -23,7 +23,7 @@ const WaveformVisualizer = ({ progress, onSeek, audioData, isLoading = false }:
|
||||
const generateRealWaveform = () => {
|
||||
if (!audioData) return generateMockWaveform();
|
||||
|
||||
return Array.from(audioData, (value, i) => {
|
||||
return Array.from(audioData, (value) => {
|
||||
const normalizedHeight = Math.max(15, value * 100 + 20);
|
||||
return normalizedHeight;
|
||||
});
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { IconCheck, IconEdit, IconEye, IconMinus, IconPlus, IconSearch } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
@@ -30,21 +30,21 @@ export function FileSelector({ shareId, selectedFiles, onSave, onEditFile }: Fil
|
||||
const [previewFile, setPreviewFile] = useState<any>(null);
|
||||
const [fileToEdit, setFileToEdit] = useState<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
loadFiles();
|
||||
}, [shareId, selectedFiles]);
|
||||
|
||||
const loadFiles = async () => {
|
||||
const loadFiles = useCallback(async () => {
|
||||
try {
|
||||
const response = await listFiles();
|
||||
const allFiles = response.data.files || [];
|
||||
|
||||
setShareFiles(allFiles.filter((file) => selectedFiles.includes(file.id)));
|
||||
setAvailableFiles(allFiles.filter((file) => !selectedFiles.includes(file.id)));
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("files.loadError"));
|
||||
}
|
||||
};
|
||||
}, [selectedFiles, t]);
|
||||
|
||||
useEffect(() => {
|
||||
loadFiles();
|
||||
}, [loadFiles]);
|
||||
|
||||
const addToShare = (fileId: string) => {
|
||||
const file = availableFiles.find((f) => f.id === fileId);
|
||||
@@ -78,7 +78,7 @@ export function FileSelector({ shareId, selectedFiles, onSave, onEditFile }: Fil
|
||||
}
|
||||
|
||||
await onSave(shareFiles.map((f) => f.id));
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("shareManager.filesUpdateError"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
|
@@ -64,7 +64,7 @@ export function RecipientSelector({ shareId, selectedRecipients, shareAlias, onS
|
||||
setNewRecipient("");
|
||||
toast.success(t("recipientSelector.addSuccess"));
|
||||
onSuccess();
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("recipientSelector.addError"));
|
||||
} finally {
|
||||
setIsAddingRecipient(false);
|
||||
@@ -82,7 +82,7 @@ export function RecipientSelector({ shareId, selectedRecipients, shareAlias, onS
|
||||
});
|
||||
toast.success(t("recipientSelector.removeSuccess"));
|
||||
onSuccess();
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("recipientSelector.removeError"));
|
||||
}
|
||||
};
|
||||
@@ -95,7 +95,7 @@ export function RecipientSelector({ shareId, selectedRecipients, shareAlias, onS
|
||||
setSelectedForAction(new Set());
|
||||
toast.success(t("recipientSelector.bulkRemoveSuccess", { count: emailsToRemove.length }));
|
||||
onSuccess();
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("recipientSelector.bulkRemoveError"));
|
||||
}
|
||||
};
|
||||
@@ -112,7 +112,7 @@ export function RecipientSelector({ shareId, selectedRecipients, shareAlias, onS
|
||||
toast.dismiss(loadingToast);
|
||||
toast.success(t("recipientSelector.bulkNotifySuccess", { count: emailsToNotify.length }));
|
||||
setSelectedForAction(new Set());
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.dismiss(loadingToast);
|
||||
toast.error(t("recipientSelector.bulkNotifyError"));
|
||||
}
|
||||
@@ -128,7 +128,7 @@ export function RecipientSelector({ shareId, selectedRecipients, shareAlias, onS
|
||||
await notifyRecipients(shareId, { shareLink: link });
|
||||
toast.dismiss(loadingToast);
|
||||
toast.success(t("recipientSelector.notifySuccess"));
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.dismiss(loadingToast);
|
||||
toast.error(t("recipientSelector.notifyError"));
|
||||
}
|
||||
@@ -300,7 +300,7 @@ export function RecipientSelector({ shareId, selectedRecipients, shareAlias, onS
|
||||
await notifyRecipients(shareId, { shareLink: link });
|
||||
toast.dismiss(loadingToast);
|
||||
toast.success(t("recipientSelector.singleNotifySuccess", { email }));
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.dismiss(loadingToast);
|
||||
toast.error(t("recipientSelector.singleNotifyError"));
|
||||
}
|
||||
|
@@ -20,6 +20,10 @@ export function BulkDownloadModal({ isOpen, onClose, onDownload, fileCount }: Bu
|
||||
const t = useTranslations();
|
||||
const [zipName, setZipName] = useState("");
|
||||
|
||||
const handleClose = () => {
|
||||
onClose();
|
||||
setZipName("");
|
||||
};
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (zipName.trim()) {
|
||||
@@ -28,11 +32,6 @@ export function BulkDownloadModal({ isOpen, onClose, onDownload, fileCount }: Bu
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
onClose();
|
||||
setZipName("");
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
|
@@ -52,7 +52,7 @@ export function CreateShareModal({ isOpen, onClose, onSuccess }: CreateShareModa
|
||||
isPasswordProtected: false,
|
||||
maxViews: "",
|
||||
});
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("createShare.error"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
|
@@ -8,12 +8,12 @@ import { toast } from "sonner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import type { ListUserShares200SharesItem } from "@/http/models/listUserShares200SharesItem";
|
||||
import { Share } from "@/http/endpoints/shares/types";
|
||||
import { customNanoid } from "@/lib/utils";
|
||||
|
||||
interface GenerateShareLinkModalProps {
|
||||
shareId: string | null;
|
||||
share: ListUserShares200SharesItem | null;
|
||||
share: Share | null;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
onGenerate: (shareId: string, alias: string) => Promise<void>;
|
||||
@@ -56,7 +56,7 @@ export function GenerateShareLinkModal({
|
||||
setGeneratedLink(link);
|
||||
onSuccess();
|
||||
toast.success(t("generateShareLink.success"));
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("generateShareLink.error"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
|
@@ -106,7 +106,7 @@ export function ShareActionsModals({
|
||||
onSuccess();
|
||||
onCloseEdit();
|
||||
toast.success(t("shareActions.editSuccess"));
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("shareActions.editError"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
IconCheck,
|
||||
IconCopy,
|
||||
@@ -88,11 +88,24 @@ export function ShareDetailsModal({
|
||||
const [showExpirationModal, setShowExpirationModal] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const loadShareDetails = useCallback(async () => {
|
||||
if (!shareId) return;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await getShare(shareId);
|
||||
setShare(response.data.share);
|
||||
} catch {
|
||||
toast.error(t("shareDetails.loadError"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [shareId, t]);
|
||||
|
||||
useEffect(() => {
|
||||
if (shareId) {
|
||||
loadShareDetails();
|
||||
}
|
||||
}, [shareId]);
|
||||
}, [shareId, loadShareDetails]);
|
||||
|
||||
useEffect(() => {
|
||||
if (editingField && inputRef.current) {
|
||||
@@ -109,26 +122,13 @@ export function ShareDetailsModal({
|
||||
if (refreshTrigger) {
|
||||
loadShareDetails();
|
||||
}
|
||||
}, [refreshTrigger]);
|
||||
|
||||
const loadShareDetails = async () => {
|
||||
if (!shareId) return;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await getShare(shareId);
|
||||
setShare(response.data.share);
|
||||
} catch (error) {
|
||||
toast.error(t("shareDetails.loadError"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
}, [refreshTrigger, loadShareDetails]);
|
||||
|
||||
const formatDate = (dateString: string | null) => {
|
||||
if (!dateString) return t("shareDetails.notAvailable");
|
||||
try {
|
||||
return format(new Date(dateString), "MM/dd/yyyy HH:mm");
|
||||
} catch (error) {
|
||||
} catch {
|
||||
return t("shareDetails.invalidDate");
|
||||
}
|
||||
};
|
||||
|
@@ -86,7 +86,7 @@ export function ShareFileModal({ isOpen, file, onClose, onSuccess }: ShareFileMo
|
||||
|
||||
toast.success(t("createShare.success"));
|
||||
setStep("link");
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("createShare.error"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -111,7 +111,7 @@ export function ShareFileModal({ isOpen, file, onClose, onSuccess }: ShareFileMo
|
||||
const link = `${window.location.origin}/s/${alias}`;
|
||||
setGeneratedLink(link);
|
||||
toast.success(t("generateShareLink.success"));
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("generateShareLink.error"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
|
@@ -87,7 +87,7 @@ export function ShareMultipleFilesModal({ files, isOpen, onClose, onSuccess }: S
|
||||
|
||||
toast.success(t("createShare.success"));
|
||||
setStep("link");
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("createShare.error"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -103,7 +103,7 @@ export function ShareMultipleFilesModal({ files, isOpen, onClose, onSuccess }: S
|
||||
const link = `${window.location.origin}/s/${alias}`;
|
||||
setGeneratedLink(link);
|
||||
toast.success(t("generateShareLink.success"));
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("generateShareLink.error"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { IconCheck, IconEye, IconEyeOff, IconLock, IconLockOpen, IconX } from "@tabler/icons-react";
|
||||
import { IconEye, IconEyeOff, IconLock, IconLockOpen } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
|
||||
|
@@ -94,7 +94,7 @@ export function UploadFileModal({ isOpen, onClose, onSuccess }: UploadFileModalP
|
||||
}
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
}, [fileUploads]);
|
||||
|
||||
const generateFileId = () => {
|
||||
return Date.now().toString() + Math.random().toString(36).substr(2, 9);
|
||||
@@ -324,16 +324,6 @@ export function UploadFileModal({ isOpen, onClose, onSuccess }: UploadFileModalP
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
const uploadsInProgress = fileUploads.filter((u) => u.status === UploadStatus.UPLOADING).length;
|
||||
|
||||
if (uploadsInProgress > 0) {
|
||||
setShowConfirmation(true);
|
||||
} else {
|
||||
handleConfirmClose();
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirmClose = () => {
|
||||
fileUploads.forEach((upload) => {
|
||||
if (upload.status === UploadStatus.UPLOADING && upload.abortController) {
|
||||
@@ -352,6 +342,16 @@ export function UploadFileModal({ isOpen, onClose, onSuccess }: UploadFileModalP
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
const uploadsInProgress = fileUploads.filter((u) => u.status === UploadStatus.UPLOADING).length;
|
||||
|
||||
if (uploadsInProgress > 0) {
|
||||
setShowConfirmation(true);
|
||||
} else {
|
||||
handleConfirmClose();
|
||||
}
|
||||
};
|
||||
|
||||
const handleContinueUploads = () => {
|
||||
setShowConfirmation(false);
|
||||
};
|
||||
|
@@ -83,6 +83,11 @@ export function FilesGrid({
|
||||
setClearSelectionCallback?.(clearSelection);
|
||||
}, [setClearSelectionCallback]);
|
||||
|
||||
const isImageFile = (fileName: string) => {
|
||||
const imageExtensions = [".jpg", ".jpeg", ".png", ".gif", ".webp"];
|
||||
return imageExtensions.some((ext) => fileName.toLowerCase().endsWith(ext));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const loadPreviewUrls = async () => {
|
||||
const imageFiles = files.filter((file) => isImageFile(file.name));
|
||||
@@ -123,7 +128,8 @@ export function FilesGrid({
|
||||
if (componentMounted.current) {
|
||||
loadPreviewUrls();
|
||||
}
|
||||
}, [files, filePreviewUrls]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [files]);
|
||||
|
||||
const formatDateTime = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
@@ -185,11 +191,6 @@ export function FilesGrid({
|
||||
|
||||
const showBulkActions = selectedFiles.size > 0 && (onBulkDelete || onBulkShare || onBulkDownload);
|
||||
|
||||
const isImageFile = (fileName: string) => {
|
||||
const imageExtensions = [".jpg", ".jpeg", ".png", ".gif", ".webp"];
|
||||
return imageExtensions.some((ext) => fileName.toLowerCase().endsWith(ext));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{showBulkActions && (
|
||||
|
@@ -1,9 +1,9 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as React from "react"
|
||||
import * as SliderPrimitive from "@radix-ui/react-slider"
|
||||
import * as React from "react";
|
||||
import * as SliderPrimitive from "@radix-ui/react-slider";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Slider({
|
||||
className,
|
||||
@@ -14,14 +14,9 @@ function Slider({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SliderPrimitive.Root>) {
|
||||
const _values = React.useMemo(
|
||||
() =>
|
||||
Array.isArray(value)
|
||||
? value
|
||||
: Array.isArray(defaultValue)
|
||||
? defaultValue
|
||||
: [min, max],
|
||||
() => (Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max]),
|
||||
[value, defaultValue, min, max]
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<SliderPrimitive.Root
|
||||
@@ -44,9 +39,7 @@ function Slider({
|
||||
>
|
||||
<SliderPrimitive.Range
|
||||
data-slot="slider-range"
|
||||
className={cn(
|
||||
"bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
|
||||
)}
|
||||
className={cn("bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full")}
|
||||
/>
|
||||
</SliderPrimitive.Track>
|
||||
{Array.from({ length: _values.length }, (_, index) => (
|
||||
@@ -57,7 +50,7 @@ function Slider({
|
||||
/>
|
||||
))}
|
||||
</SliderPrimitive.Root>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { Slider }
|
||||
export { Slider };
|
||||
|
@@ -3,9 +3,9 @@
|
||||
import { createContext, useContext, useEffect, useState } from "react";
|
||||
|
||||
import { getAppInfo, getCurrentUser } from "@/http/endpoints";
|
||||
import type { GetCurrentUser200User } from "@/http/models";
|
||||
import type { User } from "@/http/endpoints/auth/types";
|
||||
|
||||
type AuthUser = Omit<GetCurrentUser200User, "isAdmin">;
|
||||
type AuthUser = Omit<User, "isAdmin">;
|
||||
|
||||
type AuthContextType = {
|
||||
user: AuthUser | null;
|
||||
|
@@ -101,6 +101,7 @@ export function useFileManager(onRefresh: () => Promise<void>, clearSelection?:
|
||||
toast.success(t("files.downloadStart"));
|
||||
} catch (error) {
|
||||
toast.error(t("files.downloadError"));
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
|
||||
@@ -29,229 +29,6 @@ interface UseFilePreviewProps {
|
||||
}
|
||||
|
||||
export function useFilePreview({ file, isOpen, isReverseShare = false }: UseFilePreviewProps) {
|
||||
if (isReverseShare) {
|
||||
return useReverseShareFilePreview({ file, isOpen });
|
||||
}
|
||||
return useNormalFilePreview({ file, isOpen });
|
||||
}
|
||||
|
||||
// Separate hook for reverse shares - exact copy of working logic
|
||||
function useReverseShareFilePreview({ file, isOpen }: { file: UseFilePreviewProps["file"]; isOpen: boolean }) {
|
||||
const t = useTranslations();
|
||||
const [state, setState] = useState<FilePreviewState>({
|
||||
previewUrl: null,
|
||||
videoBlob: null,
|
||||
textContent: null,
|
||||
downloadUrl: null,
|
||||
isLoading: true,
|
||||
isLoadingPreview: false,
|
||||
pdfAsBlob: false,
|
||||
pdfLoadFailed: false,
|
||||
});
|
||||
|
||||
const loadedRef = useRef<string | null>(null);
|
||||
const fileType: FileType = getFileType(file.name);
|
||||
|
||||
// Reset state when file changes or modal opens
|
||||
useEffect(() => {
|
||||
if (isOpen && file.id && loadedRef.current !== file.id) {
|
||||
loadedRef.current = file.id;
|
||||
resetState();
|
||||
loadPreview();
|
||||
} else if (!isOpen) {
|
||||
loadedRef.current = null;
|
||||
}
|
||||
}, [isOpen, file.id]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
cleanupBlobUrls();
|
||||
};
|
||||
}, [state.previewUrl, state.videoBlob]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
cleanupBlobUrls();
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const resetState = () => {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
previewUrl: null,
|
||||
videoBlob: null,
|
||||
textContent: null,
|
||||
downloadUrl: null,
|
||||
pdfAsBlob: false,
|
||||
pdfLoadFailed: false,
|
||||
isLoading: true,
|
||||
}));
|
||||
};
|
||||
|
||||
const cleanupBlobUrls = () => {
|
||||
if (state.previewUrl && state.previewUrl.startsWith("blob:")) {
|
||||
URL.revokeObjectURL(state.previewUrl);
|
||||
}
|
||||
if (state.videoBlob && state.videoBlob.startsWith("blob:")) {
|
||||
URL.revokeObjectURL(state.videoBlob);
|
||||
}
|
||||
};
|
||||
|
||||
const loadPreview = async () => {
|
||||
if (!file.id || state.isLoadingPreview) return;
|
||||
|
||||
setState((prev) => ({ ...prev, isLoadingPreview: true }));
|
||||
|
||||
try {
|
||||
const response = await downloadReverseShareFile(file.id);
|
||||
const url = response.data.url;
|
||||
|
||||
setState((prev) => ({ ...prev, downloadUrl: url }));
|
||||
|
||||
switch (fileType) {
|
||||
case "video":
|
||||
await loadVideoPreview(url);
|
||||
break;
|
||||
case "audio":
|
||||
await loadAudioPreview(url);
|
||||
break;
|
||||
case "pdf":
|
||||
await loadPdfPreview(url);
|
||||
break;
|
||||
case "text":
|
||||
await loadTextPreview(url);
|
||||
break;
|
||||
default:
|
||||
setState((prev) => ({ ...prev, previewUrl: url }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load preview:", error);
|
||||
toast.error(t("filePreview.loadError"));
|
||||
} finally {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
isLoading: false,
|
||||
isLoadingPreview: false,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const loadVideoPreview = async (url: string) => {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
setState((prev) => ({ ...prev, videoBlob: blobUrl }));
|
||||
} catch (error) {
|
||||
console.error("Failed to load video as blob:", error);
|
||||
setState((prev) => ({ ...prev, previewUrl: url }));
|
||||
}
|
||||
};
|
||||
|
||||
const loadAudioPreview = async (url: string) => {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
setState((prev) => ({ ...prev, previewUrl: blobUrl }));
|
||||
} catch (error) {
|
||||
console.error("Failed to load audio as blob:", error);
|
||||
setState((prev) => ({ ...prev, previewUrl: url }));
|
||||
}
|
||||
};
|
||||
|
||||
const loadPdfPreview = async (url: string) => {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
const finalBlob = new Blob([blob], { type: "application/pdf" });
|
||||
const blobUrl = URL.createObjectURL(finalBlob);
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
previewUrl: blobUrl,
|
||||
pdfAsBlob: true,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error("Failed to load PDF as blob:", error);
|
||||
setState((prev) => ({ ...prev, previewUrl: url }));
|
||||
setTimeout(() => {
|
||||
if (!state.pdfLoadFailed && !state.pdfAsBlob) {
|
||||
handlePdfLoadError();
|
||||
}
|
||||
}, 4000);
|
||||
}
|
||||
};
|
||||
|
||||
const loadTextPreview = async (url: string) => {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
const extension = getFileExtension(file.name);
|
||||
|
||||
try {
|
||||
if (extension === "json") {
|
||||
const parsed = JSON.parse(text);
|
||||
const formatted = JSON.stringify(parsed, null, 2);
|
||||
setState((prev) => ({ ...prev, textContent: formatted }));
|
||||
} else {
|
||||
setState((prev) => ({ ...prev, textContent: text }));
|
||||
}
|
||||
} catch (jsonError) {
|
||||
setState((prev) => ({ ...prev, textContent: text }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load text content:", error);
|
||||
setState((prev) => ({ ...prev, textContent: null }));
|
||||
}
|
||||
};
|
||||
|
||||
const handlePdfLoadError = async () => {
|
||||
if (state.pdfLoadFailed || state.pdfAsBlob) return;
|
||||
setState((prev) => ({ ...prev, pdfLoadFailed: true }));
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
if (!file.id) return;
|
||||
|
||||
try {
|
||||
const response = await downloadReverseShareFile(file.id);
|
||||
const link = document.createElement("a");
|
||||
link.href = response.data.url;
|
||||
link.download = file.name;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
} catch (error) {
|
||||
console.error("Download failed:", error);
|
||||
toast.error(t("filePreview.downloadError"));
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...state,
|
||||
fileType,
|
||||
handleDownload,
|
||||
handlePdfLoadError,
|
||||
};
|
||||
}
|
||||
|
||||
function useNormalFilePreview({ file, isOpen }: { file: UseFilePreviewProps["file"]; isOpen: boolean }) {
|
||||
const t = useTranslations();
|
||||
const [state, setState] = useState<FilePreviewState>({
|
||||
previewUrl: null,
|
||||
@@ -269,32 +46,7 @@ function useNormalFilePreview({ file, isOpen }: { file: UseFilePreviewProps["fil
|
||||
|
||||
const fileType: FileType = getFileType(file.name);
|
||||
|
||||
useEffect(() => {
|
||||
const fileKey = file.objectName;
|
||||
|
||||
if (isOpen && fileKey && loadedRef.current !== fileKey) {
|
||||
loadedRef.current = fileKey;
|
||||
resetState();
|
||||
loadPreview();
|
||||
} else if (!isOpen) {
|
||||
loadedRef.current = null;
|
||||
loadingRef.current = false;
|
||||
}
|
||||
}, [isOpen, file.objectName]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
cleanupBlobUrls();
|
||||
};
|
||||
}, [state.previewUrl, state.videoBlob]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
cleanupBlobUrls();
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const resetState = () => {
|
||||
const resetState = useCallback(() => {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
previewUrl: null,
|
||||
@@ -307,32 +59,130 @@ function useNormalFilePreview({ file, isOpen }: { file: UseFilePreviewProps["fil
|
||||
}));
|
||||
loadedRef.current = null;
|
||||
loadingRef.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const cleanupBlobUrls = () => {
|
||||
if (state.previewUrl && state.previewUrl.startsWith("blob:")) {
|
||||
URL.revokeObjectURL(state.previewUrl);
|
||||
}
|
||||
if (state.videoBlob && state.videoBlob.startsWith("blob:")) {
|
||||
URL.revokeObjectURL(state.videoBlob);
|
||||
}
|
||||
};
|
||||
const cleanupBlobUrls = useCallback(() => {
|
||||
setState((prev) => {
|
||||
if (prev.previewUrl && prev.previewUrl.startsWith("blob:")) {
|
||||
URL.revokeObjectURL(prev.previewUrl);
|
||||
}
|
||||
if (prev.videoBlob && prev.videoBlob.startsWith("blob:")) {
|
||||
URL.revokeObjectURL(prev.videoBlob);
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const loadPreview = async () => {
|
||||
if (!file.objectName || state.isLoadingPreview) return;
|
||||
const loadVideoPreview = useCallback(async (url: string) => {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const currentFileKey = file.objectName;
|
||||
if (loadingRef.current) {
|
||||
return;
|
||||
const blob = await response.blob();
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
setState((prev) => ({ ...prev, videoBlob: blobUrl }));
|
||||
} catch {
|
||||
setState((prev) => ({ ...prev, previewUrl: url }));
|
||||
}
|
||||
}, []);
|
||||
|
||||
const loadAudioPreview = useCallback(async (url: string) => {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
setState((prev) => ({ ...prev, previewUrl: blobUrl }));
|
||||
} catch {
|
||||
setState((prev) => ({ ...prev, previewUrl: url }));
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handlePdfLoadError = useCallback(() => {
|
||||
setState((prev) => {
|
||||
if (prev.pdfLoadFailed || prev.pdfAsBlob) return prev;
|
||||
return { ...prev, pdfLoadFailed: true };
|
||||
});
|
||||
}, []);
|
||||
|
||||
const loadPdfPreview = useCallback(
|
||||
async (url: string) => {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
const finalBlob = new Blob([blob], { type: "application/pdf" });
|
||||
const blobUrl = URL.createObjectURL(finalBlob);
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
previewUrl: blobUrl,
|
||||
pdfAsBlob: true,
|
||||
}));
|
||||
} catch {
|
||||
setState((prev) => ({ ...prev, previewUrl: url }));
|
||||
setTimeout(() => {
|
||||
handlePdfLoadError();
|
||||
}, 4000);
|
||||
}
|
||||
},
|
||||
[handlePdfLoadError]
|
||||
);
|
||||
|
||||
const loadTextPreview = useCallback(
|
||||
async (url: string) => {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
const extension = getFileExtension(file.name);
|
||||
|
||||
try {
|
||||
if (extension === "json") {
|
||||
const parsed = JSON.parse(text);
|
||||
const formatted = JSON.stringify(parsed, null, 2);
|
||||
setState((prev) => ({ ...prev, textContent: formatted }));
|
||||
} else {
|
||||
setState((prev) => ({ ...prev, textContent: text }));
|
||||
}
|
||||
} catch {
|
||||
setState((prev) => ({ ...prev, textContent: text }));
|
||||
}
|
||||
} catch {
|
||||
setState((prev) => ({ ...prev, textContent: null }));
|
||||
}
|
||||
},
|
||||
[file.name]
|
||||
);
|
||||
|
||||
const loadPreview = useCallback(async () => {
|
||||
const fileKey = isReverseShare ? file.id : file.objectName;
|
||||
if (!fileKey || state.isLoadingPreview || loadingRef.current) return;
|
||||
|
||||
loadingRef.current = true;
|
||||
setState((prev) => ({ ...prev, isLoadingPreview: true }));
|
||||
|
||||
try {
|
||||
const encodedObjectName = encodeURIComponent(file.objectName);
|
||||
const response = await getDownloadUrl(encodedObjectName);
|
||||
const url = response.data.url;
|
||||
let url: string;
|
||||
|
||||
if (isReverseShare) {
|
||||
const response = await downloadReverseShareFile(file.id!);
|
||||
url = response.data.url;
|
||||
} else {
|
||||
const encodedObjectName = encodeURIComponent(file.objectName);
|
||||
const response = await getDownloadUrl(encodedObjectName);
|
||||
url = response.data.url;
|
||||
}
|
||||
|
||||
setState((prev) => ({ ...prev, downloadUrl: url }));
|
||||
|
||||
@@ -352,8 +202,7 @@ function useNormalFilePreview({ file, isOpen }: { file: UseFilePreviewProps["fil
|
||||
default:
|
||||
setState((prev) => ({ ...prev, previewUrl: url }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load preview:", error);
|
||||
} catch {
|
||||
toast.error(t("filePreview.loadError"));
|
||||
} finally {
|
||||
setState((prev) => ({
|
||||
@@ -363,115 +212,68 @@ function useNormalFilePreview({ file, isOpen }: { file: UseFilePreviewProps["fil
|
||||
}));
|
||||
loadingRef.current = false;
|
||||
}
|
||||
};
|
||||
}, [
|
||||
isReverseShare,
|
||||
file.id,
|
||||
file.objectName,
|
||||
state.isLoadingPreview,
|
||||
fileType,
|
||||
loadVideoPreview,
|
||||
loadAudioPreview,
|
||||
loadPdfPreview,
|
||||
loadTextPreview,
|
||||
t,
|
||||
]);
|
||||
|
||||
const loadVideoPreview = async (url: string) => {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
setState((prev) => ({ ...prev, videoBlob: blobUrl }));
|
||||
} catch (error) {
|
||||
console.error("Failed to load video as blob:", error);
|
||||
setState((prev) => ({ ...prev, previewUrl: url }));
|
||||
}
|
||||
};
|
||||
|
||||
const loadAudioPreview = async (url: string) => {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
setState((prev) => ({ ...prev, previewUrl: blobUrl }));
|
||||
} catch (error) {
|
||||
console.error("Failed to load audio as blob:", error);
|
||||
setState((prev) => ({ ...prev, previewUrl: url }));
|
||||
}
|
||||
};
|
||||
|
||||
const loadPdfPreview = async (url: string) => {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
const finalBlob = new Blob([blob], { type: "application/pdf" });
|
||||
const blobUrl = URL.createObjectURL(finalBlob);
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
previewUrl: blobUrl,
|
||||
pdfAsBlob: true,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error("Failed to load PDF as blob:", error);
|
||||
setState((prev) => ({ ...prev, previewUrl: url }));
|
||||
setTimeout(() => {
|
||||
if (!state.pdfLoadFailed && !state.pdfAsBlob) {
|
||||
handlePdfLoadError();
|
||||
}
|
||||
}, 4000);
|
||||
}
|
||||
};
|
||||
|
||||
const loadTextPreview = async (url: string) => {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
const extension = getFileExtension(file.name);
|
||||
|
||||
try {
|
||||
if (extension === "json") {
|
||||
const parsed = JSON.parse(text);
|
||||
const formatted = JSON.stringify(parsed, null, 2);
|
||||
setState((prev) => ({ ...prev, textContent: formatted }));
|
||||
} else {
|
||||
setState((prev) => ({ ...prev, textContent: text }));
|
||||
}
|
||||
} catch (jsonError) {
|
||||
setState((prev) => ({ ...prev, textContent: text }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load text content:", error);
|
||||
setState((prev) => ({ ...prev, textContent: null }));
|
||||
}
|
||||
};
|
||||
|
||||
const handlePdfLoadError = async () => {
|
||||
if (state.pdfLoadFailed || state.pdfAsBlob) return;
|
||||
setState((prev) => ({ ...prev, pdfLoadFailed: true }));
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
if (!file.objectName) return;
|
||||
const handleDownload = useCallback(async () => {
|
||||
const fileKey = isReverseShare ? file.id : file.objectName;
|
||||
if (!fileKey) return;
|
||||
|
||||
try {
|
||||
const encodedObjectName = encodeURIComponent(file.objectName);
|
||||
const response = await getDownloadUrl(encodedObjectName);
|
||||
let downloadUrl: string;
|
||||
|
||||
if (isReverseShare) {
|
||||
const response = await downloadReverseShareFile(file.id!);
|
||||
downloadUrl = response.data.url;
|
||||
} else {
|
||||
const encodedObjectName = encodeURIComponent(file.objectName);
|
||||
const response = await getDownloadUrl(encodedObjectName);
|
||||
downloadUrl = response.data.url;
|
||||
}
|
||||
|
||||
const link = document.createElement("a");
|
||||
link.href = response.data.url;
|
||||
link.href = downloadUrl;
|
||||
link.download = file.name;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
} catch (error) {
|
||||
console.error("Download failed:", error);
|
||||
} catch {
|
||||
toast.error(t("filePreview.downloadError"));
|
||||
}
|
||||
};
|
||||
}, [isReverseShare, file.id, file.objectName, file.name, t]);
|
||||
|
||||
useEffect(() => {
|
||||
const fileKey = isReverseShare ? file.id : file.objectName;
|
||||
|
||||
if (isOpen && fileKey && loadedRef.current !== fileKey) {
|
||||
loadedRef.current = fileKey;
|
||||
resetState();
|
||||
loadPreview();
|
||||
} else if (!isOpen) {
|
||||
loadedRef.current = null;
|
||||
loadingRef.current = false;
|
||||
}
|
||||
}, [isOpen, isReverseShare, file.id, file.objectName, resetState, loadPreview]);
|
||||
|
||||
useEffect(() => {
|
||||
return cleanupBlobUrls;
|
||||
}, [cleanupBlobUrls]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
cleanupBlobUrls();
|
||||
}
|
||||
}, [isOpen, cleanupBlobUrls]);
|
||||
|
||||
return {
|
||||
...state,
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { getAllConfigs } from "@/http/endpoints";
|
||||
|
||||
@@ -103,7 +103,7 @@ export function useSecureConfigValue(key: string) {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const loadConfigValue = async () => {
|
||||
const loadConfigValue = useCallback(async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
@@ -116,11 +116,11 @@ export function useSecureConfigValue(key: string) {
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
}, [key]);
|
||||
|
||||
useEffect(() => {
|
||||
loadConfigValue();
|
||||
}, [key]);
|
||||
}, [key, loadConfigValue]);
|
||||
|
||||
return {
|
||||
value,
|
||||
|
@@ -13,56 +13,56 @@ import {
|
||||
notifyRecipients,
|
||||
updateShare,
|
||||
} from "@/http/endpoints";
|
||||
import { ListUserShares200SharesItem } from "@/http/models/listUserShares200SharesItem";
|
||||
import type { Share } from "@/http/endpoints/shares/types";
|
||||
|
||||
export interface ShareManagerHook {
|
||||
shareToDelete: ListUserShares200SharesItem | null;
|
||||
shareToEdit: ListUserShares200SharesItem | null;
|
||||
shareToManageFiles: ListUserShares200SharesItem | null;
|
||||
shareToManageRecipients: ListUserShares200SharesItem | null;
|
||||
shareToManageSecurity: ListUserShares200SharesItem | null;
|
||||
shareToManageExpiration: ListUserShares200SharesItem | null;
|
||||
shareToViewDetails: ListUserShares200SharesItem | null;
|
||||
shareToGenerateLink: ListUserShares200SharesItem | null;
|
||||
sharesToDelete: ListUserShares200SharesItem[] | null;
|
||||
setShareToDelete: (share: ListUserShares200SharesItem | null) => void;
|
||||
setShareToEdit: (share: ListUserShares200SharesItem | null) => void;
|
||||
setShareToManageFiles: (share: ListUserShares200SharesItem | null) => void;
|
||||
setShareToManageRecipients: (share: ListUserShares200SharesItem | null) => void;
|
||||
setShareToManageSecurity: (share: ListUserShares200SharesItem | null) => void;
|
||||
setShareToManageExpiration: (share: ListUserShares200SharesItem | null) => void;
|
||||
setShareToViewDetails: (share: ListUserShares200SharesItem | null) => void;
|
||||
setShareToGenerateLink: (share: ListUserShares200SharesItem | null) => void;
|
||||
setSharesToDelete: (shares: ListUserShares200SharesItem[] | null) => void;
|
||||
shareToDelete: Share | null;
|
||||
shareToEdit: Share | null;
|
||||
shareToManageFiles: Share | null;
|
||||
shareToManageRecipients: Share | null;
|
||||
shareToManageSecurity: Share | null;
|
||||
shareToManageExpiration: Share | null;
|
||||
shareToViewDetails: Share | null;
|
||||
shareToGenerateLink: Share | null;
|
||||
sharesToDelete: Share[] | null;
|
||||
setShareToDelete: (share: Share | null) => void;
|
||||
setShareToEdit: (share: Share | null) => void;
|
||||
setShareToManageFiles: (share: Share | null) => void;
|
||||
setShareToManageRecipients: (share: Share | null) => void;
|
||||
setShareToManageSecurity: (share: Share | null) => void;
|
||||
setShareToManageExpiration: (share: Share | null) => void;
|
||||
setShareToViewDetails: (share: Share | null) => void;
|
||||
setShareToGenerateLink: (share: Share | null) => void;
|
||||
setSharesToDelete: (shares: Share[] | null) => void;
|
||||
handleDelete: (shareId: string) => Promise<void>;
|
||||
handleBulkDelete: (shares: ListUserShares200SharesItem[]) => void;
|
||||
handleBulkDownload: (shares: ListUserShares200SharesItem[]) => void;
|
||||
handleDownloadShareFiles: (share: ListUserShares200SharesItem) => Promise<void>;
|
||||
handleBulkDownloadWithZip: (shares: ListUserShares200SharesItem[], zipName: string) => Promise<void>;
|
||||
handleBulkDelete: (shares: Share[]) => void;
|
||||
handleBulkDownload: (shares: Share[]) => void;
|
||||
handleDownloadShareFiles: (share: Share) => Promise<void>;
|
||||
handleBulkDownloadWithZip: (shares: Share[], zipName: string) => Promise<void>;
|
||||
handleDeleteBulk: () => Promise<void>;
|
||||
handleEdit: (shareId: string, data: any) => Promise<void>;
|
||||
handleUpdateName: (shareId: string, newName: string) => Promise<void>;
|
||||
handleUpdateDescription: (shareId: string, newDescription: string) => Promise<void>;
|
||||
handleUpdateSecurity: (shareId: string) => Promise<void>;
|
||||
handleUpdateExpiration: (shareId: string) => Promise<void>;
|
||||
handleUpdateSecurity: (share: Share) => Promise<void>;
|
||||
handleUpdateExpiration: (share: Share) => Promise<void>;
|
||||
handleManageFiles: (shareId: string, files: any[]) => Promise<void>;
|
||||
handleManageRecipients: (shareId: string, recipients: any[]) => Promise<void>;
|
||||
handleGenerateLink: (shareId: string, alias: string) => Promise<void>;
|
||||
handleNotifyRecipients: (share: ListUserShares200SharesItem) => Promise<void>;
|
||||
handleNotifyRecipients: (share: Share) => Promise<void>;
|
||||
setClearSelectionCallback?: (callback: () => void) => void;
|
||||
}
|
||||
|
||||
export function useShareManager(onSuccess: () => void) {
|
||||
const t = useTranslations();
|
||||
const [shareToDelete, setShareToDelete] = useState<ListUserShares200SharesItem | null>(null);
|
||||
const [shareToEdit, setShareToEdit] = useState<ListUserShares200SharesItem | null>(null);
|
||||
const [shareToManageFiles, setShareToManageFiles] = useState<ListUserShares200SharesItem | null>(null);
|
||||
const [shareToManageRecipients, setShareToManageRecipients] = useState<ListUserShares200SharesItem | null>(null);
|
||||
const [shareToManageSecurity, setShareToManageSecurity] = useState<ListUserShares200SharesItem | null>(null);
|
||||
const [shareToManageExpiration, setShareToManageExpiration] = useState<ListUserShares200SharesItem | null>(null);
|
||||
const [shareToViewDetails, setShareToViewDetails] = useState<ListUserShares200SharesItem | null>(null);
|
||||
const [shareToGenerateLink, setShareToGenerateLink] = useState<ListUserShares200SharesItem | null>(null);
|
||||
const [sharesToDelete, setSharesToDelete] = useState<ListUserShares200SharesItem[] | null>(null);
|
||||
const [shareToDelete, setShareToDelete] = useState<Share | null>(null);
|
||||
const [shareToEdit, setShareToEdit] = useState<Share | null>(null);
|
||||
const [shareToManageFiles, setShareToManageFiles] = useState<Share | null>(null);
|
||||
const [shareToManageRecipients, setShareToManageRecipients] = useState<Share | null>(null);
|
||||
const [shareToManageSecurity, setShareToManageSecurity] = useState<Share | null>(null);
|
||||
const [shareToManageExpiration, setShareToManageExpiration] = useState<Share | null>(null);
|
||||
const [shareToViewDetails, setShareToViewDetails] = useState<Share | null>(null);
|
||||
const [shareToGenerateLink, setShareToGenerateLink] = useState<Share | null>(null);
|
||||
const [sharesToDelete, setSharesToDelete] = useState<Share[] | null>(null);
|
||||
const [clearSelectionCallback, setClearSelectionCallbackState] = useState<(() => void) | null>(null);
|
||||
|
||||
const setClearSelectionCallback = useCallback((callback: () => void) => {
|
||||
@@ -75,12 +75,12 @@ export function useShareManager(onSuccess: () => void) {
|
||||
toast.success(t("shareManager.deleteSuccess"));
|
||||
onSuccess();
|
||||
setShareToDelete(null);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("shareManager.deleteError"));
|
||||
}
|
||||
};
|
||||
|
||||
const handleBulkDelete = (shares: ListUserShares200SharesItem[]) => {
|
||||
const handleBulkDelete = (shares: Share[]) => {
|
||||
setSharesToDelete(shares);
|
||||
};
|
||||
|
||||
@@ -99,7 +99,7 @@ export function useShareManager(onSuccess: () => void) {
|
||||
if (clearSelectionCallback) {
|
||||
clearSelectionCallback();
|
||||
}
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.dismiss(loadingToast);
|
||||
toast.error(t("shareManager.bulkDeleteError"));
|
||||
}
|
||||
@@ -111,7 +111,7 @@ export function useShareManager(onSuccess: () => void) {
|
||||
toast.success(t("shareManager.updateSuccess"));
|
||||
onSuccess();
|
||||
setShareToEdit(null);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("shareManager.updateError"));
|
||||
}
|
||||
};
|
||||
@@ -121,7 +121,7 @@ export function useShareManager(onSuccess: () => void) {
|
||||
await updateShare({ id: shareId, name: newName });
|
||||
await onSuccess();
|
||||
toast.success(t("shareManager.updateSuccess"));
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("shareManager.updateError"));
|
||||
}
|
||||
};
|
||||
@@ -131,27 +131,17 @@ export function useShareManager(onSuccess: () => void) {
|
||||
await updateShare({ id: shareId, description: newDescription });
|
||||
await onSuccess();
|
||||
toast.success(t("shareManager.updateSuccess"));
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("shareManager.updateError"));
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateSecurity = async (shareId: string) => {
|
||||
try {
|
||||
await onSuccess();
|
||||
toast.success(t("shareManager.securityUpdateSuccess"));
|
||||
} catch (error) {
|
||||
toast.error(t("shareManager.securityUpdateError"));
|
||||
}
|
||||
const handleUpdateSecurity = async (share: Share) => {
|
||||
setShareToManageSecurity(share);
|
||||
};
|
||||
|
||||
const handleUpdateExpiration = async (shareId: string) => {
|
||||
try {
|
||||
await onSuccess();
|
||||
toast.success(t("shareManager.expirationUpdateSuccess"));
|
||||
} catch (error) {
|
||||
toast.error(t("shareManager.expirationUpdateError"));
|
||||
}
|
||||
const handleUpdateExpiration = async (share: Share) => {
|
||||
setShareToManageExpiration(share);
|
||||
};
|
||||
|
||||
const handleManageFiles = async (shareId: string, files: string[]) => {
|
||||
@@ -160,7 +150,7 @@ export function useShareManager(onSuccess: () => void) {
|
||||
toast.success(t("shareManager.filesUpdateSuccess"));
|
||||
onSuccess();
|
||||
setShareToManageFiles(null);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("shareManager.filesUpdateError"));
|
||||
}
|
||||
};
|
||||
@@ -171,7 +161,7 @@ export function useShareManager(onSuccess: () => void) {
|
||||
toast.success(t("shareManager.recipientsUpdateSuccess"));
|
||||
onSuccess();
|
||||
setShareToManageRecipients(null);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error(t("shareManager.recipientsUpdateError"));
|
||||
}
|
||||
};
|
||||
@@ -187,7 +177,7 @@ export function useShareManager(onSuccess: () => void) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleNotifyRecipients = async (share: ListUserShares200SharesItem) => {
|
||||
const handleNotifyRecipients = async (share: Share) => {
|
||||
const link = `${window.location.origin}/s/${share.alias?.alias}`;
|
||||
const loadingToast = toast.loading(t("shareManager.notifyLoading"));
|
||||
|
||||
@@ -195,54 +185,13 @@ export function useShareManager(onSuccess: () => void) {
|
||||
await notifyRecipients(share.id, { shareLink: link });
|
||||
toast.dismiss(loadingToast);
|
||||
toast.success(t("shareManager.notifySuccess"));
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.dismiss(loadingToast);
|
||||
toast.error(t("shareManager.notifyError"));
|
||||
}
|
||||
};
|
||||
|
||||
const handleBulkDownload = (shares: ListUserShares200SharesItem[]) => {
|
||||
const zipName =
|
||||
shares.length === 1
|
||||
? t("shareManager.singleShareZipName", { shareName: shares[0].name || t("shareManager.defaultShareName") })
|
||||
: t("shareManager.multipleSharesZipName", { count: shares.length });
|
||||
|
||||
handleBulkDownloadWithZip(shares, zipName);
|
||||
};
|
||||
|
||||
const handleDownloadShareFiles = async (share: ListUserShares200SharesItem) => {
|
||||
if (!share.files || share.files.length === 0) {
|
||||
toast.error(t("shareManager.noFilesToDownload"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (share.files.length === 1) {
|
||||
const file = share.files[0];
|
||||
try {
|
||||
const encodedObjectName = encodeURIComponent(file.objectName);
|
||||
const response = await getDownloadUrl(encodedObjectName);
|
||||
const downloadUrl = response.data.url;
|
||||
|
||||
const link = document.createElement("a");
|
||||
link.href = downloadUrl;
|
||||
link.download = file.name;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
toast.success(t("shareManager.downloadSuccess"));
|
||||
} catch (error) {
|
||||
console.error("Download error:", error);
|
||||
toast.error(t("shareManager.downloadError"));
|
||||
}
|
||||
} else {
|
||||
const zipName = t("shareManager.singleShareZipName", {
|
||||
shareName: share.name || t("shareManager.defaultShareName"),
|
||||
});
|
||||
await handleBulkDownloadWithZip([share], zipName);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBulkDownloadWithZip = async (shares: ListUserShares200SharesItem[], zipName: string) => {
|
||||
const handleBulkDownloadWithZip = async (shares: Share[], zipName: string) => {
|
||||
try {
|
||||
toast.promise(
|
||||
(async () => {
|
||||
@@ -309,6 +258,47 @@ export function useShareManager(onSuccess: () => void) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleBulkDownload = (shares: Share[]) => {
|
||||
const zipName =
|
||||
shares.length === 1
|
||||
? t("shareManager.singleShareZipName", { shareName: shares[0].name || t("shareManager.defaultShareName") })
|
||||
: t("shareManager.multipleSharesZipName", { count: shares.length });
|
||||
|
||||
handleBulkDownloadWithZip(shares, zipName);
|
||||
};
|
||||
|
||||
const handleDownloadShareFiles = async (share: Share) => {
|
||||
if (!share.files || share.files.length === 0) {
|
||||
toast.error(t("shareManager.noFilesToDownload"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (share.files.length === 1) {
|
||||
const file = share.files[0];
|
||||
try {
|
||||
const encodedObjectName = encodeURIComponent(file.objectName);
|
||||
const response = await getDownloadUrl(encodedObjectName);
|
||||
const downloadUrl = response.data.url;
|
||||
|
||||
const link = document.createElement("a");
|
||||
link.href = downloadUrl;
|
||||
link.download = file.name;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
toast.success(t("shareManager.downloadSuccess"));
|
||||
} catch (error) {
|
||||
console.error("Download error:", error);
|
||||
toast.error(t("shareManager.downloadError"));
|
||||
}
|
||||
} else {
|
||||
const zipName = t("shareManager.singleShareZipName", {
|
||||
shareName: share.name || t("shareManager.defaultShareName"),
|
||||
});
|
||||
await handleBulkDownloadWithZip([share], zipName);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
shareToDelete,
|
||||
shareToEdit,
|
||||
|
@@ -1,21 +1,60 @@
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
import type {
|
||||
CheckHealth200,
|
||||
CheckUploadAllowed200,
|
||||
CheckUploadAllowedParams,
|
||||
GetAppInfo200,
|
||||
GetDiskSpace200,
|
||||
RemoveLogo200,
|
||||
UploadLogo200,
|
||||
UploadLogoBody,
|
||||
} from "../../models";
|
||||
// Base types that are reused across different operations
|
||||
export interface FileSizeInfo {
|
||||
bytes: number;
|
||||
kb: number;
|
||||
mb: number;
|
||||
gb: number;
|
||||
}
|
||||
|
||||
export interface DiskSpaceInfo {
|
||||
diskSizeGB: number;
|
||||
diskUsedGB: number;
|
||||
diskAvailableGB: number;
|
||||
uploadAllowed: boolean;
|
||||
}
|
||||
|
||||
// Response types using base types
|
||||
export interface CheckHealth200 {
|
||||
status: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface CheckUploadAllowed200 extends DiskSpaceInfo {
|
||||
fileSizeInfo: FileSizeInfo;
|
||||
}
|
||||
|
||||
export type GetDiskSpace200 = DiskSpaceInfo;
|
||||
|
||||
export interface GetAppInfo200 {
|
||||
appName: string;
|
||||
appDescription: string;
|
||||
appLogo: string;
|
||||
firstUserAccess: boolean;
|
||||
}
|
||||
|
||||
export interface RemoveLogo200 {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface UploadLogo200 {
|
||||
logo: string;
|
||||
}
|
||||
|
||||
// Request body and parameter types
|
||||
export interface CheckUploadAllowedParams {
|
||||
fileSize: string;
|
||||
}
|
||||
|
||||
export interface UploadLogoBody {
|
||||
file?: unknown;
|
||||
}
|
||||
|
||||
// Axios response types
|
||||
export type GetAppInfoResult = AxiosResponse<GetAppInfo200>;
|
||||
export type UploadLogoResult = AxiosResponse<UploadLogo200>;
|
||||
export type RemoveLogoResult = AxiosResponse<RemoveLogo200>;
|
||||
export type CheckHealthResult = AxiosResponse<CheckHealth200>;
|
||||
export type GetDiskSpaceResult = AxiosResponse<GetDiskSpace200>;
|
||||
export type CheckUploadAllowedResult = AxiosResponse<CheckUploadAllowed200>;
|
||||
|
||||
export type { UploadLogoBody, CheckUploadAllowedParams };
|
||||
|
@@ -1,29 +1,72 @@
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
import type {
|
||||
GetCurrentUser200,
|
||||
Login200,
|
||||
LoginBody,
|
||||
Logout200,
|
||||
OidcConfig200,
|
||||
RequestPasswordReset200,
|
||||
RequestPasswordResetBody,
|
||||
ResetPassword200,
|
||||
ResetPasswordBody,
|
||||
} from "../../models";
|
||||
// Base types that are reused across different operations
|
||||
export interface BaseUser {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
username: string;
|
||||
email: string;
|
||||
isAdmin: boolean;
|
||||
isActive: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export type LoginResult = AxiosResponse<Login200>;
|
||||
export type LogoutResult = AxiosResponse<Logout200>;
|
||||
export type RequestPasswordResetResult = AxiosResponse<RequestPasswordReset200>;
|
||||
export type ResetPasswordResult = AxiosResponse<ResetPassword200>;
|
||||
export type GetCurrentUserResult = AxiosResponse<GetCurrentUser200>;
|
||||
export interface User extends BaseUser {
|
||||
image: string | null;
|
||||
}
|
||||
|
||||
export type { LoginBody, RequestPasswordResetBody, ResetPasswordBody };
|
||||
export type LoginUser = BaseUser;
|
||||
|
||||
export type OIDCConfigResult = AxiosResponse<OidcConfig200>;
|
||||
export type OIDCConfigData = OidcConfig200;
|
||||
// Common API response patterns
|
||||
export interface ApiResponse<T> {
|
||||
success: boolean;
|
||||
data: T;
|
||||
}
|
||||
|
||||
// Auth Providers Types
|
||||
export interface ApiMessageResponse {
|
||||
success: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface SimpleMessageResponse {
|
||||
message: string;
|
||||
}
|
||||
|
||||
// Auth response types using base types
|
||||
export interface GetCurrentUser200 {
|
||||
user: User;
|
||||
}
|
||||
|
||||
export interface Login200 {
|
||||
user: LoginUser;
|
||||
}
|
||||
|
||||
export interface OidcConfig200 {
|
||||
enabled: boolean;
|
||||
issuer?: string;
|
||||
authUrl?: string;
|
||||
scopes?: string[];
|
||||
}
|
||||
|
||||
// Request body types
|
||||
export interface LoginBody {
|
||||
emailOrUsername: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface RequestPasswordResetBody {
|
||||
email: string;
|
||||
origin: string;
|
||||
}
|
||||
|
||||
export interface ResetPasswordBody {
|
||||
token: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
// Auth Provider types
|
||||
export interface AuthProvider {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -44,7 +87,6 @@ export interface AuthProvider {
|
||||
userInfoEndpoint?: string;
|
||||
}
|
||||
|
||||
// Simplified auth provider for login page (only enabled providers)
|
||||
export interface EnabledAuthProvider {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -68,21 +110,6 @@ export interface NewProvider {
|
||||
userInfoEndpoint?: string;
|
||||
}
|
||||
|
||||
export interface AuthProvidersResponse {
|
||||
success: boolean;
|
||||
data: AuthProvider[];
|
||||
}
|
||||
|
||||
export interface EnabledProvidersResponse {
|
||||
success: boolean;
|
||||
data: EnabledAuthProvider[];
|
||||
}
|
||||
|
||||
export interface AuthProviderResponse {
|
||||
success: boolean;
|
||||
data: AuthProvider;
|
||||
}
|
||||
|
||||
export interface UpdateProvidersOrderBody {
|
||||
providers: Array<{
|
||||
id: string;
|
||||
@@ -90,14 +117,26 @@ export interface UpdateProvidersOrderBody {
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface AuthProviderOrderResponse {
|
||||
success: boolean;
|
||||
message: string;
|
||||
}
|
||||
// Response types using common patterns
|
||||
export type AuthProvidersResponse = ApiResponse<AuthProvider[]>;
|
||||
export type EnabledProvidersResponse = ApiResponse<EnabledAuthProvider[]>;
|
||||
export type AuthProviderResponse = ApiResponse<AuthProvider>;
|
||||
export type AuthProviderOrderResponse = ApiMessageResponse;
|
||||
export type Logout200 = SimpleMessageResponse;
|
||||
export type RequestPasswordReset200 = SimpleMessageResponse;
|
||||
export type ResetPassword200 = SimpleMessageResponse;
|
||||
|
||||
// Axios response types
|
||||
export type GetEnabledProvidersResult = AxiosResponse<EnabledProvidersResponse>;
|
||||
export type GetAllProvidersResult = AxiosResponse<AuthProvidersResponse>;
|
||||
export type CreateProviderResult = AxiosResponse<AuthProviderResponse>;
|
||||
export type UpdateProviderResult = AxiosResponse<AuthProviderResponse>;
|
||||
export type DeleteProviderResult = AxiosResponse<{ success: boolean; message: string }>;
|
||||
export type DeleteProviderResult = AxiosResponse<ApiMessageResponse>;
|
||||
export type UpdateProvidersOrderResult = AxiosResponse<AuthProviderOrderResponse>;
|
||||
export type LoginResult = AxiosResponse<Login200>;
|
||||
export type LogoutResult = AxiosResponse<Logout200>;
|
||||
export type RequestPasswordResetResult = AxiosResponse<RequestPasswordReset200>;
|
||||
export type ResetPasswordResult = AxiosResponse<ResetPassword200>;
|
||||
export type GetCurrentUserResult = AxiosResponse<GetCurrentUser200>;
|
||||
export type OIDCConfigResult = AxiosResponse<OidcConfig200>;
|
||||
export type OIDCConfigData = OidcConfig200;
|
||||
|
@@ -2,7 +2,7 @@ import type { AxiosRequestConfig } from "axios";
|
||||
|
||||
import apiInstance from "@/config/api";
|
||||
import type {
|
||||
BulkUpdateConfigsBodyItem,
|
||||
BulkUpdateConfigsBody,
|
||||
BulkUpdateConfigsResult,
|
||||
GetAllConfigsResult,
|
||||
UpdateConfigBody,
|
||||
@@ -34,8 +34,8 @@ export const getAllConfigs = <TData = GetAllConfigsResult>(options?: AxiosReques
|
||||
* @summary Bulk update configuration values
|
||||
*/
|
||||
export const bulkUpdateConfigs = <TData = BulkUpdateConfigsResult>(
|
||||
bulkUpdateConfigsBodyItem: BulkUpdateConfigsBodyItem[],
|
||||
bulkUpdateConfigsBody: BulkUpdateConfigsBody,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.patch(`api/config/update/bulk`, bulkUpdateConfigsBodyItem, options);
|
||||
return apiInstance.patch(`api/config/update/bulk`, bulkUpdateConfigsBody, options);
|
||||
};
|
||||
|
@@ -1,15 +1,40 @@
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
import type {
|
||||
BulkUpdateConfigs200,
|
||||
BulkUpdateConfigsBodyItem,
|
||||
GetAllConfigs200,
|
||||
UpdateConfig200,
|
||||
UpdateConfigBody,
|
||||
} from "../../models";
|
||||
// Base types that are reused across different operations
|
||||
export interface ConfigItem {
|
||||
key: string;
|
||||
value: string;
|
||||
type: string;
|
||||
group: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface ConfigUpdateItem {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
// Response types using base types
|
||||
export interface UpdateConfig200 {
|
||||
config: ConfigItem;
|
||||
}
|
||||
|
||||
export interface GetAllConfigs200 {
|
||||
configs: ConfigItem[];
|
||||
}
|
||||
|
||||
export interface BulkUpdateConfigs200 {
|
||||
configs: ConfigItem[];
|
||||
}
|
||||
|
||||
// Request body types
|
||||
export interface UpdateConfigBody {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export type BulkUpdateConfigsBody = ConfigUpdateItem[];
|
||||
|
||||
// Axios response types
|
||||
export type UpdateConfigResult = AxiosResponse<UpdateConfig200>;
|
||||
export type GetAllConfigsResult = AxiosResponse<GetAllConfigs200>;
|
||||
export type BulkUpdateConfigsResult = AxiosResponse<BulkUpdateConfigs200>;
|
||||
|
||||
export type { UpdateConfigBody, BulkUpdateConfigsBodyItem };
|
||||
|
@@ -1,19 +1,76 @@
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
import type {
|
||||
DeleteFile200,
|
||||
GetDownloadUrl200,
|
||||
GetPresignedUrl200,
|
||||
GetPresignedUrlParams,
|
||||
ListFiles200,
|
||||
RegisterFile201,
|
||||
CheckFile201,
|
||||
CheckFileBody,
|
||||
RegisterFileBody,
|
||||
UpdateFile200,
|
||||
UpdateFileBody,
|
||||
} from "../../models";
|
||||
// Base types that are reused across different operations
|
||||
export interface FileItem {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
extension: string;
|
||||
size: string;
|
||||
objectName: string;
|
||||
userId: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface FileOperationRequest {
|
||||
name: string;
|
||||
description?: string;
|
||||
extension: string;
|
||||
size: number;
|
||||
objectName: string;
|
||||
}
|
||||
|
||||
// Common response patterns
|
||||
export interface FileOperationResponse {
|
||||
file: FileItem;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface MessageOnlyResponse {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface UrlResponse {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface PresignedUrlResponse extends UrlResponse {
|
||||
objectName: string;
|
||||
}
|
||||
|
||||
export interface DownloadUrlResponse extends UrlResponse {
|
||||
expiresIn: number;
|
||||
}
|
||||
|
||||
// Response types using base types
|
||||
export interface ListFiles200 {
|
||||
files: FileItem[];
|
||||
}
|
||||
|
||||
// Request body types
|
||||
export type CheckFileBody = FileOperationRequest;
|
||||
export type RegisterFileBody = FileOperationRequest;
|
||||
|
||||
export interface UpdateFileBody {
|
||||
name?: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
// Query parameter types
|
||||
export interface GetPresignedUrlParams {
|
||||
filename: string;
|
||||
extension: string;
|
||||
}
|
||||
|
||||
export type RegisterFile201 = FileOperationResponse;
|
||||
export type UpdateFile200 = FileOperationResponse;
|
||||
export type DeleteFile200 = MessageOnlyResponse;
|
||||
export type CheckFile201 = MessageOnlyResponse;
|
||||
export type GetPresignedUrl200 = PresignedUrlResponse;
|
||||
export type GetDownloadUrl200 = DownloadUrlResponse;
|
||||
|
||||
// Axios response types
|
||||
export type GetPresignedUrlResult = AxiosResponse<GetPresignedUrl200>;
|
||||
export type RegisterFileResult = AxiosResponse<RegisterFile201>;
|
||||
export type CheckFileResult = AxiosResponse<CheckFile201>;
|
||||
@@ -21,5 +78,3 @@ export type ListFilesResult = AxiosResponse<ListFiles200>;
|
||||
export type GetDownloadUrlResult = AxiosResponse<GetDownloadUrl200>;
|
||||
export type DeleteFileResult = AxiosResponse<DeleteFile200>;
|
||||
export type UpdateFileResult = AxiosResponse<UpdateFile200>;
|
||||
|
||||
export type { GetPresignedUrlParams, RegisterFileBody, UpdateFileBody, CheckFileBody };
|
||||
|
@@ -1,242 +1,11 @@
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
// Base types and enums
|
||||
export type FieldRequirement = "HIDDEN" | "OPTIONAL" | "REQUIRED";
|
||||
export type PageLayout = "WETRANSFER" | "DEFAULT";
|
||||
|
||||
export type CreateReverseShareBody = {
|
||||
name?: string;
|
||||
description?: string;
|
||||
expiration?: string;
|
||||
maxFiles?: number | null;
|
||||
maxFileSize?: number | null;
|
||||
allowedFileTypes?: string | null;
|
||||
password?: string;
|
||||
pageLayout?: "WETRANSFER" | "DEFAULT";
|
||||
nameFieldRequired?: FieldRequirement;
|
||||
emailFieldRequired?: FieldRequirement;
|
||||
};
|
||||
|
||||
export type CreateReverseShareResult = AxiosResponse<{
|
||||
reverseShare: {
|
||||
id: string;
|
||||
name: string | null;
|
||||
description: string | null;
|
||||
expiration: string | null;
|
||||
maxFiles: number | null;
|
||||
maxFileSize: number | null;
|
||||
allowedFileTypes: string | null;
|
||||
pageLayout: string;
|
||||
isActive: boolean;
|
||||
hasPassword: boolean;
|
||||
nameFieldRequired: string;
|
||||
emailFieldRequired: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
creatorId: string;
|
||||
files: ReverseShareFile[];
|
||||
};
|
||||
}>;
|
||||
|
||||
export type UpdateReverseShareBody = {
|
||||
id: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
expiration?: string;
|
||||
maxFiles?: number | null;
|
||||
maxFileSize?: number | null;
|
||||
allowedFileTypes?: string | null;
|
||||
password?: string | null;
|
||||
pageLayout?: "WETRANSFER" | "DEFAULT";
|
||||
isActive?: boolean;
|
||||
nameFieldRequired?: FieldRequirement;
|
||||
emailFieldRequired?: FieldRequirement;
|
||||
};
|
||||
|
||||
export type UpdateReverseShareResult = AxiosResponse<{
|
||||
reverseShare: {
|
||||
id: string;
|
||||
name: string | null;
|
||||
description: string | null;
|
||||
expiration: string | null;
|
||||
maxFiles: number | null;
|
||||
maxFileSize: number | null;
|
||||
allowedFileTypes: string | null;
|
||||
pageLayout: string;
|
||||
isActive: boolean;
|
||||
hasPassword: boolean;
|
||||
nameFieldRequired: string;
|
||||
emailFieldRequired: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
creatorId: string;
|
||||
files: ReverseShareFile[];
|
||||
};
|
||||
}>;
|
||||
|
||||
export type ListUserReverseSharesResult = AxiosResponse<{
|
||||
reverseShares: {
|
||||
id: string;
|
||||
name: string | null;
|
||||
description: string | null;
|
||||
expiration: string | null;
|
||||
maxFiles: number | null;
|
||||
maxFileSize: number | null;
|
||||
allowedFileTypes: string | null;
|
||||
pageLayout: string;
|
||||
isActive: boolean;
|
||||
hasPassword: boolean;
|
||||
nameFieldRequired: string;
|
||||
emailFieldRequired: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
creatorId: string;
|
||||
files: ReverseShareFile[];
|
||||
alias?: {
|
||||
id: string;
|
||||
alias: string;
|
||||
reverseShareId: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
} | null;
|
||||
}[];
|
||||
}>;
|
||||
|
||||
export type GetReverseShareResult = AxiosResponse<{
|
||||
reverseShare: {
|
||||
id: string;
|
||||
name: string | null;
|
||||
description: string | null;
|
||||
expiration: string | null;
|
||||
maxFiles: number | null;
|
||||
maxFileSize: number | null;
|
||||
allowedFileTypes: string | null;
|
||||
pageLayout: string;
|
||||
isActive: boolean;
|
||||
hasPassword: boolean;
|
||||
nameFieldRequired: string;
|
||||
emailFieldRequired: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
creatorId: string;
|
||||
files: ReverseShareFile[];
|
||||
alias?: {
|
||||
id: string;
|
||||
alias: string;
|
||||
reverseShareId: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
} | null;
|
||||
};
|
||||
}>;
|
||||
|
||||
export type DeleteReverseShareResult = AxiosResponse<{
|
||||
reverseShare: {
|
||||
id: string;
|
||||
name: string | null;
|
||||
description: string | null;
|
||||
expiration: string | null;
|
||||
maxFiles: number | null;
|
||||
maxFileSize: number | null;
|
||||
allowedFileTypes: string | null;
|
||||
pageLayout: string;
|
||||
isActive: boolean;
|
||||
hasPassword: boolean;
|
||||
nameFieldRequired: string;
|
||||
emailFieldRequired: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
creatorId: string;
|
||||
files: ReverseShareFile[];
|
||||
};
|
||||
}>;
|
||||
|
||||
export type GetReverseShareForUploadParams = {
|
||||
password?: string;
|
||||
};
|
||||
|
||||
export type GetReverseShareForUploadResult = AxiosResponse<{
|
||||
reverseShare: {
|
||||
id: string;
|
||||
name: string | null;
|
||||
description: string | null;
|
||||
maxFiles: number | null;
|
||||
maxFileSize: number | null;
|
||||
allowedFileTypes: string | null;
|
||||
pageLayout: string;
|
||||
hasPassword: boolean;
|
||||
currentFileCount: number;
|
||||
nameFieldRequired: string;
|
||||
emailFieldRequired: string;
|
||||
};
|
||||
}>;
|
||||
|
||||
export type UpdateReverseSharePasswordBody = {
|
||||
password: string | null;
|
||||
};
|
||||
|
||||
export type UpdateReverseSharePasswordResult = AxiosResponse<{
|
||||
reverseShare: {
|
||||
id: string;
|
||||
name: string | null;
|
||||
description: string | null;
|
||||
expiration: string | null;
|
||||
maxFiles: number | null;
|
||||
maxFileSize: number | null;
|
||||
allowedFileTypes: string | null;
|
||||
pageLayout: string;
|
||||
isActive: boolean;
|
||||
hasPassword: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
creatorId: string;
|
||||
files: ReverseShareFile[];
|
||||
};
|
||||
}>;
|
||||
|
||||
export type GetPresignedUrlBody = {
|
||||
objectName: string;
|
||||
};
|
||||
|
||||
export type GetPresignedUrlResult = AxiosResponse<{
|
||||
url: string;
|
||||
expiresIn: number;
|
||||
}>;
|
||||
|
||||
export type RegisterFileUploadBody = {
|
||||
name: string;
|
||||
description?: string;
|
||||
extension: string;
|
||||
size: number;
|
||||
objectName: string;
|
||||
uploaderEmail?: string;
|
||||
uploaderName?: string;
|
||||
};
|
||||
|
||||
export type RegisterFileUploadParams = {
|
||||
password?: string;
|
||||
};
|
||||
|
||||
export type RegisterFileUploadResult = AxiosResponse<{
|
||||
file: ReverseShareFile;
|
||||
}>;
|
||||
|
||||
export type CheckReverseSharePasswordBody = {
|
||||
password: string;
|
||||
};
|
||||
|
||||
export type CheckReverseSharePasswordResult = AxiosResponse<{
|
||||
valid: boolean;
|
||||
}>;
|
||||
|
||||
export type DownloadReverseShareFileResult = AxiosResponse<{
|
||||
url: string;
|
||||
expiresIn: number;
|
||||
}>;
|
||||
|
||||
export type DeleteReverseShareFileResult = AxiosResponse<{
|
||||
file: ReverseShareFile;
|
||||
}>;
|
||||
|
||||
export type ReverseShareFile = {
|
||||
// Base interfaces that are reused across different operations
|
||||
export interface ReverseShareFile {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
@@ -247,51 +16,194 @@ export type ReverseShareFile = {
|
||||
uploaderName: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type ActivateReverseShareResult = AxiosResponse<{
|
||||
reverseShare: {
|
||||
id: string;
|
||||
name: string | null;
|
||||
description: string | null;
|
||||
expiration: string | null;
|
||||
maxFiles: number | null;
|
||||
maxFileSize: number | null;
|
||||
allowedFileTypes: string | null;
|
||||
pageLayout: string;
|
||||
isActive: boolean;
|
||||
hasPassword: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
creatorId: string;
|
||||
files: ReverseShareFile[];
|
||||
};
|
||||
}>;
|
||||
export interface ReverseShareAlias {
|
||||
id: string;
|
||||
alias: string;
|
||||
reverseShareId: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export type DeactivateReverseShareResult = AxiosResponse<{
|
||||
reverseShare: {
|
||||
id: string;
|
||||
name: string | null;
|
||||
description: string | null;
|
||||
expiration: string | null;
|
||||
maxFiles: number | null;
|
||||
maxFileSize: number | null;
|
||||
allowedFileTypes: string | null;
|
||||
pageLayout: string;
|
||||
isActive: boolean;
|
||||
hasPassword: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
creatorId: string;
|
||||
files: ReverseShareFile[];
|
||||
};
|
||||
}>;
|
||||
export interface BaseReverseShare {
|
||||
id: string;
|
||||
name: string | null;
|
||||
description: string | null;
|
||||
expiration: string | null;
|
||||
maxFiles: number | null;
|
||||
maxFileSize: number | null;
|
||||
allowedFileTypes: string | null;
|
||||
pageLayout: string;
|
||||
isActive: boolean;
|
||||
hasPassword: boolean;
|
||||
nameFieldRequired: string;
|
||||
emailFieldRequired: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
creatorId: string;
|
||||
files: ReverseShareFile[];
|
||||
}
|
||||
|
||||
export type UpdateReverseShareFileBody = {
|
||||
export interface ReverseShareWithAlias extends BaseReverseShare {
|
||||
alias?: ReverseShareAlias | null;
|
||||
}
|
||||
|
||||
export interface ReverseShareForUpload {
|
||||
id: string;
|
||||
name: string | null;
|
||||
description: string | null;
|
||||
maxFiles: number | null;
|
||||
maxFileSize: number | null;
|
||||
allowedFileTypes: string | null;
|
||||
pageLayout: string;
|
||||
hasPassword: boolean;
|
||||
currentFileCount: number;
|
||||
nameFieldRequired: string;
|
||||
emailFieldRequired: string;
|
||||
}
|
||||
|
||||
// Response interfaces using base types
|
||||
export interface CreateReverseShare201 {
|
||||
reverseShare: BaseReverseShare;
|
||||
}
|
||||
|
||||
export interface UpdateReverseShare200 {
|
||||
reverseShare: BaseReverseShare;
|
||||
}
|
||||
|
||||
export interface ListUserReverseShares200 {
|
||||
reverseShares: ReverseShareWithAlias[];
|
||||
}
|
||||
|
||||
export interface GetReverseShare200 {
|
||||
reverseShare: ReverseShareWithAlias;
|
||||
}
|
||||
|
||||
export interface DeleteReverseShare200 {
|
||||
reverseShare: BaseReverseShare;
|
||||
}
|
||||
|
||||
export interface GetReverseShareForUpload200 {
|
||||
reverseShare: ReverseShareForUpload;
|
||||
}
|
||||
|
||||
export interface UpdateReverseSharePassword200 {
|
||||
reverseShare: BaseReverseShare;
|
||||
}
|
||||
|
||||
export interface GetPresignedUrl200 {
|
||||
url: string;
|
||||
expiresIn: number;
|
||||
}
|
||||
|
||||
export interface RegisterFileUpload201 {
|
||||
file: ReverseShareFile;
|
||||
}
|
||||
|
||||
export interface CheckReverseSharePassword200 {
|
||||
valid: boolean;
|
||||
}
|
||||
|
||||
export interface DownloadReverseShareFile200 {
|
||||
url: string;
|
||||
expiresIn: number;
|
||||
}
|
||||
|
||||
export interface DeleteReverseShareFile200 {
|
||||
file: ReverseShareFile;
|
||||
}
|
||||
|
||||
export interface ActivateReverseShare200 {
|
||||
reverseShare: BaseReverseShare;
|
||||
}
|
||||
|
||||
export interface DeactivateReverseShare200 {
|
||||
reverseShare: BaseReverseShare;
|
||||
}
|
||||
|
||||
export interface UpdateReverseShareFile200 {
|
||||
file: ReverseShareFile;
|
||||
}
|
||||
|
||||
// Request body interfaces
|
||||
export interface CreateReverseShareBody {
|
||||
name?: string;
|
||||
description?: string;
|
||||
expiration?: string;
|
||||
maxFiles?: number | null;
|
||||
maxFileSize?: number | null;
|
||||
allowedFileTypes?: string | null;
|
||||
password?: string;
|
||||
pageLayout?: PageLayout;
|
||||
nameFieldRequired?: FieldRequirement;
|
||||
emailFieldRequired?: FieldRequirement;
|
||||
}
|
||||
|
||||
export interface UpdateReverseShareBody {
|
||||
id: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
expiration?: string;
|
||||
maxFiles?: number | null;
|
||||
maxFileSize?: number | null;
|
||||
allowedFileTypes?: string | null;
|
||||
password?: string | null;
|
||||
pageLayout?: PageLayout;
|
||||
isActive?: boolean;
|
||||
nameFieldRequired?: FieldRequirement;
|
||||
emailFieldRequired?: FieldRequirement;
|
||||
}
|
||||
|
||||
export interface UpdateReverseSharePasswordBody {
|
||||
password: string | null;
|
||||
}
|
||||
|
||||
export interface GetPresignedUrlBody {
|
||||
objectName: string;
|
||||
}
|
||||
|
||||
export interface RegisterFileUploadBody {
|
||||
name: string;
|
||||
description?: string;
|
||||
extension: string;
|
||||
size: number;
|
||||
objectName: string;
|
||||
uploaderEmail?: string;
|
||||
uploaderName?: string;
|
||||
}
|
||||
|
||||
export interface CheckReverseSharePasswordBody {
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface UpdateReverseShareFileBody {
|
||||
name?: string;
|
||||
description?: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
export type UpdateReverseShareFileResult = AxiosResponse<{
|
||||
file: ReverseShareFile;
|
||||
}>;
|
||||
// Query parameter interfaces
|
||||
export interface GetReverseShareForUploadParams {
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export interface RegisterFileUploadParams {
|
||||
password?: string;
|
||||
}
|
||||
|
||||
// Axios response types
|
||||
export type CreateReverseShareResult = AxiosResponse<CreateReverseShare201>;
|
||||
export type UpdateReverseShareResult = AxiosResponse<UpdateReverseShare200>;
|
||||
export type ListUserReverseSharesResult = AxiosResponse<ListUserReverseShares200>;
|
||||
export type GetReverseShareResult = AxiosResponse<GetReverseShare200>;
|
||||
export type DeleteReverseShareResult = AxiosResponse<DeleteReverseShare200>;
|
||||
export type GetReverseShareForUploadResult = AxiosResponse<GetReverseShareForUpload200>;
|
||||
export type UpdateReverseSharePasswordResult = AxiosResponse<UpdateReverseSharePassword200>;
|
||||
export type GetPresignedUrlResult = AxiosResponse<GetPresignedUrl200>;
|
||||
export type RegisterFileUploadResult = AxiosResponse<RegisterFileUpload201>;
|
||||
export type CheckReverseSharePasswordResult = AxiosResponse<CheckReverseSharePassword200>;
|
||||
export type DownloadReverseShareFileResult = AxiosResponse<DownloadReverseShareFile200>;
|
||||
export type DeleteReverseShareFileResult = AxiosResponse<DeleteReverseShareFile200>;
|
||||
export type ActivateReverseShareResult = AxiosResponse<ActivateReverseShare200>;
|
||||
export type DeactivateReverseShareResult = AxiosResponse<DeactivateReverseShare200>;
|
||||
export type UpdateReverseShareFileResult = AxiosResponse<UpdateReverseShareFile200>;
|
||||
|
@@ -1,32 +1,179 @@
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
import type {
|
||||
AddFiles200,
|
||||
AddFilesBody,
|
||||
AddRecipients200,
|
||||
AddRecipientsBody,
|
||||
CreateShare201,
|
||||
CreateShareAlias200,
|
||||
CreateShareAliasBody,
|
||||
CreateShareBody,
|
||||
DeleteShare200,
|
||||
GetShare200,
|
||||
GetShareByAlias200,
|
||||
GetShareByAliasParams,
|
||||
GetShareParams,
|
||||
ListUserShares200,
|
||||
NotifyRecipients200,
|
||||
NotifyRecipientsBody,
|
||||
RemoveFiles200,
|
||||
RemoveFilesBody,
|
||||
RemoveRecipients200,
|
||||
RemoveRecipientsBody,
|
||||
UpdateShare200,
|
||||
UpdateShareBody,
|
||||
UpdateSharePassword200,
|
||||
UpdateSharePasswordBody,
|
||||
} from "../../models";
|
||||
export type ShareAlias = {
|
||||
id: string;
|
||||
alias: string;
|
||||
shareId: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
} | null;
|
||||
|
||||
export interface ShareFile {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
extension: string;
|
||||
size: string;
|
||||
objectName: string;
|
||||
userId: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface ShareRecipient {
|
||||
id: string;
|
||||
email: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface ShareSecurity {
|
||||
maxViews: number | null;
|
||||
hasPassword: boolean;
|
||||
}
|
||||
|
||||
// Full share object used in most responses
|
||||
export interface Share {
|
||||
id: string;
|
||||
name: string | null;
|
||||
description: string | null;
|
||||
expiration: string | null;
|
||||
views: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
creatorId: string;
|
||||
security: ShareSecurity;
|
||||
files: ShareFile[];
|
||||
recipients: ShareRecipient[];
|
||||
alias: ShareAlias;
|
||||
}
|
||||
|
||||
// Simplified share object for specific operations
|
||||
export interface SimpleShare {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
}
|
||||
|
||||
// Response types using base types
|
||||
export interface CreateShare201 {
|
||||
share: Share;
|
||||
}
|
||||
|
||||
export interface UpdateShare200 {
|
||||
share: Share;
|
||||
}
|
||||
|
||||
export interface UpdateSharePassword200 {
|
||||
share: Share;
|
||||
}
|
||||
|
||||
export interface GetShare200 {
|
||||
share: Share;
|
||||
}
|
||||
|
||||
export interface GetShareByAlias200 {
|
||||
share: Share;
|
||||
}
|
||||
|
||||
export interface DeleteShare200 {
|
||||
share: Share;
|
||||
}
|
||||
|
||||
export interface RemoveRecipients200 {
|
||||
share: Share;
|
||||
}
|
||||
|
||||
export interface RemoveFiles200 {
|
||||
share: Share;
|
||||
}
|
||||
|
||||
export interface AddRecipients200 {
|
||||
share: Share;
|
||||
}
|
||||
|
||||
export interface AddFiles200 {
|
||||
share: SimpleShare;
|
||||
}
|
||||
|
||||
export interface ListUserShares200 {
|
||||
shares: Share[];
|
||||
}
|
||||
|
||||
export interface CreateShareAlias200 {
|
||||
alias: {
|
||||
id: string;
|
||||
alias: string;
|
||||
shareId: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface NotifyRecipients200 {
|
||||
message: string;
|
||||
notifiedRecipients: string[];
|
||||
}
|
||||
|
||||
// Request body types
|
||||
export interface CreateShareBody {
|
||||
name?: string;
|
||||
description?: string;
|
||||
expiration?: string;
|
||||
files: string[];
|
||||
password?: string;
|
||||
maxViews?: number | null;
|
||||
recipients?: string[];
|
||||
}
|
||||
|
||||
export interface UpdateShareBody {
|
||||
id: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
expiration?: string;
|
||||
password?: string;
|
||||
maxViews?: number | null;
|
||||
recipients?: string[];
|
||||
}
|
||||
|
||||
export interface UpdateSharePasswordBody {
|
||||
password: string | null;
|
||||
}
|
||||
|
||||
export interface AddFilesBody {
|
||||
files: string[];
|
||||
}
|
||||
|
||||
export interface RemoveFilesBody {
|
||||
files: string[];
|
||||
}
|
||||
|
||||
export interface AddRecipientsBody {
|
||||
emails: string[];
|
||||
}
|
||||
|
||||
export interface RemoveRecipientsBody {
|
||||
emails: string[];
|
||||
}
|
||||
|
||||
export interface CreateShareAliasBody {
|
||||
alias: string;
|
||||
}
|
||||
|
||||
export interface NotifyRecipientsBody {
|
||||
shareLink: string;
|
||||
}
|
||||
|
||||
// Query parameter types
|
||||
export interface GetShareParams {
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export interface GetShareByAliasParams {
|
||||
password?: string;
|
||||
}
|
||||
|
||||
// Axios response types
|
||||
export type CreateShareResult = AxiosResponse<CreateShare201>;
|
||||
export type UpdateShareResult = AxiosResponse<UpdateShare200>;
|
||||
export type ListUserSharesResult = AxiosResponse<ListUserShares200>;
|
||||
@@ -40,17 +187,3 @@ export type RemoveRecipientsResult = AxiosResponse<RemoveRecipients200>;
|
||||
export type CreateShareAliasResult = AxiosResponse<CreateShareAlias200>;
|
||||
export type GetShareByAliasResult = AxiosResponse<GetShareByAlias200>;
|
||||
export type NotifyRecipientsResult = AxiosResponse<NotifyRecipients200>;
|
||||
|
||||
export type {
|
||||
CreateShareBody,
|
||||
UpdateShareBody,
|
||||
GetShareParams,
|
||||
UpdateSharePasswordBody,
|
||||
AddFilesBody,
|
||||
RemoveFilesBody,
|
||||
AddRecipientsBody,
|
||||
RemoveRecipientsBody,
|
||||
CreateShareAliasBody,
|
||||
GetShareByAliasParams,
|
||||
NotifyRecipientsBody,
|
||||
};
|
||||
|
@@ -1,24 +1,72 @@
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
import type {
|
||||
ActivateUser200,
|
||||
DeactivateUser200,
|
||||
DeleteUser200,
|
||||
GetUserById200,
|
||||
ListUsers200Item,
|
||||
RegisterUser201,
|
||||
RegisterUserBody,
|
||||
RemoveAvatar200,
|
||||
UpdateUser200,
|
||||
UpdateUserBody,
|
||||
UpdateUserImage200,
|
||||
UpdateUserImageBody,
|
||||
UploadAvatar200,
|
||||
UploadAvatarBody,
|
||||
} from "../../models";
|
||||
// Base interface that is reused across all user operations
|
||||
export interface User {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
username: string;
|
||||
email: string;
|
||||
image: string | null;
|
||||
isAdmin: boolean;
|
||||
isActive: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
// Common response patterns
|
||||
export interface UserWithMessageResponse {
|
||||
user: User;
|
||||
message: string;
|
||||
}
|
||||
|
||||
// Response interfaces using base User type
|
||||
export interface ListUsers200 {
|
||||
users: User[];
|
||||
}
|
||||
|
||||
// Request body interfaces
|
||||
export interface RegisterUserBody {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
username: string;
|
||||
email: string;
|
||||
image?: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface UpdateUserBody {
|
||||
id: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
username?: string;
|
||||
email?: string;
|
||||
image?: string;
|
||||
password?: string;
|
||||
isAdmin?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateUserImageBody {
|
||||
image: string;
|
||||
}
|
||||
|
||||
export interface UploadAvatarBody {
|
||||
file?: unknown;
|
||||
}
|
||||
|
||||
export type ActivateUser200 = User;
|
||||
export type DeactivateUser200 = User;
|
||||
export type DeleteUser200 = User;
|
||||
export type GetUserById200 = User;
|
||||
export type UpdateUser200 = User;
|
||||
export type RemoveAvatar200 = User;
|
||||
export type UpdateUserImage200 = User;
|
||||
export type UploadAvatar200 = User;
|
||||
export type RegisterUser201 = UserWithMessageResponse;
|
||||
|
||||
// Axios response types
|
||||
export type RegisterUserResult = AxiosResponse<RegisterUser201>;
|
||||
export type ListUsersResult = AxiosResponse<ListUsers200Item[]>;
|
||||
export type ListUsersResult = AxiosResponse<User[]>;
|
||||
export type UpdateUserResult = AxiosResponse<UpdateUser200>;
|
||||
export type GetUserByIdResult = AxiosResponse<GetUserById200>;
|
||||
export type DeleteUserResult = AxiosResponse<DeleteUser200>;
|
||||
@@ -27,5 +75,3 @@ export type DeactivateUserResult = AxiosResponse<DeactivateUser200>;
|
||||
export type UpdateUserImageResult = AxiosResponse<UpdateUserImage200>;
|
||||
export type UploadAvatarResult = AxiosResponse<UploadAvatar200>;
|
||||
export type RemoveAvatarResult = AxiosResponse<RemoveAvatar200>;
|
||||
|
||||
export type { RegisterUserBody, UpdateUserBody, UpdateUserImageBody, UploadAvatarBody };
|
||||
|
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Generated by orval v7.5.0 🍺
|
||||
* Do not edit manually.
|
||||
* 🌴 Palmr. API
|
||||
* API documentation for Palmr file sharing system
|
||||
* OpenAPI spec version: 1.0.0
|
||||
*/
|
||||
|
||||
export type ActivateUser200 = {
|
||||
/** User ID */
|
||||
id: string;
|
||||
/** User first name */
|
||||
firstName: string;
|
||||
/** User last name */
|
||||
lastName: string;
|
||||
/** User username */
|
||||
username: string;
|
||||
/** User email */
|
||||
email: string;
|
||||
/**
|
||||
* User profile image URL
|
||||
* @nullable
|
||||
*/
|
||||
image: string | null;
|
||||
/** User is admin */
|
||||
isAdmin: boolean;
|
||||
/** User is active */
|
||||
isActive: boolean;
|
||||
/** User creation date */
|
||||
createdAt: string;
|
||||
/** User last update date */
|
||||
updatedAt: string;
|
||||
};
|
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* Generated by orval v7.5.0 🍺
|
||||
* Do not edit manually.
|
||||
* 🌴 Palmr. API
|
||||
* API documentation for Palmr file sharing system
|
||||
* OpenAPI spec version: 1.0.0
|
||||
*/
|
||||
|
||||
export type ActivateUser400 = {
|
||||
/** Error message */
|
||||
error: string;
|
||||
};
|
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* Generated by orval v7.5.0 🍺
|
||||
* Do not edit manually.
|
||||
* 🌴 Palmr. API
|
||||
* API documentation for Palmr file sharing system
|
||||
* OpenAPI spec version: 1.0.0
|
||||
*/
|
||||
|
||||
export type ActivateUser401 = {
|
||||
/** Error message */
|
||||
error: string;
|
||||
};
|
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* Generated by orval v7.5.0 🍺
|
||||
* Do not edit manually.
|
||||
* 🌴 Palmr. API
|
||||
* API documentation for Palmr file sharing system
|
||||
* OpenAPI spec version: 1.0.0
|
||||
*/
|
||||
|
||||
export type ActivateUser403 = {
|
||||
/** Error message */
|
||||
error: string;
|
||||
};
|
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* Generated by orval v7.5.0 🍺
|
||||
* Do not edit manually.
|
||||
* 🌴 Palmr. API
|
||||
* API documentation for Palmr file sharing system
|
||||
* OpenAPI spec version: 1.0.0
|
||||
*/
|
||||
import type { AddFiles200Share } from "./addFiles200Share";
|
||||
|
||||
export type AddFiles200 = {
|
||||
share: AddFiles200Share;
|
||||
};
|
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* Generated by orval v7.5.0 🍺
|
||||
* Do not edit manually.
|
||||
* 🌴 Palmr. API
|
||||
* API documentation for Palmr file sharing system
|
||||
* OpenAPI spec version: 1.0.0
|
||||
*/
|
||||
import type { AddFiles200ShareAlias } from "./addFiles200ShareAlias";
|
||||
import type { AddFiles200ShareFilesItem } from "./addFiles200ShareFilesItem";
|
||||
import type { AddFiles200ShareRecipientsItem } from "./addFiles200ShareRecipientsItem";
|
||||
import type { AddFiles200ShareSecurity } from "./addFiles200ShareSecurity";
|
||||
|
||||
export type AddFiles200Share = {
|
||||
/** The share ID */
|
||||
id: string;
|
||||
/**
|
||||
* The share name
|
||||
* @nullable
|
||||
*/
|
||||
name: string | null;
|
||||
/**
|
||||
* The share description
|
||||
* @nullable
|
||||
*/
|
||||
description: string | null;
|
||||
/**
|
||||
* The share expiration date
|
||||
* @nullable
|
||||
*/
|
||||
expiration: string | null;
|
||||
/** The number of views */
|
||||
views: number;
|
||||
/** The share creation date */
|
||||
createdAt: string;
|
||||
/** The share update date */
|
||||
updatedAt: string;
|
||||
/** The creator ID */
|
||||
creatorId: string;
|
||||
security: AddFiles200ShareSecurity;
|
||||
files: AddFiles200ShareFilesItem[];
|
||||
recipients: AddFiles200ShareRecipientsItem[];
|
||||
/** @nullable */
|
||||
alias: AddFiles200ShareAlias;
|
||||
};
|
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* Generated by orval v7.5.0 🍺
|
||||
* Do not edit manually.
|
||||
* 🌴 Palmr. API
|
||||
* API documentation for Palmr file sharing system
|
||||
* OpenAPI spec version: 1.0.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type AddFiles200ShareAlias = {
|
||||
id: string;
|
||||
alias: string;
|
||||
shareId: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
} | null;
|
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Generated by orval v7.5.0 🍺
|
||||
* Do not edit manually.
|
||||
* 🌴 Palmr. API
|
||||
* API documentation for Palmr file sharing system
|
||||
* OpenAPI spec version: 1.0.0
|
||||
*/
|
||||
|
||||
export type AddFiles200ShareFilesItem = {
|
||||
/** The file ID */
|
||||
id: string;
|
||||
/** The file name */
|
||||
name: string;
|
||||
/**
|
||||
* The file description
|
||||
* @nullable
|
||||
*/
|
||||
description: string | null;
|
||||
/** The file extension */
|
||||
extension: string;
|
||||
/** The file size */
|
||||
size: string;
|
||||
/** The file object name */
|
||||
objectName: string;
|
||||
/** The user ID */
|
||||
userId: string;
|
||||
/** The file creation date */
|
||||
createdAt: string;
|
||||
/** The file update date */
|
||||
updatedAt: string;
|
||||
};
|
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* Generated by orval v7.5.0 🍺
|
||||
* Do not edit manually.
|
||||
* 🌴 Palmr. API
|
||||
* API documentation for Palmr file sharing system
|
||||
* OpenAPI spec version: 1.0.0
|
||||
*/
|
||||
|
||||
export type AddFiles200ShareRecipientsItem = {
|
||||
/** The recipient ID */
|
||||
id: string;
|
||||
/** The recipient email */
|
||||
email: string;
|
||||
/** The recipient creation date */
|
||||
createdAt: string;
|
||||
/** The recipient update date */
|
||||
updatedAt: string;
|
||||
};
|
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Generated by orval v7.5.0 🍺
|
||||
* Do not edit manually.
|
||||
* 🌴 Palmr. API
|
||||
* API documentation for Palmr file sharing system
|
||||
* OpenAPI spec version: 1.0.0
|
||||
*/
|
||||
|
||||
export type AddFiles200ShareSecurity = {
|
||||
/**
|
||||
* The maximum number of views
|
||||
* @nullable
|
||||
*/
|
||||
maxViews: number | null;
|
||||
/** Whether the share has a password */
|
||||
hasPassword: boolean;
|
||||
};
|
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* Generated by orval v7.5.0 🍺
|
||||
* Do not edit manually.
|
||||
* 🌴 Palmr. API
|
||||
* API documentation for Palmr file sharing system
|
||||
* OpenAPI spec version: 1.0.0
|
||||
*/
|
||||
|
||||
export type AddFiles400 = {
|
||||
/** Error message */
|
||||
error: string;
|
||||
};
|
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* Generated by orval v7.5.0 🍺
|
||||
* Do not edit manually.
|
||||
* 🌴 Palmr. API
|
||||
* API documentation for Palmr file sharing system
|
||||
* OpenAPI spec version: 1.0.0
|
||||
*/
|
||||
|
||||
export type AddFiles401 = {
|
||||
/** Error message */
|
||||
error: string;
|
||||
};
|
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* Generated by orval v7.5.0 🍺
|
||||
* Do not edit manually.
|
||||
* 🌴 Palmr. API
|
||||
* API documentation for Palmr file sharing system
|
||||
* OpenAPI spec version: 1.0.0
|
||||
*/
|
||||
|
||||
export type AddFiles404 = {
|
||||
/** Error message */
|
||||
error: string;
|
||||
};
|
@@ -1,11 +0,0 @@
|
||||
/**
|
||||
* Generated by orval v7.5.0 🍺
|
||||
* Do not edit manually.
|
||||
* 🌴 Palmr. API
|
||||
* API documentation for Palmr file sharing system
|
||||
* OpenAPI spec version: 1.0.0
|
||||
*/
|
||||
|
||||
export type AddFilesBody = {
|
||||
files: string[];
|
||||
};
|
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* Generated by orval v7.5.0 🍺
|
||||
* Do not edit manually.
|
||||
* 🌴 Palmr. API
|
||||
* API documentation for Palmr file sharing system
|
||||
* OpenAPI spec version: 1.0.0
|
||||
*/
|
||||
import type { AddRecipients200Share } from "./addRecipients200Share";
|
||||
|
||||
export type AddRecipients200 = {
|
||||
share: AddRecipients200Share;
|
||||
};
|
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* Generated by orval v7.5.0 🍺
|
||||
* Do not edit manually.
|
||||
* 🌴 Palmr. API
|
||||
* API documentation for Palmr file sharing system
|
||||
* OpenAPI spec version: 1.0.0
|
||||
*/
|
||||
import type { AddRecipients200ShareAlias } from "./addRecipients200ShareAlias";
|
||||
import type { AddRecipients200ShareFilesItem } from "./addRecipients200ShareFilesItem";
|
||||
import type { AddRecipients200ShareRecipientsItem } from "./addRecipients200ShareRecipientsItem";
|
||||
import type { AddRecipients200ShareSecurity } from "./addRecipients200ShareSecurity";
|
||||
|
||||
export type AddRecipients200Share = {
|
||||
/** The share ID */
|
||||
id: string;
|
||||
/**
|
||||
* The share name
|
||||
* @nullable
|
||||
*/
|
||||
name: string | null;
|
||||
/**
|
||||
* The share description
|
||||
* @nullable
|
||||
*/
|
||||
description: string | null;
|
||||
/**
|
||||
* The share expiration date
|
||||
* @nullable
|
||||
*/
|
||||
expiration: string | null;
|
||||
/** The number of views */
|
||||
views: number;
|
||||
/** The share creation date */
|
||||
createdAt: string;
|
||||
/** The share update date */
|
||||
updatedAt: string;
|
||||
/** The creator ID */
|
||||
creatorId: string;
|
||||
security: AddRecipients200ShareSecurity;
|
||||
files: AddRecipients200ShareFilesItem[];
|
||||
recipients: AddRecipients200ShareRecipientsItem[];
|
||||
/** @nullable */
|
||||
alias: AddRecipients200ShareAlias;
|
||||
};
|
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* Generated by orval v7.5.0 🍺
|
||||
* Do not edit manually.
|
||||
* 🌴 Palmr. API
|
||||
* API documentation for Palmr file sharing system
|
||||
* OpenAPI spec version: 1.0.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type AddRecipients200ShareAlias = {
|
||||
id: string;
|
||||
alias: string;
|
||||
shareId: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
} | null;
|
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Generated by orval v7.5.0 🍺
|
||||
* Do not edit manually.
|
||||
* 🌴 Palmr. API
|
||||
* API documentation for Palmr file sharing system
|
||||
* OpenAPI spec version: 1.0.0
|
||||
*/
|
||||
|
||||
export type AddRecipients200ShareFilesItem = {
|
||||
/** The file ID */
|
||||
id: string;
|
||||
/** The file name */
|
||||
name: string;
|
||||
/**
|
||||
* The file description
|
||||
* @nullable
|
||||
*/
|
||||
description: string | null;
|
||||
/** The file extension */
|
||||
extension: string;
|
||||
/** The file size */
|
||||
size: string;
|
||||
/** The file object name */
|
||||
objectName: string;
|
||||
/** The user ID */
|
||||
userId: string;
|
||||
/** The file creation date */
|
||||
createdAt: string;
|
||||
/** The file update date */
|
||||
updatedAt: string;
|
||||
};
|
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* Generated by orval v7.5.0 🍺
|
||||
* Do not edit manually.
|
||||
* 🌴 Palmr. API
|
||||
* API documentation for Palmr file sharing system
|
||||
* OpenAPI spec version: 1.0.0
|
||||
*/
|
||||
|
||||
export type AddRecipients200ShareRecipientsItem = {
|
||||
/** The recipient ID */
|
||||
id: string;
|
||||
/** The recipient email */
|
||||
email: string;
|
||||
/** The recipient creation date */
|
||||
createdAt: string;
|
||||
/** The recipient update date */
|
||||
updatedAt: string;
|
||||
};
|
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Generated by orval v7.5.0 🍺
|
||||
* Do not edit manually.
|
||||
* 🌴 Palmr. API
|
||||
* API documentation for Palmr file sharing system
|
||||
* OpenAPI spec version: 1.0.0
|
||||
*/
|
||||
|
||||
export type AddRecipients200ShareSecurity = {
|
||||
/**
|
||||
* The maximum number of views
|
||||
* @nullable
|
||||
*/
|
||||
maxViews: number | null;
|
||||
/** Whether the share has a password */
|
||||
hasPassword: boolean;
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user