A wishlist and gift list app where all affiliate revenue goes to charity. Create wishlists, share them with friends and family, and feel good knowing that every purchase supports a good cause.
GiveTwice is built on a simple idea: gifting that gives back. When someone buys a gift from your wishlist through an affiliate link, the revenue doesn't go to us - it goes directly to charity. You get exactly what you want, your loved ones know they're making you happy, and together you're making a difference.
- Backend: Laravel 12, PHP 8.4, MySQL
- Frontend: Blade templates, Tailwind CSS v4, Alpine.js
- App server: Laravel Octane + FrankenPHP (high-performance)
- Queue: Laravel Horizon + Redis
- WebSockets: Laravel Reverb (real-time updates)
- Auth: Laravel Fortify + Socialite (Google/Facebook OAuth)
All user-facing routes are prefixed with /{locale} (en, nl, fr). The root / redirects to the user's detected browser locale. Auth POST routes (handled by Fortify) have no locale prefix.
User
├── has many: Gifts (items they want)
├── has many: GiftLists (organized collections)
└── has many: Claims (gifts they've claimed for others)
Gift
├── belongs to: User
├── belongs to many: GiftLists
└── has many: Claims
GiftList
├── belongs to: User
└── belongs to many: Gifts
Claim
├── belongs to: Gift
└── belongs to: User (nullable, supports anonymous claims)
GiveTwice uses Laravel events to decouple core workflows:
| Event | Trigger | Listeners |
|---|---|---|
GiftCreated |
User adds a gift | Dispatches FetchGiftDetailsAction to queue |
GiftFetchCompleted |
Product details fetched | Broadcasts to user via WebSocket |
GiftClaimed |
Someone claims a gift | Broadcasts to list owner via WebSocket |
Business logic lives in single-responsibility Action classes (app/Actions/):
FetchGiftDetailsAction- Fetches product title, image, price from URL (queued)ClaimGiftAction- Registered user claims a giftCreatePendingClaimAction- Anonymous user starts claim (sends confirmation email)ConfirmClaimAction- Anonymous user confirms via email token
Laravel Reverb provides WebSocket connections. When a gift is fetched or claimed, the frontend updates instantly without page refresh. Private channels ensure users only see their own updates.
- PHP 8.4+
- Composer
- Node.js 18+
- MySQL
- Redis
# Clone the repository
git clone https://github.com/GiveTwice/givetwice.app.git
cd givetwice.app
# Install dependencies
composer install
yarn install
# Configure environment
cp .env.example .env
php artisan key:generate
# Set up database
# Update .env with your MySQL credentials (default: gifting_app, root, no password)
php artisan migrate --seed
# Build assets
yarn build# Start all services (recommended)
composer dev
# Or start individually:
php artisan octane:start --watch # App server with hot reload
php artisan horizon # Queue workers
php artisan reverb:start # WebSocket server
yarn dev # Vite dev server| Password | Admin | |
|---|---|---|
| m@ttias.be | localdevelopment | Yes |
| john@doe.tld | localdevelopment | No |
Run Laravel Pint after modifying PHP files:
./vendor/bin/pint path/to/file.phpWe use Pest for testing:
./vendor/bin/pestWrite tests in Pest's functional syntax:
it('creates a gift for the authenticated user', function () {
$user = User::factory()->create();
$response = $this->actingAs($user)
->post(route('gifts.store', 'en'), [
'url' => 'https://example.com/product',
]);
expect($user->gifts)->toHaveCount(1);
});All user-facing text uses __() for translations. When adding new strings:
- Add the English string to
lang/en.json - Add translations to
lang/nl.jsonandlang/fr.jsonetc
- Add the locale to
app/Enums/SupportedLocale.php - Run
php artisan lang:add <locale> - Copy
lang/en.jsontolang/<locale>.jsonand translate
app/
├── Actions/ # Single-responsibility business logic
├── Enums/ # SupportedLocale, SupportedCurrency
├── Events/ # Domain events (GiftCreated, GiftClaimed)
├── Http/Controllers/
├── Models/
└── Policies/ # Authorization rules
resources/
├── css/app.css # Tailwind config + custom component classes
├── views/
│ ├── components/ # Blade components
│ ├── layouts/ # App and guest layouts
│ └── ...
└── js/
lang/
├── en.json # App translations
├── nl.json
└── fr.json
Features on the roadmap:
- Affiliate integration - Connect with affiliate networks to track purchases and route revenue to charity (PRIORITY)
- Friend system - Connect with friends to see their wishlists and get notified about updates
- Notification emails - Email claimer with confirmation of claim, receive emails when friends update their wishlists
This project is open-sourced software licensed under the MIT license.