V5PageMigration.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import type { FC } from 'react';
  2. import React, { useCallback, useEffect, useState } from 'react';
  3. import { useTranslation } from 'next-i18next';
  4. import { toastError, toastSuccess } from '~/client/util/toastr';
  5. import { useAdminSocket } from '~/features/admin/states/socket-io';
  6. import type {
  7. PMEndedData,
  8. PMErrorCountData,
  9. PMMigratingData,
  10. PMStartedData,
  11. } from '~/interfaces/websocket';
  12. import { SocketEventName } from '~/interfaces/websocket';
  13. import AdminAppContainer from '../../../services/AdminAppContainer';
  14. import { withUnstatedContainers } from '../../UnstatedUtils';
  15. import LabeledProgressBar from '../Common/LabeledProgressBar';
  16. import { ConfirmModal } from './ConfirmModal';
  17. type Props = {
  18. adminAppContainer: typeof AdminAppContainer & {
  19. v5PageMigrationHandler: () => Promise<{ isV5Compatible: boolean }>;
  20. };
  21. };
  22. const V5PageMigration: FC<Props> = (props: Props) => {
  23. // Modal
  24. const [isV5PageMigrationModalShown, setIsV5PageMigrationModalShown] =
  25. useState(false);
  26. // Progress bar
  27. const [isInProgress, setProgressing] = useState<boolean | undefined>(
  28. undefined,
  29. ); // use false as ended
  30. const [total, setTotal] = useState<number>(0);
  31. const [skip, setSkip] = useState<number>(0);
  32. const [current, setCurrent] = useState<number>(0);
  33. const [isSucceeded, setSucceeded] = useState<boolean | undefined>(undefined);
  34. const adminSocket = useAdminSocket();
  35. const { t } = useTranslation();
  36. const { adminAppContainer } = props;
  37. /*
  38. * Local components
  39. */
  40. const renderResultMessage = useCallback(
  41. (isSucceeded: boolean) => {
  42. return (
  43. <>
  44. {isSucceeded ? (
  45. <p className="text-success p-1">
  46. {t('admin:v5_page_migration.migration_succeeded')}
  47. </p>
  48. ) : (
  49. <p className="text-danger p-1">
  50. {t('admin:v5_page_migration.migration_failed')}
  51. </p>
  52. )}
  53. </>
  54. );
  55. },
  56. [t],
  57. );
  58. const renderProgressBar = () => {
  59. if (isInProgress == null) {
  60. return <></>;
  61. }
  62. return (
  63. <>
  64. {isSucceeded != null && renderResultMessage(isSucceeded)}
  65. <LabeledProgressBar
  66. header={t('admin:v5_page_migration.header_upgrading_progress')}
  67. currentCount={current}
  68. totalCount={total}
  69. isInProgress={isInProgress}
  70. />
  71. </>
  72. );
  73. };
  74. /*
  75. * Functions
  76. */
  77. const onConfirm = async () => {
  78. setIsV5PageMigrationModalShown(false);
  79. try {
  80. const { isV5Compatible } =
  81. await adminAppContainer.v5PageMigrationHandler();
  82. if (isV5Compatible) {
  83. return toastSuccess(t('admin:v5_page_migration.already_upgraded'));
  84. }
  85. toastSuccess(t('admin:v5_page_migration.successfully_started'));
  86. } catch (err) {
  87. toastError(err);
  88. }
  89. };
  90. /*
  91. * Use Effect
  92. */
  93. // Setup Admin Socket
  94. useEffect(() => {
  95. adminSocket?.once(SocketEventName.PMStarted, (data: PMStartedData) => {
  96. setProgressing(true);
  97. setTotal(data.total);
  98. });
  99. adminSocket?.on(SocketEventName.PMMigrating, (data: PMMigratingData) => {
  100. setProgressing(true);
  101. setCurrent(data.count);
  102. });
  103. adminSocket?.on(SocketEventName.PMErrorCount, (data: PMErrorCountData) => {
  104. setProgressing(true);
  105. setSkip(data.skip);
  106. });
  107. adminSocket?.once(SocketEventName.PMEnded, (data: PMEndedData) => {
  108. setProgressing(false);
  109. setSucceeded(data.isSucceeded);
  110. });
  111. return () => {
  112. adminSocket?.off(SocketEventName.PMStarted);
  113. adminSocket?.off(SocketEventName.PMMigrating);
  114. adminSocket?.off(SocketEventName.PMErrorCount);
  115. adminSocket?.off(SocketEventName.PMEnded);
  116. };
  117. }, [adminSocket]);
  118. return (
  119. <>
  120. <ConfirmModal
  121. isModalOpen={isV5PageMigrationModalShown}
  122. warningMessage={t('admin:v5_page_migration.modal_migration_warning')}
  123. supplymentaryMessage={t('admin:v5_page_migration.migration_note')}
  124. confirmButtonTitle={t('admin:v5_page_migration.start_upgrading')}
  125. onConfirm={onConfirm}
  126. onCancel={() => setIsV5PageMigrationModalShown(false)}
  127. />
  128. <p className="card custom-card">
  129. {t('admin:v5_page_migration.migration_desc')}
  130. <br />
  131. <br />
  132. <span className="text-danger">
  133. <span className="material-symbols-outlined">error</span>
  134. {t('admin:v5_page_migration.migration_note')}
  135. </span>
  136. </p>
  137. {renderProgressBar()}
  138. <div className="row my-3">
  139. <div className="mx-auto">
  140. <button
  141. type="button"
  142. className="btn btn-warning"
  143. onClick={() => setIsV5PageMigrationModalShown(true)}
  144. disabled={isInProgress != null}
  145. >
  146. {t('admin:v5_page_migration.upgrade_to_v5')}
  147. </button>
  148. </div>
  149. </div>
  150. </>
  151. );
  152. };
  153. export default withUnstatedContainers(V5PageMigration, [AdminAppContainer]);