├── .eslintrc.json ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── app ├── favicon.ico ├── layout.jsx └── page.jsx ├── components.json ├── components ├── Cards │ ├── BankAccountCard.jsx │ ├── CreditCardCard.jsx │ ├── EmailAccountCard.jsx │ ├── IdentificationDocumentCard.jsx │ ├── NoteCard.jsx │ ├── SocialMediaAccountCard.jsx │ ├── WebLoginCard.jsx │ └── WiFiPasswordCard.jsx ├── Dialogs │ ├── DeletePasswordDialog.jsx │ └── PasswordDialog.jsx ├── EmptyState.jsx ├── Forms │ ├── BankAccountForm.jsx │ ├── CreditCardForm.jsx │ ├── EmailAccountForm.jsx │ ├── IdentificationDocumentForm.jsx │ ├── NoteForm.jsx │ ├── PasswordForm.jsx │ ├── SocialMediaAccountForm.jsx │ ├── WebLoginForm.jsx │ └── WiFiPasswordForm.jsx ├── Header.jsx ├── Pages │ ├── Authentication.jsx │ └── Dashboard.jsx ├── PasswordGenerator.jsx ├── PasswordStrength.jsx ├── Passwords.jsx ├── SearchBar.jsx ├── SelectMenu.jsx ├── Sidebar.jsx └── ui │ ├── accordion.tsx │ ├── button.tsx │ ├── dialog.tsx │ ├── input.tsx │ ├── label.tsx │ ├── progress.tsx │ ├── select.tsx │ ├── slider.tsx │ ├── switch.tsx │ ├── textarea.tsx │ ├── toast.tsx │ ├── toaster.tsx │ ├── tooltip.tsx │ └── use-toast.ts ├── constants └── componentMappings.jsx ├── i18n.js ├── jsconfig.json ├── lib └── utils.ts ├── locales ├── en │ └── translation.json └── tr │ └── translation.json ├── next.config.js ├── package-lock.json ├── package.json ├── passwords.config.js ├── postcss.config.js ├── screenshots ├── Authentication.png ├── Dashboard.png ├── DeletePasswordDialog.png ├── PasswordDialog.png └── PasswordGenerator.png ├── src-tauri ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── build.rs ├── icons │ ├── 128x128.png │ ├── 128x128@2x.png │ ├── 32x32.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ ├── Square30x30Logo.png │ ├── Square310x310Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── StoreLogo.png │ ├── icon.icns │ ├── icon.ico │ └── icon.png ├── src │ └── main.rs └── tauri.conf.json ├── styles └── globals.css ├── tailwind.config.js └── utils ├── addNewPassword.js ├── calculateStrength.js ├── generatePassword.js ├── handleAuthentication.js ├── savePasswords.js └── updatePassword.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: workflow_dispatch 3 | 4 | jobs: 5 | release: 6 | permissions: 7 | contents: write 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | platform: [macos-latest, ubuntu-20.04, windows-latest] 12 | runs-on: ${{ matrix.platform }} 13 | 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v3 17 | 18 | - name: Install dependencies (ubuntu only) 19 | if: matrix.platform == 'ubuntu-20.04' 20 | # You can remove libayatana-appindicator3-dev if you don't use the system tray feature. 21 | run: | 22 | sudo apt-get update 23 | sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev librsvg2-dev 24 | 25 | - name: Rust setup 26 | uses: dtolnay/rust-toolchain@stable 27 | 28 | - name: Rust cache 29 | uses: swatinem/rust-cache@v2 30 | with: 31 | workspaces: "./src-tauri -> target" 32 | 33 | - name: Sync node version and setup cache 34 | uses: actions/setup-node@v3 35 | with: 36 | node-version: "lts/*" 37 | cache: "npm" # Set this to npm, yarn or pnpm. 38 | 39 | - name: Install frontend dependencies 40 | # If you don't have `beforeBuildCommand` configured you may want to build your frontend here too. 41 | run: npm install # Change this to npm, yarn or pnpm. 42 | 43 | - name: Build the app 44 | uses: tauri-apps/tauri-action@v0 45 | 46 | env: 47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 48 | with: 49 | tagName: ${{ github.ref_name }} # This only works if your workflow triggers on new tags. 50 | releaseName: "v__VERSION__" # tauri-action replaces \_\_VERSION\_\_ with the app version. 51 | releaseBody: "See the assets to download and install this version." 52 | releaseDraft: true 53 | prerelease: false 54 | -------------------------------------------------------------------------------- /.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.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts: 2 | build 3 | coverage 4 | .next 5 | out 6 | src-tauri/target -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2 4 | } 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Mustafa Pekkirişci 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 | # Passwords 2 | 3 | Passwords is an open-source desktop app that simplifies password management, offering a secure and user-friendly experience. Built with Tauri and Next.js. 4 | 5 | Dashboard 6 | 7 | ## Table of Contents 8 | 9 | - [Features](#features) 10 | 11 | - [Used Technologies](#used-technologies) 12 | 13 | - [How It Works](#how-it-works) 14 | 15 | - [Run Locally](#run-locally) 16 | 17 | - [Contribution](#contribution) 18 | 19 | - [License](#license) 20 | 21 | - [Screenshots](#screenshots) 22 | 23 | - [Download App](#download-app) 24 | 25 | ## Features 26 | 27 | - Access your encrypted password file through the user authentication page. Safeguard your passwords with an additional layer of security using user credentials. 28 | 29 | - Seamlessly add, edit, and delete passwords within the app. 30 | 31 | - Organize your passwords into different types for convenient categorization. 32 | 33 | - Utilize pre-designed forms tailored for various password types, ensuring easy and consistent data entry. 34 | 35 | - Calculate password strength in real-time, assisting you in creating robust and secure passwords. 36 | 37 | - Generate strong and unique passwords based on user preferences and choices. Customize password criteria such as length, character types, and complexity. 38 | 39 | - Enable internationalization with the integration of i18next. Reach a wider audience by providing localized language options, enhancing user experience. 40 | 41 | - Built with Tauri and Next.js, Passwords combines the power of modern web development and desktop application creation. Leverage the advanced capabilities of Tauri for seamless cross-platform deployment. 42 | 43 | - Store your passwords in an encrypted local file, ensuring the safety of your sensitive information. Utilize encryption powered by CryptoJS to maintain the confidentiality of your stored data. 44 | 45 | - Enjoy a modern and user-friendly interface designed for efficiency and ease of use. Access various password types and features with a clean and organized layout. 46 | 47 | ## Used Technologies 48 | 49 | - **Tauri**: Empower your app with the capabilities of Tauri, enabling seamless cross-platform deployment and access to native APIs for enhanced user experience. 50 | 51 | - **Next.js**: Harness the power of Next.js, leveraging its static site generation (SSG) capabilities to create a user interface. 52 | 53 | - **shadcn/ui**: Enhance your app's visual and interactive elements with components from shadcn/ui. These beautifully designed components can be seamlessly integrated into your application, offering accessibility, customizability, and open-source goodness. 54 | 55 | - **CryptoJS**: Utilize CryptoJS for strong encryption, ensuring that sensitive information such as passwords is securely stored within the app's encrypted local file. 56 | 57 | - **i18next**: Implement internationalization with i18next, enabling users to experience the app in their preferred language, enhancing global accessibility. 58 | 59 | - **Lucide React**: Enhance the visual appeal of your app with icons from Lucide React, adding a touch of aesthetic sophistication to the user interface. 60 | 61 | - **Tailwind CSS**: Employ Tailwind CSS for efficient styling, enabling rapid development through its utility-first approach and streamlined design workflow. 62 | 63 | - **Tauri APIs**: Access native functionalities seamlessly through Tauri APIs, providing your app with the potential to interact with the user's system. 64 | 65 | ## How It Works 66 | 67 | Passwords are stored locally in an **encrypted file** on the user's device using **CryptoJS (AES encryption)**, ensuring that sensitive information remains confidential and secure. 68 | 69 | > **Important:** There is **no "forgot password" option**. If you lose your email/password credentials, you will **not** be able to decrypt your password file, and your stored data will be inaccessible. Please ensure you store your credentials safely. 70 | 71 | ### Storage Mechanism 72 | 73 | - Each user's passwords are saved in a unique file named after a **SHA256 hash of their email and password combination**. 74 | 75 | - Files are saved in the system’s **AppData directory** (via Tauri’s `BaseDirectory.AppData`), which varies based on the operating system. 76 | 77 | ### Encryption Flow 78 | 79 | 1. **Authentication**: 80 | 81 | - When a user logs in, their email and password are used to generate a hash-based file name. 82 | 83 | - If the file exists, it's read and **decrypted using AES** with the same email+password as the key. 84 | 85 | - If not, a new encrypted file is created with an empty password array. 86 | 87 | 2. **Saving Passwords**: 88 | 89 | - When passwords are added, edited, or deleted, the updated array is **encrypted using AES** and written back to the same file. 90 | 91 | - AES keys are never stored—encryption is dynamically tied to the email/password pair used during authentication. 92 | 93 | 3. **Encryption Details**: 94 | 95 | - **AES-256** symmetric encryption via CryptoJS. 96 | 97 | - Keys are derived directly from the concatenation of email and password (e.g., `email + password`). 98 | 99 | - Password data is never sent or stored remotely. 100 | 101 | ## Run Locally 102 | 103 | To get a copy of Passwords up and running on your local machine, follow these steps: 104 | 105 | 1. **Install Tauri Prerequisites**: 106 | 107 | Before you begin, make sure you have the required tools and dependencies installed for Tauri. Follow the [Tauri Prerequisites Guide](https://tauri.app/v1/guides/getting-started/prerequisites) to set up your environment. 108 | 109 | 2. **Clone the repository**: 110 | 111 | ```bash 112 | git clone https://github.com/pekkiriscim/passwords.git 113 | ``` 114 | 115 | ```bash 116 | cd passwords 117 | ``` 118 | 119 | 3. **Install Dependencies**: 120 | 121 | Navigate into the project directory and install the required dependencies: 122 | 123 | ```bash 124 | npm install 125 | ``` 126 | 127 | 4. **Run the Development Server**: 128 | 129 | Start the app in development mode with the following command: 130 | 131 | ```bash 132 | npm run tauri dev 133 | ``` 134 | 135 | 5. **Build and Launch the Tauri App**: 136 | 137 | To build and launch the Tauri app, run the following command: 138 | 139 | ```bash 140 | npm run tauri build 141 | ``` 142 | 143 | ## Contribution 144 | 145 | Contributions to Passwords are welcomed and encouraged! If you're interested in improving the app, adding new features, fixing bugs, or enhancing documentation, your contributions are highly valued. 146 | 147 | ## License 148 | 149 | Passwords is open-source software released under the MIT License. 150 | 151 | The MIT License (MIT) is a permissive open-source license that allows you to use, modify, and distribute the software in your projects, both commercial and non-commercial, while providing attribution to the original authors. 152 | 153 | ## Screenshots 154 | 155 | Authentication 156 | 157 | Dashboard 158 | 159 | PasswordDialog 160 | 161 | PasswordGenerator 162 | 163 | DeletePasswordDialog 164 | 165 | ## Download App 166 | 167 | Visit the [Releases](https://github.com/pekkiriscim/passwords/releases) page of the GitHub repository to find the latest version of Passwords. Look for the release version that you want to download. 168 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pekkiriscim/passwords/42f1f792b6d7c7ce621902719a1d63fdd57b0234/app/favicon.ico -------------------------------------------------------------------------------- /app/layout.jsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect } from "react"; 4 | 5 | import "@/styles/globals.css"; 6 | 7 | import { Inter } from "next/font/google"; 8 | 9 | import i18n from "@/i18n"; 10 | 11 | const inter = Inter({ subsets: ["latin"] }); 12 | 13 | export default function RootLayout({ children }) { 14 | useEffect(() => { 15 | const preventContextMenu = (e) => e.preventDefault(); 16 | 17 | document.addEventListener("contextmenu", preventContextMenu); 18 | 19 | const userLanguage = window.navigator.language || "en"; 20 | 21 | i18n.changeLanguage(userLanguage); 22 | 23 | return () => 24 | document.removeEventListener("contextmenu", preventContextMenu); 25 | }, []); 26 | return ( 27 | 28 | {children} 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /app/page.jsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { createContext, useState } from "react"; 4 | 5 | import Authentication from "@/components/Pages/Authentication"; 6 | import Dashboard from "@/components/Pages/Dashboard"; 7 | 8 | import { Toaster } from "@/components/ui/toaster"; 9 | 10 | export const AuthContext = createContext(); 11 | export const PasswordsContext = createContext(); 12 | export const FilterContext = createContext(); 13 | 14 | export default function Home() { 15 | const [auth, setAuth] = useState({ 16 | email: "", 17 | password: "", 18 | isAuthorized: false, 19 | }); 20 | 21 | const [passwords, setPasswords] = useState([]); 22 | 23 | const [filter, setFilter] = useState({ 24 | passwordType: "passwords", 25 | filteredPasswords: passwords, 26 | }); 27 | 28 | return ( 29 | 30 | 33 | 36 | {auth.isAuthorized ? : } 37 | 38 | 39 | 40 | 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tailwind": { 6 | "config": "tailwind.config.js", 7 | "css": "styles/globals.css", 8 | "baseColor": "slate", 9 | "cssVariables": true 10 | }, 11 | "aliases": { 12 | "components": "@/components", 13 | "utils": "@/lib/utils" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /components/Cards/BankAccountCard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | import { useTranslation } from "react-i18next"; 4 | 5 | function BankAccountCard({ password }) { 6 | const [isPasswordVisible, setIsPasswordVisible] = useState(false); 7 | 8 | const { t } = useTranslation(); 9 | 10 | return ( 11 | <> 12 |
13 | {`${t("dashboard.bank_account_form.iban_label")}:`} 14 |   15 | setIsPasswordVisible(!isPasswordVisible)} 18 | > 19 | {isPasswordVisible ? password.iban : "•".repeat(password.iban.length)} 20 | 21 |
22 |
23 | {`${t("dashboard.bank_account_form.full_name_label")}:`} 24 |   25 | {password.fullName} 26 |
27 | 28 | ); 29 | } 30 | 31 | export default BankAccountCard; 32 | -------------------------------------------------------------------------------- /components/Cards/CreditCardCard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | import { useTranslation } from "react-i18next"; 4 | 5 | function CreditCardCard({ password }) { 6 | const [isPasswordVisible, setIsPasswordVisible] = useState(false); 7 | 8 | const { t } = useTranslation(); 9 | 10 | return ( 11 | <> 12 |
13 | {`${t("dashboard.credit_card_form.credit_card_number_label")}:`} 14 |   15 | setIsPasswordVisible(!isPasswordVisible)} 18 | > 19 | {isPasswordVisible 20 | ? password.creditCardNumber 21 | : "•".repeat(password.creditCardNumber.length)} 22 | 23 |
24 |
25 | {`${t("dashboard.credit_card_form.card_holder_name_label")}:`} 26 |   27 | {password.cardHolderName} 28 |
29 |
30 | {`${t("dashboard.credit_card_form.expiration_date_label")}:`} 31 |   32 | {password.expirationDate} 33 |
34 |
35 | {`${t("dashboard.credit_card_form.security_code_label")}:`} 36 |   37 | setIsPasswordVisible(!isPasswordVisible)} 40 | > 41 | {isPasswordVisible 42 | ? password.securityCode 43 | : "•".repeat(password.securityCode.length)} 44 | 45 |
46 | 47 | ); 48 | } 49 | 50 | export default CreditCardCard; 51 | -------------------------------------------------------------------------------- /components/Cards/EmailAccountCard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | import { useTranslation } from "react-i18next"; 4 | 5 | function EmailAccountCard({ password }) { 6 | const [isPasswordVisible, setIsPasswordVisible] = useState(false); 7 | 8 | const { t } = useTranslation(); 9 | 10 | return ( 11 | <> 12 |
13 | {`${t("dashboard.email_account_form.email_label")}:`} 14 |   15 | {password.email} 16 |
17 |
18 | {`${t("dashboard.email_account_form.password_label")}:`} 19 |   20 | setIsPasswordVisible(!isPasswordVisible)} 23 | > 24 | {isPasswordVisible 25 | ? password.password 26 | : "•".repeat(password.password.length)} 27 | 28 |
29 | 30 | ); 31 | } 32 | 33 | export default EmailAccountCard; 34 | -------------------------------------------------------------------------------- /components/Cards/IdentificationDocumentCard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | import { useTranslation } from "react-i18next"; 4 | 5 | function IdentificationDocumentCard({ password }) { 6 | const [isPasswordVisible, setIsPasswordVisible] = useState(false); 7 | 8 | const { t } = useTranslation(); 9 | 10 | return ( 11 | <> 12 |
13 | {`${t( 14 | "dashboard.identification_document_form.identity_number_label" 15 | )}:`} 16 |   17 | setIsPasswordVisible(!isPasswordVisible)} 20 | > 21 | {isPasswordVisible 22 | ? password.identityNumber 23 | : "•".repeat(password.identityNumber.length)} 24 | 25 |
26 |
27 | {`${t("dashboard.identification_document_form.full_name_label")}:`} 28 |   29 | {password.fullName} 30 |
31 |
32 | {`${t("dashboard.identification_document_form.birth_date_label")}:`} 33 |   34 | {password.birthDate} 35 |
36 |
37 | {`${t("dashboard.identification_document_form.series_number_label")}:`} 38 |   39 | setIsPasswordVisible(!isPasswordVisible)} 42 | > 43 | {isPasswordVisible 44 | ? password.seriesNumber 45 | : "•".repeat(password.seriesNumber.length)} 46 | 47 |
48 | 49 | ); 50 | } 51 | 52 | export default IdentificationDocumentCard; 53 | -------------------------------------------------------------------------------- /components/Cards/NoteCard.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { useTranslation } from "react-i18next"; 4 | 5 | function NoteCard({ password }) { 6 | const { t } = useTranslation(); 7 | 8 | return ( 9 | <> 10 |
11 | {`${t("dashboard.note_form.note_label")}:`} 12 |   13 | {password.note} 14 |
15 | 16 | ); 17 | } 18 | 19 | export default NoteCard; 20 | -------------------------------------------------------------------------------- /components/Cards/SocialMediaAccountCard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | import { useTranslation } from "react-i18next"; 4 | 5 | function SocialMediaAccountCard({ password }) { 6 | const [isPasswordVisible, setIsPasswordVisible] = useState(false); 7 | 8 | const { t } = useTranslation(); 9 | 10 | return ( 11 | <> 12 |
13 | {`${t("dashboard.social_media_account_form.username_label")}:`} 14 |   15 | {password.username} 16 |
17 |
18 | {`${t("dashboard.social_media_account_form.password_label")}:`} 19 |   20 | setIsPasswordVisible(!isPasswordVisible)} 23 | > 24 | {isPasswordVisible 25 | ? password.password 26 | : "•".repeat(password.password.length)} 27 | 28 |
29 | 30 | ); 31 | } 32 | 33 | export default SocialMediaAccountCard; 34 | -------------------------------------------------------------------------------- /components/Cards/WebLoginCard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | import { useTranslation } from "react-i18next"; 4 | 5 | function WebLoginCard({ password }) { 6 | const [isPasswordVisible, setIsPasswordVisible] = useState(false); 7 | 8 | const { t } = useTranslation(); 9 | 10 | return ( 11 | <> 12 |
13 | {`${t("dashboard.web_login_form.email_label")}:`} 14 |   15 | {password.email} 16 |
17 |
18 | {`${t("dashboard.web_login_form.password_label")}:`} 19 |   20 | setIsPasswordVisible(!isPasswordVisible)} 23 | > 24 | {isPasswordVisible 25 | ? password.password 26 | : "•".repeat(password.password.length)} 27 | 28 |
29 |
30 | {`${t("dashboard.web_login_form.url_label")}:`} 31 |   32 | {password.URL} 33 |
34 | 35 | ); 36 | } 37 | 38 | export default WebLoginCard; 39 | -------------------------------------------------------------------------------- /components/Cards/WiFiPasswordCard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | import { useTranslation } from "react-i18next"; 4 | 5 | function WiFiPasswordCard({ password }) { 6 | const [isPasswordVisible, setIsPasswordVisible] = useState(false); 7 | 8 | const { t } = useTranslation(); 9 | 10 | return ( 11 | <> 12 |
13 | {`${t("dashboard.wifi_password_form.wifi_name_label")}:`} 14 |   15 | {password.wifiName} 16 |
17 |
18 | {`${t("dashboard.wifi_password_form.wifi_password_label")}:`} 19 |   20 | setIsPasswordVisible(!isPasswordVisible)} 23 | > 24 | {isPasswordVisible 25 | ? password.wifiPassword 26 | : "•".repeat(password.wifiPassword.length)} 27 | 28 |
29 | 30 | ); 31 | } 32 | 33 | export default WiFiPasswordCard; 34 | -------------------------------------------------------------------------------- /components/Dialogs/DeletePasswordDialog.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useContext } from "react"; 2 | 3 | import { PasswordsContext, AuthContext } from "@/app/page"; 4 | 5 | import { 6 | Dialog, 7 | DialogContent, 8 | DialogDescription, 9 | DialogHeader, 10 | DialogTitle, 11 | DialogTrigger, 12 | } from "@/components/ui/dialog"; 13 | 14 | import { Button } from "@/components/ui/button"; 15 | 16 | import { Loader2 } from "lucide-react"; 17 | 18 | import { savePasswords } from "@/utils/savePasswords"; 19 | 20 | import { useTranslation } from "react-i18next"; 21 | 22 | function DeletePasswordDialog({ passwordId }) { 23 | const [isDialogOpen, setIsDialogOpen] = useState(false); 24 | const [isLoading, setIsLoading] = useState(false); 25 | 26 | const { passwords, setPasswords } = useContext(PasswordsContext); 27 | const { auth } = useContext(AuthContext); 28 | 29 | const deletePassword = async (e) => { 30 | try { 31 | e.preventDefault(); 32 | 33 | setIsLoading(true); 34 | 35 | const newPasswords = await passwords.filter( 36 | (password) => password.passwordId !== passwordId 37 | ); 38 | 39 | const newPasswordsState = await savePasswords( 40 | newPasswords, 41 | auth.email, 42 | auth.password 43 | ); 44 | 45 | if (newPasswordsState) { 46 | setPasswords(newPasswordsState); 47 | 48 | setIsDialogOpen(false); 49 | } 50 | } catch (error) { 51 | console.log(error); 52 | 53 | return null; 54 | } finally { 55 | setIsLoading(false); 56 | } 57 | }; 58 | 59 | const { t } = useTranslation(); 60 | 61 | return ( 62 | 63 | 64 | 76 | 77 | 78 | 79 | 80 | {t("dashboard.delete_password_dialog.dialog_title_delete")} 81 | 82 | 83 | {t("dashboard.delete_password_dialog.dialog_description_delete")} 84 | 85 | 86 |
87 | 99 | 111 |
112 |
113 |
114 | ); 115 | } 116 | 117 | export default DeletePasswordDialog; 118 | -------------------------------------------------------------------------------- /components/Dialogs/PasswordDialog.jsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState, createContext, useContext, useEffect } from "react"; 4 | 5 | import { PasswordsContext, AuthContext } from "@/app/page"; 6 | import { PasswordDialogContext } from "@/components/Pages/Dashboard"; 7 | 8 | import PasswordForm from "@/components/Forms/PasswordForm"; 9 | 10 | import { Button } from "@/components/ui/button"; 11 | 12 | import { 13 | Dialog, 14 | DialogContent, 15 | DialogDescription, 16 | DialogHeader, 17 | DialogTitle, 18 | DialogTrigger, 19 | } from "@/components/ui/dialog"; 20 | 21 | import { Plus, Loader2 } from "lucide-react"; 22 | 23 | import { passwordTypeStates } from "@/constants/componentMappings"; 24 | import { formComponents } from "@/constants/componentMappings"; 25 | 26 | import { updatePassword } from "@/utils/updatePassword"; 27 | import { addNewPassword } from "@/utils/addNewPassword"; 28 | 29 | import { useTranslation } from "react-i18next"; 30 | 31 | export const NewPasswordContext = createContext(); 32 | 33 | function PasswordDialog() { 34 | const [newPassword, setNewPassword] = useState(); 35 | 36 | const { auth } = useContext(AuthContext); 37 | const { passwords, setPasswords } = useContext(PasswordsContext); 38 | 39 | const { passwordDialog, setPasswordDialog } = useContext( 40 | PasswordDialogContext 41 | ); 42 | 43 | useEffect(() => { 44 | if (passwordDialog.isUpdating) { 45 | setNewPassword(passwordDialog.updatePassword); 46 | 47 | setPasswordDialog({ ...passwordDialog, step: 2 }); 48 | } else { 49 | setNewPassword(passwordTypeStates.webLogin); 50 | 51 | setPasswordDialog({ ...passwordDialog, step: 1 }); 52 | } 53 | }, [passwordDialog.isUpdating, passwordDialog.updatePassword]); 54 | 55 | const handleStep1 = (e) => { 56 | e.preventDefault(); 57 | 58 | setPasswordDialog({ ...passwordDialog, step: 1 }); 59 | }; 60 | 61 | const handleStep2 = (e) => { 62 | e.preventDefault(); 63 | 64 | setPasswordDialog({ ...passwordDialog, step: 2 }); 65 | }; 66 | 67 | const openDialog = (e) => { 68 | e.preventDefault(); 69 | 70 | setPasswordDialog({ 71 | ...passwordDialog, 72 | isOpen: true, 73 | isUpdating: false, 74 | step: 1, 75 | }); 76 | }; 77 | 78 | const closeDialog = (e) => { 79 | e.preventDefault(); 80 | 81 | if (passwordDialog.isUpdating) { 82 | setNewPassword(passwordDialog.updatePassword); 83 | 84 | setPasswordDialog({ ...passwordDialog, isUpdating: false }); 85 | } else { 86 | setNewPassword(passwordTypeStates.webLogin); 87 | } 88 | 89 | setPasswordDialog({ ...passwordDialog, isOpen: false }); 90 | }; 91 | 92 | const handleSubmit = async (e) => { 93 | try { 94 | e.preventDefault(); 95 | 96 | setPasswordDialog({ ...passwordDialog, isLoading: true }); 97 | 98 | if (passwordDialog.isUpdating) { 99 | const updatedPasswordsState = await updatePassword( 100 | passwords, 101 | passwordDialog.updatePassword, 102 | newPassword, 103 | auth 104 | ); 105 | 106 | if (updatedPasswordsState) { 107 | setPasswords(updatedPasswordsState); 108 | 109 | setNewPassword(passwordDialog.updatePassword); 110 | } 111 | } else { 112 | const newPasswordsState = await addNewPassword( 113 | passwords, 114 | newPassword, 115 | auth 116 | ); 117 | 118 | if (newPasswordsState) { 119 | setPasswords(newPasswordsState); 120 | 121 | setPasswordDialog({ ...passwordDialog, step: 1 }); 122 | 123 | setNewPassword(passwordTypeStates.webLogin); 124 | } 125 | } 126 | } catch (error) { 127 | console.log(error); 128 | 129 | return null; 130 | } finally { 131 | setPasswordDialog({ ...passwordDialog, isOpen: false, isLoading: false }); 132 | } 133 | }; 134 | 135 | const { t } = useTranslation(); 136 | 137 | return ( 138 | 141 | 142 | 143 | 152 | 153 | 154 | 155 | 156 | {passwordDialog.isUpdating 157 | ? t("dashboard.password_dialog.dialog_title_edit") 158 | : t("dashboard.password_dialog.dialog_title_add")} 159 | 160 | 161 | {passwordDialog.isUpdating 162 | ? t("dashboard.password_dialog.dialog_description_edit") 163 | : t("dashboard.password_dialog.dialog_description_add")} 164 | 165 | 166 |
169 |
170 | {passwordDialog.step === 1 && } 171 | {passwordDialog.step === 2 && 172 | newPassword.passwordType && 173 | formComponents[newPassword.passwordType]} 174 |
175 | 190 | 199 |
200 |
201 |
202 |
203 |
204 |
205 | ); 206 | } 207 | 208 | export default PasswordDialog; 209 | -------------------------------------------------------------------------------- /components/EmptyState.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { FolderLock } from "lucide-react"; 4 | 5 | import { useTranslation } from "react-i18next"; 6 | 7 | function EmptyState() { 8 | const { t } = useTranslation(); 9 | 10 | return ( 11 |
12 |
13 | 14 |
15 |

16 | {t("dashboard.empty_state.title")} 17 |

18 |

19 | {t("dashboard.empty_state.description")} 20 |

21 |
22 | ); 23 | } 24 | 25 | export default EmptyState; 26 | -------------------------------------------------------------------------------- /components/Forms/BankAccountForm.jsx: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | 3 | import { NewPasswordContext } from "@/components/Dialogs/PasswordDialog"; 4 | 5 | import { Input } from "@/components/ui/input"; 6 | import { Label } from "@/components/ui/label"; 7 | 8 | import { useTranslation } from "react-i18next"; 9 | 10 | function BankAccountForm() { 11 | const { newPassword, setNewPassword } = useContext(NewPasswordContext); 12 | 13 | const { t } = useTranslation(); 14 | 15 | const handleIbanChange = (e) => { 16 | e.preventDefault(); 17 | 18 | let formattedIban = e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, ""); 19 | formattedIban = formattedIban.slice(0, 26); 20 | 21 | let formattedDisplay = t("dashboard.bank_account_form.iban_placeholder"); 22 | 23 | for (let i = 2; i < formattedIban.length; i += 4) { 24 | formattedDisplay += " " + formattedIban.slice(i, i + 4); 25 | } 26 | 27 | setNewPassword({ ...newPassword, iban: formattedDisplay }); 28 | }; 29 | 30 | return ( 31 |
32 |
33 | 36 | 46 |
47 |
48 | 51 | 60 | setNewPassword({ ...newPassword, fullName: e.target.value }) 61 | } 62 | /> 63 |
64 |
65 | ); 66 | } 67 | 68 | export default BankAccountForm; 69 | -------------------------------------------------------------------------------- /components/Forms/CreditCardForm.jsx: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | 3 | import { NewPasswordContext } from "@/components/Dialogs/PasswordDialog"; 4 | 5 | import { Input } from "@/components/ui/input"; 6 | import { Label } from "@/components/ui/label"; 7 | 8 | import { useTranslation } from "react-i18next"; 9 | 10 | function CreditCardForm() { 11 | const { newPassword, setNewPassword } = useContext(NewPasswordContext); 12 | 13 | const { t } = useTranslation(); 14 | 15 | const handleCreditCardNumberChange = (e) => { 16 | e.preventDefault(); 17 | 18 | let formattedNumber = e.target.value.replace(/[^0-9]/g, ""); 19 | formattedNumber = formattedNumber.replace(/\s/g, ""); 20 | 21 | let formattedDisplay = ""; 22 | 23 | for (let i = 0; i < formattedNumber.length; i += 4) { 24 | formattedDisplay += formattedNumber.slice(i, i + 4) + " "; 25 | } 26 | 27 | setNewPassword({ 28 | ...newPassword, 29 | creditCardNumber: formattedDisplay.trim(), 30 | }); 31 | }; 32 | 33 | const handleExpirationDateChange = (e) => { 34 | e.preventDefault(); 35 | 36 | let formattedNumber = e.target.value.replace(/[^0-9]/g, ""); 37 | formattedNumber = formattedNumber.replace(/\s/g, ""); 38 | 39 | let formattedDisplay = ""; 40 | 41 | for (let i = 0; i < formattedNumber.length; i += 2) { 42 | formattedDisplay += formattedNumber.slice(i, i + 2); 43 | if (i + 2 < formattedNumber.length) { 44 | formattedDisplay += "/"; 45 | } 46 | } 47 | 48 | setNewPassword({ ...newPassword, expirationDate: formattedDisplay }); 49 | }; 50 | 51 | const handleSecurityCodeChange = (e) => { 52 | e.preventDefault(); 53 | 54 | let formattedSecurityCode = e.target.value.replace(/[^0-9]/g, ""); 55 | 56 | setNewPassword({ ...newPassword, securityCode: formattedSecurityCode }); 57 | }; 58 | 59 | return ( 60 |
61 |
62 | 65 | 78 |
79 |
80 | 83 | 94 | setNewPassword({ ...newPassword, cardHolderName: e.target.value }) 95 | } 96 | /> 97 |
98 |
99 |
100 | 103 | 116 |
117 |
118 | 121 | 134 |
135 |
136 |
137 | ); 138 | } 139 | 140 | export default CreditCardForm; 141 | -------------------------------------------------------------------------------- /components/Forms/EmailAccountForm.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useContext } from "react"; 2 | 3 | import PasswordStrength from "@/components/PasswordStrength"; 4 | import PasswordGenerator from "@/components/PasswordGenerator"; 5 | import { NewPasswordContext } from "@/components/Dialogs/PasswordDialog"; 6 | 7 | import { Input } from "@/components/ui/input"; 8 | import { Label } from "@/components/ui/label"; 9 | import { Button } from "@/components/ui/button"; 10 | 11 | import { Eye, EyeOff } from "lucide-react"; 12 | 13 | import { useTranslation } from "react-i18next"; 14 | 15 | function EmailAccountForm() { 16 | const [isPasswordVisible, setIsPasswordVisible] = useState(false); 17 | 18 | const { newPassword, setNewPassword } = useContext(NewPasswordContext); 19 | 20 | const { t } = useTranslation(); 21 | 22 | return ( 23 |
24 |
25 | 28 | 37 | setNewPassword({ ...newPassword, email: e.target.value }) 38 | } 39 | /> 40 |
41 |
42 | 45 |
46 | 55 | setNewPassword({ ...newPassword, password: e.target.value }) 56 | } 57 | /> 58 | 74 |
75 | 76 | 77 |
78 |
79 | ); 80 | } 81 | 82 | export default EmailAccountForm; 83 | -------------------------------------------------------------------------------- /components/Forms/IdentificationDocumentForm.jsx: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | 3 | import { NewPasswordContext } from "@/components/Dialogs/PasswordDialog"; 4 | 5 | import { Input } from "@/components/ui/input"; 6 | import { Label } from "@/components/ui/label"; 7 | 8 | import { useTranslation } from "react-i18next"; 9 | 10 | function IdentificationDocumentForm() { 11 | const { newPassword, setNewPassword } = useContext(NewPasswordContext); 12 | 13 | const { t } = useTranslation(); 14 | 15 | const handleIdentityNumberChange = (e) => { 16 | e.preventDefault(); 17 | 18 | let formattedIdentityNumber = e.target.value.replace(/[^0-9]/g, ""); 19 | 20 | setNewPassword({ ...newPassword, identityNumber: formattedIdentityNumber }); 21 | }; 22 | 23 | const handleBirthDateChange = (e) => { 24 | e.preventDefault(); 25 | 26 | let formattedBirthDate = e.target.value.replace(/[^0-9]/g, ""); 27 | formattedBirthDate = formattedBirthDate.slice(0, 8); 28 | 29 | let formattedDisplay = ""; 30 | formattedDisplay += formattedBirthDate.slice(0, 2); 31 | 32 | if (formattedBirthDate.length > 2) { 33 | formattedDisplay += "/" + formattedBirthDate.slice(2, 4); 34 | } 35 | 36 | if (formattedBirthDate.length > 4) { 37 | formattedDisplay += "/" + formattedBirthDate.slice(4, 8); 38 | } 39 | 40 | setNewPassword({ ...newPassword, birthDate: formattedDisplay }); 41 | }; 42 | 43 | return ( 44 |
45 |
46 | 49 | 61 |
62 |
63 | 66 | 77 | setNewPassword({ ...newPassword, fullName: e.target.value }) 78 | } 79 | /> 80 |
81 |
82 |
83 | 86 | 98 |
99 |
100 | 103 | 114 | setNewPassword({ ...newPassword, seriesNumber: e.target.value }) 115 | } 116 | /> 117 |
118 |
119 |
120 | ); 121 | } 122 | 123 | export default IdentificationDocumentForm; 124 | -------------------------------------------------------------------------------- /components/Forms/NoteForm.jsx: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | 3 | import { NewPasswordContext } from "@/components/Dialogs/PasswordDialog"; 4 | 5 | import { Textarea } from "@/components/ui/textarea"; 6 | import { Label } from "@/components/ui/label"; 7 | 8 | import { useTranslation } from "react-i18next"; 9 | 10 | function NoteForm() { 11 | const { newPassword, setNewPassword } = useContext(NewPasswordContext); 12 | 13 | const { t } = useTranslation(); 14 | 15 | return ( 16 |
17 |
18 | 19 |