Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions .claude/skills/translations/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
name: translations
description: Frontend translation workflow using Lingui - extracting, adding, and compiling translations for all supported languages
---

# Frontend Translation Workflow (Lingui)

IMPORTANT: Always update translations as you develop features. When adding new translatable strings, immediately add translations for all supported languages.

## Core Commands

```bash
cd frontend

# Extract translatable strings (use --clean for accurate counts)
yarn messages:extract --clean

# Compile translations for production
yarn messages:compile

# Check for untranslated strings
cd scripts && ./list_untranslated_strings.sh
```

## Process

1. **Extract**: `yarn messages:extract --clean`
2. **Check**: Look at the output table for missing translation counts
3. **Add translations**: Update the `.po` files for each language
4. **Verify**: Run extract again to confirm 0 missing
5. **Compile**: `yarn messages:compile`

## Adding Translations

Add entries to each locale's `.po` file in `frontend/src/locales/`:

```po
#: src/path/to/component.tsx:123
msgid "Your English String"
msgstr "Translated String"
```

## Supported Languages

| Code | Language |
|------|----------|
| en | English (source - no translation needed) |
| de | Deutsch |
| es | Espanol |
| fr | Francais |
| pt | Portugues |
| pt-br | Portugues do Brasil |
| it | Italiano |
| nl | Nederlands |
| zh-cn | Simplified Chinese |
| zh-hk | Traditional Chinese (HK) |
| vi | Tieng Viet |
| ru | Russian (currently untranslated) |

## Troubleshooting

- **Counts seem wrong**: Use `--clean` flag to remove obsolete entries
- **Translation not appearing**: Run `yarn messages:compile` after adding
- **Syntax errors**: Check for proper escaping of quotes in `.po` files
1 change: 1 addition & 0 deletions .cursorrules
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
See [CLAUDE.md](./CLAUDE.md) for project conventions and development guidelines.
1 change: 1 addition & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
See [CLAUDE.md](../CLAUDE.md) for project conventions and development guidelines.
19 changes: 12 additions & 7 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
Thank you for creating a PR! We appreciate your contribution to Hi.Events.
> ℹ️ Thank you for your contribution! We really appreciate it. However, please note that PRs that do not follow the [contribution guidelines](https://github.com/HiEventsDev/hi.events/blob/develop/CONTRIBUTING.md) will be closed without review. Non-trivial changes require prior discussion via an issue or discussion thread. PRs with empty or unedited descriptions will also be closed.
>
> Please delete everything above this line, then fill in the sections below.

To make the process as smooth as possible, please ensure you've read the [contributing guidelines](https://github.com/HiEventsDev/hi.events/blob/develop/CONTRIBUTING.md) before proceeding.
---

Please include a summary of the changes and the issue being fixed or the feature being added. The more detail, the better!
## What changes I've made

## Why I've made these changes

## How I've tested these changes

## Checklist

- [ ] I have read the contributing guidelines.
- [ ] My code is of good quality and follows the coding standards of the project.
- [ ] I have read the [contributing guidelines](https://github.com/HiEventsDev/hi.events/blob/develop/CONTRIBUTING.md).
- [ ] My code follows the coding standards of the project.
- [ ] I have tested my changes, and they work as expected.

Thank you for your contribution! 🎉
- [x] I understand that this PR will be closed if I do not follow the [contributor guidelines](https://github.com/HiEventsDev/hi.events/blob/develop/CONTRIBUTING.md) and if this PR template is left unedited.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
frontend/.env
backend/.env
docker/all-in-one/.env
docker-compose.override.yml
todo.md
CLAUDE.md
/prompts/**
/prompts

Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
See [CLAUDE.md](./CLAUDE.md) for project conventions and development guidelines.
130 changes: 130 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Hi.Events is an open-source event management and ticketing platform with a Laravel backend and React frontend, using Domain-Driven Design (DDD).

## Key Commands

### Backend (Laravel)

Commands must be executed in the `backend` docker container:

```bash
cd docker/development

docker compose -f docker-compose.dev.yml exec backend php artisan migrate
docker compose -f docker-compose.dev.yml exec backend php artisan generate-domain-objects
docker compose -f docker-compose.dev.yml exec backend php artisan test
docker compose -f docker-compose.dev.yml exec backend php artisan test --filter=TestName
docker compose -f docker-compose.dev.yml exec backend php artisan test --testsuite=Unit
docker compose -f docker-compose.dev.yml exec backend ./vendor/bin/pint --test
```

### Frontend (React + Vite) - SSR Only

```bash
cd frontend
yarn install
yarn dev:ssr # Development server
yarn build # SSR build
yarn messages:extract # Extract translatable strings
yarn messages:compile # Compile translations
npx tsc --noEmit # TypeScript validation
```

### Docker Development

```bash
cd docker/development
./start-dev.sh # Unsigned SSL certs
./start-dev.sh --certs=signed # Signed certs with mkcert
```

## Development Guidelines

### Backend

#### Architecture Flow
- Request flow: **Action → Handler → Domain Service → Repository**
- Handlers can use repositories directly when a service would be overkill
- No Eloquent in handlers or services — Eloquent belongs in repositories only
- Favour composition over inheritance
- Keep code clean, but don't be dogmatic about it

#### General Standards
- **ALWAYS** wrap all translatable strings in `__()` helper
- Domain Objects are auto-generated via `php artisan generate-domain-objects` - never edit manually
- **Always** create unit tests for new features in `backend/tests/Unit/`
- **DON'T** add comments unless absolutely necessary
- **ALWAYS** sanitize user-provided content with `HtmlPurifierService` before storing, especially content rendered as HTML

#### DTOs
- Use Spatie Laravel Data package for all new DTOs
- **ALWAYS** extend `BaseDataObject`, not `BaseDTO`
- **ALWAYS** favor DTOs over arrays when returning multiple values from services

#### HTTP Actions
- Always extend `BaseAction.php`
- **ALWAYS** use BaseAction response methods: `resourceResponse()`, `jsonResponse()`, `errorResponse()`, `deletedResponse()`, etc. Never use `response()->json()` or `new JsonResponse()` directly
- Always use `isActionAuthorized` for non-public endpoints
- **DON'T** create actions handling multiple entity types with optional parameters - create separate, focused actions instead
- **DO** use base classes to share common validation and logic

#### Exception Handling
- **DON'T** use generic exceptions like `InvalidArgumentException` and `RuntimeException`
- **DO** use custom exceptions (e.g., `EmailTemplateValidationException`, `ResourceConflictException`)
- **DO** catch custom exceptions in actions and convert to `ValidationException::withMessages()` or appropriate error responses

#### Repository Pattern
- Favour existing repository methods over creating bespoke ones. E.g., use `findFirstWhere(['event_id' => $eventId])` instead of creating `findByEventId`

#### Database & Migrations
- **DO** use auto-incrementing integer IDs (`$table->id()`), not UUIDs
- Use anonymous class syntax for migrations

#### Enums
- Status enums go in `backend/app/DomainObjects/Status/`
- Other enums go in `backend/app/DomainObjects/Enums/`

#### Testing
- **DON'T** use `RefreshDatabase` - use `DatabaseTransactions` instead
- Unit tests extend Laravel's TestCase, not PHPUnit's TestCase
- Use Mockery for mocking

### Frontend

#### General Standards
- This is a SSR app - ensure safe usage of `window` and `document` objects
- Favour using existing components over creating new ones
- **DON'T** include unnecessary React imports
- **ALWAYS** add translations when adding new user-facing strings - use Lingui `t` function or `Trans` component
- **IMMEDIATELY** after adding translatable strings, add translations for all supported languages (use `/translations` skill for the workflow)

#### Data Fetching
- Use React Query for all API interactions
- Query example: `frontend/src/queries/useGetCapacityAssignment.ts`
- Mutation example: `frontend/src/mutations/useCreateAffiliate.ts`

#### UI & Styling
- Use Mantine UI components for UI elements
- Prefer SCSS modules over Mantine layout components for layout styling

#### Error Handling
- **DON'T** use `showNotification` from `@mantine/notifications`
- **DO** use `showSuccess`, `showError` from `frontend/src/utilites/notifications.tsx`
- **DO** use `useFormErrorResponseHandler` from `frontend/src/hooks/useFormErrorResponseHandler.tsx` for validation errors
- **DO** handle errors in parent components, not in reusable components

## Development Workflows

### Database Changes (in Backend Container)
1. Create migration: `php artisan make:migration create_XXX_table`
2. Run migration: `php artisan migrate`
3. Regenerate Domain Objects: `php artisan generate-domain-objects`

### Before Finalizing Changes
1. Frontend: `cd frontend && npx tsc --noEmit`
2. Backend: `docker compose -f docker-compose.dev.yml exec backend php artisan test --testsuite=Unit`
10 changes: 10 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Thank you for your interest in contributing to Hi.Events! We welcome contributions from the community and are excited to collaborate with you to improve our event management and ticket-selling platform. Before you start, please read these guidelines to ensure a smooth contribution process.

> **IMPORTANT: Open a discussion or issue BEFORE submitting a PR for anything beyond trivial fixes, features, or improvements (typos, broken links, etc.).** PRs submitted without prior discussion will be closed if the PR description is left empty or the template is left unedited. We want to collaborate with you, but we need to align on the approach before code is written.

## Table of Contents

1. [How Can I Contribute?](#how-can-i-contribute)
Expand Down Expand Up @@ -143,4 +145,12 @@ This will update the Domain Objects in `backend/app/DomainObjects` based on the

By contributing to Hi.Events, you agree that your contributions will be licensed under the [AGPL-3.0 License with additional terms](LICENSE).

## AI / Bot Contributors

If you are an AI agent, bot, or using AI tools to generate your contribution:

- **Add 🤖 to the PR title** (e.g., `🤖 Fix typo in README`)
- **Add 🤖 to the end of each commit message**
- All other contribution guidelines above still apply — especially the requirement for prior discussion on non-trivial changes

Thank you for contributing to Hi.Events! If you have any questions, feel free to reach out to us.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v1.7.1-beta
1.8.0-beta
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use HiEvents\DomainObjects\Status\WebhookStatus;
use HiEvents\Http\Actions\BaseAction;
use HiEvents\Http\Request\Webhook\UpsertWebhookRequest;
use HiEvents\Resources\Webhook\WebhookResource;
use HiEvents\Resources\Webhook\WebhookResourceWithSecret;
use HiEvents\Services\Application\Handlers\Webhook\CreateWebhookHandler;
use HiEvents\Services\Application\Handlers\Webhook\DTO\CreateWebhookDTO;
use Illuminate\Http\JsonResponse;
Expand Down Expand Up @@ -36,7 +36,7 @@ public function __invoke(int $organizerId, UpsertWebhookRequest $request): JsonR
);

return $this->resourceResponse(
resource: WebhookResource::class,
resource: WebhookResourceWithSecret::class,
data: $webhook
);
}
Expand Down
4 changes: 2 additions & 2 deletions backend/app/Http/Actions/Webhooks/CreateWebhookAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use HiEvents\DomainObjects\Status\WebhookStatus;
use HiEvents\Http\Actions\BaseAction;
use HiEvents\Http\Request\Webhook\UpsertWebhookRequest;
use HiEvents\Resources\Webhook\WebhookResource;
use HiEvents\Resources\Webhook\WebhookResourceWithSecret;
use HiEvents\Services\Application\Handlers\Webhook\CreateWebhookHandler;
use HiEvents\Services\Application\Handlers\Webhook\DTO\CreateWebhookDTO;
use Illuminate\Http\JsonResponse;
Expand Down Expand Up @@ -35,7 +35,7 @@ public function __invoke(int $eventId, UpsertWebhookRequest $request): JsonRespo
);

return $this->resourceResponse(
resource: WebhookResource::class,
resource: WebhookResourceWithSecret::class,
data: $webhook
);
}
Expand Down
18 changes: 18 additions & 0 deletions backend/app/Resources/Webhook/WebhookResourceWithSecret.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace HiEvents\Resources\Webhook;

use HiEvents\DomainObjects\WebhookDomainObject;

/**
* @mixin WebhookDomainObject
*/
class WebhookResourceWithSecret extends WebhookResource
{
public function toArray($request): array
{
return array_merge(parent::toArray($request), [
'secret' => $this->getSecret(),
]);
}
}
2 changes: 1 addition & 1 deletion backend/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "hi.events - Ticket selling and event management.",
"keywords": ["ticketing", "events"],
"license": "AGPL-3.0",
"version": "v1.7.1-beta",
"version": "1.8.0-beta",
"require": {
"php": "^8.2",
"ext-intl": "*",
Expand Down
54 changes: 54 additions & 0 deletions backend/tests/Unit/Resources/Webhook/WebhookResourceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace Tests\Unit\Resources\Webhook;

use HiEvents\DomainObjects\WebhookDomainObject;
use HiEvents\Resources\Webhook\WebhookResource;
use HiEvents\Resources\Webhook\WebhookResourceWithSecret;
use Illuminate\Http\Request;
use Tests\TestCase;

class WebhookResourceTest extends TestCase
{
private function createWebhookDomainObject(): WebhookDomainObject
{
return (new WebhookDomainObject())
->setId(1)
->setUrl('https://example.com/webhook')
->setEventTypes(['order.created', 'attendee.created'])
->setStatus('ENABLED')
->setSecret('test-secret-key-1234567890abcdef');
}

public function test_webhook_resource_excludes_secret(): void
{
$webhook = $this->createWebhookDomainObject();
$resource = (new WebhookResource($webhook))->toArray(Request::create('/'));

$this->assertArrayNotHasKey('secret', $resource);
$this->assertEquals(1, $resource['id']);
$this->assertEquals('https://example.com/webhook', $resource['url']);
$this->assertEquals('ENABLED', $resource['status']);
}

public function test_webhook_resource_with_secret_includes_secret(): void
{
$webhook = $this->createWebhookDomainObject();
$resource = (new WebhookResourceWithSecret($webhook))->toArray(Request::create('/'));

$this->assertArrayHasKey('secret', $resource);
$this->assertEquals('test-secret-key-1234567890abcdef', $resource['secret']);
}

public function test_webhook_resource_with_secret_includes_all_base_fields(): void
{
$webhook = $this->createWebhookDomainObject();
$resource = (new WebhookResourceWithSecret($webhook))->toArray(Request::create('/'));

$this->assertEquals(1, $resource['id']);
$this->assertEquals('https://example.com/webhook', $resource['url']);
$this->assertEquals(['order.created', 'attendee.created'], $resource['event_types']);
$this->assertEquals('ENABLED', $resource['status']);
$this->assertEquals('test-secret-key-1234567890abcdef', $resource['secret']);
}
}
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "hievents-frontend",
"private": true,
"version": "v1.7.1-beta",
"version": "1.8.0-beta",
"type": "module",
"scripts": {
"dev:csr": "vite --port 5678 --host 0.0.0.0",
Expand Down
Loading
Loading