← back to hangjie2013__tradingcup

Function bodies 94 total

All specs Real LLM only Function bodies
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-backg
AdminCupDetailPage 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_participant
NewCupContent 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} clas
AdminLoginPage 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 text
GET 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 = creat
POST 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: uploadEr
GET 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.js
POST 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 }
        )
        .se
GET 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>
          <C
Same 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="border
ProfilePage 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)
      se
FinalizeButton 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="s
RegisterModal 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-a
DialogHeader 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 ›