index.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. import React from "react";
  2. import { useRouter } from "next/router";
  3. import { useQuery } from "@tanstack/react-query";
  4. import dayjs from "dayjs";
  5. import relativeTime from "dayjs/plugin/relativeTime";
  6. import toast from "react-hot-toast";
  7. import { AiOutlineEdit, AiOutlineLock, AiOutlinePlus, AiOutlineUnlock } from "react-icons/ai";
  8. import { FaTrash } from "react-icons/fa";
  9. import { IoRocketSharp } from "react-icons/io5";
  10. import { Button } from "src/components/Button";
  11. import { Modal, ModalProps } from "src/components/Modal";
  12. import { Spinner } from "src/components/Spinner";
  13. import { deleteJson, getAllJson, saveJson, updateJson } from "src/services/db/json";
  14. import useJson from "src/store/useJson";
  15. import useUser from "src/store/useUser";
  16. import { Json } from "src/typings/altogic";
  17. import styled from "styled-components";
  18. dayjs.extend(relativeTime);
  19. const StyledModalContent = styled.div`
  20. display: flex;
  21. flex-direction: column;
  22. gap: 14px;
  23. overflow: auto;
  24. `;
  25. const StyledJsonCard = styled.a<{ active?: boolean; create?: boolean }>`
  26. display: ${({ create }) => (create ? "block" : "flex")};
  27. align-items: center;
  28. justify-content: space-between;
  29. background: ${({ theme }) => theme.BLACK_SECONDARY};
  30. border: 2px solid ${({ theme, active }) => (active ? theme.SEAGREEN : theme.BLACK_SECONDARY)};
  31. border-radius: 5px;
  32. overflow: hidden;
  33. flex: 1;
  34. height: 160px;
  35. `;
  36. const StyledInfo = styled.div`
  37. padding: 4px 6px;
  38. `;
  39. const StyledTitle = styled.div`
  40. display: flex;
  41. align-items: center;
  42. gap: 4px;
  43. font-size: 14px;
  44. font-weight: 500;
  45. width: fit-content;
  46. cursor: pointer;
  47. span {
  48. overflow: hidden;
  49. text-overflow: ellipsis;
  50. }
  51. `;
  52. const StyledDetils = styled.div`
  53. display: flex;
  54. align-items: center;
  55. font-size: 12px;
  56. gap: 4px;
  57. `;
  58. const StyledModal = styled(Modal)`
  59. #modal-view {
  60. display: none;
  61. }
  62. `;
  63. const StyledDeleteButton = styled(Button)`
  64. background: transparent;
  65. `;
  66. const StyledCreateWrapper = styled.div`
  67. display: flex;
  68. height: 100%;
  69. gap: 6px;
  70. align-items: center;
  71. justify-content: center;
  72. opacity: 0.6;
  73. height: 45px;
  74. font-size: 14px;
  75. cursor: pointer;
  76. `;
  77. const StyledNameInput = styled.input`
  78. background: transparent;
  79. border: none;
  80. outline: none;
  81. width: 90%;
  82. color: ${({ theme }) => theme.SEAGREEN};
  83. font-weight: 600;
  84. `;
  85. const GraphCard: React.FC<{ data: Json; refetch: () => void; active: boolean }> = ({
  86. data,
  87. refetch,
  88. active,
  89. ...props
  90. }) => {
  91. const [editMode, setEditMode] = React.useState(false);
  92. const [name, setName] = React.useState(data.name);
  93. const onSubmit = () => {
  94. toast
  95. .promise(updateJson(data._id, { name }), {
  96. loading: "Updating document...",
  97. error: "Error occured while updating document!",
  98. success: `Renamed document to ${name}`,
  99. })
  100. .then(refetch);
  101. setEditMode(false);
  102. };
  103. const onDeleteClick = (e: React.MouseEvent<HTMLButtonElement>) => {
  104. e.preventDefault();
  105. toast
  106. .promise(deleteJson(data._id), {
  107. loading: "Deleting JSON file...",
  108. error: "An error occured while deleting the file!",
  109. success: `Deleted ${name}!`,
  110. })
  111. .then(refetch);
  112. };
  113. return (
  114. <StyledJsonCard
  115. href={`?json=${data._id}`}
  116. as={editMode ? "div" : "a"}
  117. active={active}
  118. {...props}
  119. >
  120. <StyledInfo>
  121. {editMode ? (
  122. <form onSubmit={onSubmit}>
  123. <StyledNameInput
  124. value={name}
  125. onChange={e => setName(e.currentTarget.value)}
  126. onClick={e => e.preventDefault()}
  127. autoFocus
  128. />
  129. <input type="submit" hidden />
  130. </form>
  131. ) : (
  132. <StyledTitle
  133. onClick={e => {
  134. e.preventDefault();
  135. setEditMode(true);
  136. }}
  137. >
  138. <span>{name}</span>
  139. <AiOutlineEdit />
  140. </StyledTitle>
  141. )}
  142. <StyledDetils>
  143. {data.private ? <AiOutlineLock /> : <AiOutlineUnlock />}
  144. Last modified {dayjs(data.updatedAt).fromNow()}
  145. </StyledDetils>
  146. </StyledInfo>
  147. <StyledDeleteButton onClick={onDeleteClick}>
  148. <FaTrash />
  149. </StyledDeleteButton>
  150. </StyledJsonCard>
  151. );
  152. };
  153. const CreateCard: React.FC<{ reachedLimit: boolean }> = ({ reachedLimit }) => {
  154. const { replace } = useRouter();
  155. const isPremium = useUser(state => state.isPremium());
  156. const getJson = useJson(state => state.getJson);
  157. const setHasChanges = useJson(state => state.setHasChanges);
  158. const onCreate = async () => {
  159. try {
  160. toast.loading("Saving JSON...", { id: "jsonSave" });
  161. const res = await saveJson({ data: getJson() });
  162. if (res.errors && res.errors.items.length > 0) throw res.errors;
  163. toast.success("JSON saved to cloud", { id: "jsonSave" });
  164. setHasChanges(false);
  165. replace({ query: { json: res.data._id } });
  166. } catch (error: any) {
  167. if (error?.items?.length > 0) {
  168. return toast.error(error.items[0].message, { id: "jsonSave", duration: 7000 });
  169. }
  170. toast.error("Failed to save JSON!", { id: "jsonSave" });
  171. }
  172. };
  173. if (!isPremium && reachedLimit)
  174. return (
  175. <StyledJsonCard href="/pricing" create>
  176. <StyledCreateWrapper>
  177. <IoRocketSharp size="18" />
  178. You reached max limit, upgrade to premium for more!
  179. </StyledCreateWrapper>
  180. </StyledJsonCard>
  181. );
  182. return (
  183. <StyledJsonCard onClick={onCreate} create>
  184. <StyledCreateWrapper>
  185. <AiOutlinePlus size="24" />
  186. Create New JSON
  187. </StyledCreateWrapper>
  188. </StyledJsonCard>
  189. );
  190. };
  191. export const CloudModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
  192. const { isReady, query } = useRouter();
  193. const { data, isFetching, refetch } = useQuery(["allJson", query], () => getAllJson(), {
  194. enabled: isReady && visible,
  195. });
  196. return (
  197. <StyledModal visible={visible} setVisible={setVisible}>
  198. <Modal.Header>Saved On The Cloud</Modal.Header>
  199. <Modal.Content>
  200. <StyledModalContent>
  201. {isFetching ? (
  202. <Spinner />
  203. ) : (
  204. <>
  205. <CreateCard reachedLimit={data ? data?.data.result.length > 15 : false} />
  206. {data?.data?.result?.map(json => (
  207. <GraphCard
  208. data={json}
  209. key={json._id}
  210. refetch={refetch}
  211. active={query.json === json._id}
  212. />
  213. ))}
  214. </>
  215. )}
  216. </StyledModalContent>
  217. </Modal.Content>
  218. <Modal.Controls setVisible={setVisible}></Modal.Controls>
  219. </StyledModal>
  220. );
  221. };