202 lines
6.1 KiB
TypeScript
202 lines
6.1 KiB
TypeScript
"use client";
|
|
|
|
import { getAccessToken_client } from "@/utils/apiHelper_client";
|
|
import React, { useEffect, useMemo, useState } from "react";
|
|
|
|
type Store = {
|
|
description?: string;
|
|
lastOpenedTime?: string;
|
|
logo?: { url?: string };
|
|
name?: string;
|
|
url?: string;
|
|
urlPath?: string;
|
|
};
|
|
|
|
type Payload = {
|
|
userid?: string;
|
|
message?: string;
|
|
store?: Store;
|
|
storeId?: string;
|
|
};
|
|
|
|
function tryParseData(raw: string | null): Payload | null {
|
|
if (!raw) return null;
|
|
try {
|
|
return JSON.parse(decodeURIComponent(raw));
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function formatDate(iso?: string) {
|
|
if (!iso) return "—";
|
|
const d = new Date(iso);
|
|
return isNaN(d.getTime()) ? "—" : d.toLocaleString();
|
|
}
|
|
|
|
const read = async (r: Response) =>
|
|
r.headers.get("content-type")?.includes("application/json")
|
|
? r.json()
|
|
: r.text();
|
|
|
|
export default function AuthCompleteClient() {
|
|
const [payload, setPayload] = useState<Payload | null>(null);
|
|
|
|
useEffect(() => {
|
|
const url = new URL(window.location.href);
|
|
const hash = window.location.hash;
|
|
const hashVal = hash.startsWith("#data=") ? hash.slice(6) : null;
|
|
const hashPayload = tryParseData(hashVal);
|
|
const qpPayload = tryParseData(url.searchParams.get("data"));
|
|
const storeIdOnly = url.searchParams.get("storeId");
|
|
|
|
setPayload(
|
|
hashPayload || qpPayload || (storeIdOnly ? { storeId: storeIdOnly } : null)
|
|
);
|
|
}, []);
|
|
|
|
const { title, subtitle, logoUrl, desc, link, lastOpen } = useMemo(() => {
|
|
const store = payload?.store ?? {};
|
|
const id = payload?.storeId || store.urlPath || "—";
|
|
return {
|
|
title:
|
|
payload?.message ||
|
|
(store.name
|
|
? `eBay connected! Tokens saved for Store ${store.urlPath}.`
|
|
: `eBay connected! Store ${id}`),
|
|
subtitle: store.name || id,
|
|
logoUrl: store.logo?.url,
|
|
desc: store.description,
|
|
link: store.url,
|
|
lastOpen: formatDate(store.lastOpenedTime),
|
|
};
|
|
}, [payload]);
|
|
|
|
useEffect(() => {
|
|
if (payload) {
|
|
(async () => {
|
|
const accessToken = await getAccessToken_client();
|
|
payload.userid = sessionStorage.getItem("USERID") || undefined;
|
|
if (!payload.userid) return;
|
|
|
|
const saveRes = await fetch(
|
|
"https://ebay.backend.data4autos.com/api/motorstate/auth/ebay/store/save",
|
|
{
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(payload),
|
|
}
|
|
);
|
|
|
|
const saveData = await read(saveRes);
|
|
if (!saveRes.ok) {
|
|
console.error("Save failed:", saveData);
|
|
}
|
|
})();
|
|
}
|
|
}, [payload]);
|
|
|
|
return (
|
|
<div className="min-h-[83vh] flex flex-col items-center justify-center bg-gradient-to-br from-[#00d1ff]/10 via-white to-[#00d1ff]/20 text-gray-800 px-6 py-12">
|
|
{/* 🎉 Confetti Animation */}
|
|
<Confetti />
|
|
|
|
{/* Card */}
|
|
<div className="w-full max-w-2xl bg-white shadow-xl border border-[#00d1ff]/30 rounded-3xl p-8 text-center relative">
|
|
<div className="absolute top-0 left-0 w-full h-2 bg-[#00d1ff]" />
|
|
|
|
<div className="inline-flex items-center gap-2 bg-[#00d1ff]/10 border border-[#00d1ff]/30 text-[#00d1ff] px-4 py-1 rounded-full mb-6 font-medium">
|
|
✅ Connected
|
|
</div>
|
|
|
|
<h1 className="text-2xl md:text-3xl font-bold text-[#00d1ff] mb-4">
|
|
{title || "eBay connected successfully!"} 🎉
|
|
</h1>
|
|
|
|
<div className="flex items-center justify-center gap-4 mb-6">
|
|
{logoUrl ? (
|
|
<img
|
|
src={logoUrl}
|
|
alt={subtitle || "Store Logo"}
|
|
className="w-20 h-20 rounded-xl border border-[#00d1ff]/30 object-cover"
|
|
/>
|
|
) : (
|
|
<div className="w-20 h-20 rounded-xl bg-[#00d1ff]/10 flex items-center justify-center text-2xl font-bold text-[#00d1ff] border border-[#00d1ff]/20">
|
|
{subtitle?.[0] || "S"}
|
|
</div>
|
|
)}
|
|
<div className="text-left">
|
|
<div className="text-lg font-semibold text-gray-700">{subtitle}</div>
|
|
<div className="text-sm text-gray-500">
|
|
Last opened: <strong>{lastOpen}</strong>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{desc && <p className="text-gray-600 mb-6">{desc}</p>}
|
|
|
|
<div className="flex flex-wrap justify-center gap-3">
|
|
{link && (
|
|
<a
|
|
href={link}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="bg-[#00d1ff] hover:bg-[#00b0e6] text-white px-5 py-2 rounded-lg font-medium transition"
|
|
>
|
|
Visit eBay Store
|
|
</a>
|
|
)}
|
|
<a
|
|
href="/"
|
|
className="border border-[#00d1ff] text-[#00d1ff] hover:bg-[#00d1ff]/10 px-5 py-2 rounded-lg font-medium transition"
|
|
>
|
|
Go to Dashboard
|
|
</a>
|
|
</div>
|
|
|
|
{!payload && (
|
|
<p className="text-gray-500 text-sm mt-6">
|
|
Waiting for data… redirect with <code>?data=...</code>
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/* ===== Tiny Confetti ===== */
|
|
const Confetti = () => {
|
|
const pieces = Array.from({ length: 24 });
|
|
return (
|
|
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
|
<style>{`
|
|
@keyframes drop {
|
|
0% { transform: translateY(-10vh) rotate(0deg); opacity: 0; }
|
|
10% { opacity: 1; }
|
|
100% { transform: translateY(110vh) rotate(720deg); opacity: 0; }
|
|
}
|
|
`}</style>
|
|
{pieces.map((_, i) => {
|
|
const left = Math.random() * 100;
|
|
const size = 6 + Math.random() * 8;
|
|
const duration = 2.8 + Math.random() * 1.6;
|
|
const delay = Math.random() * 0.8;
|
|
const color = ["#00d1ff", "#ff69b4", "#16a34a"][Math.floor(Math.random() * 3)];
|
|
return (
|
|
<span
|
|
key={i}
|
|
className="absolute top-[-10vh] rounded-full"
|
|
style={{
|
|
left: `${left}vw`,
|
|
width: size,
|
|
height: size,
|
|
backgroundColor: color,
|
|
animation: `drop ${duration}s ease-in ${delay}s both`,
|
|
}}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
};
|