AssociateModal.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. import React, { useState, useCallback, type JSX } from 'react';
  2. import { useTranslation } from 'next-i18next';
  3. import {
  4. Modal,
  5. ModalHeader,
  6. ModalBody,
  7. ModalFooter,
  8. Nav,
  9. NavLink,
  10. TabContent,
  11. TabPane,
  12. } from 'reactstrap';
  13. import { toastSuccess, toastError } from '~/client/util/toastr';
  14. import { useAssociateLdapAccount, useSWRxPersonalExternalAccounts } from '~/stores/personal-settings';
  15. import { LdapAuthTest } from '../Admin/Security/LdapAuthTest';
  16. type Props = {
  17. isOpen: boolean,
  18. onClose: () => void,
  19. }
  20. /**
  21. * AssociateModalSubstance - Presentation component (heavy logic, rendered only when isOpen)
  22. */
  23. type AssociateModalSubstanceProps = {
  24. onClose: () => void;
  25. };
  26. const AssociateModalSubstance = (props: AssociateModalSubstanceProps): JSX.Element => {
  27. const { onClose } = props;
  28. const { t } = useTranslation();
  29. const { mutate: mutatePersonalExternalAccounts } = useSWRxPersonalExternalAccounts();
  30. const { trigger: associateLdapAccount } = useAssociateLdapAccount();
  31. const [activeTab, setActiveTab] = useState(1);
  32. const [username, setUsername] = useState('');
  33. const [password, setPassword] = useState('');
  34. const closeModalHandler = useCallback(() => {
  35. onClose();
  36. setUsername('');
  37. setPassword('');
  38. }, [onClose]);
  39. const clickAddLdapAccountHandler = useCallback(async() => {
  40. try {
  41. await associateLdapAccount({ username, password });
  42. mutatePersonalExternalAccounts();
  43. closeModalHandler();
  44. toastSuccess(t('security_settings.updated_general_security_setting'));
  45. }
  46. catch (err) {
  47. toastError(err);
  48. }
  49. }, [associateLdapAccount, closeModalHandler, mutatePersonalExternalAccounts, password, t, username]);
  50. const setTabToLdap = useCallback(() => setActiveTab(1), []);
  51. const setTabToGithub = useCallback(() => setActiveTab(2), []);
  52. const setTabToGoogle = useCallback(() => setActiveTab(3), []);
  53. const handleUsernameChange = useCallback((username: string) => setUsername(username), []);
  54. const handlePasswordChange = useCallback((password: string) => setPassword(password), []);
  55. return (
  56. <>
  57. <ModalHeader toggle={onClose}>
  58. { t('admin:user_management.create_external_account') }
  59. </ModalHeader>
  60. <ModalBody>
  61. <div>
  62. <Nav tabs className="mb-2">
  63. <NavLink
  64. className={`${activeTab === 1 ? 'active' : ''} d-flex gap-1 align-items-center`}
  65. onClick={setTabToLdap}
  66. >
  67. <span className="material-symbols-outlined fs-5">network_node</span> LDAP
  68. </NavLink>
  69. <NavLink
  70. className={`${activeTab === 2 ? 'active' : ''} d-flex gap-1 align-items-center`}
  71. onClick={setTabToGithub}
  72. >
  73. <span className="growi-custom-icons">github</span> (TBD) GitHub
  74. </NavLink>
  75. <NavLink
  76. className={`${activeTab === 3 ? 'active' : ''} d-flex gap-1 align-items-center`}
  77. onClick={setTabToGoogle}
  78. >
  79. <span className="growi-custom-icons">google</span> (TBD) Google OAuth
  80. </NavLink>
  81. </Nav>
  82. <TabContent activeTab={activeTab}>
  83. <TabPane tabId={1}>
  84. <LdapAuthTest
  85. username={username}
  86. password={password}
  87. onChangeUsername={handleUsernameChange}
  88. onChangePassword={handlePasswordChange}
  89. />
  90. </TabPane>
  91. <TabPane tabId={2}>
  92. TBD
  93. </TabPane>
  94. <TabPane tabId={3}>
  95. TBD
  96. </TabPane>
  97. <TabPane tabId={4}>
  98. TBD
  99. </TabPane>
  100. <TabPane tabId={5}>
  101. TBD
  102. </TabPane>
  103. </TabContent>
  104. </div>
  105. </ModalBody>
  106. <ModalFooter className="border-top-0">
  107. <button type="button" className="btn btn-primary mt-3" data-testid="add-external-account-button" onClick={clickAddLdapAccountHandler}>
  108. <span className="material-symbols-outlined" aria-hidden="true">add_circle</span>
  109. {t('add')}
  110. </button>
  111. </ModalFooter>
  112. </>
  113. );
  114. };
  115. /**
  116. * AssociateModal - Container component (lightweight, always rendered)
  117. */
  118. const AssociateModal = (props: Props): JSX.Element => {
  119. const { isOpen, onClose } = props;
  120. return (
  121. <Modal isOpen={isOpen} toggle={onClose} size="lg" data-testid="grw-associate-modal">
  122. {isOpen && <AssociateModalSubstance onClose={onClose} />}
  123. </Modal>
  124. );
  125. };
  126. export default AssociateModal;