mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 11:37:47 +01:00
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:
4
.env
4
.env
@@ -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
|
||||
@@ -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": []
|
||||
|
||||
@@ -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 |
@@ -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>
|
||||
{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 && (
|
||||
|
||||
@@ -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))
|
||||
|
||||
getProfile()
|
||||
console.log(
|
||||
"response",
|
||||
`/auth/request_access_token?request_id=${app_uid}`,
|
||||
code,
|
||||
response
|
||||
);
|
||||
|
||||
if (response.data?.access_token) {
|
||||
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;
|
||||
|
||||
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();
|
||||
let 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);
|
||||
|
||||
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"
|
||||
|
||||
@@ -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">
|
||||
{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>
|
||||
);
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -18,6 +18,7 @@ export type AuthStore = {
|
||||
};
|
||||
|
||||
export type IAuthStore = {
|
||||
[x: string]: any;
|
||||
auth: AuthStore | undefined;
|
||||
userInfo: any;
|
||||
setAuth: (auth: AuthStore | undefined) => void;
|
||||
|
||||
@@ -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 };
|
||||
}),
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user