diff --git a/frontend/package.json b/frontend/package.json index bc33047..ee282f3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,7 +19,6 @@ "@stellar/freighter-api": "^1.7.1", "@walletconnect/sign-client": "^2.23.9", "@walletconnect/types": "^2.23.8", - "boring-avatars": "^2.0.4", "canvas-confetti": "^1.9.4", "confetti": "^3.0.4", "event-source-polyfill": "^1.0.31", diff --git a/frontend/src/components/MerchantProfileCard.tsx b/frontend/src/components/MerchantProfileCard.tsx index bd2bab1..47cba4b 100644 --- a/frontend/src/components/MerchantProfileCard.tsx +++ b/frontend/src/components/MerchantProfileCard.tsx @@ -1,6 +1,6 @@ "use client"; -import Avatar from "boring-avatars"; +import { Avatar } from "@/components/ui/Avatar"; import Link from "next/link"; import { useMerchantMetadata, @@ -23,7 +23,8 @@ export default function MerchantProfileCard() { // If no merchant data, show anonymous profile const displayName = merchant?.business_name || merchant?.email || "Merchant"; const email = merchant?.email || ""; - const avatarSeed = email || "anonymous"; + const avatarName = merchant?.business_name || merchant?.email || "Merchant"; + const logoUrl = merchant?.logo_url || null; const handleLogout = () => { logout(); @@ -41,10 +42,8 @@ export default function MerchantProfileCard() { >

@@ -82,16 +81,8 @@ export default function MerchantProfileCard() {

diff --git a/frontend/src/components/ui/Avatar.tsx b/frontend/src/components/ui/Avatar.tsx new file mode 100644 index 0000000..d9b7ff2 --- /dev/null +++ b/frontend/src/components/ui/Avatar.tsx @@ -0,0 +1,100 @@ +"use client"; + +import React from "react"; +import Image from "next/image"; + +interface AvatarProps { + name: string; + src?: string | null; + size?: number; + className?: string; +} + +export function Avatar({ name, src, size = 40, className = "" }: AvatarProps) { + const getInitials = (str: string) => { + const parts = str.split(" ").filter(Boolean); + if (parts.length === 0) return "?"; + if (parts.length === 1) { + const s = parts[0]; + if (s.length >= 2) return s.substring(0, 2).toUpperCase(); + return s.toUpperCase(); + } + return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase(); + }; + + const getColor = (str: string) => { + const colors = [ + { bg: "hsl(210, 80%, 60%)", text: "hsl(210, 80%, 98%)" }, + { bg: "hsl(160, 70%, 45%)", text: "hsl(160, 70%, 98%)" }, + { bg: "hsl(260, 70%, 65%)", text: "hsl(260, 70%, 98%)" }, + { bg: "hsl(340, 75%, 60%)", text: "hsl(340, 75%, 98%)" }, + { bg: "hsl(200, 80%, 50%)", text: "hsl(200, 80%, 98%)" }, + { bg: "hsl(280, 65%, 55%)", text: "hsl(280, 65%, 98%)" }, + { bg: "hsl(180, 75%, 40%)", text: "hsl(180, 75%, 98%)" }, + { bg: "hsl(230, 70%, 60%)", text: "hsl(230, 70%, 98%)" }, + ]; + + let hash = 0; + const s = str || "default"; + for (let i = 0; i < s.length; i++) { + hash = s.charCodeAt(i) + ((hash << 5) - hash); + } + const index = Math.abs(hash) % colors.length; + return colors[index]; + }; + + const initials = getInitials(name); + const color = getColor(name); + + const containerStyle: React.CSSProperties = { + width: `${size}px`, + height: `${size}px`, + minWidth: `${size}px`, + minHeight: `${size}px`, + position: "relative", + display: "flex", + alignItems: "center", + justifyContent: "center", + borderRadius: "9999px", + overflow: "hidden", + flexShrink: 0, + }; + + if (src) { + return ( +

+ {name} +
+ ); + } + + return ( +
+ {initials} + +
+
+ +
+
+ ); +} diff --git a/frontend/src/lib/framer-motion-shim.tsx b/frontend/src/lib/framer-motion-shim.tsx index 69cd06f..2ff9f5b 100644 --- a/frontend/src/lib/framer-motion-shim.tsx +++ b/frontend/src/lib/framer-motion-shim.tsx @@ -5,9 +5,45 @@ type MotionProps = { [key: string]: unknown; }; +const MOTION_PROPS = [ + "initial", + "animate", + "exit", + "transition", + "viewport", + "whileInView", + "whileHover", + "whileTap", + "whileFocus", + "whileDrag", + "variants", + "layout", + "layoutId", + "onAnimationStart", + "onAnimationComplete", + "onUpdate", + "custom", + "drag", + "dragConstraints", + "dragElastic", + "dragMomentum", + "dragListener", + "dragControls", + "onDragStart", + "onDragEnd", + "onDrag", + "onDirectionLock", + "onDragTransitionEnd", +]; + function createMotionComponent(tag: string) { return function MotionShim({ children, ...props }: MotionProps) { - return createElement(tag, props, children); + const cleanProps = { ...props }; + MOTION_PROPS.forEach((prop) => { + delete cleanProps[prop]; + }); + + return createElement(tag, cleanProps, children); }; } diff --git a/frontend/src/lib/merchant-store.ts b/frontend/src/lib/merchant-store.ts index cd924e8..53f15af 100644 --- a/frontend/src/lib/merchant-store.ts +++ b/frontend/src/lib/merchant-store.ts @@ -27,6 +27,7 @@ export interface MerchantMetadata { notification_email: string; api_key: string; webhook_secret: string; + logo_url?: string | null; branding_config?: { primary_color?: string; secondary_color?: string;