mirror of
https://github.com/woodchen-ink/Q58Connect.git
synced 2025-07-18 22:11:55 +08:00
- Separate platform and application-level authorization checks - Use current user session for authorization instead of passing user ID - Add detailed permission validation with specific error messages - Simplify authorization record creation and update logic - Remove redundant client and user checks
213 lines
7.0 KiB
TypeScript
213 lines
7.0 KiB
TypeScript
"use client";
|
||
|
||
import { useState } from "react";
|
||
import Image from "next/image";
|
||
import { useRouter } from "next/navigation";
|
||
import { handleAuthorizeAction } from "@/actions/authorizing";
|
||
import { Client } from "@prisma/client";
|
||
import {
|
||
ChevronsDownUp,
|
||
ChevronsUpDown,
|
||
GithubIcon,
|
||
Users,
|
||
} from "lucide-react";
|
||
|
||
import { Button } from "@/components/ui/button";
|
||
import {
|
||
Card,
|
||
CardContent,
|
||
CardFooter,
|
||
CardHeader,
|
||
CardTitle,
|
||
} from "@/components/ui/card";
|
||
import { Checkbox } from "@/components/ui/checkbox";
|
||
|
||
interface Permission {
|
||
id: string;
|
||
name: string;
|
||
description: string;
|
||
}
|
||
|
||
const permissions: Permission[] = [
|
||
{
|
||
id: "read_profile",
|
||
name: "个人用户数据",
|
||
description: "电子邮件地址(只读), 个人资料信息(只读)",
|
||
},
|
||
];
|
||
|
||
export function AuthorizationCard({
|
||
client,
|
||
oauthParams,
|
||
}: {
|
||
client: Client;
|
||
oauthParams: string;
|
||
}) {
|
||
const [expandedPermission, setExpandedPermission] = useState<string | null>(
|
||
null,
|
||
);
|
||
const [isAuthorizing, setIsAuthorizing] = useState(false);
|
||
const [error, setError] = useState<string | null>(null);
|
||
const router = useRouter();
|
||
|
||
const togglePermission = (id: string) => {
|
||
setExpandedPermission(expandedPermission === id ? null : id);
|
||
};
|
||
|
||
const authorizingHandler = async () => {
|
||
try {
|
||
setIsAuthorizing(true);
|
||
setError(null);
|
||
const result = await handleAuthorizeAction(
|
||
oauthParams,
|
||
client.id,
|
||
permissions[0].id,
|
||
);
|
||
|
||
if (result.error) {
|
||
setError(result.error);
|
||
setIsAuthorizing(false);
|
||
return;
|
||
}
|
||
|
||
if (result.redirectUrl) {
|
||
const url = await Promise.resolve(result.redirectUrl);
|
||
router.push(url);
|
||
}
|
||
} catch (error) {
|
||
setError("授权处理失败,请稍后重试");
|
||
setIsAuthorizing(false);
|
||
}
|
||
};
|
||
|
||
const handleCancel = () => {
|
||
// 从 URL 中获取 redirect_uri 参数
|
||
const params = new URLSearchParams(atob(oauthParams));
|
||
const redirectUri = params.get("redirect_uri");
|
||
if (redirectUri) {
|
||
// 添加错误参数返回
|
||
const redirectUrl = new URL(redirectUri);
|
||
redirectUrl.searchParams.set("error", "access_denied");
|
||
redirectUrl.searchParams.set("error_description", "用户取消了授权请求");
|
||
router.push(redirectUrl.toString());
|
||
} else {
|
||
// 如果没有 redirect_uri,返回首页
|
||
router.push("/");
|
||
}
|
||
};
|
||
|
||
return (
|
||
<Card className="w-full max-w-2xl transform transition-all duration-300 hover:shadow-lg">
|
||
<CardHeader className="space-y-4 text-center">
|
||
<div className="flex items-center justify-center space-x-6">
|
||
<div className="group relative">
|
||
<div className="absolute -inset-0.5 rounded-full bg-gradient-to-r from-pink-600 to-purple-600 opacity-50 blur transition duration-300 group-hover:opacity-75"></div>
|
||
<div className="relative flex h-20 w-20 items-center justify-center rounded-full bg-white">
|
||
{client.logo ? (
|
||
<Image
|
||
src={client.logo}
|
||
alt={client.name}
|
||
width={64}
|
||
height={64}
|
||
unoptimized
|
||
className="rounded-full object-cover"
|
||
/>
|
||
) : (
|
||
<span className="bg-gradient-to-r from-pink-600 to-purple-600 bg-clip-text text-4xl font-bold text-transparent">
|
||
{client.name[0].toUpperCase()}
|
||
</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<CardTitle className="bg-gradient-to-r from-pink-600 to-purple-600 bg-clip-text text-3xl font-bold text-transparent">
|
||
授权 {client.name}
|
||
</CardTitle>
|
||
<p className="text-sm text-gray-500">
|
||
该应用程序请求访问您的Q58论坛账号
|
||
</p>
|
||
</CardHeader>
|
||
|
||
<CardContent className="space-y-4">
|
||
{error && (
|
||
<div className="mb-4 rounded-lg border border-red-200 bg-red-50 p-4 text-red-600">
|
||
<p className="text-sm">
|
||
<strong>错误:</strong> {error}
|
||
</p>
|
||
</div>
|
||
)}
|
||
<div className="space-y-3 rounded-lg bg-gray-100 p-4 dark:bg-gray-800">
|
||
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||
请求的权限
|
||
</h3>
|
||
{permissions.map((permission) => (
|
||
<div
|
||
key={permission.id}
|
||
className="rounded-md border border-gray-200 bg-white p-4 shadow-sm transition-all duration-200 hover:border-purple-200 dark:border-gray-700 dark:bg-gray-900"
|
||
onClick={() => togglePermission(permission.id)}
|
||
>
|
||
<div className="flex cursor-pointer items-center justify-between">
|
||
<div className="flex items-center space-x-2">
|
||
<Checkbox id={permission.id} checked disabled />
|
||
<label
|
||
htmlFor={permission.id}
|
||
className="font-medium text-gray-900 dark:text-gray-100"
|
||
>
|
||
{permission.name}
|
||
</label>
|
||
</div>
|
||
{expandedPermission === permission.id ? (
|
||
<ChevronsDownUp className="h-4 w-4 text-gray-500" />
|
||
) : (
|
||
<ChevronsUpDown className="h-4 w-4 text-gray-500" />
|
||
)}
|
||
</div>
|
||
{expandedPermission === permission.id && (
|
||
<div className="mt-2 pl-6 text-sm text-gray-600 dark:text-gray-400">
|
||
{permission.description}
|
||
</div>
|
||
)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</CardContent>
|
||
|
||
<CardFooter className="flex flex-col items-center space-y-4">
|
||
<div className="flex space-x-4">
|
||
<Button
|
||
variant="outline"
|
||
className="min-w-[100px] transition-all duration-200 hover:bg-gray-100"
|
||
onClick={handleCancel}
|
||
disabled={isAuthorizing}
|
||
>
|
||
取消
|
||
</Button>
|
||
<Button
|
||
className="min-w-[100px] bg-gradient-to-r from-pink-600 to-purple-600 text-white transition-all duration-200 hover:from-pink-700 hover:to-purple-700"
|
||
onClick={authorizingHandler}
|
||
disabled={isAuthorizing}
|
||
>
|
||
{isAuthorizing ? (
|
||
<div className="flex items-center">
|
||
<div className="mr-2 h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent"></div>
|
||
授权中...
|
||
</div>
|
||
) : (
|
||
`授权 ${client.name}`
|
||
)}
|
||
</Button>
|
||
</div>
|
||
<p className="text-sm text-gray-500">
|
||
授权将重定向到 {client.redirectUri}
|
||
</p>
|
||
<div className="flex justify-center space-x-8 text-sm text-gray-500">
|
||
<div className="flex items-center">
|
||
<Users className="mr-1 h-4 w-4" />
|
||
<span>Q58论坛运营</span>
|
||
</div>
|
||
</div>
|
||
</CardFooter>
|
||
</Card>
|
||
);
|
||
}
|