PersonalDropdown.jsx 3.7 KB

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