Skip to content
Open

Dev #92

Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0e57640
Refactor: env (#91)
TatevikGr Jul 29, 2026
88c00fc
use dev branch for core
tatevikg1 Jul 29, 2026
cbf56ab
feat: enhance template error handling with field-specific validation …
tatevikg1 Jul 29, 2026
20b89db
Update openapi.json from web frontend workflow 2026-08-04T12:38:04Z
Aug 4, 2026
34972eb
feat: add cursor pointer to buttons for better user interaction
tatevikg1 Aug 4, 2026
c0437c6
Merge branch 'dev' of github.com:phpList/web-frontend into dev
tatevikg1 Aug 4, 2026
f2dd0dc
chore: update @symfony/webpack-encore and other dependencies in packa…
tatevikg1 Aug 4, 2026
48707a2
feat: add cursor pointer style to interactive buttons globally
tatevikg1 Aug 5, 2026
dab6039
feat: add dark mode
tatevikg1 Aug 5, 2026
b62c256
feat: add JSON manifest path and enable versioning in webpack configu…
tatevikg1 Aug 9, 2026
08380af
feat: set custom session name in framework configuration
tatevikg1 Aug 16, 2026
1b1b7b4
Update openapi.json from web frontend workflow 2026-08-17T08:24:03Z
Aug 17, 2026
0507986
feat: update dashboard statistics handling to use new API responses
tatevikg1 Aug 17, 2026
bb2b810
fix: response structure in dashboard controller tests
tatevikg1 Aug 19, 2026
7e4053b
Update openapi.json from web frontend workflow 2026-08-20T11:04:47Z
Aug 20, 2026
cae1e63
feat: implement pagination and filtering for campaigns in CampaignDir…
tatevikg1 Aug 20, 2026
db3a633
feat: add exception logging configuration for NotFoundHttpException i…
tatevikg1 Aug 25, 2026
d6f365a
feat: refactor dashboard to load statistics from API and pass summary…
tatevikg1 Aug 25, 2026
f633d01
feat: refactor dashboard data handling into a composable for improved…
tatevikg1 Aug 25, 2026
e185992
feat: return JSON response for expired session in AJAX requests
tatevikg1 Aug 25, 2026
2bfca6f
feat: refactor analytics data loading into a composable for improved …
tatevikg1 Aug 25, 2026
3e41f56
Update openapi.json from web frontend workflow 2026-09-09T15:21:57Z
Sep 9, 2026
6af8a03
feat: add Stuck Campaigns view and list components for managing (#104)
TatevikGr Sep 18, 2026
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
3 changes: 2 additions & 1 deletion assets/vue/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ const redirectToLogin = () => {

const appElement = document.getElementById('vue-app');
const apiToken = appElement?.dataset.apiToken;
const apiBaseUrl = appElement?.dataset.apiBaseUrl;
const apiElement = document.getElementById('api-config');
const apiBaseUrl = apiElement?.dataset.apiBaseUrl;

if (!apiBaseUrl) {
console.error('API Base URL is not configured.');
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
},
"require": {
"php": "^8.1",
"phplist/core": "dev-main",
"phplist/core": "dev-env",
"symfony/twig-bundle": "^6.4",
"symfony/webpack-encore-bundle": "^2.2",
"symfony/security-bundle": "^6.4",
Expand Down Expand Up @@ -96,6 +96,7 @@
"PhpList\\Core\\Composer\\ScriptHandler::createBundleConfiguration",
"PhpList\\Core\\Composer\\ScriptHandler::createRoutesConfiguration",
"PhpList\\Core\\Composer\\ScriptHandler::createParametersConfiguration",
"PhpList\\Core\\Composer\\ScriptHandler::createDotenvConfiguration",
"PhpList\\Core\\Composer\\ScriptHandler::clearAllCaches"
],
"publish-phplist-texts": [
Expand Down
2 changes: 2 additions & 0 deletions config/packages/twig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ twig:
strict_variables: '%kernel.debug%'
default_path: '%kernel.application_dir%/templates'
auto_reload: true
globals:
api_base_url: '%app.api_base_url%'
6 changes: 1 addition & 5 deletions config/services.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
# config/services.yaml
parameters:
api_base_url: '%env(API_BASE_URL)%'
env(API_BASE_URL): 'http://api.phplist.local/'

services:
_defaults:
autowire: true
Expand All @@ -27,7 +23,7 @@ services:
Symfony\Component\HttpFoundation\Session\SessionInterface: '@session'

PhpList\RestApiClient\Client:
$baseUrl: '%api_base_url%'
$baseUrl: '%app.api_base_url%'

PhpList\WebFrontend\EventListener\ApiSessionListener:
tags:
Expand Down
4 changes: 2 additions & 2 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/6.2/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
bootstrap="tests/bootstrap.php"
>
<php>
<env name="API_BASE_URL" value="http://api.phplist.local/"/>
<ini name="error_reporting" value="-1"/>
<server name="KERNEL_CLASS" value="PhpList\Core\Core\ApplicationKernel"/>
<server name="APP_ENV" value="test" force="true"/>
</php>
</phpunit>
2 changes: 0 additions & 2 deletions src/Controller/AnalyticsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ public function index(Request $request): Response
{
return $this->render('@PhpListFrontend/spa.html.twig', [
'page' => 'Analytics',
'api_token' => $request->getSession()->get('auth_token'),
'api_base_url' => $this->getParameter('api_base_url'),
]);
}
}
2 changes: 0 additions & 2 deletions src/Controller/BouncesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ public function index(Request $request): Response

return $this->render('@PhpListFrontend/spa.html.twig', [
'page' => 'Bounces',
'api_token' => $request->getSession()->get('auth_token'),
'api_base_url' => $this->getParameter('api_base_url'),
'bounce_actions' => $bounceActions,
]);
}
Expand Down
6 changes: 0 additions & 6 deletions src/Controller/CampaignsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ public function index(Request $request): Response
{
return $this->render('@PhpListFrontend/spa.html.twig', [
'page' => 'Campaigns',
'api_token' => $request->getSession()->get('auth_token'),
'api_base_url' => $this->getParameter('api_base_url'),
]);
}

Expand All @@ -27,8 +25,6 @@ public function create(Request $request): Response
{
return $this->render('@PhpListFrontend/spa.html.twig', [
'page' => 'Create Campaign',
'api_token' => $request->getSession()->get('auth_token'),
'api_base_url' => $this->getParameter('api_base_url'),
]);
}

Expand All @@ -37,8 +33,6 @@ public function edit(Request $request, int $campaignId): Response
{
return $this->render('@PhpListFrontend/spa.html.twig', [
'page' => sprintf('Edit Campaign #%d', $campaignId),
'api_token' => $request->getSession()->get('auth_token'),
'api_base_url' => $this->getParameter('api_base_url'),
]);
}
}
2 changes: 0 additions & 2 deletions src/Controller/DashboardController.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ public function index(Request $request): Response

return $this->render('@PhpListFrontend/spa.html.twig', [
'page' => 'Dashboard',
'api_token' => $request->getSession()->get('auth_token'),
'api_base_url' => $this->getParameter('api_base_url'),
'dashboard_stats' => $dashboardStats,
'dashboard_error' => $dashboardError,
]);
Expand Down
4 changes: 0 additions & 4 deletions src/Controller/ListsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ public function index(Request $request): JsonResponse|Response
if (! $wantsJson) {
return $this->render('@PhpListFrontend/spa.html.twig', [
'page' => 'Lists',
'api_token' => $request->getSession()->get('auth_token'),
'api_base_url' => $this->getParameter('api_base_url'),
]);
}
$initialData = $this->listClient->getLists();
Expand All @@ -40,8 +38,6 @@ public function view(Request $request, int $listId): JsonResponse|Response
{
return $this->render('@PhpListFrontend/spa.html.twig', [
'page' => 'List Subscribers',
'api_token' => $request->getSession()->get('auth_token'),
'api_base_url' => $this->getParameter('api_base_url'),
]);
}
}
6 changes: 0 additions & 6 deletions src/Controller/PublicPagesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ public function index(Request $request): Response
{
return $this->render('@PhpListFrontend/spa.html.twig', [
'page' => 'Subscribe Pages',
'api_token' => $request->getSession()->get('auth_token'),
'api_base_url' => $this->getParameter('api_base_url'),
]);
}

Expand All @@ -27,8 +25,6 @@ public function create(Request $request): Response
{
return $this->render('@PhpListFrontend/spa.html.twig', [
'page' => 'Create Subscribe Page',
'api_token' => $request->getSession()->get('auth_token'),
'api_base_url' => $this->getParameter('api_base_url'),
]);
}

Expand All @@ -37,8 +33,6 @@ public function edit(Request $request, int $pageId): Response
{
return $this->render('@PhpListFrontend/spa.html.twig', [
'page' => sprintf('Edit Subscribe Page #%d', $pageId),
'api_token' => $request->getSession()->get('auth_token'),
'api_base_url' => $this->getParameter('api_base_url'),
]);
}
}
2 changes: 0 additions & 2 deletions src/Controller/SettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ public function index(Request $request): Response
{
return $this->render('@PhpListFrontend/spa.html.twig', [
'page' => 'Settings',
'api_token' => $request->getSession()->get('auth_token'),
'api_base_url' => $this->getParameter('api_base_url'),
]);
}
}
2 changes: 0 additions & 2 deletions src/Controller/SubscribersController.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ public function index(Request $request): JsonResponse|Response
if (! $wantsJson) {
return $this->render('@PhpListFrontend/spa.html.twig', [
'page' => 'Subscribers',
'api_token' => $request->getSession()->get('auth_token'),
'api_base_url' => $this->getParameter('api_base_url'),
]);
}

Expand Down
6 changes: 0 additions & 6 deletions src/Controller/TemplatesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ public function index(Request $request): Response
{
return $this->render('@PhpListFrontend/spa.html.twig', [
'page' => 'Templates',
'api_token' => $request->getSession()->get('auth_token'),
'api_base_url' => $this->getParameter('api_base_url'),
]);
}

Expand All @@ -27,8 +25,6 @@ public function create(Request $request): Response
{
return $this->render('@PhpListFrontend/spa.html.twig', [
'page' => 'Create Template',
'api_token' => $request->getSession()->get('auth_token'),
'api_base_url' => $this->getParameter('api_base_url'),
]);
}

Expand All @@ -37,8 +33,6 @@ public function edit(Request $request, int $templateId): Response
{
return $this->render('@PhpListFrontend/spa.html.twig', [
'page' => sprintf('Edit Template #%d', $templateId),
'api_token' => $request->getSession()->get('auth_token'),
'api_base_url' => $this->getParameter('api_base_url'),
]);
}
}
1 change: 1 addition & 0 deletions templates/base.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<meta charset="UTF-8">
<title>{% block title %}phpList{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta id="api-config" data-api-base-url="{{ api_base_url }}">
<link rel="stylesheet" href="{{ asset('build/app.css', 'phplist_web_frontend') }}">
<link rel="stylesheet" href="{{ asset('build/styles.css', 'phplist_web_frontend') }}">

Expand Down
3 changes: 1 addition & 2 deletions templates/spa.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@

{% block body %}
<div id="vue-app"
data-api-token="{{ api_token|default('') }}"
data-api-base-url="{{ api_base_url|default('') }}"
data-api-token="{{ app.session.get('auth_token')|default('') }}"
data-dashboard-stats="{{ dashboard_stats|default({})|json_encode|e('html_attr') }}"
data-dashboard-error="{{ dashboard_error|default('')|e('html_attr') }}"
data-bounce-actions="{{ bounce_actions|default({})|json_encode|e('html_attr') }}">
Expand Down
4 changes: 3 additions & 1 deletion tests/Integration/Controller/AnalyticsControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PhpList\WebFrontend\Controller\AnalyticsController;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use Symfony\Component\Routing\RouterInterface;
Expand All @@ -27,12 +28,13 @@ public function testAnalyticsPageRendersExpectedSpaPayload(): void
self::bootKernel();
/** @var AnalyticsController $controller */
$controller = static::getContainer()->get(AnalyticsController::class);
$apiBaseUrl = (string) static::getContainer()->getParameter('api_base_url');
$apiBaseUrl = (string) static::getContainer()->getParameter('app.api_base_url');

$request = Request::create('/analytics/');
$session = new Session(new MockArraySessionStorage());
$session->set('auth_token', 'integration-token');
$request->setSession($session);
static::getContainer()->get(RequestStack::class)->push($request);

$response = $controller->index($request);
$content = (string) $response->getContent();
Expand Down
4 changes: 3 additions & 1 deletion tests/Integration/Controller/BouncesControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PhpList\WebFrontend\Controller\BouncesController;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use Symfony\Component\Routing\RouterInterface;
Expand All @@ -27,12 +28,13 @@ public function testBouncesPageRendersExpectedSpaPayload(): void
self::bootKernel();
/** @var BouncesController $controller */
$controller = static::getContainer()->get(BouncesController::class);
$apiBaseUrl = (string) static::getContainer()->getParameter('api_base_url');
$apiBaseUrl = (string) static::getContainer()->getParameter('app.api_base_url');

$request = Request::create('/bounces/');
$session = new Session(new MockArraySessionStorage());
$session->set('auth_token', 'integration-token');
$request->setSession($session);
static::getContainer()->get(RequestStack::class)->push($request);

$response = $controller->index($request);
$content = (string) $response->getContent();
Expand Down
2 changes: 2 additions & 0 deletions tests/Integration/Controller/CampaignsControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PhpList\WebFrontend\Controller\CampaignsController;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use Symfony\Component\Routing\RouterInterface;
Expand Down Expand Up @@ -41,6 +42,7 @@ public function testCampaignPagesRenderExpectedSpaPayload(
$session = new Session(new MockArraySessionStorage());
$session->set('auth_token', 'integration-token');
$request->setSession($session);
static::getContainer()->get(RequestStack::class)->push($request);

$response = $controller->{$action}($request, ...$actionArguments);

Expand Down
4 changes: 3 additions & 1 deletion tests/Integration/Controller/DashboardControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use PhpList\WebFrontend\Controller\DashboardController;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use Symfony\Component\Routing\RouterInterface;
Expand All @@ -29,7 +30,7 @@ public function testDashboardRouteIsRegistered(): void
public function testDashboardRendersSpaPayloadWithStats(): void
{
self::bootKernel();
$apiBaseUrl = (string) static::getContainer()->getParameter('api_base_url');
$apiBaseUrl = (string) static::getContainer()->getParameter('app.api_base_url');

$statsClient = $this->createMock(StatisticsClient::class);
$statsClient->expects(self::once())
Expand All @@ -43,6 +44,7 @@ public function testDashboardRendersSpaPayloadWithStats(): void
$session = new Session(new MockArraySessionStorage());
$session->set('auth_token', 'integration-token');
$request->setSession($session);
static::getContainer()->get(RequestStack::class)->push($request);

$response = $controller->index($request);
$content = (string) $response->getContent();
Expand Down
4 changes: 3 additions & 1 deletion tests/Integration/Controller/ListsControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use Symfony\Component\Routing\RouterInterface;
Expand All @@ -31,7 +32,7 @@ public function testListRoutesAreRegistered(string $routeName, array $parameters
public function testListsIndexRendersSpaForHtmlRequests(): void
{
self::bootKernel();
$apiBaseUrl = (string) static::getContainer()->getParameter('api_base_url');
$apiBaseUrl = (string) static::getContainer()->getParameter('app.api_base_url');

$listClient = $this->createMock(ListClient::class);
$listClient->expects(self::never())->method('getLists');
Expand Down Expand Up @@ -106,6 +107,7 @@ private function createRequestWithSession(string $path): Request
$session = new Session(new MockArraySessionStorage());
$session->set('auth_token', 'integration-token');
$request->setSession($session);
static::getContainer()->get(RequestStack::class)->push($request);

return $request;
}
Expand Down
11 changes: 11 additions & 0 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

use Symfony\Component\Dotenv\Dotenv;

require dirname(__DIR__) . '/vendor/autoload.php';

if (method_exists(Dotenv::class, 'bootEnv')) {
(new Dotenv())->bootEnv(dirname(__DIR__) . '/.env');
}
Loading