├── .gitignore ├── LICENSE ├── README.md ├── eslint.config.mjs ├── fonts └── helvetica │ ├── HelveticaNowDisplay-Black.woff2 │ ├── HelveticaNowDisplay-Bold.woff2 │ ├── HelveticaNowDisplay-ExtBlk.woff2 │ ├── HelveticaNowDisplay-ExtraBold.woff2 │ ├── HelveticaNowDisplay-Light.woff2 │ ├── HelveticaNowDisplay-Medium.woff2 │ ├── HelveticaNowDisplay-Regular.woff2 │ └── HelveticaNowDisplay-Thin.woff2 ├── next.config.ts ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public └── assets │ ├── VOUZ.svg │ ├── close.svg │ ├── delete.svg │ ├── download.svg │ ├── emoji │ ├── pencil.png │ ├── secure.png │ └── share.png │ ├── feature.jpg │ ├── features.jpg │ ├── file.svg │ ├── github.svg │ ├── loading-light.svg │ ├── loading.svg │ ├── meta-image.png │ └── uploadFile.svg ├── src ├── app │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ ├── locker │ │ └── [id] │ │ │ └── page.tsx │ └── page.tsx ├── components │ ├── features.tsx │ ├── footer.tsx │ ├── metrics.tsx │ └── navbar.tsx └── libs │ ├── api.ts │ └── utils.ts ├── tailwind.config.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Zade 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vouz Frontend 2 | 3 | Welcome to the frontend repository of **Vouz**, an open-source, no-login, fully encrypted file-sharing platform. This project allows users to create secure lockers for file storage and sharing without the need for user accounts. For backend implementation details, visit the [Vouz Backend Repository](https://github.com/zadescoxp/locker-backend). 4 | 5 | ## Features 6 | 7 | - **No Login Required**: Share files effortlessly without creating an account. 8 | - **Secure Lockers**: Protect your files with unique passkeys. 9 | - **Easy Sharing**: Share locker names and passkeys to grant access. 10 | - **Open Source**: Contribute to the project and help improve it. 11 | 12 | ## Demo 13 | 14 | Explore the live application at [vouz.tech](https://vouz.tech). 15 | Take a look at the features on [YouTube](https://youtu.be/vPx3gnUQ1K8) 16 | 17 | ## Getting Started 18 | 19 | To set up the project locally, follow these steps: 20 | 21 | 1. **Clone the Repository**: 22 | 23 | ```bash 24 | git clone https://github.com/zadescoxp/locker-frontend.git 25 | cd locker-frontend 26 | ``` 27 | 28 | 2. **Install Dependencies**: 29 | 30 | ```bash 31 | npm install 32 | ``` 33 | 34 | 3. **Configure Environment Variables**: 35 | 36 | Create a `.env.local` file in the root directory with the following variables: 37 | 38 | ```env 39 | LOCK_SECRET=your_lock_secret 40 | NEXT_PUBLIC_BACKEND_URL=http://localhost:5000 41 | ``` 42 | 43 | - `LOCK_SECRET`: Replace `your_lock_secret` with a secure secret key for encryption. 44 | - `NEXT_PUBLIC_BACKEND_URL`: Set this to the URL where your backend server is running. 45 | 46 | > **Note**: Variables prefixed with `NEXT_PUBLIC_` are exposed to the browser. Ensure that no sensitive information is included in these variables. 47 | 48 | 4. **Run the Development Server**: 49 | 50 | ```bash 51 | npm run dev 52 | ``` 53 | 54 | Open [http://localhost:3000](http://localhost:3000) in your browser to view the application. 55 | 56 | ## Contributing 57 | 58 | We welcome contributions! To get started: 59 | 60 | 1. Fork the repository. 61 | 2. Create a new branch: `git checkout -b feature-branch-name`. 62 | 3. Make your changes. 63 | 4. Commit your changes: `git commit -m 'Add new feature'`. 64 | 5. Push to the branch: `git push origin feature-branch-name`. 65 | 6. Open a pull request. 66 | 67 | Please ensure your code adheres to our coding standards and includes appropriate tests. 68 | 69 | ## License 70 | 71 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 72 | 73 | ## Contact 74 | 75 | For questions or suggestions, please open an issue in this repository. 76 | 77 | --- 78 | 79 | Thank you for contributing to Vouz! 80 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /fonts/helvetica/HelveticaNowDisplay-Black.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zadescoxp/locker-frontend/19171c39c9bbf03de0e9e606b2131c4381975da4/fonts/helvetica/HelveticaNowDisplay-Black.woff2 -------------------------------------------------------------------------------- /fonts/helvetica/HelveticaNowDisplay-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zadescoxp/locker-frontend/19171c39c9bbf03de0e9e606b2131c4381975da4/fonts/helvetica/HelveticaNowDisplay-Bold.woff2 -------------------------------------------------------------------------------- /fonts/helvetica/HelveticaNowDisplay-ExtBlk.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zadescoxp/locker-frontend/19171c39c9bbf03de0e9e606b2131c4381975da4/fonts/helvetica/HelveticaNowDisplay-ExtBlk.woff2 -------------------------------------------------------------------------------- /fonts/helvetica/HelveticaNowDisplay-ExtraBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zadescoxp/locker-frontend/19171c39c9bbf03de0e9e606b2131c4381975da4/fonts/helvetica/HelveticaNowDisplay-ExtraBold.woff2 -------------------------------------------------------------------------------- /fonts/helvetica/HelveticaNowDisplay-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zadescoxp/locker-frontend/19171c39c9bbf03de0e9e606b2131c4381975da4/fonts/helvetica/HelveticaNowDisplay-Light.woff2 -------------------------------------------------------------------------------- /fonts/helvetica/HelveticaNowDisplay-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zadescoxp/locker-frontend/19171c39c9bbf03de0e9e606b2131c4381975da4/fonts/helvetica/HelveticaNowDisplay-Medium.woff2 -------------------------------------------------------------------------------- /fonts/helvetica/HelveticaNowDisplay-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zadescoxp/locker-frontend/19171c39c9bbf03de0e9e606b2131c4381975da4/fonts/helvetica/HelveticaNowDisplay-Regular.woff2 -------------------------------------------------------------------------------- /fonts/helvetica/HelveticaNowDisplay-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zadescoxp/locker-frontend/19171c39c9bbf03de0e9e606b2131c4381975da4/fonts/helvetica/HelveticaNowDisplay-Thin.woff2 -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | env: { 6 | LOCK_SECRET: process.env.LOCK_SECRET, 7 | }, 8 | images: { 9 | remotePatterns: [ 10 | { 11 | protocol: "https", 12 | hostname: "vouz-files.s3.eu-north-1.amazonaws.com", 13 | }, 14 | ], 15 | }, 16 | }; 17 | 18 | export default nextConfig; 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "open-weather", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@microsoft/clarity": "^1.0.0", 13 | "@vercel/analytics": "^1.4.1", 14 | "@vercel/speed-insights": "^1.1.0", 15 | "hmm-api": "^1.0.87", 16 | "next": "15.1.2", 17 | "react": "^19.0.0", 18 | "react-dom": "^19.0.0", 19 | "sonner": "^1.7.1", 20 | "tanmayo7lock": "^1.0.18" 21 | }, 22 | "devDependencies": { 23 | "@eslint/eslintrc": "^3", 24 | "@types/node": "^20", 25 | "@types/react": "^19", 26 | "@types/react-dom": "^19", 27 | "eslint": "^9", 28 | "eslint-config-next": "15.1.2", 29 | "postcss": "^8", 30 | "tailwindcss": "^3.4.1", 31 | "typescript": "^5" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /public/assets/VOUZ.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/assets/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/assets/delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/assets/download.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/assets/emoji/pencil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zadescoxp/locker-frontend/19171c39c9bbf03de0e9e606b2131c4381975da4/public/assets/emoji/pencil.png -------------------------------------------------------------------------------- /public/assets/emoji/secure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zadescoxp/locker-frontend/19171c39c9bbf03de0e9e606b2131c4381975da4/public/assets/emoji/secure.png -------------------------------------------------------------------------------- /public/assets/emoji/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zadescoxp/locker-frontend/19171c39c9bbf03de0e9e606b2131c4381975da4/public/assets/emoji/share.png -------------------------------------------------------------------------------- /public/assets/feature.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zadescoxp/locker-frontend/19171c39c9bbf03de0e9e606b2131c4381975da4/public/assets/feature.jpg -------------------------------------------------------------------------------- /public/assets/features.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zadescoxp/locker-frontend/19171c39c9bbf03de0e9e606b2131c4381975da4/public/assets/features.jpg -------------------------------------------------------------------------------- /public/assets/file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/assets/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/assets/loading-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/assets/loading.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/assets/meta-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zadescoxp/locker-frontend/19171c39c9bbf03de0e9e606b2131c4381975da4/public/assets/meta-image.png -------------------------------------------------------------------------------- /public/assets/uploadFile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zadescoxp/locker-frontend/19171c39c9bbf03de0e9e606b2131c4381975da4/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | * { 6 | padding: 0; 7 | margin: 0; 8 | box-sizing: border-box; 9 | } 10 | 11 | body { 12 | overflow-x: hidden; 13 | background: #ffffff; 14 | color: #000000; 15 | } 16 | .helvetica { 17 | font-family: var(--font-helvetica), sans-serif; 18 | } 19 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Toaster } from "sonner"; 3 | import "./globals.css"; 4 | import localFont from "next/font/local"; 5 | import { Analytics } from "@vercel/analytics/react"; 6 | import { SpeedInsights } from "@vercel/speed-insights/next"; 7 | import Metrics from "@/components/metrics" 8 | 9 | 10 | export const metadata: Metadata = { 11 | title: "VOUZ", 12 | description: 13 | "Vouz is an open-source file sharing application that requires no login. Enjoy a hasle free file sharing experience.", 14 | openGraph: { 15 | images: ["/assets/meta-image.png"], 16 | }, 17 | }; 18 | 19 | const helvetica = localFont({ 20 | src: [ 21 | { 22 | path: "../../fonts/helvetica/HelveticaNowDisplay-Thin.woff2", 23 | weight: "100", 24 | style: "normal", 25 | }, 26 | { 27 | path: "../../fonts/helvetica/HelveticaNowDisplay-Regular.woff2", 28 | weight: "200", 29 | style: "normal", 30 | }, 31 | { 32 | path: "../../fonts/helvetica/HelveticaNowDisplay-Medium.woff2", 33 | weight: "300", 34 | style: "normal", 35 | }, 36 | { 37 | path: "../../fonts/helvetica/HelveticaNowDisplay-Bold.woff2", 38 | weight: "400", 39 | style: "normal", 40 | }, 41 | { 42 | path: "../../fonts/helvetica/HelveticaNowDisplay-ExtraBold.woff2", 43 | weight: "500", 44 | style: "normal", 45 | }, 46 | { 47 | path: "../../fonts/helvetica/HelveticaNowDisplay-Black.woff2", 48 | weight: "600", 49 | style: "normal", 50 | }, 51 | { 52 | path: "../../fonts/helvetica/HelveticaNowDisplay-ExtBlk.woff2", 53 | weight: "700", 54 | style: "normal", 55 | }, 56 | ], 57 | variable: "--font-helvetica", 58 | }); 59 | 60 | export default function RootLayout({ 61 | children, 62 | }: Readonly<{ 63 | children: React.ReactNode; 64 | }>) { 65 | return ( 66 | 67 | 68 | 69 | 70 | 71 | 72 | {children} 73 | 74 | 75 | ); 76 | } 77 | -------------------------------------------------------------------------------- /src/app/locker/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import api from "@/libs/api"; 3 | import { encryptObjectValues } from "@/libs/utils"; 4 | import Image from "next/image"; 5 | import Link from "next/link"; 6 | import { redirect } from "next/navigation"; 7 | import { useState, useEffect } from "react"; 8 | import { toast } from "sonner"; 9 | import { encrypt } from "tanmayo7lock"; 10 | 11 | interface FileData { 12 | fileName: string; 13 | url: string; 14 | } 15 | 16 | export default function Locker(props: { params: Promise<{ id: string }> }) { 17 | const [id, setId] = useState(""); 18 | const [auth, setAuth] = useState(false); 19 | const [passkey, setPasskey] = useState(""); 20 | const [name, setName] = useState(""); 21 | const [data, setData] = useState([]); 22 | const [deleteLocker, setDeleteLocker] = useState(false); 23 | const [exists, setExists] = useState(false); 24 | const [image, setImage] = useState(new FormData()); 25 | const [loading, setLoading] = useState(false); 26 | const [pageLoading, setPageLoading] = useState(true); 27 | const [deleteLoading, setDeleteLoading] = useState(false); 28 | const [uploadProgress, setUploadProgress] = useState(0); 29 | 30 | useEffect(() => { 31 | setPageLoading(true); 32 | const loadId = async () => { 33 | const resolvedId = (await props.params).id; 34 | setId(resolvedId); 35 | }; 36 | 37 | loadId(); 38 | }, [props.params]); 39 | 40 | useEffect(() => { 41 | if (!id) return; 42 | get_locker(); 43 | }, [id]); 44 | 45 | const get_locker = async () => { 46 | const response = await api.post( 47 | `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/get/`, 48 | encryptObjectValues({ name: id }) 49 | ); 50 | if (response.success) { 51 | if ((response.data as { status: number }).status === 1) { 52 | setPageLoading(false); 53 | setExists(true); 54 | } 55 | } else { 56 | setPageLoading(false); 57 | } 58 | }; 59 | 60 | const check_key = async () => { 61 | setPageLoading(true); 62 | const payload = { 63 | name: id, 64 | key: passkey, 65 | }; 66 | const response = await api.post( 67 | `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/check_key`, 68 | encryptObjectValues(payload) 69 | ); 70 | if (response.success) { 71 | const responseData = response.data as { 72 | status: number; 73 | name: string; 74 | data: { fileName: string; url: string }[]; 75 | }; 76 | if (responseData.status === 1) { 77 | setAuth(true); 78 | setName(responseData.name); 79 | setData(responseData.data); 80 | setPageLoading(false); 81 | } 82 | } else { 83 | console.log(response.error); 84 | setPageLoading(false); 85 | } 86 | }; 87 | 88 | const delete_locker = async () => { 89 | setDeleteLoading(true); 90 | const payload = { 91 | name: id, 92 | passkey: passkey, 93 | }; 94 | const response = await api.post( 95 | `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/delete`, 96 | encryptObjectValues(payload) 97 | ); 98 | if (response.success) { 99 | if ((response.data as { status: number }).status === 1) { 100 | setDeleteLoading(false); 101 | redirect("/"); 102 | } else { 103 | setDeleteLoading(false); 104 | console.log(response.error); 105 | } 106 | } 107 | }; 108 | 109 | const addFiles = async (e: React.ChangeEvent) => { 110 | const formData = new FormData(); 111 | if (e.target.files && e.target.files[0]) { 112 | formData.append("file", e.target.files[0], e.target.files[0].name); 113 | } 114 | formData.append("name", encrypt(id)); 115 | formData.append("passkey", encrypt(passkey)); 116 | setImage(formData); 117 | }; 118 | 119 | const uploadFile = async () => { 120 | setLoading(true); 121 | const xhr = new XMLHttpRequest(); 122 | 123 | xhr.open( 124 | "POST", 125 | `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/upload/`, 126 | true 127 | ); 128 | xhr.upload.onprogress = (event) => { 129 | if (event.lengthComputable) { 130 | const percentComplete = (event.loaded / event.total) * 100; 131 | setUploadProgress(percentComplete); 132 | } 133 | }; 134 | // Send the form data 135 | xhr.onload = async () => { 136 | const response = JSON.parse(xhr.responseText); 137 | if (xhr.status !== 200) { 138 | toast.error(response.message); 139 | setLoading(false); 140 | setUploadProgress(0); 141 | } else { 142 | await check_key(); 143 | image.delete("file"); 144 | setLoading(false); 145 | setUploadProgress(0); 146 | } 147 | }; 148 | 149 | xhr.onerror = () => { 150 | setLoading(false); 151 | setUploadProgress(0); 152 | console.log("Upload failed"); 153 | }; 154 | 155 | xhr.send(image); 156 | }; 157 | 158 | const deleteFile = async (fileName: string) => { 159 | const payload = { 160 | name: id, 161 | passkey: passkey, 162 | fileName: fileName, 163 | }; 164 | const response = await api.post( 165 | `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/delete_file`, 166 | encryptObjectValues(payload) 167 | ); 168 | if (response.success) { 169 | if ((response.data as { status: number }).status === 1) { 170 | check_key(); 171 | } else { 172 | console.log(response.error); 173 | } 174 | } 175 | }; 176 | 177 | if (pageLoading) { 178 | return ( 179 |
180 | loading 187 |
188 | ); 189 | } 190 | 191 | if (!exists) { 192 | return ( 193 |
194 |
195 |

Locker not found

196 | 202 |
203 |
204 | ); 205 | } 206 | if (!auth) { 207 | return ( 208 |
209 |
210 |

Enter passkey

211 | setPasskey(e.target.value)} 217 | onKeyDown={(e) => e.key === "Enter" && check_key()} 218 | /> 219 | 225 |
226 |
227 | ); 228 | } 229 | 230 | const DeleteLocker = () => { 231 | return ( 232 |
233 |
234 |

Sure you want to delete it ?

235 | 236 | 242 | 245 | 246 |
247 |
248 | ); 249 | }; 250 | 251 | return ( 252 |
253 |
254 |
255 |

{name}

256 | 257 | 263 |
264 | 265 |
266 |

267 | {data.length} {data.length < 2 ? "file" : "files"} 268 |

269 |
270 | {data.length > 0 && 271 | data.map((item, index) => ( 272 |
276 | 277 | file 283 |

284 | {item?.fileName.length > 10 285 | ? `${item?.fileName.substring(0, 12)}...` 286 | : item?.fileName} 287 |

288 |
289 | 290 | 302 | 308 | file 314 | 315 | 316 |
317 | ))} 318 |
319 |
320 | 321 | {data.length < 10 ? ( 322 | 323 | 353 | {image.get("file") ? ( 354 | 355 | 367 | {loading && ( 368 |
369 |
370 |
374 |
375 |
376 | )} 377 | 378 | ) : ( 379 | 382 | )} 383 | 384 | ) : ( 385 |

Limit reached

386 | )} 387 | {deleteLocker && } 388 |
389 |
390 | ); 391 | } 392 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import Feature from "@/components/features"; 3 | import Footer from "@/components/footer"; 4 | import Navbar from "@/components/navbar"; 5 | import api from "@/libs/api"; 6 | import { encryptObjectValues } from "@/libs/utils"; 7 | import Image from "next/image"; 8 | import { redirect } from "next/navigation"; 9 | import { useState } from "react"; 10 | 11 | export default function Home() { 12 | const [message, setMessage] = useState(); 13 | const [error, setError] = useState(); 14 | const [lockerName, setLockerName] = useState(""); 15 | const [passkey, setPasskey] = useState(""); 16 | const [confirmPasskey, setConfirmPasskey] = useState(""); 17 | const [loading, setLoading] = useState(false); 18 | 19 | // POST request to the API 20 | const post = async ({ url, cred }: { url: string; cred: boolean }) => { 21 | setLoading(true); 22 | const payload = { 23 | name: lockerName, 24 | passkey: passkey, 25 | }; 26 | setError(undefined); 27 | setMessage(undefined); 28 | let response = null; 29 | if (cred) { 30 | response = await api.post(url, encryptObjectValues(payload)); 31 | } else { 32 | response = await api.post(url, encryptObjectValues({ name: lockerName })); 33 | } 34 | if (response?.success) { 35 | const data = response.data as { 36 | error?: string; 37 | message?: string; 38 | status?: number; 39 | }; 40 | 41 | if (data.status === 2) { 42 | redirect(`/locker/${lockerName}`); 43 | } 44 | 45 | if (data.error) { 46 | setError(data.error); 47 | setLoading(false); 48 | } else if (data.message) { 49 | setMessage(data.message); 50 | setLoading(false); 51 | } else { 52 | setError("Something went wrong"); 53 | setLoading(false); 54 | } 55 | 56 | setLoading(false); 57 | } 58 | }; 59 | 60 | // check if the name exists in the database 61 | const checkLocker = async () => { 62 | setLockerName(lockerName.replaceAll(" ", "")); 63 | if (lockerName) { 64 | post({ 65 | url: `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/check`, 66 | cred: false, 67 | }); 68 | } 69 | }; 70 | 71 | // create a new locker 72 | const createLocker = async () => { 73 | setLoading(true); 74 | if (passkey.length >= 5 && passkey === confirmPasskey) { 75 | post({ 76 | url: `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/locker`, 77 | cred: true, 78 | }); 79 | setLoading(false); 80 | redirect(`/locker/${lockerName}`); 81 | } 82 | }; 83 | 84 | return ( 85 |
86 | 87 |
88 |
89 |

90 | CREATE{" "} 91 | Pencil 98 |

99 | 100 |

101 | SECURE 102 | Lock 109 |

110 | 111 |

112 | SHARE 113 | Share 120 |

121 |
122 | 123 |

124 | Open Source File Sharing Platform 125 |

126 | 127 | 128 |

vouz.tech/locker/

129 | { 135 | setLockerName(e.target.value); 136 | }} 137 | onKeyDown={(e) => e.key === "Enter" && checkLocker()} 138 | /> 139 | 155 |
156 | {error &&

{error}

} 157 | {message &&

{message}

} 158 |
159 | 160 | 161 |
162 | 163 | {message && ( 164 |
165 |
166 | 177 |

Passkey

178 | setPasskey(e.target.value)} 184 | onKeyDown={(e) => e.key === "Enter" && createLocker()} 185 | /> 186 | setConfirmPasskey(e.target.value)} 196 | onKeyDown={(e) => e.key === "Enter" && createLocker()} 197 | /> 198 | 199 |

200 | {passkey !== confirmPasskey ? "Passkeys do not match" : ""} 201 |

202 |

203 | {passkey.length < 5 204 | ? "Passkey must be at least 5 characters" 205 | : ""} 206 |

207 |
208 | 232 |
233 |
234 | )} 235 |
236 | ); 237 | } 238 | -------------------------------------------------------------------------------- /src/components/features.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | export default function Feature() { 4 | return ( 5 |
6 |

Features

7 |
8 |
9 |
    10 |
  1. 11 |

    No Login Required 🚫

    12 |

    13 | Share files effortlessly without creating an account. Simply 14 | create a locker with a unique passkey. 15 |

    16 |
  2. 17 |
  3. 18 |

    Secure Lockers 🔒

    19 |

    20 | Each locker is protected by its own passkey, ensuring only 21 | authorized access. 22 |

    23 |
  4. 24 |
  5. 25 |

    Easily Shareable 🔗

    26 |

    27 | Share your locker by providing its name and passkey—quick and 28 | simple! 29 |

    30 |
  6. 31 |
  7. 32 |

    Open Source 🌐

    33 |

    34 | Vouz is fully open-source, inviting contributions, feedback, and 35 | new ideas from the community. 36 |

    37 |
  8. 38 |
39 |
40 |
41 | Feature 47 |
48 |
49 |
50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /src/components/footer.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | export default function Footer() { 4 | return ( 5 |
6 |

7 | Made by{" "} 8 | 13 | @zadescoxp 14 | 15 |

16 | 17 |

18 | Vouz is an open source project, show your love and contribution on{" "} 19 | 24 | GitHub 25 | 26 |

27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/components/metrics.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect } from "react"; 4 | import Clarity from "@microsoft/clarity"; 5 | 6 | export default function Analytics() { 7 | useEffect(() => { 8 | // Ensure Clarity is available before initializing 9 | if (Clarity && Clarity.init) { 10 | const projectId = "pt5e0mrzpn"; 11 | Clarity.init(projectId); 12 | } else { 13 | console.warn("Clarity is not available for initialization."); 14 | } 15 | }, []); // Empty dependency array ensures this runs only once 16 | 17 | return null; // This component doesn't render anything visible 18 | } 19 | -------------------------------------------------------------------------------- /src/components/navbar.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import Link from "next/link"; 3 | 4 | export default function Navbar() { 5 | return ( 6 |
7 | 8 | VOUZ 15 | 16 | 17 | 18 | GITHUB 25 | 26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/libs/api.ts: -------------------------------------------------------------------------------- 1 | import ApiClient from "hmm-api"; 2 | import { toast } from "sonner"; 3 | 4 | const api = new ApiClient({ 5 | toast: toast, 6 | }); 7 | 8 | export default api; 9 | -------------------------------------------------------------------------------- /src/libs/utils.ts: -------------------------------------------------------------------------------- 1 | import { encrypt, decrypt } from "tanmayo7lock"; 2 | 3 | export function encryptObjectValues(obj: Record) { 4 | return Object.keys(obj).reduce((acc, key) => { 5 | acc[key] = encrypt(obj[key as string]); // Apply decrypt to each value 6 | return acc; 7 | }, {} as Record); 8 | } 9 | 10 | export function decryptObjectValues(obj: Record) { 11 | return Object.keys(obj).reduce((acc, key) => { 12 | acc[key] = decrypt(obj[key as string]); // Apply decrypt to each value 13 | return acc; 14 | }, {} as Record); 15 | } 16 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | export default { 4 | content: [ 5 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | colors: { 12 | background: "var(--background)", 13 | foreground: "var(--foreground)", 14 | white: "#ffffff", 15 | grey: "#535353", 16 | lightgrey: "#F6F6F6", 17 | }, 18 | }, 19 | }, 20 | plugins: [], 21 | } satisfies Config; 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | --------------------------------------------------------------------------------