Commit Graph

30 Commits

Author SHA1 Message Date
Víctor Falcón a190d37307
feat(deps): upgrade Inertia.js from v2 to v3 (#769)
Upgrades Inertia.js from v2 to v3 on both sides — the major deliberately
left out of #764.

| Package | From | To |
| --- | --- | --- |
| `inertiajs/inertia-laravel` | 2.0.24 | **3.3.1** |
| `@inertiajs/react` | 2.3.17 | **3.6.1** |
| `axios` | transitive | **direct dep** (1.19.0) |

## Why axios becomes a direct dependency

The app imports `axios` in 18 runtime files, but it was never declared —
it arrived transitively through `@inertiajs/core` v2. v3 drops axios in
favour of its own XHR client, so without this the whole app would fail
to resolve the import.

Nothing relied on Inertia configuring axios: v2 never touched
`axios.defaults`, and there are no interceptors. `^1.19.0` also
satisfies core's optional peer range (`^1.15.2`).

## Breaking changes swept

Every v2→v3 breaking change from the [official upgrade
guide](https://inertiajs.com/upgrade-guide) was checked against the
codebase:

| Change | Call sites |
| --- | --- |
| `<title inertia>` → `<title data-inertia>` | fixed (`app.blade.php`) |
| `testing.page_paths/page_extensions` → `pages.paths/extensions` |
fixed (`config/inertia.php`) |
| `ComponentResolver` no longer accepts `Promise<{default}>` | fixed
(`app.tsx`, `ssr.tsx`) |
| `router.on('invalid'/'exception')` → `httpException`/`networkError` |
none — only `'navigate'` is used |
| `router.cancel()` → `cancelAll()` | none |
| `Inertia::lazy()` → `optional()` | none |
| `future` options block | none |
| `hideProgress`/`revealProgress` | none |
| arrow-fn `.layout =` → `[Layout]` | none — no page sets `.layout` |
| deprecated testing traits (`Has`/`Matching`/`Debugging`) | none |
| `qs` / `lodash-es` imports | none |

React 19.2.4, Laravel 13 and PHP 8.4 already satisfy v3's raised floors.

## DevTools kept opt-in

v3 adds a DevTools recorder that writes every request's page props to
`storage/inertia-devtools/` and **defaults to on in local**. On this app
that produced 38 dumps, the largest ~1 MB, holding whole transaction and
balance payloads — redaction only covers credential-shaped keys. It is
gitignored by the package, so it never reaches the repo, but for a
privacy-first product it shouldn't be the default. Gated behind
`INERTIA_DEVTOOLS_ENABLED` (set it to `true` in your own `.env` when you
need it).

## Deliberate behaviour change: `<Deferred>` on partial reloads

v3 no longer resets `<Deferred>` to its `fallback` during a partial
reload — existing content stays visible while fresh data loads. This
affects the dashboard (`onBalanceUpdated`) and account detail
(`handleTransactionCreated`).

**Kept as-is, on purpose.** Two reasons:

1. `dashboard.tsx:131-133` already carried a workaround for exactly this
v2 behaviour — it requests an unrelated cheap prop purely so the
deferred prop isn't refetched into a skeleton. The team already treated
the skeleton flash as a problem and v3 fixes it upstream, so restoring
it via the new `reloading` slot prop would undo an improvement. That
workaround is now redundant and can be simplified in a follow-up.
2. The "user can't tell it worked and re-submits" risk doesn't apply:
`edit-transaction-dialog.tsx:390` fires `toast.success('Transaction
created successfully')` *before* the reload, so the confirmation is
explicit and independent of the list.

## Verification

Full CI set, locally:

- `bun run build` — green
- `bun run test` — **353/353**
- `bun run types` — zero new errors (advisory step; pre-existing backlog
unchanged)
- `bun run format`, `bun run lint`, `bun run dry` — green
- `vendor/bin/pint --test`, `vendor/bin/phpstan analyse` — green
- `php artisan crap --base=origin/main` — 0 methods above complexity 10
- `pest --exclude-testsuite=Browser,Performance` — **2086/2088**
- `pest --testsuite=Browser` — **132/133**

Both test failures are pre-existing and reproduce identically on `main`
with Inertia v2 — I ran each one there to confirm:

- `DashboardTest::dashboard top categories roll child spending up into
the parent` — 409, the known local Inertia asset-version artifact
- `BudgetsFeatureNavigationTest::user cannot access another users
budget` — `Unknown column 'category_id'` on `budgets`, a factory/schema
mismatch

## Browser QA

Real browser sweep against the production build over dashboard,
transactions, accounts, budgets, settings and billing: **0 console
errors, 0 uncaught exceptions, 0 failed requests**. Page titles apply,
deferred props resolve, sidebar `<Link>` navigation stays client-side
with no document reloads, back/forward restores from Inertia history,
and a profile form submit persisted correctly.

## SSR: measured, left alone

v3 routes SSR through the Vite dev server (`/__inertia_ssr`) whenever
Vite is running hot, and we don't use the `@inertiajs/vite` plugin — so
in theory local dev makes a doomed POST per render. Measured it rather
than guessed: with `bun run dev` up, page loads are **16 ms** and the
failed attempt is a connection-refused costing ~0.6 ms, then it falls
back to client rendering. Dev mode renders correctly (React mounts, 0
console errors). Not worth a config knob.

Production is untouched — `Vite::isRunningHot()` is false there, so it
uses the real SSR server from `docker/supervisor/supervisord.conf`
exactly as on v2.

## Notes

- Wayfinder needed nothing: #764 already brought it to v0.1.21 (latest),
and `@laravel/vite-plugin-wayfinder` is on 0.1.7 (latest).
- Pre-existing bug both reviews surfaced, left for its own PR:
`auth/register.tsx:40` passes an `async` handler to `<Form onBefore>`,
which Inertia never awaits (same in v2 and v3), so
`transactionSyncService.clearAll()` is fire-and-forget on registration.
- `config/inertia.php` is hand-patched rather than republished. The v3
stub is ~190 lines; the unset keys resolve to package defaults via
`mergeConfigFrom`. Adopting the whole stub is a separate, reviewable
change.
- Remaining "Inertia v2" strings live inside Boost-generated blocks
(`CLAUDE.md`'s guidelines block and the vendored `SKILL.md` copies).
Hand-edits there get overwritten, so they should come from a Boost
regeneration. The three hand-written references are updated here.

## Demo


https://github.com/user-attachments/assets/3f0e7f6d-621a-4c3a-a2a0-68473ec59f10


<!-- PLACEHOLDER: drag the QA video in here -->
2026-08-11 14:33:31 +02:00
Víctor Falcón fe747c4472
feat(stats): post the Discord stats reports in Spanish, opened by an AI summary (#752)
## What

The admin Discord channel is Spanish, but the four scheduled `stats:*`
reports posted English. They now post Spanish, and the three
cohort/experiment reports open with a short AI-written summary so a
reader understands the situation without decoding the table.

### 1. Spanish reports

`stats:daily-report`, `stats:subscription-funnel`,
`stats:experiment-funnel` and `stats:ai-cohort-report` now post Spanish
embed titles, field names, ASCII table headers, legends and disclaimers,
with Spanish dates (`sáb., 13 jun. 2026`). Table headers and cells stay
ASCII (`Semana`, `Variante`, `UMad`, `pdte`, …) so `sprintf`'s byte
padding keeps the columns aligned inside the code block.

Hardcoded, not `__()`: this is an internal channel, not user-facing UI,
so it never needs a second language and doesn't belong in
`lang/es.json`. Everything else — code, comments, PHPDoc, command
descriptions, `$this->info()` — stays English.

### 2. AI summary (best-effort)

New `ReportSummarizer` + `ReportSummaryAgent` (laravel/ai, same
Gemini-Flash pattern and `AI_PROVIDER` switch as the other AI features,
config in `config/ai_reports.php`). It prepends a few sentences to the
embed description that compare the current period against the previous
one (week over week for the two weekly reports, month over month for the
monthly cohort one), say what got better or worse, and call out
explicitly when a figure isn't conclusive — small sample, immature
cohort, signup surge week, or no previous period.

- **Data**: only what each collector already computes. The two cohort
reports pass their weekly series with the per-metric maturity flags; the
experiment report passes the per-variant figures plus the
already-rendered significance verdict (one source of truth), with money
figures nulled exactly where the table renders `—`, so the summary can't
report a zero where the reader sees no data.
- **Previous period**: the experiment report has no time series, so each
run caches its figures (with a capture timestamp) as the next run's
baseline. A same-day manual re-run doesn't overwrite it, and the model
is told to flag a gap that isn't roughly one period.
- **Degrades safely**: no API key, a provider outage, a bad provider
name or a slow response (30s timeout) → the report is posted unchanged,
without the summary. Transient provider errors are logged; anything else
is reported, matching `CategorizeTransactions` /
`LaravelAiRuleSuggestionGenerator`.

### 3. Fix found while reviewing: Discord's embed limits

The translated "Cómo leerlo" field came out at 1152 characters, past
Discord's 1024-character cap on a field value — Discord rejects the
**whole** payload, so the experiment funnel would have silently stopped
appearing in the channel every Monday (`DiscordWebhook` only logs the
400). Both long fields are now tighter (745 and 898 chars),
`DiscordWebhook` trims anything still over the limit instead of losing
the report, and a test measures every scheduled report's embed so
growing copy fails in CI rather than in production.

## Testing

- `tests/Feature/Ai/ReportSummarizerTest.php`: baseline in/out, same-day
re-run, dry run, backtick stripping, truncation, empty answer,
transient-vs-reported failures.
- `tests/Feature/DiscordReportEmbedLimitsTest.php`: all four embeds
inside Discord's limits, plus the trimming fallback.
- The four command tests: Spanish assertions, summary is the first thing
in the description, and the report is still posted when the AI throws.
- 64 tests / 338 assertions pass locally, plus PHPStan and Pint.

## QA

Ran all four commands against the local DB with `--no-discord` (and
dumped the real webhook payloads with the Discord call faked). Real
Gemini output, e.g. the experiment funnel on the second run:

> No se ha producido ningún cambio en las métricas del experimento
respecto a la ejecución previa del 10 de agosto de 2026. La tasa de
conversión sobre usuarios maduros se mantiene en el 4,7 % para la
variante reducida, el 4,0 % para pay_now y el 3,7 % para el control. Las
cifras no son conclusivas ya que las diferencias entre variantes no son
estadísticamente significativas, con un p-valor de 0,593 frente al
umbral α de 0,017.

Also verified with an unreachable provider: the report renders in full,
without the summary.

## Not included

- `stats:stuck-cohort-report` is still English. It posts to the same
webhook but isn't scheduled in `routes/console.php`, so it was out of
the four active reports; worth its own decision.
- The `—` and `` cells are still a couple of bytes off inside the code
block (multibyte in `sprintf`). Pre-existing on `main`, unchanged here.
2026-08-10 10:13:40 +02:00
Víctor Falcón 87c63f2100
fix(auth): make build deterministic when REGISTRATION_ENABLED=false (#720)
## Problem

`bun run build` failed whenever `REGISTRATION_ENABLED=false`.

`config/fortify.php` only added `Features::registration()` when the env
flag
was truthy, so with the flag off Fortify never registered the
`/register`
routes. Wayfinder only generates helpers for registered routes, so the
`register` / `register.store` helpers imported by
`resources/js/pages/auth/login.tsx`
and `resources/js/pages/auth/register.tsx` no longer resolved and the
build
broke. The build outcome depended on an env flag — not deterministic.

## Fix

Decouple *route registration* from *whether sign-ups are accepted*:

- **Always register** `Features::registration()` in
`config/fortify.php`, so the
`/register` routes (and their Wayfinder helpers) always exist and the
build is
  deterministic regardless of the flag.
- Gate acceptance at **runtime** via a new
`config('auth.registration_enabled')`
value (mapped from `env('REGISTRATION_ENABLED', true)`), the single
source of truth.
- When registration is disabled:
- `GET /register` (Fortify register view) and `POST /register`
(`CreateNewUser`)
    return **403**.
- Registration CTAs stay hidden via `canRegister` (login view + landing
page).
- Guests are redirected to `/login`
(`AuthEntryPointService::guestRedirectRoute`).
- With the flag enabled (the default), registration behaves exactly as
before.

## Tests

- Rewrote `tests/Feature/Auth/RegistrationDisabledTest.php` to the
runtime
mechanism: registration enabled by default, route helpers always
registered
regardless of the flag, `GET`/`POST /register` return 403 when disabled
(and no
  user is created), and the landing/login CTAs hide.
- Updated the `DashboardTest` disabled-registration case to toggle
`config('auth.registration_enabled')` instead of filtering the Fortify
feature.
- Verified `bun run build` succeeds with `REGISTRATION_ENABLED=false`
and that the
  `register` / `register.store` Wayfinder helpers are generated.

## Docs

- Updated `README.md` and `.env.example` to describe the 403 behavior
(routes stay
  registered rather than being removed).
2026-07-22 07:04:20 +00:00
Jesús Mejías Leiva 350e0031f1
feat(ai): make the AI provider configurable (any laravel/ai provider, incl. local Ollama) (#718)
## Summary

Closes #716.

Both AI execution paths hard-coded `provider: Lab::Gemini`, so even
though `laravel/ai` already understands `OLLAMA_URL` and the model was
env-overridable, no other provider could ever be reached by the actual
UI features. This makes the **AI provider configurable**, keeping
**Gemini as the default** so existing deployments are unaffected.

Although issue #716 asked specifically for **Ollama**, the fix is
generic: because the provider is resolved through the
`Laravel\Ai\Enums\Lab` enum, this unlocks **any text provider
`laravel/ai` supports** — `gemini`, `openai`, `anthropic`, `azure`,
`groq`, `xai`, `deepseek`, `mistral`, and self-hosted `ollama`. Ollama
is the headline case (fully local, private processing), but nothing in
the code is Ollama-specific.

> Note: each provider still needs its own credentials configured for
`laravel/ai` (e.g. `GEMINI_API_KEY`, `OPENAI_API_KEY`, `OLLAMA_URL`),
and only text-capable providers apply — a non-text or unknown provider
fails fast.

## Changes

- **`config/ai_suggestions.php` / `config/ai_categorization.php`** — add
a `provider` key. Each reads its own `AI_SUGGESTIONS_PROVIDER` /
`AI_CATEGORIZATION_PROVIDER`, both falling back to a shared
`AI_PROVIDER` and finally `gemini`. So `AI_PROVIDER=<provider>` flips
every AI feature at once, and either feature can still be overridden
individually.
- **`app/Services/Ai/CategorizeTransactions.php` /
`app/Services/Ai/LaravelAiRuleSuggestionGenerator.php`** — resolve the
configured provider to the `Laravel\Ai\Enums\Lab` enum via
`Lab::from((string) config('...provider'))` and pass it to `prompt()`
(the SDK recommends referencing providers by the `Lab` enum rather than
a plain string). `Lab::from()` also **validates** the value: an unknown
provider fails fast with a clear `ValueError` instead of erroring deep
in the provider stack.
- **`.env.example`** — document the provider vars plus an Ollama block
(`OLLAMA_URL`, `OLLAMA_API_KEY`, `AI_CATEGORIZATION_MODEL`), kept
commented so defaults stay Gemini.
- **`README.md`** — new *AI Provider* section: the generic provider
switch, the list of supported text providers, and a local-Ollama
example.
- **Tests** — cover the `gemini` default and a non-Gemini (`ollama`)
override for both the categorization and rule-suggestion paths, plus the
fail-fast `ValueError` on an unknown provider.

## Usage

Any supported provider follows the same pattern — set `AI_PROVIDER`,
that provider's credentials, and the `*_MODEL` vars. Example, fully
local/private with Ollama:

```dotenv
AI_PROVIDER=ollama
OLLAMA_URL=http://ollama.example.local:11434
AI_SUGGESTIONS_MODEL=gemma3:12b
AI_CATEGORIZATION_MODEL=gemma3:12b
```

## Validation

- `./vendor/bin/pest tests/Feature/Ai` → **128 passed**.
- `vendor/bin/pint` and `vendor/bin/phpstan` (level 5) → clean.
- **End-to-end against a real Ollama server** (`gemma3:12b`), through
the actual application code (not faked):
  - Categorization: `MERCADONA COMPRA` → *Groceries*, confidence 0.95.
- Rule suggestion: `netflix` → *Subscriptions*, structured output
intact.

## Backward compatibility

Default provider is unchanged (`gemini`); no env changes are required
for existing installs.
2026-07-22 09:01:51 +02:00
Víctor Falcón a5117aaae6
refactor: remove HIDE_AUTH_BUTTONS launch gate and waitlist apparatus (#717)
## What & why

The `HIDE_AUTH_BUTTONS` flag and its signed-link auth-override existed
only to gate registration/login behind a waitlist during the launch
period. That period is long over, so this removes the flag and the whole
waitlist apparatus. Registration and login are now always open, still
governed by `REGISTRATION_ENABLED` (Fortify's registration feature),
which is untouched.

## Removed

- **`HIDE_AUTH_BUTTONS`** config/env and
**`LandingAuthOverrideService`** — the signed-link override, override
cookie, and `?force=` bypass — plus its
`GenerateLandingAuthLinkCommand`.
- **Waitlist lead-capture + invitation apparatus**: `UserLeadController`
+ `waitlist.*`/`user-leads.*` routes, `StoreUserLeadRequest`, the
landing `WaitlistForm`, `waitlist/*` pages, waitlist/invitation mails,
invitation & re-invitation commands, the lead verification notification,
`LeadCohort`/`LeadCohortResolver`/`LeadPromoCodeAllocator`, and the
lead→Resend segment sync (scheduled tasks removed too).
- Frontend `hideAuthButtons`/`forcedRegistration` threading and the
`?force=` query on auth links (header, welcome, register, login).
- All tests for the removed features; `landing.hide_auth_buttons` config
stubs stripped from surviving tests.

## Kept on purpose (data preservation)

The `user_leads` **table, its migrations, and a lean `UserLead` model
are retained** — no destructive migration. Pre-launch contacts are not
lost, and new signups whose email matches a preserved lead still receive
that lead's promo code at Stripe checkout
(`SubscriptionController::resolveLeadPromotionCodeId`, unchanged).

## Testing

- Full non-Browser Pest suite green locally (**1948 passed**), plus
Pint, Prettier, ESLint.
- Browser QA (Playwright) on the running app: landing no longer shows
the waitlist form and renders the register/login CTAs; `/register`
renders the form (previously returned null when hidden); `/login` shows
the "Sign up" link pointing to `/register` with no `?force`. 16/16
functional checks passed; only pre-existing external-resource 404s in
console (gravatar `d=404`, `via.placeholder.com` seed images).

## Demo


https://github.com/user-attachments/assets/18a70f6d-b82f-4d92-b9de-da2610dc017a
2026-07-22 08:51:48 +02:00
Jesús Mejías Leiva d55ee5bdcd
feat(auth): add REGISTRATION_ENABLED env to disable public sign-ups (#713)
## Summary

Adds a dedicated `REGISTRATION_ENABLED` env var (default `true`) so
self-hosters on a publicly reachable instance can **close public
sign-ups while keeping login open** — the exact "sign-ups closed, login
open, permanently" case from #711 that `HIDE_AUTH_BUTTONS` can't cover.

Closes #711.

## What changed

- **`config/fortify.php`** — the Fortify `registration()` feature is now
conditional on `env('REGISTRATION_ENABLED', true)`. When disabled:
- Fortify **never registers the `/register` routes** (GET form + POST
submit → `404`), i.e. a real server-side block, not just a hidden
button.
- The existing `canRegister =
Features::enabled(Features::registration())` flag flips to `false`
automatically everywhere it's used (`routes/web.php`,
`FortifyServiceProvider`, login page).
- **`resources/js/pages/welcome.tsx`** — the hardcoded `/register` CTAs
(hero button, Free/paid pricing cards, final "Ready to take control"
CTA) now respect `canRegister`, falling back to a `Log in` → `/login`
button when sign-ups are closed. The header already gated its `Register`
button on `canRegister`.
- **`.env.example` / `README.md`** — document the new variable.

**Backward compatible / optional:** `env('REGISTRATION_ENABLED', true)`
defaults to `true`, so existing installs that upgrade without setting
the var keep public sign-ups exactly as today. It's shown as a
commented, optional example in `.env.example`.

`/login` and the "Iniciar sesión / Log in" button stay fully available
in all cases. `AuthEntryPointService::guestRedirectRoute()` already
redirects guests to `/login` when registration is disabled, so no dead
`route('register')` calls remain.

## Tests

New `tests/Feature/Auth/RegistrationDisabledTest.php`:

- registration feature present by default / removed when
`REGISTRATION_ENABLED=false` (config-level).
- landing page exposes `canRegister=true` by default and `false` when
the feature is disabled.
- login page hides its sign-up link when registration is disabled.
- **`/register` GET + POST return 404 and the named routes are gone**
when disabled (via `refreshApplication()` with the env set), while
`/login` still returns 200.

All new tests pass; the wider `tests/Feature/Auth` + landing-override
suites stay green (the one unrelated `Asia/Calcutta` legacy-timezone
failure is a pre-existing tzdata quirk of the CI-less local box, not
touched here). `pint`, `prettier --check`, and `eslint` are clean.

## Manual QA

Verified against a running instance (headless Chromium) in both states:

| `REGISTRATION_ENABLED=true` (default) | `REGISTRATION_ENABLED=false` |
| --- | --- |
| Header: `Log in` + `Register`; hero CTA `Get Started` → `/register` |
Header: `Register` gone, only `Log in`; hero CTA `Log in` → `/login` |
| `GET /register` → `200` | `GET /register` → `404`, `GET /login` →
`200`, `Route::has('register')` → `false` |
2026-07-21 16:22:45 +02:00
Víctor Falcón abe31ff967
Update README.md (#662) 2026-07-09 09:16:29 +00:00
Víctor Falcón cbc2f4f85f
chore: update Discord invite link (#452)
Swap Discord invite link `discord.gg/2WZmDW9QZ8` →
`discord.gg/m8hUhx6D9D` across all files (README, header, user menu,
connections page, welcome email, test).
2026-05-29 15:56:48 +02:00
Víctor Falcón 259a9a9712
chore: replace Caddy with Portless for local HTTPS proxy (#258)
## Summary

- Replace Caddy reverse proxy with [Portless](https://portless.sh) for
local HTTPS, eliminating manual cert generation and `/etc/hosts` editing
- Two URL strategies coexist: `composer run dev` uses worktree-aware
URLs (`https://<branch>.dev.whisper.money.localhost`), `whispermoney
start` uses a fixed alias (`https://whisper.money.localhost`)
- Remove Caddyfile, `docker/caddy/` directory, caddy service from
`compose.yaml`, and cert-related `.gitignore` entries
- Simplify `vite.config.ts` by removing caddy cert detection block (Vite
stays on plain HTTP since browsers treat localhost as secure context)
- Overhaul `public/setup.sh` to use `portless trust`, `portless proxy
start`, and `portless alias` instead of SSL cert generation and hosts
file editing
2026-04-02 16:39:44 +01:00
Víctor Falcón d7f0084338 fix: Discord link 2026-02-18 13:00:30 +01:00
Víctor Falcón 1f15bdf923
Update image URL in README.md 2026-02-15 07:54:02 +01:00
Víctor Falcón 60ebe7e86f
Replace E2E encryption mentions with privacy-first messaging (#124)
## Summary

- Removes all references to "end-to-end encryption" and "E2E" across the
codebase
- Replaces with privacy-first messaging: data is never shared with third
parties, users own their data, app remains private and secure
- Updates all user-facing content: landing page, onboarding, billing,
terms, emails, setup script, subscription plans, and Coolify template
- Updates Spanish translations (`lang/es.json`) to match new messaging
- Updates `CLAUDE.md` and `README.md` project descriptions

## Files changed (12)

- `CLAUDE.md` — Project description
- `README.md` — Feature list
- `config/subscriptions.php` — Plan feature lists
- `lang/es.json` — Spanish translations (18 keys updated)
- `public/setup.sh` — CLI banner text
- `resources/js/components/onboarding/step-smart-rules.tsx` — Onboarding
step
- `resources/js/pages/settings/billing.tsx` — Billing benefits
- `resources/js/pages/terms.tsx` — Terms of service
- `resources/views/mail/drip/promo-code.blade.php` — Promo email
- `resources/views/mail/drip/welcome.blade.php` — Welcome email
- `resources/views/mail/user-lead-invitation.blade.php` — Lead
invitation email
- `templates/coolify/whisper-money.yaml` — Coolify slogan

## Test plan

- [x] All 492 tests pass
- [x] Pint formatting passes
- [x] Prettier formatting passes
- [x] ESLint passes
2026-02-14 16:59:50 +01:00
Víctor Falcón 1083f60494 chore: Update discord link 2026-02-10 14:12:37 +01:00
Víctor Falcón 9620d3cfe1 chore: Update README to make it clearer that whispermoney install command must be run first 2026-01-27 12:09:35 +01:00
Víctor Falcón ccba0cde12 Add star history to the README 2026-01-26 14:59:29 +01:00
Víctor Falcón 02b8317b17 docs: Update README 2026-01-17 19:47:15 +01:00
Víctor Falcón ffd96949e5
feat: Add wispermoney local command (#59)
## Changes

- Moved `setup.sh` to `public/setup.sh` so it's accessible via
`https://whisper.money/setup.sh` for remote users
- Added `wispermoney` wrapper command in root directory for easy local
execution
- Updated README to document the new `wispermoney` command
- Removed duplicate `setup.sh` from root directory

## Benefits

- Remote users can still use: `bash <(curl -fsSL
https://whisper.money/setup.sh)`
- Local developers can now use: `./wispermoney install` (or
start/stop/upgrade)
- Single source of truth - no duplicate setup scripts
- Cleaner project structure
2026-01-16 16:40:15 +01:00
Víctor Falcón 819bea1922
feat: Automated setup script for local deployment (#58)
This PR adds a comprehensive setup script that automates the entire
local development setup process.

<img width="1470" height="932" alt="image"
src="https://github.com/user-attachments/assets/b173333a-6605-4fd9-b2e4-4c62d164d048"
/>

### Key Features

- **Automated Setup Script** (`setup.sh`): One-command installation that
handles everything
- **Caddy Integration**: Replaced Traefik with Caddy for simpler SSL
certificate management
- **Automatic Repository Cloning**: Script can be run remotely and will
clone the repo automatically
- **SSL Certificate Management**: Uses mkcert for trusted local
certificates
- **Automatic Hosts Configuration**: Configures /etc/hosts automatically
- **Version Checking**: Warns users when their local copy is outdated
- **Service Management**: Easy commands to start/stop/upgrade services

### Improvements

- Simplified local development setup
- Better SSL certificate handling (no browser warnings with mkcert)
- Updated README with quick start instructions
- Updated environment configuration for Docker
- Added production Dockerfile for CI/CD builds

### Usage

```bash
bash <(curl -fsSL https://whisper.money/setup.sh)
```

After installation, visit https://whisper.money.local
2026-01-16 15:57:12 +01:00
Víctor Falcón fc4bc8610b Add demo account link on the readme page 2026-01-07 11:26:01 +01:00
Víctor Falcón 2634a501b5
Enhance README with language options (#50)
Added multilingual links to the README for better accessibility.
2026-01-05 12:23:13 +01:00
Víctor Falcón beecff404a
Add subscription cancellation email with manual trigger command (#47)
## Summary

- Add a subscription cancellation email that can be manually triggered
via artisan command
- Include CONTINUE50 coupon code (50% off current and future payments
for monthly/yearly subscriptions)
- Add option to disable all drip emails via `DRIP_EMAILS_ENABLED` env
var

## Usage

```bash
# Send to single user
php artisan email:subscription-cancelled user@example.com

# Send to multiple users
php artisan email:subscription-cancelled user1@example.com,user2@example.com

# Disable all drip emails
DRIP_EMAILS_ENABLED=false
```
2025-12-30 20:32:44 +01:00
Víctor Falcón b641b23ca2 docs: add Coolify deployment instructions with direct template link 2025-12-30 07:22:19 +01:00
Víctor Falcón 39ef165655 Add production Docker setup for easy self-hosting with the CI-built image (#42)
- Add production Docker setup for easy self-hosting with the CI-built
image
- Build multi-architecture Docker images (amd64 + arm64)

### Changes

  Docker Production Setup
- Add docker-compose.production.yml for running the production image
locally
- Add docker/entrypoint.sh with automatic setup (migrations, caching,
storage, APP_KEY generation)
  - Add HEALTHCHECK to Dockerfile for container health monitoring
  - Add .env.production.example with documented production defaults

### CI/CD
  - Add QEMU for multi-arch builds
  - Build Docker images for both linux/amd64 and linux/arm64

### Coolify / WIP
  - Add templates/coolify/whisper-money.yaml one-click template
  - Includes MySQL service with health checks
  - Auto-generates credentials using Coolify's SERVICE_* variables

### Documentation
  - Update README with production Docker instructions
  - Add environment variable reference table
2025-12-30 07:22:19 +01:00
Víctor Falcón be74d7844b Update README: reformat community section and fix repository URL 2025-12-12 19:33:16 +01:00
Víctor Falcón 415ecb2a49 Add Discord community section to README and remove unused license image reference 2025-12-12 18:24:53 +01:00
Víctor Falcón c70ca03138
Remove commented Product Hunt badge from README 2025-12-02 09:24:03 +01:00
Víctor Falcón 33858a04db
Add Product Hunt badge to README 2025-11-27 09:30:29 +01:00
Víctor Falcón 009f370500
Revise README for license details and CI badge
Updated the README to reflect license information and badge formatting.
2025-11-26 17:11:17 +01:00
Víctor Falcón 7b20798a39
Update README with project description and CI badge 2025-11-26 12:50:36 +01:00
Víctor Falcón 85fd6c0910 Add README.md and update .env.example for HTTPS URL 2025-11-26 12:28:24 +01:00