Skip to content
Closed

10y #15919

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 76 additions & 60 deletions app/[locale]/10years/_components/CountDown/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,18 @@ interface CountDownProps {
className?: string
timeLeftLabels: TimeLeftLabels
expiredLabel: string
dateTime: string
hideZeroUnits?: boolean
onExpired?: () => void
}

const CountDown = ({
className,
timeLeftLabels,
expiredLabel,
dateTime,
hideZeroUnits = false,
onExpired,
}: CountDownProps) => {
const [timeLeft, setTimeLeft] = useState({
days: 0,
Expand All @@ -26,7 +32,7 @@ const CountDown = ({
const [isExpired, setIsExpired] = useState(false)

useEffect(() => {
const targetDate = new Date("2025-07-30T15:44:00Z")
const targetDate = new Date(dateTime)

const calculateTimeLeft = () => {
const now = new Date()
Expand All @@ -41,7 +47,10 @@ const CountDown = ({
seconds: Math.floor((difference / 1000) % 60),
})
} else {
setIsExpired(true)
if (!isExpired) {
setIsExpired(true)
onExpired?.()
}
}
}

Expand All @@ -51,74 +60,81 @@ const CountDown = ({
const timer = setInterval(calculateTimeLeft, 1000)

return () => clearInterval(timer)
}, [])
}, [isExpired, onExpired, dateTime])

if (isExpired) {
return <div className="text-center text-2xl font-bold">{expiredLabel}</div>
}

return (
<div className="flex items-center justify-center gap-10">
<div
className={cn(
"flex h-20 w-20 flex-col items-center justify-center rounded-2xl border text-center [box-shadow:-2.372px_2.372px_14.234px_1.186px_rgba(52,43,64,0.02),-18.979px_18.979px_14.234px_-3.559px_rgba(52,43,64,0.02),-37.958px_37.958px_28.469px_-7.117px_rgba(52,43,64,0.02),-47.448px_47.448px_47.448px_-14.234px_rgba(88,55,131,0.04)] dark:bg-[#171717]",
className
)}
>
<div className="font-mono text-4xl font-bold text-accent-a">
{timeLeft.days}
<div className="flex items-center justify-center gap-10 text-accent-a">
{(!hideZeroUnits || timeLeft.days > 0) && (
<div
className={cn(
"flex h-20 w-20 flex-col items-center justify-center rounded-2xl border text-center [box-shadow:-2.372px_2.372px_14.234px_1.186px_rgba(52,43,64,0.02),-18.979px_18.979px_14.234px_-3.559px_rgba(52,43,64,0.02),-37.958px_37.958px_28.469px_-7.117px_rgba(52,43,64,0.02),-47.448px_47.448px_47.448px_-14.234px_rgba(88,55,131,0.04)] dark:bg-[#171717]",
className
)}
>
<div className="font-mono text-4xl font-bold">{timeLeft.days}</div>
<div className="font-mono text-xs">
{timeLeft.days === 1
? timeLeftLabels.days.singular
: timeLeftLabels.days.plural}
</div>
</div>
<div className="font-mono text-xs text-accent-a">
{timeLeft.days === 1
? timeLeftLabels.days.singular
: timeLeftLabels.days.plural}
)}
{(!hideZeroUnits || timeLeft.days > 0 || timeLeft.hours > 0) && (
<div
className={cn(
"flex h-20 w-20 flex-col items-center justify-center rounded-2xl border text-center [box-shadow:-2.372px_2.372px_14.234px_1.186px_rgba(52,43,64,0.02),-18.979px_18.979px_14.234px_-3.559px_rgba(52,43,64,0.02),-37.958px_37.958px_28.469px_-7.117px_rgba(52,43,64,0.02),-47.448px_47.448px_47.448px_-14.234px_rgba(88,55,131,0.04)] dark:bg-[#171717]",
className
)}
>
<div className="font-mono text-4xl font-bold">{timeLeft.hours}</div>
<div className="font-mono text-xs">
{timeLeft.hours === 1
? timeLeftLabels.hours.singular
: timeLeftLabels.hours.plural}
</div>
</div>
</div>
<div
className={cn(
"flex h-20 w-20 flex-col items-center justify-center rounded-2xl border text-center [box-shadow:-2.372px_2.372px_14.234px_1.186px_rgba(52,43,64,0.02),-18.979px_18.979px_14.234px_-3.559px_rgba(52,43,64,0.02),-37.958px_37.958px_28.469px_-7.117px_rgba(52,43,64,0.02),-47.448px_47.448px_47.448px_-14.234px_rgba(88,55,131,0.04)] dark:bg-[#171717]",
className
)}
>
<div className="font-mono text-4xl font-bold text-accent-a">
{timeLeft.hours}
)}
{(!hideZeroUnits ||
timeLeft.days > 0 ||
timeLeft.hours > 0 ||
timeLeft.minutes > 0) && (
<div
className={cn(
"flex h-20 w-20 flex-col items-center justify-center rounded-2xl border text-center [box-shadow:-2.372px_2.372px_14.234px_1.186px_rgba(52,43,64,0.02),-18.979px_18.979px_14.234px_-3.559px_rgba(52,43,64,0.02),-37.958px_37.958px_28.469px_-7.117px_rgba(52,43,64,0.02),-47.448px_47.448px_47.448px_-14.234px_rgba(88,55,131,0.04)] dark:bg-[#171717]",
className
)}
>
<div className="font-mono text-4xl font-bold">{timeLeft.minutes}</div>
<div className="font-mono text-xs">
{timeLeft.minutes === 1
? timeLeftLabels.minutes.singular
: timeLeftLabels.minutes.plural}
</div>
</div>
<div className="font-mono text-xs text-accent-a">
{timeLeft.hours === 1
? timeLeftLabels.hours.singular
: timeLeftLabels.hours.plural}
)}
{(!hideZeroUnits ||
timeLeft.days > 0 ||
timeLeft.hours > 0 ||
timeLeft.minutes > 0 ||
timeLeft.seconds > 0) && (
<div
className={cn(
"hidden h-20 w-20 flex-col items-center justify-center rounded-2xl border text-center [box-shadow:-2.372px_2.372px_14.234px_1.186px_rgba(52,43,64,0.02),-18.979px_18.979px_14.234px_-3.559px_rgba(52,43,64,0.02),-37.958px_37.958px_28.469px_-7.117px_rgba(52,43,64,0.02),-47.448px_47.448px_47.448px_-14.234px_rgba(88,55,131,0.04)] lg:flex dark:bg-[#171717]",
className
)}
>
<div className="font-mono text-4xl font-bold">{timeLeft.seconds}</div>
<div className="font-mono text-xs">
{timeLeft.seconds === 1
? timeLeftLabels.seconds.singular
: timeLeftLabels.seconds.plural}
</div>
</div>
</div>
<div
className={cn(
"flex h-20 w-20 flex-col items-center justify-center rounded-2xl border text-center [box-shadow:-2.372px_2.372px_14.234px_1.186px_rgba(52,43,64,0.02),-18.979px_18.979px_14.234px_-3.559px_rgba(52,43,64,0.02),-37.958px_37.958px_28.469px_-7.117px_rgba(52,43,64,0.02),-47.448px_47.448px_47.448px_-14.234px_rgba(88,55,131,0.04)] dark:bg-[#171717]",
className
)}
>
<div className="font-mono text-4xl font-bold text-accent-a">
{timeLeft.minutes}
</div>
<div className="font-mono text-xs text-accent-a">
{timeLeft.minutes === 1
? timeLeftLabels.minutes.singular
: timeLeftLabels.minutes.plural}
</div>
</div>
<div
className={cn(
"hidden h-20 w-20 flex-col items-center justify-center rounded-2xl border text-center [box-shadow:-2.372px_2.372px_14.234px_1.186px_rgba(52,43,64,0.02),-18.979px_18.979px_14.234px_-3.559px_rgba(52,43,64,0.02),-37.958px_37.958px_28.469px_-7.117px_rgba(52,43,64,0.02),-47.448px_47.448px_47.448px_-14.234px_rgba(88,55,131,0.04)] lg:flex dark:bg-[#171717]",
className
)}
>
<div className="font-mono text-4xl font-bold text-accent-a">
{timeLeft.seconds}
</div>
<div className="font-mono text-xs text-accent-a">
{timeLeft.seconds === 1
? timeLeftLabels.seconds.singular
: timeLeftLabels.seconds.plural}
</div>
</div>
)}
</div>
)
}
Expand Down
77 changes: 77 additions & 0 deletions app/[locale]/10years/_components/NFTMintCard/Connected.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Address } from "viem"
import { useDisconnect, useSwitchChain } from "wagmi"

import { Avatar } from "@/components/ui/avatar"
import { Button } from "@/components/ui/buttons/Button"

import { useNetworkContract } from "@/hooks/useNetworkContract"
import {
formatAddress,
getAddressEtherscanUrl,
getBlockieImage,
} from "@/lib/torch"

export default function Connected({
address,
ensName,
}: {
address: Address
ensName?: string | null
}) {
const { disconnect } = useDisconnect()
const { isSupportedNetwork, networkName, chainId } = useNetworkContract()
const { switchChain, isPending } = useSwitchChain()

const handleSwitchNetwork = () => {
switchChain({ chainId })
}

return (
<div className="flex flex-col items-center justify-between space-y-2">
{/* Wallet Info */}
<div className="flex items-center gap-4">
<Avatar
className="h-6 w-6 !shadow-none"
src={getBlockieImage(address)}
href={getAddressEtherscanUrl(address)}
name={ensName || formatAddress(address)}
/>
<div>{ensName || formatAddress(address)}</div>
</div>

{/* Network Status */}
<div className="text-center">
{isSupportedNetwork ? (
<div className="flex items-center gap-2 text-sm text-success">
<div className="h-2 w-2 rounded-full bg-success"></div>
<span>Connected to {networkName}</span>
</div>
) : (
<div className="flex flex-col items-center gap-2">
<div className="flex items-center gap-2 text-sm text-error">
<div className="h-2 w-2 rounded-full bg-error"></div>
<span>Unsupported Network</span>
</div>
<Button
size="sm"
onClick={handleSwitchNetwork}
disabled={isPending}
>
{isPending ? "Switching..." : `Switch to ${networkName}`}
</Button>
</div>
)}
</div>

{/* Disconnect Button */}
<Button
variant="link"
size="sm"
onClick={() => disconnect()}
className="text-body-medium"
>
disconnect
</Button>
</div>
)
}
14 changes: 14 additions & 0 deletions app/[locale]/10years/_components/NFTMintCard/Connection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useAccount } from "wagmi"

import MintConnect from "./views/MintConnect"
import Prechecks from "./Prechecks"

export default function Connection() {
const { address, isConnected } = useAccount()

if (!isConnected || !address) {
return <MintConnect />
}

return <Prechecks address={address} />
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Info } from "lucide-react"

import {
Alert,
AlertContent,
AlertDescription,
AlertTitle,
} from "@/components/ui/alert"

const GasFeeInformation = () => {
return (
<Alert variant="warning" className="w-full rounded-none border-none">
<Info className="h-5 w-5 !text-yellow-600" />
<AlertContent>
<AlertTitle className="!text-yellow-800">About Network Fees</AlertTitle>
<AlertDescription className="text-sm text-yellow-700">
While the NFT is free, you&apos;ll need to pay Ethereum network fees
to complete the transaction. Network fees vary throughout the day -
consider waiting for lower network fees periods to save on costs.
</AlertDescription>
</AlertContent>
</Alert>
)
}

export default GasFeeInformation
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"use client"

import { cn } from "@/lib/utils/cn"

import { type GasPriceLevel, useGasPrice } from "@/hooks/useGasPrice"

const getGasLevelConfig = (level: GasPriceLevel | null) => {
switch (level) {
case "low":
return {
color: "text-green-600",
bgColor: "bg-green-100",
indicator: "bg-green-500",
label: "Low",
description: "Good time to mint!",
}
case "moderate":
return {
color: "text-yellow-600",
bgColor: "bg-yellow-100",
indicator: "bg-yellow-500",
label: "Moderate",
description: "Normal gas prices",
}
case "high":
return {
color: "text-orange-600",
bgColor: "bg-orange-100",
indicator: "bg-orange-500",
label: "High",
description: "Consider waiting for lower gas",
}
case "very_high":
return {
color: "text-red-600",
bgColor: "bg-red-100",
indicator: "bg-red-500",
label: "Very High",
description: "Wait for lower gas prices",
}
default:
return {
color: "text-gray-600",
bgColor: "bg-gray-100",
indicator: "bg-gray-400",
label: "Loading",
description: "Fetching gas prices...",
}
}
}

interface GasPriceDisplayProps {
className?: string
}

const GasPriceDisplay = ({ className }: GasPriceDisplayProps) => {
const { error, gasLevel } = useGasPrice()

const loading = false

const config = getGasLevelConfig(gasLevel)

if (error) {
return (
<div className={cn("text-center text-sm text-gray-500", className)}>
<p>Unable to fetch network fees</p>
</div>
)
}

return (
<div className={cn("space-y-3", className)}>
{/* Gas Price Display */}
<div className="flex items-center justify-between rounded-lg border p-3 text-sm">
<div className="flex items-center gap-2">
<div className={cn("h-2 w-2 rounded-full", config.indicator)} />
<span className="font-medium">Current Network Fee:</span>
</div>
<div className="text-right">
{loading ? (
<div className="h-4 w-16 animate-pulse rounded bg-gray-200" />
) : (
<div className="flex items-center gap-1">
<span className={cn("font-semibold", config.color)}>
{config.label}
</span>
</div>
)}
</div>
</div>
</div>
)
}

export default GasPriceDisplay
Loading