304 lines
7.8 KiB
TypeScript
304 lines
7.8 KiB
TypeScript
import { CopyToClipboard } from "react-copy-to-clipboard";
|
|
import { useFormState } from "react-use-form-state";
|
|
import { Flex } from "reflexbox/styled-components";
|
|
import React, { FC, useState } from "react";
|
|
import styled from "styled-components";
|
|
|
|
import { useStoreActions, useStoreState } from "../store";
|
|
import { Checkbox, Select, TextInput } from "./Input";
|
|
import { Col, RowCenterH, RowCenter } from "./Layout";
|
|
import { useMessage, useCopy } from "../hooks";
|
|
import { removeProtocol } from "../utils";
|
|
import Text, { H1, Span } from "./Text";
|
|
import { Link } from "../store/links";
|
|
import Animation from "./Animation";
|
|
import { Colors } from "../consts";
|
|
import Icon from "./Icon";
|
|
|
|
const SubmitIconWrapper = styled.div`
|
|
content: "";
|
|
position: absolute;
|
|
top: 0;
|
|
right: 12px;
|
|
width: 64px;
|
|
height: 100%;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
cursor: pointer;
|
|
|
|
:hover svg {
|
|
fill: #673ab7;
|
|
}
|
|
@media only screen and (max-width: 448px) {
|
|
right: 8px;
|
|
width: 40px;
|
|
}
|
|
`;
|
|
|
|
const ShortenedLink = styled(H1)`
|
|
cursor: "pointer";
|
|
border-bottom: 1px dotted ${Colors.StatsTotalUnderline};
|
|
cursor: pointer;
|
|
|
|
:hover {
|
|
opacity: 0.8;
|
|
}
|
|
`;
|
|
|
|
interface Form {
|
|
target: string;
|
|
domain?: string;
|
|
customurl?: string;
|
|
password?: string;
|
|
showAdvanced?: boolean;
|
|
}
|
|
|
|
const defaultDomain = process.env.DEFAULT_DOMAIN;
|
|
|
|
const Shortener = () => {
|
|
const { isAuthenticated } = useStoreState(s => s.auth);
|
|
const domains = useStoreState(s => s.settings.domains);
|
|
const submit = useStoreActions(s => s.links.submit);
|
|
const [link, setLink] = useState<Link | null>(null);
|
|
const [message, setMessage] = useMessage(3000);
|
|
const [loading, setLoading] = useState(false);
|
|
const [copied, setCopied] = useCopy();
|
|
const [formState, { raw, password, text, select, label }] = useFormState<
|
|
Form
|
|
>(
|
|
{ showAdvanced: false },
|
|
{
|
|
withIds: true,
|
|
onChange(e, stateValues, nextStateValues) {
|
|
if (stateValues.showAdvanced && !nextStateValues.showAdvanced) {
|
|
formState.clear();
|
|
formState.setField("target", stateValues.target);
|
|
}
|
|
}
|
|
}
|
|
);
|
|
|
|
const submitLink = async () => {
|
|
try {
|
|
const link = await submit({ ...formState.values });
|
|
setLink(link);
|
|
formState.clear();
|
|
} catch (err) {
|
|
setMessage(
|
|
err?.response?.data?.error || "Couldn't create the short link."
|
|
);
|
|
}
|
|
setLoading(false);
|
|
};
|
|
|
|
const onSubmit = async e => {
|
|
e.preventDefault();
|
|
if (loading) return;
|
|
setCopied(false);
|
|
setLoading(true);
|
|
|
|
return submitLink();
|
|
};
|
|
|
|
const title = !link && (
|
|
<H1 fontSize={[25, 27, 32]} light>
|
|
Klip your links{" "}
|
|
<Span style={{ borderBottom: "2px dotted #999" }} light>
|
|
shorter
|
|
</Span>
|
|
.
|
|
</H1>
|
|
);
|
|
|
|
const result = link && (
|
|
<Animation
|
|
as={RowCenter}
|
|
offset="-20px"
|
|
duration="0.4s"
|
|
style={{ position: "relative" }}
|
|
>
|
|
{copied ? (
|
|
<Animation offset="10px" duration="0.2s" alignItems="center">
|
|
<Icon
|
|
size={[30, 35]}
|
|
py={0}
|
|
px={0}
|
|
mr={3}
|
|
p={["4px", "5px"]}
|
|
name="check"
|
|
strokeWidth="3"
|
|
stroke={Colors.CheckIcon}
|
|
/>
|
|
</Animation>
|
|
) : (
|
|
<Animation offset="-10px" duration="0.2s">
|
|
<CopyToClipboard text={link.link} onCopy={setCopied}>
|
|
<Icon
|
|
as="button"
|
|
py={0}
|
|
px={0}
|
|
mr={3}
|
|
size={[30, 35]}
|
|
p={["6px", "7px"]}
|
|
name="copy"
|
|
strokeWidth="2.5"
|
|
stroke={Colors.CopyIcon}
|
|
backgroundColor={Colors.CopyIconBg}
|
|
/>
|
|
</CopyToClipboard>
|
|
</Animation>
|
|
)}
|
|
<CopyToClipboard text={link.link} onCopy={setCopied}>
|
|
<ShortenedLink fontSize={[24, 26, 30]} pb="2px" light>
|
|
{removeProtocol(link.link)}
|
|
</ShortenedLink>
|
|
</CopyToClipboard>
|
|
</Animation>
|
|
);
|
|
|
|
return (
|
|
<Col width={800} maxWidth="100%" px={[3]} flex="0 0 auto" mt={4}>
|
|
<RowCenterH mb={[4, 48]}>
|
|
{title}
|
|
{result}
|
|
</RowCenterH>
|
|
<Flex
|
|
as="form"
|
|
id="shortenerform"
|
|
width={1}
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
style={{ position: "relative" }}
|
|
onSubmit={onSubmit}
|
|
>
|
|
<TextInput
|
|
{...text("target")}
|
|
placeholder="Paste your long URL"
|
|
placeholderSize={[16, 17, 18]}
|
|
fontSize={[18, 20, 22]}
|
|
width={1}
|
|
height={[58, 64, 72]}
|
|
px={0}
|
|
pr={[48, 84]}
|
|
pl={[32, 40]}
|
|
autoFocus
|
|
data-lpignore
|
|
/>
|
|
<SubmitIconWrapper onClick={onSubmit}>
|
|
<Icon
|
|
name={loading ? "spinner" : "send"}
|
|
size={[22, 26, 28]}
|
|
fill={loading ? "none" : "#aaa"}
|
|
stroke={loading ? Colors.Spinner : "none"}
|
|
mb={1}
|
|
mr={1}
|
|
/>
|
|
</SubmitIconWrapper>
|
|
</Flex>
|
|
{message.text && (
|
|
<Text color={message.color} mt={24} mb={1} textAlign="center">
|
|
{message.text}
|
|
</Text>
|
|
)}
|
|
<Checkbox
|
|
{...raw({
|
|
name: "showAdvanced",
|
|
onChange: e => {
|
|
if (!isAuthenticated) {
|
|
setMessage(
|
|
"You need to log in or sign up to use advanced options."
|
|
);
|
|
return false;
|
|
}
|
|
return !formState.values.showAdvanced;
|
|
}
|
|
})}
|
|
checked={formState.values.showAdvanced}
|
|
label="Show advanced options"
|
|
mt={[3, 24]}
|
|
alignSelf="flex-start"
|
|
/>
|
|
{formState.values.showAdvanced && (
|
|
<Flex mt={4} flexDirection={["column", "row"]}>
|
|
<Col mb={[3, 0]}>
|
|
<Text
|
|
as="label"
|
|
{...label("domain")}
|
|
fontSize={[14, 15]}
|
|
mb={2}
|
|
bold
|
|
>
|
|
Domain
|
|
</Text>
|
|
<Select
|
|
{...select("domain")}
|
|
data-lpignore
|
|
pl={[3, 24]}
|
|
pr={[3, 24]}
|
|
fontSize={[14, 15]}
|
|
height={[40, 44]}
|
|
width={[170, 200]}
|
|
options={[
|
|
{ key: defaultDomain, value: "" },
|
|
...domains.map(d => ({
|
|
key: d.address,
|
|
value: d.address
|
|
}))
|
|
]}
|
|
/>
|
|
</Col>
|
|
<Col mb={[3, 0]} ml={[0, 24]}>
|
|
<Text
|
|
as="label"
|
|
{...label("customurl")}
|
|
fontSize={[14, 15]}
|
|
mb={2}
|
|
bold
|
|
>
|
|
{formState.values.domain || defaultDomain}/
|
|
</Text>
|
|
<TextInput
|
|
{...text("customurl")}
|
|
placeholder="Custom address..."
|
|
autocomplete="off"
|
|
data-lpignore
|
|
pl={[3, 24]}
|
|
pr={[3, 24]}
|
|
placeholderSize={[13, 14]}
|
|
fontSize={[14, 15]}
|
|
height={[40, 44]}
|
|
width={[210, 240]}
|
|
/>
|
|
</Col>
|
|
<Col ml={[0, 24]}>
|
|
<Text
|
|
as="label"
|
|
{...label("password")}
|
|
fontSize={[14, 15]}
|
|
mb={2}
|
|
bold
|
|
>
|
|
Password:
|
|
</Text>
|
|
<TextInput
|
|
{...password("password")}
|
|
placeholder="Password..."
|
|
autocomplete="off"
|
|
data-lpignore
|
|
pl={[3, 24]}
|
|
pr={[3, 24]}
|
|
placeholderSize={[13, 14]}
|
|
fontSize={[14, 15]}
|
|
height={[40, 44]}
|
|
width={[210, 240]}
|
|
/>
|
|
</Col>
|
|
</Flex>
|
|
)}
|
|
</Col>
|
|
);
|
|
};
|
|
|
|
export default Shortener;
|