diff --git a/app/(pages)/dashboard/buckets/[bucket-name]/page.tsx b/app/(pages)/dashboard/buckets/[bucket-name]/page.tsx
deleted file mode 100644
index e63d32a..0000000
--- a/app/(pages)/dashboard/buckets/[bucket-name]/page.tsx
+++ /dev/null
@@ -1,277 +0,0 @@
-"use client";
-
-import React from "react";
-import { Button } from "@/components/ui/button";
-import { Upload, Check, ChevronsUpDown } from "lucide-react";
-import dynamic from "next/dynamic";
-
-import {
- Command,
- CommandEmpty,
- CommandGroup,
- CommandInput,
- CommandItem,
- CommandList,
-} from "@/components/ui/command";
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@/components/ui/popover";
-import { cn } from "@/lib/utils";
-import { S3File, S3Root } from "@/types/S3Objects";
-
-type FileType =
- | "document"
- | "compressed"
- | "image"
- | "audio"
- | "video"
- | "unknown"
- | "folder";
-
-const mockFileData: S3Root = {
- folders: [
- {
- folders: [],
- name: "compressed",
- files: [
- {
- extension: "rar",
- etag: '"dd9bb762731449c0e06a2c869b739db1"',
- size: "1.56 MB",
- type: "compressed",
- name: "lab.rar",
- location: "compressed/lab.rar",
- lastModified: "2025-03-08T05:15:12Z",
- },
- ],
- location: "compressed/",
- type: "folder",
- itemCount: 1,
- },
- {
- folders: [],
- name: "documents",
- files: [
- {
- extension: "pdf",
- etag: '"52e419c0508aaeea9213f513b84e8d73"',
- size: "9.17 KB",
- type: "document",
- name: "rptTimeTableStudent.pdf",
- location: "documents/rptTimeTableStudent.pdf",
- lastModified: "2025-03-08T05:15:11Z",
- },
- ],
- location: "documents/",
- type: "folder",
- itemCount: 1,
- },
- {
- folders: [],
- name: "videos",
- files: [
- {
- extension: "mp4",
- etag: '"447b7f6a669fc87a1e5c1d506474a7cb"',
- size: "1.12 MB",
- type: "video",
- name: "VID_4556446.mp4",
- location: "videos/VID_4556446.mp4",
- lastModified: "2025-03-08T05:15:12Z",
- },
- {
- extension: "mp4",
- etag: '"4aa28625598a7adfb7ec3fef5e44453b-2"',
- size: "31.48 MB",
- type: "video",
- name: "VID_5468553.mp4",
- location: "videos/VID_5468553.mp4",
- lastModified: "2025-03-08T05:15:11Z",
- },
- ],
- location: "videos/",
- type: "folder",
- itemCount: 2,
- },
- ],
- name: "root",
- files: [
- {
- extension: "jpg",
- etag: '"8aa445cbeca5119ec77c4e4bb760e12e"',
- size: "130.04 KB",
- type: "image",
- name: "IMG_2135545.jpg",
- location: "IMG_2135545.jpg",
- lastModified: "2025-03-08T05:15:13Z",
- },
- {
- extension: "jpg",
- etag: '"3ee4b3bb7fe145c77a883db5b201a172"',
- size: "93.66 KB",
- type: "image",
- name: "IMG_2135546.jpg",
- location: "IMG_2135546.jpg",
- lastModified: "2025-03-08T05:15:13Z",
- },
- ],
- location: "",
- type: "folder",
- itemCount: 5,
-};
-
-const fileTypes = [
- { value: "all", label: "All Files" },
- { value: "document", label: "Documents" },
- { value: "compressed", label: "Compressed" },
- { value: "image", label: "Images" },
- { value: "audio", label: "Audio" },
- { value: "video", label: "Video" },
- { value: "folder", label: "Folders" },
-];
-
-const Files = dynamic(() => import("@/components/files-component/files"));
-const SearchBar = dynamic(() => import("@/components/bucket-component/search"));
-const FilterButtons = dynamic(
- () => import("@/components/bucket-component/filter")
-);
-
-export default function Page() {
- const [searchTerm, setSearchTerm] = React.useState("");
- const [activeFilter, setActiveFilter] = React.useState<
- "size" | "date" | null
- >(null);
- const [open, setOpen] = React.useState(false);
- const [selectedType, setSelectedType] = React.useState("all");
-
- const filteredAndSortedData = React.useMemo(() => {
- const filterItems = (items: S3File[] | S3Root["folders"]) => {
- return items.filter((item) =>
- item.name.toLowerCase().includes(searchTerm.toLowerCase())
- );
- };
-
- let filteredFiles = filterItems(mockFileData.files);
- const filteredFolders = filterItems(mockFileData.folders);
-
- // Apply type filter
- if (selectedType !== "all") {
- filteredFiles = filteredFiles.filter((file) => file.type === selectedType);
- }
-
- // Apply date or size filter only to files
- if (activeFilter === "date") {
- filteredFiles = [...filteredFiles].sort((a, b) => {
- if ('lastModified' in a && 'lastModified' in b) {
- return new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime();
- }
- return 0;
- });
- } else if (activeFilter === "size") {
- const convertToBytes = (size: string) => {
- const num = parseFloat(size);
- if (size.includes("GB")) return num * 1024 * 1024 * 1024;
- if (size.includes("MB")) return num * 1024 * 1024;
- if (size.includes("KB")) return num * 1024;
- return num;
- };
- filteredFiles = [...filteredFiles].sort((a, b) => {
- if ('size' in a && 'size' in b) {
- return convertToBytes(b.size) - convertToBytes(a.size);
- }
- return 0;
- });
- }
-
- return {
- files: filteredFiles,
- folders: filteredFolders,
- };
- }, [searchTerm, activeFilter, selectedType]);
-
-
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- No results found.
-
-
- {fileTypes.map((type) => (
- {
- setSelectedType(currentValue);
- setOpen(false);
- }}
- className="dark:text-gray-100 dark:hover:bg-gray-700"
- >
- {type.label}
-
-
- ))}
-
-
-
-
-
-
-
-
-
- {filteredAndSortedData.folders.map((folder) => (
-
- ))}
- {filteredAndSortedData.files.map((file) => (
-
- ))}
-
-
- );
-}
diff --git a/app/(pages)/dashboard/buckets/[bucketName]/page.tsx b/app/(pages)/dashboard/buckets/[bucketName]/page.tsx
new file mode 100644
index 0000000..37ed119
--- /dev/null
+++ b/app/(pages)/dashboard/buckets/[bucketName]/page.tsx
@@ -0,0 +1,213 @@
+"use client";
+
+import React from "react";
+import { Button } from "@/components/ui/button";
+import { Upload } from "lucide-react";
+import dynamic from "next/dynamic";
+import { S3File, S3Folder, S3Root } from "@/types/S3Objects";
+import Breadcrumb from "@/components/bucket-component/breadcrumb";
+import LoadingSkeleton from "@/components/bucket-component/loading-skeleton";
+import FileTypeFilter from "@/components/bucket-component/file-type-filter";
+import { s3Service } from "@/services/s3.service";
+
+type FileType =
+ | "document"
+ | "compressed"
+ | "image"
+ | "audio"
+ | "video"
+ | "unknown"
+ | "folder";
+
+const Files = dynamic(() => import("@/components/files-component/files"));
+const SearchBar = dynamic(() => import("@/components/bucket-component/search"));
+const FilterButtons = dynamic(
+ () => import("@/components/bucket-component/filter")
+);
+
+interface PageProps {
+ params: Promise<{
+ bucketName: string;
+ }>;
+}
+
+interface FileData {
+ readonly name: string;
+ readonly itemCount?: number;
+ readonly size?: string;
+ readonly extension?: string;
+ readonly key?: string;
+}
+
+export default function Page({ params }: PageProps) {
+ const resolvedParams = React.use(params);
+ const [searchTerm, setSearchTerm] = React.useState("");
+ const [activeFilter, setActiveFilter] = React.useState<
+ "size" | "date" | null
+ >(null);
+ const [selectedType, setSelectedType] = React.useState("all");
+ const [currentPath, setCurrentPath] = React.useState("");
+ const [isLoading, setIsLoading] = React.useState(true);
+ const [error, setError] = React.useState(null);
+ const [fileData, setFileData] = React.useState(null);
+
+ const fetchFiles = React.useCallback(async () => {
+ try {
+ setIsLoading(true);
+ setError(null);
+
+ const response = await s3Service.listObjects({
+ bucketName: resolvedParams.bucketName,
+ objectPrefix: currentPath,
+ });
+
+ setFileData(response.data);
+ } catch (err) {
+ if (err instanceof Error) {
+ console.error("Error fetching files:", err);
+ setError(err.message || "Failed to fetch files");
+ }
+ } finally {
+ setIsLoading(false);
+ }
+ }, [resolvedParams.bucketName, currentPath]);
+
+ React.useEffect(() => {
+ fetchFiles();
+ }, [fetchFiles]);
+
+ const handleFolderClick = (folder: S3Folder) => {
+ setCurrentPath(folder.key);
+ };
+
+ const filteredAndSortedData = React.useMemo(() => {
+ if (!fileData) return { files: [], folders: [] };
+
+ const filterFiles = (files: S3File[] = []) => {
+ return files.filter((file) => {
+ const fileName = file.key.split("/").pop() || "";
+ return fileName.toLowerCase().includes(searchTerm.toLowerCase());
+ });
+ };
+
+ const filterFolders = (folders: S3Folder[] = []) => {
+ return folders.filter((folder) => {
+ const folderName = folder.key.replace(/\/$/, "");
+ return folderName.toLowerCase().includes(searchTerm.toLowerCase());
+ });
+ };
+
+ let filteredFiles = filterFiles(fileData.files || []);
+ const filteredFolders = filterFolders(fileData.folders || []);
+
+ if (selectedType !== "all") {
+ filteredFiles = filteredFiles.filter(
+ (file) => file.fileType.toLowerCase() === selectedType
+ );
+ }
+
+ if (activeFilter === "date") {
+ filteredFiles = [...filteredFiles].sort((a, b) => {
+ return (
+ new Date(b.lastModified).getTime() -
+ new Date(a.lastModified).getTime()
+ );
+ });
+ } else if (activeFilter === "size") {
+ const convertToBytes = (size: string) => {
+ const num = parseFloat(size);
+ if (size.includes("GB")) return num * 1024 * 1024 * 1024;
+ if (size.includes("MB")) return num * 1024 * 1024;
+ if (size.includes("KB")) return num * 1024;
+ return num;
+ };
+ filteredFiles = [...filteredFiles].sort((a, b) => {
+ return convertToBytes(b.size) - convertToBytes(a.size);
+ });
+ }
+
+ return {
+ files: filteredFiles,
+ folders: filteredFolders,
+ };
+ }, [fileData, searchTerm, activeFilter, selectedType]);
+
+ const mapS3FileToFileData = (file: S3File): FileData => ({
+ name: file.key.split("/").pop() || "",
+ size: file.size,
+ extension: file.extension,
+ key: file.key,
+ });
+
+ const mapS3FolderToFileData = (folder: S3Folder): FileData => ({
+ name: folder.key.replace(/\/$/, ""),
+ itemCount: 0,
+ key: folder.key,
+ });
+
+ if (error) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {isLoading ? (
+
+ ) : (
+
+ {filteredAndSortedData.folders.map((folder) => (
+
+ handleFolderClick(folder)}
+ />
+
+ ))}
+ {filteredAndSortedData.files.map((file) => (
+
+
+
+ ))}
+
+ )}
+
+
+ );
+}
diff --git a/app/(pages)/dashboard/buckets/page.tsx b/app/(pages)/dashboard/buckets/page.tsx
index dff3b7f..d37cb88 100644
--- a/app/(pages)/dashboard/buckets/page.tsx
+++ b/app/(pages)/dashboard/buckets/page.tsx
@@ -1,36 +1,24 @@
"use client";
-import { useState, useMemo } from "react";
-import dynamic from "next/dynamic";
-import { CreateBucket } from "@/components/bucket-component/create-bkt";
+import { useState } from "react";
import useBuckets from "@/hooks/useBuckets";
+import { useBucketFiltering } from "@/hooks/useBucketFiltering";
+import dynamic from "next/dynamic";
-// Dynamically imported components with loading placeholders
-const SearchBar = dynamic(
- () => import("@/components/bucket-component/search"),
- {
- loading: () => (
-
- ),
- }
-);
-const FilterButtons = dynamic(
- () => import("@/components/bucket-component/filter"),
- {
- loading: () => ,
- }
+const BucketToolbar = dynamic(() =>
+ import("@/components/bucket-component/bucket-toolbar").then(
+ (mod) => mod.default
+ )
);
-const BucketCard = dynamic(
- () => import("@/components/bucket-component/bucket-card"),
- {
- loading: () => ,
- }
+
+const BucketGrid = dynamic(() =>
+ import("@/components/bucket-component/bucket-grid").then((mod) => mod.default)
);
-const SelectRegions = dynamic(
- () => import("@/components/bucket-component/regions"),
- {
- loading: () => ,
- }
+
+const BucketGridSkeleton = dynamic(() =>
+ import("@/components/bucket-component/bucket-grid-skeleton").then(
+ (mod) => mod.default
+ )
);
export default function Page() {
@@ -40,69 +28,36 @@ export default function Page() {
);
const [searchTerm, setSearchTerm] = useState("");
- // Compute the displayed buckets based on the search term and active filter
- const displayedBuckets = useMemo(() => {
- let data = [...buckets];
+ const filteredBuckets = useBucketFiltering({
+ buckets,
+ searchTerm,
+ activeFilter,
+ });
- if (searchTerm) {
- data = data.filter((bucket) =>
- bucket.bucketName.toLowerCase().includes(searchTerm.toLowerCase())
- );
- }
- if (activeFilter === "size") {
- data = data.sort((a, b) => {
- const sizeToBytes = (size: string) => {
- const [value, unit] = size.split(" ");
- const numValue = parseFloat(value);
- switch (unit?.toUpperCase()) {
- case "KB":
- return numValue * 1024;
- case "MB":
- return numValue * 1024 * 1024;
- case "GB":
- return numValue * 1024 * 1024 * 1024;
- case "TB":
- return numValue * 1024 * 1024 * 1024 * 1024;
- default:
- return numValue;
- }
- };
- return sizeToBytes(b.size || "0") - sizeToBytes(a.size || "0");
- });
- } else if (activeFilter === "date") {
- data = data.sort(
- (a, b) =>
- new Date(b.createdOn).getTime() - new Date(a.createdOn).getTime()
- );
- }
- return data;
- }, [buckets, searchTerm, activeFilter]);
+ if (isError) {
+ return (
+
+ );
+ }
return (
-
-
-
-
-
-
+
+
{isLoading ? (
-
- {[...Array(4)].map((_, index) => (
-
- ))}
-
- ) : isError ? (
-
{isError}
+
) : (
-
- {displayedBuckets.map((bucket) => (
-
- ))}
-
+
)}
);
diff --git a/app/(pages)/login/components/login-form.tsx b/app/(pages)/login/components/login-form.tsx
index 9775cd2..a9f3052 100644
--- a/app/(pages)/login/components/login-form.tsx
+++ b/app/(pages)/login/components/login-form.tsx
@@ -9,12 +9,20 @@ import { useRouter } from "next/navigation";
import { toast } from "sonner";
import SpinnerIcon from "@/icons/spinner-icon";
import Redirecting from "@/skeleton/redirecting";
+import { useAuth } from "@/hooks/useAuth";
-export default function LoginForm({
- className,
- ...props
-}: React.ComponentPropsWithoutRef<"form">) {
- const [credentials, setCredentials] = useState({
+interface LoginCredentials {
+ accessKey: string;
+ secretKey: string;
+ region: string;
+}
+
+interface LoginFormProps extends React.ComponentPropsWithoutRef<"form"> {
+ className?: string;
+}
+
+export default function LoginForm({ className, ...props }: LoginFormProps) {
+ const [credentials, setCredentials] = useState({
accessKey: "",
secretKey: "",
region: "ap-south-1",
@@ -22,37 +30,31 @@ export default function LoginForm({
const [loading, setLoading] = useState(false);
const [isRedirecting, setIsRedirecting] = useState(false);
const router = useRouter();
+ const { login } = useAuth();
+
+ const handleInputChange =
+ (field: keyof LoginCredentials) =>
+ (e: React.ChangeEvent) => {
+ setCredentials((prev) => ({
+ ...prev,
+ [field]: e.target.value,
+ }));
+ };
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
- const response = await fetch(
- `${process.env.NEXT_PUBLIC_API_BASE_URL}/auth/login`,
- {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify(credentials),
- }
- );
-
- const data = await response.json();
-
- if (response.ok && data.status === "SUCCESS" && data.data.sessionToken) {
- document.cookie = `sessionToken=${data.data.sessionToken}; path=/; Secure; SameSite=Strict`;
- toast.success("Login successful!");
+ const success = await login(credentials);
+ if (success) {
setIsRedirecting(true);
setTimeout(() => {
router.push("/dashboard/buckets");
}, 200);
- } else {
- toast.error(data.message || "Invalid credentials");
}
} catch (error) {
- console.error("Error:", error);
+ console.error("Login error:", error);
toast.error("Failed to validate credentials");
} finally {
setLoading(false);
@@ -88,12 +90,7 @@ export default function LoginForm({
maxLength={264}
value={credentials.accessKey}
className="dark:bg-gray-800 dark:text-white dark:border-gray-700 h-12"
- onChange={(e) =>
- setCredentials((prev) => ({
- ...prev,
- accessKey: e.target.value,
- }))
- }
+ onChange={handleInputChange("accessKey")}
/>
@@ -109,12 +106,7 @@ export default function LoginForm({
disabled={loading}
value={credentials.secretKey}
className="dark:bg-gray-800 dark:text-white dark:border-gray-700 h-12"
- onChange={(e) =>
- setCredentials((prev) => ({
- ...prev,
- secretKey: e.target.value,
- }))
- }
+ onChange={handleInputChange("secretKey")}
/>