BottomBar.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  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 ?? true);
  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. setIsUpdating(true);
  109. const res = await updateJson(query.json as string, { private: !isPrivate });
  110. if (!res.errors?.items.length) {
  111. setIsPrivate(res.data.private);
  112. toast.success(`Document set to ${isPrivate ? "public" : "private"}.`);
  113. } else throw res.errors;
  114. } catch (error) {
  115. toast.error("An error occured while updating document!");
  116. } finally {
  117. setIsUpdating(false);
  118. }
  119. };
  120. return (
  121. <StyledBottomBar>
  122. <StyledLeft>
  123. <StyledBottomBarItem onClick={handleLoginClick}>
  124. <VscAccount />
  125. {user ? user.name : "Login"}
  126. </StyledBottomBarItem>
  127. <StyledBottomBarItem onClick={handleSaveJson} disabled={isUpdating}>
  128. {hasChanges ? <AiOutlineCloudUpload /> : <AiOutlineCloudSync />}
  129. {hasChanges ? "Unsaved Changes" : "Saved"}
  130. </StyledBottomBarItem>
  131. {data && (
  132. <>
  133. {typeof data.private !== "undefined" && (
  134. <StyledBottomBarItem onClick={setPrivate} disabled={isUpdating}>
  135. {isPrivate ? <AiOutlineLock /> : <AiOutlineUnlock />}
  136. {isPrivate ? "Private" : "Public"}
  137. </StyledBottomBarItem>
  138. )}
  139. <StyledBottomBarItem onClick={() => setVisible("share")(true)}>
  140. <AiOutlineLink />
  141. Share
  142. </StyledBottomBarItem>
  143. </>
  144. )}
  145. </StyledLeft>
  146. <StyledRight>
  147. <a
  148. href="https://www.altogic.com/?utm_source=jsoncrack&utm_medium=referral&utm_campaign=sponsorship"
  149. rel="sponsored noreferrer"
  150. target="_blank"
  151. >
  152. <StyledBottomBarItem>
  153. Powered by
  154. <StyledImg
  155. height="20"
  156. width="54"
  157. src="https://www.altogic.com/img/logo_dark.svg"
  158. alt="powered by altogic"
  159. light={lightmode}
  160. />
  161. </StyledBottomBarItem>
  162. </a>
  163. </StyledRight>
  164. </StyledBottomBar>
  165. );
  166. };