V5PageMigration.tsx 4.7 KB

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