BottomBar.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import React from "react";
  2. import { useRouter } from "next/router";
  3. import toast from "react-hot-toast";
  4. import {
  5. AiOutlineCloudSync,
  6. AiOutlineCloudUpload,
  7. AiOutlineLink,
  8. AiOutlineLock,
  9. AiOutlineUnlock,
  10. } from "react-icons/ai";
  11. import { VscAccount } from "react-icons/vsc";
  12. import { saveJson, updateJson } from "src/services/db/json";
  13. import useJson from "src/store/useJson";
  14. import useModal from "src/store/useModal";
  15. import useStored from "src/store/useStored";
  16. import useUser from "src/store/useUser";
  17. import styled from "styled-components";
  18. const StyledBottomBar = styled.div`
  19. display: flex;
  20. align-items: center;
  21. justify-content: space-between;
  22. border-top: 1px solid ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT};
  23. background: ${({ theme }) => theme.BACKGROUND_TERTIARY};
  24. max-height: 28px;
  25. height: 28px;
  26. padding: 0 6px;
  27. @media only screen and (max-width: 768px) {
  28. display: none;
  29. }
  30. `;
  31. const StyledLeft = styled.div`
  32. display: flex;
  33. align-items: center;
  34. justify-content: left;
  35. gap: 4px;
  36. `;
  37. const StyledRight = styled.div`
  38. display: flex;
  39. align-items: center;
  40. justify-content: right;
  41. gap: 4px;
  42. `;
  43. const StyledBottomBarItem = styled.button`
  44. display: flex;
  45. align-items: center;
  46. gap: 4px;
  47. width: fit-content;
  48. margin: 0;
  49. height: 28px;
  50. padding: 4px;
  51. font-size: 12px;
  52. font-weight: 400;
  53. color: ${({ theme }) => theme.INTERACTIVE_NORMAL};
  54. &:hover:not(&:disabled) {
  55. background-image: linear-gradient(rgba(0, 0, 0, 0.1) 0 0);
  56. color: ${({ theme }) => theme.INTERACTIVE_HOVER};
  57. }
  58. &:disabled {
  59. opacity: 0.4;
  60. cursor: progress;
  61. }
  62. `;
  63. const StyledImg = styled.img<{ light: boolean }>`
  64. filter: ${({ light }) => light && "invert(100%)"};
  65. `;
  66. export const BottomBar = () => {
  67. const { replace, query } = useRouter();
  68. const data = useJson(state => state.data);
  69. const user = useUser(state => state.user);
  70. const lightmode = useStored(state => state.lightmode);
  71. const hasChanges = useJson(state => state.hasChanges);
  72. const getJson = useJson(state => state.getJson);
  73. const setVisible = useModal(state => state.setVisible);
  74. const setHasChanges = useJson(state => state.setHasChanges);
  75. const [isPrivate, setIsPrivate] = React.useState(false);
  76. const [isUpdating, setIsUpdating] = React.useState(false);
  77. React.useEffect(() => {
  78. setIsPrivate(data?.private ?? false);
  79. }, [data]);
  80. const handleSaveJson = React.useCallback(async () => {
  81. if (!user) return setVisible("login")(true);
  82. if (hasChanges) {
  83. try {
  84. setIsUpdating(true);
  85. toast.loading("Saving JSON...", { id: "jsonSave" });
  86. const res = await saveJson({ id: query.json as string, data: getJson() });
  87. if (res.errors && res.errors.items.length > 0) throw res.errors;
  88. if (res.data._id) replace({ query: { json: res.data._id } });
  89. toast.success("JSON saved to cloud", { id: "jsonSave" });
  90. setHasChanges(false);
  91. } catch (error: any) {
  92. if (error?.items?.length > 0) {
  93. return toast.error(error.items[0].message, { id: "jsonSave", duration: 5000 });
  94. }
  95. toast.error("Failed to save JSON!", { id: "jsonSave" });
  96. } finally {
  97. setIsUpdating(false);
  98. }
  99. }
  100. }, [getJson, hasChanges, query.json, replace, setHasChanges, setVisible, user]);
  101. const handleLoginClick = () => {
  102. if (user) return setVisible("account")(true);
  103. else setVisible("login")(true);
  104. };
  105. const setPrivate = async () => {
  106. try {
  107. if (!query.json) return handleSaveJson();
  108. if (!isPrivate && user?.type === 0) {
  109. return window.open("https://jsoncrack.com/pricing", "_blank");
  110. }
  111. setIsUpdating(true);
  112. const res = await updateJson(query.json as string, { private: !isPrivate });
  113. if (!res.errors?.items.length) {
  114. setIsPrivate(res.data.private);
  115. toast.success(`Document set to ${isPrivate ? "public" : "private"}.`);
  116. } else throw res.errors;
  117. } catch (error) {
  118. toast.error("An error occured while updating document!");
  119. } finally {
  120. setIsUpdating(false);
  121. }
  122. };
  123. return (
  124. <StyledBottomBar>
  125. <StyledLeft>
  126. <StyledBottomBarItem onClick={handleLoginClick}>
  127. <VscAccount />
  128. {user ? user.name : "Login"}
  129. </StyledBottomBarItem>
  130. <StyledBottomBarItem onClick={handleSaveJson} disabled={isUpdating}>
  131. {hasChanges ? <AiOutlineCloudUpload /> : <AiOutlineCloudSync />}
  132. {hasChanges ? "Unsaved Changes" : "Saved"}
  133. </StyledBottomBarItem>
  134. {data && (
  135. <>
  136. {typeof data.private !== "undefined" && (
  137. <StyledBottomBarItem onClick={setPrivate} disabled={isUpdating}>
  138. {isPrivate ? <AiOutlineLock /> : <AiOutlineUnlock />}
  139. {isPrivate ? "Private" : "Public"}
  140. </StyledBottomBarItem>
  141. )}
  142. <StyledBottomBarItem onClick={() => setVisible("share")(true)}>
  143. <AiOutlineLink />
  144. Share
  145. </StyledBottomBarItem>
  146. </>
  147. )}
  148. </StyledLeft>
  149. <StyledRight>
  150. <a
  151. href="https://www.altogic.com/?utm_source=jsoncrack&utm_medium=referral&utm_campaign=sponsorship"
  152. rel="sponsored noreferrer"
  153. target="_blank"
  154. >
  155. <StyledBottomBarItem>
  156. Powered by
  157. <StyledImg
  158. height="20"
  159. width="54"
  160. src="https://www.altogic.com/img/logo_dark.svg"
  161. alt="powered by altogic"
  162. light={lightmode}
  163. />
  164. </StyledBottomBarItem>
  165. </a>
  166. </StyledRight>
  167. </StyledBottomBar>
  168. );
  169. };