feat: adjust oauth callback & profile info (#110)

* chore: adjust oauth callback

* chore: debug profile info

* chore: modify logs logic and display

* chore: add logout

* chore: add userinfo

* chore: remove advanced settings
This commit is contained in:
BiggerRain
2025-01-22 20:00:38 +08:00
committed by GitHub
parent 9f0dcbc3dd
commit 6a272bda50
17 changed files with 136 additions and 111 deletions

4
.env
View File

@@ -1,3 +1,3 @@
COCO_SERVER_URL=https://infini.tpddns.cn:27200 #https://coco.infini.cloud # http://localhost:2900
COCO_SERVER_URL=http://infini.tpddns.cn:27200 #https://coco.infini.cloud # http://localhost:9000
COCO_WEBSOCKET_URL=wss://infini.tpddns.cn:27200/ws #wss://coco.infini.cloud/ws # ws://localhost:2900/ws
COCO_WEBSOCKET_URL=ws://infini.tpddns.cn:27200/ws #wss://coco.infini.cloud/ws # ws://localhost:9000/ws

View File

@@ -52,9 +52,6 @@
{
"identifier": "http:default",
"allow": [
{
"url": "http://localhost:2900"
},
{
"url": "http://localhost:9000"
},
@@ -62,7 +59,7 @@
"url": "https://coco.infini.cloud"
},
{
"url": "https://infini.tpddns.cn:27200"
"url": "http://infini.tpddns.cn:27200"
}
],
"deny": []

View File

@@ -54,7 +54,7 @@ export const tauriFetch = async <T = any>({
headers["Content-Type"] = "application/json";
}
headers["X-API-TOKEN"] = auth?.token || "";
headers["X-API-TOKEN"] = headers["X-API-TOKEN"] || auth?.token || "";
// debug API
const requestInfo = {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 621 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 439 B

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect, useCallback } from "react";
import { X } from "lucide-react";
import { Maximize, X } from "lucide-react";
import { useLogStore } from "@/stores/logStore";
@@ -7,11 +7,17 @@ const ApiDetails: React.FC = () => {
const logs = useLogStore((state) => state.logs);
const [showAPIDetails, setShowAPIDetails] = useState(false);
const [showFullscreen, setShowFullscreen] = useState(false);
const [showIndex, setShowIndex] = useState<number | null>(null);
const toggleAPIDetails = useCallback(() => {
setShowAPIDetails((prev) => !prev);
}, []);
const toggleFullscreen = useCallback(() => {
setShowFullscreen((prev) => !prev);
}, []);
const handleKeyDown = useCallback(
(event: KeyboardEvent) => {
if (event.metaKey && event.key === "d") {
@@ -38,15 +44,18 @@ const ApiDetails: React.FC = () => {
return (
<div
className={`fixed bottom-0 z-[2000] overflow-y-auto overflow-x-hidden transition-all duration-300 ease-in-out ${
className={`fixed bottom-0 z-[2000] overflow-y-auto overflow-x-hidden transition-all duration-300 ease-in-out border-t ${
showAPIDetails ? "h-[60vh] w-full bg-white shadow-lg rounded-t-lg" : ""
}`}
} ${showFullscreen ? "h-[100vh]" : "h-[60vh]"}`}
>
{showAPIDetails && (
<div className="p-6">
<h3 className="text-lg font-bold text-gray-800 border-b pb-2 cursor-pointer flex justify-between" onClick={toggleAPIDetails}>
<h3 className="text-lg font-bold text-gray-800 border-b pb-2 cursor-pointer flex justify-between">
API Logs (Latest 10)
<X />
<div className="flex gap-4">
<Maximize onClick={toggleFullscreen} />
<X onClick={toggleAPIDetails} />
</div>
</h3>
<div className="space-y-4 mt-4 ">
{logs.map((log, index) => (
@@ -55,7 +64,7 @@ const ApiDetails: React.FC = () => {
className="p-4 border rounded-md shadow-sm bg-gray-50"
>
<h4 className="font-semibold text-gray-800">
Request {index + 1}:
Latest Request {index + 1}:
</h4>
<div className="text-sm text-gray-700 mt-1">
<pre className="bg-gray-100 p-2 rounded-md whitespace-pre-wrap">
@@ -65,13 +74,23 @@ const ApiDetails: React.FC = () => {
{log.response && (
<>
<h4 className="font-semibold text-green-800 mt-4">
Response:
Response:{" "}
<button
onClick={() =>
setShowIndex(showIndex === index ? null : index)
}
className="text-sm text-blue-600 mt-2 underline"
>
{showIndex === index ? "Collapse" : "Expand"}
</button>
</h4>
<div className="text-sm text-gray-700 mt-1">
<pre className="bg-green-100 p-2 rounded-md text-green-700 whitespace-pre-wrap">
{JSON.stringify(log.response, null, 2)}
</pre>
</div>
{showIndex === index ? (
<div className="text-sm text-gray-700 mt-1">
<pre className="bg-green-100 p-2 rounded-md text-green-700 whitespace-pre-wrap">
{JSON.stringify(log.response, null, 2)}
</pre>
</div>
) : null}
</>
)}
{log.error && (

View File

@@ -17,12 +17,7 @@ import {
} from "@tauri-apps/plugin-deep-link";
export default function CocoCloud() {
const appStore = useAppStore();
const [lastUrl, setLastUrl] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const [info, setInfo] = useState<string | null>(null);
const [info2, setInfo2] = useState<string | null>(null);
const [isConnect] = useState(true);
@@ -31,19 +26,19 @@ export default function CocoCloud() {
const endpoint_http = useAppStore((state) => state.endpoint_http);
const { auth, setAuth } = useAuthStore();
const userInfo = useAuthStore((state) => state.userInfo);
const setUserInfo = useAuthStore((state) => state.setUserInfo);
const [loading, setLoading] = useState(false);
const getProfile = async () => {
const response: any = await tauriFetch({
url: `/profile`,
url: `/provider/account/profile`,
method: "GET",
baseURL: appStore.endpoint_http,
});
console.log("getProfile", response);
setInfo2(JSON.stringify(response))
}
setUserInfo(response.data || {});
};
const handleOAuthCallback = async (
code: string | null,
@@ -54,37 +49,40 @@ export default function CocoCloud() {
return;
}
// mock
// code = "d11feeab43f6c3e48a43"
// provider = "coco-cloud"
try {
console.log("Handling OAuth callback:", { code, provider });
const response: any = await tauriFetch({
url: `/auth/request_access_token?request_id=${app_uid}`,
method: "GET",
baseURL: appStore.endpoint_http,
headers: {
"X-API-TOKEN": code,
},
});
// { "access_token":xxx, "expire_at": "unix_timestamp_in_s" }
console.log("response", response);
setInfo(JSON.stringify(response))
console.log(
"response",
`/auth/request_access_token?request_id=${app_uid}`,
code,
response
);
getProfile()
if (response.data?.access_token) {
await setAuth({
token: response.data?.access_token,
expires: response.data?.expire_at,
plan: { upgraded: false, last_checked: 0 },
});
await setAuth({
token: response.data?.access_token,
expires: response.data?.expire_at,
plan: { upgraded: false, last_checked: 0 },
});
getProfile();
} else {
setError("Sign in failed: " + response.data?.error?.reason);
}
getCurrentWindow()
.setFocus()
.catch(() => {});
} catch (e) {
console.error("Sign in failed:", error);
setError("Sign in failed: catch");
await setAuth(undefined);
throw error;
} finally {
@@ -94,21 +92,23 @@ export default function CocoCloud() {
const handleUrl = (url: string) => {
try {
// url = "coco://oauth_callback?code=cu8ag982sdb06e0j6k3g&provider=coco-cloud"
const urlObject = new URL(url);
console.error("1111111:", urlObject);
console.log("urlObject:", urlObject);
switch (urlObject.pathname) {
case "oauth_callback":
const code = urlObject.searchParams.get("code");
const provider = urlObject.searchParams.get("provider");
handleOAuthCallback(code, provider);
break;
const code = urlObject.searchParams.get("code");
const provider = urlObject.searchParams.get("provider");
handleOAuthCallback(code, provider);
default:
console.log("Unhandled deep link path:", urlObject.pathname);
}
// switch (urlObject.hostname) {
// case "/oauth_callback":
// break;
// default:
// console.log("Unhandled deep link path:", urlObject.pathname);
// }
setLastUrl(url);
} catch (err) {
console.error("Failed to parse URL:", err);
setError("Invalid URL format");
@@ -117,15 +117,12 @@ export default function CocoCloud() {
// Fetch the initial deep link intent
useEffect(() => {
// coco://oauth_calback?code=&provider=
// handleOAuthCallback("cu0bpu53q95r66at2010", "coco-cloud");
//
// handleUrl("");
getCurrentDeepLinkUrls()
.then((urls) => {
console.error("22222 URLs:", urls);
console.log("URLs:", urls);
if (urls && urls.length > 0) {
handleUrl(urls[0]);
console.error("URLs:", urls);
}
})
.catch((err) => {
@@ -141,22 +138,11 @@ export default function CocoCloud() {
}, []);
function LoginClick() {
let uid = app_uid;
if (!uid) {
uid = uuidv4();
setAppUid(uid);
}
// const response = await fetch("/api/register", {
// method: "POST",
// headers: { "Content-Type": "application/json" },
// body: JSON.stringify({ uid }),
// });
// const { token } = await response.json();
// localStorage.setItem("auth_token", token);
let uid = uuidv4();
setAppUid(uid);
OpenBrowserURL(
`${endpoint_http}/sso/login/github?provider=coco-cloud&product=coco&request_id=${uid}`
`${endpoint_http}/sso/login/?provider=coco-cloud&product=coco&request_id=${uid}`
);
setLoading(true);
@@ -169,24 +155,8 @@ export default function CocoCloud() {
<main className="flex-1">
<div>
{error && (
<div className="text-red-500 dark:text-red-400">Error: {error}</div>
)}
{lastUrl && (
<div className="text-gray-700 dark:text-gray-300">
Last opened URL: {lastUrl}
</div>
)}
{info && (
<div className="text-gray-700 dark:text-gray-300">
Info : {info}
</div>
)}
{info2 && (
<div className="text-gray-700 dark:text-gray-300">
info2 : {info2}
<div className="text-red-500 dark:text-red-400 p-4">
Error: {error}
</div>
)}
</div>
@@ -231,7 +201,7 @@ export default function CocoCloud() {
Account Information
</h2>
{auth ? (
<UserProfile name="Rain" email="an121245@gmail.com" />
<UserProfile userInfo={userInfo} />
) : (
<button
className="px-6 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors"

View File

@@ -1,27 +1,65 @@
import { User, Edit } from 'lucide-react';
import { User, Edit, LogOut } from "lucide-react";
interface UserProfileProps {
name: string;
import { useAuthStore } from "@/stores/authStore";
interface UserPreferences {
theme: "dark" | "light";
language: string;
}
interface UserInfo {
username: string;
email: string;
avatar?: string;
roles: string[]; // ["admin", "editor"]
preferences: UserPreferences;
}
export function UserProfile({ name, email }: UserProfileProps) {
interface UserProfileProps {
userInfo: UserInfo;
}
export function UserProfile({ userInfo }: UserProfileProps) {
const setAuth = useAuthStore((state) => state.setAuth);
const setUserInfo = useAuthStore((state) => state.setUserInfo);
const handleLogout = () => {
setAuth(undefined);
setUserInfo({});
};
return (
<div className="space-y-6">
<div className="flex items-center space-x-4">
<div className="w-12 h-12 bg-gray-100 rounded-full flex items-center justify-center">
<User className="w-6 h-6 text-gray-500" />
{userInfo.avatar ? (
<img
src={userInfo.avatar}
alt=""
className="w-6 h-6"
/>
) : (
<User className="w-6 h-6 text-gray-500" />
)}
</div>
<div className="flex-1">
<div className="flex items-center space-x-2">
<span className="font-medium text-gray-900">{name}</span>
<span className="font-medium text-gray-900">
{userInfo.username || "-"}
</span>
<button className="text-gray-400 hover:text-gray-600">
<Edit className="w-4 h-4" />
</button>
</div>
<span className="text-sm text-gray-500">{email}</span>
<span className="text-sm text-gray-500">{userInfo.email || "-"}</span>
</div>
</div>
<button
onClick={handleLogout}
className="flex items-center space-x-1 text-red-500 hover:text-red-600"
>
<LogOut className="w-4 h-4" />
<span>Logout</span>
</button>
</div>
);
}
}

View File

@@ -7,9 +7,8 @@ import { AppEndpoint } from "@/utils/tauri";
const ENDPOINTS = [
{ value: "https://coco.infini.cloud", label: "https://coco.infini.cloud" },
{ value: "http://localhost:2900", label: "http://localhost:2900" },
{ value: "http://localhost:9000", label: "http://localhost:9000" },
{ value: "https://infini.tpddns.cn:27200", label: "https://infini.tpddns.cn:27200" },
{ value: "http://infini.tpddns.cn:27200", label: "http://infini.tpddns.cn:27200" },
];
export default function AdvancedSettings() {

View File

@@ -5,12 +5,12 @@ import { useSearchParams } from "react-router-dom";
import SettingsPanel from "./SettingsPanel";
import GeneralSettings from "./GeneralSettings";
import AdvancedSettings from "./AdvancedSettings";
import AboutView from "./AboutView";
import CocoCloud from "@/components/Auth/CocoCloud"
import Footer from "../Footer";
import { useTheme } from "../../contexts/ThemeContext";
import { AppTheme } from "../../utils/tauri";
import ApiDetails from "@/components/AppAI/ApiDetails";
function SettingsPage() {
const [defaultIndex, setDefaultIndex] = useState<number>(0);
@@ -82,7 +82,9 @@ function SettingsPage() {
</TabPanel>
<TabPanel>
<SettingsPanel title="">
<AdvancedSettings />
<div className="text-gray-600 dark:text-gray-400">
Advanced Settings content
</div>
</SettingsPanel>
</TabPanel>
<TabPanel>
@@ -95,6 +97,8 @@ function SettingsPage() {
</div>
</div>
<Footer />
<ApiDetails />
</div>
);
}

View File

@@ -102,7 +102,7 @@ export default function LoginPage() {
<button
className="w-[60px] h-[60px] bg-white hover:bg-gray-100 text-black rounded-full py-3 px-4 flex items-center justify-center space-x-2 transition-colors"
onClick={handleGithubSignIn}
onClick={handleAppleSignIn}
>
<img
src={AppleImg}
@@ -113,7 +113,7 @@ export default function LoginPage() {
<button
className="w-[60px] h-[60px] bg-white hover:bg-gray-100 text-black rounded-full py-3 px-4 flex items-center justify-center space-x-2 transition-colors"
onClick={handleAppleSignIn}
onClick={handleGithubSignIn}
>
<img
src={GithubImg}

View File

@@ -18,6 +18,7 @@ export type AuthStore = {
};
export type IAuthStore = {
[x: string]: any;
auth: AuthStore | undefined;
userInfo: any;
setAuth: (auth: AuthStore | undefined) => void;

View File

@@ -19,9 +19,9 @@ export const useLogStore = create<ILogStore>()(
logs: [],
addLog: (log: ApiLog) =>
set((state) => {
const newLogs = [...state.logs, log];
const newLogs = [log, ...state.logs];
if (newLogs.length > 10) {
newLogs.shift();
newLogs.pop();
}
return { logs: newLogs };
}),

View File

@@ -2,6 +2,3 @@ export const clientEnv = {
COCO_SERVER_URL: import.meta.env.COCO_SERVER_URL ?? "https://coco.infini.cloud",
COCO_WEBSOCKET_URL: import.meta.env.COCO_WEBSOCKET_URL ?? "wss://coco.infini.cloud/ws",
};
// http://localhost:2900
// ws://localhost:2900/ws