diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ddb068a0b..ddf9ecbb0 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -4,6 +4,7 @@ on: - 'src/**' - 'docs/**' - 'examples/**' + - 'web/landing/**' - 'README.md' push: branches: diff --git a/src/cli/tests/Flow/CLI/Tests/Integration/DatabaseTableListCommandTest.php b/src/cli/tests/Flow/CLI/Tests/Integration/DatabaseTableListCommandTest.php index 08e7f19c0..e9f56f5d1 100644 --- a/src/cli/tests/Flow/CLI/Tests/Integration/DatabaseTableListCommandTest.php +++ b/src/cli/tests/Flow/CLI/Tests/Integration/DatabaseTableListCommandTest.php @@ -61,23 +61,21 @@ public function test_run_db_table_list(): void $tester->assertCommandIsSuccessful(); - static::assertSame(<<<'OUTPUT' + $display = $tester->getDisplay(); + + static::assertStringContainsString(<<<'OUTPUT' ┌──────────┬───────────┬─────────┐ │ Name │ Namespace │ Columns │ ├──────────┼───────────┼─────────┤ │ table_01 │ public │ 3 │ │ table_02 │ public │ 3 │ └──────────┴───────────┴─────────┘ - ------------------ ----- - Summary - ------------------ ----- - Total tables 2 - Total namespaces 1 - Total columns 6 - ------------------ ----- - + OUTPUT, $display); - OUTPUT, $tester->getDisplay()); + static::assertStringContainsString('Summary', $display); + static::assertStringContainsString('Total tables 2', $display); + static::assertStringContainsString('Total namespaces 1', $display); + static::assertStringContainsString('Total columns 6', $display); } protected function dbContext(): DatabaseContext diff --git a/src/lib/postgresql/tests/Flow/PostgreSql/Tests/Integration/Client/ListenNotifyTest.php b/src/lib/postgresql/tests/Flow/PostgreSql/Tests/Integration/Client/ListenNotifyTest.php index d87b68a62..715ae0d60 100644 --- a/src/lib/postgresql/tests/Flow/PostgreSql/Tests/Integration/Client/ListenNotifyTest.php +++ b/src/lib/postgresql/tests/Flow/PostgreSql/Tests/Integration/Client/ListenNotifyTest.php @@ -20,15 +20,15 @@ public function test_blocking_wait_unblocks_on_concurrent_notify(): void $listener = $this->pgsqlContext()->client; $listener->listen('flow_test_blocking'); - $this->pgsqlContext()->spawnBackgroundNotifier('flow_test_blocking', 'delivered', 200); + $this->pgsqlContext()->scheduleNotify('flow_test_blocking', 'delivered', 200); $startNs = hrtime(true); $notification = $listener->wait(3000); $elapsedMs = (hrtime(true) - $startNs) / 1_000_000; if ($notification === null) { - $stderr = implode("\n---\n", $this->pgsqlContext()->backgroundStderrContents()); - static::fail('No notification received. Background sender stderr:' . "\n" . $stderr); + $errors = implode("\n---\n", $this->pgsqlContext()->backgroundNotifierErrors()); + static::fail('No notification received. Scheduled notification errors:' . "\n" . $errors); } static::assertSame('flow_test_blocking', $notification->channel); diff --git a/src/lib/postgresql/tests/Flow/PostgreSql/Tests/Integration/PostgreSqlContext.php b/src/lib/postgresql/tests/Flow/PostgreSql/Tests/Integration/PostgreSqlContext.php index 779c5a8a8..3611e021e 100644 --- a/src/lib/postgresql/tests/Flow/PostgreSql/Tests/Integration/PostgreSqlContext.php +++ b/src/lib/postgresql/tests/Flow/PostgreSql/Tests/Integration/PostgreSqlContext.php @@ -5,41 +5,37 @@ namespace Flow\PostgreSql\Tests\Integration; use Flow\PostgreSql\Client\Client; +use PgSql\Connection; use RuntimeException; -use function fclose; -use function file_get_contents; use function Flow\PostgreSql\DSL\and_; use function Flow\PostgreSql\DSL\col; use function Flow\PostgreSql\DSL\drop; use function Flow\PostgreSql\DSL\eq; use function Flow\PostgreSql\DSL\func; use function Flow\PostgreSql\DSL\literal; +use function Flow\PostgreSql\DSL\notify; use function Flow\PostgreSql\DSL\pgsql_client; use function Flow\PostgreSql\DSL\pgsql_connection_dsn; use function Flow\PostgreSql\DSL\select; use function Flow\PostgreSql\DSL\table; -use function getcwd; use function getenv; -use function is_file; -use function is_resource; -use function proc_close; -use function proc_open; +use function pg_close; +use function pg_connect; +use function pg_get_result; +use function pg_last_error; +use function pg_result_error; +use function pg_send_query; use function sprintf; -use function sys_get_temp_dir; -use function uniqid; -use function unlink; -use function var_export; + +use const PGSQL_CONNECT_FORCE_NEW; final class PostgreSqlContext { public readonly Client $client; - /** @var list */ - private array $backgroundProcesses = []; - - /** @var list */ - private array $backgroundStderrLogs = []; + /** @var list */ + private array $backgroundConnections = []; private readonly string $dsn; @@ -59,19 +55,26 @@ public function __construct() } /** + * Drains the results of every scheduled notification and returns the errors + * PostgreSQL reported for them. Empty when all of them succeeded. + * * @return list */ - public function backgroundStderrContents(): array + public function backgroundNotifierErrors(): array { - $out = []; + $errors = []; - foreach ($this->backgroundStderrLogs as $path) { - if (is_file($path)) { - $out[] = file_get_contents($path) ?: ''; + foreach ($this->backgroundConnections as $connection) { + while (($result = pg_get_result($connection)) !== false) { + $error = pg_result_error($result); + + if ($error !== false && $error !== '') { + $errors[] = $error; + } } } - return $out; + return $errors; } public function client(): Client @@ -81,17 +84,10 @@ public function client(): Client public function close(): void { - foreach ($this->backgroundProcesses as $process) { - proc_close($process); - } - $this->backgroundProcesses = []; - - foreach ($this->backgroundStderrLogs as $path) { - if (is_file($path)) { - @unlink($path); - } + foreach ($this->backgroundConnections as $connection) { + pg_close($connection); } - $this->backgroundStderrLogs = []; + $this->backgroundConnections = []; foreach ($this->secondaryClients as $client) { $client->close(); @@ -190,56 +186,33 @@ public function newClient(): Client } /** - * Spawns a detached child PHP process that sleeps for $delayMs and then - * fires a NOTIFY on $channel with $payload using its own Client - * connection. Returns the child process handle; the context closes it - * during close(). Used for integration tests that need to exercise the - * blocking wait path of waitForNotification(). + * Fires a NOTIFY on $channel with $payload after $delayMs on a dedicated + * connection, without blocking the caller. The delay runs server side, so + * the notification arrives while the caller sits in a blocking wait on its + * own connection. Used by tests that exercise the blocking wait path of + * waitForNotification(). */ - public function spawnBackgroundNotifier(string $channel, string $payload, int $delayMs): void + public function scheduleNotify(string $channel, string $payload, int $delayMs): void { - $cwd = getcwd(); + $connection = pg_connect(pgsql_connection_dsn($this->dsn)->toString(), PGSQL_CONNECT_FORCE_NEW); - if ($cwd === false) { - throw new RuntimeException('Failed to determine current working directory'); + if ($connection === false) { + throw new RuntimeException('Failed to open a connection for the scheduled notification'); } - $autoload = $cwd . '/vendor/autoload.php'; + $this->backgroundConnections[] = $connection; - if (!is_file($autoload)) { - throw new RuntimeException(sprintf('Project vendor/autoload.php not found at %s', $autoload)); - } + $sent = pg_send_query($connection, sprintf( + '%s; %s', + select(func('pg_sleep', [literal($delayMs / 1000)]))->toSql(), + notify($channel)->withPayload($payload)->toSql(), + )); - $phpCode = sprintf( - 'usleep(%d); require %s; try { $c = \Flow\PostgreSql\DSL\pgsql_client(\Flow\PostgreSql\DSL\pgsql_connection_dsn(%s)); $c->execute(\Flow\PostgreSql\DSL\notify(%s)->withPayload(%s)); $c->close(); } catch (\Throwable $e) { fwrite(STDERR, "child error: " . get_class($e) . ": " . $e->getMessage() . "\n"); exit(1); }', - $delayMs * 1000, - var_export($autoload, true), - var_export($this->dsn, true), - var_export($channel, true), - var_export($payload, true), - ); - - $stderrLog = sys_get_temp_dir() . '/flow-bg-notifier-' . uniqid('', true) . '.log'; - $descriptors = [ - 0 => ['pipe', 'r'], - 1 => ['file', '/dev/null', 'w'], - 2 => ['file', $stderrLog, 'w'], - ]; - - $pipes = []; - $process = proc_open(['php', '-r', $phpCode], $descriptors, $pipes); - - if (!is_resource($process)) { - throw new RuntimeException('Failed to spawn background notifier process'); + if ($sent === false) { + throw new RuntimeException(sprintf( + 'Failed to send the scheduled notification: %s', + pg_last_error($connection), + )); } - - if (!is_resource($pipes[0] ?? null)) { - throw new RuntimeException('Failed to open stdin pipe to background notifier process'); - } - - fclose($pipes[0]); - - $this->backgroundProcesses[] = $process; - $this->backgroundStderrLogs[] = $stderrLog; } } diff --git a/web/landing/assets/controllers/shop_carousel_controller.js b/web/landing/assets/controllers/work_shop_carousel_controller.js similarity index 100% rename from web/landing/assets/controllers/shop_carousel_controller.js rename to web/landing/assets/controllers/work_shop_carousel_controller.js diff --git a/web/landing/assets/controllers/shop_checkout_controller.js b/web/landing/assets/controllers/work_shop_checkout_controller.js similarity index 100% rename from web/landing/assets/controllers/shop_checkout_controller.js rename to web/landing/assets/controllers/work_shop_checkout_controller.js diff --git a/web/landing/assets/controllers/shop_confetti_controller.js b/web/landing/assets/controllers/work_shop_confetti_controller.js similarity index 100% rename from web/landing/assets/controllers/shop_confetti_controller.js rename to web/landing/assets/controllers/work_shop_confetti_controller.js diff --git a/web/landing/assets/controllers/shop_nav_controller.js b/web/landing/assets/controllers/work_shop_nav_controller.js similarity index 100% rename from web/landing/assets/controllers/shop_nav_controller.js rename to web/landing/assets/controllers/work_shop_nav_controller.js diff --git a/web/landing/assets/controllers/shop_order_controller.js b/web/landing/assets/controllers/work_shop_order_controller.js similarity index 100% rename from web/landing/assets/controllers/shop_order_controller.js rename to web/landing/assets/controllers/work_shop_order_controller.js diff --git a/web/landing/assets/images/shop/consulting-2.jpg b/web/landing/assets/images/work-shop/consulting-2.jpg similarity index 100% rename from web/landing/assets/images/shop/consulting-2.jpg rename to web/landing/assets/images/work-shop/consulting-2.jpg diff --git a/web/landing/assets/images/shop/consulting.jpg b/web/landing/assets/images/work-shop/consulting.jpg similarity index 100% rename from web/landing/assets/images/shop/consulting.jpg rename to web/landing/assets/images/work-shop/consulting.jpg diff --git a/web/landing/assets/images/shop/symfony-backoffice-api.png b/web/landing/assets/images/work-shop/symfony-backoffice-api.png similarity index 100% rename from web/landing/assets/images/shop/symfony-backoffice-api.png rename to web/landing/assets/images/work-shop/symfony-backoffice-api.png diff --git a/web/landing/assets/images/shop/symfony-backoffice-app.png b/web/landing/assets/images/work-shop/symfony-backoffice-app.png similarity index 100% rename from web/landing/assets/images/shop/symfony-backoffice-app.png rename to web/landing/assets/images/work-shop/symfony-backoffice-app.png diff --git a/web/landing/assets/images/shop/symfony-backoffice-import.png b/web/landing/assets/images/work-shop/symfony-backoffice-import.png similarity index 100% rename from web/landing/assets/images/shop/symfony-backoffice-import.png rename to web/landing/assets/images/work-shop/symfony-backoffice-import.png diff --git a/web/landing/assets/images/shop/symfony-backoffice-profiler.png b/web/landing/assets/images/work-shop/symfony-backoffice-profiler.png similarity index 100% rename from web/landing/assets/images/shop/symfony-backoffice-profiler.png rename to web/landing/assets/images/work-shop/symfony-backoffice-profiler.png diff --git a/web/landing/assets/images/shop/symfony-backoffice-telemetry.png b/web/landing/assets/images/work-shop/symfony-backoffice-telemetry.png similarity index 100% rename from web/landing/assets/images/shop/symfony-backoffice-telemetry.png rename to web/landing/assets/images/work-shop/symfony-backoffice-telemetry.png diff --git a/web/landing/assets/styles/app.css b/web/landing/assets/styles/app.css index 502857049..a814245e9 100644 --- a/web/landing/assets/styles/app.css +++ b/web/landing/assets/styles/app.css @@ -1,5 +1,5 @@ @import './changelog.css'; -@import './shop-legal.css'; +@import './work-shop-legal.css'; @tailwind base; @tailwind components; diff --git a/web/landing/assets/styles/shop-legal.css b/web/landing/assets/styles/work-shop-legal.css similarity index 70% rename from web/landing/assets/styles/shop-legal.css rename to web/landing/assets/styles/work-shop-legal.css index c3f8d82a8..fecb74e73 100644 --- a/web/landing/assets/styles/shop-legal.css +++ b/web/landing/assets/styles/work-shop-legal.css @@ -1,53 +1,53 @@ -section.shop-legal { +section.work-shop-legal { @apply text-slate-700 leading-relaxed dark:text-white/85; } -section.shop-legal h1 { +section.work-shop-legal h1 { @apply font-bold text-3xl sm:text-4xl tracking-tight text-slate-900 mt-2 mb-6 dark:text-white; } -section.shop-legal h2 { +section.work-shop-legal h2 { @apply font-bold text-2xl tracking-tight text-slate-900 mt-10 mb-3 dark:text-white; } -section.shop-legal h3 { +section.work-shop-legal h3 { @apply font-semibold text-xl text-slate-900 mt-7 mb-2 dark:text-white; } -section.shop-legal p { +section.work-shop-legal p { @apply mb-4; } -section.shop-legal a { +section.work-shop-legal a { @apply text-blue-300 underline-offset-2 hover:text-orange-300 transition-colors dark:text-blue-100 dark:hover:text-orange-100; } -section.shop-legal ul { +section.work-shop-legal ul { @apply list-disc pl-6 mb-4 space-y-1.5; } -section.shop-legal ol { +section.work-shop-legal ol { @apply list-decimal pl-6 mb-4 space-y-1.5; } -section.shop-legal strong { +section.work-shop-legal strong { @apply font-semibold text-slate-900 dark:text-white; } -section.shop-legal blockquote { +section.work-shop-legal blockquote { @apply my-6 rounded-r-lg border-l-4 border-orange-100/70 bg-orange-100/10 px-4 py-3 text-sm muted; } -section.shop-legal blockquote p { +section.work-shop-legal blockquote p { @apply mb-0; } -section.shop-legal hr { +section.work-shop-legal hr { @apply my-8 border-0 h-px bg-slate-200 dark:bg-white/10; } -section.shop-legal :not(pre) > code { +section.work-shop-legal :not(pre) > code { @apply font-mono text-[0.9em] px-1.5 py-0.5 rounded bg-violet-50 text-violet-700 dark:bg-white/[0.06] dark:text-[#d2a8ff]; } diff --git a/web/landing/config/services.yaml b/web/landing/config/services.yaml index 936c384b9..9d6e8d2b3 100644 --- a/web/landing/config/services.yaml +++ b/web/landing/config/services.yaml @@ -11,7 +11,7 @@ parameters: router.request_context.scheme: '%env(SCHEME)%' flow_version: '%env(FLOW_VERSION)%' turnstile_appearance_default: 'interaction-only' - shop_checkout_links: + work_shop_checkout_links: symfony_backoffice: 'https://buy.polar.sh/polar_cl_KAb6EWLVtewQhsDZV2bGeqsxSxTWKOOvl1KAq4GbDlx' services: @@ -27,9 +27,9 @@ services: Flow\Website\Service\: resource: '../src/Flow/Website/Service/' - Flow\Website\Controller\ShopController: + Flow\Website\Controller\WorkShopController: arguments: - $checkoutLinks: '%shop_checkout_links%' + $checkoutLinks: '%work_shop_checkout_links%' Flow\Website\Factory\Github\ContributorsRequestFactory: arguments: diff --git a/web/landing/content/shop/privacy-policy.md b/web/landing/content/work-shop/privacy-policy.md similarity index 98% rename from web/landing/content/shop/privacy-policy.md rename to web/landing/content/work-shop/privacy-policy.md index c325e3593..fba5c83ec 100644 --- a/web/landing/content/shop/privacy-policy.md +++ b/web/landing/content/work-shop/privacy-policy.md @@ -155,4 +155,3 @@ address, and material changes are reflected by updating the date above. ## 13. Contact For any privacy question or request, contact us at support@flow-php.com. -Our Terms of Sale are available at [Terms of Sale](/work-shop/terms-of-sales). diff --git a/web/landing/content/shop/terms-of-sales.md b/web/landing/content/work-shop/terms-of-sales.md similarity index 99% rename from web/landing/content/shop/terms-of-sales.md rename to web/landing/content/work-shop/terms-of-sales.md index 9ee04d8c3..8a15dc2df 100644 --- a/web/landing/content/shop/terms-of-sales.md +++ b/web/landing/content/work-shop/terms-of-sales.md @@ -161,7 +161,7 @@ arrangements and invoicing apply. We process personal data needed to deliver access and provide support (such as your email and GitHub account). Polar processes data needed for payment as Merchant of Record. For details on how we handle personal data, see our -[Privacy Policy](/work-shop/privacy-policy). Where we list purchasers as project +Privacy Policy. Where we list purchasers as project sponsors, we do so only for those who explicitly opt in, and only the GitHub profile they choose to display. diff --git a/web/landing/src/Flow/Website/Controller/ShopController.php b/web/landing/src/Flow/Website/Controller/ShopController.php deleted file mode 100644 index c3290f595..000000000 --- a/web/landing/src/Flow/Website/Controller/ShopController.php +++ /dev/null @@ -1,76 +0,0 @@ - $checkoutLinks - */ - public function __construct( - private readonly array $checkoutLinks, - ) {} - - #[Route('/work-shop', name: 'shop', options: ['sitemap' => false])] - public function index(): Response - { - return $this->render('shop/index.html.twig'); - } - - #[Route('/work-shop/blueprints/symfony-backoffice', name: 'shop_blueprint_symfony', options: ['sitemap' => false])] - public function blueprintSymfony(): Response - { - return $this->render('shop/blueprints/symfony-backoffice/index.html.twig', [ - 'listing_checkout_url' => $this->checkoutLinks['symfony_backoffice'] ?? null, - ]); - } - - #[Route('/work-shop/blueprints/how-it-works', name: 'shop_blueprint_how_it_works', options: ['sitemap' => false])] - public function blueprintHowItWorks(): Response - { - return $this->render('shop/blueprints/how-it-works/index.html.twig', [ - 'listing_checkout_url' => $this->checkoutLinks['symfony_backoffice'] ?? null, - ]); - } - - #[Route('/work-shop/success', name: 'shop_success', options: ['sitemap' => false])] - public function success(): Response - { - return $this->render('shop/success/index.html.twig'); - } - - #[Route('/work-shop/consulting', name: 'shop_consulting', options: ['sitemap' => false])] - public function consulting(): Response - { - return $this->render('shop/consulting/index.html.twig'); - } - - #[Route('/work-shop/terms-of-sales', name: 'shop_terms_of_sales', options: ['sitemap' => false])] - public function termsOfSales(): Response - { - return $this->render('shop/terms-of-sales/index.html.twig', [ - 'terms_markdown' => file_get_contents( - type_string()->assert($this->getParameter('kernel.project_dir')) . '/content/shop/terms-of-sales.md', - ), - ]); - } - - #[Route('/work-shop/privacy-policy', name: 'shop_privacy_policy', options: ['sitemap' => false])] - public function privacyPolicy(): Response - { - return $this->render('shop/privacy-policy/index.html.twig', [ - 'privacy_markdown' => file_get_contents( - type_string()->assert($this->getParameter('kernel.project_dir')) . '/content/shop/privacy-policy.md', - ), - ]); - } -} diff --git a/web/landing/src/Flow/Website/Controller/WorkShopController.php b/web/landing/src/Flow/Website/Controller/WorkShopController.php new file mode 100644 index 000000000..867414a1f --- /dev/null +++ b/web/landing/src/Flow/Website/Controller/WorkShopController.php @@ -0,0 +1,82 @@ + $checkoutLinks + */ + public function __construct( + private readonly array $checkoutLinks, + ) {} + + #[Route('/work-shop', name: 'work_shop', options: ['sitemap' => false])] + public function index(): Response + { + return $this->render('work-shop/index.html.twig'); + } + + #[Route('/work-shop/blueprints/symfony-backoffice', name: 'work_shop_blueprint_symfony', options: [ + 'sitemap' => false, + ])] + public function blueprintSymfony(): Response + { + return $this->render('work-shop/blueprints/symfony-backoffice/index.html.twig', [ + 'listing_checkout_url' => $this->checkoutLinks['symfony_backoffice'] ?? null, + ]); + } + + #[Route('/work-shop/blueprints/how-it-works', name: 'work_shop_blueprint_how_it_works', options: [ + 'sitemap' => false, + ])] + public function blueprintHowItWorks(): Response + { + return $this->render('work-shop/blueprints/how-it-works/index.html.twig', [ + 'listing_checkout_url' => $this->checkoutLinks['symfony_backoffice'] ?? null, + ]); + } + + #[Route('/work-shop/success', name: 'work_shop_success', options: ['sitemap' => false])] + public function success(): Response + { + return $this->render('work-shop/success/index.html.twig'); + } + + #[Route('/work-shop/consulting', name: 'work_shop_consulting', options: ['sitemap' => false])] + public function consulting(): Response + { + return $this->render('work-shop/consulting/index.html.twig'); + } + + #[Route('/work-shop/terms-of-sales', name: 'work_shop_terms_of_sales', options: ['sitemap' => false])] + public function termsOfSales(): Response + { + return $this->render('work-shop/terms-of-sales/index.html.twig', [ + 'terms_markdown' => file_get_contents( + type_string()->assert($this->getParameter('kernel.project_dir')) + . '/content/work-shop/terms-of-sales.md', + ), + ]); + } + + #[Route('/work-shop/privacy-policy', name: 'work_shop_privacy_policy', options: ['sitemap' => false])] + public function privacyPolicy(): Response + { + return $this->render('work-shop/privacy-policy/index.html.twig', [ + 'privacy_markdown' => file_get_contents( + type_string()->assert($this->getParameter('kernel.project_dir')) + . '/content/work-shop/privacy-policy.md', + ), + ]); + } +} diff --git a/web/landing/templates/main/sponsor.html.twig b/web/landing/templates/main/sponsor.html.twig index 809618e90..557293bcf 100644 --- a/web/landing/templates/main/sponsor.html.twig +++ b/web/landing/templates/main/sponsor.html.twig @@ -54,7 +54,7 @@ Hire me for consulting, workshops, or implementation support. Engagements of 20+ hours include a sponsor logo on this page.

- Learn more diff --git a/web/landing/templates/shop/_listing.html.twig b/web/landing/templates/work-shop/_listing.html.twig similarity index 85% rename from web/landing/templates/shop/_listing.html.twig rename to web/landing/templates/work-shop/_listing.html.twig index 535ea68cd..58d127784 100644 --- a/web/landing/templates/shop/_listing.html.twig +++ b/web/landing/templates/work-shop/_listing.html.twig @@ -33,7 +33,7 @@ {% set listing_images = listing_images ?? [] %} {% set listing_image_aspect = listing_image_aspect ?? '16 / 9' %}
- Back to the Work-Shop @@ -41,14 +41,14 @@
{% if listing_images|length %} -
-
+
{% for image in listing_images %} -
+
{{ listing_name }} screenshot {{ loop.index }} @@ -57,18 +57,18 @@
{% if listing_images|length > 1 %} - -
{% for image in listing_images %} - {% endfor %} @@ -117,7 +117,7 @@ {% if listing_available %} {% if listing_price|default(false) %} @@ -137,9 +137,9 @@ {% if listing_checkout_url %}

By buying you accept our - Terms of Sale + Terms of Sale and - Privacy Policy. + Privacy Policy.

{% endif %}
diff --git a/web/landing/templates/shop/blueprints/how-it-works/index.html.twig b/web/landing/templates/work-shop/blueprints/how-it-works/index.html.twig similarity index 96% rename from web/landing/templates/shop/blueprints/how-it-works/index.html.twig rename to web/landing/templates/work-shop/blueprints/how-it-works/index.html.twig index db342e94d..835ebceff 100644 --- a/web/landing/templates/shop/blueprints/how-it-works/index.html.twig +++ b/web/landing/templates/work-shop/blueprints/how-it-works/index.html.twig @@ -49,7 +49,7 @@ - Back to the Blueprint @@ -210,7 +210,7 @@ No surprises left. If anything above is still unclear, ask before you pay rather than after.

The full terms are in our - Terms of Sale + Terms of Sale and - Privacy Policy. + Privacy Policy.

diff --git a/web/landing/templates/shop/blueprints/symfony-backoffice/index.html.twig b/web/landing/templates/work-shop/blueprints/symfony-backoffice/index.html.twig similarity index 96% rename from web/landing/templates/shop/blueprints/symfony-backoffice/index.html.twig rename to web/landing/templates/work-shop/blueprints/symfony-backoffice/index.html.twig index 0a69cbd0f..e6c0b4ce4 100644 --- a/web/landing/templates/shop/blueprints/symfony-backoffice/index.html.twig +++ b/web/landing/templates/work-shop/blueprints/symfony-backoffice/index.html.twig @@ -1,4 +1,4 @@ -{% extends 'shop/_listing.html.twig' %} +{% extends 'work-shop/_listing.html.twig' %} {% set listing_name = 'Symfony Backoffice Blueprint' %} {% set listing_eyebrow = 'Blueprint' %} @@ -7,11 +7,11 @@ {# Matches the shipped screenshot dimensions, so nothing is cropped. #} {% set listing_image_aspect = '1600 / 692' %} {% set listing_images = [ - asset('images/shop/symfony-backoffice-app.png'), - asset('images/shop/symfony-backoffice-import.png'), - asset('images/shop/symfony-backoffice-api.png'), - asset('images/shop/symfony-backoffice-profiler.png'), - asset('images/shop/symfony-backoffice-telemetry.png'), + asset('images/work-shop/symfony-backoffice-app.png'), + asset('images/work-shop/symfony-backoffice-import.png'), + asset('images/work-shop/symfony-backoffice-api.png'), + asset('images/work-shop/symfony-backoffice-profiler.png'), + asset('images/work-shop/symfony-backoffice-telemetry.png'), ] %} {% set listing_tagline = 'A working Symfony back office built on Flow PHP. Read it, run it, take the patterns to your own project.' %} @@ -144,7 +144,7 @@ {% endblock %} {% block listing_aside %} - How it works before you buy diff --git a/web/landing/templates/shop/consulting/index.html.twig b/web/landing/templates/work-shop/consulting/index.html.twig similarity index 97% rename from web/landing/templates/shop/consulting/index.html.twig rename to web/landing/templates/work-shop/consulting/index.html.twig index 55387fb7b..796794c45 100644 --- a/web/landing/templates/shop/consulting/index.html.twig +++ b/web/landing/templates/work-shop/consulting/index.html.twig @@ -1,11 +1,11 @@ -{% extends 'shop/_listing.html.twig' %} +{% extends 'work-shop/_listing.html.twig' %} {% set listing_name = 'Consulting' %} {% set listing_eyebrow = 'Consulting' %} {% set listing_available = true %} {% set listing_images = [ - asset('images/shop/consulting.jpg'), - asset('images/shop/consulting-2.jpg'), + asset('images/work-shop/consulting.jpg'), + asset('images/work-shop/consulting-2.jpg'), ] %} {% set listing_cta_label = 'Book a free call' %} {% set listing_cta_url = 'https://calendar.proton.me/bookings#EyjcEe07NRPoD6ECOGt-Qd--PRYh7XmulLqtf5l2zKE=' %} diff --git a/web/landing/templates/shop/index.html.twig b/web/landing/templates/work-shop/index.html.twig similarity index 95% rename from web/landing/templates/shop/index.html.twig rename to web/landing/templates/work-shop/index.html.twig index 753eaba6a..7d5b2dbb7 100644 --- a/web/landing/templates/shop/index.html.twig +++ b/web/landing/templates/work-shop/index.html.twig @@ -20,8 +20,8 @@ { name: 'Symfony Backoffice Blueprint', price: '$59', - href: path('shop_blueprint_symfony'), - image: asset('images/shop/symfony-backoffice-app.png'), + href: path('work_shop_blueprint_symfony'), + image: asset('images/work-shop/symfony-backoffice-app.png'), blurb: 'A working Symfony back office built on Flow PHP. One schema per model drives the database, the API and the imports, and every request is traced from the browser down to the SQL query.', features: ['Schema drives the database, API and imports', 'Streaming CSV, JSON and XML exports', 'OpenTelemetry across browser and backend'], available: true @@ -41,8 +41,8 @@ listings: [ { name: 'Consulting', - href: path('shop_consulting'), - image: asset('images/shop/consulting.jpg'), + href: path('work_shop_consulting'), + image: asset('images/work-shop/consulting.jpg'), blurb: 'Sixteen years of data engineering, software architecture and observability work, from pipelines that hold up to APM bills that come down.', features: ['Data engineering, architecture, observability', 'Workshops, fractional architect, or hands-on build', 'Quoted per engagement, first call free'], available: true @@ -69,12 +69,12 @@

-