react-hot-toast#Toast TypeScript Examples

The following examples show how to use react-hot-toast#Toast. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.ts    From personal-archive with MIT License 6 votes vote down vote up
useApi = (): [
  boolean,
  (apiFn: () => Promise<void>) => Promise<void>,
  AxiosError | null,
] => {
  const [fetching, setFetching] = useState(false)
  const [error, setError] = useState(null as AxiosError | null)

  const fetchData = (apiFn: () => Promise<void>) => {
    setFetching(true)
    return apiFn()
      .catch(err => {
        if (err) {
          if (err.response != null && err.response.data != null && err.response.data.message != null) {
            err.message = err.response.data.message
          }
          toast.error(err.toString())
          setError(err)
          console.error({ err })
        }
      })
      .finally(() => setFetching(false))
  }

  return [ fetching, useCallback(fetchData, []), error ]
}
Example #2
Source File: dismissibleToast.tsx    From mysterium-vpn-desktop with MIT License 6 votes vote down vote up
dismissibleToast = (message: JSX.Element | string | null): ((t: Toast) => JSX.Element) => {
    return function dismissibleToast(t: Toast): JSX.Element {
        return (
            <Container>
                <div>{message}</div>
                <Dismiss onClick={() => toast.dismiss(t.id)}>
                    <FontAwesomeIcon icon={faTimes} />
                </Dismiss>
            </Container>
        )
    }
}
Example #3
Source File: EditNoteParagraphPage.tsx    From personal-archive with MIT License 6 votes vote down vote up
useSubmit = (noteID: number, paragraphID: number): [
  boolean,
  (content: string, referencedArticles: Article[], referencedWebURLs: string[]) => void,
] => {
  const [fetching, editParagraph] = useRequestEditParagraph()
  const history = useHistory()

  const submit = (content: string, referencedArticles: Article[], referenceWebURLs: string[]) => {
    if (content.trim().length === 0) {
      toast.error('content required')
      return
    }

    const articleIDs = referencedArticles.map(a => a.id)
    editParagraph(noteID, paragraphID, content, articleIDs, referenceWebURLs)
      .then(() => history.push(`/notes/${noteID}`))
  }
  return [fetching, submit]
}
Example #4
Source File: NewNotePage.tsx    From personal-archive with MIT License 6 votes vote down vote up
useSubmit = (): [
  boolean,
  (title: string, content: string, referenceArticles: Article[], referenceWebURLs: string[]) => void,
] => {
  const [fetching, createNote, note] = useRequestCreateNote()
  const history = useHistory()

  const submit = (title: string, content: string, referenceArticles: Article[], referenceWebURLs: string[]) => {
    if (title.trim().length === 0) {
      toast.error('title required')
      return
    }

    if (content.trim().length === 0) {
      toast.error('content required')
      return
    }

    const articleIDs = referenceArticles.map(({id}) => id)
    createNote(title, content, articleIDs, referenceWebURLs)
      .then(() => history.push(`/notes/${note.id}`))
  }

  return [ fetching, submit ]
}
Example #5
Source File: NewNoteParagraphPage.tsx    From personal-archive with MIT License 6 votes vote down vote up
useSubmit = (noteID: number): [
  boolean,
  (content: string, referencedArticles: Article[], referenceWebURLs: string[]) => void,
] => {
  const [fetching, createParagraph] = useRequestCreateParagraph()
  const history = useHistory()

  const submit = (content: string, referencedArticles: Article[], referenceWebURLs: string[]) => {
    if (content.trim().length === 0) {
      toast.error('content required')
      return
    }

    const articleIDs = referencedArticles.map(a => a.id)
    createParagraph(noteID, content, articleIDs, referenceWebURLs)
      .then(note => history.push(`/notes/${noteID}`))
  }

  return [fetching, submit]
}
Example #6
Source File: SearchPage.tsx    From personal-archive with MIT License 6 votes vote down vote up
SearchPage: FC = () => {
  const keyword = useQuery().get('q') || ''
  const page = usePage()
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const [ fetching, searchArticles, _, articles, pagination] = useRequestSearchArticles()
  const history = useHistory()

  useEffect(() => {
    if (keyword.length <= 1) {
      toast.error('keyword should be more than 2 characters')
      return
    }

    searchArticles(keyword, page)
  }, [keyword, page, searchArticles])

  const onReload = () => searchArticles(keyword, page)

  return (
    <ArticleTagTreeLayout loading={fetching}>
      <Keyword>Keyword: {keyword}</Keyword>
      <ArticleList
        articles={articles}
        pagination={pagination}
        onSelectPage={page => history.push(`/articles/search?q=${encodeURIComponent(keyword)}&page=${page}`)}
        onReload={onReload}
      />
      <CommandPalette
        keyword={keyword}
        page={page}
      />
    </ArticleTagTreeLayout>
  )
}
Example #7
Source File: PocketSettingUnauthenticated.tsx    From personal-archive with MIT License 6 votes vote down vote up
useActivate = (): [boolean, (consumerKey: string) => void] => {
  const [fetching, obtainPocketRequestToken] = useRequestObtainPocketRequestToken()

  const activate = (consumerKey: string) => {
    if (consumerKey.length <= 0) {
      toast.error('consumer key required')
      return
    }

    const redirectURI = `${window.location.protocol}//${window.location.host}/settings/pocket-auth`
    obtainPocketRequestToken(consumerKey, redirectURI)
      .then(requestToken => {
        window.location.href = `https://getpocket.com/auth/authorize?request_token=${requestToken}&redirect_uri=${redirectURI}`
      })
  }

  return [fetching, activate]
}
Example #8
Source File: RoomCode.tsx    From NextLevelWeek with MIT License 6 votes vote down vote up
export function RoomCode(props: RoomCodeProps) {
  // Função de copar o código da sala.
  function copyRoomCodeToClipboard() {
    toast.success("Código da sala copiado com sucesso!", {
      id: props.code,
      style: {
        width: "40rem",
      },
    });

    navigator.clipboard.writeText(props.code);
  }

  return (
    <>
      <Toaster position="top-right" reverseOrder={false} />
      <button className="room-code" onClick={copyRoomCodeToClipboard}>
        <div>
          <img src={copyImg} alt="Copy room code" />
        </div>
        <span>Sala #{props.code}</span>
      </button>
    </>
  );
}
Example #9
Source File: use-notification.tsx    From admin with MIT License 6 votes vote down vote up
useNotification = () => {
  return (title: string, message: string, type: NotificationTypes) => {
    toast.custom(
      (t) => (
        <Notification toast={t} type={type} title={title} message={message} />
      ),
      {
        position: "top-right",
        duration: 3000,
      }
    )
  }
}
Example #10
Source File: use-detect-change.tsx    From admin with MIT License 6 votes vote down vote up
useDetectChange = ({ isDirty, reset, options }: UseDetectChangeProps) => {
  useEffect(() => {
    const { fn, title, message, icon } = options

    const showToaster = () => {
      toast.custom(
        (t) => (
          <SaveNotification
            toast={t}
            icon={icon}
            title={title}
            message={message}
            onSave={fn}
            reset={reset}
          />
        ),
        {
          position: "bottom-right",
          duration: Infinity,
          id: "form-change",
        }
      )
    }

    if (isDirty) {
      showToaster()
    } else {
      toast.dismiss("form-change")
    }
  }, [isDirty, options])
}
Example #11
Source File: index.tsx    From admin with MIT License 6 votes vote down vote up
Toaster = ({ visible, children, ...options }: ToasterProps) => {
  React.useEffect(() => {
    if (visible) {
      toast.custom((t) => React.cloneElement(children, { toast: t }), {
        ...options,
      })
    } else {
      toast.dismiss(options.id)
    }
  }, [visible, children])

  return null
}
Example #12
Source File: AddReferenceWebDrawer.tsx    From personal-archive with MIT License 5 votes vote down vote up
AddReferenceWebDrawer: FC<Props> = ({show, onConfirm, onCancel}) => {
  const [url, setUrl] = useState('')

  const clear = () => {
    setUrl('')
  }

  const onClose = () => {
    clear()
    onCancel()
  }

  const onSubmit = () => {
    if (url.length === 0) {
      toast.error('url required')
      return
    }

    if (!url.startsWith('http')) {
      toast.error('invalid url')
      return
    }

    clear()
    onConfirm(url)
  }

  return (
    <>
      <Drawer shown={show} onClose={onClose} title="Add Web Reference">
        <InputField
          size="small"
          value={url}
          onChange={e => setUrl((e.target as any).value)}
          onKeyDown={e => {
            if (e.key === 'Enter') {
              onSubmit()
            }
          }}
          suffix={
            <span role="button" onClick={onSubmit} style={{ marginRight: '10px' }}>
            <FaSearch />
          </span>
          }
        />
        <ButtonWrapper>
          <Button onClick={onSubmit} size="small">Add</Button>
        </ButtonWrapper>
      </Drawer>
    </>
  )
}
Example #13
Source File: TopupSuccess.tsx    From mysterium-vpn-desktop with MIT License 5 votes vote down vote up
TopupSuccess: React.FC = observer(() => {
    const { payment, identity, navigation } = useStores()
    const isOnboarding = identity.identity?.registrationStatus != IdentityRegistrationStatus.Registered
    const handleAction = () => {
        if (isOnboarding) {
            navigation.push(locations.registering)
        } else {
            navigation.goHome()
        }
    }
    useEffect(() => {
        toast.success(`${payment.appCurrency}s will be credited to your wallet within next 1-3 minutes.`)
    }, [])
    return (
        <ViewContainer>
            <ViewNavBar>
                <div style={{ width: 375, textAlign: "center" }}>
                    <StepProgressBar step={3} />
                </div>
            </ViewNavBar>
            <ViewSplit>
                <ViewSidebar>
                    <SideTop>
                        <TitleIcon>
                            <IconWallet color={brandLight} />
                        </TitleIcon>
                        <Title>Payment successful!</Title>
                        <TitleDescription>
                            {payment.appCurrency}s will be credited to your wallet within next 1-3 minutes.
                        </TitleDescription>
                    </SideTop>
                    <SideBot>
                        <BrandButton style={{ marginTop: "auto" }} onClick={handleAction}>
                            Continue
                        </BrandButton>
                    </SideBot>
                </ViewSidebar>
                <Content>
                    <div style={{ marginTop: "auto", marginBottom: "auto" }}>
                        <Heading2>
                            <FontAwesomeIcon className="icon" icon={faCheckCircle} color="#ffffff44" size="10x" />
                            <div style={{ marginTop: 20, cursor: "pointer" }} onClick={() => payment.downloadInvoice()}>
                                <FontAwesomeIcon
                                    style={{ marginRight: 10 }}
                                    className="icon"
                                    icon={faDownload}
                                    color="#ffffff44"
                                    size="1x"
                                />
                                Download invoice
                            </div>
                        </Heading2>
                    </div>
                </Content>
            </ViewSplit>
        </ViewContainer>
    )
})
Example #14
Source File: UseReferralCodePrompt.tsx    From mysterium-vpn-desktop with MIT License 5 votes vote down vote up
UseReferralCodePrompt: React.FC<UseReferralCodePromptProps> = ({ visible, onSubmit, onCancel }) => {
    const {
        register,
        handleSubmit,
        reset,
        trigger,
        formState: { errors },
    } = useForm<ReferralCodeFormFields>({ reValidateMode: "onSubmit" })
    const { referral, payment } = useStores()
    useEffect(() => {
        if (!visible) {
            reset()
            referral.resetToken()
        }
    }, [visible])
    const handleValidate = async () => {
        await trigger()
    }
    return (
        <Prompt
            title="Enter a referral code"
            visible={visible}
            onSubmit={referral.token ? handleSubmit(onSubmit) : handleValidate}
            onCancel={onCancel}
            submitText={referral.token ? "Apply" : "OK"}
        >
            <PromptExplanation />
            <PromptInput
                autoFocus
                placeholder="Code"
                {...register("code", {
                    required: "This is required",
                    validate: {
                        valid: async (code) => {
                            if (referral.token && code === referral.token) {
                                // Do not revalidate and toast on 'Apply'
                                return true
                            }
                            const loadingToastID = toast.loading("Validating token...")
                            const valid = await referral.validateToken(code)
                            const dismissWait = valid ? 500 : 850
                            _.debounce(() => toast.dismiss(loadingToastID), dismissWait, { trailing: true })()
                            return valid || "This token is not valid"
                        },
                    },
                })}
            />
            <PromptValidation>{errors.code?.message}</PromptValidation>
            {!!referral.rewardAmount && (
                <RewardPreview>
                    <RewardIcon className="icon" icon={faCheckCircle} color={brand} size="2x" />
                    <RewardAmount>
                        You will be awarded {referral.rewardAmount} {payment.appCurrency}(s)
                    </RewardAmount>
                </RewardPreview>
            )}
        </Prompt>
    )
}
Example #15
Source File: NewRoom.tsx    From NextLevelWeek with MIT License 5 votes vote down vote up
export function NewRoom() {
  // Tendo acesso ao usuário autenticado.
  const { user } = useAuth();

  const history = useHistory();

  const [newRoom, setNewRoom] = useState("");

  // Função de criação de uma sala.
  async function handleCreateRoom(event: FormEvent) {
    // Prevenindo o comportamento padrão do formulário.
    event.preventDefault();

    // Tendo acesso ao valor do input.
    // console.log(newRoom);
    if (newRoom.trim() === "") {
      toast.error("Room name cannot be empty.", {
        icon: "⚠️",
      });
      return;
    }

    const roomRef = database.ref("rooms");
    // "jogando" uma informação dentro de "rooms"
    const firebaseRoom = await roomRef.push({
      title: newRoom,
      authorId: user?.id,
    });

    // Após o usuário crar a sala, ele será redirecionado para a nova sala criada.
    history.push(`/rooms/${firebaseRoom.key}`);
  }

  return (
    <div id="page-auth">
      <Toaster position="top-right" reverseOrder={false} />
      <aside>
        <img
          src={illustrationImg}
          alt="Ilustração simbolizando perguntas e respostas."
        />
        <strong>Crie salas de Q&amp;E ao-vivo.</strong>
        <p>Tire as dúvidas da sua audência em tempo-real.</p>
      </aside>
      <main>
        <div className="main-content">
          <img src={logoImg} alt="Letmeask" />

          <h2>Criar uma nova sala</h2>
          <form onSubmit={handleCreateRoom}>
            <input
              type="text"
              placeholder="Nome da sala"
              onChange={(event) => setNewRoom(event.target.value)}
              value={newRoom}
            />
            <Button type="submit">Criar sala</Button>
          </form>
          <p>
            Quer entrar em uma sala existente ? <Link to="/">clique aqui</Link>
          </p>
        </div>
      </main>
    </div>
  );
}
Example #16
Source File: EditNoteParagraphPage.tsx    From personal-archive with MIT License 5 votes vote down vote up
useNoteAndParagraph = (noteID: number, paragraphID: number): [
  boolean,
  Note | null,
  Paragraph | null,
  Article[],
  string[],
] => {
  const [fetching, getNote, note, articles] = useRequestGetNote()
  const [paragraph, setParagraph] = useState(null as Paragraph | null)
  const [referencedArticles, setReferencedArticles] = useState([] as Article[])
  const [referencedWebURLs, setReferencedWebURLs] = useState([] as string[])
  const history = useHistory()

  useEffect(() => {
    getNote(noteID)
  }, [getNote, noteID])

  useEffect(() => {
    if (!note) {
      return
    }

    const paragraph = note!.paragraphs.find(p => p.id === paragraphID)
    if (!paragraph) {
      toast.error('invalid paragraph id')
      setTimeout(() => history.goBack(), 3000)
      return
    }

    const referencedArticleIDs = paragraph.referenceArticles.map(a => a.articleID)
    const referencedWebURLs = paragraph.referenceWebs.map(w => w.url)
    const referencedArticles = articles.filter(a => referencedArticleIDs.includes(a.id))

    setParagraph(paragraph)
    setReferencedArticles(referencedArticles)
    setReferencedWebURLs(referencedWebURLs)
  }, [note, articles, history, paragraphID])

  return [fetching, note, paragraph, referencedArticles, referencedWebURLs]
}
Example #17
Source File: Room.tsx    From NextLevelWeek with MIT License 4 votes vote down vote up
export function Room() {
  // Apenas usuários autenticados podem enviar novas perguntas.
  const { user } = useAuth();

  // "Pegando" o código da sala através dos parâmetros.
  const params = useParams<RoomParams>();
  const roomId = params.id;

  // Importando o Hook useRoom.
  const { questions, title } = useRoom(roomId);

  // Informação da nova pergunta.
  const [newQuestion, setNewQuestion] = useState("");

  // Função para a criação de uma nova pergunta.
  async function handleSendQuestion(event: FormEvent) {
    event.preventDefault();

    if (newQuestion.trim() === "") {
      toast.error("You cannot submit an empty question.", {
        icon: "⚠️",
      });
      return;
    }

    // Se não existir nem um usuário, retornar um erro.
    // Dica de Toast: https://react-hot-toast.com/
    if (!user) {
      throw new Error("You must be logged in.");
    }

    // Criando a pergunta em si.
    const question = {
      content: newQuestion,
      author: {
        name: user.name,
        avatar: user.avatar,
      },
      // Se ela está com "Highlighte" o destaque que o Admin dá na pergunta.
      isHighlighted: false,
      // Se já foi respondida ou não.
      isAnswered: false,
    };

    await database.ref(`rooms/${roomId}/questions`).push(question);

    // "Apagando" a pergunta no textarea após ela ser enviada.
    setNewQuestion("");
  }

  // Função do "like".
  async function handleLikeQuestion(
    questionId: string,
    likeId: string | undefined
  ) {
    if (likeId) {
      // Remover o "like".
      await database
        .ref(`rooms/${roomId}/questions/${questionId}/likes/${likeId}`)
        .remove();
    } else {
      // Adicionando o "like".
      await database.ref(`rooms/${roomId}/questions/${questionId}/likes`).push({
        authorId: user?.id,
      });
    }
  }

  return (
    <div id="page-room">
      <Toaster position="top-right" reverseOrder={false} />
      <header>
        <div className="content">
          <img src={logoImg} alt="Letmeask" />
          <RoomCode code={roomId} />
        </div>
      </header>

      <main>
        <div className="room-title">
          <h1>Sala {title}</h1>
          {questions.length > 0 && <span>{questions.length} pergunta(s)</span>}
        </div>

        <form onSubmit={handleSendQuestion}>
          <textarea
            placeholder="O que deseja perguntar ?"
            onChange={(event) => setNewQuestion(event.target.value)}
            value={newQuestion}
          />
          <div className="form-footer">
            {user ? (
              <div className="user-info">
                <img src={user.avatar} alt={user.name} />
                <span>{user.name}</span>
              </div>
            ) : (
              <span>
                Para enviar uma pergunta, <button>faça seu login</button>.
              </span>
            )}
            <Button type="submit" disabled={!user}>
              Enviar pergunta
            </Button>
          </div>
        </form>

        {/* {JSON.stringify(questions)} */}
        <div className="question-list">
          {questions.map((question) => {
            return (
              <Question
                key={question.id}
                content={question.content}
                author={question.author}
                isAnswered={question.isAnswered}
                isHighlighted={question.isHighlighted}
              >
                {!question.isAnswered && (
                  <button
                    className={`like-button ${question.likeId ? "liked" : ""}`}
                    type="button"
                    aria-label="Marcar como gostei"
                    onClick={() => {
                      handleLikeQuestion(question.id, question.likeId);
                    }}
                  >
                    {question.likeCount > 0 && (
                      <span>{question.likeCount}</span>
                    )}
                    <svg
                      width="24"
                      height="24"
                      viewBox="0 0 24 24"
                      fill="none"
                      xmlns="http://www.w3.org/2000/svg"
                    >
                      <path
                        d="M7 22H4C3.46957 22 2.96086 21.7893 2.58579 21.4142C2.21071 21.0391 2 20.5304 2 20V13C2 12.4696 2.21071 11.9609 2.58579 11.5858C2.96086 11.2107 3.46957 11 4 11H7M14 9V5C14 4.20435 13.6839 3.44129 13.1213 2.87868C12.5587 2.31607 11.7956 2 11 2L7 11V22H18.28C18.7623 22.0055 19.2304 21.8364 19.5979 21.524C19.9654 21.2116 20.2077 20.7769 20.28 20.3L21.66 11.3C21.7035 11.0134 21.6842 10.7207 21.6033 10.4423C21.5225 10.1638 21.3821 9.90629 21.1919 9.68751C21.0016 9.46873 20.7661 9.29393 20.5016 9.17522C20.2371 9.0565 19.9499 8.99672 19.66 9H14Z"
                        stroke="#737380"
                        strokeWidth="1.5"
                        strokeLinecap="round"
                        strokeLinejoin="round"
                      />
                    </svg>
                  </button>
                )}
              </Question>
            );
          })}
        </div>
      </main>
    </div>
  );
}
Example #18
Source File: Home.tsx    From NextLevelWeek with MIT License 4 votes vote down vote up
export function Home() {
  const history = useHistory();
  const { user, signInWithGoogle } = useAuth();

  // Estado para armazenar o código da sala.
  const [roomCode, setRoomCode] = useState("");

  // Navegando para a página de criação de uma sala.
  async function handleCreateRoom() {
    // Se o usuário NÃO estiver autenticado.
    if (!user) {
      await signInWithGoogle();
    }

    // Se já estiver autenticado, redireciona.
    history.push("/rooms/new");
  }

  // Função para entrar em uma sala existente.
  async function handleJoinRoom(event: FormEvent) {
    event.preventDefault();

    // Se retornar vazio, nada irá acontecer.
    if (roomCode.trim() === "") {
      toast.error("Enter a valid room code.", {
        icon: "⚠️",
      });
      return;
    }

    // Verificando se a sala que o usuário está tentando entrar realmente existe.
    const roomRef = await database.ref(`rooms/${roomCode}`).get();

    // Caso retorne falso.
    if (!roomRef.exists()) {
      // alert("Room does not exists.");
      toast.error("Room does not exists.", {
        icon: "⚠️",
      });
      return;
    }

    // Verificando se a sala já não está encerrada.
    if (roomRef.val().endedAt) {
      // alert("Room already closed.");
      toast.error("Room already closed.", {
        icon: "⚠️",
      });
      return;
    }

    // Caso verdadeiro.
    history.push(`/rooms/${roomCode}`);
  }

  return (
    <div id="page-auth">
      <Toaster position="top-right" reverseOrder={false} />
      <aside>
        <img
          src={illustrationImg}
          alt="Ilustração simbolizando perguntas e respostas."
        />
        <strong>Crie salas de Q&amp;E ao-vivo.</strong>
        <p>Tire as dúvidas da sua audência em tempo-real.</p>
      </aside>
      <main>
        <div className="main-content">
          <img src={logoImg} alt="Letmeask" />
          <button onClick={handleCreateRoom} className="create-room">
            <img src={googleIconImg} alt="Logo do Google" />
            Crie sua sala com o Google
          </button>
          <div className="separator">ou entre em uma sala</div>
          <form onSubmit={handleJoinRoom}>
            <input
              type="text"
              placeholder="Digite o código da sala"
              onChange={(event) => setRoomCode(event.target.value)}
              value={roomCode}
            />
            <Button type="submit">Entrar na sala</Button>
          </form>
        </div>
      </main>
    </div>
  );
}
Example #19
Source File: talk.tsx    From website with Apache License 2.0 4 votes vote down vote up
export default function Talk() {
	const router = useRouter();
	const {data: lanyard} = useLanyard(DISCORD_ID);

	return (
		<div className="space-y-4">
			<h1 className="text-2xl font-bold sm:text-3xl">Let's talk ?</h1>
			<p>
				Leave a message on the form below or get in touch through Discord,
				Twitter or email.
			</p>

			<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
				<div className="p-5 bg-gray-100 dark:bg-white/5 rounded-lg">
					<form
						className="space-y-2"
						action="/api/form"
						method="POST"
						onSubmit={async event => {
							event.preventDefault();

							const values = Object.fromEntries(
								new FormData(event.target as HTMLFormElement).entries(),
							);

							const promise = fetcher('/api/talk', {
								headers: {'Content-Type': 'application/json'},
								body: JSON.stringify(values),
								method: 'POST',
							});

							await toast
								.promise(promise, {
									success: 'Success!',
									loading: 'Sending...',
									error: (error: Error) =>
										error?.message ?? 'Something went wrong...',
								})
								.then(async () => router.push('/thanks'))
								.catch(() => null);
						}}
					>
						<label htmlFor="email" className="block">
							<span className="text-sm font-bold tracking-wide dark:text-white uppercase select-none text-opacity-50">
								Email Address
							</span>

							<input
								required
								type="email"
								name="email"
								id="email"
								className="block py-1 px-4 w-full font-sans text-lg bg-gray-200 dark:bg-white/5 rounded-md focus:outline-none focus:ring"
							/>
						</label>

						<label htmlFor="body" className="block">
							<span className="text-sm font-bold tracking-wide dark:text-white uppercase select-none text-opacity-50">
								Your message
							</span>

							<textarea
								rows={5}
								name="body"
								minLength={10}
								id="body"
								className="block py-1 px-4 w-full font-sans text-lg bg-gray-200 dark:bg-white/5 rounded-md focus:outline-none focus:ring resize-none"
							/>
						</label>

						<div className="block pt-2">
							<button
								type="submit"
								className="inline-flex items-center py-2 px-8 space-x-2 text-lg text-blue-100 dark:text-white bg-blue-700 dark:bg-white/5 dark:hover:bg-white/10 rounded-full focus:outline-none focus:ring"
							>
								<span>Send</span> <RiSendPlane2Line />
							</button>
						</div>
					</form>
				</div>

				<div>
					<ul className="space-y-2 list-disc list-inside">
						<ListItem icon={HiOutlineMail} text="[email protected]" />
						<ListItem
							icon={SiDiscord}
							text={
								lanyard ? (
									<span className="flex items-center space-x-1">
										<span>
											{lanyard.discord_user.username}#
											{lanyard.discord_user.discriminator}
										</span>

										<span
											className={`${
												statusMap[
													lanyard.discord_status as keyof typeof statusMap
												]
											} h-2 w-2 inline-block rounded-full`}
										/>
									</span>
								) : null
							}
						/>
						<ListItem icon={SiTwitter} text="alistaiiiir" />
						<ListItem icon={RiPhoneLine} text="+1 (424) 395-8523" />
					</ul>
				</div>
			</div>
		</div>
	);
}
Example #20
Source File: CoingatePaymentOptions.tsx    From mysterium-vpn-desktop with MIT License 4 votes vote down vote up
CoingatePaymentOptions: React.FC = observer(() => {
    const { payment } = useStores()
    const navigate = useNavigate()
    const [loading, setLoading] = useState(false)

    const isOptionActive = (cur: string) => {
        return payment.paymentCurrency == cur
    }
    const selectOption = (cur: string) => () => {
        payment.setPaymentCurrency(cur)
    }
    const setUseLightning = (): void => {
        const val = !payment.lightningNetwork
        payment.setLightningNetwork(val)
    }

    const handleNextClick = async () => {
        setLoading(() => true)
        try {
            await payment.createOrder()
            setLoading(() => false)
            navigate("../" + topupSteps.coingateOrderSummary)
        } catch (err) {
            setLoading(() => false)
            const msg = parseError(err)
            logErrorMessage("Could not create a payment order", msg)
            toast.error(dismissibleToast(<span>{msg.humanReadable}</span>))
        }
    }
    const options = payment.paymentMethod?.gatewayData.currencies.filter((it) => it !== Currency.MYST) || []
    return (
        <ViewContainer>
            <ViewNavBar onBack={() => navigate(-1)}>
                <div style={{ width: 375, textAlign: "center" }}>
                    <StepProgressBar step={1} />
                </div>
            </ViewNavBar>
            <ViewSplit>
                <ViewSidebar>
                    <SideTop>
                        <IconWallet color={brandLight} />
                        <Title>Top up your account</Title>
                        <TitleDescription>Select the cryptocurrency in which the top-up will be done</TitleDescription>
                    </SideTop>
                    <SideBot>
                        <AmountSelect>
                            {options.map((opt) => (
                                <AmountToggle
                                    key={opt}
                                    active={isOptionActive(opt)}
                                    onClick={selectOption(opt)}
                                    inactiveColor={lightBlue}
                                    height="36px"
                                    justify="center"
                                >
                                    <div style={{ textAlign: "center" }}>
                                        <OptionValue>{opt}</OptionValue>
                                    </div>
                                </AmountToggle>
                            ))}
                        </AmountSelect>
                        {isLightningAvailable(payment.paymentCurrency) && (
                            <LightningCheckbox checked={payment.lightningNetwork} onChange={setUseLightning}>
                                Use lightning network
                            </LightningCheckbox>
                        )}
                        {payment.paymentCurrency == Currency.MYST && (
                            <Paragraph style={{ color: "red" }}>
                                <FontAwesomeIcon icon={faExclamationTriangle} style={{ marginRight: 5 }} />
                                {Currency.MYST} is currently only supported on the Ethereum network!
                            </Paragraph>
                        )}
                        <BrandButton
                            style={{ marginTop: "auto" }}
                            onClick={handleNextClick}
                            loading={loading}
                            disabled={loading || !payment.paymentCurrency}
                        >
                            Next
                        </BrandButton>
                    </SideBot>
                </ViewSidebar>
                <ViewContent>
                    <div style={{ paddingTop: 100 }}>
                        <CryptoAnimation currency={payment.paymentCurrency} />
                    </div>
                </ViewContent>
            </ViewSplit>
        </ViewContainer>
    )
})
Example #21
Source File: MystSelectAmount.tsx    From mysterium-vpn-desktop with MIT License 4 votes vote down vote up
MystSelectAmount: React.FC = observer(() => {
    const { payment } = useStores()
    const navigate = useNavigate()
    const [loading, setLoading] = useState(false)
    const isOptionActive = (amt: number) => {
        return payment.topUpAmountUSD == amt
    }
    const selectOption = (amt: number) => () => {
        payment.setTopupAmountUSD(amt)
    }
    const [estimates, setEstimates] = useState<EntertainmentEstimateResponse | undefined>(undefined)
    useEffect(() => {
        if (payment.topUpAmountUSD) {
            payment.estimateEntertainment({ USD: payment.topUpAmountUSD }).then((res) => setEstimates(res))
        }
    }, [payment.topUpAmountUSD])
    const handleNextClick = async () => {
        setLoading(() => true)
        try {
            await payment.createOrder()
            setLoading(() => false)
            navigate("../" + topupSteps.coingateOrderSummary)
        } catch (err) {
            setLoading(() => false)
            const msg = parseError(err)
            logErrorMessage("Could not create a payment order", msg)
            toast.error(dismissibleToast(<span>{msg.humanReadable}</span>))
        }
    }
    return (
        <ViewContainer>
            <ViewNavBar onBack={() => navigate(-1)}>
                <div style={{ width: 375, textAlign: "center" }}>
                    <StepProgressBar step={1} />
                </div>
            </ViewNavBar>
            <ViewSplit>
                <ViewSidebar>
                    <SideTop>
                        <IconWallet color={brandLight} />
                        <Title>Top up your account</Title>
                        <TitleDescription>Select the desired amount in {payment.appFiatCurrency}</TitleDescription>
                    </SideTop>
                    <SideBot>
                        <AmountSelect>
                            {payment.orderOptions.map((opt: number) => (
                                <AmountToggle
                                    key={opt}
                                    active={isOptionActive(opt)}
                                    onClick={selectOption(opt)}
                                    inactiveColor={lightBlue}
                                    height="63px"
                                    justify="center"
                                >
                                    <div style={{ textAlign: "center" }}>
                                        <Amount>{opt}</Amount>
                                        <Currency>{payment.appFiatCurrency}</Currency>
                                    </div>
                                </AmountToggle>
                            ))}
                        </AmountSelect>
                        <BrandButton
                            style={{ marginTop: "15px" }}
                            onClick={handleNextClick}
                            disabled={!payment.topUpAmountUSD || loading}
                            loading={loading}
                        >
                            Next
                        </BrandButton>
                    </SideBot>
                </ViewSidebar>
                <Content>
                    {!!estimates && (
                        <>
                            <EntertainmentBlocks>
                                <EntertainmentBlock>
                                    <BlockIcon>
                                        <IconPlay color={brandLight} />
                                    </BlockIcon>
                                    <BlockMetric>{estimates.videoMinutes}h</BlockMetric>
                                    <EntertainmentExplanation>Online video</EntertainmentExplanation>
                                </EntertainmentBlock>
                                <EntertainmentBlock>
                                    <BlockIcon>
                                        <IconMusic color={brandLight} />
                                    </BlockIcon>
                                    <BlockMetric>{estimates.musicMinutes}h</BlockMetric>
                                    <EntertainmentExplanation>Online music</EntertainmentExplanation>
                                </EntertainmentBlock>
                                <EntertainmentBlock>
                                    <BlockIcon>
                                        <IconCloudDownload color={brandLight} />
                                    </BlockIcon>
                                    <BlockMetric>{estimates.trafficMb}GiB</BlockMetric>
                                    <EntertainmentExplanation>of data download</EntertainmentExplanation>
                                </EntertainmentBlock>
                                <EntertainmentBlock>
                                    <BlockIcon>
                                        <IconDocument color={brandLight} />
                                    </BlockIcon>
                                    <BlockMetric>{estimates.browsingMinutes}h</BlockMetric>
                                    <EntertainmentExplanation>Web browsing</EntertainmentExplanation>
                                </EntertainmentBlock>
                            </EntertainmentBlocks>
                        </>
                    )}
                </Content>
            </ViewSplit>
        </ViewContainer>
    )
})
Example #22
Source File: PaypalPaymentOptions.tsx    From mysterium-vpn-desktop with MIT License 4 votes vote down vote up
PaypalPaymentOptions: React.FC = observer(() => {
    const { payment } = useStores()
    const navigate = useNavigate()
    const [loading, setLoading] = useState(false)
    const isOptionActive = (cur: string) => {
        return payment.paymentCurrency == cur
    }
    const selectOption = (cur: string) => () => {
        payment.setPaymentCurrency(cur)
    }
    const handleNextClick = async () => {
        setLoading(() => true)
        try {
            await payment.createOrder()
            setLoading(() => false)
            navigate("../" + topupSteps.paypalOrderSummary)
        } catch (err) {
            setLoading(() => false)
            const msg = parseError(err)
            logErrorMessage("Could not create a payment order", msg)
            toast.error(dismissibleToast(<span>{msg.humanReadable}</span>))
        }
    }
    const options = payment.paymentMethod?.gatewayData.currencies || []
    return (
        <ViewContainer>
            <ViewNavBar onBack={() => navigate(-1)}>
                <div style={{ width: 375, textAlign: "center" }}>
                    <StepProgressBar step={1} />
                </div>
            </ViewNavBar>
            <ViewSplit>
                <ViewSidebar>
                    <SideTop>
                        <IconWallet color={brandLight} />
                        <Title>Top up your account</Title>
                        <TitleDescription>Select the payment options</TitleDescription>
                    </SideTop>
                    <SideBot>
                        <PaymentOption>Payment currency:</PaymentOption>
                        <AmountSelect>
                            {options.map((opt) => {
                                let currencyIcon = faQuestionCircle
                                switch (opt) {
                                    case "EUR":
                                        currencyIcon = faEuroSign
                                        break
                                    case "USD":
                                        currencyIcon = faDollarSign
                                        break
                                    case "GBP":
                                        currencyIcon = faPoundSign
                                        break
                                }
                                return (
                                    <AmountToggle
                                        key={opt}
                                        active={isOptionActive(opt)}
                                        onClick={selectOption(opt)}
                                        inactiveColor={lightBlue}
                                        height="36px"
                                        justify="center"
                                    >
                                        <div style={{ textAlign: "center" }}>
                                            <OptionValue>
                                                <FontAwesomeIcon icon={currencyIcon} fixedWidth size="sm" />
                                                {opt}
                                            </OptionValue>
                                        </div>
                                    </AmountToggle>
                                )
                            })}
                        </AmountSelect>
                        <PaymentOption>Tax residence country (VAT):</PaymentOption>
                        <SelectTaxCountry />
                        <BrandButton
                            style={{ marginTop: "auto" }}
                            onClick={handleNextClick}
                            loading={loading}
                            disabled={loading || !payment.paymentCurrency || !payment.taxCountry}
                        >
                            Next
                        </BrandButton>
                    </SideBot>
                </ViewSidebar>
                <ViewContent />
            </ViewSplit>
        </ViewContainer>
    )
})
Example #23
Source File: StripePaymentOptions.tsx    From mysterium-vpn-desktop with MIT License 4 votes vote down vote up
StripePaymentOptions: React.FC = observer(() => {
    const { payment } = useStores()
    const navigate = useNavigate()
    const [loading, setLoading] = useState(false)
    const isOptionActive = (cur: string) => {
        return payment.paymentCurrency == cur
    }
    const selectOption = (cur: string) => () => {
        payment.setPaymentCurrency(cur)
    }
    const handleNextClick = async () => {
        setLoading(() => true)
        try {
            await payment.createOrder()
            setLoading(() => false)
            navigate("../" + topupSteps.stripeOrderSummary)
        } catch (err) {
            setLoading(() => false)
            const msg = parseError(err)
            logErrorMessage("Could not create a payment order", msg)
            toast.error(dismissibleToast(<span>{msg.humanReadable}</span>))
        }
    }
    const options = payment.paymentMethod?.gatewayData.currencies || []
    return (
        <ViewContainer>
            <ViewNavBar onBack={() => navigate(-1)}>
                <div style={{ width: 375, textAlign: "center" }}>
                    <StepProgressBar step={1} />
                </div>
            </ViewNavBar>
            <ViewSplit>
                <ViewSidebar>
                    <SideTop>
                        <IconWallet color={brandLight} />
                        <Title>Top up your account</Title>
                        <TitleDescription>Select the payment options</TitleDescription>
                    </SideTop>
                    <SideBot>
                        <PaymentOption>Payment currency:</PaymentOption>
                        <AmountSelect>
                            {options.map((opt) => {
                                let currencyIcon = faQuestionCircle
                                switch (opt) {
                                    case "EUR":
                                        currencyIcon = faEuroSign
                                        break
                                    case "USD":
                                        currencyIcon = faDollarSign
                                        break
                                    case "GBP":
                                        currencyIcon = faPoundSign
                                        break
                                }
                                return (
                                    <AmountToggle
                                        key={opt}
                                        active={isOptionActive(opt)}
                                        onClick={selectOption(opt)}
                                        inactiveColor={lightBlue}
                                        height="36px"
                                        justify="center"
                                    >
                                        <div style={{ textAlign: "center" }}>
                                            <OptionValue>
                                                <FontAwesomeIcon icon={currencyIcon} fixedWidth size="sm" />
                                                {opt}
                                            </OptionValue>
                                        </div>
                                    </AmountToggle>
                                )
                            })}
                        </AmountSelect>
                        <PaymentOption>Tax residence country (VAT):</PaymentOption>
                        <SelectTaxCountry />
                        <BrandButton
                            style={{ marginTop: "auto" }}
                            onClick={handleNextClick}
                            loading={loading}
                            disabled={loading || !payment.paymentCurrency || !payment.taxCountry}
                        >
                            Next
                        </BrandButton>
                    </SideBot>
                </ViewSidebar>
                <ViewContent />
            </ViewSplit>
        </ViewContainer>
    )
})
Example #24
Source File: WalletView.tsx    From mysterium-vpn-desktop with MIT License 4 votes vote down vote up
WalletView: React.FC = observer(function WalletView() {
    const { identity, payment } = useStores()
    const [topupLoading, setTopupLoading] = useState(false)
    const balance = Number(identity.identity?.balanceTokens.human) ?? 0
    const handleTopupClick = async () => {
        setTopupLoading(true)
        try {
            await payment.startTopupFlow(locations.walletTopup)
        } catch (err) {
            setTopupLoading(false)
            const msg = parseError(err)
            logErrorMessage("Could not contact payment gateways", msg)
            toast.error(dismissibleToast(<span>{msg.humanReadable}</span>))
        }
    }
    const [estimates, setEstimates] = useState<EntertainmentEstimateResponse | undefined>(undefined)
    useEffect(() => {
        payment.estimateEntertainment({ MYST: balance }).then((res) => setEstimates(res))
    }, [balance])
    const handleRefreshBalanceClick = () => {
        if (!identity.identity?.id) {
            return
        }
        toast.promise(identity.refreshBalance(), {
            loading: "Refreshing balance from blockchain",
            success: "Balance updated",
            error: "Failed to refresh balance",
        })
    }
    return (
        <ViewContainer>
            <ViewNavBar />

            <ViewSplit>
                <ViewSidebar>
                    <SideTop>
                        <IconWallet color={brandLight} />
                        <Balance>
                            {balance}{" "}
                            <BalanceRefreshButton
                                icon={faSync}
                                onClick={handleRefreshBalanceClick}
                                data-tip=""
                                data-for="balance-refresh-tooltip"
                            />
                        </Balance>
                        <Tooltip id="balance-refresh-tooltip">
                            <span>Force refresh wallet&apos;s balance from the blockchain.</span>
                        </Tooltip>
                        <BalanceCurrency>{payment.appCurrency}</BalanceCurrency>
                        <BalanceFiatEquivalent>
                            {payment.appFiatCurrency} equivalent ≈ {displayUSD(payment.fiatEquivalent(balance))}
                        </BalanceFiatEquivalent>
                    </SideTop>
                    <SideBot>
                        {!!estimates && (
                            <>
                                <Paragraph style={{ textAlign: "center", marginBottom: 10 }}>
                                    Will be enough for:
                                </Paragraph>
                                <EntertainmentBlocks>
                                    <EntertainmentBlock>
                                        <BlockIcon>
                                            <IconPlay color={brandLight} />
                                        </BlockIcon>
                                        <Heading2>{estimates.videoMinutes}h</Heading2>
                                        <EntertainmentExplanation>
                                            Online <br />
                                            video
                                        </EntertainmentExplanation>
                                    </EntertainmentBlock>
                                    <EntertainmentBlock>
                                        <BlockIcon>
                                            <IconMusic color={brandLight} />
                                        </BlockIcon>
                                        <Heading2>{estimates.musicMinutes}h</Heading2>
                                        <EntertainmentExplanation>
                                            Online <br />
                                            music
                                        </EntertainmentExplanation>
                                    </EntertainmentBlock>
                                    <EntertainmentBlock>
                                        <BlockIcon>
                                            <IconCloudDownload color={brandLight} />
                                        </BlockIcon>
                                        <Heading2>{estimates.trafficMb}GiB</Heading2>
                                        <EntertainmentExplanation>of data download</EntertainmentExplanation>
                                    </EntertainmentBlock>
                                    <EntertainmentBlock>
                                        <BlockIcon>
                                            <IconDocument color={brandLight} />
                                        </BlockIcon>
                                        <Heading2>{estimates.browsingMinutes}h</Heading2>
                                        <EntertainmentExplanation>
                                            Web <br />
                                            browsing
                                        </EntertainmentExplanation>
                                    </EntertainmentBlock>
                                </EntertainmentBlocks>
                            </>
                        )}
                        <BrandButton style={{ marginTop: "auto" }} onClick={handleTopupClick} loading={topupLoading}>
                            Top up
                        </BrandButton>
                    </SideBot>
                </ViewSidebar>
                <Content />
            </ViewSplit>
        </ViewContainer>
    )
})