{"id":22442757,"url":"https://github.com/developer-ronnie/cloudvault","last_synced_at":"2025-09-21T16:16:09.966Z","repository":{"id":266385685,"uuid":"898189331","full_name":"Developer-RONNIE/cloudvault","owner":"Developer-RONNIE","description":"A storage management and file sharing platform that lets users effortlessly upload, organize, and share files. Built with the latest Next.js 15 and the Appwrite Node SDK, utilizing advanced features for seamless file management.","archived":false,"fork":false,"pushed_at":"2024-12-17T22:18:53.000Z","size":1165,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-01T15:09:15.053Z","etag":null,"topics":["appwrite-auth","appwrite-database","appwrite-storage","nextjs","storage-management-system"],"latest_commit_sha":null,"homepage":"https://cloudvault-ruby.vercel.app/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Developer-RONNIE.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-12-04T00:20:38.000Z","updated_at":"2024-12-17T22:22:05.000Z","dependencies_parsed_at":"2024-12-04T02:29:30.701Z","dependency_job_id":"70268a8e-59f2-4233-8d4e-0e26199ba322","html_url":"https://github.com/Developer-RONNIE/cloudvault","commit_stats":null,"previous_names":["developer-ronnie/cloudvault"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Developer-RONNIE%2Fcloudvault","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Developer-RONNIE%2Fcloudvault/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Developer-RONNIE%2Fcloudvault/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Developer-RONNIE%2Fcloudvault/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Developer-RONNIE","download_url":"https://codeload.github.com/Developer-RONNIE/cloudvault/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245823318,"owners_count":20678173,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["appwrite-auth","appwrite-database","appwrite-storage","nextjs","storage-management-system"],"created_at":"2024-12-06T02:20:22.840Z","updated_at":"2025-09-21T16:16:04.920Z","avatar_url":"https://github.com/Developer-RONNIE.png","language":"TypeScript","readme":"\u003cdiv align=\"center\"\u003e\n  \u003cbr /\u003e\n    \u003ca href=\"https://cloudvault-ruby.vercel.app/\" target=\"_blank\"\u003e\n      \u003cimg src=\"https://github.com/Developer-RONNIE/cloudvault/blob/main/public/assets/icons/git-banner.png\" alt=\"Project Banner\"\u003e\n    \u003c/a\u003e\n  \u003cbr /\u003e\n\n  \u003cdiv\u003e\n     \u003cimg src=\"https://img.shields.io/badge/-Next_JS-black?style=for-the-badge\u0026logoColor=white\u0026logo=nextdotjs\u0026color=000000\" alt=\"nextdotjs\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/-TypeScript-black?style=for-the-badge\u0026logoColor=white\u0026logo=typescript\u0026color=3178C6\" alt=\"typescript\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/-Tailwind_CSS-black?style=for-the-badge\u0026logoColor=white\u0026logo=tailwindcss\u0026color=06B6D4\" alt=\"tailwindcss\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/-Appwrite-black?style=for-the-badge\u0026logoColor=white\u0026logo=appwrite\u0026color=FD366E\" alt=\"appwrite\" /\u003e\n  \u003c/div\u003e\n\n\u003ch3 align=\"center\"\u003eCloudvault - Storage and File Sharing Platform\u003c/h3\u003e\n\u003c/div\u003e\n\n## 📋 \u003ca name=\"table\"\u003eTable of Contents\u003c/a\u003e\n\n1. 🤖 [Introduction](#introduction)\n2. ⚙️ [Tech Stack](#tech-stack)\n3. 🔋 [Features](#features)\n4. 🤸 [Quick Start](#quick-start)\n5. 🕸️ [Snippets (Code to Copy)](#snippets)\n6. 🔗 [Assets](#links)\n\n\n\n## \u003ca name=\"introduction\"\u003e🤖 Introduction\u003c/a\u003e\n\nA storage management and file sharing platform that lets users effortlessly upload, organize, and share files. Built with the latest Next.js 15 and the Appwrite Node SDK, utilizing advanced features for seamless file management.\n\nIf you're getting started and need assistance or face any bugs, join our active Discord community. It's a place where people help each other out.\n\n\u003ca href=\"https://discord.gg/P2ZdEgfzTZ\" target=\"_blank\"\u003e\u003cimg src=\"https://github.com/sujatagunale/EasyRead/assets/151519281/618f4872-1e10-42da-8213-1d69e486d02e\" /\u003e\u003c/a\u003e\n\n## \u003ca name=\"tech-stack\"\u003e⚙️ Tech Stack\u003c/a\u003e\n\n- React 19\n- Next.js 15\n- Appwrite\n- TailwindCSS\n- ShadCN\n- TypeScript\n\n## \u003ca name=\"features\"\u003e🔋 Features\u003c/a\u003e\n\n👉 **User Authentication with Appwrite**: Implement signup, login, and logout functionality using Appwrite's authentication system.\n\n👉 **FIle Uploads**: Effortlessly upload a variety of file types, including documents, images, videos, and audio, ensuring all your important data.\n\n👉 **View and Manage Files**: Users can browse through their uploaded files stored in Appwrite storage, view on a new tab, rename file or delete.\n\n👉 **Download Files**: Users can download their uploaded files giving them instant access to essential documents.\n\n👉 **File Sharing**: Users can easily share their uploaded files with others, enabling collaboration and easy access to important content.\n\n👉 **Dashboard**: Gain insights at a glance with a dynamic dashboard that showcases total and consumed storage, recent uploads, and a summary of files grouped by type.\n\n👉 **Global Search**: Users can quickly find files and shared content across the platform with a robust global search feature.\n\n👉 **Sorting Options**: Organize files efficiently by sorting them by date, name, or size, making file management a breeze.\n\n👉 **Modern Responsive Design**: A fresh and minimalist UI that emphasizes usability, ensuring a clean aesthetic across all devices.\n\nand many more, including the latest **React 19**, **Next.js 15** and **Appwrite** features alongside code architecture and\nreusability\n\n## \u003ca name=\"quick-start\"\u003e🤸 Quick Start\u003c/a\u003e\n\nFollow these steps to set up the project locally on your machine.\n\n**Prerequisites**\n\nMake sure you have the following installed on your machine:\n\n- [Git](https://git-scm.com/)\n- [Node.js](https://nodejs.org/en)\n- [npm](https://www.npmjs.com/) (Node Package Manager)\n\n**Cloning the Repository**\n\n```bash\ngit clone https://github.com/Developer-RONNIE/cloudvault.git\ncd cloudvault\n```\n\n**Installation**\n\nInstall the project dependencies using npm:\n\n```bash\nnpm install\n```\n\n**Set Up Environment Variables**\n\nCreate a new file named `.env.local` in the root of your project and add the following content:\n\n```env\nNEXT_PUBLIC_APPWRITE_ENDPOINT=\"https://cloud.appwrite.io/v1\"\nNEXT_PUBLIC_APPWRITE_PROJECT=\"\"\nNEXT_PUBLIC_APPWRITE_DATABASE=\"\"\nNEXT_PUBLIC_APPWRITE_USERS_COLLECTION=\"\"\nNEXT_PUBLIC_APPWRITE_FILES_COLLECTION=\"\"\nNEXT_PUBLIC_APPWRITE_BUCKET=\"\"\nNEXT_APPWRITE_KEY=\"\"\n```\n\nReplace the values with your actual Appwrite credentials. You can obtain these credentials by signing up \u0026\ncreating a new project on the [Appwrite website](https://appwrite.io/).\n\n**Running the Project**\n\n```bash\nnpm run dev\n```\n\nOpen [http://localhost:3000](http://localhost:3000) in your browser to view the project.\n\n## \u003ca name=\"snippets\"\u003e🕸️ Snippets\u003c/a\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003etailwind.config.ts\u003c/code\u003e\u003c/summary\u003e\n\n```typescript\nimport type { Config } from 'tailwindcss';\n\nconst config: Config = {\n  darkMode: ['class'],\n  content: [\n    './pages/**/*.{js,ts,jsx,tsx,mdx}',\n    './components/**/*.{js,ts,jsx,tsx,mdx}',\n    './app/**/*.{js,ts,jsx,tsx,mdx}',\n  ],\n  theme: {\n    extend: {\n      colors: {\n        brand: {\n          DEFAULT: '#FA7275',\n          100: '#EA6365',\n        },\n        red: '#FF7474',\n        error: '#b80000',\n        green: '#3DD9B3',\n        blue: '#56B8FF',\n        pink: '#EEA8FD',\n        orange: '#F9AB72',\n        light: {\n          100: '#333F4E',\n          200: '#A3B2C7',\n          300: '#F2F5F9',\n          400: '#F2F4F8',\n        },\n        dark: {\n          100: '#04050C',\n          200: '#131524',\n        },\n      },\n      fontFamily: {\n        poppins: ['var(--font-poppins)'],\n      },\n      boxShadow: {\n        'drop-1': '0px 10px 30px 0px rgba(66, 71, 97, 0.1)',\n        'drop-2': '0 8px 30px 0 rgba(65, 89, 214, 0.3)',\n        'drop-3': '0 8px 30px 0 rgba(65, 89, 214, 0.1)',\n      },\n      borderRadius: {\n        lg: 'var(--radius)',\n        md: 'calc(var(--radius) - 2px)',\n        sm: 'calc(var(--radius) - 4px)',\n      },\n      keyframes: {\n        'caret-blink': {\n          '0%,70%,100%': { opacity: '1' },\n          '20%,50%': { opacity: '0' },\n        },\n      },\n      animation: {\n        'caret-blink': 'caret-blink 1.25s ease-out infinite',\n      },\n    },\n  },\n  plugins: [require('tailwindcss-animate')],\n};\nexport default config;\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003eglobals.css\u003c/code\u003e\u003c/summary\u003e\n\n```css\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n  * {\n    @apply scroll-smooth;\n  }\n\n  body {\n    @apply bg-white text-dark-200 min-h-screen;\n  }\n\n  .custom-scrollbar::-webkit-scrollbar {\n    width: 6px;\n    height: 3px;\n    border-radius: 50px;\n  }\n\n  .custom-scrollbar::-webkit-scrollbar-track {\n    background: transparent;\n  }\n\n  .custom-scrollbar::-webkit-scrollbar-thumb {\n    background: #e5e7eb;\n    border-radius: 50px;\n  }\n\n  .custom-scrollbar::-webkit-scrollbar-thumb:hover {\n    background: #fa7275;\n  }\n\n  /* Remove scrollbar */\n  .remove-scrollbar::-webkit-scrollbar {\n    width: 0px;\n    height: 0px;\n    border-radius: 0px;\n  }\n\n  .remove-scrollbar::-webkit-scrollbar-track {\n    background: transparent;\n  }\n\n  .remove-scrollbar::-webkit-scrollbar-thumb {\n    background: transparent;\n    border-radius: 0px;\n  }\n\n  .remove-scrollbar::-webkit-scrollbar-thumb:hover {\n    /* background: #1e2238; */\n    background: transparent;\n  }\n\n  .recharts-responsive-container {\n    height: initial !important;\n  }\n}\n\n@layer utilities {\n  /* ===== TYPOGRAPHY */\n  .h1 {\n    @apply text-[34px] leading-[42px] font-bold;\n  }\n  .h2 {\n    @apply text-[24px] leading-[36px] font-bold;\n  }\n  .h3 {\n    @apply text-[20px] leading-[28px] font-semibold;\n  }\n  .h4 {\n    @apply text-[18px] leading-[20px] font-medium;\n  }\n  .h5 {\n    @apply text-[16px] leading-[24px] font-semibold;\n  }\n  .subtitle-1 {\n    @apply text-[16px] leading-[24px] font-medium;\n  }\n  .subtitle-2 {\n    @apply text-[14px] leading-[20px] font-semibold;\n  }\n  .body-1 {\n    @apply text-[16px] leading-[24px] font-normal;\n  }\n  .body-2 {\n    @apply text-[14px] leading-[20px] font-normal;\n  }\n  .button {\n    @apply text-[14px] leading-[20px] font-medium;\n  }\n  .caption {\n    @apply text-[12px] leading-[16px] font-normal;\n  }\n  .overline {\n    @apply text-[10px] leading-[14px] font-normal;\n  }\n\n  /* ===== HELPER CLASSES */\n  .container {\n    @apply mx-auto max-w-7xl px-5;\n  }\n  .primary-btn {\n    @apply bg-brand hover:bg-brand-100 transition-all rounded-full button !important;\n  }\n  .flex-center {\n    @apply flex items-center justify-center;\n  }\n\n  /* =====  SHADCN OVERRIDES */\n  .shad-no-focus {\n    @apply outline-none ring-offset-transparent focus:ring-transparent focus:ring-offset-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-transparent focus-visible:ring-offset-0 !important;\n  }\n  .shad-input {\n    @apply border-none shadow-none p-0 shad-no-focus placeholder:text-light-200 body-2 !important;\n  }\n\n  .shad-form-item {\n    @apply flex h-[78px] flex-col justify-center rounded-xl border border-light-300 px-4 shadow-drop-1;\n  }\n  .shad-form-label {\n    @apply text-light-100 pt-2 body-2 w-full !important;\n  }\n  .shad-form-message {\n    @apply text-red body-2 ml-4 !important;\n  }\n  .shad-alert-dialog {\n    @apply space-y-4 max-w-[95%] sm:w-fit rounded-xl md:rounded-[30px] px-4 md:px-8 py-10 bg-white outline-none !important;\n  }\n  .shad-submit-btn {\n    @apply bg-brand button hover:bg-brand-100 transition-all rounded-full !important;\n  }\n  .shad-otp {\n    @apply w-full flex gap-1 sm:gap-2 justify-between !important;\n  }\n  .shad-otp-slot {\n    @apply text-[40px] font-medium rounded-xl ring-brand shadow-drop-1 text-brand-100 justify-center flex border-2 border-light-300 size-12 md:size-16 gap-5 !important;\n  }\n\n  .shad-sheet {\n    @apply pt-0 !important;\n  }\n  .shad-sheet button,\n  .shad-dialog button {\n    @apply focus:ring-0 focus:ring-offset-0 focus-visible:border-none outline-none focus-visible:outline-none focus-visible:ring-transparent focus-visible:ring-offset-0 !important;\n  }\n  .shad-dropdown-item {\n    @apply cursor-pointer !important;\n  }\n  .shad-dialog {\n    @apply rounded-[26px] w-[90%] max-w-[400px] px-6 py-8   !important;\n  }\n  .shad-chart-title {\n    @apply text-white !important;\n  }\n  .shad-select-item {\n    @apply cursor-pointer !important;\n  }\n\n  /* Sidebar \u0026 MobileNavigation */\n  .nav-icon {\n    @apply w-6 filter invert opacity-25 !important;\n  }\n  .nav-icon-active {\n    @apply invert-0 opacity-100 !important;\n  }\n\n  /* =====  STYLE CLASSES */\n\n  /* Root Layout */\n  .main-content {\n    @apply remove-scrollbar h-full flex-1 overflow-auto bg-light-400 px-5 py-7 sm:mr-7 sm:rounded-[30px] md:mb-7 md:px-9 md:py-10 !important;\n  }\n\n  /* Dashboard */\n  .dashboard-container {\n    @apply mx-auto grid max-w-7xl grid-cols-1 gap-6 md:grid-cols-2 xl:gap-10 !important;\n  }\n  .dashboard-summary-list {\n    @apply mt-6 grid grid-cols-1 gap-4 xl:mt-10 xl:grid-cols-2 xl:gap-9 !important;\n  }\n  .dashboard-summary-card {\n    @apply relative mt-6 rounded-[20px] bg-white p-5 transition-all hover:scale-105 !important;\n  }\n  .summary-type-icon {\n    @apply absolute -left-3 top-[-25px] z-10 w-[190px] object-contain !important;\n  }\n  .summary-type-size {\n    @apply h4 relative z-20 w-full text-right !important;\n  }\n  .summary-type-title {\n    @apply h5 relative z-20 text-center !important;\n  }\n  .dashboard-recent-files {\n    @apply h-full rounded-[20px] xl:h-[654px] custom-scrollbar overflow-auto bg-white p-5 xl:p-7 !important;\n  }\n  .recent-file-details {\n    @apply flex w-full justify-between items-center !important;\n  }\n  .recent-file-name {\n    @apply subtitle-2 line-clamp-1 w-full text-light-100 sm:max-w-[200px] lg:max-w-[250px] !important;\n  }\n  .recent-file-date {\n    @apply body-2 text-light-100/80 !important;\n  }\n  .empty-list {\n    @apply body-1 mt-10 text-center text-light-200 !important;\n  }\n\n  /* Type page */\n  .page-container {\n    @apply mx-auto flex w-full max-w-7xl flex-col items-center gap-8 !important;\n  }\n  .total-size-section {\n    @apply flex mt-2 flex-col justify-between sm:flex-row sm:items-center !important;\n  }\n  .file-list {\n    @apply grid w-full gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 !important;\n  }\n  .sort-container {\n    @apply mt-5 flex items-center sm:mt-0 sm:gap-3 !important;\n  }\n\n  /* ActionsDropdown */\n  .rename-input-field {\n    @apply body-2 shad-no-focus h-[52px] w-full rounded-full border px-4 shadow-drop-1 !important;\n  }\n  .delete-confirmation {\n    @apply text-center text-light-100 !important;\n  }\n  .delete-file-name {\n    @apply font-medium text-brand-100 !important;\n  }\n  .modal-cancel-button {\n    @apply h-[52px] flex-1 rounded-full bg-white text-light-100 hover:bg-transparent !important;\n  }\n  .modal-submit-button {\n    @apply primary-btn !mx-0 h-[52px] w-full flex-1 !important;\n  }\n\n  /* ActionsModalContent */\n  .file-details-thumbnail {\n    @apply !mb-1 flex items-center gap-3 rounded-xl border border-light-200/40 bg-light-400/50 p-3 !important;\n  }\n  .file-details-label {\n    @apply body-2 w-[30%] text-light-100 !important;\n  }\n  .file-details-value {\n    @apply subtitle-2 flex-1 !important;\n  }\n\n  .share-wrapper {\n    @apply !mt-2 space-y-2 !important;\n  }\n  .share-input-field {\n    @apply body-2 shad-no-focus h-[52px] w-full rounded-full border px-4 shadow-drop-1 !important;\n  }\n  .share-remove-user {\n    @apply rounded-full bg-transparent text-light-100 shadow-none hover:bg-transparent !important;\n  }\n  .remove-icon {\n    @apply aspect-square rounded-full !important;\n  }\n\n  /* AuthForm */\n  .auth-form {\n    @apply flex max-h-[800px] w-full max-w-[580px] flex-col justify-center space-y-6 transition-all lg:h-full lg:space-y-8 !important;\n  }\n  .form-title {\n    @apply h1 text-center text-light-100 md:text-left !important;\n  }\n  .form-submit-button {\n    @apply primary-btn h-[66px] !important;\n  }\n  .error-message {\n    @apply body-2 mx-auto w-fit rounded-xl bg-error/5 px-8 py-4 text-center text-error !important;\n  }\n\n  /* Card */\n  .file-card {\n    @apply flex cursor-pointer flex-col gap-6 rounded-[18px] bg-white p-5 shadow-sm transition-all hover:shadow-drop-3 !important;\n  }\n  .file-card-details {\n    @apply flex flex-col gap-2 text-light-100 !important;\n  }\n\n  /* Chart */\n  .chart {\n    @apply flex items-center rounded-[20px] bg-brand p-2 text-white md:flex-col xl:flex-row !important;\n  }\n  .chart-container {\n    @apply mx-auto aspect-square w-[180px] text-white xl:w-[200px] !important;\n  }\n  .polar-grid {\n    @apply first:fill-white/20 last:fill-brand !important;\n  }\n  .chart-details {\n    @apply flex-1 items-start px-3 py-0 sm:px-5 lg:p-3 xl:pr-5 !important;\n  }\n  .chart-total-percentage {\n    @apply fill-white text-4xl font-bold !important;\n  }\n  .chart-title {\n    @apply h3 font-bold md:text-center lg:text-left !important;\n  }\n  .chart-description {\n    @apply subtitle-1 mt-2 w-full text-white/70 md:text-center lg:text-left !important;\n  }\n\n  /* FileUploader */\n  .uploader-button {\n    @apply primary-btn h-[52px] gap-2 px-10 shadow-drop-1 !important;\n  }\n  .uploader-preview-list {\n    @apply fixed bottom-10 right-10 z-50 flex size-full h-fit max-w-[480px] flex-col gap-3 rounded-[20px] bg-white p-7 shadow-drop-3 !important;\n  }\n  .uploader-preview-item {\n    @apply flex items-center justify-between  gap-3 rounded-xl p-3 shadow-drop-3 !important;\n  }\n  .preview-item-name {\n    @apply subtitle-2 mb-2 line-clamp-1 max-w-[300px] !important;\n  }\n\n  .error-toast {\n    @apply bg-red !rounded-[10px] !important;\n  }\n\n  /* Header */\n  .header {\n    @apply hidden items-center justify-between gap-5 p-5 sm:flex lg:py-7 xl:gap-10 !important;\n  }\n  .header-wrapper {\n    @apply flex-center min-w-fit gap-4 !important;\n  }\n  .sign-out-button {\n    @apply flex-center h-[52px] min-w-[54px] items-center rounded-full bg-brand/10 p-0 text-brand shadow-none transition-all hover:bg-brand/20 !important;\n  }\n\n  /* Mobile Navigation */\n  .mobile-header {\n    @apply flex h-[60px] justify-between px-5 sm:hidden !important;\n  }\n  .header-user {\n    @apply my-3 flex items-center gap-2 rounded-full p-1 text-light-100 sm:justify-center sm:bg-brand/10 lg:justify-start lg:p-3 !important;\n  }\n  .header-user-avatar {\n    @apply aspect-square w-10 rounded-full object-cover !important;\n  }\n  .mobile-nav {\n    @apply h5 flex-1 gap-1 text-brand !important;\n  }\n  .mobile-nav-list {\n    @apply flex flex-1 flex-col gap-4 !important;\n  }\n  .mobile-nav-item {\n    @apply flex text-light-100 gap-4 w-full justify-start items-center h5 px-6 h-[52px] rounded-full !important;\n  }\n  .mobile-sign-out-button {\n    @apply h5 flex h-[52px] w-full items-center gap-4 rounded-full bg-brand/10 px-6 text-brand shadow-none transition-all hover:bg-brand/20 !important;\n  }\n\n  /* OTP Modal */\n  .otp-close-button {\n    @apply absolute -right-1 -top-7 cursor-pointer sm:-right-2 sm:-top-4  !important;\n  }\n\n  /* Search */\n  .search {\n    @apply relative w-full md:max-w-[480px] !important;\n  }\n  .search-input-wrapper {\n    @apply flex h-[52px] flex-1 items-center gap-3 rounded-full px-4 shadow-drop-3 !important;\n  }\n  .search-input {\n    @apply body-2 shad-no-focus  placeholder:body-1 w-full border-none p-0 shadow-none placeholder:text-light-200 !important;\n  }\n  .search-result {\n    @apply absolute left-0 top-16 z-50 flex w-full flex-col gap-3 rounded-[20px] bg-white p-4 !important;\n  }\n  .empty-result {\n    @apply body-2 text-center text-light-100 !important;\n  }\n\n  /* Sidebar */\n  .sidebar {\n    @apply remove-scrollbar hidden h-screen w-[90px] flex-col overflow-auto px-5 py-7 sm:flex lg:w-[280px] xl:w-[325px] !important;\n  }\n  .sidebar-nav {\n    @apply h5 mt-9 flex-1 gap-1 text-brand !important;\n  }\n  .sidebar-nav-item {\n    @apply flex text-light-100 gap-4 rounded-xl lg:w-full justify-center lg:justify-start items-center h5 lg:px-[30px] h-[52px] lg:rounded-full !important;\n  }\n  .sidebar-user-info {\n    @apply mt-4 flex items-center justify-center gap-2 rounded-full bg-brand/10 p-1 text-light-100 lg:justify-start lg:p-3 !important;\n  }\n  .sidebar-user-avatar {\n    @apply aspect-square w-10 rounded-full object-cover !important;\n  }\n\n  .shad-active {\n    @apply bg-brand text-white shadow-drop-2 !important;\n  }\n\n  /* Sort */\n  .sort-select {\n    @apply shad-no-focus h-11 w-full rounded-[8px] border-transparent bg-white !shadow-sm sm:w-[210px] !important;\n  }\n  .sort-select-content {\n    @apply !shadow-drop-3 !important;\n  }\n\n  /* Thumbnail */\n  .thumbnail {\n    @apply flex-center size-[50px] min-w-[50px] overflow-hidden rounded-full bg-brand/10;\n  }\n  .thumbnail-image {\n    @apply size-full object-cover object-center !important;\n  }\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003econstants/index.ts\u003c/code\u003e\u003c/summary\u003e\n\n```typescript\nexport const navItems = [\n  {\n    name: 'Dashboard',\n    icon: '/assets/icons/dashboard.svg',\n    url: '/',\n  },\n  {\n    name: 'Documents',\n    icon: '/assets/icons/documents.svg',\n    url: '/documents',\n  },\n  {\n    name: 'Images',\n    icon: '/assets/icons/images.svg',\n    url: '/images',\n  },\n  {\n    name: 'Media',\n    icon: '/assets/icons/video.svg',\n    url: '/media',\n  },\n  {\n    name: 'Others',\n    icon: '/assets/icons/others.svg',\n    url: '/others',\n  },\n];\n\nexport const actionsDropdownItems = [\n  {\n    label: 'Rename',\n    icon: '/assets/icons/edit.svg',\n    value: 'rename',\n  },\n  {\n    label: 'Details',\n    icon: '/assets/icons/info.svg',\n    value: 'details',\n  },\n  {\n    label: 'Share',\n    icon: '/assets/icons/share.svg',\n    value: 'share',\n  },\n  {\n    label: 'Download',\n    icon: '/assets/icons/download.svg',\n    value: 'download',\n  },\n  {\n    label: 'Delete',\n    icon: '/assets/icons/delete.svg',\n    value: 'delete',\n  },\n];\n\nexport const sortTypes = [\n  {\n    label: 'Date created (newest)',\n    value: '$createdAt-desc',\n  },\n  {\n    label: 'Created Date (oldest)',\n    value: '$createdAt-asc',\n  },\n  {\n    label: 'Name (A-Z)',\n    value: 'name-asc',\n  },\n  {\n    label: 'Name (Z-A)',\n    value: 'name-desc',\n  },\n  {\n    label: 'Size (Highest)',\n    value: 'size-desc',\n  },\n  {\n    label: 'Size (Lowest)',\n    value: 'size-asc',\n  },\n];\n\nexport const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003elib/utils.ts\u003c/code\u003e\u003c/summary\u003e\n\n```typescript\nimport { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\nexport const parseStringify = (value: unknown) =\u003e\n  JSON.parse(JSON.stringify(value));\n\nexport const convertFileToUrl = (file: File) =\u003e URL.createObjectURL(file);\n\nexport const convertFileSize = (sizeInBytes: number, digits?: number) =\u003e {\n  if (sizeInBytes \u003c 1024) {\n    return sizeInBytes + ' Bytes'; // Less than 1 KB, show in Bytes\n  } else if (sizeInBytes \u003c 1024 * 1024) {\n    const sizeInKB = sizeInBytes / 1024;\n    return sizeInKB.toFixed(digits || 1) + ' KB'; // Less than 1 MB, show in KB\n  } else if (sizeInBytes \u003c 1024 * 1024 * 1024) {\n    const sizeInMB = sizeInBytes / (1024 * 1024);\n    return sizeInMB.toFixed(digits || 1) + ' MB'; // Less than 1 GB, show in MB\n  } else {\n    const sizeInGB = sizeInBytes / (1024 * 1024 * 1024);\n    return sizeInGB.toFixed(digits || 2) + ' GB'; // 1 GB or more, show in GB\n  }\n};\n\nexport const calculateAngle = (sizeInBytes: number) =\u003e {\n  const totalSizeInBytes = 2 * 1024 * 1024 * 1024; // 2GB in bytes\n  const percentage = (sizeInBytes / totalSizeInBytes) * 360;\n  return Number(percentage.toFixed(2));\n};\n\nexport const calculatePercentage = (sizeInBytes: number) =\u003e {\n  const totalSizeInBytes = 2 * 1024 * 1024 * 1024; // 2GB in bytes\n  const percentage = (sizeInBytes / totalSizeInBytes) * 100;\n  return Number(percentage.toFixed(1));\n};\n\nexport const getFileType = (fileName: string) =\u003e {\n  const extension = fileName.split('.').pop()?.toLowerCase();\n\n  if (!extension) return { type: 'other', extension: '' };\n\n  const documentExtensions = [\n    'pdf',\n    'doc',\n    'docx',\n    'txt',\n    'xls',\n    'xlsx',\n    'csv',\n    'rtf',\n    'ods',\n    'ppt',\n    'odp',\n    'md',\n    'html',\n    'htm',\n    'epub',\n    'pages',\n    'fig',\n    'psd',\n    'ai',\n    'indd',\n    'xd',\n    'sketch',\n    'afdesign',\n    'afphoto',\n    'afphoto',\n  ];\n  const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp'];\n  const videoExtensions = ['mp4', 'avi', 'mov', 'mkv', 'webm'];\n  const audioExtensions = ['mp3', 'wav', 'ogg', 'flac'];\n\n  if (documentExtensions.includes(extension))\n    return { type: 'document', extension };\n  if (imageExtensions.includes(extension)) return { type: 'image', extension };\n  if (videoExtensions.includes(extension)) return { type: 'video', extension };\n  if (audioExtensions.includes(extension)) return { type: 'audio', extension };\n\n  return { type: 'other', extension };\n};\n\nexport const formatDateTime = (isoString: string | null | undefined) =\u003e {\n  if (!isoString) return '—';\n\n  const date = new Date(isoString);\n\n  // Get hours and adjust for 12-hour format\n  let hours = date.getHours();\n  const minutes = date.getMinutes();\n  const period = hours \u003e= 12 ? 'pm' : 'am';\n\n  // Convert hours to 12-hour format\n  hours = hours % 12 || 12;\n\n  // Format the time and date parts\n  const time = `${hours}:${minutes.toString().padStart(2, '0')}${period}`;\n  const day = date.getDate();\n  const monthNames = [\n    'Jan',\n    'Feb',\n    'Mar',\n    'Apr',\n    'May',\n    'Jun',\n    'Jul',\n    'Aug',\n    'Sep',\n    'Oct',\n    'Nov',\n    'Dec',\n  ];\n  const month = monthNames[date.getMonth()];\n\n  return `${time}, ${day} ${month}`;\n};\n\nexport const getFileIcon = (\n  extension: string | undefined,\n  type: FileType | string,\n) =\u003e {\n  switch (extension) {\n    // Document\n    case 'pdf':\n      return '/assets/icons/file-pdf.svg';\n    case 'doc':\n      return '/assets/icons/file-doc.svg';\n    case 'docx':\n      return '/assets/icons/file-docx.svg';\n    case 'csv':\n      return '/assets/icons/file-csv.svg';\n    case 'txt':\n      return '/assets/icons/file-txt.svg';\n    case 'xls':\n    case 'xlsx':\n      return '/assets/icons/file-document.svg';\n    // Image\n    case 'svg':\n      return '/assets/icons/file-image.svg';\n    // Video\n    case 'mkv':\n    case 'mov':\n    case 'avi':\n    case 'wmv':\n    case 'mp4':\n    case 'flv':\n    case 'webm':\n    case 'm4v':\n    case '3gp':\n      return '/assets/icons/file-video.svg';\n    // Audio\n    case 'mp3':\n    case 'mpeg':\n    case 'wav':\n    case 'aac':\n    case 'flac':\n    case 'ogg':\n    case 'wma':\n    case 'm4a':\n    case 'aiff':\n    case 'alac':\n      return '/assets/icons/file-audio.svg';\n\n    default:\n      switch (type) {\n        case 'image':\n          return '/assets/icons/file-image.svg';\n        case 'document':\n          return '/assets/icons/file-document.svg';\n        case 'video':\n          return '/assets/icons/file-video.svg';\n        case 'audio':\n          return '/assets/icons/file-audio.svg';\n        default:\n          return '/assets/icons/file-other.svg';\n      }\n  }\n};\n\n// APPWRITE URL UTILS\n// Construct appwrite file URL - https://appwrite.io/docs/apis/rest#images\nexport const constructFileUrl = (bucketFileId: string) =\u003e {\n  return `${process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT}/storage/buckets/${process.env.NEXT_PUBLIC_APPWRITE_BUCKET}/files/${bucketFileId}/view?project=${process.env.NEXT_PUBLIC_APPWRITE_PROJECT}`;\n};\n\nexport const constructDownloadUrl = (bucketFileId: string) =\u003e {\n  return `${process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT}/storage/buckets/${process.env.NEXT_PUBLIC_APPWRITE_BUCKET}/files/${bucketFileId}/download?project=${process.env.NEXT_PUBLIC_APPWRITE_PROJECT}`;\n};\n\n// DASHBOARD UTILS\nexport const getUsageSummary = (totalSpace: any) =\u003e {\n  return [\n    {\n      title: 'Documents',\n      size: totalSpace.document.size,\n      latestDate: totalSpace.document.latestDate,\n      icon: '/assets/icons/file-document-light.svg',\n      url: '/documents',\n    },\n    {\n      title: 'Images',\n      size: totalSpace.image.size,\n      latestDate: totalSpace.image.latestDate,\n      icon: '/assets/icons/file-image-light.svg',\n      url: '/images',\n    },\n    {\n      title: 'Media',\n      size: totalSpace.video.size + totalSpace.audio.size,\n      latestDate:\n        totalSpace.video.latestDate \u003e totalSpace.audio.latestDate\n          ? totalSpace.video.latestDate\n          : totalSpace.audio.latestDate,\n      icon: '/assets/icons/file-video-light.svg',\n      url: '/media',\n    },\n    {\n      title: 'Others',\n      size: totalSpace.other.size,\n      latestDate: totalSpace.other.latestDate,\n      icon: '/assets/icons/file-other-light.svg',\n      url: '/others',\n    },\n  ];\n};\n\nexport const getFileTypesParams = (type: string) =\u003e {\n  switch (type) {\n    case 'documents':\n      return ['document'];\n    case 'images':\n      return ['image'];\n    case 'media':\n      return ['video', 'audio'];\n    case 'others':\n      return ['other'];\n    default:\n      return ['document'];\n  }\n};\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003eindex.d.ts\u003c/code\u003e\u003c/summary\u003e\n\n```ts\n/* eslint-disable no-unused-vars */\n\ndeclare type FileType = 'document' | 'image' | 'video' | 'audio' | 'other';\n\ndeclare interface ActionType {\n  label: string;\n  icon: string;\n  value: string;\n}\n\ndeclare interface SearchParamProps {\n  params?: Promise\u003cSegmentParams\u003e;\n  searchParams?: Promise\u003c{ [key: string]: string | string[] | undefined }\u003e;\n}\n\ndeclare interface UploadFileProps {\n  file: File;\n  ownerId: string;\n  accountId: string;\n  path: string;\n}\ndeclare interface GetFilesProps {\n  types: FileType[];\n  searchText?: string;\n  sort?: string;\n  limit?: number;\n}\ndeclare interface RenameFileProps {\n  fileId: string;\n  name: string;\n  extension: string;\n  path: string;\n}\ndeclare interface UpdateFileUsersProps {\n  fileId: string;\n  emails: string[];\n  path: string;\n}\ndeclare interface DeleteFileProps {\n  fileId: string;\n  bucketFileId: string;\n  path: string;\n}\n\ndeclare interface FileUploaderProps {\n  ownerId: string;\n  accountId: string;\n  className?: string;\n}\n\ndeclare interface MobileNavigationProps {\n  ownerId: string;\n  accountId: string;\n  fullName: string;\n  avatar: string;\n  email: string;\n}\ndeclare interface SidebarProps {\n  fullName: string;\n  avatar: string;\n  email: string;\n}\n\ndeclare interface ThumbnailProps {\n  type: string;\n  extension: string;\n  url: string;\n  className?: string;\n  imageClassName?: string;\n}\n\ndeclare interface ShareInputProps {\n  file: Models.Document;\n  onInputChange: (e: React.ChangeEvent\u003cHTMLInputElement\u003e) =\u003e void;\n  onRemove: (email: string) =\u003e void;\n}\n```\n\n\u003c/details\u003e\n\n## \u003ca name=\"links\"\u003e🔗 Assets\u003c/a\u003e\n\n- Assets used in the project can be found [here](https://github.com/Developer-RONNIE/cloudvault/tree/main/public/assets)\n\n\n\n\n#","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdeveloper-ronnie%2Fcloudvault","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdeveloper-ronnie%2Fcloudvault","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdeveloper-ronnie%2Fcloudvault/lists"}