Shortener.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. import { CopyToClipboard } from "react-copy-to-clipboard";
  2. import { Flex } from "reflexbox/styled-components";
  3. import React, { useState } from "react";
  4. import styled from "styled-components";
  5. import QRCode from "qrcode.react";
  6. import { useStoreActions, useStoreState } from "../store";
  7. import { Col, RowCenterH, RowCenter } from "./Layout";
  8. import { useFormState } from "react-use-form-state";
  9. import { removeProtocol } from "../utils";
  10. import { Link } from "../store/links";
  11. import { useMessage } from "../hooks";
  12. import TextInput from "./TextInput";
  13. import Animation from "./Animation";
  14. import Checkbox from "./Checkbox";
  15. import { Button } from "./Button";
  16. import Tooltip from "./Tooltip";
  17. import Modal from "./Modal";
  18. import Text from "./Text";
  19. import Icon from "./Icon";
  20. const SubmitIconWrapper = styled.div`
  21. content: "";
  22. position: absolute;
  23. top: 0;
  24. right: 12px;
  25. width: 64px;
  26. height: 100%;
  27. display: flex;
  28. justify-content: center;
  29. align-items: center;
  30. cursor: pointer;
  31. :hover svg {
  32. fill: #673ab7;
  33. }
  34. @media only screen and (max-width: 448px) {
  35. right: 8px;
  36. width: 40px;
  37. }
  38. `;
  39. const ShortenedLink = styled(Text)`
  40. border-bottom: 2px dotted #aaa;
  41. cursor: pointer;
  42. transition: all 0.2s ease;
  43. :hover {
  44. opacity: 0.5;
  45. }
  46. `;
  47. interface Form {
  48. target: string;
  49. customurl?: string;
  50. password?: string;
  51. showAdvanced?: boolean;
  52. }
  53. const Shortener = () => {
  54. const { isAuthenticated } = useStoreState(s => s.auth);
  55. const [domain] = useStoreState(s => s.settings.domains);
  56. const submit = useStoreActions(s => s.links.submit);
  57. const [link, setLink] = useState<Link | null>(null);
  58. const [message, setMessage] = useMessage(3000);
  59. const [loading, setLoading] = useState(false);
  60. const [qrModal, setQRModal] = useState(false);
  61. const [copied, setCopied] = useMessage(3000);
  62. const [formState, { raw, password, text, label }] = useFormState<Form>(null, {
  63. withIds: true,
  64. onChange(e, stateValues, nextStateValues) {
  65. if (stateValues.showAdvanced && !nextStateValues.showAdvanced) {
  66. formState.clear();
  67. formState.setField("target", stateValues.target);
  68. }
  69. }
  70. });
  71. const onSubmit = async e => {
  72. e.preventDefault();
  73. if (loading) return;
  74. setCopied("");
  75. setLoading(true);
  76. try {
  77. const link = await submit(formState.values);
  78. setLink(link);
  79. formState.clear();
  80. } catch (err) {
  81. setMessage(
  82. err?.response?.data?.error || "Couldn't create the short link."
  83. );
  84. }
  85. setLoading(false);
  86. };
  87. const title = !link && (
  88. <Text as="h1" fontWeight={300}>
  89. Kutt your links{" "}
  90. <Text
  91. as="span"
  92. fontWeight={300}
  93. style={{ borderBottom: "2px dotted #999" }}
  94. >
  95. shorter
  96. </Text>
  97. .
  98. </Text>
  99. );
  100. const onCopy = () => setCopied("Copied to clipboard.", "green");
  101. const result = link && (
  102. <Animation
  103. as={RowCenter}
  104. offset="-20px"
  105. duration="0.4s"
  106. style={{ position: "relative" }}
  107. >
  108. <CopyToClipboard text={link.shortLink} onCopy={onCopy}>
  109. <ShortenedLink as="h1" fontWeight={300} mr={3} mb={1} pb="2px">
  110. {removeProtocol(link.shortLink)}
  111. </ShortenedLink>
  112. </CopyToClipboard>
  113. <CopyToClipboard text={link.shortLink} onCopy={onCopy}>
  114. <Button>
  115. <Icon name="clipboard" stroke="white" mr={2} />
  116. Copy
  117. </Button>
  118. </CopyToClipboard>
  119. {copied && (
  120. <Animation
  121. as={Text}
  122. offset="10px"
  123. color={copied.color}
  124. fontSize={15}
  125. style={{ position: "absolute", left: 0, top: -24 }}
  126. >
  127. {copied.text}
  128. </Animation>
  129. )}
  130. </Animation>
  131. );
  132. return (
  133. <Col width={800} maxWidth="98%" flex="0 0 auto" mt={4}>
  134. <RowCenterH mb={40}>
  135. {title}
  136. {result}
  137. </RowCenterH>
  138. <Flex
  139. as="form"
  140. id="shortenerform"
  141. width={800}
  142. maxWidth="100%"
  143. alignItems="center"
  144. justifyContent="center"
  145. style={{ position: "relative" }}
  146. onSubmit={onSubmit}
  147. >
  148. <TextInput
  149. {...text("target")}
  150. placeholder="Paste your long URL"
  151. placeholderSize={[16, 18]}
  152. fontSize={[20, 22]}
  153. width={1}
  154. height={[72]}
  155. autoFocus
  156. />
  157. <SubmitIconWrapper onClick={onSubmit}>
  158. <Icon
  159. name={loading ? "spinner" : "send"}
  160. size={28}
  161. fill={loading ? "none" : "#aaa"}
  162. stroke={loading ? "#888" : "none"}
  163. mb={1}
  164. mr={1}
  165. />
  166. </SubmitIconWrapper>
  167. </Flex>
  168. {message.text && (
  169. <Text color={message.color} mt={24} mb={1} textAlign="center">
  170. {message.text}
  171. </Text>
  172. )}
  173. <Checkbox
  174. {...raw({
  175. name: "showAdvanced",
  176. onChange: e => {
  177. if (!isAuthenticated) {
  178. setMessage(
  179. "You need to log in or sign up to use advanced options."
  180. );
  181. return false;
  182. }
  183. return !formState.values.showAdvanced;
  184. }
  185. })}
  186. checked={formState.values.showAdvanced}
  187. label="Show advanced options"
  188. mt={24}
  189. />
  190. {formState.values.showAdvanced && (
  191. <Flex mt={4}>
  192. <Col>
  193. <Text
  194. as="label"
  195. {...label("customurl")}
  196. fontWeight={700}
  197. fontSize={15}
  198. mb={2}
  199. >
  200. {(domain || {}).customDomain ||
  201. (typeof window !== "undefined" && window.location.hostname)}
  202. /
  203. </Text>
  204. <TextInput
  205. {...text("customurl")}
  206. placeholder="Custom address"
  207. pl={24}
  208. pr={24}
  209. placeholderSize={[13, 14, 14, 14]}
  210. fontSize={[14, 15]}
  211. height={48}
  212. width={240}
  213. />
  214. </Col>
  215. <Col ml={4}>
  216. <Text
  217. as="label"
  218. {...label("password")}
  219. fontWeight={700}
  220. fontSize={15}
  221. mb={2}
  222. >
  223. Password:
  224. </Text>
  225. <TextInput
  226. {...password("password")}
  227. placeholder="Password"
  228. pl={24}
  229. pr={24}
  230. placeholderSize={[13, 14, 14, 14]}
  231. fontSize={[14, 15]}
  232. height={48}
  233. width={240}
  234. />
  235. </Col>
  236. </Flex>
  237. )}
  238. </Col>
  239. );
  240. };
  241. export default Shortener;