index.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import useSWR from "swr";
  2. import {
  3. type AccountHistoryAction,
  4. type StakeDetails,
  5. AccountHistoryItemType,
  6. StakeType,
  7. loadAccountHistory,
  8. } from "../../api";
  9. import { useApiContext } from "../../use-api-context";
  10. import { LoadingSpinner } from "../LoadingSpinner";
  11. import { ModalButton } from "../ModalButton";
  12. import { Tokens } from "../Tokens";
  13. const ONE_SECOND_IN_MS = 1000;
  14. const ONE_MINUTE_IN_MS = 60 * ONE_SECOND_IN_MS;
  15. const REFRESH_INTERVAL = 1 * ONE_MINUTE_IN_MS;
  16. export const AccountHistoryButton = () => (
  17. <ModalButton
  18. title="Account history"
  19. description="A history of events that have affected your account balances"
  20. >
  21. <ModalBody />
  22. </ModalButton>
  23. );
  24. const ModalBody = () => {
  25. const history = useAccountHistoryData();
  26. switch (history.type) {
  27. case DataStateType.NotLoaded:
  28. case DataStateType.Loading: {
  29. return <LoadingSpinner />;
  30. }
  31. case DataStateType.Error: {
  32. return <p>Uh oh, an error occured!</p>;
  33. }
  34. case DataStateType.Loaded: {
  35. return (
  36. <table className="text-sm">
  37. <thead className="font-medium">
  38. <tr>
  39. <td className="pr-4">Timestamp</td>
  40. <td className="pr-4">Description</td>
  41. <td className="pr-4">Amount</td>
  42. <td className="pr-4">Account Total</td>
  43. <td className="pr-4">Available Rewards</td>
  44. <td className="pr-4">Available to Withdraw</td>
  45. <td>Locked</td>
  46. </tr>
  47. </thead>
  48. <tbody>
  49. {history.data.map(
  50. (
  51. {
  52. accountTotal,
  53. action,
  54. amount,
  55. availableRewards,
  56. availableToWithdraw,
  57. locked,
  58. timestamp,
  59. },
  60. i,
  61. ) => (
  62. <tr key={i}>
  63. <td className="pr-4">{timestamp.toLocaleString()}</td>
  64. <td className="pr-4">{mkDescription(action)}</td>
  65. <td className="pr-4">
  66. <Tokens>{amount}</Tokens>
  67. </td>
  68. <td className="pr-4">
  69. <Tokens>{accountTotal}</Tokens>
  70. </td>
  71. <td className="pr-4">
  72. <Tokens>{availableRewards}</Tokens>
  73. </td>
  74. <td className="pr-4">
  75. <Tokens>{availableToWithdraw}</Tokens>
  76. </td>
  77. <td>
  78. <Tokens>{locked}</Tokens>
  79. </td>
  80. </tr>
  81. ),
  82. )}
  83. </tbody>
  84. </table>
  85. );
  86. }
  87. }
  88. };
  89. const mkDescription = (action: AccountHistoryAction): string => {
  90. switch (action.type) {
  91. case AccountHistoryItemType.Claim: {
  92. return "Rewards claimed";
  93. }
  94. case AccountHistoryItemType.Deposit: {
  95. return "Tokens deposited";
  96. }
  97. case AccountHistoryItemType.LockedDeposit: {
  98. return `Locked tokens deposited, unlocking ${action.unlockDate.toLocaleString()}`;
  99. }
  100. case AccountHistoryItemType.RewardsCredited: {
  101. return "Rewards credited";
  102. }
  103. case AccountHistoryItemType.Slash: {
  104. return `Staked tokens slashed from ${action.publisherName}`;
  105. }
  106. case AccountHistoryItemType.StakeCreated: {
  107. return `Created stake position for ${getStakeDetails(action.details)}`;
  108. }
  109. case AccountHistoryItemType.StakeFinishedWarmup: {
  110. return `Warmup complete for position for ${getStakeDetails(action.details)}`;
  111. }
  112. case AccountHistoryItemType.Unlock: {
  113. return "Locked tokens unlocked";
  114. }
  115. case AccountHistoryItemType.UnstakeCreated: {
  116. return `Requested unstake for position for ${getStakeDetails(action.details)}`;
  117. }
  118. case AccountHistoryItemType.UnstakeExitedCooldown: {
  119. return `Cooldown completed for ${getStakeDetails(action.details)}`;
  120. }
  121. case AccountHistoryItemType.Withdrawal: {
  122. return "Tokens withdrawn to wallet";
  123. }
  124. }
  125. };
  126. const getStakeDetails = (details: StakeDetails): string => {
  127. switch (details.type) {
  128. case StakeType.Governance: {
  129. return "Governance Staking";
  130. }
  131. case StakeType.IntegrityStaking: {
  132. return `Integrity Staking, publisher: ${details.publisherName}`;
  133. }
  134. }
  135. };
  136. const useAccountHistoryData = () => {
  137. const apiContext = useApiContext();
  138. const { data, isLoading, ...rest } = useSWR(
  139. `${apiContext.stakeAccount.publicKey}/history`,
  140. () => loadAccountHistory(apiContext),
  141. {
  142. refreshInterval: REFRESH_INTERVAL,
  143. },
  144. );
  145. const error = rest.error as unknown;
  146. if (error) {
  147. return DataState.ErrorState(error);
  148. } else if (isLoading) {
  149. return DataState.Loading();
  150. } else if (data) {
  151. return DataState.Loaded(data);
  152. } else {
  153. return DataState.NotLoaded();
  154. }
  155. };
  156. enum DataStateType {
  157. NotLoaded,
  158. Loading,
  159. Loaded,
  160. Error,
  161. }
  162. const DataState = {
  163. NotLoaded: () => ({ type: DataStateType.NotLoaded as const }),
  164. Loading: () => ({ type: DataStateType.Loading as const }),
  165. Loaded: (data: Awaited<ReturnType<typeof loadAccountHistory>>) => ({
  166. type: DataStateType.Loaded as const,
  167. data,
  168. }),
  169. ErrorState: (error: unknown) => ({
  170. type: DataStateType.Error as const,
  171. error,
  172. }),
  173. };
  174. type DataState = ReturnType<(typeof DataState)[keyof typeof DataState]>;