UserArea.jsx 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. /*
  2. Copyright (C) 2025 QuantumNous
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <https://www.gnu.org/licenses/>.
  13. For commercial licensing, please contact support@quantumnous.com
  14. */
  15. import React, { useRef } from 'react';
  16. import { Link } from 'react-router-dom';
  17. import { Avatar, Button, Dropdown, Typography } from '@douyinfe/semi-ui';
  18. import { ChevronDown } from 'lucide-react';
  19. import {
  20. IconExit,
  21. IconUserSetting,
  22. IconCreditCard,
  23. IconKey,
  24. } from '@douyinfe/semi-icons';
  25. import { stringToColor } from '../../../helpers';
  26. import SkeletonWrapper from '../components/SkeletonWrapper';
  27. const UserArea = ({
  28. userState,
  29. isLoading,
  30. isMobile,
  31. isSelfUseMode,
  32. logout,
  33. navigate,
  34. t,
  35. }) => {
  36. const dropdownRef = useRef(null);
  37. if (isLoading) {
  38. return (
  39. <SkeletonWrapper
  40. loading={true}
  41. type='userArea'
  42. width={50}
  43. isMobile={isMobile}
  44. />
  45. );
  46. }
  47. if (userState.user) {
  48. return (
  49. <div className='relative' ref={dropdownRef}>
  50. <Dropdown
  51. position='bottomRight'
  52. getPopupContainer={() => dropdownRef.current}
  53. render={
  54. <Dropdown.Menu className='!bg-semi-color-bg-overlay !border-semi-color-border !shadow-lg !rounded-lg dark:!bg-gray-700 dark:!border-gray-600'>
  55. <Dropdown.Item
  56. onClick={() => {
  57. navigate('/console/personal');
  58. }}
  59. className='!px-3 !py-1.5 !text-sm !text-semi-color-text-0 hover:!bg-semi-color-fill-1 dark:!text-gray-200 dark:hover:!bg-blue-500 dark:hover:!text-white'
  60. >
  61. <div className='flex items-center gap-2'>
  62. <IconUserSetting
  63. size='small'
  64. className='text-gray-500 dark:text-gray-400'
  65. />
  66. <span>{t('个人设置')}</span>
  67. </div>
  68. </Dropdown.Item>
  69. <Dropdown.Item
  70. onClick={() => {
  71. navigate('/console/token');
  72. }}
  73. className='!px-3 !py-1.5 !text-sm !text-semi-color-text-0 hover:!bg-semi-color-fill-1 dark:!text-gray-200 dark:hover:!bg-blue-500 dark:hover:!text-white'
  74. >
  75. <div className='flex items-center gap-2'>
  76. <IconKey
  77. size='small'
  78. className='text-gray-500 dark:text-gray-400'
  79. />
  80. <span>{t('令牌管理')}</span>
  81. </div>
  82. </Dropdown.Item>
  83. <Dropdown.Item
  84. onClick={() => {
  85. navigate('/console/topup');
  86. }}
  87. className='!px-3 !py-1.5 !text-sm !text-semi-color-text-0 hover:!bg-semi-color-fill-1 dark:!text-gray-200 dark:hover:!bg-blue-500 dark:hover:!text-white'
  88. >
  89. <div className='flex items-center gap-2'>
  90. <IconCreditCard
  91. size='small'
  92. className='text-gray-500 dark:text-gray-400'
  93. />
  94. <span>{t('钱包管理')}</span>
  95. </div>
  96. </Dropdown.Item>
  97. <Dropdown.Item
  98. onClick={logout}
  99. className='!px-3 !py-1.5 !text-sm !text-semi-color-text-0 hover:!bg-semi-color-fill-1 dark:!text-gray-200 dark:hover:!bg-red-500 dark:hover:!text-white'
  100. >
  101. <div className='flex items-center gap-2'>
  102. <IconExit
  103. size='small'
  104. className='text-gray-500 dark:text-gray-400'
  105. />
  106. <span>{t('退出')}</span>
  107. </div>
  108. </Dropdown.Item>
  109. </Dropdown.Menu>
  110. }
  111. >
  112. <Button
  113. theme='borderless'
  114. type='tertiary'
  115. className='flex items-center gap-1.5 !p-1 !rounded-full hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-700 !bg-semi-color-fill-0 dark:!bg-semi-color-fill-1 dark:hover:!bg-semi-color-fill-2'
  116. >
  117. <Avatar
  118. size='extra-small'
  119. color={stringToColor(userState.user.username)}
  120. className='mr-1'
  121. >
  122. {userState.user.username[0].toUpperCase()}
  123. </Avatar>
  124. <span className='hidden md:inline'>
  125. <Typography.Text className='!text-xs !font-medium !text-semi-color-text-1 dark:!text-gray-300 mr-1'>
  126. {userState.user.username}
  127. </Typography.Text>
  128. </span>
  129. <ChevronDown
  130. size={14}
  131. className='text-xs text-semi-color-text-2 dark:text-gray-400'
  132. />
  133. </Button>
  134. </Dropdown>
  135. </div>
  136. );
  137. } else {
  138. const showRegisterButton = !isSelfUseMode;
  139. const commonSizingAndLayoutClass =
  140. 'flex items-center justify-center !py-[10px] !px-1.5';
  141. const loginButtonSpecificStyling =
  142. '!bg-semi-color-fill-0 dark:!bg-semi-color-fill-1 hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-700 transition-colors';
  143. let loginButtonClasses = `${commonSizingAndLayoutClass} ${loginButtonSpecificStyling}`;
  144. let registerButtonClasses = `${commonSizingAndLayoutClass}`;
  145. const loginButtonTextSpanClass =
  146. '!text-xs !text-semi-color-text-1 dark:!text-gray-300 !p-1.5';
  147. const registerButtonTextSpanClass = '!text-xs !text-white !p-1.5';
  148. if (showRegisterButton) {
  149. if (isMobile) {
  150. loginButtonClasses += ' !rounded-full';
  151. } else {
  152. loginButtonClasses += ' !rounded-l-full !rounded-r-none';
  153. }
  154. registerButtonClasses += ' !rounded-r-full !rounded-l-none';
  155. } else {
  156. loginButtonClasses += ' !rounded-full';
  157. }
  158. return (
  159. <div className='flex items-center'>
  160. <Link to='/login' className='flex'>
  161. <Button
  162. theme='borderless'
  163. type='tertiary'
  164. className={loginButtonClasses}
  165. >
  166. <span className={loginButtonTextSpanClass}>{t('登录')}</span>
  167. </Button>
  168. </Link>
  169. {showRegisterButton && (
  170. <div className='hidden md:block'>
  171. <Link to='/register' className='flex -ml-px'>
  172. <Button
  173. theme='solid'
  174. type='primary'
  175. className={registerButtonClasses}
  176. >
  177. <span className={registerButtonTextSpanClass}>{t('注册')}</span>
  178. </Button>
  179. </Link>
  180. </div>
  181. )}
  182. </div>
  183. );
  184. }
  185. };
  186. export default UserArea;