ui refactor: yuhhhhhh 80% done
This commit is contained in:
@@ -9,131 +9,145 @@
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 24 33.3333% 97.0588%;
|
||||
--foreground: 0 0% 10.1961%;
|
||||
--card: 24 33.3333% 97.0588%;
|
||||
--card-foreground: 0 0% 10.1961%;
|
||||
--popover: 24 33.3333% 97.0588%;
|
||||
--popover-foreground: 0 0% 10.1961%;
|
||||
--primary: 0 55.7789% 39.0196%;
|
||||
/* Backgrounds - Clean neutral grays */
|
||||
--background: 0 0% 98%;
|
||||
--foreground: 222 47% 11%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222 47% 11%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222 47% 11%;
|
||||
|
||||
/* Primary - Modern slate/blue (Stripe-inspired) */
|
||||
--primary: 221 83% 53%;
|
||||
--primary-foreground: 0 0% 100%;
|
||||
--secondary: 43.0769 90.6977% 91.5686%;
|
||||
--secondary-foreground: 39.8438 100% 25.098%;
|
||||
--muted: 22.5 21.0526% 92.549%;
|
||||
--muted-foreground: 33.3333 5.4545% 32.3529%;
|
||||
--accent: 48 96.4912% 88.8235%;
|
||||
--accent-foreground: 0 62.8205% 30.5882%;
|
||||
--destructive: 0 70% 35.2941%;
|
||||
|
||||
/* Secondary - Soft gray-blue */
|
||||
--secondary: 214 32% 91%;
|
||||
--secondary-foreground: 222 47% 11%;
|
||||
|
||||
/* Muted - Light grays */
|
||||
--muted: 210 40% 96%;
|
||||
--muted-foreground: 215 16% 47%;
|
||||
|
||||
/* Accent - Vibrant blue */
|
||||
--accent: 217 91% 60%;
|
||||
--accent-foreground: 0 0% 100%;
|
||||
|
||||
/* Destructive - Red for errors */
|
||||
--destructive: 0 84% 60%;
|
||||
--destructive-foreground: 0 0% 100%;
|
||||
--border: 37.7143 63.6364% 89.2157%;
|
||||
--input: 37.7143 63.6364% 89.2157%;
|
||||
--ring: 0 55.7789% 39.0196%;
|
||||
--chart-1: 0 73.7089% 41.7647%;
|
||||
--chart-2: 0 55.7789% 39.0196%;
|
||||
--chart-3: 0 62.8205% 30.5882%;
|
||||
--chart-4: 25.9649 90.4762% 37.0588%;
|
||||
--chart-5: 22.7273 82.5% 31.3725%;
|
||||
--sidebar: 22.5 21.0526% 92.549%;
|
||||
--sidebar-foreground: 0 0% 10.1961%;
|
||||
--sidebar-primary: 0 55.7789% 39.0196%;
|
||||
|
||||
/* Borders & Inputs - Subtle gray */
|
||||
--border: 214 32% 91%;
|
||||
--input: 214 32% 91%;
|
||||
--ring: 221 83% 53%;
|
||||
|
||||
/* Charts - Modern palette */
|
||||
--chart-1: 221 83% 53%;
|
||||
--chart-2: 142 76% 36%;
|
||||
--chart-3: 262 83% 58%;
|
||||
--chart-4: 41 96% 50%;
|
||||
--chart-5: 0 84% 60%;
|
||||
|
||||
/* Sidebar */
|
||||
--sidebar: 0 0% 98%;
|
||||
--sidebar-foreground: 222 47% 11%;
|
||||
--sidebar-primary: 221 83% 53%;
|
||||
--sidebar-primary-foreground: 0 0% 100%;
|
||||
--sidebar-accent: 48 96.4912% 88.8235%;
|
||||
--sidebar-accent-foreground: 0 62.8205% 30.5882%;
|
||||
--sidebar-border: 37.7143 63.6364% 89.2157%;
|
||||
--sidebar-ring: 0 55.7789% 39.0196%;
|
||||
--font-sans: Poppins, sans-serif;
|
||||
--font-serif: Libre Baskerville, serif;
|
||||
--font-mono: IBM Plex Mono, monospace;
|
||||
--radius: 0.375rem;
|
||||
--shadow-x: 1px;
|
||||
--shadow-y: 1px;
|
||||
--shadow-blur: 16px;
|
||||
--shadow-spread: -2px;
|
||||
--shadow-opacity: 0.12;
|
||||
--shadow-color: hsl(0 63% 18%);
|
||||
--shadow-2xs: 1px 1px 16px -2px hsl(0 63% 18% / 0.06);
|
||||
--shadow-xs: 1px 1px 16px -2px hsl(0 63% 18% / 0.06);
|
||||
--shadow-sm:
|
||||
1px 1px 16px -2px hsl(0 63% 18% / 0.12),
|
||||
1px 1px 2px -3px hsl(0 63% 18% / 0.12);
|
||||
--shadow:
|
||||
1px 1px 16px -2px hsl(0 63% 18% / 0.12),
|
||||
1px 1px 2px -3px hsl(0 63% 18% / 0.12);
|
||||
--shadow-md:
|
||||
1px 1px 16px -2px hsl(0 63% 18% / 0.12),
|
||||
1px 2px 4px -3px hsl(0 63% 18% / 0.12);
|
||||
--shadow-lg:
|
||||
1px 1px 16px -2px hsl(0 63% 18% / 0.12),
|
||||
1px 4px 6px -3px hsl(0 63% 18% / 0.12);
|
||||
--shadow-xl:
|
||||
1px 1px 16px -2px hsl(0 63% 18% / 0.12),
|
||||
1px 8px 10px -3px hsl(0 63% 18% / 0.12);
|
||||
--shadow-2xl: 1px 1px 16px -2px hsl(0 63% 18% / 0.3);
|
||||
--tracking-normal: 0em;
|
||||
--spacing: 0.25rem;
|
||||
--sidebar-accent: 214 32% 91%;
|
||||
--sidebar-accent-foreground: 222 47% 11%;
|
||||
--sidebar-border: 214 32% 91%;
|
||||
--sidebar-ring: 221 83% 53%;
|
||||
|
||||
/* Typography */
|
||||
--font-sans: Fredoka, sans-serif;
|
||||
--font-serif: Georgia, serif;
|
||||
--font-mono: ui-monospace, monospace;
|
||||
|
||||
/* Design tokens */
|
||||
--radius: 0.5rem;
|
||||
|
||||
/* Shadows - Neutral */
|
||||
--shadow-color: 220 9% 46%;
|
||||
--shadow-2xs: 0 1px 2px 0 hsl(220 9% 46% / 0.05);
|
||||
--shadow-xs: 0 1px 3px 0 hsl(220 9% 46% / 0.1);
|
||||
--shadow-sm: 0 2px 4px 0 hsl(220 9% 46% / 0.1);
|
||||
--shadow: 0 4px 6px -1px hsl(220 9% 46% / 0.1);
|
||||
--shadow-md: 0 6px 12px -2px hsl(220 9% 46% / 0.15);
|
||||
--shadow-lg: 0 10px 20px -5px hsl(220 9% 46% / 0.2);
|
||||
--shadow-xl: 0 20px 25px -5px hsl(220 9% 46% / 0.25);
|
||||
--shadow-2xl: 0 25px 50px -12px hsl(220 9% 46% / 0.3);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 24 9.8039% 10%;
|
||||
--foreground: 60 4.7619% 95.8824%;
|
||||
--card: 12 6.4935% 15.098%;
|
||||
--card-foreground: 60 4.7619% 95.8824%;
|
||||
--popover: 12 6.4935% 15.098%;
|
||||
--popover-foreground: 60 4.7619% 95.8824%;
|
||||
--primary: 0 73.7089% 41.7647%;
|
||||
--primary-foreground: 24 33.3333% 97.0588%;
|
||||
--secondary: 22.7273 82.5% 31.3725%;
|
||||
--secondary-foreground: 48 96.4912% 88.8235%;
|
||||
--muted: 24 8.7719% 11.1765%;
|
||||
--muted-foreground: 24 5.7471% 82.9412%;
|
||||
--accent: 25.9649 90.4762% 37.0588%;
|
||||
--accent-foreground: 48 96.4912% 88.8235%;
|
||||
--destructive: 0 84.2365% 60.1961%;
|
||||
--destructive-foreground: 0 0% 100%;
|
||||
--border: 30 6.25% 25.098%;
|
||||
--input: 30 6.25% 25.098%;
|
||||
--ring: 0 73.7089% 41.7647%;
|
||||
--chart-1: 0 90.604% 70.7843%;
|
||||
--chart-2: 0 84.2365% 60.1961%;
|
||||
--chart-3: 0 72.2222% 50.5882%;
|
||||
--chart-4: 43.2558 96.4126% 56.2745%;
|
||||
--chart-5: 37.6923 92.126% 50.1961%;
|
||||
--sidebar: 24 9.8039% 10%;
|
||||
--sidebar-foreground: 60 4.7619% 95.8824%;
|
||||
--sidebar-primary: 0 73.7089% 41.7647%;
|
||||
--sidebar-primary-foreground: 24 33.3333% 97.0588%;
|
||||
--sidebar-accent: 25.9649 90.4762% 37.0588%;
|
||||
--sidebar-accent-foreground: 48 96.4912% 88.8235%;
|
||||
--sidebar-border: 30 6.25% 25.098%;
|
||||
--sidebar-ring: 0 73.7089% 41.7647%;
|
||||
--font-sans: Poppins, sans-serif;
|
||||
--font-serif: Libre Baskerville, serif;
|
||||
--font-mono: IBM Plex Mono, monospace;
|
||||
--radius: 0.375rem;
|
||||
--shadow-x: 1px;
|
||||
--shadow-y: 1px;
|
||||
--shadow-blur: 16px;
|
||||
--shadow-spread: -2px;
|
||||
--shadow-opacity: 0.12;
|
||||
--shadow-color: hsl(0 63% 18%);
|
||||
--shadow-2xs: 1px 1px 16px -2px hsl(0 63% 18% / 0.06);
|
||||
--shadow-xs: 1px 1px 16px -2px hsl(0 63% 18% / 0.06);
|
||||
--shadow-sm:
|
||||
1px 1px 16px -2px hsl(0 63% 18% / 0.12),
|
||||
1px 1px 2px -3px hsl(0 63% 18% / 0.12);
|
||||
--shadow:
|
||||
1px 1px 16px -2px hsl(0 63% 18% / 0.12),
|
||||
1px 1px 2px -3px hsl(0 63% 18% / 0.12);
|
||||
--shadow-md:
|
||||
1px 1px 16px -2px hsl(0 63% 18% / 0.12),
|
||||
1px 2px 4px -3px hsl(0 63% 18% / 0.12);
|
||||
--shadow-lg:
|
||||
1px 1px 16px -2px hsl(0 63% 18% / 0.12),
|
||||
1px 4px 6px -3px hsl(0 63% 18% / 0.12);
|
||||
--shadow-xl:
|
||||
1px 1px 16px -2px hsl(0 63% 18% / 0.12),
|
||||
1px 8px 10px -3px hsl(0 63% 18% / 0.12);
|
||||
--shadow-2xl: 1px 1px 16px -2px hsl(0 63% 18% / 0.3);
|
||||
/* Backgrounds - Dark mode */
|
||||
--background: 222 47% 11%;
|
||||
--foreground: 210 40% 98%;
|
||||
--card: 222 47% 15%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--popover: 222 47% 15%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
|
||||
/* Primary - Brighter blue for dark mode */
|
||||
--primary: 217 91% 60%;
|
||||
--primary-foreground: 222 47% 11%;
|
||||
|
||||
/* Secondary */
|
||||
--secondary: 217 33% 17%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
|
||||
/* Muted */
|
||||
--muted: 223 47% 11%;
|
||||
--muted-foreground: 215 20% 65%;
|
||||
|
||||
/* Accent */
|
||||
--accent: 217 91% 60%;
|
||||
--accent-foreground: 222 47% 11%;
|
||||
|
||||
/* Destructive */
|
||||
--destructive: 0 63% 50%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
/* Borders & Inputs */
|
||||
--border: 217 33% 17%;
|
||||
--input: 217 33% 17%;
|
||||
--ring: 217 91% 60%;
|
||||
|
||||
/* Charts */
|
||||
--chart-1: 217 91% 60%;
|
||||
--chart-2: 142 76% 36%;
|
||||
--chart-3: 262 83% 58%;
|
||||
--chart-4: 41 96% 50%;
|
||||
--chart-5: 0 84% 60%;
|
||||
|
||||
/* Sidebar */
|
||||
--sidebar: 222 47% 11%;
|
||||
--sidebar-foreground: 210 40% 98%;
|
||||
--sidebar-primary: 217 91% 60%;
|
||||
--sidebar-primary-foreground: 222 47% 11%;
|
||||
--sidebar-accent: 217 33% 17%;
|
||||
--sidebar-accent-foreground: 210 40% 98%;
|
||||
--sidebar-border: 217 33% 17%;
|
||||
--sidebar-ring: 217 91% 60%;
|
||||
|
||||
/* Typography */
|
||||
--font-sans: Fredoka, sans-serif;
|
||||
--font-serif: Georgia, serif;
|
||||
--font-mono: ui-monospace, monospace;
|
||||
|
||||
/* Design tokens */
|
||||
--radius: 0.5rem;
|
||||
|
||||
/* Shadows - Dark mode */
|
||||
--shadow-color: 0 0% 0%;
|
||||
--shadow-2xs: 0 1px 2px 0 hsl(0 0% 0% / 0.3);
|
||||
--shadow-xs: 0 1px 3px 0 hsl(0 0% 0% / 0.4);
|
||||
--shadow-sm: 0 2px 4px 0 hsl(0 0% 0% / 0.4);
|
||||
--shadow: 0 4px 6px -1px hsl(0 0% 0% / 0.4);
|
||||
--shadow-md: 0 6px 12px -2px hsl(0 0% 0% / 0.5);
|
||||
--shadow-lg: 0 10px 20px -5px hsl(0 0% 0% / 0.6);
|
||||
--shadow-xl: 0 20px 25px -5px hsl(0 0% 0% / 0.7);
|
||||
--shadow-2xl: 0 25px 50px -12px hsl(0 0% 0% / 0.75);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,19 +161,20 @@
|
||||
scroll-behavior: smooth;
|
||||
font-family:
|
||||
"Fredoka",
|
||||
system-ui,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
Roboto,
|
||||
"Helvetica Neue",
|
||||
Arial,
|
||||
"Noto Sans",
|
||||
sans-serif,
|
||||
"Apple Color Emoji",
|
||||
"Segoe UI Emoji",
|
||||
"Segoe UI Symbol",
|
||||
"Noto Color Emoji";
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-weight: 400;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,8 +189,49 @@
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
/* Stripe-inspired gradients */
|
||||
.gradient-mesh {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
.gradient-success {
|
||||
background: linear-gradient(135deg, #84fab0 0%, #8fd3f4 100%);
|
||||
}
|
||||
|
||||
.gradient-error {
|
||||
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
|
||||
}
|
||||
|
||||
/* Smooth transitions */
|
||||
.transition-smooth {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* Glass morphism effect */
|
||||
.glass {
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
}
|
||||
}
|
||||
|
||||
.all-reset {
|
||||
all: unset;
|
||||
}
|
||||
|
||||
/* Custom focus styles for better accessibility */
|
||||
@layer base {
|
||||
*:focus-visible {
|
||||
@apply outline-none ring-2 ring-primary ring-offset-2;
|
||||
}
|
||||
}
|
||||
|
||||
/* Smooth page transitions */
|
||||
@layer base {
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,8 @@
|
||||
<input
|
||||
bind:this={ref}
|
||||
class={cn(
|
||||
"flex w-full items-center rounded-md border border-input bg-background/50 px-3 py-2 text-sm shadow-[inset_0_3px_8px_0_rgba(0,0,0,0.15)] file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"flex w-full items-center rounded-lg border border-input bg-white px-3 py-2 text-sm shadow-sm transition-all file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:border-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/20 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
inputSizes[inputSize],
|
||||
TRANSITION_COLORS,
|
||||
className,
|
||||
)}
|
||||
bind:value
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { Select as SelectPrimitive, type WithoutChild } from "bits-ui";
|
||||
import ChevronDown from "@lucide/svelte/icons/chevron-down";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { TRANSITION_COLORS } from "$lib/core/constants";
|
||||
|
||||
const inputSizes = {
|
||||
sm: "px-3 py-2 text-sm file:text-sm",
|
||||
@@ -27,13 +26,9 @@
|
||||
<SelectPrimitive.Trigger
|
||||
bind:ref
|
||||
class={cn(
|
||||
// !overrideClasses &&
|
||||
// "flex w-full items-center justify-between rounded-md border border-input bg-white ring-offset-background focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[placeholder]:text-muted-foreground [&>span]:line-clamp-1",
|
||||
|
||||
!overrideClasses &&
|
||||
"flex w-full items-center justify-between rounded-md border border-input bg-background shadow-[inset_0_3px_8px_0_rgba(0,0,0,0.15)] file:border-0 file:bg-transparent file:font-medium placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"flex w-full items-center justify-between rounded-lg border border-input bg-white shadow-sm transition-all file:border-0 file:bg-transparent file:font-medium placeholder:text-muted-foreground focus-visible:border-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/20 disabled:cursor-not-allowed disabled:opacity-50 data-[placeholder]:text-muted-foreground [&>span]:line-clamp-1",
|
||||
inputSizes[inputSize],
|
||||
TRANSITION_COLORS,
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -97,17 +97,15 @@
|
||||
</Sheet>
|
||||
|
||||
<div class="hidden w-full overflow-x-auto lg:block">
|
||||
<div
|
||||
class="flex w-full min-w-[30rem] items-center justify-between gap-2 overflow-x-auto py-8"
|
||||
>
|
||||
<div class="flex w-full items-center justify-between gap-2 overflow-x-auto">
|
||||
{#each checkoutSteps as step, index}
|
||||
<div class="flex flex-1 items-center gap-2">
|
||||
<div
|
||||
class={cn(
|
||||
"flex items-center justify-center",
|
||||
"flex items-center justify-center transition-all",
|
||||
index <= activeStepIndex
|
||||
? "cursor-pointer"
|
||||
: "cursor-not-allowed opacity-50",
|
||||
: "cursor-not-allowed opacity-40",
|
||||
)}
|
||||
onclick={() => handleStepClick(index, step.id)}
|
||||
onkeydown={(e) => {
|
||||
@@ -120,23 +118,28 @@
|
||||
>
|
||||
<div
|
||||
class={cn(
|
||||
"flex h-10 min-h-10 w-10 min-w-10 items-center justify-center rounded-full border-2 transition-colors",
|
||||
index <= activeStepIndex
|
||||
? "hover:bg-primary-600 border-brand-700 bg-primary text-white/60"
|
||||
: "border-gray-400 bg-gray-100 text-gray-700",
|
||||
index === activeStepIndex
|
||||
? "text-lg font-semibold text-white"
|
||||
: "",
|
||||
"flex h-10 min-h-10 w-10 min-w-10 items-center justify-center rounded-full border-2 font-medium transition-all",
|
||||
index < activeStepIndex
|
||||
? "border-green-500 bg-green-500 text-white"
|
||||
: index === activeStepIndex
|
||||
? "border-primary bg-primary text-white shadow-md"
|
||||
: "border-gray-300 bg-white text-gray-400",
|
||||
)}
|
||||
>
|
||||
{index + 1}
|
||||
{#if index < activeStepIndex}
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
{:else}
|
||||
{index + 1}
|
||||
{/if}
|
||||
</div>
|
||||
<span
|
||||
class={cn(
|
||||
"ml-2 hidden w-max text-sm md:block",
|
||||
"ml-3 hidden w-max text-sm transition-all md:block",
|
||||
index <= activeStepIndex
|
||||
? "font-semibold"
|
||||
: "text-gray-800",
|
||||
? "font-semibold text-gray-900"
|
||||
: "text-gray-500",
|
||||
)}
|
||||
>
|
||||
{step.label}
|
||||
@@ -145,10 +148,10 @@
|
||||
{#if index !== checkoutSteps.length - 1}
|
||||
<div
|
||||
class={cn(
|
||||
"h-0.5 w-full min-w-4 flex-1 border-t transition-colors",
|
||||
index <= activeStepIndex
|
||||
? "border-primary"
|
||||
: "border-gray-400",
|
||||
"h-0.5 w-full min-w-4 flex-1 transition-all",
|
||||
index < activeStepIndex
|
||||
? "bg-green-500"
|
||||
: "bg-gray-300",
|
||||
)}
|
||||
></div>
|
||||
{/if}
|
||||
|
||||
@@ -24,73 +24,77 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 rounded-lg bg-white p-4 drop-shadow-lg md:p-8">
|
||||
<Title size="h4" weight="medium">Payment Summary</Title>
|
||||
<div class="h-0.5 w-full border-t-2 border-gray-200"></div>
|
||||
<div class="flex flex-col gap-6">
|
||||
<Title size="h4" weight="medium">Order Summary</Title>
|
||||
|
||||
{#if !calculating}
|
||||
<!-- Product Information -->
|
||||
{#if $productStore}
|
||||
<div class="flex flex-col gap-2">
|
||||
<Title size="p" weight="medium">{$productStore.title}</Title>
|
||||
<p class="text-sm text-gray-600">{$productStore.description}</p>
|
||||
<div class="flex flex-col gap-3 border-b border-gray-200 pb-6">
|
||||
<Title size="p" weight="medium">
|
||||
{$productStore.title}
|
||||
</Title>
|
||||
<p class="text-sm leading-relaxed text-gray-600">
|
||||
{$productStore.description}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Price Breakdown -->
|
||||
<div class="mt-2 flex flex-col gap-2 border-t pt-4">
|
||||
<div class="flex justify-between text-sm">
|
||||
<span>Base Price</span>
|
||||
<span>{convertAndFormatCurrency(priceDetails.basePrice)}</span>
|
||||
<div class="flex flex-col gap-3 border-b border-gray-200 pb-6">
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-gray-600">Base Price</span>
|
||||
<span class="font-medium text-gray-900">
|
||||
{convertAndFormatCurrency(priceDetails.basePrice)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{#if priceDetails.discountAmount > 0}
|
||||
<div class="flex justify-between text-sm text-green-600">
|
||||
<span>Discount</span>
|
||||
<span
|
||||
>-{convertAndFormatCurrency(
|
||||
priceDetails.discountAmount,
|
||||
)}</span
|
||||
>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-gray-600">Discount</span>
|
||||
<span class="font-medium text-green-600">
|
||||
-{convertAndFormatCurrency(priceDetails.discountAmount)}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-between text-sm">
|
||||
<span>Display Price</span>
|
||||
<span
|
||||
>{convertAndFormatCurrency(
|
||||
priceDetails.displayPrice,
|
||||
)}</span
|
||||
>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-gray-600">Display Price</span>
|
||||
<span class="font-medium text-gray-900">
|
||||
{convertAndFormatCurrency(priceDetails.displayPrice)}
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Final Total -->
|
||||
<div class="mt-4 flex flex-col gap-2 border-t pt-4">
|
||||
<div class="flex justify-between font-medium">
|
||||
<Title size="h5" weight="medium"
|
||||
>Total ({$currencyStore.code})</Title
|
||||
>
|
||||
<span class="text-lg"
|
||||
>{convertAndFormatCurrency(priceDetails.orderPrice)}</span
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-base font-semibold text-gray-900">
|
||||
Total ({$currencyStore.code})
|
||||
</span>
|
||||
<span class="text-2xl font-bold text-gray-900">
|
||||
{convertAndFormatCurrency(priceDetails.orderPrice)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Security Badge -->
|
||||
<div class="mt-4 rounded-lg bg-blue-50 p-4">
|
||||
<div class="flex items-start gap-3">
|
||||
<svg class="mt-0.5 h-5 w-5 flex-shrink-0 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
</svg>
|
||||
<div class="flex-1">
|
||||
<p class="text-xs font-medium text-blue-900">Secure Checkout</p>
|
||||
<p class="mt-1 text-xs text-blue-700">
|
||||
Your payment information is encrypted and secure
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="grid place-items-center p-8 text-center">
|
||||
<span class="text-gray-600">Calculating...</span>
|
||||
<div class="grid place-items-center py-12">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="h-5 w-5 animate-spin rounded-full border-2 border-gray-300 border-t-primary"></div>
|
||||
<span class="text-sm text-gray-600">Calculating...</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Important Information -->
|
||||
<div class="mt-4 rounded-lg bg-gray-50 p-4 text-xs text-gray-600">
|
||||
<p class="mb-2 font-medium">Important Information:</p>
|
||||
<ul class="list-disc space-y-1 pl-4">
|
||||
<li>Price includes all applicable taxes and fees</li>
|
||||
<li>
|
||||
Cancellation and refund policies may apply as per our terms of
|
||||
service
|
||||
</li>
|
||||
<li>Payment will be processed securely upon order confirmation</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
<script lang="ts">
|
||||
import Icon from "$lib/components/atoms/icon.svelte";
|
||||
import Footer from "$lib/components/molecules/footer/footer.svelte";
|
||||
import Navbar from "$lib/components/molecules/navbar/navbar.svelte";
|
||||
import { currencyVM } from "$lib/domains/currency/view/currency.vm.svelte";
|
||||
import { svTrpcApiStore, trpcApiStore } from "$lib/stores/api";
|
||||
import { sessionInfo, sessionUserInfo } from "$lib/stores/session.info";
|
||||
import { trpc, trpcRaw } from "$lib/trpc/trpc";
|
||||
import { onMount } from "svelte";
|
||||
import UpIcon from "~icons/material-symbols/keyboard-double-arrow-up-rounded";
|
||||
import type { LayoutData } from "./$types";
|
||||
|
||||
let {
|
||||
@@ -23,18 +19,6 @@
|
||||
|
||||
svTrpcApiStore.set(trpc());
|
||||
|
||||
// let invert = $derived(page.url.pathname === "/");
|
||||
|
||||
let showScrollTop = $state(false);
|
||||
|
||||
function scrollToTop() {
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
}
|
||||
|
||||
function handleScroll() {
|
||||
showScrollTop = window.scrollY > 200; // Show button when scrolled down 200px
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
trpcApiStore.set(trpcRaw());
|
||||
|
||||
@@ -42,32 +26,11 @@
|
||||
setTimeout(() => {
|
||||
currencyVM.getCurrencies();
|
||||
}, 500);
|
||||
|
||||
// Add scroll event listener
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
return () => {
|
||||
window.removeEventListener("scroll", handleScroll);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<Navbar invert={false} />
|
||||
|
||||
<div class="grid h-full w-full place-items-center overflow-x-hidden">
|
||||
<main class="flex w-full flex-col gap-8 overflow-x-hidden">
|
||||
<div class="min-h-screen w-full">
|
||||
<main class="flex w-full flex-col">
|
||||
{@render children?.()}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
|
||||
<!-- Scroll to top FAB -->
|
||||
{#if showScrollTop}
|
||||
<button
|
||||
onclick={scrollToTop}
|
||||
class="fixed bottom-4 left-4 z-50 flex h-12 w-12 items-center justify-center rounded-full border-2 border-white/60 bg-primary text-white shadow-lg transition-opacity hover:bg-primary/90 lg:bottom-8 lg:right-8"
|
||||
aria-label="Scroll to top"
|
||||
>
|
||||
<Icon icon={UpIcon} cls="w-auto h-6 text-white" />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
<script lang="ts">
|
||||
import Title from "$lib/components/atoms/title.svelte";
|
||||
import { checkoutSessionIdStore } from "$lib/domains/checkout/sid.store";
|
||||
import { onMount } from "svelte";
|
||||
import { toast } from "svelte-sonner";
|
||||
import type { PageData } from "./$types";
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
onMount(() => {
|
||||
toast("Do the check here to then either redirect or show the error");
|
||||
if (data.error) {
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (!data.data) {
|
||||
toast.error("An error occurred during checkout", {
|
||||
description: "Please try again later or contact support",
|
||||
});
|
||||
return;
|
||||
}
|
||||
toast("Please hold...", {
|
||||
description: "Preparing checkout session",
|
||||
});
|
||||
window.location.replace(
|
||||
`/checkout/${$checkoutSessionIdStore}/${data.data.linkId}`,
|
||||
);
|
||||
}, 3000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex w-full flex-col items-center justify-center gap-8 p-20">
|
||||
{#if data.data}
|
||||
<Title size="h3" weight="medium">{data.data.title}</Title>
|
||||
<p>{data.data.description}</p>
|
||||
<span>
|
||||
Either show the user the product as being valid and redirecting them
|
||||
to the checkout
|
||||
</span>
|
||||
{:else}
|
||||
<span
|
||||
>Show the user an error around "page not found" or "expired link"</span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -2,6 +2,6 @@ import { getProductUseCases } from "$lib/domains/product/usecases";
|
||||
import type { PageServerLoad } from "./$types";
|
||||
|
||||
export const load: PageServerLoad = async ({ params }) => {
|
||||
const pid = params.pageid;
|
||||
return await getProductUseCases().getProductByLinkId(pid);
|
||||
const plid = params.plid;
|
||||
return await getProductUseCases().getProductByLinkId(plid);
|
||||
};
|
||||
107
apps/frontend/src/routes/(main)/[plid]/+page.svelte
Normal file
107
apps/frontend/src/routes/(main)/[plid]/+page.svelte
Normal file
@@ -0,0 +1,107 @@
|
||||
<script lang="ts">
|
||||
import Icon from "$lib/components/atoms/icon.svelte";
|
||||
import Title from "$lib/components/atoms/title.svelte";
|
||||
import { checkoutSessionIdStore } from "$lib/domains/checkout/sid.store";
|
||||
import { onMount } from "svelte";
|
||||
import { toast } from "svelte-sonner";
|
||||
import CheckCircleIcon from "~icons/heroicons/check-circle-20-solid";
|
||||
import ExclamationIcon from "~icons/heroicons/exclamation-triangle-20-solid";
|
||||
import type { PageData } from "./$types";
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
onMount(() => {
|
||||
if (data.error) {
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (!data.data) {
|
||||
toast.error("An error occurred during checkout", {
|
||||
description: "Please try again later or contact support",
|
||||
});
|
||||
return;
|
||||
}
|
||||
window.location.replace(
|
||||
`/checkout/${$checkoutSessionIdStore}/${data.data.linkId}`,
|
||||
);
|
||||
}, 2000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex min-h-screen w-full items-center justify-center bg-gradient-to-br from-gray-50 to-gray-100 p-4">
|
||||
<div class="w-full max-w-md">
|
||||
{#if data.data}
|
||||
<!-- Valid Product Card -->
|
||||
<div class="animate-fade-in rounded-2xl bg-white p-8 shadow-xl">
|
||||
<div class="mb-6 flex justify-center">
|
||||
<div class="rounded-full bg-green-100 p-3">
|
||||
<Icon icon={CheckCircleIcon} cls="h-12 w-12 text-green-600" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Title size="h4" weight="medium" center>
|
||||
{data.data.title}
|
||||
</Title>
|
||||
|
||||
{#if data.data.description}
|
||||
<p class="mb-6 mt-3 text-center text-gray-600">
|
||||
{data.data.description}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<div class="mb-6 flex justify-center">
|
||||
<div class="flex space-x-1">
|
||||
<div class="h-2 w-2 animate-bounce rounded-full bg-primary [animation-delay:-0.3s]"></div>
|
||||
<div class="h-2 w-2 animate-bounce rounded-full bg-primary [animation-delay:-0.15s]"></div>
|
||||
<div class="h-2 w-2 animate-bounce rounded-full bg-primary"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-center text-sm text-gray-500">
|
||||
Redirecting you to checkout...
|
||||
</p>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Error State -->
|
||||
<div class="animate-fade-in rounded-2xl bg-white p-8 shadow-xl">
|
||||
<div class="mb-6 flex justify-center">
|
||||
<div class="rounded-full bg-red-100 p-3">
|
||||
<Icon icon={ExclamationIcon} cls="h-12 w-12 text-red-600" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Title size="h4" weight="medium" center>Page Not Found</Title>
|
||||
|
||||
<p class="mb-6 mt-3 text-center text-gray-600">
|
||||
The link you followed may be expired or invalid. Please contact support for assistance.
|
||||
</p>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<a
|
||||
href="/"
|
||||
class="inline-flex items-center justify-center rounded-lg bg-primary px-6 py-3 text-sm font-medium text-white transition-colors hover:bg-primary/90"
|
||||
>
|
||||
Return Home
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fade-in {
|
||||
animation: fade-in 0.5s ease-out;
|
||||
}
|
||||
</style>
|
||||
@@ -44,25 +44,50 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="grid h-full w-full place-items-center">
|
||||
<MaxWidthWrapper cls="p-4 md:p-8 lg:p-10 3xl:p-0">
|
||||
{#if !pageData.data || !!pageData.error}
|
||||
<div class="grid h-full min-h-screen w-full place-items-center">
|
||||
<div class="flex flex-col items-center justify-center gap-4">
|
||||
<Icon icon={SearchIcon} cls="w-12 h-12" />
|
||||
<Title size="h4" color="black">Product not found</Title>
|
||||
<p>Something went wrong, please try again or contact us</p>
|
||||
<div class="min-h-screen w-full bg-gradient-to-br from-gray-50 to-gray-100">
|
||||
{#if !pageData.data || !!pageData.error}
|
||||
<!-- Error State -->
|
||||
<div class="flex min-h-screen w-full items-center justify-center p-4">
|
||||
<div class="animate-fade-in w-full max-w-md rounded-2xl bg-white p-8 shadow-xl">
|
||||
<div class="mb-6 flex justify-center">
|
||||
<div class="rounded-full bg-red-100 p-3">
|
||||
<Icon icon={SearchIcon} cls="h-12 w-12 text-red-600" />
|
||||
</div>
|
||||
</div>
|
||||
<Title size="h4" weight="medium" center>Product Not Found</Title>
|
||||
<p class="mb-6 mt-3 text-center text-gray-600">
|
||||
Something went wrong. Please try again or contact support for assistance.
|
||||
</p>
|
||||
<div class="flex justify-center">
|
||||
<a
|
||||
href="/"
|
||||
class="inline-flex items-center justify-center rounded-lg bg-gray-900 px-6 py-3 text-sm font-medium text-white transition-colors hover:bg-gray-800"
|
||||
>
|
||||
Return Home
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{:else if checkoutVM.checkoutStep === CheckoutStep.Confirmation}
|
||||
<div class="grid w-full place-items-center p-4 py-32">
|
||||
</div>
|
||||
{:else if checkoutVM.checkoutStep === CheckoutStep.Confirmation}
|
||||
<!-- Confirmation State -->
|
||||
<div class="flex min-h-screen w-full items-center justify-center p-4">
|
||||
<MaxWidthWrapper cls="w-full">
|
||||
<CheckoutConfirmationSection />
|
||||
</MaxWidthWrapper>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Checkout Flow -->
|
||||
<div class="mx-auto w-full max-w-7xl px-4 py-8 md:px-6 lg:px-8 lg:py-12">
|
||||
<!-- Steps Indicator -->
|
||||
<div class="mb-8 lg:mb-12">
|
||||
<CheckoutStepsIndicator />
|
||||
</div>
|
||||
{:else}
|
||||
<CheckoutStepsIndicator />
|
||||
<div class="flex w-full flex-col gap-8 lg:flex-row">
|
||||
<div class="flex w-full flex-col">
|
||||
<div class="flex w-full flex-col gap-12">
|
||||
|
||||
<!-- Main Checkout Layout -->
|
||||
<div class="flex flex-col gap-6 lg:flex-row lg:gap-8">
|
||||
<!-- Left Column: Forms -->
|
||||
<div class="flex-1">
|
||||
<div class="rounded-2xl bg-white p-6 shadow-lg md:p-8 lg:p-10">
|
||||
{#if checkoutVM.loading}
|
||||
<CheckoutLoadingSection />
|
||||
{:else if checkoutVM.checkoutStep === CheckoutStep.Initial}
|
||||
@@ -74,16 +99,33 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="grid w-full place-items-center lg:max-w-lg lg:place-items-start"
|
||||
>
|
||||
<div
|
||||
class="flex w-full flex-col gap-8 pt-8 md:max-w-md lg:max-w-full lg:pt-0"
|
||||
>
|
||||
<PaymentSummary />
|
||||
|
||||
<!-- Right Column: Summary (Sticky on larger screens) -->
|
||||
<div class="lg:w-[400px] xl:w-[440px]">
|
||||
<div class="lg:sticky lg:top-8">
|
||||
<div class="rounded-2xl bg-white p-6 shadow-lg md:p-8">
|
||||
<PaymentSummary />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</MaxWidthWrapper>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fade-in {
|
||||
animation: fade-in 0.5s ease-out;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,41 +1,98 @@
|
||||
<script lang="ts">
|
||||
import Icon from "$lib/components/atoms/icon.svelte";
|
||||
import Title from "$lib/components/atoms/title.svelte";
|
||||
import MaxWidthWrapper from "$lib/components/molecules/max-width-wrapper.svelte";
|
||||
import CheckIcon from "~icons/ic/round-check";
|
||||
import CheckIcon from "~icons/heroicons/check-circle-20-solid";
|
||||
import ChatBubbleLeftRightIcon from "~icons/heroicons/chat-bubble-left-right-20-solid";
|
||||
// Maybe todo? if the `uid` search param is present, do something?? figure out later
|
||||
</script>
|
||||
|
||||
<div class="grid min-h-[80vh] w-full place-items-center px-4 sm:px-6">
|
||||
<MaxWidthWrapper
|
||||
cls="flex flex-col gap-6 sm:gap-8 items-center justify-center"
|
||||
>
|
||||
<div
|
||||
class="flex w-full max-w-md flex-col items-center justify-center gap-6 rounded-xl bg-white p-4 drop-shadow-lg sm:gap-8 sm:p-6 md:p-8 md:py-12 lg:max-w-xl"
|
||||
>
|
||||
<div
|
||||
class="rounded-full bg-emerald-100 p-1.5 text-emerald-600 drop-shadow-lg sm:p-2"
|
||||
>
|
||||
<Icon
|
||||
icon={CheckIcon}
|
||||
cls="w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12"
|
||||
/>
|
||||
<div class="flex min-h-screen w-full items-center justify-center bg-gradient-to-br from-green-50 via-blue-50 to-purple-50 p-4">
|
||||
<div class="w-full max-w-lg">
|
||||
<div class="animate-scale-in rounded-3xl bg-white p-8 shadow-2xl md:p-12">
|
||||
<!-- Success Icon with Animation -->
|
||||
<div class="mb-8 flex justify-center">
|
||||
<div class="animate-check-bounce rounded-full bg-gradient-to-br from-green-400 to-green-600 p-4 shadow-lg">
|
||||
<Icon icon={CheckIcon} cls="h-16 w-16 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Title size="h3" center weight="medium">Booking confirmed</Title>
|
||||
<!-- Title -->
|
||||
<Title size="h3" weight="medium" center>Order Confirmed!</Title>
|
||||
|
||||
<p
|
||||
class="w-full max-w-prose text-center text-sm text-gray-600 sm:text-base"
|
||||
>
|
||||
Thank you for booking your flight! Your order has been placed
|
||||
successfully. You will receive a confirmation email shortly.
|
||||
</p>
|
||||
<p
|
||||
class="w-full max-w-prose text-center text-sm text-gray-600 sm:text-base"
|
||||
>
|
||||
In it you will not only find the booking details, but also a
|
||||
tracking number for your flight.
|
||||
<!-- Description -->
|
||||
<p class="mb-6 mt-4 text-center text-base text-gray-600">
|
||||
Thank you for your order! Your payment has been processed successfully.
|
||||
</p>
|
||||
|
||||
<!-- Confirmation Notification Card -->
|
||||
<div class="mb-8 rounded-xl bg-blue-50 p-4 md:p-6">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="rounded-lg bg-blue-100 p-2">
|
||||
<Icon icon={ChatBubbleLeftRightIcon} cls="h-5 w-5 text-blue-600" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="mb-1 text-sm font-semibold text-gray-900">
|
||||
We'll Be In Touch
|
||||
</p>
|
||||
<p class="text-sm text-gray-600">
|
||||
Our team will reach back to you soon with order confirmation and details.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info Cards Grid -->
|
||||
<div class="mb-8 grid gap-4 sm:grid-cols-2">
|
||||
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
|
||||
<div class="mb-1 text-xs font-medium uppercase tracking-wide text-gray-500">
|
||||
Status
|
||||
</div>
|
||||
<div class="text-sm font-semibold text-gray-900">
|
||||
Processing
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
|
||||
<div class="mb-1 text-xs font-medium uppercase tracking-wide text-gray-500">
|
||||
Confirmation
|
||||
</div>
|
||||
<div class="text-sm font-semibold text-gray-900">
|
||||
Via Contact
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</MaxWidthWrapper>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes scale-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes check-bounce {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-scale-in {
|
||||
animation: scale-in 0.5s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
|
||||
.animate-check-bounce {
|
||||
animation: check-bounce 0.6s ease-in-out;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
import { page } from "$app/state";
|
||||
import Icon from "$lib/components/atoms/icon.svelte";
|
||||
import Title from "$lib/components/atoms/title.svelte";
|
||||
import MaxWidthWrapper from "$lib/components/molecules/max-width-wrapper.svelte";
|
||||
import { onMount } from "svelte";
|
||||
import CloseIcon from "~icons/mdi/window-close";
|
||||
import ExclamationIcon from "~icons/heroicons/exclamation-triangle-20-solid";
|
||||
import ClockIcon from "~icons/heroicons/clock-20-solid";
|
||||
import SupportIcon from "~icons/heroicons/chat-bubble-left-right-20-solid";
|
||||
|
||||
let canRedirect = $state(false);
|
||||
|
||||
@@ -18,25 +19,90 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="grid min-h-[80vh] w-full place-items-center">
|
||||
<MaxWidthWrapper
|
||||
cls="flex flex-col gap-8 items-center justify-center p-4 md:p-8"
|
||||
>
|
||||
<div
|
||||
class="flex w-full flex-col items-center justify-center gap-8 rounded-xl bg-white p-4 drop-shadow-lg md:p-8 md:py-12 lg:w-max"
|
||||
>
|
||||
<div
|
||||
class="rounded-full bg-rose-100 p-2 text-rose-600 drop-shadow-lg"
|
||||
>
|
||||
<Icon icon={CloseIcon} cls="w-12 h-12" />
|
||||
<div class="flex min-h-screen w-full items-center justify-center bg-gradient-to-br from-red-50 via-orange-50 to-yellow-50 p-4">
|
||||
<div class="w-full max-w-lg">
|
||||
<div class="animate-scale-in rounded-3xl bg-white p-8 shadow-2xl md:p-12">
|
||||
<!-- Error Icon with Animation -->
|
||||
<div class="mb-8 flex justify-center">
|
||||
<div class="animate-pulse-slow rounded-full bg-gradient-to-br from-red-400 to-red-600 p-4 shadow-lg">
|
||||
<Icon icon={ExclamationIcon} cls="h-16 w-16 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Title size="h3" center weight="medium">Session Terminated</Title>
|
||||
<p class="w-full max-w-prose text-center text-gray-600">
|
||||
Unfortunately, your session has been terminated due to inactivity
|
||||
or something went wrong, please contact us to walk through the
|
||||
steps again.
|
||||
<!-- Title -->
|
||||
<Title size="h3" weight="medium" center>Session Terminated</Title>
|
||||
|
||||
<!-- Description -->
|
||||
<p class="mb-8 mt-4 text-center text-base text-gray-600">
|
||||
Your checkout session has been terminated. This may have occurred due to inactivity or a connection issue.
|
||||
</p>
|
||||
|
||||
<!-- Reason Cards -->
|
||||
<div class="mb-8 space-y-3">
|
||||
<div class="flex items-start gap-3 rounded-lg border border-gray-200 bg-gray-50 p-4">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="rounded-lg bg-orange-100 p-2">
|
||||
<Icon icon={ClockIcon} cls="h-5 w-5 text-orange-600" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="mb-1 text-sm font-semibold text-gray-900">
|
||||
Session Timeout
|
||||
</p>
|
||||
<p class="text-sm text-gray-600">
|
||||
Sessions expire after a period of inactivity for security.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-start gap-3 rounded-lg border border-gray-200 bg-gray-50 p-4">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="rounded-lg bg-blue-100 p-2">
|
||||
<Icon icon={SupportIcon} cls="h-5 w-5 text-blue-600" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="mb-1 text-sm font-semibold text-gray-900">
|
||||
Need Help?
|
||||
</p>
|
||||
<p class="text-sm text-gray-600">
|
||||
Contact our support team to continue your order.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</MaxWidthWrapper>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes scale-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-slow {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.animate-scale-in {
|
||||
animation: scale-in 0.5s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
|
||||
.animate-pulse-slow {
|
||||
animation: pulse-slow 2s ease-in-out infinite;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user