mirror of
https://github.com/kyantech/Palmr.git
synced 2025-11-01 20:43:39 +00:00
refactor(web): restructure and clean up project files
Restructured the project by moving and deleting unused files, updating configurations, and organizing code for better maintainability. This includes removing deprecated models, updating environment settings, and consolidating utility functions.
This commit is contained in:
41
apps/app/.gitignore
vendored
41
apps/app/.gitignore
vendored
@@ -1,41 +0,0 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"importOrder": [
|
||||
"^(react/(.*)$)|^(react$)",
|
||||
"^(next/(.*)$)|^(next$)",
|
||||
"<THIRD_PARTY_MODULES>",
|
||||
"",
|
||||
"^@/(.*)$",
|
||||
"^[./]"
|
||||
],
|
||||
"importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy"],
|
||||
"plugins": ["@ianvs/prettier-plugin-sort-imports", "prettier-plugin-sort-json"],
|
||||
"printWidth": 120,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
# Use the official Node.js image as the base image
|
||||
|
||||
FROM node:18-alpine AS base
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies based on the preferred package manager
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN corepack enable pnpm && pnpm install --frozen-lockfile
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
ENV NODE_ENV=production
|
||||
|
||||
RUN corepack enable pnpm && pnpm run build
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
@@ -1,68 +0,0 @@
|
||||
{
|
||||
"name": "palmr-app-v2",
|
||||
"version": "2.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint \"src/**/*.+(ts|tsx)\"",
|
||||
"lint:fix": "eslint \"src/**/*.+(ts|tsx)\" --fix",
|
||||
"format": "prettier . --write",
|
||||
"format:check": "prettier . --check"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^5.0.1",
|
||||
"@radix-ui/react-aspect-ratio": "^1.1.3",
|
||||
"@radix-ui/react-avatar": "^1.1.4",
|
||||
"@radix-ui/react-dialog": "^1.1.6",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
||||
"@radix-ui/react-label": "^2.1.2",
|
||||
"@radix-ui/react-progress": "^1.1.3",
|
||||
"@radix-ui/react-scroll-area": "^1.2.4",
|
||||
"@radix-ui/react-select": "^2.1.7",
|
||||
"@radix-ui/react-separator": "^1.1.3",
|
||||
"@radix-ui/react-slot": "^1.1.2",
|
||||
"@radix-ui/react-switch": "^1.1.4",
|
||||
"@tabler/icons-react": "^3.31.0",
|
||||
"axios": "^1.8.4",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"framer-motion": "^12.6.3",
|
||||
"lucide-react": "^0.487.0",
|
||||
"nanoid": "^5.1.5",
|
||||
"next": "15.2.4",
|
||||
"next-intl": "^4.0.2",
|
||||
"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-hook-form": "^7.55.0",
|
||||
"sonner": "^2.0.3",
|
||||
"tailwind-merge": "^3.1.0",
|
||||
"tw-animate-css": "^1.2.5",
|
||||
"zod": "^3.24.2",
|
||||
"zustand": "^5.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "3.3.1",
|
||||
"@eslint/js": "9.23.0",
|
||||
"@ianvs/prettier-plugin-sort-imports": "4.4.1",
|
||||
"@tailwindcss/postcss": "4.1.2",
|
||||
"@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",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-plugin-prettier": "5.2.6",
|
||||
"prettier": "3.5.3",
|
||||
"prettier-plugin-sort-json": "4.1.1",
|
||||
"tailwindcss": "4.1.2",
|
||||
"typescript": "5.8.2"
|
||||
}
|
||||
}
|
||||
5198
apps/app/pnpm-lock.yaml
generated
5198
apps/app/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,189 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { IconArrowLeft, IconArrowRight, IconFile } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { addFiles, listFiles, removeFiles } from "@/http/endpoints";
|
||||
|
||||
interface FileSelectorProps {
|
||||
shareId: string;
|
||||
selectedFiles: string[];
|
||||
onSave: (files: string[]) => Promise<void>;
|
||||
}
|
||||
|
||||
export function FileSelector({ shareId, selectedFiles, onSave }: FileSelectorProps) {
|
||||
const t = useTranslations();
|
||||
const [availableFiles, setAvailableFiles] = useState<any[]>([]);
|
||||
const [shareFiles, setShareFiles] = useState<any[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [availableFilter, setAvailableFilter] = useState("");
|
||||
const [shareFilter, setShareFilter] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
loadFiles();
|
||||
}, [shareId, selectedFiles]);
|
||||
|
||||
const loadFiles = 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) {
|
||||
console.error(error);
|
||||
toast.error("Failed to load files");
|
||||
}
|
||||
};
|
||||
|
||||
const moveToShare = (fileId: string) => {
|
||||
const file = availableFiles.find((f) => f.id === fileId);
|
||||
|
||||
if (file) {
|
||||
setShareFiles([...shareFiles, file]);
|
||||
setAvailableFiles(availableFiles.filter((f) => f.id !== fileId));
|
||||
}
|
||||
};
|
||||
|
||||
const removeFromShare = (fileId: string) => {
|
||||
const file = shareFiles.find((f) => f.id === fileId);
|
||||
|
||||
if (file) {
|
||||
setAvailableFiles([...availableFiles, file]);
|
||||
setShareFiles(shareFiles.filter((f) => f.id !== fileId));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const filesToAdd = shareFiles.filter((file) => !selectedFiles.includes(file.id)).map((file) => file.id);
|
||||
|
||||
const filesToRemove = selectedFiles.filter((fileId) => !shareFiles.find((f) => f.id === fileId));
|
||||
|
||||
if (filesToAdd.length > 0) {
|
||||
await addFiles(shareId, { files: filesToAdd });
|
||||
}
|
||||
|
||||
if (filesToRemove.length > 0) {
|
||||
await removeFiles(shareId, { files: filesToRemove });
|
||||
}
|
||||
|
||||
await onSave(shareFiles.map((f) => f.id));
|
||||
toast.success("Files updated successfully");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error("Failed to update files");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const filteredAvailableFiles = availableFiles.filter((file) =>
|
||||
file.name.toLowerCase().includes(availableFilter.toLowerCase())
|
||||
);
|
||||
|
||||
const filteredShareFiles = shareFiles.filter((file) => file.name.toLowerCase().includes(shareFilter.toLowerCase()));
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex gap-4 h-[500px]">
|
||||
<div className="flex-1 border rounded-lg">
|
||||
<div className="p-4 border-b">
|
||||
<h3 className="font-medium">
|
||||
{t("fileSelector.availableFiles", { count: filteredAvailableFiles.length })}
|
||||
</h3>
|
||||
<Input
|
||||
className="mt-2"
|
||||
placeholder={t("fileSelector.searchPlaceholder")}
|
||||
type="search"
|
||||
value={availableFilter}
|
||||
onChange={(e) => setAvailableFilter(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-4 h-[calc(100%-115px)] overflow-y-auto">
|
||||
{filteredAvailableFiles.length === 0 ? (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
{availableFilter ? t("fileSelector.noMatchingFiles") : t("fileSelector.noAvailableFiles")}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-2">
|
||||
{filteredAvailableFiles.map((file) => (
|
||||
<div
|
||||
key={file.id}
|
||||
className="flex items-center justify-between p-3 rounded-lg border border-transparent hover:border-primary-500 cursor-pointer"
|
||||
onClick={() => moveToShare(file.id)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<IconFile className="text-gray-400" size={20} />
|
||||
<span className="truncate max-w-[150px]" title={file.name}>
|
||||
{file.name}
|
||||
</span>
|
||||
</div>
|
||||
<IconArrowRight className="text-gray-400" size={20} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 border rounded-lg">
|
||||
<div className="p-4 border-b">
|
||||
<h3 className="font-medium">{t("fileSelector.shareFiles", { count: filteredShareFiles.length })}</h3>
|
||||
<Input
|
||||
className="mt-2"
|
||||
placeholder={t("fileSelector.searchPlaceholder")}
|
||||
type="search"
|
||||
value={shareFilter}
|
||||
onChange={(e) => setShareFilter(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-4 h-[calc(100%-115px)] overflow-y-auto">
|
||||
{filteredShareFiles.length === 0 ? (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
{shareFilter ? t("fileSelector.noMatchingFiles") : t("fileSelector.noFilesInShare")}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-2">
|
||||
{filteredShareFiles.map((file) => (
|
||||
<div
|
||||
key={file.id}
|
||||
className="flex items-center justify-between p-3 rounded-lg border border-transparent hover:border-primary-500 cursor-pointer"
|
||||
onClick={() => removeFromShare(file.id)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<IconFile className="text-gray-400" size={20} />
|
||||
<span className="truncate max-w-[150px]" title={file.name}>
|
||||
{file.name}
|
||||
</span>
|
||||
</div>
|
||||
<IconArrowLeft className="text-gray-400" size={20} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button variant="default" disabled={isLoading} onClick={handleSave}>
|
||||
{isLoading ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="animate-spin">⠋</div>
|
||||
{t("fileSelector.saveChanges")}
|
||||
</div>
|
||||
) : (
|
||||
t("fileSelector.saveChanges")
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { IconLanguage } from "@tabler/icons-react";
|
||||
import { useLocale } from "next-intl";
|
||||
import { setCookie } from "nookies";
|
||||
import ReactCountryFlag from "react-country-flag";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
|
||||
const languages = {
|
||||
"en-US": "English",
|
||||
"pt-BR": "Português",
|
||||
"fr-FR": "Français",
|
||||
"es-ES": "Español",
|
||||
"de-DE": "Deutsch",
|
||||
"tr-TR": "Türkçe (Turkish)",
|
||||
"ru-RU": "Русский (Russian)",
|
||||
"hi-IN": "हिन्दी (Hindi)",
|
||||
"ar-SA": "العربية (Arabic)",
|
||||
"zh-CN": "中文 (Chinese)",
|
||||
"ja-JP": "日本語 (Japanese)",
|
||||
"ko-KR": "한국어 (Korean)",
|
||||
};
|
||||
|
||||
const COOKIE_LANG_KEY = "NEXT_LOCALE";
|
||||
const COOKIE_MAX_AGE = 365 * 24 * 60 * 60;
|
||||
|
||||
const RTL_LANGUAGES = ["ar-SA"];
|
||||
|
||||
export function LanguageSwitcher() {
|
||||
const locale = useLocale();
|
||||
const router = useRouter();
|
||||
|
||||
const changeLanguage = (fullLocale: string) => {
|
||||
const isRTL = RTL_LANGUAGES.includes(fullLocale);
|
||||
document.documentElement.dir = isRTL ? "rtl" : "ltr";
|
||||
|
||||
setCookie(null, COOKIE_LANG_KEY, fullLocale, {
|
||||
maxAge: COOKIE_MAX_AGE,
|
||||
path: "/",
|
||||
sameSite: "lax",
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
});
|
||||
|
||||
router.refresh();
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-9 w-9 p-0">
|
||||
<IconLanguage className="h-5 w-5" />
|
||||
<span className="sr-only">Change language</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{Object.entries(languages).map(([code, name]) => {
|
||||
const isCurrentLocale = locale === code.split("-")[0];
|
||||
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={code}
|
||||
onClick={() => changeLanguage(code)}
|
||||
className={isCurrentLocale ? "bg-accent" : ""}
|
||||
>
|
||||
<ReactCountryFlag
|
||||
svg
|
||||
countryCode={code.split("-")[1]}
|
||||
style={{
|
||||
marginRight: "8px",
|
||||
width: "1em",
|
||||
height: "1em",
|
||||
}}
|
||||
/>
|
||||
{name}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { IconBell, IconMail, IconPlus, IconTrash } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useShareContext } from "@/contexts/share-context";
|
||||
import { addRecipients, notifyRecipients, removeRecipients } from "@/http/endpoints";
|
||||
|
||||
interface Recipient {
|
||||
id: string;
|
||||
email: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
interface RecipientSelectorProps {
|
||||
shareId: string;
|
||||
selectedRecipients: Recipient[];
|
||||
shareAlias?: string;
|
||||
onSuccess: () => void;
|
||||
}
|
||||
|
||||
export function RecipientSelector({ shareId, selectedRecipients, shareAlias, onSuccess }: RecipientSelectorProps) {
|
||||
const t = useTranslations();
|
||||
const { smtpEnabled } = useShareContext();
|
||||
const [recipients, setRecipients] = useState<string[]>(selectedRecipients?.map((recipient) => recipient.email) || []);
|
||||
const [newRecipient, setNewRecipient] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
setRecipients(selectedRecipients?.map((recipient) => recipient.email) || []);
|
||||
}, [selectedRecipients]);
|
||||
|
||||
const handleAddRecipient = () => {
|
||||
if (newRecipient && !recipients.includes(newRecipient)) {
|
||||
addRecipients(shareId, { emails: [newRecipient] })
|
||||
.then(() => {
|
||||
setRecipients([...recipients, newRecipient]);
|
||||
setNewRecipient("");
|
||||
toast.success(t("recipientSelector.addSuccess"));
|
||||
onSuccess();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error(t("recipientSelector.addError"));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveRecipient = (email: string) => {
|
||||
removeRecipients(shareId, { emails: [email] })
|
||||
.then(() => {
|
||||
setRecipients(recipients.filter((r) => r !== email));
|
||||
toast.success(t("recipientSelector.removeSuccess"));
|
||||
onSuccess();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error(t("recipientSelector.removeError"));
|
||||
});
|
||||
};
|
||||
|
||||
const handleNotifyRecipients = async () => {
|
||||
if (!shareAlias) return;
|
||||
|
||||
const link = `${window.location.origin}/s/${shareAlias}`;
|
||||
const loadingToast = toast.loading(t("recipientSelector.sendingNotifications"));
|
||||
|
||||
try {
|
||||
await notifyRecipients(shareId, { shareLink: link });
|
||||
toast.dismiss(loadingToast);
|
||||
toast.success(t("recipientSelector.notifySuccess"));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.dismiss(loadingToast);
|
||||
toast.error(t("recipientSelector.notifyError"));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 mb-4">
|
||||
<div className="flex gap-2">
|
||||
<div className="relative flex-1">
|
||||
<IconMail className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500 h-4 w-4" />
|
||||
<Input
|
||||
className="pl-9"
|
||||
placeholder={t("recipientSelector.emailPlaceholder")}
|
||||
value={newRecipient}
|
||||
onChange={(e) => setNewRecipient(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && handleAddRecipient()}
|
||||
/>
|
||||
</div>
|
||||
<Button onClick={handleAddRecipient}>
|
||||
<IconPlus className="h-4 w-4" />
|
||||
{t("recipientSelector.add")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="border rounded-lg p-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="font-medium">{t("recipientSelector.recipients", { count: recipients.length })}</h3>
|
||||
{recipients.length > 0 && shareAlias && smtpEnabled === "true" && (
|
||||
<Button variant="outline" size="sm" onClick={handleNotifyRecipients}>
|
||||
<IconBell className="h-4 w-4" />
|
||||
{t("recipientSelector.notifyAll")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="max-h-[400px] overflow-y-auto">
|
||||
<div className="flex flex-col gap-2">
|
||||
{recipients.length === 0 ? (
|
||||
<div className="text-center py-8 text-gray-500">{t("recipientSelector.noRecipients")}</div>
|
||||
) : (
|
||||
recipients.map((email, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center justify-between p-3 bg-secondary rounded-lg hover:bg-secondary/80"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<IconMail className="text-gray-500 h-4 w-4" />
|
||||
<span>{email}</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-destructive hover:text-destructive hover:bg-destructive/10"
|
||||
onClick={() => handleRemoveRecipient(email)}
|
||||
>
|
||||
<IconTrash className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import { ReactNode } from "react";
|
||||
import Link from "next/link";
|
||||
import { IconLayoutDashboard } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { Navbar } from "@/components/layout/navbar";
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/components/ui/breadcrumb";
|
||||
import { DefaultFooter } from "@/components/ui/default-footer";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
interface FileManagerLayoutProps {
|
||||
children: ReactNode;
|
||||
title: string;
|
||||
icon: ReactNode;
|
||||
breadcrumbLabel?: string;
|
||||
showBreadcrumb?: boolean;
|
||||
}
|
||||
|
||||
export function FileManagerLayout({
|
||||
children,
|
||||
title,
|
||||
icon,
|
||||
breadcrumbLabel,
|
||||
showBreadcrumb = true,
|
||||
}: FileManagerLayoutProps) {
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<div className="w-full min-h-screen flex flex-col">
|
||||
<Navbar />
|
||||
<div className="flex-1 max-w-7xl mx-auto w-full p-6 py-8">
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
{icon}
|
||||
<h1 className="text-2xl font-bold">{title}</h1>
|
||||
</div>
|
||||
<Separator />
|
||||
{showBreadcrumb && breadcrumbLabel && (
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink asChild>
|
||||
<Link href="/dashboard" className="flex items-center">
|
||||
<IconLayoutDashboard size={20} className="mr-2" />
|
||||
{t("navigation.dashboard")}
|
||||
</Link>
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<span className="flex items-center gap-2">
|
||||
{icon} {breadcrumbLabel}
|
||||
</span>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
<DefaultFooter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { motion } from "framer-motion";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { BackgroundLights } from "@/components/ui/background-lights";
|
||||
|
||||
export function LoadingScreen() {
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-background">
|
||||
<BackgroundLights />
|
||||
<div className="relative flex flex-col items-center justify-center h-full">
|
||||
<motion.div
|
||||
animate={{ scale: [1, 1.1, 1] }}
|
||||
className="flex flex-col items-center gap-4"
|
||||
transition={{
|
||||
duration: 1.5,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
>
|
||||
<span className="text-xl font-semibold text-primary">{t("common.loading")}</span>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { IconLogout, IconSettings, IconUser, IconUsers } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { LanguageSwitcher } from "@/components/general/language-switcher";
|
||||
import { ModeToggle } from "@/components/general/mode-toggle";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { useAppInfo } from "@/contexts/app-info-context";
|
||||
import { useAuth } from "@/contexts/auth-context";
|
||||
import { logout as logoutAPI } from "@/http/endpoints";
|
||||
|
||||
export function Navbar() {
|
||||
const t = useTranslations();
|
||||
const router = useRouter();
|
||||
const { user, isAdmin, logout } = useAuth();
|
||||
const { appName, appLogo } = useAppInfo();
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
await logoutAPI();
|
||||
logout();
|
||||
router.push("/login");
|
||||
} catch (err) {
|
||||
console.error("Error logging out:", err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-40 w-full border-b border-border/50 bg-background/70 backdrop-blur-sm px-6">
|
||||
<div className="container flex h-16 max-w-screen-xl items-center mx-auto lg:px-6">
|
||||
<div className="flex flex-1 items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Link href="/dashboard" className="flex items-center gap-2 cursor-pointer">
|
||||
{appLogo && <img alt={t("navbar.logoAlt")} className="h-8 w-8 object-contain rounded" src={appLogo} />}
|
||||
<p className="font-bold text-2xl">{appName}</p>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 cursor-pointer">
|
||||
<LanguageSwitcher />
|
||||
<ModeToggle />
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger className="rounded-full">
|
||||
<Avatar className="cursor-pointer h-10 w-10 rounded-full">
|
||||
<AvatarImage src={user?.image as string | undefined} />
|
||||
<AvatarFallback>{user?.firstName?.[0]}</AvatarFallback>
|
||||
</Avatar>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<div className="flex flex-col px-2 py-1.5 gap-0.5">
|
||||
<p className="font-semibold text-sm">
|
||||
{user?.firstName} {user?.lastName}
|
||||
</p>
|
||||
<p className="font-semibold text-xs text-muted-foreground">{user?.email}</p>
|
||||
</div>
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/profile" className="flex items-center gap-2 cursor-pointer">
|
||||
<IconUser className="h-4 w-4" />
|
||||
{t("navbar.profile")}
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
{isAdmin && (
|
||||
<>
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/settings" className="flex items-center gap-2 cursor-pointer">
|
||||
<IconSettings className="h-4 w-4" />
|
||||
{t("navbar.settings")}
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/users-management" className="flex items-center gap-2 cursor-pointer">
|
||||
<IconUsers className="h-4 w-4" />
|
||||
{t("navbar.usersManagement")}
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
className="text-destructive focus:text-destructive cursor-pointer"
|
||||
onClick={handleLogout}
|
||||
>
|
||||
<IconLogout className="h-4 w-4 mr-2 text-destructive" />
|
||||
{t("navbar.logout")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { IconCalendar, IconEye, IconLock, IconShare } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
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 { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { createShare } from "@/http/endpoints";
|
||||
|
||||
interface CreateShareModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
}
|
||||
|
||||
export function CreateShareModal({ isOpen, onClose, onSuccess }: CreateShareModalProps) {
|
||||
const t = useTranslations();
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
password: "",
|
||||
expiresAt: "",
|
||||
isPasswordProtected: false,
|
||||
maxViews: "",
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await createShare({
|
||||
name: formData.name,
|
||||
password: formData.isPasswordProtected ? formData.password : undefined,
|
||||
expiration: formData.expiresAt ? new Date(formData.expiresAt).toISOString() : undefined,
|
||||
maxViews: formData.maxViews ? parseInt(formData.maxViews) : undefined,
|
||||
files: [],
|
||||
});
|
||||
toast.success(t("createShare.success"));
|
||||
onSuccess();
|
||||
onClose();
|
||||
setFormData({
|
||||
name: "",
|
||||
password: "",
|
||||
expiresAt: "",
|
||||
isPasswordProtected: false,
|
||||
maxViews: "",
|
||||
});
|
||||
} catch (error) {
|
||||
toast.error(t("createShare.error"));
|
||||
console.error(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<IconShare size={20} />
|
||||
{t("createShare.title")}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>{t("createShare.nameLabel")}</Label>
|
||||
<Input value={formData.name} onChange={(e) => setFormData({ ...formData, name: e.target.value })} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="flex items-center gap-2">
|
||||
<IconCalendar size={16} />
|
||||
{t("createShare.expirationLabel")}
|
||||
</Label>
|
||||
<Input
|
||||
placeholder={t("createShare.expirationPlaceholder")}
|
||||
type="datetime-local"
|
||||
value={formData.expiresAt}
|
||||
onChange={(e) => setFormData({ ...formData, expiresAt: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="flex items-center gap-2">
|
||||
<IconEye size={16} />
|
||||
{t("createShare.maxViewsLabel")}
|
||||
</Label>
|
||||
<Input
|
||||
min="1"
|
||||
placeholder={t("createShare.maxViewsPlaceholder")}
|
||||
type="number"
|
||||
value={formData.maxViews}
|
||||
onChange={(e) => setFormData({ ...formData, maxViews: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={formData.isPasswordProtected}
|
||||
onCheckedChange={(checked) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
isPasswordProtected: checked,
|
||||
password: "",
|
||||
})
|
||||
}
|
||||
id="password-protection"
|
||||
/>
|
||||
<Label htmlFor="password-protection" className="flex items-center gap-2">
|
||||
<IconLock size={16} />
|
||||
{t("createShare.passwordProtection")}
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
{formData.isPasswordProtected && (
|
||||
<div className="space-y-2">
|
||||
<Label>{t("createShare.passwordLabel")}</Label>
|
||||
<Input
|
||||
type="password"
|
||||
value={formData.password}
|
||||
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={onClose}>
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button disabled={isLoading} onClick={handleSubmit}>
|
||||
{isLoading ? <div className="animate-spin">⠋</div> : t("createShare.create")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
import { IconEdit, IconTrash } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
||||
interface FileActionsModalsProps {
|
||||
fileToRename: { id: string; name: string; description?: string } | null;
|
||||
fileToDelete: { id: string; name: string } | null;
|
||||
onRename: (fileId: string, newName: string, description?: string) => Promise<void>;
|
||||
onDelete: (fileId: string) => Promise<void>;
|
||||
onCloseRename: () => void;
|
||||
onCloseDelete: () => void;
|
||||
}
|
||||
|
||||
export function FileActionsModals({
|
||||
fileToRename,
|
||||
fileToDelete,
|
||||
onRename,
|
||||
onDelete,
|
||||
onCloseRename,
|
||||
onCloseDelete,
|
||||
}: FileActionsModalsProps) {
|
||||
const t = useTranslations();
|
||||
|
||||
const splitFileName = (fullName: string) => {
|
||||
const lastDotIndex = fullName.lastIndexOf(".");
|
||||
|
||||
return lastDotIndex === -1
|
||||
? { name: fullName, extension: "" }
|
||||
: {
|
||||
name: fullName.substring(0, lastDotIndex),
|
||||
extension: fullName.substring(lastDotIndex),
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={!!fileToRename} onOpenChange={() => onCloseRename()}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<IconEdit size={20} />
|
||||
{t("fileActions.editFile")}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
{fileToRename && (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<Input
|
||||
defaultValue={splitFileName(fileToRename.name).name}
|
||||
placeholder={t("fileActions.namePlaceholder")}
|
||||
onKeyUp={(e) => {
|
||||
if (e.key === "Enter" && fileToRename) {
|
||||
const newName = e.currentTarget.value + splitFileName(fileToRename.name).extension;
|
||||
onRename(fileToRename.id, newName);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("fileActions.extension")}: {splitFileName(fileToRename.name).extension}
|
||||
</p>
|
||||
</div>
|
||||
<Input
|
||||
defaultValue={fileToRename.description || ""}
|
||||
placeholder={t("fileActions.descriptionPlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={onCloseRename}>
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const nameInput = document.querySelector(
|
||||
`input[placeholder="${t("fileActions.namePlaceholder")}"]`
|
||||
) as HTMLInputElement;
|
||||
const descInput = document.querySelector(
|
||||
`input[placeholder="${t("fileActions.descriptionPlaceholder")}"]`
|
||||
) as HTMLInputElement;
|
||||
|
||||
if (fileToRename && nameInput && descInput) {
|
||||
const newName = nameInput.value + splitFileName(fileToRename.name).extension;
|
||||
onRename(fileToRename.id, newName, descInput.value);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog open={!!fileToDelete} onOpenChange={() => onCloseDelete()}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<IconTrash size={20} />
|
||||
{t("fileActions.deleteFile")}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DialogDescription>
|
||||
<p className="text-base font-semibold mb-2 text-foreground">{t("fileActions.deleteConfirmation")}</p>
|
||||
<p>
|
||||
{(fileToDelete?.name &&
|
||||
(fileToDelete.name.length > 50 ? fileToDelete.name.substring(0, 50) + "..." : fileToDelete.name)) ||
|
||||
""}
|
||||
</p>
|
||||
<p className="text-sm mt-2 text-amber-500">{t("fileActions.deleteWarning")}</p>
|
||||
</DialogDescription>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={onCloseDelete}>
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={() => fileToDelete && onDelete(fileToDelete.id)}>
|
||||
{t("common.delete")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { IconDownload } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { AspectRatio } from "@/components/ui/aspect-ratio";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader } from "@/components/ui/dialog";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { getDownloadUrl } from "@/http/endpoints";
|
||||
import { getFileIcon } from "@/utils/file-icons";
|
||||
|
||||
interface FilePreviewModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
file: {
|
||||
name: string;
|
||||
objectName: string;
|
||||
type?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function FilePreviewModal({ isOpen, onClose, file }: FilePreviewModalProps) {
|
||||
const t = useTranslations();
|
||||
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && file.objectName) {
|
||||
setIsLoading(true);
|
||||
setPreviewUrl(null);
|
||||
loadPreview();
|
||||
}
|
||||
}, [file.objectName, isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (previewUrl) {
|
||||
URL.revokeObjectURL(previewUrl);
|
||||
}
|
||||
};
|
||||
}, [previewUrl]);
|
||||
|
||||
const loadPreview = async () => {
|
||||
if (!file.objectName) return;
|
||||
|
||||
try {
|
||||
const encodedObjectName = encodeURIComponent(file.objectName);
|
||||
const response = await getDownloadUrl(encodedObjectName);
|
||||
|
||||
setPreviewUrl(response.data.url);
|
||||
} catch (error) {
|
||||
console.error("Failed to load preview:", error);
|
||||
toast.error(t("filePreview.loadError"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
try {
|
||||
const encodedObjectName = encodeURIComponent(file.objectName);
|
||||
const response = await getDownloadUrl(encodedObjectName);
|
||||
const downloadUrl = response.data.url;
|
||||
|
||||
const fileResponse = await fetch(downloadUrl);
|
||||
const blob = await fileResponse.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
|
||||
const link = document.createElement("a");
|
||||
|
||||
link.href = url;
|
||||
link.download = file.name;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
toast.error(t("filePreview.downloadError"));
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const getFileType = () => {
|
||||
const extension = file.name.split(".").pop()?.toLowerCase();
|
||||
|
||||
if (extension === "pdf") return "pdf";
|
||||
if (["jpg", "jpeg", "png", "gif", "webp"].includes(extension || "")) return "image";
|
||||
if (["mp3", "wav", "ogg", "m4a"].includes(extension || "")) return "audio";
|
||||
if (["mp4", "webm", "ogg", "mov", "avi", "mkv"].includes(extension || "")) return "video";
|
||||
|
||||
return "other";
|
||||
};
|
||||
|
||||
const renderPreview = () => {
|
||||
const fileType = getFileType();
|
||||
const { icon: FileIcon, color } = getFileIcon(file.name);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-96 gap-4">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" />
|
||||
<p className="text-muted-foreground">{t("filePreview.loading")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!previewUrl) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-96 gap-4">
|
||||
<FileIcon className={`h-12 w-12 ${color}`} />
|
||||
<p className="text-muted-foreground">{t("filePreview.notAvailable")}</p>
|
||||
<p className="text-sm text-muted-foreground">{t("filePreview.downloadToView")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
switch (fileType) {
|
||||
case "pdf":
|
||||
return (
|
||||
<ScrollArea className="w-full">
|
||||
<iframe className="w-full h-full min-h-[600px]" src={previewUrl} title={file.name} />
|
||||
</ScrollArea>
|
||||
);
|
||||
case "image":
|
||||
return (
|
||||
<AspectRatio ratio={16 / 9} className="bg-muted">
|
||||
<img src={previewUrl} alt={file.name} className="object-contain w-full h-full rounded-md" />
|
||||
</AspectRatio>
|
||||
);
|
||||
case "audio":
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center gap-6 py-12">
|
||||
<FileIcon className={`text-6xl ${color}`} />
|
||||
<audio controls className="w-full max-w-md">
|
||||
<source src={previewUrl} type={`audio/${file.name.split(".").pop()}`} />
|
||||
{t("filePreview.audioNotSupported")}
|
||||
</audio>
|
||||
</div>
|
||||
);
|
||||
case "video":
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center gap-6 py-12">
|
||||
<video controls className="w-full max-w-4xl">
|
||||
<source src={previewUrl} type={`video/${file.name.split(".").pop()}`} />
|
||||
{t("filePreview.videoNotSupported")}
|
||||
</video>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-96 gap-4">
|
||||
<FileIcon className={`text-6xl ${color}`} />
|
||||
<p className="text-gray-500">{t("filePreview.notAvailable")}</p>
|
||||
<p className="text-sm text-gray-400">{t("filePreview.downloadToView")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="sm:max-w-4xl">
|
||||
<DialogHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
{(() => {
|
||||
const FileIcon = getFileIcon(file.name).icon;
|
||||
return <FileIcon size={24} />;
|
||||
})()}
|
||||
<span>{file.name}</span>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
<div className="py-4">{renderPreview()}</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={onClose}>
|
||||
{t("common.close")}
|
||||
</Button>
|
||||
<Button onClick={handleDownload}>
|
||||
<IconDownload className="h-4 w-4" />
|
||||
{t("common.download")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { IconCopy } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
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 { customNanoid } from "@/lib/utils";
|
||||
|
||||
interface GenerateShareLinkModalProps {
|
||||
shareId: string | null;
|
||||
share: ListUserShares200SharesItem | null;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
onGenerate: (shareId: string, alias: string) => Promise<void>;
|
||||
}
|
||||
|
||||
const generateCustomId = () => customNanoid(10, "0123456789abcdefghijklmnopqrstuvwxyz");
|
||||
|
||||
export function GenerateShareLinkModal({
|
||||
shareId,
|
||||
share,
|
||||
onClose,
|
||||
onSuccess,
|
||||
onGenerate,
|
||||
}: GenerateShareLinkModalProps) {
|
||||
const t = useTranslations();
|
||||
const [alias, setAlias] = useState(() => generateCustomId());
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [generatedLink, setGeneratedLink] = useState("");
|
||||
const [isEdit, setIsEdit] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (shareId && share?.alias?.alias) {
|
||||
setIsEdit(true);
|
||||
setAlias(share.alias.alias);
|
||||
} else {
|
||||
setIsEdit(false);
|
||||
setAlias(generateCustomId());
|
||||
}
|
||||
setGeneratedLink("");
|
||||
}, [shareId, share]);
|
||||
|
||||
const handleGenerate = async () => {
|
||||
if (!shareId) return;
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await onGenerate(shareId, alias);
|
||||
const link = `${window.location.origin}/s/${alias}`;
|
||||
|
||||
setGeneratedLink(link);
|
||||
onSuccess();
|
||||
toast.success(t("generateShareLink.success"));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error(t("generateShareLink.error"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopyLink = () => {
|
||||
navigator.clipboard.writeText(generatedLink);
|
||||
toast.success(t("generateShareLink.copied"));
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={!!shareId} onOpenChange={() => onClose()}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{isEdit ? t("generateShareLink.updateTitle") : t("generateShareLink.generateTitle")}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
{!generatedLink ? (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{isEdit ? t("generateShareLink.updateDescription") : t("generateShareLink.generateDescription")}
|
||||
</p>
|
||||
<Input
|
||||
placeholder={t("generateShareLink.aliasPlaceholder")}
|
||||
value={alias}
|
||||
onChange={(e) => setAlias(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">{t("generateShareLink.linkReady")}</p>
|
||||
<Input readOnly value={generatedLink} />
|
||||
</div>
|
||||
)}
|
||||
<DialogFooter>
|
||||
{!generatedLink ? (
|
||||
<Button disabled={!alias || isLoading} onClick={handleGenerate}>
|
||||
{isEdit ? t("generateShareLink.updateButton") : t("generateShareLink.generateButton")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={handleCopyLink}>
|
||||
<IconCopy className="h-4 w-4" />
|
||||
{t("generateShareLink.copyButton")}
|
||||
</Button>
|
||||
)}
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -1,235 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { FileSelector } from "@/components/general/file-selector";
|
||||
import { RecipientSelector } from "@/components/general/recipient-selector";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { updateSharePassword } from "@/http/endpoints";
|
||||
|
||||
export interface ShareActionsModalsProps {
|
||||
shareToDelete: any;
|
||||
shareToEdit: any;
|
||||
shareToManageFiles: any;
|
||||
shareToManageRecipients: any;
|
||||
onCloseDelete: () => void;
|
||||
onCloseEdit: () => void;
|
||||
onCloseManageFiles: () => void;
|
||||
onCloseManageRecipients: () => void;
|
||||
onDelete: (shareId: string) => Promise<void>;
|
||||
onEdit: (shareId: string, data: any) => Promise<void>;
|
||||
onManageFiles: (shareId: string, files: string[]) => Promise<void>;
|
||||
onManageRecipients: (shareId: string, recipients: string[]) => Promise<void>;
|
||||
onSuccess: () => void;
|
||||
}
|
||||
|
||||
export function ShareActionsModals({
|
||||
shareToDelete,
|
||||
shareToEdit,
|
||||
shareToManageFiles,
|
||||
shareToManageRecipients,
|
||||
onCloseDelete,
|
||||
onCloseEdit,
|
||||
onCloseManageFiles,
|
||||
onCloseManageRecipients,
|
||||
onDelete,
|
||||
onEdit,
|
||||
onManageFiles,
|
||||
onSuccess,
|
||||
}: ShareActionsModalsProps) {
|
||||
const t = useTranslations();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [editForm, setEditForm] = useState({
|
||||
name: "",
|
||||
expiresAt: "",
|
||||
isPasswordProtected: false,
|
||||
password: "",
|
||||
maxViews: "",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (shareToEdit) {
|
||||
setEditForm({
|
||||
name: shareToEdit.name || "",
|
||||
expiresAt: shareToEdit.expiration ? new Date(shareToEdit.expiration).toISOString().slice(0, 16) : "",
|
||||
isPasswordProtected: Boolean(shareToEdit.security?.hasPassword),
|
||||
password: "",
|
||||
maxViews: shareToEdit.security?.maxViews?.toString() || "",
|
||||
});
|
||||
}
|
||||
}, [shareToEdit]);
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!shareToDelete) return;
|
||||
setIsLoading(true);
|
||||
await onDelete(shareToDelete.id);
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const handleEdit = async () => {
|
||||
if (!shareToEdit) return;
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const updateData = {
|
||||
name: editForm.name,
|
||||
expiration: editForm.expiresAt ? new Date(editForm.expiresAt).toISOString() : undefined,
|
||||
maxViews: editForm.maxViews ? parseInt(editForm.maxViews) : null,
|
||||
};
|
||||
|
||||
await onEdit(shareToEdit.id, updateData);
|
||||
|
||||
if (!editForm.isPasswordProtected && shareToEdit.security.hasPassword) {
|
||||
await updateSharePassword(shareToEdit.id, { password: "" });
|
||||
} else if (editForm.isPasswordProtected && editForm.password) {
|
||||
await updateSharePassword(shareToEdit.id, { password: editForm.password });
|
||||
}
|
||||
|
||||
onSuccess();
|
||||
onCloseEdit();
|
||||
toast.success(t("shareActions.editSuccess"));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error(t("shareActions.editError"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={!!shareToDelete} onOpenChange={() => onCloseDelete()}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("shareActions.deleteTitle")}</DialogTitle>
|
||||
<DialogDescription>{t("shareActions.deleteConfirmation")}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={onCloseDelete}>
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button variant="destructive" disabled={isLoading} onClick={handleDelete}>
|
||||
{t("common.delete")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog open={!!shareToEdit} onOpenChange={() => onCloseEdit()}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("shareActions.editTitle")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="grid w-full items-center gap-1.5">
|
||||
<Label>{t("shareActions.nameLabel")}</Label>
|
||||
<Input value={editForm.name} onChange={(e) => setEditForm({ ...editForm, name: e.target.value })} />
|
||||
</div>
|
||||
<div className="grid w-full items-center gap-1.5">
|
||||
<Label>{t("shareActions.expirationLabel")}</Label>
|
||||
<Input
|
||||
placeholder={t("shareActions.expirationPlaceholder")}
|
||||
type="datetime-local"
|
||||
value={editForm.expiresAt}
|
||||
onChange={(e) => setEditForm({ ...editForm, expiresAt: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid w-full items-center gap-1.5">
|
||||
<Label>{t("shareActions.maxViewsLabel")}</Label>
|
||||
<Input
|
||||
min="1"
|
||||
placeholder={t("shareActions.maxViewsPlaceholder")}
|
||||
type="number"
|
||||
value={editForm.maxViews}
|
||||
onChange={(e) => setEditForm({ ...editForm, maxViews: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch
|
||||
checked={editForm.isPasswordProtected}
|
||||
onCheckedChange={(checked) =>
|
||||
setEditForm({
|
||||
...editForm,
|
||||
isPasswordProtected: checked,
|
||||
password: "",
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Label>{t("shareActions.passwordProtection")}</Label>
|
||||
</div>
|
||||
{editForm.isPasswordProtected && (
|
||||
<div className="grid w-full items-center gap-1.5">
|
||||
<Label>
|
||||
{shareToEdit?.security?.hasPassword
|
||||
? t("shareActions.newPasswordLabel")
|
||||
: t("shareActions.passwordLabel")}
|
||||
</Label>
|
||||
<Input
|
||||
placeholder={
|
||||
shareToEdit?.security?.hasPassword
|
||||
? t("shareActions.newPasswordPlaceholder")
|
||||
: t("shareActions.passwordPlaceholder")
|
||||
}
|
||||
type="password"
|
||||
value={editForm.password}
|
||||
onChange={(e) => setEditForm({ ...editForm, password: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={onCloseEdit}>
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button disabled={isLoading} onClick={handleEdit}>
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog open={!!shareToManageFiles} onOpenChange={() => onCloseManageFiles()}>
|
||||
<DialogContent className="sm:max-w-[425px] md:max-w-[700px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("shareActions.manageFilesTitle")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<FileSelector
|
||||
selectedFiles={shareToManageFiles?.files?.map((file: { id: string }) => file.id) || []}
|
||||
shareId={shareToManageFiles?.id}
|
||||
onSave={async (files) => {
|
||||
await onManageFiles(shareToManageFiles?.id, files);
|
||||
onSuccess();
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog open={!!shareToManageRecipients} onOpenChange={() => onCloseManageRecipients()}>
|
||||
<DialogContent className="sm:max-w-[425px] md:max-w-[700px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("shareActions.manageRecipientsTitle")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<RecipientSelector
|
||||
selectedRecipients={shareToManageRecipients?.recipients || []}
|
||||
shareAlias={shareToManageRecipients?.alias?.alias}
|
||||
shareId={shareToManageRecipients?.id}
|
||||
onSuccess={onSuccess}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { IconLock, IconLockOpen, IconMail } from "@tabler/icons-react";
|
||||
import { format } from "date-fns";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Loader } from "@/components/ui/loader";
|
||||
import { getShare } from "@/http/endpoints";
|
||||
import { getFileIcon } from "@/utils/file-icons";
|
||||
|
||||
interface ShareDetailsModalProps {
|
||||
shareId: string | null;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
interface ShareFile {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
extension: string;
|
||||
size: number;
|
||||
objectName: string;
|
||||
userId: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
interface ShareRecipient {
|
||||
id: string;
|
||||
email: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export function ShareDetailsModal({ shareId, onClose }: ShareDetailsModalProps) {
|
||||
const t = useTranslations();
|
||||
const [share, setShare] = useState<any>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (shareId) {
|
||||
loadShareDetails();
|
||||
}
|
||||
}, [shareId]);
|
||||
|
||||
const loadShareDetails = async () => {
|
||||
if (!shareId) return;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await getShare(shareId);
|
||||
|
||||
setShare(response.data.share);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error(t("shareDetails.loadError"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string | null) => {
|
||||
if (!dateString) return t("shareDetails.notAvailable");
|
||||
try {
|
||||
return format(new Date(dateString), "MM/dd/yyyy HH:mm");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
console.error("Invalid date:", dateString);
|
||||
|
||||
return t("shareDetails.invalidDate");
|
||||
}
|
||||
};
|
||||
|
||||
if (!share) return null;
|
||||
|
||||
return (
|
||||
<Dialog open={!!shareId} onOpenChange={() => onClose()}>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("shareDetails.title")}</DialogTitle>
|
||||
<DialogDescription>{t("shareDetails.subtitle")}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center py-8">
|
||||
<Loader size="lg" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-lg border p-4">
|
||||
<h3 className="font-medium">{t("shareDetails.basicInfo")}</h3>
|
||||
<div className="mt-3 space-y-3">
|
||||
<div>
|
||||
<span className="text-sm text-default-500">{t("shareDetails.name")}</span>
|
||||
<p className="mt-1 font-medium">{share.name || t("shareDetails.untitled")}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm text-default-500">{t("shareDetails.views")}</span>
|
||||
<p className="mt-1 font-medium">{share.views}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-lg border p-4">
|
||||
<h3 className="font-medium">{t("shareDetails.dates")}</h3>
|
||||
<div className="mt-3 space-y-3">
|
||||
<div>
|
||||
<span className="text-sm text-default-500">{t("shareDetails.created")}</span>
|
||||
<p className="mt-1 font-medium">{formatDate(share.createdAt)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm text-default-500">{t("shareDetails.expires")}</span>
|
||||
<p className="mt-1 font-medium">
|
||||
{share.expiration ? formatDate(share.expiration) : t("shareDetails.never")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border p-4">
|
||||
<h3 className="font-medium">{t("shareDetails.security")}</h3>
|
||||
<div className="mt-3 flex gap-2">
|
||||
{share.security?.hasPassword ? (
|
||||
<Badge variant="secondary">
|
||||
<IconLock className="h-4 w-4" />
|
||||
{t("shareDetails.passwordProtected")}
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge variant="secondary">
|
||||
<IconLockOpen className="h-4 w-4" />
|
||||
{t("shareDetails.publicAccess")}
|
||||
</Badge>
|
||||
)}
|
||||
{share.security?.maxViews && (
|
||||
<Badge variant="secondary">
|
||||
{t("shareDetails.maxViews")} {share.security.maxViews}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border p-4">
|
||||
<h3 className="font-medium">
|
||||
{t("shareDetails.files")} ({share.files?.length || 0})
|
||||
</h3>
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{share.files?.map((file: ShareFile) => {
|
||||
const { icon: FileIcon, color } = getFileIcon(file.name);
|
||||
return (
|
||||
<Badge key={file.id} variant="secondary">
|
||||
<FileIcon className={`h-4 w-4 ${color}`} />
|
||||
{file.name.length > 20 ? file.name.substring(0, 20) + "..." : file.name}
|
||||
</Badge>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border p-4">
|
||||
<h3 className="font-medium">
|
||||
{t("shareDetails.recipients")} ({share.recipients?.length || 0})
|
||||
</h3>
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{share.recipients?.map((recipient: ShareRecipient) => (
|
||||
<Badge key={recipient.id} variant="secondary">
|
||||
<IconMail className="h-4 w-4" />
|
||||
{recipient.email}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button onClick={onClose}>{t("common.close")}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { IconCloudUpload, IconFileText, IconFileTypePdf, IconFileTypography, IconPhoto } from "@tabler/icons-react";
|
||||
import axios from "axios";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import { getPresignedUrl, registerFile } from "@/http/endpoints";
|
||||
import { generateSafeFileName } from "@/utils/file-utils";
|
||||
|
||||
interface UploadFileModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
export function UploadFileModal({ isOpen, onClose, onSuccess }: UploadFileModalProps) {
|
||||
const t = useTranslations();
|
||||
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||||
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
|
||||
const [uploadProgress, setUploadProgress] = useState(0);
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (previewUrl) {
|
||||
URL.revokeObjectURL(previewUrl);
|
||||
}
|
||||
};
|
||||
}, [previewUrl]);
|
||||
|
||||
const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
|
||||
if (file) {
|
||||
setSelectedFile(file);
|
||||
|
||||
if (file.type.startsWith("image/")) {
|
||||
const url = URL.createObjectURL(file);
|
||||
|
||||
setPreviewUrl(url);
|
||||
} else {
|
||||
setPreviewUrl(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getFileIcon = (fileType: string) => {
|
||||
if (fileType.startsWith("image/")) return <IconPhoto size={64} className="text-blue-500" />;
|
||||
if (fileType.includes("pdf")) return <IconFileTypePdf size={64} className="text-red-500" />;
|
||||
if (fileType.includes("word")) return <IconFileTypography size={64} className="text-blue-700" />;
|
||||
|
||||
return <IconFileText size={64} className="text-gray-500" />;
|
||||
};
|
||||
|
||||
const handleUpload = async () => {
|
||||
if (!selectedFile) return;
|
||||
|
||||
try {
|
||||
setIsUploading(true);
|
||||
setUploadProgress(0);
|
||||
|
||||
const fileName = selectedFile.name;
|
||||
const extension = fileName.split(".").pop() || "";
|
||||
const safeObjectName = generateSafeFileName(fileName);
|
||||
|
||||
const presignedResponse = await getPresignedUrl({
|
||||
filename: safeObjectName.replace(`.${extension}`, ""),
|
||||
extension: extension,
|
||||
});
|
||||
|
||||
const { url, objectName } = presignedResponse.data;
|
||||
|
||||
await axios.put(url, selectedFile, {
|
||||
headers: {
|
||||
"Content-Type": selectedFile.type,
|
||||
},
|
||||
onUploadProgress: (progressEvent) => {
|
||||
const progress = (progressEvent.loaded / (progressEvent.total || selectedFile.size)) * 100;
|
||||
|
||||
setUploadProgress(Math.round(progress));
|
||||
},
|
||||
});
|
||||
|
||||
await registerFile({
|
||||
name: fileName,
|
||||
objectName: objectName,
|
||||
size: selectedFile.size,
|
||||
extension: extension,
|
||||
});
|
||||
|
||||
toast.success(t("uploadFile.success"));
|
||||
onSuccess?.();
|
||||
handleClose();
|
||||
} catch (error) {
|
||||
console.error("Upload failed:", error);
|
||||
toast.error(t("uploadFile.error"));
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setSelectedFile(null);
|
||||
setUploadProgress(0);
|
||||
setIsUploading(false);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={() => handleClose()}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("uploadFile.title")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<input ref={fileInputRef} className="hidden" type="file" onChange={handleFileSelect} />
|
||||
|
||||
{!selectedFile ? (
|
||||
<div
|
||||
className="border-2 border-dashed border-gray-300 rounded-lg p-8 cursor-pointer hover:border-primary-500 transition-colors"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
>
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<IconCloudUpload size={32} className="text-gray-400" />
|
||||
<p className="text-gray-600">{t("uploadFile.selectFile")}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-full">
|
||||
<div className="flex flex-col items-center gap-4 mb-4">
|
||||
{previewUrl ? (
|
||||
<img
|
||||
alt={t("uploadFile.preview")}
|
||||
className="max-w-full h-auto max-h-48 rounded-lg object-contain"
|
||||
src={previewUrl}
|
||||
/>
|
||||
) : (
|
||||
getFileIcon(selectedFile.type)
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* <IconFile size={24} className="text-gray-500" /> */}
|
||||
<span className="font-medium">
|
||||
{selectedFile.name.length > 40 ? selectedFile.name.substring(0, 40) + "..." : selectedFile.name} (
|
||||
{selectedFile.size / 1000} KB)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{isUploading && <Progress value={uploadProgress} className="w-full" />}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={handleClose}>
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button variant="default" disabled={!selectedFile || isUploading} onClick={handleUpload}>
|
||||
{isUploading && <IconCloudUpload className="mr-2 h-4 w-4 animate-spin" />}
|
||||
{t("uploadFile.upload")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
import { IconDotsVertical, IconDownload, IconEdit, IconEye, IconTrash } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { getFileIcon } from "@/utils/file-icons";
|
||||
import { formatFileSize } from "@/utils/format-file-size";
|
||||
|
||||
interface File {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
size: number;
|
||||
objectName: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
interface FilesTableProps {
|
||||
files: File[];
|
||||
onPreview: (file: File) => void;
|
||||
onRename: (file: File) => void;
|
||||
onDownload: (objectName: string, fileName: string) => void;
|
||||
onDelete: (file: File) => void;
|
||||
}
|
||||
|
||||
export function FilesTable({ files, onPreview, onRename, onDownload, onDelete }: FilesTableProps) {
|
||||
const t = useTranslations();
|
||||
|
||||
const formatDateTime = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
|
||||
return new Intl.DateTimeFormat("en-US", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
}).format(date);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="rounded-lg shadow-sm overflow-hidden border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-b-0">
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4 rounded-tl-lg">
|
||||
{t("filesTable.columns.name")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("filesTable.columns.description")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("filesTable.columns.size")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("filesTable.columns.createdAt")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("filesTable.columns.updatedAt")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 w-[70px] text-xs font-bold text-muted-foreground bg-muted/50 px-4 rounded-tr-lg">
|
||||
{t("filesTable.columns.actions")}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{files.map((file) => {
|
||||
const { icon: FileIcon, color } = getFileIcon(file.name);
|
||||
|
||||
return (
|
||||
<TableRow key={file.id} className="hover:bg-muted/50 transition-colors border-0">
|
||||
<TableCell className="h-12 px-4 border-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileIcon className={`h-5 w-5 ${color}`} />
|
||||
<span className="truncate max-w-[250px] font-medium" title={file.name}>
|
||||
{file.name}
|
||||
</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="h-12 px-4">{file.description || "-"}</TableCell>
|
||||
<TableCell className="h-12 px-4">{formatFileSize(file.size)}</TableCell>
|
||||
<TableCell className="h-12 px-4">{formatDateTime(file.createdAt)}</TableCell>
|
||||
<TableCell className="h-12 px-4">{formatDateTime(file.updatedAt || file.createdAt)}</TableCell>
|
||||
<TableCell className="h-12 px-4 text-right">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 hover:bg-muted cursor-pointer">
|
||||
<IconDotsVertical className="h-4 w-4" />
|
||||
<span className="sr-only">{t("filesTable.actions.menu")}</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-[200px]">
|
||||
<DropdownMenuItem className="cursor-pointer py-2" onClick={() => onPreview(file)}>
|
||||
<IconEye className="mr-2 h-4 w-4" />
|
||||
{t("filesTable.actions.preview")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="cursor-pointer py-2" onClick={() => onRename(file)}>
|
||||
<IconEdit className="mr-2 h-4 w-4" />
|
||||
{t("filesTable.actions.edit")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer py-2"
|
||||
onClick={() => onDownload(file.objectName, file.name)}
|
||||
>
|
||||
<IconDownload className="mr-2 h-4 w-4" />
|
||||
{t("filesTable.actions.download")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => onDelete(file)}
|
||||
className="cursor-pointer py-2 text-destructive focus:text-destructive"
|
||||
>
|
||||
<IconTrash className="mr-2 h-4 w-4" />
|
||||
{t("filesTable.actions.delete")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
import {
|
||||
IconCopy,
|
||||
IconDotsVertical,
|
||||
IconEdit,
|
||||
IconEye,
|
||||
IconFolder,
|
||||
IconLink,
|
||||
IconLock,
|
||||
IconLockOpen,
|
||||
IconMail,
|
||||
IconTrash,
|
||||
IconUsers,
|
||||
} from "@tabler/icons-react";
|
||||
import { format } from "date-fns";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { useShareContext } from "../../contexts/share-context";
|
||||
|
||||
export interface SharesTableProps {
|
||||
shares: any[];
|
||||
onDelete: (share: any) => void;
|
||||
onEdit: (share: any) => void;
|
||||
onManageFiles: (share: any) => void;
|
||||
onManageRecipients: (share: any) => void;
|
||||
onViewDetails: (share: any) => void;
|
||||
onGenerateLink: (share: any) => void;
|
||||
onCopyLink: (share: any) => void;
|
||||
onNotifyRecipients: (share: any) => void;
|
||||
}
|
||||
|
||||
export function SharesTable({
|
||||
shares,
|
||||
onDelete,
|
||||
onEdit,
|
||||
onManageFiles,
|
||||
onManageRecipients,
|
||||
onViewDetails,
|
||||
onGenerateLink,
|
||||
onCopyLink,
|
||||
onNotifyRecipients,
|
||||
}: SharesTableProps) {
|
||||
const t = useTranslations();
|
||||
const { smtpEnabled } = useShareContext();
|
||||
|
||||
return (
|
||||
<div className="rounded-lg shadow-sm overflow-hidden border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-b-0">
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("sharesTable.columns.name")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("sharesTable.columns.createdAt")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("sharesTable.columns.expiresAt")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("sharesTable.columns.status")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("sharesTable.columns.security")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("sharesTable.columns.files")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("sharesTable.columns.recipients")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 w-[70px] text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("sharesTable.columns.actions")}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{shares.map((share) => (
|
||||
<TableRow key={share.id} className="hover:bg-muted/50 transition-colors border-0">
|
||||
<TableCell className="h-12 px-4 border-0">{share.name}</TableCell>
|
||||
<TableCell className="h-12 px-4">{format(new Date(share.createdAt), "MM/dd/yyyy HH:mm")}</TableCell>
|
||||
<TableCell className="h-12 px-4">
|
||||
{share.expiration ? format(new Date(share.expiration), "MM/dd/yyyy HH:mm") : t("sharesTable.never")}
|
||||
</TableCell>
|
||||
<TableCell className="h-12 px-4">
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className={
|
||||
!share.expiration || new Date(share.expiration) > new Date()
|
||||
? "bg-green-500/20 hover:bg-green-500/30 text-green-500"
|
||||
: "bg-red-500/20 hover:bg-red-500/30 text-red-500"
|
||||
}
|
||||
>
|
||||
{!share.expiration
|
||||
? t("sharesTable.status.neverExpires")
|
||||
: new Date(share.expiration) > new Date()
|
||||
? t("sharesTable.status.active")
|
||||
: t("sharesTable.status.expired")}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="h-12 px-4">
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className={`flex items-center gap-1 ${
|
||||
share.security.hasPassword
|
||||
? "bg-yellow-500/20 hover:bg-yellow-500/30 text-yellow-500"
|
||||
: "bg-green-500/20 hover:bg-green-500/30 text-green-500"
|
||||
}`}
|
||||
>
|
||||
{share.security.hasPassword ? <IconLock className="h-4 w-4" /> : <IconLockOpen className="h-4 w-4" />}
|
||||
{share.security.hasPassword ? t("sharesTable.security.protected") : t("sharesTable.security.public")}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="h-12 px-4">
|
||||
{share.files?.length || 0} {t("sharesTable.filesCount")}
|
||||
</TableCell>
|
||||
<TableCell className="h-12 px-4">
|
||||
{share.recipients?.length || 0} {t("sharesTable.recipientsCount")}
|
||||
</TableCell>
|
||||
<TableCell className="h-12 px-4 text-right">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 hover:bg-muted cursor-pointer">
|
||||
<IconDotsVertical className="h-4 w-4" />
|
||||
<span className="sr-only">{t("sharesTable.actions.menu")}</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-[200px]">
|
||||
<DropdownMenuItem className="cursor-pointer py-2" onClick={() => onEdit(share)}>
|
||||
<IconEdit className="mr-2 h-4 w-4" />
|
||||
{t("sharesTable.actions.edit")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="cursor-pointer py-2" onClick={() => onManageFiles(share)}>
|
||||
<IconFolder className="mr-2 h-4 w-4" />
|
||||
{t("sharesTable.actions.manageFiles")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="cursor-pointer py-2" onClick={() => onManageRecipients(share)}>
|
||||
<IconUsers className="mr-2 h-4 w-4" />
|
||||
{t("sharesTable.actions.manageRecipients")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="cursor-pointer py-2" onClick={() => onViewDetails(share)}>
|
||||
<IconEye className="mr-2 h-4 w-4" />
|
||||
{t("sharesTable.actions.viewDetails")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="cursor-pointer py-2" onClick={() => onGenerateLink(share)}>
|
||||
<IconLink className="mr-2 h-4 w-4" />
|
||||
{share.alias ? t("sharesTable.actions.editLink") : t("sharesTable.actions.generateLink")}
|
||||
</DropdownMenuItem>
|
||||
{share.alias && (
|
||||
<DropdownMenuItem className="cursor-pointer py-2" onClick={() => onCopyLink(share)}>
|
||||
<IconCopy className="mr-2 h-4 w-4" />
|
||||
{t("sharesTable.actions.copyLink")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{share.recipients?.length > 0 && share.alias && smtpEnabled === "true" && (
|
||||
<DropdownMenuItem className="cursor-pointer py-2" onClick={() => onNotifyRecipients(share)}>
|
||||
<IconMail className="mr-2 h-4 w-4" />
|
||||
{t("sharesTable.actions.notifyRecipients")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
onClick={() => onDelete(share)}
|
||||
className="cursor-pointer py-2 text-destructive focus:text-destructive"
|
||||
>
|
||||
<IconTrash className="mr-2 h-4 w-4" />
|
||||
{t("sharesTable.actions.delete")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import Link from "next/link";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { version } from "../../../package.json";
|
||||
|
||||
export function DefaultFooter() {
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<footer className="w-full flex items-center justify-center py-3 h-16">
|
||||
<div className="flex flex-col items-center">
|
||||
<Link
|
||||
target="_blank"
|
||||
className="flex items-center gap-1 text-current"
|
||||
href="https://kyantech.com.br"
|
||||
title={t("footer.kyanHomepage")}
|
||||
>
|
||||
<span className="text-default-600 text-xs sm:text-sm">{t("footer.poweredBy")}</span>
|
||||
<p className="text-green-700 text-xs sm:text-sm">Kyantech Solutions</p>
|
||||
</Link>
|
||||
<span className="text-default-500 text-[11px] mt-1">v{version}-beta</span>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
export type SiteConfig = typeof siteConfig;
|
||||
|
||||
export const siteConfig = {
|
||||
navItems: [
|
||||
{
|
||||
label: "Login",
|
||||
href: "/login",
|
||||
},
|
||||
{
|
||||
label: "Docs",
|
||||
href: "https://palmr-docs.kyantech.com.br",
|
||||
},
|
||||
],
|
||||
navMenuItems: [
|
||||
{
|
||||
label: "Login",
|
||||
href: "/login",
|
||||
},
|
||||
{
|
||||
label: "Docs",
|
||||
href: "https://palmr-docs.kyantech.com.br",
|
||||
},
|
||||
],
|
||||
links: {
|
||||
github: "https://github.com/kyantech/Palmr",
|
||||
docs: "https://palmr-docs.kyantech.com.br",
|
||||
sponsor: "https://github.com/sponsors/kyantech",
|
||||
},
|
||||
};
|
||||
@@ -1,55 +0,0 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
import { getAppInfo } from "@/http/endpoints";
|
||||
|
||||
interface AppInfoStore {
|
||||
appName: string;
|
||||
appLogo: string;
|
||||
setAppName: (name: string) => void;
|
||||
setAppLogo: (logo: string) => void;
|
||||
refreshAppInfo: () => Promise<void>;
|
||||
}
|
||||
|
||||
const updateTitle = (name: string) => {
|
||||
document.title = name;
|
||||
};
|
||||
|
||||
export const useAppInfo = create<AppInfoStore>((set) => {
|
||||
if (typeof window !== "undefined") {
|
||||
getAppInfo()
|
||||
.then((response) => {
|
||||
set({
|
||||
appName: response.data.appName,
|
||||
appLogo: response.data.appLogo,
|
||||
});
|
||||
updateTitle(response.data.appName);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to fetch app info:", error);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
appName: "",
|
||||
appLogo: "",
|
||||
setAppName: (name: string) => {
|
||||
set({ appName: name });
|
||||
updateTitle(name);
|
||||
},
|
||||
setAppLogo: (logo: string) => {
|
||||
set({ appLogo: logo });
|
||||
},
|
||||
refreshAppInfo: async () => {
|
||||
try {
|
||||
const response = await getAppInfo();
|
||||
set({
|
||||
appName: response.data.appName,
|
||||
appLogo: response.data.appLogo,
|
||||
});
|
||||
updateTitle(response.data.appName);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch app info:", error);
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -1,82 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, useContext, useEffect, useState } from "react";
|
||||
|
||||
import { getCurrentUser } from "@/http/endpoints";
|
||||
import type { GetCurrentUser200User } from "@/http/models";
|
||||
|
||||
type AuthUser = Omit<GetCurrentUser200User, "isAdmin">;
|
||||
|
||||
type AuthContextType = {
|
||||
user: AuthUser | null;
|
||||
setUser: (user: AuthUser | null) => void;
|
||||
isAuthenticated: boolean | null;
|
||||
setIsAuthenticated: (value: boolean) => void;
|
||||
isAdmin: boolean | null;
|
||||
setIsAdmin: (value: boolean) => void;
|
||||
logout: () => void;
|
||||
};
|
||||
|
||||
const AuthContext = createContext<AuthContextType>({
|
||||
user: null,
|
||||
setUser: () => {},
|
||||
isAuthenticated: null,
|
||||
setIsAuthenticated: () => {},
|
||||
isAdmin: null,
|
||||
setIsAdmin: () => {},
|
||||
logout: () => {},
|
||||
});
|
||||
|
||||
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
|
||||
const [user, setUser] = useState<AuthUser | null>(null);
|
||||
const [isAdmin, setIsAdmin] = useState<boolean | null>(null);
|
||||
|
||||
const logout = () => {
|
||||
setUser(null);
|
||||
setIsAdmin(false);
|
||||
setIsAuthenticated(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const checkAuth = async () => {
|
||||
try {
|
||||
const response = await getCurrentUser();
|
||||
if (!response?.data?.user) {
|
||||
throw new Error("No user data");
|
||||
}
|
||||
|
||||
const { isAdmin, ...userData } = response.data.user;
|
||||
|
||||
setUser(userData);
|
||||
setIsAdmin(isAdmin);
|
||||
setIsAuthenticated(true);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setUser(null);
|
||||
setIsAdmin(false);
|
||||
setIsAuthenticated(false);
|
||||
}
|
||||
};
|
||||
|
||||
checkAuth();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
user,
|
||||
setUser,
|
||||
isAuthenticated,
|
||||
setIsAuthenticated,
|
||||
isAdmin,
|
||||
setIsAdmin,
|
||||
logout,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useAuth = () => useContext(AuthContext);
|
||||
@@ -1,102 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { deleteFile, getDownloadUrl, updateFile } from "@/http/endpoints";
|
||||
|
||||
interface FileToRename {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface FileToDelete {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface PreviewFile {
|
||||
name: string;
|
||||
objectName: string;
|
||||
}
|
||||
|
||||
export interface FileManagerHook {
|
||||
previewFile: PreviewFile | null;
|
||||
fileToDelete: any;
|
||||
fileToRename: any;
|
||||
setFileToDelete: (file: any) => void;
|
||||
setFileToRename: (file: any) => void;
|
||||
setPreviewFile: (file: PreviewFile | null) => void;
|
||||
handleDelete: (fileId: string) => Promise<void>;
|
||||
handleDownload: (objectName: string, fileName: string) => Promise<void>;
|
||||
handleRename: (fileId: string, newName: string, description?: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export function useFileManager(onRefresh: () => Promise<void>) {
|
||||
const [previewFile, setPreviewFile] = useState<PreviewFile | null>(null);
|
||||
const [fileToRename, setFileToRename] = useState<FileToRename | null>(null);
|
||||
const [fileToDelete, setFileToDelete] = useState<FileToDelete | null>(null);
|
||||
|
||||
const handleDownload = async (objectName: string, fileName: string) => {
|
||||
try {
|
||||
const encodedObjectName = encodeURIComponent(objectName);
|
||||
const response = await getDownloadUrl(encodedObjectName);
|
||||
const downloadUrl = response.data.url;
|
||||
|
||||
const fileResponse = await fetch(downloadUrl);
|
||||
const blob = await fileResponse.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
|
||||
const link = document.createElement("a");
|
||||
|
||||
link.href = url;
|
||||
link.download = fileName;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error("Failed to download file");
|
||||
}
|
||||
};
|
||||
|
||||
const handleRename = async (fileId: string, newName: string, description?: string) => {
|
||||
try {
|
||||
await updateFile(fileId, {
|
||||
name: newName,
|
||||
description: description || null,
|
||||
});
|
||||
await onRefresh();
|
||||
toast.success("File updated successfully");
|
||||
setFileToRename(null);
|
||||
} catch (error) {
|
||||
console.error("Failed to update file:", error);
|
||||
toast.error("Failed to update file");
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (fileId: string) => {
|
||||
try {
|
||||
await deleteFile(fileId);
|
||||
await onRefresh();
|
||||
toast.success("File deleted successfully");
|
||||
setFileToDelete(null);
|
||||
} catch (error) {
|
||||
console.error("Failed to delete file:", error);
|
||||
toast.error("Failed to delete file");
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
previewFile,
|
||||
setPreviewFile,
|
||||
fileToRename,
|
||||
setFileToRename,
|
||||
fileToDelete,
|
||||
setFileToDelete,
|
||||
handleDownload,
|
||||
handleRename,
|
||||
handleDelete,
|
||||
};
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import {
|
||||
addFiles,
|
||||
addRecipients,
|
||||
createShareAlias,
|
||||
deleteShare,
|
||||
notifyRecipients,
|
||||
updateShare,
|
||||
} from "@/http/endpoints";
|
||||
import { ListUserShares200SharesItem } from "@/http/models/listUserShares200SharesItem";
|
||||
|
||||
export interface ShareManagerHook {
|
||||
shareToDelete: ListUserShares200SharesItem | null;
|
||||
shareToEdit: ListUserShares200SharesItem | null;
|
||||
shareToManageFiles: ListUserShares200SharesItem | null;
|
||||
shareToManageRecipients: ListUserShares200SharesItem | null;
|
||||
shareToViewDetails: ListUserShares200SharesItem | null;
|
||||
shareToGenerateLink: ListUserShares200SharesItem | null;
|
||||
setShareToDelete: (share: ListUserShares200SharesItem | null) => void;
|
||||
setShareToEdit: (share: ListUserShares200SharesItem | null) => void;
|
||||
setShareToManageFiles: (share: ListUserShares200SharesItem | null) => void;
|
||||
setShareToManageRecipients: (share: ListUserShares200SharesItem | null) => void;
|
||||
setShareToViewDetails: (share: ListUserShares200SharesItem | null) => void;
|
||||
setShareToGenerateLink: (share: ListUserShares200SharesItem | null) => void;
|
||||
handleDelete: (shareId: string) => Promise<void>;
|
||||
handleEdit: (shareId: string, data: any) => 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>;
|
||||
}
|
||||
|
||||
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 [shareToViewDetails, setShareToViewDetails] = useState<ListUserShares200SharesItem | null>(null);
|
||||
const [shareToGenerateLink, setShareToGenerateLink] = useState<ListUserShares200SharesItem | null>(null);
|
||||
|
||||
const handleDelete = async (shareId: string) => {
|
||||
try {
|
||||
await deleteShare(shareId);
|
||||
toast.success(t("shareManager.deleteSuccess"));
|
||||
onSuccess();
|
||||
setShareToDelete(null);
|
||||
} catch (error) {
|
||||
toast.error(t("shareManager.deleteError"));
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEdit = async (shareId: string, data: any) => {
|
||||
try {
|
||||
await updateShare({ id: shareId, ...data });
|
||||
toast.success(t("shareManager.updateSuccess"));
|
||||
onSuccess();
|
||||
setShareToEdit(null);
|
||||
} catch (error) {
|
||||
toast.error(t("shareManager.updateError"));
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleManageFiles = async (shareId: string, files: string[]) => {
|
||||
try {
|
||||
await addFiles(shareId, { files });
|
||||
toast.success(t("shareManager.filesUpdateSuccess"));
|
||||
onSuccess();
|
||||
setShareToManageFiles(null);
|
||||
} catch (error) {
|
||||
toast.error(t("shareManager.filesUpdateError"));
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleManageRecipients = async (shareId: string, recipients: string[]) => {
|
||||
try {
|
||||
await addRecipients(shareId, { emails: recipients });
|
||||
toast.success(t("shareManager.recipientsUpdateSuccess"));
|
||||
onSuccess();
|
||||
setShareToManageRecipients(null);
|
||||
} catch (error) {
|
||||
toast.error(t("shareManager.recipientsUpdateError"));
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGenerateLink = async (shareId: string, alias: string) => {
|
||||
try {
|
||||
await createShareAlias(shareId, { alias });
|
||||
toast.success(t("shareManager.linkGenerateSuccess"));
|
||||
onSuccess();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error(t("shareManager.linkGenerateError"));
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const handleNotifyRecipients = async (share: ListUserShares200SharesItem) => {
|
||||
const link = `${window.location.origin}/s/${share.alias?.alias}`;
|
||||
const loadingToast = toast.loading(t("shareManager.notifyLoading"));
|
||||
|
||||
try {
|
||||
await notifyRecipients(share.id, { shareLink: link });
|
||||
toast.dismiss(loadingToast);
|
||||
toast.success(t("shareManager.notifySuccess"));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.dismiss(loadingToast);
|
||||
toast.error(t("shareManager.notifyError"));
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
shareToDelete,
|
||||
shareToEdit,
|
||||
shareToManageFiles,
|
||||
shareToManageRecipients,
|
||||
shareToViewDetails,
|
||||
shareToGenerateLink,
|
||||
setShareToDelete,
|
||||
setShareToEdit,
|
||||
setShareToManageFiles,
|
||||
setShareToManageRecipients,
|
||||
setShareToViewDetails,
|
||||
setShareToGenerateLink,
|
||||
handleDelete,
|
||||
handleEdit,
|
||||
handleManageFiles,
|
||||
handleManageRecipients,
|
||||
handleGenerateLink,
|
||||
handleNotifyRecipients,
|
||||
};
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
import type { AxiosRequestConfig } from "axios";
|
||||
|
||||
import apiInstance from "@/config/api";
|
||||
import type {
|
||||
CheckHealthResult,
|
||||
CheckUploadAllowedParams,
|
||||
CheckUploadAllowedResult,
|
||||
GetAppInfoResult,
|
||||
GetDiskSpaceResult,
|
||||
RemoveLogoResult,
|
||||
UploadLogoBody,
|
||||
UploadLogoResult,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* Get application base information
|
||||
* @summary Get application base information
|
||||
*/
|
||||
export const getAppInfo = <TData = GetAppInfoResult>(options?: AxiosRequestConfig): Promise<TData> => {
|
||||
return apiInstance.get(`/api/app/info`, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Upload a new app logo (admin only)
|
||||
* @summary Upload app logo
|
||||
*/
|
||||
export const uploadLogo = <TData = UploadLogoResult>(
|
||||
uploadLogoBody: UploadLogoBody,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
const formData = new FormData();
|
||||
|
||||
if (uploadLogoBody.file !== undefined) {
|
||||
formData.append("file", uploadLogoBody.file as Blob);
|
||||
}
|
||||
|
||||
return apiInstance.post(`/api/app/upload-logo`, formData, {
|
||||
...options,
|
||||
headers: {
|
||||
...options?.headers,
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove the current app logo (admin only)
|
||||
* @summary Remove app logo
|
||||
*/
|
||||
export const removeLogo = <TData = RemoveLogoResult>(options?: AxiosRequestConfig): Promise<TData> => {
|
||||
return apiInstance.delete(`/api/app/remove-logo`, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the health status of the API
|
||||
* @summary Check API Health
|
||||
*/
|
||||
export const checkHealth = <TData = CheckHealthResult>(options?: AxiosRequestConfig): Promise<TData> => {
|
||||
return apiInstance.get(`/api/app/health`, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get server disk space information
|
||||
* @summary Get server disk space information
|
||||
*/
|
||||
export const getDiskSpace = <TData = GetDiskSpaceResult>(options?: AxiosRequestConfig): Promise<TData> => {
|
||||
return apiInstance.get(`/api/app/disk-space`, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if file upload is allowed based on available space (fileSize in bytes)
|
||||
* @summary Check if file upload is allowed
|
||||
*/
|
||||
export const checkUploadAllowed = <TData = CheckUploadAllowedResult>(
|
||||
params: CheckUploadAllowedParams,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.get(`/api/app/check-upload`, {
|
||||
...options,
|
||||
params: { ...params, ...options?.params },
|
||||
});
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
import type {
|
||||
CheckHealth200,
|
||||
CheckUploadAllowed200,
|
||||
CheckUploadAllowedParams,
|
||||
GetAppInfo200,
|
||||
GetDiskSpace200,
|
||||
RemoveLogo200,
|
||||
UploadLogo200,
|
||||
UploadLogoBody,
|
||||
} from "../../models";
|
||||
|
||||
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,39 +0,0 @@
|
||||
import type { AxiosRequestConfig } from "axios";
|
||||
|
||||
import apiInstance from "@/config/api";
|
||||
import type {
|
||||
GetCurrentUserResult,
|
||||
LoginBody,
|
||||
LoginResult,
|
||||
LogoutResult,
|
||||
RequestPasswordResetBody,
|
||||
RequestPasswordResetResult,
|
||||
ResetPasswordBody,
|
||||
ResetPasswordResult,
|
||||
} from "./types";
|
||||
|
||||
export const login = <TData = LoginResult>(loginBody: LoginBody, options?: AxiosRequestConfig): Promise<TData> => {
|
||||
return apiInstance.post(`/api/auth/login`, loginBody, options);
|
||||
};
|
||||
|
||||
export const logout = <TData = LogoutResult>(options?: AxiosRequestConfig): Promise<TData> => {
|
||||
return apiInstance.post(`/api/auth/logout`, undefined, options);
|
||||
};
|
||||
|
||||
export const requestPasswordReset = <TData = RequestPasswordResetResult>(
|
||||
requestPasswordResetBody: RequestPasswordResetBody,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.post(`/api/auth/forgot-password`, requestPasswordResetBody, options);
|
||||
};
|
||||
|
||||
export const resetPassword = <TData = ResetPasswordResult>(
|
||||
resetPasswordBody: ResetPasswordBody,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.post(`/api/auth/reset-password`, resetPasswordBody, options);
|
||||
};
|
||||
|
||||
export const getCurrentUser = <TData = GetCurrentUserResult>(options?: AxiosRequestConfig): Promise<TData> => {
|
||||
return apiInstance.get(`/api/auth/me`, options);
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
import type {
|
||||
GetCurrentUser200,
|
||||
Login200,
|
||||
LoginBody,
|
||||
Logout200,
|
||||
RequestPasswordReset200,
|
||||
RequestPasswordResetBody,
|
||||
ResetPassword200,
|
||||
ResetPasswordBody,
|
||||
} from "../../models";
|
||||
|
||||
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 { LoginBody, RequestPasswordResetBody, ResetPasswordBody };
|
||||
@@ -1,41 +0,0 @@
|
||||
import type { AxiosRequestConfig } from "axios";
|
||||
|
||||
import apiInstance from "@/config/api";
|
||||
import type {
|
||||
BulkUpdateConfigsBodyItem,
|
||||
BulkUpdateConfigsResult,
|
||||
GetAllConfigsResult,
|
||||
UpdateConfigBody,
|
||||
UpdateConfigResult,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* Update a configuration value (admin only)
|
||||
* @summary Update a configuration value
|
||||
*/
|
||||
export const updateConfig = <TData = UpdateConfigResult>(
|
||||
key: string,
|
||||
updateConfigBody: UpdateConfigBody,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.patch(`/api/config/update/${key}`, updateConfigBody, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* List all configurations (admin only)
|
||||
* @summary List all configurations
|
||||
*/
|
||||
export const getAllConfigs = <TData = GetAllConfigsResult>(options?: AxiosRequestConfig): Promise<TData> => {
|
||||
return apiInstance.get(`/api/config/list`, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Bulk update configuration values (admin only)
|
||||
* @summary Bulk update configuration values
|
||||
*/
|
||||
export const bulkUpdateConfigs = <TData = BulkUpdateConfigsResult>(
|
||||
bulkUpdateConfigsBodyItem: BulkUpdateConfigsBodyItem[],
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.patch(`api/config/update/bulk`, bulkUpdateConfigsBodyItem, options);
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
import type {
|
||||
BulkUpdateConfigs200,
|
||||
BulkUpdateConfigsBodyItem,
|
||||
GetAllConfigs200,
|
||||
UpdateConfig200,
|
||||
UpdateConfigBody,
|
||||
} from "../../models";
|
||||
|
||||
export type UpdateConfigResult = AxiosResponse<UpdateConfig200>;
|
||||
export type GetAllConfigsResult = AxiosResponse<GetAllConfigs200>;
|
||||
export type BulkUpdateConfigsResult = AxiosResponse<BulkUpdateConfigs200>;
|
||||
|
||||
export type { UpdateConfigBody, BulkUpdateConfigsBodyItem };
|
||||
@@ -1,78 +0,0 @@
|
||||
import type { AxiosRequestConfig } from "axios";
|
||||
|
||||
import apiInstance from "@/config/api";
|
||||
import type {
|
||||
DeleteFileResult,
|
||||
GetDownloadUrlResult,
|
||||
GetPresignedUrlParams,
|
||||
GetPresignedUrlResult,
|
||||
ListFilesResult,
|
||||
RegisterFileBody,
|
||||
RegisterFileResult,
|
||||
UpdateFileBody,
|
||||
UpdateFileResult,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* Generates a pre-signed URL for direct upload to MinIO
|
||||
* @summary Get Presigned URL
|
||||
*/
|
||||
export const getPresignedUrl = <TData = GetPresignedUrlResult>(
|
||||
params: GetPresignedUrlParams,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.get(`/api/files/generate-presigned-url`, {
|
||||
...options,
|
||||
params: { ...params, ...options?.params },
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers file metadata in the database
|
||||
* @summary Register File Metadata
|
||||
*/
|
||||
export const registerFile = <TData = RegisterFileResult>(
|
||||
registerFileBody: RegisterFileBody,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.post(`/api/files/register`, registerFileBody, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Lists user files
|
||||
* @summary List Files
|
||||
*/
|
||||
export const listFiles = <TData = ListFilesResult>(options?: AxiosRequestConfig): Promise<TData> => {
|
||||
return apiInstance.get(`/api/files/list`, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a pre-signed URL for downloading a private file
|
||||
* @summary Get Download URL
|
||||
*/
|
||||
export const getDownloadUrl = <TData = GetDownloadUrlResult>(
|
||||
objectName: string,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.get(`/api/files/${objectName}/download`, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes a user file
|
||||
* @summary Delete File
|
||||
*/
|
||||
export const deleteFile = <TData = DeleteFileResult>(id: string, options?: AxiosRequestConfig): Promise<TData> => {
|
||||
return apiInstance.delete(`/api/files/delete/${id}`, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates file metadata in the database
|
||||
* @summary Update File Metadata
|
||||
*/
|
||||
export const updateFile = <TData = UpdateFileResult>(
|
||||
id: string,
|
||||
updateFileBody: UpdateFileBody,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.patch(`/api/files/update/${id}`, updateFileBody, options);
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
import type {
|
||||
DeleteFile200,
|
||||
GetDownloadUrl200,
|
||||
GetPresignedUrl200,
|
||||
GetPresignedUrlParams,
|
||||
ListFiles200,
|
||||
RegisterFile201,
|
||||
RegisterFileBody,
|
||||
UpdateFile200,
|
||||
UpdateFileBody,
|
||||
} from "../../models";
|
||||
|
||||
export type GetPresignedUrlResult = AxiosResponse<GetPresignedUrl200>;
|
||||
export type RegisterFileResult = AxiosResponse<RegisterFile201>;
|
||||
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 };
|
||||
@@ -1,6 +0,0 @@
|
||||
export * from "./auth";
|
||||
export * from "./users";
|
||||
export * from "./files";
|
||||
export * from "./shares";
|
||||
export * from "./config";
|
||||
export * from "./app";
|
||||
@@ -1,181 +0,0 @@
|
||||
import type { AxiosRequestConfig } from "axios";
|
||||
|
||||
import apiInstance from "@/config/api";
|
||||
import type {
|
||||
AddFilesBody,
|
||||
AddFilesResult,
|
||||
AddRecipientsBody,
|
||||
AddRecipientsResult,
|
||||
CreateShareAliasBody,
|
||||
CreateShareAliasResult,
|
||||
CreateShareBody,
|
||||
CreateShareResult,
|
||||
DeleteShareResult,
|
||||
GetShareByAliasParams,
|
||||
GetShareByAliasResult,
|
||||
GetShareParams,
|
||||
GetShareResult,
|
||||
ListUserSharesResult,
|
||||
NotifyRecipientsBody,
|
||||
NotifyRecipientsResult,
|
||||
RemoveFilesBody,
|
||||
RemoveFilesResult,
|
||||
RemoveRecipientsBody,
|
||||
RemoveRecipientsResult,
|
||||
UpdateShareBody,
|
||||
UpdateSharePasswordBody,
|
||||
UpdateSharePasswordResult,
|
||||
UpdateShareResult,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* Create a new share
|
||||
* @summary Create a new share
|
||||
*/
|
||||
export const createShare = <TData = CreateShareResult>(
|
||||
createShareBody: CreateShareBody,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.post(`/api/shares/create`, createShareBody, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update a share
|
||||
* @summary Update a share
|
||||
*/
|
||||
export const updateShare = <TData = UpdateShareResult>(
|
||||
updateShareBody: UpdateShareBody,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.put(`/api/shares/update`, updateShareBody, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* List all shares created by the authenticated user
|
||||
* @summary List all shares created by the authenticated user
|
||||
*/
|
||||
export const listUserShares = <TData = ListUserSharesResult>(options?: AxiosRequestConfig): Promise<TData> => {
|
||||
return apiInstance.get(`/api/shares/list`, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a share by ID
|
||||
* @summary Get a share by ID
|
||||
*/
|
||||
export const getShare = <TData = GetShareResult>(
|
||||
shareId: string,
|
||||
params?: GetShareParams,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.get(`/api/shares/details/${shareId}`, {
|
||||
...options,
|
||||
params: { ...params, ...options?.params },
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a share
|
||||
* @summary Delete a share
|
||||
*/
|
||||
export const deleteShare = <TData = DeleteShareResult>(id: string, options?: AxiosRequestConfig): Promise<TData> => {
|
||||
return apiInstance.delete(`/api/shares/delete/${id}`, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Update share password
|
||||
*/
|
||||
export const updateSharePassword = <TData = UpdateSharePasswordResult>(
|
||||
shareId: string,
|
||||
updateSharePasswordBody: UpdateSharePasswordBody,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.patch(`api/shares/password/update/${shareId}`, updateSharePasswordBody, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Add files to share
|
||||
*/
|
||||
export const addFiles = <TData = AddFilesResult>(
|
||||
shareId: string,
|
||||
addFilesBody: AddFilesBody,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.post(`/api/shares/files/add/${shareId}`, addFilesBody, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Remove files from share
|
||||
*/
|
||||
export const removeFiles = <TData = RemoveFilesResult>(
|
||||
shareId: string,
|
||||
removeFilesBody: RemoveFilesBody,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.delete(`/api/shares/files/remove/${shareId}`, {
|
||||
data: removeFilesBody,
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Add recipients to a share
|
||||
*/
|
||||
export const addRecipients = <TData = AddRecipientsResult>(
|
||||
shareId: string,
|
||||
addRecipientsBody: AddRecipientsBody,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.post(`/api/shares/recipients/add/${shareId}`, addRecipientsBody, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove recipients from a share
|
||||
* @summary Remove recipients from a share
|
||||
*/
|
||||
export const removeRecipients = <TData = RemoveRecipientsResult>(
|
||||
shareId: string,
|
||||
removeRecipientsBody: RemoveRecipientsBody,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.delete(`/api/shares/recipients/remove/${shareId}`, {
|
||||
data: removeRecipientsBody,
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Create or update share alias
|
||||
*/
|
||||
export const createShareAlias = <TData = CreateShareAliasResult>(
|
||||
shareId: string,
|
||||
createShareAliasBody: CreateShareAliasBody,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.post(`/api/shares/alias/create/${shareId}`, createShareAliasBody, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Get share by alias
|
||||
*/
|
||||
export const getShareByAlias = <TData = GetShareByAliasResult>(
|
||||
alias: string,
|
||||
params?: GetShareByAliasParams,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.get(`/api/shares/alias/get/${alias}`, {
|
||||
...options,
|
||||
params: { ...params, ...options?.params },
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Send email notification with share link to all recipients
|
||||
* @summary Send email notification to share recipients
|
||||
*/
|
||||
export const notifyRecipients = <TData = NotifyRecipientsResult>(
|
||||
shareId: string,
|
||||
notifyRecipientsBody: NotifyRecipientsBody,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.post(`/api/shares/recipients/notify/${shareId}`, notifyRecipientsBody, options);
|
||||
};
|
||||
@@ -1,56 +0,0 @@
|
||||
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 CreateShareResult = AxiosResponse<CreateShare201>;
|
||||
export type UpdateShareResult = AxiosResponse<UpdateShare200>;
|
||||
export type ListUserSharesResult = AxiosResponse<ListUserShares200>;
|
||||
export type GetShareResult = AxiosResponse<GetShare200>;
|
||||
export type DeleteShareResult = AxiosResponse<DeleteShare200>;
|
||||
export type UpdateSharePasswordResult = AxiosResponse<UpdateSharePassword200>;
|
||||
export type AddFilesResult = AxiosResponse<AddFiles200>;
|
||||
export type RemoveFilesResult = AxiosResponse<RemoveFiles200>;
|
||||
export type AddRecipientsResult = AxiosResponse<AddRecipients200>;
|
||||
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,127 +0,0 @@
|
||||
import type { AxiosRequestConfig } from "axios";
|
||||
|
||||
import apiInstance from "@/config/api";
|
||||
import type {
|
||||
ActivateUserResult,
|
||||
DeactivateUserResult,
|
||||
DeleteUserResult,
|
||||
GetUserByIdResult,
|
||||
ListUsersResult,
|
||||
RegisterUserBody,
|
||||
RegisterUserResult,
|
||||
RemoveAvatarResult,
|
||||
UpdateUserBody,
|
||||
UpdateUserImageBody,
|
||||
UpdateUserImageResult,
|
||||
UpdateUserResult,
|
||||
UploadAvatarBody,
|
||||
UploadAvatarResult,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* Register a new user (admin only)
|
||||
* @summary Register New User
|
||||
*/
|
||||
export const registerUser = <TData = RegisterUserResult>(
|
||||
registerUserBody: RegisterUserBody,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.post(`/api/users/register`, registerUserBody, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* List all users (admin only)
|
||||
* @summary List All Users
|
||||
*/
|
||||
export const listUsers = <TData = ListUsersResult>(options?: AxiosRequestConfig): Promise<TData> => {
|
||||
return apiInstance.get(`/api/users/list`, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update user data (admin only)
|
||||
* @summary Update User Data
|
||||
*/
|
||||
export const updateUser = <TData = UpdateUserResult>(
|
||||
updateUserBody: UpdateUserBody,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.put(`/api/users/update`, updateUserBody, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a user by ID (admin only)
|
||||
* @summary Get User by ID
|
||||
*/
|
||||
export const getUserById = <TData = GetUserByIdResult>(id: string, options?: AxiosRequestConfig): Promise<TData> => {
|
||||
return apiInstance.get(`/api/users/details/${id}`, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a user (admin only)
|
||||
* @summary Delete User
|
||||
*/
|
||||
export const deleteUser = <TData = DeleteUserResult>(id: string, options?: AxiosRequestConfig): Promise<TData> => {
|
||||
return apiInstance.delete(`/api/users/delete/${id}`, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Activate a user (admin only)
|
||||
* @summary Activate User
|
||||
*/
|
||||
export const activateUser = <TData = ActivateUserResult>(id: string, options?: AxiosRequestConfig): Promise<TData> => {
|
||||
return apiInstance.patch(`/api/users/activate/${id}`, undefined, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Deactivate a user (admin only)
|
||||
* @summary Deactivate User
|
||||
*/
|
||||
export const deactivateUser = <TData = DeactivateUserResult>(
|
||||
id: string,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.patch(`/api/users/deactivate/${id}`, undefined, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update user profile image (admin only)
|
||||
* @summary Update User Image
|
||||
*/
|
||||
export const updateUserImage = <TData = UpdateUserImageResult>(
|
||||
id: string,
|
||||
updateUserImageBody: UpdateUserImageBody,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
return apiInstance.patch(`/api/users/update-image/${id}`, updateUserImageBody, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Upload and update user profile image
|
||||
* @summary Upload user avatar
|
||||
*/
|
||||
export const uploadAvatar = <TData = UploadAvatarResult>(
|
||||
uploadAvatarBody: UploadAvatarBody,
|
||||
options?: AxiosRequestConfig
|
||||
): Promise<TData> => {
|
||||
const formData = new FormData();
|
||||
|
||||
if (uploadAvatarBody.file !== undefined) {
|
||||
formData.append("file", uploadAvatarBody.file as Blob);
|
||||
}
|
||||
|
||||
return apiInstance.post(`/api/users/avatar/upload`, formData, {
|
||||
...options,
|
||||
headers: {
|
||||
...options?.headers,
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove user profile image
|
||||
* @summary Remove user avatar
|
||||
*/
|
||||
export const removeAvatar = <TData = RemoveAvatarResult>(options?: AxiosRequestConfig): Promise<TData> => {
|
||||
return apiInstance.delete(`/api/users/avatar/remove`, options);
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
import type {
|
||||
ActivateUser200,
|
||||
DeactivateUser200,
|
||||
DeleteUser200,
|
||||
GetUserById200,
|
||||
ListUsers200Item,
|
||||
RegisterUser201,
|
||||
RegisterUserBody,
|
||||
RemoveAvatar200,
|
||||
UpdateUser200,
|
||||
UpdateUserBody,
|
||||
UpdateUserImage200,
|
||||
UpdateUserImageBody,
|
||||
UploadAvatar200,
|
||||
UploadAvatarBody,
|
||||
} from "../../models";
|
||||
|
||||
export type RegisterUserResult = AxiosResponse<RegisterUser201>;
|
||||
export type ListUsersResult = AxiosResponse<ListUsers200Item[]>;
|
||||
export type UpdateUserResult = AxiosResponse<UpdateUser200>;
|
||||
export type GetUserByIdResult = AxiosResponse<GetUserById200>;
|
||||
export type DeleteUserResult = AxiosResponse<DeleteUser200>;
|
||||
export type ActivateUserResult = AxiosResponse<ActivateUser200>;
|
||||
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;
|
||||
};
|
||||
@@ -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 AddRecipients400 = {
|
||||
/** 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 AddRecipients401 = {
|
||||
/** 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 AddRecipients404 = {
|
||||
/** 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 AddRecipientsBody = {
|
||||
emails: 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 { BulkUpdateConfigs200ConfigsItem } from "./bulkUpdateConfigs200ConfigsItem";
|
||||
|
||||
export type BulkUpdateConfigs200 = {
|
||||
configs: BulkUpdateConfigs200ConfigsItem[];
|
||||
};
|
||||
@@ -1,20 +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 BulkUpdateConfigs200ConfigsItem = {
|
||||
/** The config key */
|
||||
key: string;
|
||||
/** The config value */
|
||||
value: string;
|
||||
/** The config type */
|
||||
type: string;
|
||||
/** The config group */
|
||||
group: string;
|
||||
/** The config 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 BulkUpdateConfigs400 = {
|
||||
/** 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 BulkUpdateConfigs401 = {
|
||||
/** 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 BulkUpdateConfigs403 = {
|
||||
/** Error message */
|
||||
error: string;
|
||||
};
|
||||
@@ -1,14 +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 BulkUpdateConfigsBodyItem = {
|
||||
/** The config key */
|
||||
key: string;
|
||||
/** The config value */
|
||||
value: string;
|
||||
};
|
||||
@@ -1,14 +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 CheckHealth200 = {
|
||||
/** The health status */
|
||||
status: string;
|
||||
/** The timestamp of the health check */
|
||||
timestamp: string;
|
||||
};
|
||||
@@ -1,20 +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 { CheckUploadAllowed200FileSizeInfo } from "./checkUploadAllowed200FileSizeInfo";
|
||||
|
||||
export type CheckUploadAllowed200 = {
|
||||
/** The server disk size in GB */
|
||||
diskSizeGB: number;
|
||||
/** The server disk used in GB */
|
||||
diskUsedGB: number;
|
||||
/** The server disk available in GB */
|
||||
diskAvailableGB: number;
|
||||
/** Whether file upload is allowed */
|
||||
uploadAllowed: boolean;
|
||||
fileSizeInfo: CheckUploadAllowed200FileSizeInfo;
|
||||
};
|
||||
@@ -1,14 +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 CheckUploadAllowed200FileSizeInfo = {
|
||||
bytes: number;
|
||||
kb: number;
|
||||
mb: number;
|
||||
gb: number;
|
||||
};
|
||||
@@ -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 CheckUploadAllowed400 = {
|
||||
/** 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 CheckUploadAllowed500 = {
|
||||
/** Error message */
|
||||
error: string;
|
||||
};
|
||||
@@ -1,14 +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 CheckUploadAllowedParams = {
|
||||
/**
|
||||
* The file size in bytes
|
||||
*/
|
||||
fileSize: 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 { CreateShare201Share } from "./createShare201Share";
|
||||
|
||||
export type CreateShare201 = {
|
||||
share: CreateShare201Share;
|
||||
};
|
||||
@@ -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 { CreateShare201ShareAlias } from "./createShare201ShareAlias";
|
||||
import type { CreateShare201ShareFilesItem } from "./createShare201ShareFilesItem";
|
||||
import type { CreateShare201ShareRecipientsItem } from "./createShare201ShareRecipientsItem";
|
||||
import type { CreateShare201ShareSecurity } from "./createShare201ShareSecurity";
|
||||
|
||||
export type CreateShare201Share = {
|
||||
/** 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: CreateShare201ShareSecurity;
|
||||
files: CreateShare201ShareFilesItem[];
|
||||
recipients: CreateShare201ShareRecipientsItem[];
|
||||
/** @nullable */
|
||||
alias: CreateShare201ShareAlias;
|
||||
};
|
||||
@@ -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 CreateShare201ShareAlias = {
|
||||
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 CreateShare201ShareFilesItem = {
|
||||
/** 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 CreateShare201ShareRecipientsItem = {
|
||||
/** 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 CreateShare201ShareSecurity = {
|
||||
/**
|
||||
* 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 CreateShare400 = {
|
||||
/** 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 CreateShare401 = {
|
||||
/** 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 { CreateShareAlias200Alias } from "./createShareAlias200Alias";
|
||||
|
||||
export type CreateShareAlias200 = {
|
||||
alias: CreateShareAlias200Alias;
|
||||
};
|
||||
@@ -1,15 +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 CreateShareAlias200Alias = {
|
||||
id: string;
|
||||
alias: string;
|
||||
shareId: string;
|
||||
createdAt: string;
|
||||
updatedAt: 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 CreateShareAlias400 = {
|
||||
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 CreateShareAlias401 = {
|
||||
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 CreateShareAlias404 = {
|
||||
error: string;
|
||||
};
|
||||
@@ -1,16 +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 CreateShareAliasBody = {
|
||||
/**
|
||||
* @minLength 3
|
||||
* @maxLength 30
|
||||
* @pattern ^[a-zA-Z0-9]+$
|
||||
*/
|
||||
alias: string;
|
||||
};
|
||||
@@ -1,26 +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 CreateShareBody = {
|
||||
/** The share name */
|
||||
name?: string;
|
||||
/** The share description */
|
||||
description?: string;
|
||||
expiration?: string;
|
||||
/** The file IDs */
|
||||
files: string[];
|
||||
/** The share password */
|
||||
password?: string;
|
||||
/**
|
||||
* The maximum number of views
|
||||
* @nullable
|
||||
*/
|
||||
maxViews?: number | null;
|
||||
/** The recipient emails */
|
||||
recipients?: string[];
|
||||
};
|
||||
@@ -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 DeactivateUser200 = {
|
||||
/** 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 DeactivateUser400 = {
|
||||
/** 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 DeactivateUser401 = {
|
||||
/** 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 DeactivateUser403 = {
|
||||
/** Error message */
|
||||
error: string;
|
||||
};
|
||||
@@ -1,9 +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 Def0 = 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 DeleteFile200 = {
|
||||
/** The file deletion message */
|
||||
message: 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 DeleteFile400 = {
|
||||
/** 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 DeleteFile401 = {
|
||||
/** 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 DeleteFile404 = {
|
||||
/** 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 DeleteFile500 = {
|
||||
/** Error message */
|
||||
error: string;
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user