Skip to content

Commit 624d4c9

Browse files
authored
Merge pull request #1143 from HiEventsDev/feat/recurring-events
2 parents e0b34a9 + 8fb1413 commit 624d4c9

558 files changed

Lines changed: 80440 additions & 21055 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
---
2+
name: translations
3+
description: Frontend translation workflow using Lingui - extracting, adding, and compiling translations for all supported languages
4+
---
5+
6+
# Frontend Translation Workflow (Lingui)
7+
8+
IMPORTANT: Always update translations as you develop features. When adding new translatable strings, immediately add translations for all supported languages.
9+
10+
## Core Commands
11+
12+
```bash
13+
cd frontend
14+
15+
# Extract translatable strings (use --clean for accurate counts)
16+
yarn messages:extract --clean
17+
18+
# Compile translations for production
19+
yarn messages:compile
20+
21+
# Check for untranslated strings
22+
cd scripts && ./list_untranslated_strings.sh
23+
```
24+
25+
## Process
26+
27+
1. **Extract**: `yarn messages:extract --clean`
28+
2. **Check**: Look at the output table for missing translation counts
29+
3. **Add translations**: Update the `.po` files for each language
30+
4. **Verify**: Run extract again to confirm 0 missing
31+
5. **Compile**: `yarn messages:compile`
32+
33+
## Adding Translations
34+
35+
Add entries to each locale's `.po` file in `frontend/src/locales/`:
36+
37+
```po
38+
#: src/path/to/component.tsx:123
39+
msgid "Your English String"
40+
msgstr "Translated String"
41+
```
42+
43+
## Supported Languages
44+
45+
| Code | Language |
46+
|------|----------|
47+
| en | English (source - no translation needed) |
48+
| de | Deutsch |
49+
| es | Espanol |
50+
| fr | Francais |
51+
| pt | Portugues |
52+
| pt-br | Portugues do Brasil |
53+
| it | Italiano |
54+
| nl | Nederlands |
55+
| zh-cn | Simplified Chinese |
56+
| zh-hk | Traditional Chinese (HK) |
57+
| vi | Tieng Viet |
58+
| ru | Russian (currently untranslated) |
59+
60+
## Troubleshooting
61+
62+
- **Counts seem wrong**: Use `--clean` flag to remove obsolete entries
63+
- **Translation not appearing**: Run `yarn messages:compile` after adding
64+
- **Syntax errors**: Check for proper escaping of quotes in `.po` files
Lines changed: 1 addition & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1 @@
1-
---
2-
name: translations
3-
description: Frontend translation workflow using Lingui - extracting, adding, and compiling translations for all supported languages
4-
---
5-
6-
# Frontend Translation Workflow (Lingui)
7-
8-
IMPORTANT: Always update translations as you develop features. When adding new translatable strings, immediately add translations for all supported languages.
9-
10-
## Core Commands
11-
12-
```bash
13-
cd frontend
14-
15-
# Extract translatable strings (use --clean for accurate counts)
16-
yarn messages:extract --clean
17-
18-
# Compile translations for production
19-
yarn messages:compile
20-
21-
# Check for untranslated strings
22-
cd scripts && ./list_untranslated_strings.sh
23-
```
24-
25-
## Process
26-
27-
1. **Extract**: `yarn messages:extract --clean`
28-
2. **Check**: Look at the output table for missing translation counts
29-
3. **Add translations**: Update the `.po` files for each language
30-
4. **Verify**: Run extract again to confirm 0 missing
31-
5. **Compile**: `yarn messages:compile`
32-
33-
## Adding Translations
34-
35-
Add entries to each locale's `.po` file in `frontend/src/locales/`:
36-
37-
```po
38-
#: src/path/to/component.tsx:123
39-
msgid "Your English String"
40-
msgstr "Translated String"
41-
```
42-
43-
## Supported Languages
44-
45-
| Code | Language |
46-
|------|----------|
47-
| en | English (source - no translation needed) |
48-
| de | Deutsch |
49-
| es | Espanol |
50-
| fr | Francais |
51-
| pt | Portugues |
52-
| pt-br | Portugues do Brasil |
53-
| it | Italiano |
54-
| nl | Nederlands |
55-
| zh-cn | Simplified Chinese |
56-
| zh-hk | Traditional Chinese (HK) |
57-
| vi | Tieng Viet |
58-
| ru | Russian (currently untranslated) |
59-
60-
## Troubleshooting
61-
62-
- **Counts seem wrong**: Use `--clean` flag to remove obsolete entries
63-
- **Translation not appearing**: Run `yarn messages:compile` after adding
64-
- **Syntax errors**: Check for proper escaping of quotes in `.po` files
1+
see @../../../.agents/skills/translations/SKILL.md
Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,28 @@
1-
name: Run Unit Tests
1+
name: Backend Tests
22

33
on:
44
push:
55
branches: [main, develop]
66
paths:
77
- 'backend/**'
8-
- '.github/workflows/unit-tests.yml'
8+
- '.github/workflows/tests.yml'
99
pull_request:
1010
paths:
1111
- 'backend/**'
12-
- '.github/workflows/unit-tests.yml'
12+
- '.github/workflows/tests.yml'
1313

1414
jobs:
15-
run-tests:
15+
tests:
1616
runs-on: ubuntu-latest
1717

1818
strategy:
1919
matrix:
2020
php-versions: ['8.2', '8.3', '8.4']
2121

2222
services:
23+
# Postgres is started for the Feature suite. The Unit suite does not need
24+
# a live connection — it only reads DB_DATABASE for the _test guard check
25+
# in CreatesApplication — so it runs in parallel with Postgres warming up.
2326
postgres:
2427
image: postgres:15
2528
env:
@@ -80,6 +83,13 @@ jobs:
8083
# to .env so both paths see the same config.
8184
run: cp backend/.env.testing backend/.env
8285

86+
- name: Run Unit test suite
87+
# Pure unit tests — no DB connection, no migrations. The CreatesApplication
88+
# bootstrap detects the absence of DatabaseTransactions / RefreshDatabase
89+
# traits and skips migrate:fresh entirely. Runs in parallel with the
90+
# Postgres service container coming up.
91+
run: cd backend && ./vendor/bin/phpunit --testsuite=Unit --no-coverage
92+
8393
- name: Wait for Postgres
8494
run: |
8595
for i in {1..30}; do
@@ -91,5 +101,8 @@ jobs:
91101
echo "Postgres did not become ready in time" >&2
92102
exit 1
93103
94-
- name: Run PHPUnit Tests
95-
run: cd backend && ./vendor/bin/phpunit tests/Unit --no-coverage
104+
- name: Run Feature test suite
105+
# Integration tests against the real PostgreSQL test database. The first
106+
# test that boots Laravel triggers migrate:fresh once per process via
107+
# CreatesApplication::ensureTestDatabaseIsMigrated.
108+
run: cd backend && ./vendor/bin/phpunit --testsuite=Feature --no-coverage

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ prompts/
2020

2121
/plans/**
2222
/plans
23+
24+
.claude/worktrees/

backend/app/Console/Kernel.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,27 @@
66
use HiEvents\Jobs\Waitlist\ProcessExpiredWaitlistOffersJob;
77
use Illuminate\Console\Scheduling\Schedule;
88
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
9+
use Illuminate\Support\Facades\DB;
10+
use Illuminate\Support\Facades\Log;
911

1012
class Kernel extends ConsoleKernel
1113
{
1214
protected function schedule(Schedule $schedule): void
1315
{
1416
$schedule->job(new SendScheduledMessagesJob)->everyMinute()->withoutOverlapping();
1517
$schedule->job(new ProcessExpiredWaitlistOffersJob)->everyMinute()->withoutOverlapping();
18+
19+
$schedule->call(function (): void {
20+
$count = DB::table('failed_jobs')->count();
21+
if ($count > 0) {
22+
Log::warning('Failed jobs present in queue', ['count' => $count]);
23+
}
24+
})->everyFiveMinutes()->name('failed-jobs-monitor')->withoutOverlapping();
1625
}
1726

1827
protected function commands(): void
1928
{
20-
$this->load(__DIR__ . '/Commands');
29+
$this->load(__DIR__.'/Commands');
2130

2231
include base_path('routes/console.php');
2332
}

backend/app/DomainObjects/AttendeeDomainObject.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ class AttendeeDomainObject extends Generated\AttendeeDomainObjectAbstract implem
2323
/** @var Collection<AttendeeCheckInDomainObject>|null */
2424
private ?Collection $checkIns = null;
2525

26+
private ?EventOccurrenceDomainObject $eventOccurrence = null;
27+
2628
public static function getDefaultSort(): string
2729
{
2830
return self::CREATED_AT;
@@ -71,6 +73,7 @@ public static function getAllowedFilterFields(): array
7173
self::STATUS,
7274
self::PRODUCT_ID,
7375
self::PRODUCT_PRICE_ID,
76+
self::EVENT_OCCURRENCE_ID,
7477
];
7578
}
7679

@@ -138,4 +141,15 @@ public function getCheckIns(): ?Collection
138141
{
139142
return $this->checkIns;
140143
}
144+
145+
public function setEventOccurrence(?EventOccurrenceDomainObject $eventOccurrence): AttendeeDomainObject
146+
{
147+
$this->eventOccurrence = $eventOccurrence;
148+
return $this;
149+
}
150+
151+
public function getEventOccurrence(): ?EventOccurrenceDomainObject
152+
{
153+
return $this->eventOccurrence;
154+
}
141155
}

backend/app/DomainObjects/CheckInListDomainObject.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,20 @@
77
use HiEvents\DomainObjects\SortingAndFiltering\AllowedSorts;
88
use Illuminate\Support\Collection;
99

10+
/**
11+
* A `null` event_occurrence_id means the list applies to every occurrence of the
12+
* event (the default for recurring events and the only meaningful value for
13+
* single-occurrence events). A non-null value scopes the list to that one
14+
* occurrence — only attendees on that occurrence can be checked in via the list.
15+
*/
1016
class CheckInListDomainObject extends Generated\CheckInListDomainObjectAbstract implements IsSortable
1117
{
1218
private ?Collection $products = null;
1319

1420
private ?EventDomainObject $event = null;
1521

22+
private ?EventOccurrenceDomainObject $eventOccurrence = null;
23+
1624
private ?int $checkedInCount = null;
1725

1826
private ?int $totalAttendeesCount = null;
@@ -77,6 +85,18 @@ public function setEvent(?EventDomainObject $event): static
7785
return $this;
7886
}
7987

88+
public function getEventOccurrence(): ?EventOccurrenceDomainObject
89+
{
90+
return $this->eventOccurrence;
91+
}
92+
93+
public function setEventOccurrence(?EventOccurrenceDomainObject $eventOccurrence): static
94+
{
95+
$this->eventOccurrence = $eventOccurrence;
96+
97+
return $this;
98+
}
99+
80100
public function isExpired(string $timezone): bool
81101
{
82102
if ($this->getExpiresAt() === null) {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace HiEvents\DomainObjects\Enums;
4+
5+
enum BulkOccurrenceAction: string
6+
{
7+
use BaseEnum;
8+
9+
case UPDATE = 'update';
10+
case CANCEL = 'cancel';
11+
case DELETE = 'delete';
12+
}

backend/app/DomainObjects/Enums/EmailTemplateType.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ enum EmailTemplateType: string
88

99
case ORDER_CONFIRMATION = 'order_confirmation';
1010
case ATTENDEE_TICKET = 'attendee_ticket';
11+
case OCCURRENCE_CANCELLATION = 'occurrence_cancellation';
1112

1213
public function label(): string
1314
{
1415
return match ($this) {
1516
self::ORDER_CONFIRMATION => __('Order Confirmation'),
1617
self::ATTENDEE_TICKET => __('Attendee Ticket'),
18+
self::OCCURRENCE_CANCELLATION => __('Date Cancellation'),
1719
};
1820
}
1921

@@ -22,6 +24,16 @@ public function description(): string
2224
return match ($this) {
2325
self::ORDER_CONFIRMATION => __('Sent to the customer after placing an order'),
2426
self::ATTENDEE_TICKET => __('Sent to each attendee with their ticket'),
27+
self::OCCURRENCE_CANCELLATION => __('Sent to attendees when a scheduled date is cancelled'),
28+
};
29+
}
30+
31+
public function ctaUrlToken(): string
32+
{
33+
return match ($this) {
34+
self::ORDER_CONFIRMATION => 'order.url',
35+
self::ATTENDEE_TICKET => 'ticket.url',
36+
self::OCCURRENCE_CANCELLATION => 'event.url',
2537
};
2638
}
2739
}

0 commit comments

Comments
 (0)