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
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,48 @@
namespace HiEvents\Http\Actions\EmailTemplates;

use HiEvents\DomainObjects\Enums\EmailTemplateType;
use HiEvents\Exceptions\AccountNotVerifiedException;
use HiEvents\Http\Actions\BaseAction;
use HiEvents\Repository\Interfaces\AccountRepositoryInterface;
use HiEvents\Services\Application\Handlers\EmailTemplate\DTO\PreviewEmailTemplateDTO;
use HiEvents\Services\Application\Handlers\EmailTemplate\PreviewEmailTemplateHandler;
use Illuminate\Config\Repository;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rules\Enum;

abstract class BaseEmailTemplateAction extends BaseAction
{
/**
* Yes, it's ugly to put this here, but it's in response to an ongoing spam issue.
*
* @throws AccountNotVerifiedException
*/
protected function verifyAccountCanModifyEmailTemplates(): void
{
/** @var Repository $config */
$config = app(Repository::class);

if (!$config->get('app.saas_mode_enabled')) {
return;
}

/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);

$account = $accountRepository->findById($this->getAuthenticatedAccountId());

if ($account->getAccountVerifiedAt() === null) {
throw new AccountNotVerifiedException(__('You cannot modify email templates until your account is verified.'));
}

if (!$account->getIsManuallyVerified()) {
throw new AccountNotVerifiedException(
__('Due to issues with spam, you must connect a Stripe account before you can modify email templates.')
);
}
}

protected function validateEmailTemplateRequest(Request $request): array
{
return $request->validate([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use HiEvents\DomainObjects\Enums\EmailTemplateType;
use HiEvents\DomainObjects\EventDomainObject;
use HiEvents\Exceptions\AccountNotVerifiedException;
use HiEvents\Exceptions\EmailTemplateValidationException;
use HiEvents\Exceptions\ResourceConflictException;
use HiEvents\Http\Resources\EmailTemplateResource;
Expand All @@ -13,6 +14,7 @@
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpFoundation\Response;

class CreateEventEmailTemplateAction extends BaseEmailTemplateAction
{
Expand All @@ -29,6 +31,12 @@ public function __invoke(Request $request, int $eventId): JsonResponse
{
$this->isActionAuthorized($eventId, EventDomainObject::class);

try {
$this->verifyAccountCanModifyEmailTemplates();
} catch (AccountNotVerifiedException $e) {
return $this->errorResponse($e->getMessage(), Response::HTTP_UNAUTHORIZED);
}

$validated = $this->validateEmailTemplateRequest($request);

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use HiEvents\DomainObjects\Enums\EmailTemplateType;
use HiEvents\DomainObjects\OrganizerDomainObject;
use HiEvents\Exceptions\AccountNotVerifiedException;
use HiEvents\Exceptions\EmailTemplateValidationException;
use HiEvents\Exceptions\ResourceConflictException;
use HiEvents\Http\Resources\EmailTemplateResource;
Expand All @@ -13,6 +14,7 @@
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpFoundation\Response;

class CreateOrganizerEmailTemplateAction extends BaseEmailTemplateAction
{
Expand All @@ -29,6 +31,12 @@ public function __invoke(Request $request, int $organizerId): JsonResponse
{
$this->isActionAuthorized($organizerId, OrganizerDomainObject::class);

try {
$this->verifyAccountCanModifyEmailTemplates();
} catch (AccountNotVerifiedException $e) {
return $this->errorResponse($e->getMessage(), Response::HTTP_UNAUTHORIZED);
}

$validated = $this->validateEmailTemplateRequest($request);

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@
namespace HiEvents\Http\Actions\EmailTemplates;

use HiEvents\DomainObjects\EventDomainObject;
use HiEvents\Exceptions\AccountNotVerifiedException;
use HiEvents\Exceptions\EmailTemplateNotFoundException;
use HiEvents\Http\Actions\BaseAction;
use HiEvents\Http\ResponseCodes;
use HiEvents\Services\Application\Handlers\EmailTemplate\DeleteEmailTemplateHandler;
use HiEvents\Services\Application\Handlers\EmailTemplate\DTO\DeleteEmailTemplateDTO;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;

class DeleteEventEmailTemplateAction extends BaseAction
class DeleteEventEmailTemplateAction extends BaseEmailTemplateAction
{
public function __construct(
private readonly DeleteEmailTemplateHandler $handler
Expand All @@ -23,6 +24,12 @@ public function __invoke(int $eventId, int $templateId): Response|JsonResponse
{
$this->isActionAuthorized($eventId, EventDomainObject::class);

try {
$this->verifyAccountCanModifyEmailTemplates();
} catch (AccountNotVerifiedException $e) {
return $this->errorResponse($e->getMessage(), SymfonyResponse::HTTP_UNAUTHORIZED);
}

try {
$this->handler->handle(
new DeleteEmailTemplateDTO(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
namespace HiEvents\Http\Actions\EmailTemplates;

use HiEvents\DomainObjects\OrganizerDomainObject;
use HiEvents\Exceptions\AccountNotVerifiedException;
use HiEvents\Exceptions\EmailTemplateNotFoundException;
use HiEvents\Http\Actions\BaseAction;
use HiEvents\Http\ResponseCodes;
use HiEvents\Services\Application\Handlers\EmailTemplate\DeleteEmailTemplateHandler;
use HiEvents\Services\Application\Handlers\EmailTemplate\DTO\DeleteEmailTemplateDTO;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;

class DeleteOrganizerEmailTemplateAction extends BaseAction
class DeleteOrganizerEmailTemplateAction extends BaseEmailTemplateAction
{
public function __construct(
private readonly DeleteEmailTemplateHandler $handler
Expand All @@ -21,6 +22,12 @@ public function __invoke(int $organizerId, int $templateId): JsonResponse
{
$this->isActionAuthorized($organizerId, OrganizerDomainObject::class);

try {
$this->verifyAccountCanModifyEmailTemplates();
} catch (AccountNotVerifiedException $e) {
return $this->errorResponse($e->getMessage(), Response::HTTP_UNAUTHORIZED);
}

try {
$this->handler->handle(
new DeleteEmailTemplateDTO(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use HiEvents\DomainObjects\Enums\EmailTemplateType;
use HiEvents\DomainObjects\EventDomainObject;
use HiEvents\Exceptions\AccountNotVerifiedException;
use HiEvents\Exceptions\EmailTemplateNotFoundException;
use HiEvents\Exceptions\EmailTemplateValidationException;
use HiEvents\Exceptions\InvalidEmailTemplateException;
Expand All @@ -14,6 +15,7 @@
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpFoundation\Response;

class UpdateEventEmailTemplateAction extends BaseEmailTemplateAction
{
Expand All @@ -30,6 +32,12 @@ public function __invoke(Request $request, int $eventId, int $templateId): JsonR
{
$this->isActionAuthorized($eventId, EventDomainObject::class);

try {
$this->verifyAccountCanModifyEmailTemplates();
} catch (AccountNotVerifiedException $e) {
return $this->errorResponse($e->getMessage(), Response::HTTP_UNAUTHORIZED);
}

$validated = $this->validateUpdateEmailTemplateRequest($request);

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use HiEvents\DomainObjects\Enums\EmailTemplateType;
use HiEvents\DomainObjects\OrganizerDomainObject;
use HiEvents\Exceptions\AccountNotVerifiedException;
use HiEvents\Exceptions\EmailTemplateNotFoundException;
use HiEvents\Exceptions\EmailTemplateValidationException;
use HiEvents\Exceptions\InvalidEmailTemplateException;
Expand All @@ -14,6 +15,7 @@
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpFoundation\Response;

class UpdateOrganizerEmailTemplateAction extends BaseEmailTemplateAction
{
Expand All @@ -29,6 +31,12 @@ public function __invoke(Request $request, int $organizerId, int $templateId): J
{
$this->isActionAuthorized($organizerId, OrganizerDomainObject::class);

try {
$this->verifyAccountCanModifyEmailTemplates();
} catch (AccountNotVerifiedException $e) {
return $this->errorResponse($e->getMessage(), Response::HTTP_UNAUTHORIZED);
}

$validated = $this->validateUpdateEmailTemplateRequest($request);

try {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useState} from 'react';
import {ActionIcon, Alert, Badge, Button, Group, LoadingOverlay, Modal, Paper, Stack, Text} from '@mantine/core';
import {IconEdit, IconInfoCircle, IconMail, IconPlus, IconTrash} from '@tabler/icons-react';
import {IconAlertCircle, IconEdit, IconInfoCircle, IconMail, IconPlus, IconTrash} from '@tabler/icons-react';
import {t, Trans} from '@lingui/macro';
import {useDisclosure} from '@mantine/hooks';
import {EmailTemplateEditor} from '../EmailTemplateEditor';
Expand All @@ -16,6 +16,8 @@ import {
} from '../../../types';
import {Card} from '../Card';
import {HeadingWithDescription} from '../Card/CardHeading';
import {useGetAccount} from '../../../queries/useGetAccount';
import {StripeConnectButton} from '../StripeConnectButton';

interface EmailTemplateSettingsBaseProps {
// Context
Expand Down Expand Up @@ -72,6 +74,10 @@ export const EmailTemplateSettingsBase = ({
const [editingTemplate, setEditingTemplate] = useState<EmailTemplate | null>(null);
const [editingType, setEditingType] = useState<EmailTemplateType>('order_confirmation');
const handleFormError = useFormErrorResponseHandler();
const {data: account, isFetched: isAccountFetched} = useGetAccount();
const isAccountVerified = isAccountFetched && account?.is_account_email_confirmed;
const accountRequiresManualVerification = isAccountFetched && account?.requires_manual_verification;
const isModifyDisabled = !isAccountVerified || accountRequiresManualVerification;

const orderConfirmationTemplate = templates.find(t => t.template_type === 'order_confirmation');
const attendeeTicketTemplate = templates.find(t => t.template_type === 'attendee_ticket');
Expand Down Expand Up @@ -271,6 +277,7 @@ export const EmailTemplateSettingsBase = ({
<ActionIcon
variant="subtle"
onClick={() => handleEditTemplate(template)}
disabled={isModifyDisabled}
>
<IconEdit size={16}/>
</ActionIcon>
Expand All @@ -279,6 +286,7 @@ export const EmailTemplateSettingsBase = ({
color="red"
onClick={() => handleDeleteTemplate(template)}
loading={deleteMutation.isPending}
disabled={isModifyDisabled}
>
<IconTrash size={16}/>
</ActionIcon>
Expand All @@ -288,6 +296,7 @@ export const EmailTemplateSettingsBase = ({
size="xs"
leftSection={<IconPlus size={16}/>}
onClick={() => handleCreateTemplate(type)}
disabled={isModifyDisabled}
>
<Trans>Create Custom Template</Trans>
</Button>
Expand Down Expand Up @@ -328,6 +337,25 @@ export const EmailTemplateSettingsBase = ({
description={getHeadingDescription()}
/>

{(!isAccountVerified && isAccountFetched) && (
<Alert icon={<IconAlertCircle size={16}/>} variant="light" mb="lg" color="orange">
<Text size="sm">
{t`You need to verify your account email before you can modify email templates.`}
</Text>
</Alert>
)}

{accountRequiresManualVerification && (
<Alert icon={<IconAlertCircle size={16}/>} variant="light" mb="lg" color="orange" title={t`Connect Stripe to enable email template editing`}>
<Text size="sm">
{t`Due to the high risk of spam, you must connect a Stripe account before you can modify email templates. This is to ensure that all event organizers are verified and accountable.`}
</Text>
<div style={{marginTop: '0.75rem'}}>
<StripeConnectButton/>
</div>
</Alert>
)}

<Alert icon={<IconInfoCircle size={16}/>} variant="light" mb="lg">
<Text size="sm">
{getAlertMessage()}
Expand Down
Loading