auth.tsx 5.6 KB

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