auth.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import styles from "./auth.module.scss";
  2. import { IconButton } from "./button";
  3. import { useState, useEffect } from "react";
  4. import { useNavigate } from "react-router-dom";
  5. import { Path, SAAS_CHAT_URL } from "../constant";
  6. import { useAccessStore } from "../store";
  7. import Locale from "../locales";
  8. import Delete from "../icons/close.svg";
  9. import Arrow from "../icons/arrow.svg";
  10. import Logo from "../icons/logo.svg";
  11. import { useMobileScreen } from "@/app/utils";
  12. import BotIcon from "../icons/bot.svg";
  13. import { getClientConfig } from "../config/client";
  14. import { PasswordInput } from "./ui-lib";
  15. import LeftIcon from "@/app/icons/left.svg";
  16. import { safeLocalStorage } from "@/app/utils";
  17. import {
  18. trackSettingsPageGuideToCPaymentClick,
  19. trackAuthorizationPageButtonToCPaymentClick,
  20. } from "../utils/auth-settings-events";
  21. import clsx from "clsx";
  22. const storage = safeLocalStorage();
  23. export function AuthPage() {
  24. const navigate = useNavigate();
  25. const accessStore = useAccessStore();
  26. const goHome = () => navigate(Path.Home);
  27. const goChat = () => navigate(Path.Chat);
  28. const goSaas = () => {
  29. trackAuthorizationPageButtonToCPaymentClick();
  30. window.location.href = SAAS_CHAT_URL;
  31. };
  32. const resetAccessCode = () => {
  33. accessStore.update((access) => {
  34. access.openaiApiKey = "";
  35. access.accessCode = "";
  36. });
  37. }; // Reset access code to empty string
  38. useEffect(() => {
  39. if (getClientConfig()?.isApp) {
  40. navigate(Path.Settings);
  41. }
  42. // eslint-disable-next-line react-hooks/exhaustive-deps
  43. }, []);
  44. return (
  45. <div className={styles["auth-page"]}>
  46. <TopBanner></TopBanner>
  47. <div className={styles["auth-header"]}>
  48. <IconButton
  49. icon={<LeftIcon />}
  50. text={Locale.Auth.Return}
  51. onClick={() => navigate(Path.Home)}
  52. ></IconButton>
  53. </div>
  54. <div className={clsx("no-dark", styles["auth-logo"])}>
  55. <BotIcon />
  56. </div>
  57. <div className={styles["auth-title"]}>{Locale.Auth.Title}</div>
  58. <div className={styles["auth-tips"]}>{Locale.Auth.Tips}</div>
  59. <PasswordInput
  60. style={{ marginTop: "3vh", marginBottom: "3vh" }}
  61. aria={Locale.Settings.ShowPassword}
  62. aria-label={Locale.Auth.Input}
  63. value={accessStore.accessCode}
  64. type="text"
  65. placeholder={Locale.Auth.Input}
  66. onChange={(e) => {
  67. accessStore.update(
  68. (access) => (access.accessCode = e.currentTarget.value),
  69. );
  70. }}
  71. />
  72. {!accessStore.hideUserApiKey ? (
  73. <>
  74. <div className={styles["auth-tips"]}>{Locale.Auth.SubTips}</div>
  75. <PasswordInput
  76. style={{ marginTop: "3vh", marginBottom: "3vh" }}
  77. aria={Locale.Settings.ShowPassword}
  78. aria-label={Locale.Settings.Access.OpenAI.ApiKey.Placeholder}
  79. value={accessStore.openaiApiKey}
  80. type="text"
  81. placeholder={Locale.Settings.Access.OpenAI.ApiKey.Placeholder}
  82. onChange={(e) => {
  83. accessStore.update(
  84. (access) => (access.openaiApiKey = e.currentTarget.value),
  85. );
  86. }}
  87. />
  88. <PasswordInput
  89. style={{ marginTop: "3vh", marginBottom: "3vh" }}
  90. aria={Locale.Settings.ShowPassword}
  91. aria-label={Locale.Settings.Access.Google.ApiKey.Placeholder}
  92. value={accessStore.googleApiKey}
  93. type="text"
  94. placeholder={Locale.Settings.Access.Google.ApiKey.Placeholder}
  95. onChange={(e) => {
  96. accessStore.update(
  97. (access) => (access.googleApiKey = e.currentTarget.value),
  98. );
  99. }}
  100. />
  101. </>
  102. ) : null}
  103. <div className={styles["auth-actions"]}>
  104. <IconButton
  105. text={Locale.Auth.Confirm}
  106. type="primary"
  107. onClick={goChat}
  108. />
  109. <IconButton
  110. text={Locale.Auth.SaasTips}
  111. onClick={() => {
  112. goSaas();
  113. }}
  114. />
  115. </div>
  116. </div>
  117. );
  118. }
  119. function TopBanner() {
  120. const [isHovered, setIsHovered] = useState(false);
  121. const [isVisible, setIsVisible] = useState(true);
  122. const isMobile = useMobileScreen();
  123. useEffect(() => {
  124. // 检查 localStorage 中是否有标记
  125. const bannerDismissed = storage.getItem("bannerDismissed");
  126. // 如果标记不存在,存储默认值并显示横幅
  127. if (!bannerDismissed) {
  128. storage.setItem("bannerDismissed", "false");
  129. setIsVisible(true); // 显示横幅
  130. } else if (bannerDismissed === "true") {
  131. // 如果标记为 "true",则隐藏横幅
  132. setIsVisible(false);
  133. }
  134. }, []);
  135. const handleMouseEnter = () => {
  136. setIsHovered(true);
  137. };
  138. const handleMouseLeave = () => {
  139. setIsHovered(false);
  140. };
  141. const handleClose = () => {
  142. setIsVisible(false);
  143. storage.setItem("bannerDismissed", "true");
  144. };
  145. if (!isVisible) {
  146. return null;
  147. }
  148. return (
  149. <div
  150. className={styles["top-banner"]}
  151. onMouseEnter={handleMouseEnter}
  152. onMouseLeave={handleMouseLeave}
  153. >
  154. <div className={clsx(styles["top-banner-inner"], "no-dark")}>
  155. <Logo className={styles["top-banner-logo"]}></Logo>
  156. <span>
  157. {Locale.Auth.TopTips}
  158. <a
  159. href={SAAS_CHAT_URL}
  160. rel="stylesheet"
  161. onClick={() => {
  162. trackSettingsPageGuideToCPaymentClick();
  163. }}
  164. >
  165. {Locale.Settings.Access.SaasStart.ChatNow}
  166. <Arrow style={{ marginLeft: "4px" }} />
  167. </a>
  168. </span>
  169. </div>
  170. {(isHovered || isMobile) && (
  171. <Delete className={styles["top-banner-close"]} onClick={handleClose} />
  172. )}
  173. </div>
  174. );
  175. }