diff --git a/src/components/ui/hover-border-gradient.tsx b/src/components/ui/hover-border-gradient.tsx new file mode 100644 index 0000000..1213d59 --- /dev/null +++ b/src/components/ui/hover-border-gradient.tsx @@ -0,0 +1,93 @@ +'use client'; +import React, { useState, useEffect } from 'react'; +import { motion } from 'framer-motion'; +import { cn } from '@/lib/utils'; + +type Direction = 'TOP' | 'LEFT' | 'BOTTOM' | 'RIGHT'; + +export function HoverBorderGradient({ + children, + containerClassName, + className, + as: Tag = 'button', + duration = 1, + clockwise = true, + ...props +}: React.PropsWithChildren< + { + as?: React.ElementType; + containerClassName?: string; + className?: string; + duration?: number; + clockwise?: boolean; + } & React.HTMLAttributes +>) { + const [hovered, setHovered] = useState(false); + const [direction, setDirection] = useState('TOP'); + + const rotateDirection = (currentDirection: Direction): Direction => { + const directions: Direction[] = ['TOP', 'LEFT', 'BOTTOM', 'RIGHT']; + const currentIndex = directions.indexOf(currentDirection); + const nextIndex = clockwise + ? (currentIndex - 1 + directions.length) % directions.length + : (currentIndex + 1) % directions.length; + return directions[nextIndex]; + }; + + const movingMap: Record = { + TOP: 'radial-gradient(20.7% 50% at 50% 0%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)', + LEFT: 'radial-gradient(16.6% 43.1% at 0% 50%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)', + BOTTOM: + 'radial-gradient(20.7% 50% at 50% 100%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)', + RIGHT: + 'radial-gradient(16.2% 41.2% at 100% 50%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)', + }; + + const highlight = + 'radial-gradient(75% 181.16% at 50% 50%, #3275F8 0%, rgba(255, 255, 255, 0) 100%)'; + + useEffect(() => { + if (!hovered) { + const interval = setInterval(() => { + setDirection((prevState) => rotateDirection(prevState)); + }, duration * 1000); + return () => clearInterval(interval); + } + }, [hovered, duration]); + + return ( + setHovered(true)} + onMouseLeave={() => setHovered(false)} + className={cn( + 'relative flex content-center items-center justify-center overflow-visible rounded-xl border-none bg-black/20 p-px transition duration-500 hover:bg-black/10', + containerClassName, + )} + {...props} + > +
+ {children} +
+ +
+ + ); +} diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index cbccda3..fd10941 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -10,6 +10,7 @@ import { balanceApi } from '../api/balance'; import { wheelApi } from '../api/wheel'; import Onboarding, { useOnboarding } from '../components/Onboarding'; import PromoOffersSection from '../components/PromoOffersSection'; +import { HoverBorderGradient } from '../components/ui/hover-border-gradient'; import { useCurrency } from '../hooks/useCurrency'; import { API } from '../config/constants'; @@ -411,13 +412,14 @@ export default function Dashboard() { {t('dashboard.viewSubscription')} {subscription.subscription_url && ( - + )}
@@ -590,30 +592,30 @@ export default function Dashboard() { {trialInfo.requires_payment && trialInfo.price_kopeks > 0 ? ( (balanceData?.balance_kopeks || 0) >= trialInfo.price_kopeks ? ( - + ) : ( {t('subscription.trial.topUpToActivate', 'Top Up Balance')} ) ) : ( - + )} diff --git a/src/pages/Subscription.tsx b/src/pages/Subscription.tsx index 7756531..4395da6 100644 --- a/src/pages/Subscription.tsx +++ b/src/pages/Subscription.tsx @@ -5,6 +5,7 @@ import { useLocation, useNavigate } from 'react-router'; import { AxiosError } from 'axios'; import { subscriptionApi } from '../api/subscription'; import { promoApi } from '../api/promo'; +import { HoverBorderGradient } from '../components/ui/hover-border-gradient'; import type { PurchaseSelection, PeriodOption, @@ -756,9 +757,10 @@ export default function Subscription() { {/* Get Config Button */} - + {/* Subscription URL - hidden when hide_subscription_link is true */} {!subscription.hide_subscription_link && (