diff --git a/.env.example b/.env.example
index 339471ce..6426f2c9 100644
--- a/.env.example
+++ b/.env.example
@@ -2,7 +2,7 @@ APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
-APP_URL=http://localhost
+APP_URL=https://whispermoney.test
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..09576386
--- /dev/null
+++ b/README.md
@@ -0,0 +1,92 @@
+
+
+
+
+# Whisper Money
+
+**The most secure way to understand your finances.**
+
+Whisper Money is a privacy-first personal finance application that helps you track, categorize, and understand your spendingβall while keeping your financial data encrypted and secure.
+
+## Features
+
+- π **End-to-end encryption** β Your financial data stays private
+- π¦ **Bank account management** β Track multiple accounts in one place
+- π **Transaction categorization** β Automatic and manual categorization
+- π€ **Automation rules** β Set up rules to auto-categorize transactions
+- π **Financial insights** β Understand your spending patterns
+
+## Tech Stack
+
+- **Backend:** Laravel 12, PHP 8.4
+- **Frontend:** React 19, Inertia.js v2, TypeScript
+- **Styling:** Tailwind CSS v4
+- **Database:** MySQL
+- **Cache/Queue:** Redis
+- **Testing:** Pest v4
+
+## Running Locally
+
+### Prerequisites
+
+- Docker & Docker Compose
+- Composer
+- Node.js / Bun
+
+### Setup
+
+1. **Clone the repository:**
+
+```bash
+git clone https://github.com/your-username/whisper_money.git
+cd whisper_money
+```
+
+2. **Copy the environment file:**
+
+```bash
+cp .env.example .env
+```
+
+3. **Start the Docker services:**
+
+```bash
+docker compose up -d
+```
+
+4. **Install dependencies and setup the application:**
+
+```bash
+composer setup
+```
+
+5. **Start the development server:**
+
+```bash
+composer run dev
+```
+
+This will concurrently start:
+
+- PHP development server
+- Queue worker
+- Log viewer (Pail)
+- Vite dev server
+
+The application will be available at `https://whispermoney.test`.
+
+## License
+
+This project is licensed under the **Creative Commons Attribution-NonCommercial 4.0 International License (CC BY-NC 4.0)**.
+
+You are free to:
+
+- **Share** β copy and redistribute the material in any medium or format
+- **Adapt** β remix, transform, and build upon the material
+
+Under the following terms:
+
+- **Attribution** β You must give appropriate credit, provide a link to the license, and indicate if changes were made
+- **NonCommercial** β You may not use the material for commercial purposes
+
+For more details, see the [full license](https://creativecommons.org/licenses/by-nc/4.0/).
diff --git a/composer.json b/composer.json
index 16a0910e..1e6cc4c7 100644
--- a/composer.json
+++ b/composer.json
@@ -46,8 +46,8 @@
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
"@php artisan key:generate",
"@php artisan migrate --force",
- "npm install",
- "npm run build"
+ "bun install",
+ "bun run build"
],
"dev": [
"Composer\\Config::disableProcessTimeout",
diff --git a/package-lock.json b/package-lock.json
index b7b2f045..6172a025 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,11 +11,13 @@
"@radix-ui/react-avatar": "^1.1.3",
"@radix-ui/react-checkbox": "^1.1.4",
"@radix-ui/react-collapsible": "^1.1.3",
+ "@radix-ui/react-context-menu": "^2.2.16",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-navigation-menu": "^1.2.5",
"@radix-ui/react-popover": "^1.1.15",
+ "@radix-ui/react-progress": "^1.1.8",
"@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.8",
@@ -33,6 +35,8 @@
"cmdk": "^1.1.1",
"concurrently": "^9.0.1",
"date-fns": "^4.1.0",
+ "dexie": "^4.2.1",
+ "dexie-react-hooks": "^4.2.0",
"globals": "^15.14.0",
"input-otp": "^1.4.2",
"json-logic-js": "^2.0.5",
@@ -40,6 +44,7 @@
"lucide-react": "^0.553.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
+ "sonner": "^2.0.7",
"tailwind-merge": "^3.0.1",
"tailwindcss": "^4.0.0",
"tw-animate-css": "^1.4.0",
@@ -59,6 +64,7 @@
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-react": "^7.37.3",
"eslint-plugin-react-hooks": "^7.0.0",
+ "playwright": "^1.57.0",
"prettier": "^3.4.2",
"prettier-plugin-organize-imports": "^4.1.0",
"prettier-plugin-tailwindcss": "^0.6.11",
@@ -1332,6 +1338,34 @@
}
}
},
+ "node_modules/@radix-ui/react-context-menu": {
+ "version": "2.2.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.16.tgz",
+ "integrity": "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-menu": "2.1.16",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-dialog": {
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz",
@@ -1794,6 +1828,68 @@
}
}
},
+ "node_modules/@radix-ui/react-progress": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.8.tgz",
+ "integrity": "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-context": "1.1.3",
+ "@radix-ui/react-primitive": "2.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz",
+ "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
+ "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-radio-group": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz",
@@ -4062,6 +4158,23 @@
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
},
+ "node_modules/dexie": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.2.1.tgz",
+ "integrity": "sha512-Ckej0NS6jxQ4Po3OrSQBFddayRhTCic2DoCAG5zacOfOVB9P2Q5Xc5uL/nVa7ZVs+HdMnvUPzLFCB/JwpB6Csg==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/dexie-react-hooks": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/dexie-react-hooks/-/dexie-react-hooks-4.2.0.tgz",
+ "integrity": "sha512-u7KqTX9JpBQK8+tEyA9X0yMGXlSCsbm5AU64N6gjvGk/IutYDpLBInMYEAEC83s3qhIvryFS+W+sqLZUBEvePQ==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "@types/react": ">=16",
+ "dexie": ">=4.2.0-alpha.1 <5.0.0",
+ "react": ">=16"
+ }
+ },
"node_modules/doctrine": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
@@ -6221,6 +6334,53 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/playwright": {
+ "version": "1.57.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz",
+ "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.57.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.57.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz",
+ "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/playwright/node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
@@ -6934,6 +7094,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/sonner": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
+ "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",