profile-switcher.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. import { View, Pressable, Row } from 'app/design/view'
  2. import Link from 'app/ui/atoms/link'
  3. import { useState, useRef } from 'react'
  4. import { NeoButton, NeoButtonLink } from 'app/design/controls'
  5. import { Text } from 'app/design/typography'
  6. import Profile from 'app/ui/molecules/profile/profile'
  7. import { useCurrentUser } from 'app/context/user'
  8. import { fetcher } from 'app/lib/fetcher';
  9. import { useFetch } from 'app/lib/hooks/use-fetch';
  10. import Redirect from 'app/ui/atoms/redirect';
  11. import { useTranslation } from 'react-i18next';
  12. import { Modal } from 'app/design/controls'
  13. import { BlockWrapper } from 'app/components/block-wrapper'
  14. /**
  15. * Children mode: the Pressable host owns the press, so `accessibilityLabel` names
  16. * it (a passive NeoButton inside drops its own). Callers pass `u-neo-btn-link`
  17. * (+ `hit-area-4` small / `hit-area-8` mini) in `className` for the ring and target.
  18. */
  19. export default function ProfileSwitcher({ className, rounded = 'rounded-lg', children, hideTitle, listOnly, blockWrapperProps, accessibilityLabel }) {
  20. const { t } = useTranslation();
  21. const { currentUser, setCurrentUser } = useCurrentUser();
  22. const [show, setShow] = useState(false)
  23. const redirectdRef = useRef();
  24. const wrapperClassName = className || '';
  25. const handleSwitch = async (id) => {
  26. const result = await fetcher('/api.php?r=system/switch_profile/TemplServiceAccount&params[]=' + id);
  27. setCurrentUser(result.data);
  28. setShow(false);
  29. };
  30. // Loaded when the modal opens; staleTime 0 re-checks on every open (cached list shows meanwhile).
  31. const { data: sResponse } = useFetch(
  32. show ? '/api.php?r=system/account_profile_switcher/TemplServiceProfiles' : null,
  33. { staleTime: 0 }
  34. );
  35. const data = sResponse?.data?.[0]?.data || false;
  36. const handleClick = async (id) => {
  37. setShow(true)
  38. };
  39. const profileList = data?.profiles?.filter((item) => (item.id != currentUser.id));
  40. if (!currentUser || (currentUser.profiles_count < 2 && currentUser?.menu?.items == 0) || currentUser.profiles_count == currentUser.profiles_limit) {
  41. return <></>;
  42. }
  43. return (
  44. <BlockWrapper {...blockWrapperProps}>
  45. {true ?
  46. <Pressable
  47. className={wrapperClassName}
  48. accessibilityRole="button"
  49. accessibilityLabel={accessibilityLabel}
  50. onPress={() => handleClick()}
  51. >
  52. {children}
  53. </Pressable> :
  54. <Link href={currentUser.url} emulate={true} >
  55. <Row className={(rounded + " w-full group items-center px-0.5 justify-between cursor-pointer " + wrapperClassName).trim()}>
  56. <Row className='flex-row items-center p-1.5'>
  57. <Profile
  58. {...currentUser}
  59. url_avatar={currentUser.avatar}
  60. displayType="unit_wo_info"
  61. displaySize="base"
  62. />
  63. <View className='flex-col'>
  64. <Text className=" text-base p-1.5 flex-auto my-auto font-semibold truncate text-secondary-foreground web:group-hover:text-foreground web:duration-300">
  65. {currentUser.display_name}
  66. </Text>
  67. <Text className=" text-xs p-1.5 flex-auto my-auto truncate text-muted-foreground web:group-hover:text-secondary-foreground web:duration-300">
  68. {currentUser.membership_name}
  69. </Text></View>
  70. </Row>
  71. <View className='flex-none p-1.5'>
  72. {currentUser.profiles_count > 1 && <NeoButton
  73. style="borderless"
  74. controlSize="small"
  75. image="RefreshCw"
  76. accessibilityLabel={t('Switch profile')}
  77. onPress={() => handleClick()}
  78. />}
  79. </View>
  80. </Row>
  81. </Link>
  82. }
  83. {(show && data) && <Modal id='file-preview' title={t("Your Profiles")} onVisible={show} onClose={() => { setShow(false) }} scrollable>
  84. <Redirect ref={redirectdRef} />
  85. <View className=" flex-col">
  86. {!hideTitle && <View className="flex-row items-center justify-between">
  87. <Text className="text-lg px-1.5 py-2 font-bold text-secondary-foreground ">
  88. Your Profiles
  89. </Text>
  90. </View>
  91. }
  92. {profileList && profileList.map((item, index) => {
  93. let dUser = { ...item }
  94. dUser.url_avatar = dUser.avatar
  95. let profile = <Profile {...dUser} displayType="unit_wo_info" displaySize="base" />
  96. return (
  97. <Link href={dUser.url} emulate={true} key={index}>
  98. <View key={'index' + index} className=" p-2 flex-row
  99. web:duration-200 rounded-lg
  100. max-w-5xl self-center w-full gap-x-3">
  101. <View className="flex-none ">{profile}</View>
  102. <Text className='text-sm my-auto flex-auto font-semibold truncate text-popover-foreground '>{item.display_name}</Text>
  103. <View className="text-sm bont-semibold flex-none my-auto">
  104. <NeoButton controlSize="small" image="RefreshCw" label={t('Switch')} expoUI={false} onPress={() => handleSwitch(item.id)} />
  105. </View>
  106. </View>
  107. </Link>
  108. )
  109. })}
  110. {!listOnly && <View className='sm:flex-row justity-between mt-4 w-full sm:mx-0'>
  111. {!!currentUser?.menu?.items && currentUser.menu.items.map((item, index) => (
  112. <View key={index} className={'mb-2 sm:mb-0 w-full sm:w-1/' + (currentUser.menu.items.length + 1) + ' pr-2 '}>
  113. <NeoButtonLink
  114. href={`/${item.name}`}
  115. width="fill"
  116. image="UserCircle"
  117. label={t("New " + item.title)}
  118. />
  119. </View>
  120. ))}
  121. {!!currentUser?.menu?.items && <View className={'w-full sm:w-1/' + (currentUser.menu.items.length + 1) + ''}>
  122. <NeoButtonLink
  123. href="/logout"
  124. width="fill"
  125. image="LogOut"
  126. label={t("Sign out")}
  127. expoUI={false}
  128. />
  129. </View>}
  130. </View>}
  131. </View>
  132. </Modal>}
  133. </BlockWrapper>
  134. )
  135. }