ui refactor: yuhhhhhh 80% done
This commit is contained in:
@@ -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