main
tsx 185 lines 6.68 KB
Raw
1 import { useEffect, useState } from "react";
2 import { useLang } from "../context/LanguageContext";
3 import { SupportedLanguage } from "../utils/i18n";
4 import haptics from "./../utils/haptics";
5 import { debug, error } from "@tauri-apps/plugin-log";
6 import { invoke } from "@tauri-apps/api/core";
7 import { OauthRedirect, TokenResponse } from "../lib/crate/generated";
8 import { listen } from "@tauri-apps/api/event";
9 import { openUrl } from "@tauri-apps/plugin-opener";
10 import { useAccount } from "../context/AccountContext";
11 import { cancel } from "@fabianlars/tauri-plugin-oauth";
12
13 export default function Profile() {
14 const handleNavClick = async () => {
15 await haptics.navigation();
16 };
17 const { t, lang, setLang } = useLang();
18 const { user, onLoggedIn, onLoggedOut } = useAccount();
19 const isRTL = lang === "fa";
20 const dir = isRTL ? "rtl" : "ltr";
21 const align = isRTL ? "text-right" : "text-left";
22 const [port, setPort] = useState<number>();
23
24 const handleOAuthLogin = async (provider: "google" | "apple") => {
25 try {
26 // Handle all operations in the Rust backend.
27 const port = await invoke<number>("start_server");
28 setPort(port);
29 debug(`OAuth server started on port ${port}`);
30 // Tauri OAuth plugin server.
31 const redirectUri = `http://localhost:${port}`;
32 if (provider === "google") {
33 const baseUrl = await invoke<string>("get_consent_url", {
34 redirectUri,
35 });
36 debug(`Google OAuth URL: ${baseUrl}`);
37 await openUrl(baseUrl);
38 }
39 } catch (e) {
40 error(`Error: ${e}`);
41 }
42 };
43
44 const handleLogout = async () => {
45 onLoggedOut();
46 };
47
48 // Don't forget to stop the server when you're done
49 async function stopOAuthServer() {
50 if (!port) return;
51
52 try {
53 await cancel(port);
54 console.log("OAuth server stopped");
55 } catch (error) {
56 console.error("Error stopping OAuth server:", error);
57 }
58 }
59
60 const init = async () => {
61 try {
62 // Listen for OAuth emitted events from the Rust backend.
63 await listen<OauthRedirect>("oauth_redirect_uri", async (event) => {
64 debug(
65 `Received OAuth redirect event: ${JSON.stringify(event.payload)}`,
66 );
67 const oauthRedirect = event.payload;
68 // Handle the redirect event here
69 try {
70 const res = await invoke<TokenResponse>("get_token", {
71 oauthRedirect,
72 });
73 debug(`OAuth response: ${JSON.stringify(res)}`);
74 onLoggedIn("google", res.access_token);
75 await stopOAuthServer();
76 } catch (e) {
77 error(`Error processing OAuth response: ${JSON.stringify(e)}`);
78 }
79 });
80 } catch (e) {
81 error(`Error listening OAuth event: ${e}`);
82 }
83 };
84
85 useEffect(() => {
86 init();
87 return () => {
88 stopOAuthServer();
89 };
90 });
91
92 return (
93 <section dir={dir} className={`flex flex-col items-center py-8 ${align}`}>
94 {user && (
95 <>
96 {/* Profile Header */}
97 <div className="bg-white rounded-lg shadow-md p-6">
98 <div className="flex items-center space-x-4 mb-4">
99 <img
100 src={user.picture}
101 alt="Avatar"
102 className="w-20 h-20 mb-4 rounded-full border-4 border-food-orange bg-gradient-to-br from-food-orange to-food-red "
103 />
104 <div>
105 <h2 className="text-2xl font-bold text-gray-800">
106 {user.name}
107 </h2>
108 </div>
109 </div>
110 <div className="px-8 py-4 mt-2 w-full max-w-xs">
111 <button
112 onClick={async () => {
113 await haptics.buttonPress();
114 await handleLogout();
115 }}
116 className="w-full bg-white hover:bg-gray-50 active:bg-gray-100 text-gray-700 font-medium rounded-lg px-4 py-3 flex items-center justify-center gap-3 transition-colors border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 shadow-sm"
117 aria-label={t.googleOAuthLogin}
118 >
119 {t.logout}
120 </button>
121 </div>
122 </div>
123 </>
124 )}
125 {/* LOGGED OUT */}
126 {!user && (
127 <div className=" rounded-lg shadow-md p-6">
128 <div className="flex items-center space-x-4 mb-4">
129 {t.oAuthLoginExplanation}
130 </div>
131 <button
132 onClick={async () => {
133 await haptics.buttonPress();
134 await handleOAuthLogin("google");
135 }}
136 className="w-full bg-white hover:bg-gray-50 active:bg-gray-100 text-gray-700 font-medium rounded-lg px-4 py-3 flex items-center justify-center gap-3 transition-colors border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 shadow-sm"
137 aria-label={t.googleOAuthLogin}
138 >
139 <svg
140 className="w-5 h-5"
141 viewBox="0 0 24 24"
142 xmlns="http://www.w3.org/2000/svg"
143 >
144 <path
145 d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
146 fill="#4285F4"
147 />
148 <path
149 d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
150 fill="#34A853"
151 />
152 <path
153 d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
154 fill="#FBBC05"
155 />
156 <path
157 d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
158 fill="#EA4335"
159 />
160 </svg>
161 <span className="text-sm font-medium leading-none">
162 {t.googleOAuthLogin}
163 </span>
164 </button>
165 </div>
166 )}
167 <div className="bg-white/80 shadow rounded-xl px-8 py-4 mt-2 w-full max-w-xs">
168 <h2 className="text-lg font-bold mb-2 text-blue-700">App Settings</h2>
169 <div>
170 <select
171 className="rounded border px-2 py-1 bg-white text-sm text-blue-700 cursor-pointer shadow"
172 value={lang}
173 onClick={handleNavClick}
174 onChange={(e) => setLang(e.target.value as SupportedLanguage)}
175 aria-label="Change language"
176 >
177 <option value="fa">فارسی</option>
178 <option value="en">English</option>
179 <option value="sv">Svenska</option>
180 </select>
181 </div>
182 </div>
183 </section>
184 );
185 }