Skip to content
2 changes: 1 addition & 1 deletion src/app/[locale]/dashboard/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export default function LoginForm({ onLoginSuccess }: LoginFormProps) {
</h2>

<form
className="mt-12 flex flex-col gap-5 text-lg leading-8 text-balance text-gray-600 dark:text-gray-400"
className="mt-12 flex flex-col gap-5 text-lg leading-8 text-gray-600 dark:text-gray-400"
onSubmit={onSubmit}
>
<YearMonthPicker onChange={handleMonthChange} />
Expand Down
2 changes: 1 addition & 1 deletion src/app/[locale]/get-key/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export default function GetKey() {
onChange={e => setInputOrderId(e.target.value)}
className={cn(
"block w-full rounded-lg border border-gray-300 bg-gray-50 px-4 py-3.5 text-base text-gray-900 transition-all dark:border-gray-600 dark:bg-gray-700 dark:text-white",
"placeholder:text-gray-500 focus:border-indigo-600 focus:bg-white focus:ring-1 focus:ring-indigo-600 focus:outline-none dark:placeholder:text-gray-400 dark:focus:bg-gray-700"
"placeholder:text-gray-500 focus:border-indigo-600 focus:bg-white focus:shadow-[0_0_0_1px_#4f46e5] focus:outline-none dark:placeholder:text-gray-400 dark:focus:bg-gray-700"
)}
/>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/app/[locale]/get-start/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ export default async function GetStart({
<div className="flex flex-1 flex-col justify-center px-6 py-12 lg:px-8">
<div className="px-6 py-12 sm:px-6 sm:py-8 lg:px-8">
<div className="mx-auto max-w-2xl text-center">
<h2 className="text-4xl font-semibold tracking-tight text-balance text-gray-900 sm:text-5xl dark:text-white">
<h2 className="text-4xl font-semibold tracking-tight text-gray-900 sm:text-5xl dark:text-white">
{t("title")}
</h2>
<p className="mx-auto mt-6 max-w-xl text-lg/8 text-pretty text-gray-600">
<p className="mx-auto mt-6 max-w-xl text-lg/8 text-gray-600">
{t.rich("description", {
br: () => <br />,
})}
Expand Down
6 changes: 4 additions & 2 deletions src/app/[locale]/projects/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ProjectCardView from "@/components/ProjectCardView";
import HomeButton from "@/components/HomeButton";
import SourceTracker from "@/components/SourceTracker";
import OrderInfoModalWrapper from "@/components/OrderInfoModalWrapper";
import DownloadModalWrapper from "@/components/DownloadModalWrapper";
import { Suspense } from "react";
import { Divider } from "@heroui/divider";

Expand Down Expand Up @@ -40,16 +41,17 @@ export default async function ProjectsPage({
<SourceTracker source={source} />
<Suspense fallback={null}>
<OrderInfoModalWrapper />
<DownloadModalWrapper />
</Suspense>
<BackgroundLines className="min-h-screen">
<div className="container mx-auto px-3 py-10">
<HomeButton className="absolute" />
<div className="px-6 py-12 sm:px-6 sm:py-8 lg:px-8">
<div className="mx-auto max-w-2xl text-center">
<h2 className="text-4xl font-semibold tracking-tight text-balance text-gray-900 sm:text-5xl dark:text-white">
<h2 className="text-4xl font-semibold tracking-tight text-gray-900 sm:text-5xl dark:text-white">
{t("title")}
</h2>
<p className="mx-auto mt-6 max-w-xl text-lg/8 text-pretty text-gray-600">
<p className="mx-auto mt-6 max-w-xl text-lg/8 text-gray-600">
{t.rich("description", {
br: () => <br />,
})}
Expand Down
20 changes: 20 additions & 0 deletions src/app/[locale]/transfer/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ export default function Transmission() {
const [fromCdk, setFromCdk] = useState("");
const [fromCdkDescription, setFromCdkDescription] = useState("");
const [fromCdkValid, setFromCdkValid] = useState(false);
const [fromCdkHasSameError, setFromCdkHasSameError] = useState(false);
const [toCdk, setToCdk] = useState("");
const [toCdkDescription, setToCdkDescription] = useState("");
const [toCdkValid, setToCdkValid] = useState(false);
const [toCdkHasSameError, setToCdkHasSameError] = useState(false);
const [showOrderId, setShowOrderId] = useState("");
const [transfering, setTransfering] = useState(false);

Expand Down Expand Up @@ -72,6 +74,7 @@ export default function Transmission() {
if (cdk === toCdkRef.current) {
setFromCdkDescription(t("sameCdk"));
setFromCdkValid(false);
setFromCdkHasSameError(true);
return;
}

Expand Down Expand Up @@ -107,6 +110,7 @@ export default function Transmission() {
if (cdk === fromCdkRef.current) {
setToCdkDescription(t("sameCdk"));
setToCdkValid(false);
setToCdkHasSameError(true);
return;
}

Expand Down Expand Up @@ -136,6 +140,14 @@ export default function Transmission() {
setFromCdk(value);
setFromCdkDescription("");
setFromCdkValid(false);
setFromCdkHasSameError(false);
// 如果目标CDK之前因为"相同CDK"而报错,现在来源改变了需要重新验证
if (toCdkHasSameError && toCdkRef.current) {
setToCdkDescription("");
setToCdkValid(false);
setToCdkHasSameError(false);
requestToCdkDebounced(toCdkRef.current);
}
requestFromCdkDebounced(value);
}

Expand All @@ -144,6 +156,14 @@ export default function Transmission() {
setToCdk(value);
setToCdkDescription("");
setToCdkValid(false);
setToCdkHasSameError(false);
// 如果来源CDK之前因为"相同CDK"而报错,现在目标改变了需要重新验证
if (fromCdkHasSameError && fromCdkRef.current) {
setFromCdkDescription("");
setFromCdkValid(false);
setFromCdkHasSameError(false);
requestFromCdkDebounced(fromCdkRef.current);
}
requestToCdkDebounced(value);
}

Expand Down
16 changes: 10 additions & 6 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,22 @@
}
}

html {
scrollbar-width: thin;
}

body {
color: var(--foreground);
background: var(--background);
font-family: Arial, Helvetica, sans-serif;
overflow-y: scroll;
/* 始终显示滚动条,防止抖动(兼容不支持 scrollbar-gutter 的浏览器) */
}

html {
scrollbar-width: thin;
scrollbar-gutter: stable;
/* 为滚动条预留空间,防止抖动 */
overflow-y: auto;
/* 正常显示滚动条 */
/* 渐变遮罩工具类(兼容需要 -webkit- 前缀的旧版浏览器) */
.radial-mask-fade {
-webkit-mask-image: radial-gradient(900px at center, transparent 30%, white);
mask-image: radial-gradient(900px at center, transparent 30%, white);
}

.custom-scrollbar {
Expand Down
202 changes: 202 additions & 0 deletions src/components/DownloadModalWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
"use client";

import { useSearchParams } from "next/navigation";
import { useTranslations } from "next-intl";
import { useEffect, useRef, useState } from "react";
import { CLIENT_BACKEND } from "@/app/requests/misc";
import { Button, Modal, ModalBody, ModalContent, ModalFooter } from "@heroui/react";
import { getGroupUrl } from "@/lib/utils/constant";

export default function DownloadModalWrapper() {
const searchParams = useSearchParams();
const download = searchParams.get("download");
const t = useTranslations("Download");
const common = useTranslations("Common");

const [showDownloadModal, setShowDownloadModal] = useState(false);
const [isLoadingAnimation, setIsLoadingAnimation] = useState(false);
// 确保只触发一次下载
const downloadTriggeredRef = useRef(false);

// 触发下载的 effect
useEffect(() => {
if (download && !downloadTriggeredRef.current) {
downloadTriggeredRef.current = true;

const url = `${CLIENT_BACKEND}/api/resources/download/${download}`;
window.location.href = url;

setShowDownloadModal(true);
setIsLoadingAnimation(true);

console.log(`downloading ${url}`);

const s = new URLSearchParams(window.location.search);
s.delete("download");
const newUrl = s.toString() ? `${window.location.pathname}?${s}` : window.location.pathname;
window.history.replaceState(null, "", newUrl);
}
}, [download]);

// 单独处理加载动画的 timer,避免 React 严格模式下 cleanup 导致 timer 被清除
useEffect(() => {
if (showDownloadModal && isLoadingAnimation) {
const timerId = setTimeout(() => {
setIsLoadingAnimation(false);
}, 1000);

return () => {
clearTimeout(timerId);
};
}
}, [showDownloadModal, isLoadingAnimation]);

const handleCloseModal = () => {
setShowDownloadModal(false);
setIsLoadingAnimation(false);
};

if (!download && !showDownloadModal) {
return null;
}

return (
<Modal
isDismissable={false}
isOpen={showDownloadModal}
onOpenChange={setShowDownloadModal}
onClose={handleCloseModal}
backdrop="opaque"
size="2xl"
placement="center"
scrollBehavior="inside"
>
<ModalContent>
<>
<ModalBody>
<div className="space-y-6 py-8">
<div className="text-center">
<div className="mb-4">
<div
className={`mb-4 inline-flex h-16 w-16 items-center justify-center rounded-full ${
isLoadingAnimation
? "bg-primary-100 dark:bg-primary-900"
: "bg-green-100 dark:bg-green-900"
}`}
>
{isLoadingAnimation ? (
<svg
className="text-primary-600 dark:text-primary-400 h-8 w-8 animate-spin"
fill="none"
viewBox="0 0 24 24"
>
<circle
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
className="opacity-25"
></circle>
<path
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
className="opacity-75"
></path>
</svg>
) : (
<svg
className="h-8 w-8 text-green-600 dark:text-green-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M5 13l4 4L19 7"
/>
</svg>
)}
</div>
<h3 className="mb-2 text-xl font-semibold text-gray-900 dark:text-white">
{t("downloadStartedForShare")}
</h3>
<p className="text-gray-600 dark:text-gray-300">
{isLoadingAnimation ? t("pleaseWait") : t("downloadInProgress")}
</p>
</div>
</div>

<div className="rounded-lg border border-orange-200 bg-orange-50 p-4 dark:border-orange-800 dark:bg-orange-900/20">
<div className="flex items-start">
<div className="flex-shrink-0">
<svg
className="h-5 w-5 text-orange-400"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
clipRule="evenodd"
/>
</svg>
</div>
<div className="ml-3">
<h4 className="text-sm font-medium text-orange-800 dark:text-orange-300">
{t("importantNote")}
</h4>
<div className="mt-1 text-sm text-orange-700 dark:text-orange-400">
<p>{t("downloadWarning")}</p>
</div>
</div>
</div>
</div>

<div className="rounded-lg border border-blue-200 bg-blue-50 p-4 dark:border-blue-800 dark:bg-blue-900/20">
<div className="flex items-start">
<div className="flex-shrink-0">
<svg className="h-5 w-5 text-blue-400" viewBox="0 0 20 20" fill="currentColor">
<path
fillRule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
clipRule="evenodd"
/>
</svg>
</div>
<button
type="button"
className="group cursor-pointer text-left"
onClick={() => {
getGroupUrl().then(url => {
window.open(url, "_blank");
});
}}
>
<div className="ml-3">
<h4 className="text-sm font-medium text-blue-800 group-hover:underline dark:text-blue-300">
{t("downloadProblems")}
</h4>
<div className="mt-1 text-sm text-blue-700 group-hover:underline dark:text-blue-400">
<ul className="list-inside list-disc space-y-1">
<li className="group-hover:underline">{t("troubleshoot1")}</li>
</ul>
</div>
</div>
</button>
</div>
</div>
</div>
</ModalBody>
<ModalFooter>
<Button color="primary" onPress={handleCloseModal} className="w-full">
{common("close")}
</Button>
</ModalFooter>
</>
</ModalContent>
</Modal>
);
}
8 changes: 2 additions & 6 deletions src/components/LoadingState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,9 @@ export default function LoadingState({ title, description }: LoadingStateProps)
<div className="px-6 py-24 sm:px-6 sm:py-32 lg:px-8">
<div className="mx-auto max-w-2xl text-center">
{title && (
<h2 className="text-3xl font-semibold tracking-tight text-balance sm:text-4xl">
{title}
</h2>
)}
{description && (
<div className="mt-6 text-lg/8 text-pretty text-gray-600">{description}</div>
<h2 className="text-3xl font-semibold tracking-tight sm:text-4xl">{title}</h2>
)}
{description && <div className="mt-6 text-lg/8 text-gray-600">{description}</div>}

<div className="mt-8 flex justify-center">
<div className="h-16 w-16 animate-spin rounded-full border-t-2 border-b-2 border-indigo-500"></div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/MultiStepLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export const MultiStepLoader = ({
<LoaderCore value={currentState} loadingStates={loadingStates} />
</div>

<div className="absolute inset-x-0 bottom-0 z-20 h-full bg-white bg-gradient-to-t [mask-image:radial-gradient(900px_at_center,transparent_30%,white)] dark:bg-black" />
<div className="radial-mask-fade absolute inset-x-0 bottom-0 z-20 h-full bg-white bg-gradient-to-t dark:bg-black" />
</motion.div>
)}
</AnimatePresence>
Expand Down
2 changes: 1 addition & 1 deletion src/components/OrderInfoModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export default function OrderInfoModal({ orderId, onClose }: OrderInfoModalProps
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="bg-opacity-25 fixed inset-0 bg-black backdrop-blur-sm" />
<div className="fixed inset-0 bg-black/25 backdrop-blur-sm" />
</TransitionChild>

<div className="fixed inset-0 overflow-y-auto">
Expand Down
Loading
Loading