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:
Daniel Luiz Alves
2025-04-16 13:53:51 -03:00
parent a119ab4d46
commit efba0b75dc
636 changed files with 3789 additions and 33213 deletions

41
apps/app/.gitignore vendored
View File

@@ -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

View File

@@ -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"
}

View File

@@ -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"]

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
</>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
</>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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",
},
};

View File

@@ -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);
}
},
};
});

View File

@@ -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);

View File

@@ -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,
};
}

View File

@@ -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,
};
}

View File

@@ -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 },
});
};

View File

@@ -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 };

View File

@@ -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);
};

View File

@@ -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 };

View File

@@ -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);
};

View File

@@ -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 };

View File

@@ -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);
};

View File

@@ -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 };

View File

@@ -1,6 +0,0 @@
export * from "./auth";
export * from "./users";
export * from "./files";
export * from "./shares";
export * from "./config";
export * from "./app";

View File

@@ -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);
};

View File

@@ -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,
};

View File

@@ -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);
};

View File

@@ -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 };

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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[];
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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[];
};

View File

@@ -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[];
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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[];
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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