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
9 changes: 5 additions & 4 deletions e2e/tests/checkout/order-lifecycle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ test.describe('order lifecycle', () => {
await page.goto(`/checkout/${event.eventId}/${orderShortId}/details?session_identifier=${sessionId}`);
await page.waitForLoadState('networkidle');

const timerLabel = page.getByText('Time left:');
await expect(timerLabel).toBeVisible();
await expect(timerLabel.locator('..')).toContainText(/\d+:\d{2}/);
const timer = page.getByTestId('checkout-timer');
await expect(timer).toBeVisible();
await expect(timer).toContainText(/\d+:\d{2}/);
await expect(page.locator('header').getByText(event.title, { exact: true })).toBeVisible();
});

test('abandoning a checkout returns to the event page and cancels the order', async ({ page, api, account }) => {
Expand All @@ -24,7 +25,7 @@ test.describe('order lifecycle', () => {
await checkout.continueToCheckout();
const detailsUrl = page.url();

await page.getByRole('button', { name: 'Event Homepage' }).click();
await page.getByRole('button', { name: 'Back to event page' }).click();
await expect(page.getByRole('heading', { name: 'Are you sure you want to leave?' })).toBeVisible();
await page.getByRole('button', { name: 'Yes, cancel my order' }).click();

Expand Down
3 changes: 0 additions & 3 deletions frontend/src/components/forms/StripeCheckoutForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,6 @@ export default function StripeCheckoutForm({setSubmitHandler}: {
return (
<form id="payment-form">
<>
<h2>
{t`Payment`}
</h2>
{(order?.payment_status === 'PAYMENT_FAILED' || window?.location.search.includes('payment_failed')) && (
<Alert mb={20} color={'red'}>{t`Your payment was unsuccessful. Please try again.`}</Alert>
)}
Expand Down
183 changes: 120 additions & 63 deletions frontend/src/components/layouts/Checkout/Checkout.module.scss
Original file line number Diff line number Diff line change
@@ -1,99 +1,156 @@
@use "../../../styles/mixins";

$font-display: 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
$font-body: 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
$content-max-width: 700px;
$header-row-padding-inline: 24px;

.container {
display: flex;
// Use dvh (dynamic viewport height) with fallback for older browsers
// This fixes iOS Safari where 100vh includes the address bar height
min-height: 100vh;
min-height: 100dvh;
font-family: $font-body;

// CSS variables are set by CheckoutThemeProvider via MantineProvider cssVariablesResolver
// Available variables:
// --checkout-accent, --checkout-accent-contrast, --checkout-accent-soft, --checkout-accent-muted
// --checkout-background, --checkout-surface
// --checkout-text-primary, --checkout-text-secondary, --checkout-text-tertiary
// --checkout-border

background-color: var(--checkout-background, #F9FAFB);
color: var(--checkout-text-primary, #1a1a1a);

// Countdown timer colors - semantic colors that don't change with theme
.countdown {
color: var(--mantine-color-teal-6, #0ca678);
font-weight: 600;
font-size: 14px;
font-weight: 700;
font-variant-numeric: tabular-nums;
}

.countdownCloseToExpiry {
color: var(--mantine-color-red-7, #ad2f26);
}

.timerGroup {
flex-shrink: 0;
}

.timerLabel {
color: var(--checkout-text-secondary);
font-size: 0.875rem;

@include mixins.respond-below(sm) {
display: none;
}
}

.subTitle {
font-size: 0.95em;
margin-top: 10px;
color: var(--checkout-text-secondary);
}

.mainContent {
flex: 1;
min-width: 0;

.header {
height: 60px;
background-color: var(--checkout-surface);
border-bottom: 1px solid var(--checkout-border);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
}

.headerRow {
display: flex;
justify-content: center;
align-items: center;
gap: 12px;
height: 58px;
max-width: $content-max-width + 2 * $header-row-padding-inline;
margin-inline: auto;
padding-inline: $header-row-padding-inline;
}

.backButton {
--ai-size: 34px;
color: var(--checkout-text-secondary);
flex-shrink: 0;
}

.headerDivider {
width: 1px;
height: 22px;
flex-shrink: 0;
background-color: var(--checkout-border);
}

.titleBlock {
display: flex;
flex-direction: column;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
text-align: center;

.actionBar {
padding: var(--hi-spacing-md);
width: 100%;

.title {
font-family: $font-display;
font-size: 1.2em;
font-weight: 600;
color: var(--checkout-text-primary);
}
gap: 1px;
min-width: 0;
}

.eventName {
min-width: 0;
font-size: 15px;
font-weight: 700;
color: var(--checkout-text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.headerRowComplete .eventName {
font-size: 14.5px;
}

.eyebrow {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
white-space: nowrap;
color: var(--checkout-accent);
}

.eyebrowComplete {
color: var(--mantine-color-teal-6, #0ca678);
}

.eyebrowAwaiting {
color: var(--mantine-color-yellow-8, #a16207);
}

.headerSpacer {
flex: 1;
}

.timerCaption {
flex-shrink: 0;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--checkout-text-tertiary);

@include mixins.respond-below(sm) {
display: none;
}
}

h1 {
font-family: $font-display;
font-size: 1.3em;
font-weight: 600;
width: 100%;
max-width: 700px;
padding: 0 15px;
color: var(--checkout-text-primary);

overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;

@include mixins.respond-below(md) {
font-size: 1em;
}
.timerChip {
display: inline-flex;
align-items: center;
gap: 6px;
flex-shrink: 0;
padding: 6px 12px;
border-radius: 999px;
color: var(--mantine-color-teal-6, #0ca678);
background-color: color-mix(in srgb, var(--mantine-color-teal-6, #0ca678) 12%, var(--checkout-surface));

&:has(.countdownCloseToExpiry) {
color: var(--mantine-color-red-7, #ad2f26);
background-color: color-mix(in srgb, var(--mantine-color-red-7, #ad2f26) 12%, var(--checkout-surface));
}
}

.headerActions {
display: flex;
align-items: center;
gap: 2px;
flex-shrink: 0;

:global(.mantine-ActionIcon-root) {
--ai-size: 34px;
color: var(--checkout-text-secondary);
}
}

.progressTrack {
height: 3px;
background-color: color-mix(in srgb, var(--checkout-text-tertiary) 12%, var(--checkout-surface));
}

.progressFill {
height: 100%;
background-color: var(--checkout-accent);
border-start-end-radius: 2px;
border-end-end-radius: 2px;
transition: width 250ms cubic-bezier(0.4, 0, 0.2, 1);
}
}

a:not([class]) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
@use "../../../../styles/mixins";

$font-display: 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

.stepTitleBlock {
display: flex;
flex-direction: column;
gap: 2px;
margin-bottom: 14px;
}

.title {
font-family: $font-display;
font-size: 19px;
font-weight: 700;
letter-spacing: -0.01em;
color: var(--checkout-text-primary);
margin: 0;

@include mixins.respond-below(md) {
font-size: 17px;
}
}

.subtitle {
font-size: 13px;
color: var(--checkout-text-secondary);
margin: 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import classes from "./CheckoutStepTitle.module.scss";

interface CheckoutStepTitleProps {
title: string;
subtitle?: string;
}

export const CheckoutStepTitle = ({title, subtitle}: CheckoutStepTitleProps) => (
<div className={classes.stepTitleBlock}>
<h2 className={classes.title}>{title}</h2>
{subtitle && <p className={classes.subtitle}>{subtitle}</p>}
</div>
);
Loading
Loading