375 lines
12 KiB
TypeScript
375 lines
12 KiB
TypeScript
"use client"
|
|
|
|
import { useState, useEffect } from "react"
|
|
import { Button } from "@/components/ui/button"
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/components/ui/card"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import {
|
|
CheckCircle,
|
|
XCircle,
|
|
AlertTriangle,
|
|
RefreshCw,
|
|
ExternalLink,
|
|
Package,
|
|
ShoppingCart,
|
|
CreditCard,
|
|
} from "lucide-react"
|
|
|
|
interface StripeStatus {
|
|
success: boolean
|
|
account?: {
|
|
id: string
|
|
name: string
|
|
email: string
|
|
country: string
|
|
charges_enabled: boolean
|
|
payouts_enabled: boolean
|
|
}
|
|
products?: {
|
|
total: number
|
|
active: number
|
|
sample: Array<{
|
|
id: string
|
|
name: string
|
|
description?: string
|
|
price?: {
|
|
id: string
|
|
unit_amount: number
|
|
currency: string
|
|
}
|
|
}>
|
|
}
|
|
paymentMethods?: {
|
|
total: number
|
|
types: string[]
|
|
}
|
|
recentInvoices: number
|
|
environment: string
|
|
apiVersion: string
|
|
timestamp: string
|
|
error?: string
|
|
}
|
|
|
|
export function StripeStatus() {
|
|
const [status, setStatus] = useState<StripeStatus | null>(null)
|
|
const [loading, setLoading] = useState(true)
|
|
const [lastUpdated, setLastUpdated] = useState<string>("")
|
|
|
|
const fetchStatus = async () => {
|
|
try {
|
|
const response = await fetch("/api/stripe/test", {
|
|
method: "POST",
|
|
cache: "no-store",
|
|
})
|
|
|
|
if (response.ok) {
|
|
const data = await response.json()
|
|
setStatus(data)
|
|
setLastUpdated(new Date().toLocaleTimeString())
|
|
} else {
|
|
setStatus({
|
|
success: false,
|
|
error: "Failed to fetch status",
|
|
timestamp: new Date().toISOString(),
|
|
recentInvoices: 0,
|
|
environment: process.env.NODE_ENV || "development",
|
|
apiVersion: "unknown",
|
|
})
|
|
}
|
|
} catch (error) {
|
|
setStatus({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : "Network error",
|
|
timestamp: new Date().toISOString(),
|
|
recentInvoices: 0,
|
|
environment: process.env.NODE_ENV || "development",
|
|
apiVersion: "unknown",
|
|
})
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
fetchStatus()
|
|
const interval = setInterval(fetchStatus, 30000) // Refresh every 30 seconds
|
|
return () => clearInterval(interval)
|
|
}, [])
|
|
|
|
if (loading) {
|
|
return (
|
|
<Card>
|
|
<CardContent className="p-4 flex items-center justify-center">
|
|
<RefreshCw className="h-4 w-4 animate-spin mr-2" />
|
|
<span className="text-sm">Checking Stripe connection...</span>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
if (!status) {
|
|
return (
|
|
<Card>
|
|
<CardContent className="p-4">
|
|
<div className="flex items-center gap-2 text-red-600">
|
|
<XCircle className="h-4 w-4" />
|
|
<span className="text-sm">Unable to connect to Stripe</span>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
const stripeWorking = status.success
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
{/* Status Overview */}
|
|
<Card className={stripeWorking ? "border-green-200" : "border-red-200"}>
|
|
<CardHeader className="pb-2">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-2">
|
|
{stripeWorking ? (
|
|
<CheckCircle className="h-5 w-5 text-green-600" />
|
|
) : (
|
|
<XCircle className="h-5 w-5 text-red-600" />
|
|
)}
|
|
<CardTitle className="text-lg">
|
|
Stripe {stripeWorking ? "Connected" : "Not Connected"}
|
|
</CardTitle>
|
|
</div>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={fetchStatus}
|
|
className="flex items-center gap-1"
|
|
>
|
|
<RefreshCw
|
|
className={`h-3 w-3 ${loading ? "animate-spin" : ""}`}
|
|
/>
|
|
Refresh
|
|
</Button>
|
|
</div>
|
|
<CardDescription className="text-xs">
|
|
Last updated: {lastUpdated}
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{stripeWorking ? (
|
|
<p className="text-green-700">
|
|
Your Stripe account is connected and ready for products.
|
|
</p>
|
|
) : (
|
|
<div className="space-y-2">
|
|
<p className="text-red-700">
|
|
{status.error ||
|
|
"Unable to connect to Stripe. Please check your configuration."}
|
|
</p>
|
|
<div className="flex items-center gap-2">
|
|
<Badge variant="outline" className="text-yellow-600">
|
|
<AlertTriangle className="h-3 w-3 mr-1" />
|
|
Setup Required
|
|
</Badge>
|
|
<Button size="sm" variant="outline" asChild>
|
|
<a href="https://dashboard.stripe.com/login">
|
|
<ExternalLink className="h-3 w-3 mr-1" />
|
|
Go to Stripe
|
|
</a>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Account Info */}
|
|
{stripeWorking && status.account && (
|
|
<Card>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-base flex items-center gap-2">
|
|
<CreditCard className="h-4 w-4" />
|
|
Account Information
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="pt-0">
|
|
<div className="grid grid-cols-2 gap-4 text-sm">
|
|
<div>
|
|
<span className="text-muted-foreground">Account ID:</span>
|
|
<div className="font-medium">{status.account.id}</div>
|
|
</div>
|
|
<div>
|
|
<span className="text-muted-foreground">Business Name:</span>
|
|
<div className="font-medium">
|
|
{status.account.name || "Not set"}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<span className="text-muted-foreground">Country:</span>
|
|
<div className="font-medium">{status.account.country}</div>
|
|
</div>
|
|
<div>
|
|
<span className="text-muted-foreground">Charges Enabled:</span>
|
|
<div>
|
|
{status.account.charges_enabled ? (
|
|
<Badge variant="default">Yes</Badge>
|
|
) : (
|
|
<Badge variant="destructive">No</Badge>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
{/* Products Status */}
|
|
<Card>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-base flex items-center gap-2">
|
|
<Package className="h-4 w-4" />
|
|
Products Status
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="pt-0">
|
|
{stripeWorking && status.products ? (
|
|
<div className="space-y-3">
|
|
<div className="flex items-center gap-2">
|
|
<Badge variant="default">
|
|
{status.products.total} total products
|
|
</Badge>
|
|
<Badge variant="outline">{status.products.active} active</Badge>
|
|
</div>
|
|
|
|
{status.products.sample.length > 0 && (
|
|
<div className="space-y-2">
|
|
<p className="text-xs text-muted-foreground">
|
|
Recent products:
|
|
</p>
|
|
{status.products.sample.map((product) => (
|
|
<div
|
|
key={product.id}
|
|
className="flex items-center justify-between text-sm p-2 bg-muted rounded"
|
|
>
|
|
<div>
|
|
<div className="font-medium">{product.name}</div>
|
|
{product.price && (
|
|
<div className="text-xs text-muted-foreground">
|
|
${(product.price.unit_amount / 100).toFixed(2)}{" "}
|
|
{product.price.currency.toUpperCase()}
|
|
</div>
|
|
)}
|
|
</div>
|
|
{product.price ? (
|
|
<Badge variant="outline">Available</Badge>
|
|
) : (
|
|
<Badge variant="destructive">No Price</Badge>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{status.products.total === 0 && (
|
|
<div className="text-center py-4">
|
|
<Package className="h-8 w-8 text-muted-foreground mx-auto mb-2" />
|
|
<p className="text-sm text-muted-foreground">
|
|
No products found. Add some products in Stripe Dashboard.
|
|
</p>
|
|
<Button size="sm" className="mt-2" asChild>
|
|
<a href="https://dashboard.stripe.com/products/new">
|
|
<ExternalLink className="h-3 w-3 mr-1" />
|
|
Add Products
|
|
</a>
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
) : !stripeWorking ? (
|
|
<div className="text-center py-4">
|
|
<AlertTriangle className="h-8 w-8 text-yellow-600 mx-auto mb-2" />
|
|
<p className="text-sm text-muted-foreground">
|
|
Connect to Stripe to view product status
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<div className="text-center py-4">
|
|
<RefreshCw className="h-6 w-6 animate-spin text-muted-foreground mx-auto mb-2" />
|
|
<p className="text-sm text-muted-foreground">
|
|
Loading products...
|
|
</p>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Payment Methods */}
|
|
{stripeWorking && status.paymentMethods && (
|
|
<Card>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-base flex items-center gap-2">
|
|
<CreditCard className="h-4 w-4" />
|
|
Payment Methods
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="pt-0">
|
|
<div className="flex items-center gap-2">
|
|
<Badge variant="default">
|
|
{status.paymentMethods.total} methods available
|
|
</Badge>
|
|
<div className="flex gap-1">
|
|
{status.paymentMethods.types.map((type) => (
|
|
<Badge key={type} variant="outline" className="text-xs">
|
|
{type}
|
|
</Badge>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
{/* Quick Actions */}
|
|
{stripeWorking && (
|
|
<Card>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-base">Quick Actions</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="pt-0">
|
|
<div className="flex gap-2 flex-wrap">
|
|
<Button size="sm" variant="outline" asChild>
|
|
<a href="https://dashboard.stripe.com/products/new">
|
|
<Package className="h-3 w-3 mr-1" />
|
|
Add Products
|
|
</a>
|
|
</Button>
|
|
<Button size="sm" variant="outline" asChild>
|
|
<a href="/products">
|
|
<ShoppingCart className="h-3 w-3 mr-1" />
|
|
View Store
|
|
</a>
|
|
</Button>
|
|
<Button size="sm" variant="outline" asChild>
|
|
<a href="/admin">
|
|
<CreditCard className="h-3 w-3 mr-1" />
|
|
Admin Panel
|
|
</a>
|
|
</Button>
|
|
<Button size="sm" variant="outline" asChild>
|
|
<a href="/stripe-setup">
|
|
<RefreshCw className="h-3 w-3 mr-1" />
|
|
Setup Guide
|
|
</a>
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|