+
全局排行榜
-
+
-
+
-
-
-
+
+
)
}
diff --git a/frontend/components/common/leaderboard/leaderboard-podium.tsx b/frontend/components/common/leaderboard/leaderboard-podium.tsx
index f49bad40..a34be48f 100644
--- a/frontend/components/common/leaderboard/leaderboard-podium.tsx
+++ b/frontend/components/common/leaderboard/leaderboard-podium.tsx
@@ -5,80 +5,114 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Skeleton } from "@/components/ui/skeleton"
import { cn } from "@/lib/utils"
import type { LeaderboardEntry } from "@/lib/services/leaderboard"
-import { Crown, Medal, Trophy } from "lucide-react"
+
interface LeaderboardPodiumProps {
items: LeaderboardEntry[]
loading?: boolean
}
-const rankStyles = {
+type PodiumRank = 1 | 2 | 3
+
+function formatBalance(value: string) {
+ return Number.parseFloat(value).toLocaleString("zh-CN", {
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ })
+}
+
+const rankConfig = {
1: {
- icon: Crown,
- iconClassName: "text-amber-500 dark:text-amber-300",
- rowClassName: "h-[68px] grid-cols-[44px_40px_minmax(0,1fr)_112px] bg-amber-500/[0.10] border-amber-400/45 px-4 dark:bg-amber-300/[0.08] dark:border-amber-200/20",
- badgeClassName: "text-amber-700 dark:text-amber-200",
- avatarClassName: "size-10",
- nameClassName: "text-sm",
- metaClassName: "text-amber-700/70 dark:text-amber-100/65",
- valueClassName: "text-lg leading-none text-amber-700 dark:text-amber-100",
+ numColor: "from-amber-500/60 to-amber-500/8",
+ scoreColor: "text-amber-500",
+ avatarSize: "size-16 sm:size-[72px]",
+ numSize: "text-[72px] sm:text-[88px]",
+ nameSize: "text-base font-bold",
+ scoreSize: "text-lg font-black",
+ avatarNegMt: "-mt-6 sm:-mt-7",
+ order: "md:order-2",
+ shift: "",
},
2: {
- icon: Trophy,
- iconClassName: "text-slate-500 dark:text-slate-200",
- rowClassName: "h-[68px] grid-cols-[44px_40px_minmax(0,1fr)_112px] bg-slate-100/70 border-slate-200/80 px-4 dark:bg-slate-200/[0.04] dark:border-slate-200/12",
- badgeClassName: "text-slate-600 dark:text-slate-200",
- avatarClassName: "size-10",
- nameClassName: "text-sm",
- metaClassName: "text-slate-600/75 dark:text-slate-300/70",
- valueClassName: "text-lg leading-none text-foreground dark:text-slate-100",
+ numColor: "from-zinc-400/45 to-zinc-400/5",
+ scoreColor: "text-zinc-400",
+ avatarSize: "size-12 sm:size-[52px]",
+ numSize: "text-[52px] sm:text-[64px]",
+ nameSize: "text-sm font-semibold",
+ scoreSize: "text-sm font-bold",
+ avatarNegMt: "-mt-5",
+ order: "md:order-1",
+ shift: "md:translate-y-9",
},
3: {
- icon: Medal,
- iconClassName: "text-orange-600 dark:text-orange-300",
- rowClassName: "h-[68px] grid-cols-[44px_40px_minmax(0,1fr)_112px] bg-orange-100/60 border-orange-200/70 px-4 dark:bg-orange-300/[0.05] dark:border-orange-200/12",
- badgeClassName: "text-orange-700 dark:text-orange-200",
- avatarClassName: "size-10",
- nameClassName: "text-sm",
- metaClassName: "text-orange-700/75 dark:text-orange-200/70",
- valueClassName: "text-lg leading-none text-foreground dark:text-orange-100",
+ numColor: "from-orange-500/55 to-orange-500/8",
+ scoreColor: "text-orange-500",
+ avatarSize: "size-12 sm:size-[52px]",
+ numSize: "text-[52px] sm:text-[64px]",
+ nameSize: "text-sm font-semibold",
+ scoreSize: "text-sm font-bold",
+ avatarNegMt: "-mt-5",
+ order: "md:order-3",
+ shift: "md:translate-y-14",
},
} as const
-function TopRankRow({
- entry,
- rank,
-}: {
- entry: LeaderboardEntry
- rank: 1 | 2 | 3
-}) {
- const style = rankStyles[rank]
- const Icon = style.icon
+function PodiumSkeleton() {
+ return (
+
+
+ {([2, 1, 3] as PodiumRank[]).map((rank) => {
+ const cfg = rankConfig[rank]
+ return (
+
+
+
+
+
+
+ )
+ })}
+
+
+ )
+}
+
+function PodiumColumn({ entry, rank }: { entry: LeaderboardEntry; rank: PodiumRank }) {
+ const cfg = rankConfig[rank]
return (
-
-
-
+
+
+ {/* 排名大字 */}
+
+ {String(rank).padStart(2, "0")}
-
-
-
+ {/* 头像 — 圆形 */}
+
+
+
{entry.username.slice(0, 2).toUpperCase()}
-
-
{entry.username}
+ {/* 用户名 */}
+
+ {entry.username}
-
-
可用积分
-
- {parseFloat(entry.available_balance).toFixed(2)}
-
+ {/* 积分 */}
+
+ {formatBalance(entry.available_balance)}
-
+
+
)
}
@@ -86,58 +120,25 @@ export const LeaderboardPodium = React.memo(function LeaderboardPodium({
items,
loading,
}: LeaderboardPodiumProps) {
- if (loading) {
- return (
-
- Top 3
-
- {Array.from({ length: 3 }).map((_, index) => (
-
- ))}
-
-
- )
- }
+ if (loading) return
if (items.length === 0) {
- return (
-
- )
+ return
暂无排行数据
}
+ const ordered = [
+ items[1] ? { entry: items[1], rank: 2 as PodiumRank } : null,
+ items[0] ? { entry: items[0], rank: 1 as PodiumRank } : null,
+ items[2] ? { entry: items[2], rank: 3 as PodiumRank } : null,
+ ].filter(Boolean) as Array<{ entry: LeaderboardEntry; rank: PodiumRank }>
+
return (
-
- Top 3
-
- {items.map((entry, index) => (
-
+
+
+ {ordered.map(({ entry, rank }) => (
+
))}
-
+
)
})
diff --git a/frontend/components/common/leaderboard/leaderboard-table.tsx b/frontend/components/common/leaderboard/leaderboard-table.tsx
index 1c77180b..aebd1563 100644
--- a/frontend/components/common/leaderboard/leaderboard-table.tsx
+++ b/frontend/components/common/leaderboard/leaderboard-table.tsx
@@ -3,9 +3,13 @@
import * as React from "react"
import { useVirtualizer } from "@tanstack/react-virtual"
import { Skeleton } from "@/components/ui/skeleton"
+import { ScrollArea } from "@/components/ui/scroll-area"
import { RankRowItem } from "./rank-row-item"
import type { LeaderboardEntry } from "@/lib/services/leaderboard"
+const leaderboardTableColumns = "grid-cols-[72px_minmax(0,1fr)_132px] sm:grid-cols-[88px_minmax(0,1fr)_152px]"
+const leaderboardRowHeight = 60
+
interface LeaderboardTableProps {
items: LeaderboardEntry[]
loading?: boolean
@@ -28,21 +32,28 @@ export const LeaderboardTable = React.memo(function LeaderboardTable({
startRank = 1,
}: LeaderboardTableProps) {
const parentRef = React.useRef
(null)
+
+ const maxBalance = React.useMemo(() => {
+ if (items.length === 0) return 1
+ return Math.max(...items.map(item => parseFloat(item.available_balance))) || 1
+ }, [items])
+
const rankedItems = React.useMemo(
() =>
items
.map((entry, index) => ({
entry,
rank: startRank + index,
+ percentage: (parseFloat(entry.available_balance) / maxBalance) * 100,
}))
.filter(({ entry }) => entry.user_id !== currentUserEntry?.user_id),
- [items, startRank, currentUserEntry?.user_id]
+ [items, startRank, currentUserEntry?.user_id, maxBalance]
)
const virtualizer = useVirtualizer({
count: rankedItems.length,
getScrollElement: () => parentRef.current,
- estimateSize: () => 68,
+ estimateSize: () => leaderboardRowHeight,
overscan: 6,
useFlushSync: false,
})
@@ -52,106 +63,110 @@ export const LeaderboardTable = React.memo(function LeaderboardTable({
React.useEffect(() => {
const lastItem = virtualItems[virtualItems.length - 1]
if (!lastItem) return
-
if (lastItem.index >= rankedItems.length - 1 && hasMore && !loading && onLoadMore) {
onLoadMore()
}
}, [virtualItems, rankedItems.length, hasMore, loading, onLoadMore])
+ const currentUserPercentage = currentUserEntry
+ ? (parseFloat(currentUserEntry.available_balance) / maxBalance) * 100
+ : 0
+
if (loading && items.length === 0) {
return (
-
- 完整榜单
-
+
+
+
{Array.from({ length: 8 }).map((_, i) => (
-
-
+
)
}
if (items.length === 0) {
- return (
-
- )
+ return
暂无排行数据
}
return (
-
- 完整榜单
-
-
-
+
+ {/* 列头 */}
+
- {currentUserEntry && currentUserRank && (
+ {/* 当前用户置顶行 */}
+ {currentUserEntry && currentUserRank && (
+
- )}
-
-
-
- {virtualItems.map((virtualRow) => {
- const { entry, rank } = rankedItems[virtualRow.index]
- return (
-
-
-
- )
- })}
-
+ )}
- {loading && items.length > 0 && (
-
- 正在加载更多排行...
-
- )}
+ {/* 榜单主体 — 从 muted 过渡到页面背景色 */}
+
+
+
+
+ {virtualItems.map((virtualRow) => {
+ const { entry, rank, percentage } = rankedItems[virtualRow.index]
+ return (
+
+
+
+ )
+ })}
+
+
+
-
+
+ {loading && items.length > 0 && (
+
+ 正在加载更多...
+
+ )}
+
)
})
diff --git a/frontend/components/common/leaderboard/rank-row-item.tsx b/frontend/components/common/leaderboard/rank-row-item.tsx
index b447cb7a..9073cbd2 100644
--- a/frontend/components/common/leaderboard/rank-row-item.tsx
+++ b/frontend/components/common/leaderboard/rank-row-item.tsx
@@ -5,33 +5,58 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { cn } from "@/lib/utils"
import type { LeaderboardEntry } from "@/lib/services/leaderboard"
+const leaderboardRowColumns = "grid-cols-[72px_minmax(0,1fr)_112px] sm:grid-cols-[88px_minmax(0,1fr)_128px]"
+
interface RankRowItemProps {
entry: LeaderboardEntry
rank: number
+ percentage?: number
isCurrentUser?: boolean
metaLabel?: string
pinned?: boolean
}
+
export const RankRowItem = React.memo(function RankRowItem({
entry,
rank,
+ percentage = 0,
isCurrentUser,
metaLabel,
pinned = false,
}: RankRowItemProps) {
+ const rankClass =
+ rank === 1 ? "text-amber-500 font-black" :
+ rank === 2 ? "text-zinc-400 font-bold" :
+ rank === 3 ? "text-orange-500 font-bold" :
+ "text-muted-foreground"
+
return (
-
{rank}
+ {/* 进度条背景 */}
+ {percentage > 0 && (
+
+ )}
+
+
+ #{rank}
+
-
-
+
+
{entry.username.slice(0, 2).toUpperCase()}
@@ -40,12 +65,14 @@ export const RankRowItem = React.memo(function RankRowItem({
{entry.username}
{metaLabel && (
-
{metaLabel}
+
+ {metaLabel}
+
)}
-
+
{parseFloat(entry.available_balance).toFixed(2)}
diff --git a/frontend/components/ui/scroll-area.tsx b/frontend/components/ui/scroll-area.tsx
index 8e4fa13f..abf0d822 100644
--- a/frontend/components/ui/scroll-area.tsx
+++ b/frontend/components/ui/scroll-area.tsx
@@ -5,11 +5,16 @@ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { cn } from "@/lib/utils"
+type ScrollAreaProps = React.ComponentProps & {
+ viewportRef?: React.Ref
+}
+
function ScrollArea({
className,
children,
+ viewportRef,
...props
-}: React.ComponentProps) {
+}: ScrollAreaProps) {
return (
From 33d4b44d8e3aac8204538e265befe48380ccbedb Mon Sep 17 00:00:00 2001
From: Chenyme <118253778+chenyme@users.noreply.github.com>
Date: Fri, 17 Apr 2026 18:43:33 +0800
Subject: [PATCH 2/2] chore: update axios and form-data versions, enhance login
page session handling, and improve leaderboard component loading states
---
frontend/components/auth/login-page.tsx | 97 ++++++++++++++++---
.../common/leaderboard/leaderboard-main.tsx | 10 +-
.../common/leaderboard/leaderboard-podium.tsx | 10 +-
frontend/package.json | 2 +-
frontend/pnpm-lock.yaml | 27 +++---
5 files changed, 102 insertions(+), 44 deletions(-)
diff --git a/frontend/components/auth/login-page.tsx b/frontend/components/auth/login-page.tsx
index f02f63d0..5bab9f08 100644
--- a/frontend/components/auth/login-page.tsx
+++ b/frontend/components/auth/login-page.tsx
@@ -1,6 +1,6 @@
"use client"
-import { useEffect, useState } from "react"
+import { useCallback, useEffect, useState } from "react"
import { motion, AnimatePresence } from "motion/react"
import { useRouter, useSearchParams } from "next/navigation"
import { toast } from "sonner"
@@ -12,6 +12,7 @@ import { Check } from "lucide-react"
import { AuroraBackground } from "@/components/ui/aurora-background"
import services from "@/lib/services"
+import type { ApiResponse } from "@/lib/services/core/types"
/**
@@ -34,6 +35,7 @@ export function LoginPage() {
const code = searchParams.get('code')
return !!(state && code)
})
+ const [isCheckingSession, setIsCheckingSession] = useState(() => !searchParams.get('state') || !searchParams.get('code'))
const [loginSuccess, setLoginSuccess] = useState(false)
const [needsPayKeySetup, setNeedsPayKeySetup] = useState(false)
@@ -47,6 +49,18 @@ export function LoginPage() {
const isConfirmValid = confirmPayKey.length === 6 && /^\d{6}$/.test(confirmPayKey)
const passwordsMatch = payKey === confirmPayKey
+ const resolveRedirectTarget = useCallback(() => {
+ const callbackUrl = searchParams.get('callbackUrl')
+ const storedRedirect = sessionStorage.getItem('redirect_after_login')
+ const target = callbackUrl || storedRedirect || '/home'
+
+ if (storedRedirect) {
+ sessionStorage.removeItem('redirect_after_login')
+ }
+
+ return target
+ }, [searchParams])
+
/* 安全密码输入 */
const handlePayKeyChange = (value: string) => {
const numericValue = value.replace(/\D/g, '')
@@ -59,6 +73,56 @@ export function LoginPage() {
setConfirmPayKey(numericValue)
}
+ /* 登录页兜底:已登录用户直接跳转 */
+ useEffect(() => {
+ const state = searchParams.get('state')
+ const code = searchParams.get('code')
+
+ if (state && code) {
+ setIsCheckingSession(false)
+ return
+ }
+
+ let cancelled = false
+
+ const checkExistingSession = async () => {
+ setIsCheckingSession(true)
+
+ try {
+ const response = await fetch('/api/v1/oauth/user-info', {
+ credentials: 'include',
+ cache: 'no-store',
+ })
+
+ if (cancelled) return
+
+ if (response.ok) {
+ await response.json() as ApiResponse
+ router.replace(resolveRedirectTarget())
+ return
+ }
+
+ if (response.status !== 401) {
+ console.error('Session probe failed:', response.status)
+ }
+ } catch (error) {
+ if (!cancelled) {
+ console.error('Session probe error:', error)
+ }
+ } finally {
+ if (!cancelled) {
+ setIsCheckingSession(false)
+ }
+ }
+ }
+
+ checkExistingSession()
+
+ return () => {
+ cancelled = true
+ }
+ }, [router, searchParams, resolveRedirectTarget])
+
/* 回调逻辑 */
useEffect(() => {
const handleOAuthCallback = async () => {
@@ -78,13 +142,8 @@ export function LoginPage() {
setLoginSuccess(true)
toast.success("登录成功")
- const callbackUrl = searchParams.get('callbackUrl') || sessionStorage.getItem('redirect_after_login') || '/home'
- if (sessionStorage.getItem('redirect_after_login')) {
- sessionStorage.removeItem('redirect_after_login')
- }
-
setTimeout(() => {
- router.replace(callbackUrl)
+ router.replace(resolveRedirectTarget())
}, 1500)
}
} catch (error) {
@@ -96,7 +155,7 @@ export function LoginPage() {
}
}
handleOAuthCallback()
- }, [searchParams, router])
+ }, [searchParams, router, resolveRedirectTarget])
/* 安全密码设置 */
const handlePayKeySubmit = async (e: React.FormEvent) => {
@@ -131,11 +190,7 @@ export function LoginPage() {
setConfirmPayKey("")
setSetupStep('password')
setTimeout(() => {
- const callbackUrl = searchParams.get('callbackUrl') || sessionStorage.getItem('redirect_after_login') || '/home'
- if (sessionStorage.getItem('redirect_after_login')) {
- sessionStorage.removeItem('redirect_after_login')
- }
- router.replace(callbackUrl)
+ router.replace(resolveRedirectTarget())
}, 1500)
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "设置安全密码失败"
@@ -266,15 +321,25 @@ export function LoginPage() {
- {isProcessingCallback ? (
+ {isProcessingCallback || isCheckingSession ? (
- {needsPayKeySetup ? (
+ {isCheckingSession ? (
+
+
+
+
+
+
正在检查登录状态
+
请稍候,我们正在确认当前会话...
+
+
+ ) : needsPayKeySetup ? (
renderPayKeySetup("oauth-pay-key-setup")
) : loginSuccess ? (
diff --git a/frontend/components/common/leaderboard/leaderboard-main.tsx b/frontend/components/common/leaderboard/leaderboard-main.tsx
index a1c6dbc3..830cce03 100644
--- a/frontend/components/common/leaderboard/leaderboard-main.tsx
+++ b/frontend/components/common/leaderboard/leaderboard-main.tsx
@@ -14,7 +14,6 @@ import { useUser } from "@/contexts/user-context"
import { useLeaderboard } from "@/hooks/use-leaderboard"
import { LeaderboardPodium } from "@/components/common/leaderboard/leaderboard-podium"
import { LeaderboardTable } from "@/components/common/leaderboard/leaderboard-table"
-import { LoadingState } from "@/components/layout/loading"
import { cn } from "@/lib/utils"
/**
@@ -32,6 +31,7 @@ export function LeaderboardMain() {
loadNextPage,
refresh,
} = useLeaderboard()
+ const isInitialLoading = loading && items.length === 0
const currentUserEntry = React.useMemo(() => {
if (!myRank?.user) return undefined
@@ -49,10 +49,6 @@ export function LeaderboardMain() {
}
}, [items, myRank, user])
- if (loading && items.length === 0) {
- return
- }
-
return (
@@ -85,14 +81,14 @@ export function LeaderboardMain() {
@@ -83,10 +83,9 @@ function PodiumColumn({ entry, rank }: { entry: LeaderboardEntry; rank: PodiumRa
return (
- {/* 排名大字 */}
- {/* 头像 — 圆形 */}
@@ -102,13 +100,11 @@ function PodiumColumn({ entry, rank }: { entry: LeaderboardEntry; rank: PodiumRa
- {/* 用户名 */}
{entry.username}
- {/* 积分 */}
-
+
{formatBalance(entry.available_balance)}
diff --git a/frontend/package.json b/frontend/package.json
index dc9a917a..1a7ac323 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -38,7 +38,7 @@
"@tabler/icons-react": "^3.35.0",
"@tanstack/react-table": "^8.21.3",
"@tanstack/react-virtual": "^3.13.16",
- "axios": "^1.13.2",
+ "axios": "^1.15.0",
"babel-plugin-react-compiler": "^1.0.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index fc6293b0..1fd51fb7 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -96,8 +96,8 @@ importers:
specifier: ^3.13.16
version: 3.13.16(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
axios:
- specifier: ^1.13.2
- version: 1.13.2
+ specifier: ^1.15.0
+ version: 1.15.0
babel-plugin-react-compiler:
specifier: ^1.0.0
version: 1.0.0
@@ -1811,8 +1811,8 @@ packages:
resolution: {integrity: sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==}
engines: {node: '>=4'}
- axios@1.13.2:
- resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==}
+ axios@1.15.0:
+ resolution: {integrity: sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==}
axobject-query@4.1.0:
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
@@ -2328,8 +2328,8 @@ packages:
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
engines: {node: '>= 0.4'}
- form-data@4.0.4:
- resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==}
+ form-data@4.0.5:
+ resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
engines: {node: '>= 6'}
format@0.2.2:
@@ -3135,8 +3135,9 @@ packages:
property-information@7.1.0:
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
- proxy-from-env@1.1.0:
- resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
+ proxy-from-env@2.1.0:
+ resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
+ engines: {node: '>=10'}
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
@@ -5223,11 +5224,11 @@ snapshots:
axe-core@4.11.0: {}
- axios@1.13.2:
+ axios@1.15.0:
dependencies:
follow-redirects: 1.15.11
- form-data: 4.0.4
- proxy-from-env: 1.1.0
+ form-data: 4.0.5
+ proxy-from-env: 2.1.0
transitivePeerDependencies:
- debug
@@ -5877,7 +5878,7 @@ snapshots:
dependencies:
is-callable: 1.2.7
- form-data@4.0.4:
+ form-data@4.0.5:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
@@ -6908,7 +6909,7 @@ snapshots:
property-information@7.1.0: {}
- proxy-from-env@1.1.0: {}
+ proxy-from-env@2.1.0: {}
punycode@2.3.1: {}