Function bodies 94 total
FinalizeResultsPage function · typescript · L13-L86 (74 LOC)app/admin/cups/[id]/finalize/page.tsx
export default function FinalizeResultsPage() {
const { id } = useParams<{ id: string }>()
const [cup, setCup] = useState<Cup | null>(null)
const [participants, setParticipants] = useState<CupParticipantWithProfile[]>([])
const [loading, setLoading] = useState(true)
useEffect(() => {
const load = async () => {
try {
const [cupRes, rankRes] = await Promise.all([
fetch(`/api/cups/${id}`),
fetch(`/api/cups/${id}/ranking`),
])
if (cupRes.ok) setCup((await cupRes.json()).data)
if (rankRes.ok) setParticipants((await rankRes.json()).data ?? [])
} catch {
toast.error('読み込みに失敗しました')
} finally {
setLoading(false)
}
}
load()
}, [id])
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
)
}
return (
<div className="min-h-screen bg-backgAdminCupDetailPage function · typescript · L32-L227 (196 LOC)app/admin/cups/[id]/page.tsx
export default function AdminCupDetailPage() {
const { id } = useParams<{ id: string }>()
const router = useRouter()
const [cup, setCup] = useState<Cup | null>(null)
const [participants, setParticipants] = useState<CupParticipantWithProfile[]>([])
const [loading, setLoading] = useState(true)
useEffect(() => {
const load = async () => {
try {
const [cupRes, rankRes] = await Promise.all([
fetch(`/api/cups/${id}`),
fetch(`/api/cups/${id}/ranking`),
])
if (cupRes.ok) setCup((await cupRes.json()).data)
if (rankRes.ok) setParticipants((await rankRes.json()).data ?? [])
} catch {
toast.error('読み込みに失敗しました')
} finally {
setLoading(false)
}
}
load()
}, [id])
const handleDisqualify = async (participantId: string, userId: string) => {
if (!confirm('この参加者を失格にしますか?')) return
const supabase = createClient()
const { error } = await supabase
.from('cup_participantNewCupContent function · typescript · L10-L42 (33 LOC)app/admin/cups/new/page.tsx
function NewCupContent() {
const searchParams = useSearchParams()
const editId = searchParams.get('edit')
const [cup, setCup] = useState<Partial<Cup> | undefined>(undefined)
const [loading, setLoading] = useState(!!editId)
useEffect(() => {
if (!editId) return
fetch(`/api/cups/${editId}`)
.then((r) => r.json())
.then(({ data }) => setCup(data))
.catch(() => setCup(undefined))
.finally(() => setLoading(false))
}, [editId])
return (
<main className="container mx-auto px-4 py-8 max-w-xl">
<h1 className="text-2xl font-bold mb-6">
{editId ? '大会を編集' : '新しい大会を作成'}
</h1>
{loading ? (
<div className="flex justify-center py-12">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
) : (
<CupForm mode={editId ? 'edit' : 'create'} cup={cup} />
)}
</main>
)
}NewCupPage function · typescript · L44-L68 (25 LOC)app/admin/cups/new/page.tsx
export default function NewCupPage() {
return (
<div className="min-h-screen bg-background">
<header className="border-b border-border/40">
<div className="container mx-auto flex h-16 items-center px-4">
<Link
href="/admin/cups"
className="flex items-center gap-2 text-muted-foreground hover:text-foreground text-sm"
>
<ArrowLeft className="h-4 w-4" />
大会一覧に戻る
</Link>
</div>
</header>
<Suspense fallback={
<div className="flex justify-center py-12">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
}>
<NewCupContent />
</Suspense>
</div>
)
}AdminCupsPage function · typescript · L27-L147 (121 LOC)app/admin/cups/page.tsx
export default function AdminCupsPage() {
const router = useRouter()
const [cups, setCups] = useState<Cup[]>([])
const [loading, setLoading] = useState(true)
useEffect(() => {
const load = async () => {
try {
const res = await fetch('/api/cups')
if (!res.ok) throw new Error('取得失敗')
const { data } = await res.json()
setCups(data ?? [])
} catch {
toast.error('大会一覧の取得に失敗しました')
} finally {
setLoading(false)
}
}
load()
}, [])
const handleLogout = async () => {
const supabase = createClient()
await supabase.auth.signOut()
router.push('/admin/login')
}
return (
<div className="min-h-screen bg-background">
<header className="border-b border-border/40">
<div className="container mx-auto flex h-16 items-center justify-between px-4">
<h1 className="font-bold text-lg">TradingCup 管理</h1>
<Button variant="ghost" size="sm" onClick={handleLogout} clasAdminLoginPage function · typescript · L13-L90 (78 LOC)app/admin/login/page.tsx
export default function AdminLoginPage() {
const router = useRouter()
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [loading, setLoading] = useState(false)
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault()
setLoading(true)
try {
const supabase = createClient()
const { error } = await supabase.auth.signInWithPassword({ email, password })
if (error) throw error
toast.success('ログインしました')
router.push('/admin/cups')
} catch (err) {
toast.error(err instanceof Error ? err.message : 'ログインに失敗しました')
} finally {
setLoading(false)
}
}
return (
<div className="min-h-screen flex items-center justify-center bg-background p-4">
<div className="w-full max-w-sm space-y-6">
<div className="text-center">
<div className="inline-flex rounded-full border border-border/50 bg-muted p-4 mb-3">
<Shield className="h-8 w-8 textGET function · typescript · L9-L40 (32 LOC)app/api/auth/wallet/check/route.ts
export async function GET(request: NextRequest) {
const sessionToken = request.cookies.get('wallet_session')?.value
if (!sessionToken) {
return NextResponse.json({ authenticated: false })
}
try {
const { payload } = await jwtVerify(sessionToken, JWT_SECRET)
const profileId = payload.profile_id as string
// Verify profile actually exists in DB (handles DB resets)
const supabase = createServiceClient()
const { data: profile } = await supabase
.from('profiles')
.select('id')
.eq('id', profileId)
.single()
if (!profile) {
return NextResponse.json({ authenticated: false })
}
return NextResponse.json({
authenticated: true,
wallet_address: payload.wallet_address,
profile_id: profileId,
})
} catch {
return NextResponse.json({ authenticated: false })
}
}Repobility analyzer · published findings · https://repobility.com
POST function · typescript · L10-L80 (71 LOC)app/api/auth/wallet/route.ts
export async function POST(request: NextRequest) {
try {
const { wallet_address, message, signature } = await request.json()
if (!wallet_address || !message || !signature) {
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 })
}
// Verify the signature
const address = wallet_address as `0x${string}`
const isValid = await verifyMessage({
address,
message,
signature: signature as `0x${string}`,
})
if (!isValid) {
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 })
}
// Extract nonce and timestamp from message to prevent replay attacks
const timestampMatch = message.match(/Timestamp: (\d+)/)
if (timestampMatch) {
const timestamp = parseInt(timestampMatch[1])
const now = Date.now()
if (now - timestamp > 5 * 60 * 1000) {
return NextResponse.json({ error: 'Signature expired' }, { status: 401 })
}
}
const supabase = creatPOST function · typescript · L6-L133 (128 LOC)app/api/cron/ranking/route.ts
export async function POST(request: NextRequest) {
// Verify cron secret
const authHeader = request.headers.get('authorization')
const cronSecret = process.env.CRON_SECRET
if (!cronSecret || authHeader !== `Bearer ${cronSecret}`) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const supabase = createServiceClient()
try {
// Get all active cups
const { data: cups, error: cupsError } = await supabase
.from('cups')
.select('*')
.eq('status', 'active')
if (cupsError) throw cupsError
if (!cups || cups.length === 0) {
return NextResponse.json({ message: 'No active cups' })
}
const results = []
for (const cup of cups) {
const now = new Date()
const endAt = cup.end_at ? new Date(cup.end_at) : null
// Auto-end cup if past end time
if (endAt && now > endAt) {
await supabase
.from('cups')
.update({ status: 'ended' })
.eq('id', cup.id)
GET function · typescript · L136-L141 (6 LOC)app/api/cron/ranking/route.ts
export async function GET(request: NextRequest) {
if (process.env.NODE_ENV !== 'development') {
return NextResponse.json({ error: 'Not allowed' }, { status: 405 })
}
return POST(request)
}POST function · typescript · L6-L60 (55 LOC)app/api/cups/[id]/cover-image/route.ts
export async function POST(request: NextRequest, { params }: Params) {
try {
const { id } = await params
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const formData = await request.formData()
const file = formData.get('file') as File | null
if (!file) {
return NextResponse.json({ error: 'No file provided' }, { status: 400 })
}
if (!file.type.startsWith('image/')) {
return NextResponse.json({ error: 'File must be an image' }, { status: 400 })
}
if (file.size > 5 * 1024 * 1024) {
return NextResponse.json({ error: 'Image must be under 5MB' }, { status: 400 })
}
const ext = file.name.split('.').pop()?.toLowerCase() ?? 'jpg'
const path = `${id}.${ext}`
const buffer = await file.arrayBuffer()
const serviceClient = createServiceClient()
const { error: uploadErGET function · typescript · L6-L32 (27 LOC)app/api/cups/[id]/ranking/route.ts
export async function GET(request: NextRequest, { params }: Params) {
try {
const { id: cupId } = await params
const supabase = await createClient()
const { data, error } = await supabase
.from('cup_participants')
.select(`
*,
profiles (
wallet_address,
display_name
)
`)
.eq('cup_id', cupId)
.eq('is_disqualified', false)
.order('rank', { ascending: true, nullsFirst: false })
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
}
return NextResponse.json({ data: data ?? [] })
} catch (error) {
return NextResponse.json({ error: 'Failed to fetch ranking' }, { status: 500 })
}
}POST function · typescript · L13-L104 (92 LOC)app/api/cups/[id]/register/route.ts
export async function POST(request: NextRequest, { params }: Params) {
try {
const { id: cupId } = await params
const sessionToken = request.cookies.get('wallet_session')?.value
if (!sessionToken) {
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 })
}
const { payload } = await jwtVerify(sessionToken, JWT_SECRET)
const profileId = payload.profile_id as string
const supabase = createServiceClient()
// Check cup exists and is accepting registrations
const { data: cup, error: cupError } = await supabase
.from('cups')
.select('*')
.eq('id', cupId)
.single()
if (cupError || !cup) {
return NextResponse.json({ error: 'Cup not found' }, { status: 404 })
}
if (!['scheduled', 'active'].includes(cup.status)) {
return NextResponse.json(
{ error: 'Cup is not accepting registrations' },
{ status: 400 }
)
}
// Check already registered
const { data:GET function · typescript · L6-L31 (26 LOC)app/api/cups/[id]/route.ts
export async function GET(request: NextRequest, { params }: Params) {
try {
const { id } = await params
const supabase = await createClient()
const { data, error } = await supabase
.from('cups')
.select('*')
.eq('id', id)
.single()
if (error) {
return NextResponse.json({ error: 'Cup not found' }, { status: 404 })
}
// Get participant count
const { count } = await supabase
.from('cup_participants')
.select('*', { count: 'exact', head: true })
.eq('cup_id', id)
return NextResponse.json({ data: { ...data, participant_count: count ?? 0 } })
} catch (error) {
return NextResponse.json({ error: 'Failed to fetch cup' }, { status: 500 })
}
}PATCH function · typescript · L33-L61 (29 LOC)app/api/cups/[id]/route.ts
export async function PATCH(request: NextRequest, { params }: Params) {
try {
const { id } = await params
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const body = await request.json()
const serviceClient = createServiceClient()
const { data, error } = await serviceClient
.from('cups')
.update(body)
.eq('id', id)
.select()
.single()
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
}
return NextResponse.json({ data })
} catch (error) {
return NextResponse.json({ error: 'Failed to update cup' }, { status: 500 })
}
}Powered by Repobility — scan your code at https://repobility.com
DELETE function · typescript · L63-L87 (25 LOC)app/api/cups/[id]/route.ts
export async function DELETE(request: NextRequest, { params }: Params) {
try {
const { id } = await params
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const serviceClient = createServiceClient()
const { error } = await serviceClient
.from('cups')
.delete()
.eq('id', id)
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
}
return NextResponse.json({ success: true })
} catch (error) {
return NextResponse.json({ error: 'Failed to delete cup' }, { status: 500 })
}
}GET function · typescript · L4-L29 (26 LOC)app/api/cups/route.ts
export async function GET(request: NextRequest) {
try {
const supabase = await createClient()
const { searchParams } = new URL(request.url)
const status = searchParams.get('status')
let query = supabase
.from('cups')
.select('*')
.order('created_at', { ascending: false })
if (status) {
query = query.eq('status', status)
}
const { data, error } = await query
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
}
return NextResponse.json({ data })
} catch (error) {
return NextResponse.json({ error: 'Failed to fetch cups' }, { status: 500 })
}
}POST function · typescript · L31-L73 (43 LOC)app/api/cups/route.ts
export async function POST(request: NextRequest) {
try {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const body = await request.json()
const { name, exchange, pair, start_at, end_at, min_volume_usdt, description, rewards } = body
if (!name) {
return NextResponse.json({ error: 'Name is required' }, { status: 400 })
}
const serviceClient = createServiceClient()
const { data, error } = await serviceClient
.from('cups')
.insert({
name,
exchange: exchange ?? 'lbank',
pair: pair ?? 'IZKY/USDT',
start_at,
end_at,
min_volume_usdt: min_volume_usdt ?? 100,
description,
rewards: rewards ?? [],
created_by: user.id,
status: 'draft',
})
.select()
.single()
if (error) {
return NextResponse.jsPOST function · typescript · L11-L101 (91 LOC)app/api/lbank/save-key/route.ts
export async function POST(request: NextRequest) {
try {
const sessionToken = request.cookies.get('wallet_session')?.value
if (!sessionToken) {
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 })
}
const { payload } = await jwtVerify(sessionToken, JWT_SECRET)
const profileId = payload.profile_id as string
const walletAddress = payload.wallet_address as string
// Ensure profile exists (recover from DB resets)
const supabase = createServiceClient()
const { data: existingProfile } = await supabase
.from('profiles')
.select('id')
.eq('id', profileId)
.single()
if (!existingProfile) {
// Profile missing — re-upsert by wallet address
const { data: newProfile, error: upsertError } = await supabase
.from('profiles')
.upsert(
{ wallet_address: walletAddress.toLowerCase() },
{ onConflict: 'wallet_address', ignoreDuplicates: false }
)
.seGET function · typescript · L9-L31 (23 LOC)app/api/lbank/status/route.ts
export async function GET(request: NextRequest) {
const sessionToken = request.cookies.get('wallet_session')?.value
if (!sessionToken) {
return NextResponse.json({ connected: false })
}
try {
const { payload } = await jwtVerify(sessionToken, JWT_SECRET)
const profileId = payload.profile_id as string
const supabase = createServiceClient()
const { data } = await supabase
.from('exchange_api_keys')
.select('id, created_at')
.eq('user_id', profileId)
.eq('exchange', 'lbank')
.eq('is_verified', true)
.maybeSingle()
return NextResponse.json({ connected: !!data, saved_at: data?.created_at ?? null })
} catch {
return NextResponse.json({ connected: false })
}
}RootLayout function · typescript · L21-L33 (13 LOC)app/layout.tsx
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en" className="dark">
<body className={`${geistSans.variable} ${geistMono.variable} antialiased bg-background text-foreground`}>
<Providers>{children}</Providers>
</body>
</html>
)
}Providers function · typescript · L12-L23 (12 LOC)app/providers.tsx
export function Providers({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={wagmiConfig}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider theme={darkTheme()}>
{children}
<Toaster />
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
)
}LoginPage function · typescript · L9-L63 (55 LOC)app/(user)/login/page.tsx
export default function LoginPage() {
const router = useRouter()
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-b from-background to-background/80 p-4">
<div className="w-full max-w-md space-y-6">
<div className="text-center space-y-2">
<div className="flex justify-center">
<div className="rounded-full border border-primary/30 bg-primary/10 p-4">
<Trophy className="h-10 w-10 text-primary" />
</div>
</div>
<h1 className="text-3xl font-bold">TradingCup</h1>
<p className="text-muted-foreground">Sign in with your Ethereum wallet to participate</p>
</div>
<Card className="border-border/50">
<CardHeader>
<CardTitle>Connect Your Wallet</CardTitle>
<CardDescription>
No email or password required. Your wallet is your identity.
</CardDescription>
</CardHeader>
<CSame scanner, your repo: https://repobility.com — Repobility
LobbyPage function · typescript · L8-L72 (65 LOC)app/(user)/page.tsx
export default function LobbyPage() {
return (
<div className="min-h-screen bg-background">
{/* Header */}
<header className="sticky top-0 z-40 border-b border-border bg-card/80 backdrop-blur-sm shadow-[0px_8px_24px_0px_rgba(17,23,61,0.8)]">
<div className="container mx-auto flex h-16 items-center justify-between px-4">
<Link href="/" className="flex items-center gap-2">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-gradient-to-b from-primary to-accent shrink-0">
<Trophy className="h-6 w-6 text-white" />
</div>
<div className="flex flex-col leading-none">
<span className="text-sm font-medium text-foreground">トレーディングカップ</span>
<span className="text-xs text-muted-foreground mt-0.5">IZKYプラットフォーム</span>
</div>
</Link>
<ConnectButton />
</div>
</header>
{/* Navigation */}
<nav className="borderProfilePage function · typescript · L15-L92 (78 LOC)app/(user)/profile/page.tsx
export default function ProfilePage() {
const { address, isConnected } = useAccount()
const [copied, setCopied] = useState(false)
const handleCopy = () => {
if (!address) return
navigator.clipboard.writeText(address)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
return (
<div className="min-h-screen bg-background">
<header className="sticky top-0 z-40 border-b border-border/40 bg-background/80 backdrop-blur">
<div className="container mx-auto flex h-16 items-center justify-between px-4">
<Link href="/" className="flex items-center gap-2 text-muted-foreground hover:text-foreground text-sm">
<ArrowLeft className="h-4 w-4" />
戻る
</Link>
<ConnectButton />
</div>
</header>
<main className="container mx-auto px-4 py-8 max-w-xl">
<h1 className="text-2xl font-bold mb-6">プロフィール</h1>
{!isConnected ? (
<Card className="border-border/50">
RankingPage function · typescript · L17-L123 (107 LOC)app/(user)/ranking/[id]/page.tsx
export default function RankingPage() {
const { id } = useParams<{ id: string }>()
const { address } = useAccount()
const [cup, setCup] = useState<Cup | null>(null)
const [participants, setParticipants] = useState<CupParticipantWithProfile[]>([])
const [loading, setLoading] = useState(true)
const [refreshing, setRefreshing] = useState(false)
const [lastUpdated, setLastUpdated] = useState<Date | null>(null)
const fetchRanking = useCallback(async (isRefresh = false) => {
if (isRefresh) setRefreshing(true)
else setLoading(true)
try {
const [cupRes, rankRes] = await Promise.all([
fetch(`/api/cups/${id}`),
fetch(`/api/cups/${id}/ranking`),
])
if (cupRes.ok) {
const { data } = await cupRes.json()
setCup(data)
}
if (rankRes.ok) {
const { data } = await rankRes.json()
setParticipants(data ?? [])
setLastUpdated(new Date())
}
} finally {
setLoading(false)
seFinalizeButton function · typescript · L21-L81 (61 LOC)components/admin/FinalizeButton.tsx
export function FinalizeButton({ cupId }: FinalizeButtonProps) {
const router = useRouter()
const [open, setOpen] = useState(false)
const [loading, setLoading] = useState(false)
const handleFinalize = async () => {
setLoading(true)
try {
const res = await fetch(`/api/cups/${cupId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: 'finalized' }),
})
if (!res.ok) {
const err = await res.json()
throw new Error(err.error ?? '確定に失敗しました')
}
toast.success('大会結果を確定しました!')
setOpen(false)
router.refresh()
} catch (err) {
toast.error(err instanceof Error ? err.message : '確定に失敗しました')
} finally {
setLoading(false)
}
}
return (
<>
<Button onClick={() => setOpen(true)} className="gap-2">
<Trophy className="h-4 w-4" />
結果を確定
</Button>
<Dialog open={open} onOpenChange={setOpen}>
CupCard function · typescript · L14-L141 (128 LOC)components/cup/CupCard.tsx
export function CupCard({ cup }: CupCardProps) {
const isActive = cup.status === 'active'
const isScheduled = cup.status === 'scheduled'
const isEnded = cup.status === 'ended' || cup.status === 'finalized'
const formatDate = (d: string | null) =>
d ? format(new Date(d), 'yyyy.MM.dd', { locale: ja }) : '—'
const rewardSummary = cup.rewards?.length
? cup.rewards.slice(0, 2).map((r) => `${r.rank}: ${r.reward}`).join(' / ')
+ (cup.rewards.length > 2 ? ' …' : '')
: null
return (
<div className="flex flex-col bg-card border border-border rounded-xl overflow-hidden shadow-[0px_8px_24px_0px_rgba(17,23,61,0.8)] transition-transform hover:-translate-y-0.5">
{/* Live accent line */}
{isActive && (
<div className="h-0.5 w-full bg-gradient-to-r from-green-400 via-emerald-300 to-green-400 shrink-0" />
)}
{/* Cover image */}
<div className="relative aspect-video overflow-hidden bg-[#1d2766]">
{cup.cover_image_CupLobby function · typescript · L9-L85 (77 LOC)components/cup/CupLobby.tsx
export function CupLobby() {
const [cups, setCups] = useState<(Cup & { participant_count?: number })[]>([])
const [loading, setLoading] = useState(true)
const [activeTab, setActiveTab] = useState<string>('active')
useEffect(() => {
const fetchCups = async () => {
setLoading(true)
try {
const res = await fetch('/api/cups')
if (!res.ok) throw new Error('Failed to fetch')
const { data } = await res.json()
setCups(data ?? [])
} catch {
setCups([])
} finally {
setLoading(false)
}
}
fetchCups()
}, [])
const filterCups = (status: string) => {
if (status === 'active') return cups.filter((c) => c.status === 'active')
if (status === 'upcoming') return cups.filter((c) => c.status === 'scheduled')
if (status === 'ended') return cups.filter((c) => ['ended', 'finalized'].includes(c.status))
return cups
}
const filtered = filterCups(activeTab)
return (
<div className="sRegisterModal function · typescript · L24-L113 (90 LOC)components/cup/RegisterModal.tsx
export function RegisterModal({
cupId,
cupName,
open,
onOpenChange,
onSuccess,
}: RegisterModalProps) {
const [loading, setLoading] = useState(false)
const [done, setDone] = useState(false)
const [error, setError] = useState<string | null>(null)
const handleRegister = async () => {
setLoading(true)
setError(null)
try {
const res = await fetch(`/api/cups/${cupId}/register`, {
method: 'POST',
})
const data = await res.json()
if (!res.ok) {
throw new Error(data.error ?? '登録に失敗しました')
}
setDone(true)
toast.success('参加登録が完了しました!')
onSuccess?.()
setTimeout(() => {
onOpenChange(false)
setDone(false)
}, 1500)
} catch (err) {
const msg = err instanceof Error ? err.message : '登録に失敗しました'
setError(msg)
toast.error(msg)
} finally {
setLoading(false)
}
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
LobbyHero function · typescript · L6-L28 (23 LOC)components/lobby/LobbyHero.tsx
export function LobbyHero() {
const { isConnected } = useAccount()
return (
<div className="rounded-lg bg-gradient-to-b from-primary to-accent p-6 shadow-[0px_8px_24px_0px_rgba(17,23,61,0.8)]">
<div className="flex items-center gap-2 mb-2">
<Trophy className="h-8 w-8 text-white shrink-0" />
<h1 className="text-base font-normal text-white leading-tight">IZKYトレーディングカップ</h1>
</div>
<p className="text-sm text-white/90 leading-relaxed mb-4">
世界中のトレーダーと競い合い、賞品を獲得しよう。すべての取引は実際の取引所データで検証されます。
</p>
{!isConnected && (
<div className="border border-accent/50 bg-accent/10 rounded-lg px-4 py-2.5 flex items-center gap-2">
<Info className="h-4 w-4 text-white shrink-0" />
<p className="text-sm text-white">
トレーディングカップに参加するにはウォレットを接続してください
</p>
</div>
)}
</div>
)
}Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
MyRankCard function · typescript · L13-L60 (48 LOC)components/ranking/MyRankCard.tsx
export function MyRankCard({ participant, totalParticipants }: MyRankCardProps) {
const pnlPct = participant.pnl_pct ?? 0
const rank = participant.rank
const isOutOfRange = !rank || rank > 10
if (!isOutOfRange) return null
return (
<div className="sticky bottom-4 px-4">
<Card className="border-primary/30 bg-card/90 backdrop-blur shadow-lg">
<CardContent className="py-3 px-4">
<div className="flex items-center justify-between gap-4">
<div className="flex items-center gap-3">
<div className="text-sm text-muted-foreground">自分の順位</div>
<div className="font-bold text-lg">
{rank ? `#${rank}` : '圏外'}
<span className="text-sm text-muted-foreground font-normal ml-1">
/ {totalParticipants}
</span>
</div>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-1.5">
RankingTable function · typescript · L26-L111 (86 LOC)components/ranking/RankingTable.tsx
export function RankingTable({ participants, currentUserWallet }: RankingTableProps) {
if (participants.length === 0) {
return (
<div className="text-center py-12 text-muted-foreground">
<Trophy className="h-12 w-12 mx-auto mb-3 opacity-30" />
<p>まだランキングはありません。大会が開始されていないか、参加者がいません。</p>
</div>
)
}
return (
<div className="rounded-lg border border-border/50 overflow-hidden">
<Table>
<TableHeader>
<TableRow className="border-border/50 bg-muted/20">
<TableHead className="w-16 text-center">順位</TableHead>
<TableHead>トレーダー</TableHead>
<TableHead className="text-right">PNL %</TableHead>
<TableHead className="text-right hidden sm:table-cell">取引量 (USDT)</TableHead>
<TableHead className="text-right hidden sm:table-cell">ステータス</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{participants.map((p, idx) => {
const rank =Badge function · typescript · L29-L46 (18 LOC)components/ui/badge.tsx
function Badge({
className,
variant = "default",
asChild = false,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot.Root : "span"
return (
<Comp
data-slot="badge"
data-variant={variant}
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
)
}Button function · typescript · L41-L62 (22 LOC)components/ui/button.tsx
function Button({
className,
variant = "default",
size = "default",
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot.Root : "button"
return (
<Comp
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}Card function · typescript · L5-L16 (12 LOC)components/ui/card.tsx
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className
)}
{...props}
/>
)
}CardHeader function · typescript · L18-L29 (12 LOC)components/ui/card.tsx
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className
)}
{...props}
/>
)
}CardTitle function · typescript · L31-L39 (9 LOC)components/ui/card.tsx
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn("leading-none font-semibold", className)}
{...props}
/>
)
}CardDescription function · typescript · L41-L49 (9 LOC)components/ui/card.tsx
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
}Repobility analyzer · published findings · https://repobility.com
CardAction function · typescript · L51-L62 (12 LOC)components/ui/card.tsx
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...props}
/>
)
}CardContent function · typescript · L64-L72 (9 LOC)components/ui/card.tsx
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-6", className)}
{...props}
/>
)
}CardFooter function · typescript · L74-L82 (9 LOC)components/ui/card.tsx
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
{...props}
/>
)
}DialogOverlay function · typescript · L34-L48 (15 LOC)components/ui/dialog.tsx
function DialogOverlay({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className
)}
{...props}
/>
)
}DialogContent function · typescript · L50-L82 (33 LOC)components/ui/dialog.tsx
function DialogContent({
className,
children,
showCloseButton = true,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean
}) {
return (
<DialogPortal data-slot="dialog-portal">
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
className
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close
data-slot="dialog-close"
className="ring-offset-background focus:ring-ring data-[state=open]:bg-aDialogHeader function · typescript · L84-L92 (9 LOC)components/ui/dialog.tsx
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-header"
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props}
/>
)
}DialogFooter function · typescript · L94-L119 (26 LOC)components/ui/dialog.tsx
function DialogFooter({
className,
showCloseButton = false,
children,
...props
}: React.ComponentProps<"div"> & {
showCloseButton?: boolean
}) {
return (
<div
data-slot="dialog-footer"
className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
className
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close asChild>
<Button variant="outline">Close</Button>
</DialogPrimitive.Close>
)}
</div>
)
}DialogTitle function · typescript · L121-L132 (12 LOC)components/ui/dialog.tsx
function DialogTitle({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn("text-lg leading-none font-semibold", className)}
{...props}
/>
)
}Powered by Repobility — scan your code at https://repobility.com
DialogDescription function · typescript · L134-L145 (12 LOC)components/ui/dialog.tsx
function DialogDescription({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
}FormItem function · typescript · L76-L88 (13 LOC)components/ui/form.tsx
function FormItem({ className, ...props }: React.ComponentProps<"div">) {
const id = React.useId()
return (
<FormItemContext.Provider value={{ id }}>
<div
data-slot="form-item"
className={cn("grid gap-2", className)}
{...props}
/>
</FormItemContext.Provider>
)
}FormLabel function · typescript · L90-L105 (16 LOC)components/ui/form.tsx
function FormLabel({
className,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
const { error, formItemId } = useFormField()
return (
<Label
data-slot="form-label"
data-error={!!error}
className={cn("data-[error=true]:text-destructive", className)}
htmlFor={formItemId}
{...props}
/>
)
}page 1 / 2next ›