PersonalDropdown.jsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import { useRef, useState } from 'react';
  2. import { UserPicture } from '@growi/ui/dist/components';
  3. import { useTranslation } from 'next-i18next';
  4. import dynamic from 'next/dynamic';
  5. import Link from 'next/link';
  6. import { useRipple } from 'react-use-ripple';
  7. import { apiv3Post } from '~/client/util/apiv3-client';
  8. import { toastError } from '~/client/util/toastr';
  9. import { useCurrentUser } from '~/stores/context';
  10. const ProactiveQuestionnaireModal = dynamic(() => import('~/features/questionnaire/client/components/ProactiveQuestionnaireModal'), { ssr: false });
  11. const PersonalDropdown = () => {
  12. const { t } = useTranslation('commons');
  13. const { data: currentUser } = useCurrentUser();
  14. const [isQuestionnaireModalOpen, setQuestionnaireModalOpen] = useState(false);
  15. // ripple
  16. const buttonRef = useRef(null);
  17. useRipple(buttonRef, { rippleColor: 'rgba(255, 255, 255, 0.3)' });
  18. if (currentUser == null) {
  19. return <div className="text-muted text-center mb-5">
  20. <i className="fa fa-2x fa-spinner fa-pulse mr-1" />
  21. </div>;
  22. }
  23. const logoutHandler = async() => {
  24. try {
  25. await apiv3Post('/logout');
  26. window.location.reload();
  27. }
  28. catch (err) {
  29. toastError(err);
  30. }
  31. };
  32. return (
  33. <>
  34. {/* Button */}
  35. {/* remove .dropdown-toggle for hide caret */}
  36. {/* See https://stackoverflow.com/a/44577512/13183572 */}
  37. <button className="bg-transparent border-0 nav-link" type="button" ref={buttonRef} data-toggle="dropdown" data-testid="personal-dropdown-button">
  38. <UserPicture user={currentUser} noLink noTooltip /><span className="ml-1 d-none d-lg-inline-block">&nbsp;{currentUser.name}</span>
  39. </button>
  40. {/* Menu */}
  41. <div className="dropdown-menu dropdown-menu-right" data-testid="personal-dropdown-menu">
  42. <div className="px-4 pt-3 pb-2 text-center">
  43. <UserPicture user={currentUser} size="lg" noLink noTooltip />
  44. <h5 className="mt-2">
  45. {currentUser.name}
  46. </h5>
  47. <div className="my-2">
  48. <i className="icon-user icon-fw"></i>{currentUser.username}<br />
  49. <i className="icon-envelope icon-fw"></i><span className="grw-email-sm">{currentUser.email}</span>
  50. </div>
  51. <div className="btn-group btn-block mt-2" role="group">
  52. <Link
  53. href={`/user/${currentUser.username}`}
  54. className="btn btn-sm btn-outline-secondary col"
  55. data-testid="grw-personal-dropdown-menu-user-home"
  56. >
  57. <i className="icon-fw icon-home"></i>{t('personal_dropdown.home')}
  58. </Link>
  59. <Link
  60. href="/me"
  61. className="btn btn-sm btn-outline-secondary col"
  62. data-testid="grw-personal-dropdown-menu-user-settings"
  63. >
  64. <i className="icon-fw icon-wrench"></i>{t('personal_dropdown.settings')}
  65. </Link>
  66. </div>
  67. </div>
  68. <div className="dropdown-divider"></div>
  69. <button
  70. data-testid="grw-proactive-questionnaire-modal-toggle-btn"
  71. type="button"
  72. className="dropdown-item"
  73. onClick={() => setQuestionnaireModalOpen(true)}>
  74. <i className="icon-fw icon-pencil"></i>{t('personal_dropdown.feedback')}
  75. </button>
  76. <div className="dropdown-divider"></div>
  77. <button type="button" className="dropdown-item" onClick={logoutHandler}>
  78. <i className="icon-fw icon-power"></i>{t('Sign out')}
  79. </button>
  80. </div>
  81. <ProactiveQuestionnaireModal isOpen={isQuestionnaireModalOpen} onClose={() => setQuestionnaireModalOpen(false)} />
  82. </>
  83. );
  84. };
  85. export default PersonalDropdown;