mirror of
https://github.com/lukevella/rallly.git
synced 2025-08-06 09:59:00 +02:00
Translations (#225)
This commit is contained in:
parent
9c61d34f24
commit
bfb14b0e02
29 changed files with 342 additions and 275 deletions
|
@ -216,7 +216,7 @@ const Page: NextPage<CreatePollPageProps> = ({
|
|||
type="primary"
|
||||
>
|
||||
{currentStepIndex < steps.length - 1
|
||||
? t("next")
|
||||
? t("continue")
|
||||
: t("createPoll")}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import clsx from "clsx";
|
||||
import dayjs from "dayjs";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { usePlausible } from "next-plausible";
|
||||
import * as React from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
|
@ -25,6 +26,7 @@ interface CommentForm {
|
|||
|
||||
const Discussion: React.VoidFunctionComponent = () => {
|
||||
const queryClient = trpc.useContext();
|
||||
const { t } = useTranslation("app");
|
||||
const { poll } = usePoll();
|
||||
|
||||
const pollId = poll.id;
|
||||
|
@ -82,7 +84,7 @@ const Discussion: React.VoidFunctionComponent = () => {
|
|||
return (
|
||||
<div className="overflow-hidden border-t border-b shadow-sm md:rounded-lg md:border">
|
||||
<div className="border-b bg-white px-4 py-2">
|
||||
<div className="font-medium">Comments</div>
|
||||
<div className="font-medium">{t("comments")}</div>
|
||||
</div>
|
||||
<div
|
||||
className={clsx({
|
||||
|
@ -129,7 +131,7 @@ const Discussion: React.VoidFunctionComponent = () => {
|
|||
>
|
||||
<DropdownItem
|
||||
icon={Trash}
|
||||
label="Delete comment"
|
||||
label={t("deleteComment")}
|
||||
disabled={!canDelete}
|
||||
onClick={() => {
|
||||
deleteComment.mutate({
|
||||
|
@ -158,7 +160,7 @@ const Discussion: React.VoidFunctionComponent = () => {
|
|||
>
|
||||
<textarea
|
||||
id="comment"
|
||||
placeholder="Thanks for the invite!"
|
||||
placeholder={t("commentPlaceholder")}
|
||||
className="input w-full py-2 pl-3 pr-4"
|
||||
{...register("content", { validate: requiredString })}
|
||||
/>
|
||||
|
@ -175,7 +177,7 @@ const Discussion: React.VoidFunctionComponent = () => {
|
|||
/>
|
||||
</div>
|
||||
<Button htmlType="submit" loading={formState.isSubmitting}>
|
||||
Comment
|
||||
{t("comment")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import clsx from "clsx";
|
||||
import dayjs from "dayjs";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { usePlausible } from "next-plausible";
|
||||
import * as React from "react";
|
||||
|
||||
|
@ -37,6 +38,7 @@ const MonthCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
|||
duration,
|
||||
onChangeDuration,
|
||||
}) => {
|
||||
const { t } = useTranslation("app");
|
||||
const isTimedEvent = options.some((option) => option.type === "timeSlot");
|
||||
|
||||
const plausible = usePlausible();
|
||||
|
@ -91,14 +93,14 @@ const MonthCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
|||
<div className="mb-3 flex items-center justify-center space-x-4">
|
||||
<Button
|
||||
icon={<ChevronLeft />}
|
||||
title="Previous month"
|
||||
title={t("previousMonth")}
|
||||
onClick={datepicker.prev}
|
||||
/>
|
||||
<div className="grow text-center text-lg font-medium">
|
||||
{datepicker.label}
|
||||
</div>
|
||||
<Button
|
||||
title="Next month"
|
||||
title={t("nextMonth")}
|
||||
icon={<ChevronRight />}
|
||||
onClick={datepicker.next}
|
||||
/>
|
||||
|
@ -190,9 +192,9 @@ const MonthCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
|||
>
|
||||
<div className="flex items-center space-x-3 p-4">
|
||||
<div className="grow">
|
||||
<div className="font-medium">Specify times</div>
|
||||
<div className="font-medium">{t("specifyTimes")}</div>
|
||||
<div className="text-sm text-slate-400">
|
||||
Include start and end times for each option
|
||||
{t("specifyTimesDescription")}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
@ -338,7 +340,7 @@ const MonthCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
|||
]);
|
||||
}}
|
||||
>
|
||||
Add time option
|
||||
{t("addTimeOption")}
|
||||
</Button>
|
||||
<Dropdown
|
||||
trigger={<CompactButton icon={DotsHorizontal} />}
|
||||
|
@ -347,7 +349,7 @@ const MonthCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
|||
<DropdownItem
|
||||
icon={Magic}
|
||||
disabled={datepicker.selection.length < 2}
|
||||
label="Apply to all dates"
|
||||
label={t("applyToAllDates")}
|
||||
onClick={() => {
|
||||
plausible("Applied options to all dates");
|
||||
const times = optionsForDay.map(
|
||||
|
@ -384,7 +386,7 @@ const MonthCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
|||
}}
|
||||
/>
|
||||
<DropdownItem
|
||||
label="Delete date"
|
||||
label={t("deleteDate")}
|
||||
icon={Trash}
|
||||
onClick={() => {
|
||||
onChange(
|
||||
|
@ -431,7 +433,7 @@ const MonthCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
|||
<div className="flex h-full items-center justify-center py-12">
|
||||
<div className="text-center font-medium text-gray-400">
|
||||
<Calendar className="mb-2 inline-block h-12 w-12" />
|
||||
<div>No dates selected</div>
|
||||
<div>{t("noDatesSelected")}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -52,18 +52,18 @@ const PollOptionsForm: React.VoidFunctionComponent<
|
|||
const views = React.useMemo(() => {
|
||||
const res = [
|
||||
{
|
||||
label: "Month view",
|
||||
label: t("monthView"),
|
||||
value: "month",
|
||||
Component: MonthCalendar,
|
||||
},
|
||||
{
|
||||
label: "Week view",
|
||||
label: t("weekView"),
|
||||
value: "week",
|
||||
Component: WeekCalendar,
|
||||
},
|
||||
];
|
||||
return res;
|
||||
}, []);
|
||||
}, [t]);
|
||||
|
||||
const watchView = watch("view");
|
||||
|
||||
|
@ -79,10 +79,9 @@ const PollOptionsForm: React.VoidFunctionComponent<
|
|||
const datesOnly = watchOptions.every((option) => option.type === "date");
|
||||
|
||||
const [dateOrTimeRangeModal, openDateOrTimeRangeModal] = useModal({
|
||||
title: "Wait a minute… 🤔",
|
||||
description:
|
||||
"You can't have both time and date options in the same poll. Which would you like to keep?",
|
||||
okText: "Keep time options",
|
||||
title: t("mixedOptionsTitle"),
|
||||
description: t("mixedOptionsDescription"),
|
||||
okText: t("mixedOptionsKeepTimes"),
|
||||
onOk: () => {
|
||||
setValue(
|
||||
"options",
|
||||
|
@ -92,7 +91,7 @@ const PollOptionsForm: React.VoidFunctionComponent<
|
|||
setValue("timeZone", getBrowserTimeZone());
|
||||
}
|
||||
},
|
||||
cancelText: "Keep date options",
|
||||
cancelText: t("mixedOptionsKeepDates"),
|
||||
onCancel: () => {
|
||||
setValue(
|
||||
"options",
|
||||
|
@ -130,7 +129,7 @@ const PollOptionsForm: React.VoidFunctionComponent<
|
|||
|
||||
const [calendarHelpModal, openHelpModal] = useModal({
|
||||
overlayClosable: true,
|
||||
title: "Forget something?",
|
||||
title: t("calendarHelpTitle"),
|
||||
description: t("calendarHelp"),
|
||||
okText: t("ok"),
|
||||
});
|
||||
|
@ -172,7 +171,7 @@ const PollOptionsForm: React.VoidFunctionComponent<
|
|||
}}
|
||||
type="button"
|
||||
>
|
||||
<Calendar className="mr-2 h-5 w-5" /> Month view
|
||||
<Calendar className="mr-2 h-5 w-5" /> {t("monthView")}
|
||||
</button>
|
||||
<button
|
||||
className={clsx({
|
||||
|
@ -183,7 +182,7 @@ const PollOptionsForm: React.VoidFunctionComponent<
|
|||
setValue("view", "week");
|
||||
}}
|
||||
>
|
||||
<Table className="mr-2 h-5 w-5" /> Week view
|
||||
<Table className="mr-2 h-5 w-5" /> {t("weekView")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -191,7 +190,9 @@ const PollOptionsForm: React.VoidFunctionComponent<
|
|||
<div className="relative w-full">
|
||||
<React.Suspense
|
||||
fallback={
|
||||
<FullPageLoader className="h-[400px]">Loading…</FullPageLoader>
|
||||
<FullPageLoader className="h-[400px]">
|
||||
{t("loading")}
|
||||
</FullPageLoader>
|
||||
}
|
||||
>
|
||||
<selectedView.Component
|
||||
|
|
|
@ -38,7 +38,7 @@ export const UserDetailsForm: React.VoidFunctionComponent<
|
|||
style={{ width: 400 }}
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<h2>Your details</h2>
|
||||
<h2>{t("yourDetails")}</h2>
|
||||
<div className="formField">
|
||||
<label className="text-slate-500" htmlFor="name">
|
||||
{t("name")}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Head from "next/head";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import React from "react";
|
||||
|
||||
import Bonus from "./home/bonus";
|
||||
|
@ -7,14 +8,12 @@ import Hero from "./home/hero";
|
|||
import PageLayout from "./page-layout";
|
||||
|
||||
const Home: React.VoidFunctionComponent = () => {
|
||||
const { t } = useTranslation("homepage");
|
||||
return (
|
||||
<PageLayout>
|
||||
<Head>
|
||||
<meta
|
||||
name="description"
|
||||
content="Create polls and vote to find the best day or time. A free alternative to Doodle."
|
||||
/>
|
||||
<title>Rallly - Schedule group meetings</title>
|
||||
<meta name="description" content={t("metaDescription")} />
|
||||
<title>{t("metaTitle")}</title>
|
||||
</Head>
|
||||
<Hero />
|
||||
<Features />
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { Trans, useTranslation } from "next-i18next";
|
||||
import * as React from "react";
|
||||
|
||||
import Code from "@/components/icons/code.svg";
|
||||
|
@ -7,50 +8,52 @@ import Server from "@/components/icons/server.svg";
|
|||
import Ban from "./ban-ads.svg";
|
||||
|
||||
const Bonus: React.VoidFunctionComponent = () => {
|
||||
const { t } = useTranslation("homepage");
|
||||
return (
|
||||
<div className="mx-auto max-w-7xl px-8 pt-8 pb-24">
|
||||
<h2 className="heading">Principles</h2>
|
||||
<p className="subheading">We're not like the others</p>
|
||||
<h2 className="heading">{t("principles")}</h2>
|
||||
<p className="subheading">{t("principlesSubheading")}</p>
|
||||
<div className="grid grid-cols-4 gap-16">
|
||||
<div className="col-span-4 md:col-span-2 lg:col-span-1">
|
||||
<div className="mb-4 text-gray-400">
|
||||
<CursorClick className="w-16" />
|
||||
</div>
|
||||
<h3 className="heading-sm">No login required</h3>
|
||||
<h3 className="heading-sm">{t("noLoginRequired")}</h3>
|
||||
<div className="text text-base leading-relaxed">
|
||||
We keep things simple and don't ask for more than what we need.
|
||||
{t("noLoginRequiredDescription")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-4 md:col-span-2 lg:col-span-1">
|
||||
<div className="mb-4 text-gray-400">
|
||||
<Code className="w-16" />
|
||||
</div>
|
||||
<h3 className="heading-sm">Open-source</h3>
|
||||
<h3 className="heading-sm">{t("openSource")}</h3>
|
||||
<div className="text text-base leading-relaxed">
|
||||
The codebase is fully open-source and{" "}
|
||||
<a href="https://github.com/lukevella/rallly">
|
||||
available on github
|
||||
</a>
|
||||
.
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey={"openSourceDescription"}
|
||||
components={{
|
||||
a: <a href="https://github.com/lukevella/rallly" />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-4 md:col-span-2 lg:col-span-1">
|
||||
<div className="mb-4 text-gray-400">
|
||||
<Server className="w-16" />
|
||||
</div>
|
||||
<h3 className="heading-sm">Self-hostable</h3>
|
||||
<h3 className="heading-sm">{t("selfHostable")}</h3>
|
||||
<div className="text text-base leading-relaxed">
|
||||
Run it on your own server to get full control of your data.
|
||||
{t("selfHostableDescription")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-4 md:col-span-2 lg:col-span-1">
|
||||
<div className="mb-4 text-gray-400">
|
||||
<Ban className="w-16" />
|
||||
</div>
|
||||
<h3 className="heading-sm">Ad-free</h3>
|
||||
<h3 className="heading-sm">{t("adFree")}</h3>
|
||||
<div className="text text-base leading-relaxed">
|
||||
You can give your ad-blocker a rest – You won't need it
|
||||
here.
|
||||
{t("adFreeDescription")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { useTranslation } from "next-i18next";
|
||||
import * as React from "react";
|
||||
|
||||
import Bell from "@/components/icons/bell.svg";
|
||||
|
@ -6,56 +7,44 @@ import Clock from "@/components/icons/clock.svg";
|
|||
import DeviceMobile from "@/components/icons/device-mobile.svg";
|
||||
|
||||
const Features: React.VoidFunctionComponent = () => {
|
||||
const { t } = useTranslation("homepage");
|
||||
return (
|
||||
<div className="mx-auto max-w-7xl py-16 px-8">
|
||||
<h2 className="heading">Features</h2>
|
||||
<p className="subheading">Everything you need to get the job done</p>
|
||||
<h2 className="heading">{t("features")}</h2>
|
||||
<p className="subheading">{t("featuresSubheading")}</p>
|
||||
<div className="grid grid-cols-2 gap-12">
|
||||
<div className="col-span-2 md:col-span-1">
|
||||
<div className="mb-4 inline-block rounded-2xl bg-green-100/50 p-3 text-green-400">
|
||||
<Clock className="h-8 w-8" />
|
||||
</div>
|
||||
<h3 className="heading-sm flex items-center">
|
||||
Time slots
|
||||
{t("timeSlots")}
|
||||
<span className="ml-2 rounded-full bg-green-500 px-2 py-1 text-sm font-normal text-white">
|
||||
New
|
||||
{t("new")}
|
||||
</span>
|
||||
</h3>
|
||||
<p className="text">
|
||||
If you need more granular options, Rallly lets you choose time slots
|
||||
as options. If your participants are international, they can see
|
||||
times in their own time zone.
|
||||
</p>
|
||||
<p className="text">{t("timeSlotsDescription")}</p>
|
||||
</div>
|
||||
<div className="col-span-2 md:col-span-1">
|
||||
<div className="mb-4 inline-block rounded-2xl bg-cyan-100/50 p-3 text-cyan-400">
|
||||
<DeviceMobile className="h-8 w-8" />
|
||||
</div>
|
||||
<h3 className="heading-sm">Mobile friendly design</h3>
|
||||
<p className="text">
|
||||
Rallly is optimized to look and work great on mobile devices so you
|
||||
and your participants can use it on the go.
|
||||
</p>
|
||||
<h3 className="heading-sm">{t("mobileFriendly")}</h3>
|
||||
<p className="text">{t("mobileFriendlyDescription")}</p>
|
||||
</div>
|
||||
<div className="col-span-2 md:col-span-1">
|
||||
<div className="mb-4 inline-block rounded-2xl bg-rose-100/50 p-3 text-rose-400">
|
||||
<Bell className="h-8 w-8" />
|
||||
</div>
|
||||
<h3 className="heading-sm">Notifications</h3>
|
||||
<p className="text">
|
||||
Need help staying on top of things? Rallly can send you an email
|
||||
whenever participants vote or comment on your poll.
|
||||
</p>
|
||||
<h3 className="heading-sm">{t("notifications")}</h3>
|
||||
<p className="text">{t("notificationsDescription")}</p>
|
||||
</div>
|
||||
<div className="col-span-2 md:col-span-1">
|
||||
<div className="mb-4 inline-block rounded-2xl bg-yellow-100/50 p-3 text-yellow-400">
|
||||
<Chat className="h-8 w-8" />
|
||||
</div>
|
||||
<h3 className="heading-sm">Comments</h3>
|
||||
<p className="text">
|
||||
Got a question or just have something to say? You and your
|
||||
participants can comment on polls to start a discussion.
|
||||
</p>
|
||||
<h3 className="heading-sm">{t("comments")}</h3>
|
||||
<p className="text">{t("commentsDescription")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { motion } from "framer-motion";
|
||||
import Link from "next/link";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { Trans, useTranslation } from "next-i18next";
|
||||
import * as React from "react";
|
||||
|
||||
import { UserAvatarProvider } from "../poll/user-avatar";
|
||||
|
@ -15,15 +15,16 @@ const Hero: React.VoidFunctionComponent = () => {
|
|||
<div className="mx-auto max-w-7xl items-end p-8 lg:flex lg:justify-between">
|
||||
<div className="my-8 text-center lg:text-left">
|
||||
<h1 className="text-4xl font-bold sm:text-5xl">
|
||||
Schedule
|
||||
<br />
|
||||
<span className="text-primary-500">group meetings</span>
|
||||
<br />
|
||||
with ease
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey="heroText"
|
||||
components={{
|
||||
br: <br />,
|
||||
s: <span className="whitespace-nowrap text-primary-500" />,
|
||||
}}
|
||||
/>
|
||||
</h1>
|
||||
<div className="mb-12 text-xl text-gray-400">
|
||||
Find the right date without the back and forth.
|
||||
</div>
|
||||
<div className="mb-12 text-xl text-gray-400">{t("heroSubText")}</div>
|
||||
<div className="space-x-3">
|
||||
<Link href="/new">
|
||||
<a className="rounded-lg bg-primary-500 px-5 py-3 font-semibold text-white shadow-sm transition-all hover:bg-primary-500/90 hover:text-white hover:no-underline hover:shadow-md focus:ring-2 focus:ring-primary-200 active:bg-primary-600/90">
|
||||
|
@ -35,7 +36,7 @@ const Hero: React.VoidFunctionComponent = () => {
|
|||
className="rounded-lg bg-slate-500 px-5 py-3 font-semibold text-white shadow-sm transition-all hover:bg-slate-500/90 hover:text-white hover:no-underline hover:shadow-md focus:ring-2 focus:ring-primary-200 active:bg-slate-600/90"
|
||||
rel="nofollow"
|
||||
>
|
||||
{t("viewDemo")}
|
||||
{t("liveDemo")}
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
|
@ -60,7 +61,7 @@ const Hero: React.VoidFunctionComponent = () => {
|
|||
animate={{ opacity: 1, translateY: 0 }}
|
||||
transition={{ type: "spring", delay: 2 }}
|
||||
>
|
||||
Perfect! 🤩
|
||||
{t("perfect")} 🤩
|
||||
<ScribbleArrow className="absolute -right-8 top-3 text-slate-400" />
|
||||
</motion.div>
|
||||
<motion.div
|
||||
|
|
|
@ -33,7 +33,7 @@ const participants = [
|
|||
const options = ["2022-12-14", "2022-12-15", "2022-12-16", "2022-12-17"];
|
||||
|
||||
const PollDemo: React.VoidFunctionComponent = () => {
|
||||
const { t } = useTranslation("app");
|
||||
const { t } = useTranslation("homepage");
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
import Link from "next/link";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import * as React from "react";
|
||||
|
||||
const Stats: React.VoidFunctionComponent = () => {
|
||||
const { t } = useTranslation("homepage");
|
||||
return (
|
||||
<div className="py-16">
|
||||
<h2 className="heading text-center">Stats</h2>
|
||||
<p className="subheading text-center">100,000+ polls created</p>
|
||||
<div className="flex justify-center space-x-3">
|
||||
<Link href="/new">
|
||||
<a className="bg-primary-500 hover:bg-primary-500/90 focus:ring-primary-200 active:bg-primary-600/90 rounded-lg px-5 py-3 font-semibold text-white shadow-sm transition-all hover:text-white hover:no-underline hover:shadow-md focus:ring-2">
|
||||
{t("getStarted")}
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="/demo">
|
||||
<a
|
||||
className="focus:ring-primary-200 rounded-lg bg-slate-500 px-5 py-3 font-semibold text-white shadow-sm transition-all hover:bg-slate-500/90 hover:text-white hover:no-underline hover:shadow-md focus:ring-2 active:bg-slate-600/90"
|
||||
rel="nofollow"
|
||||
>
|
||||
{t("viewDemo")}
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Stats;
|
|
@ -3,6 +3,7 @@ import dynamic from "next/dynamic";
|
|||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { Trans, useTranslation } from "next-i18next";
|
||||
import * as React from "react";
|
||||
import { createBreakpoint } from "react-use";
|
||||
|
||||
|
@ -28,7 +29,7 @@ const Menu: React.VoidFunctionComponent<{ className: string }> = ({
|
|||
<Link href="/">
|
||||
<a
|
||||
className={clsx(
|
||||
"hover:text-primary-500 text-gray-400 transition-colors hover:no-underline hover:underline-offset-2",
|
||||
"text-gray-400 transition-colors hover:text-primary-500 hover:no-underline hover:underline-offset-2",
|
||||
{
|
||||
"pointer-events-none font-bold text-gray-600":
|
||||
pathname === "/home",
|
||||
|
@ -41,7 +42,7 @@ const Menu: React.VoidFunctionComponent<{ className: string }> = ({
|
|||
<Link href="https://blog.rallly.co">
|
||||
<a
|
||||
className={clsx(
|
||||
"hover:text-primary-500 text-gray-400 transition-colors hover:no-underline hover:underline-offset-2",
|
||||
"text-gray-400 transition-colors hover:text-primary-500 hover:no-underline hover:underline-offset-2",
|
||||
)}
|
||||
>
|
||||
Blog
|
||||
|
@ -49,12 +50,12 @@ const Menu: React.VoidFunctionComponent<{ className: string }> = ({
|
|||
</Link>
|
||||
<a
|
||||
href="https://support.rallly.co"
|
||||
className="hover:text-primary-500 text-gray-400 transition-colors hover:no-underline hover:underline-offset-2"
|
||||
className="text-gray-400 transition-colors hover:text-primary-500 hover:no-underline hover:underline-offset-2"
|
||||
>
|
||||
Support
|
||||
</a>
|
||||
<Link href="https://github.com/lukevella/rallly">
|
||||
<a className="hover:text-primary-500 text-gray-400 transition-colors hover:no-underline hover:underline-offset-2">
|
||||
<a className="text-gray-400 transition-colors hover:text-primary-500 hover:no-underline hover:underline-offset-2">
|
||||
<Github className="w-6" />
|
||||
</a>
|
||||
</Link>
|
||||
|
@ -66,6 +67,7 @@ const PageLayout: React.VoidFunctionComponent<PageLayoutProps> = ({
|
|||
children,
|
||||
}) => {
|
||||
const breakpoint = useBreakpoint();
|
||||
const { t } = useTranslation("homepage");
|
||||
return (
|
||||
<div className="bg-pattern min-h-full overflow-x-hidden">
|
||||
<Head>
|
||||
|
@ -76,11 +78,11 @@ const PageLayout: React.VoidFunctionComponent<PageLayoutProps> = ({
|
|||
<div className="relative inline-block">
|
||||
<Link href="/">
|
||||
<a>
|
||||
<Logo className="text-primary-500 w-40" alt="Rallly" />
|
||||
<Logo className="w-40 text-primary-500" alt="Rallly" />
|
||||
</a>
|
||||
</Link>
|
||||
<span className="absolute -bottom-6 right-0 text-sm text-slate-400 transition-colors">
|
||||
Yes—with 3 <em>L</em>s
|
||||
<Trans t={t} i18nKey="3Ls" components={{ e: <em /> }} />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -89,7 +91,7 @@ const PageLayout: React.VoidFunctionComponent<PageLayoutProps> = ({
|
|||
<Popover
|
||||
placement="left-start"
|
||||
trigger={
|
||||
<button className="hover:text-primary-500 text-gray-400 transition-colors hover:no-underline hover:underline-offset-2">
|
||||
<button className="text-gray-400 transition-colors hover:text-primary-500 hover:no-underline hover:underline-offset-2">
|
||||
<DotsVertical className="w-5" />
|
||||
</button>
|
||||
}
|
||||
|
|
|
@ -34,32 +34,32 @@ const Footer: React.VoidFunctionComponent = () => {
|
|||
aria-label="Star lukevella/rallly on GitHub"
|
||||
data-show-count={true}
|
||||
>
|
||||
Star
|
||||
{t("star")}
|
||||
</GitHubButton>
|
||||
<GitHubButton
|
||||
href="https://github.com/sponsors/lukevella"
|
||||
data-icon="octicon-heart"
|
||||
aria-label="Sponsor @lukevella on GitHub"
|
||||
>
|
||||
Sponsor this project
|
||||
{t("sponsorThisProject")}
|
||||
</GitHubButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-6 md:col-span-2">
|
||||
<div className="mb-4 font-medium">Links</div>
|
||||
<div className="mb-4 font-medium">{t("links")}</div>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
className="font-normal leading-loose text-gray-400 hover:text-gray-800 hover:no-underline"
|
||||
href="https://github.com/lukevella/rallly/discussions"
|
||||
>
|
||||
Forum
|
||||
{t("discussions")}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="https://blog.rallly.co">
|
||||
<a className="font-normal leading-loose text-gray-400 hover:text-gray-800 hover:no-underline">
|
||||
Blog
|
||||
{t("blog")}
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
|
@ -68,20 +68,20 @@ const Footer: React.VoidFunctionComponent = () => {
|
|||
href="https://support.rallly.co"
|
||||
className="font-normal leading-loose text-gray-400 hover:text-gray-800 hover:no-underline"
|
||||
>
|
||||
Support
|
||||
{t("support")}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/privacy-policy">
|
||||
<a className="font-normal leading-loose text-gray-400 hover:text-gray-800 hover:no-underline">
|
||||
Privacy Policy
|
||||
{t("privacyPolicy")}
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="col-span-6 md:col-span-2">
|
||||
<div className="mb-4 font-medium">Follow</div>
|
||||
<div className="mb-4 font-medium">{t("follow")}</div>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
|
@ -107,7 +107,7 @@ const Footer: React.VoidFunctionComponent = () => {
|
|||
className="inline-block text-white"
|
||||
>
|
||||
<span className="mb-1 inline-block w-full text-right text-xs italic text-gray-400">
|
||||
Powered by
|
||||
{t("poweredBy")}
|
||||
</span>
|
||||
<Vercel className="w-24" />
|
||||
</a>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Participant, Vote, VoteType } from "@prisma/client";
|
||||
import { keyBy } from "lodash";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import React from "react";
|
||||
|
||||
import Trash from "@/components/icons/trash.svg";
|
||||
|
@ -57,6 +58,7 @@ export const PollContextProvider: React.VoidFunctionComponent<{
|
|||
admin: boolean;
|
||||
children?: React.ReactNode;
|
||||
}> = ({ poll, urlId, admin, children }) => {
|
||||
const { t } = useTranslation("app");
|
||||
const { participants } = useParticipants();
|
||||
const [isDeleted, setDeleted] = React.useState(false);
|
||||
const { user } = useSession();
|
||||
|
@ -176,8 +178,8 @@ export const PollContextProvider: React.VoidFunctionComponent<{
|
|||
return (
|
||||
<ErrorPage
|
||||
icon={Trash}
|
||||
title="Deleted poll"
|
||||
description="This poll doesn't exist anymore."
|
||||
title={t("deletedPoll")}
|
||||
description={t("deletedPollInfo")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ const PollPage: NextPage = () => {
|
|||
|
||||
const verifyEmail = trpc.useMutation(["polls.verification.verify"], {
|
||||
onSuccess: () => {
|
||||
toast.success("Your poll has been verified");
|
||||
toast.success(t("pollHasBeenVerified"));
|
||||
queryClient.setQueryData(["polls.get", { urlId, admin }], {
|
||||
...poll,
|
||||
verified: true,
|
||||
|
@ -61,7 +61,7 @@ const PollPage: NextPage = () => {
|
|||
plausible("Verified email");
|
||||
},
|
||||
onError: () => {
|
||||
toast.error("Your link has expired or is no longer valid");
|
||||
toast.error(t("linkHasExpired"));
|
||||
},
|
||||
onSettled: () => {
|
||||
router.replace(`/admin/${router.query.urlId}`, undefined, {
|
||||
|
@ -83,7 +83,7 @@ const PollPage: NextPage = () => {
|
|||
{ urlId: urlId, notifications: false },
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.success("Notifications have been disabled");
|
||||
toast.success(t("notificationsDisabled"));
|
||||
plausible("Unsubscribed from notifications");
|
||||
},
|
||||
},
|
||||
|
@ -92,7 +92,7 @@ const PollPage: NextPage = () => {
|
|||
shallow: true,
|
||||
});
|
||||
}
|
||||
}, [plausible, urlId, router, updatePollMutation]);
|
||||
}, [plausible, urlId, router, updatePollMutation, t]);
|
||||
|
||||
const checkIfWideScreen = () => window.innerWidth > 640;
|
||||
|
||||
|
@ -146,7 +146,7 @@ const PollPage: NextPage = () => {
|
|||
setSharingVisible((value) => !value);
|
||||
}}
|
||||
>
|
||||
Share
|
||||
{t("share")}
|
||||
</Button>
|
||||
</div>
|
||||
<AnimatePresence initial={false}>
|
||||
|
@ -196,11 +196,10 @@ const PollPage: NextPage = () => {
|
|||
{!poll.admin && poll.adminUrlId ? (
|
||||
<div className="mb-4 items-center justify-between rounded-lg px-4 md:flex md:space-x-4 md:border md:p-2 md:pl-4">
|
||||
<div className="mb-4 font-medium md:mb-0">
|
||||
Hey {poll.user.name}, looks like you are the owner of this
|
||||
poll.
|
||||
{t("pollOwnerNotice", { name: poll.user.name })}
|
||||
</div>
|
||||
<a href={`/admin/${poll.adminUrlId}`} className="btn-default">
|
||||
Go to admin →
|
||||
{t("goToAdmin")} →
|
||||
</a>
|
||||
</div>
|
||||
) : null}
|
||||
|
@ -210,7 +209,7 @@ const PollPage: NextPage = () => {
|
|||
<LockClosed className="w-6" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium">This poll has been locked</div>
|
||||
<div className="font-medium">{t("pollHasBeenLocked")}</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
@ -243,22 +242,26 @@ const PollPage: NextPage = () => {
|
|||
) : null}
|
||||
<div>
|
||||
<div className="mb-2 text-sm text-slate-500">
|
||||
Possible answers
|
||||
{t("possibleAnswers")}
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<span className="inline-flex items-center space-x-1">
|
||||
<VoteIcon type="yes" />
|
||||
<span className="text-xs text-slate-500">Yes</span>
|
||||
<span className="text-xs text-slate-500">
|
||||
{t("yes")}
|
||||
</span>
|
||||
</span>
|
||||
<span className="inline-flex items-center space-x-1">
|
||||
<VoteIcon type="ifNeedBe" />
|
||||
<span className="text-xs text-slate-500">
|
||||
If need be
|
||||
{t("ifNeedBe")}
|
||||
</span>
|
||||
</span>
|
||||
<span className="inline-flex items-center space-x-1">
|
||||
<VoteIcon type="no" />
|
||||
<span className="text-xs text-slate-500">No</span>
|
||||
<span className="text-xs text-slate-500">
|
||||
{t("no")}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -269,7 +272,9 @@ const PollPage: NextPage = () => {
|
|||
</React.Suspense>
|
||||
</div>
|
||||
|
||||
<React.Suspense fallback={<div className="p-4">Loading…</div>}>
|
||||
<React.Suspense
|
||||
fallback={<div className="p-4">{t("loading")}</div>}
|
||||
>
|
||||
<Discussion />
|
||||
</React.Suspense>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import clsx from "clsx";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import * as React from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
|
||||
|
@ -27,6 +28,7 @@ const ParticipantRowForm: React.ForwardRefRenderFunction<
|
|||
HTMLFormElement,
|
||||
ParticipantRowFormProps
|
||||
> = ({ defaultValues, onSubmit, className, onCancel }, ref) => {
|
||||
const { t } = useTranslation("app");
|
||||
const {
|
||||
columnWidth,
|
||||
scrollPosition,
|
||||
|
@ -168,7 +170,7 @@ const ParticipantRowForm: React.ForwardRefRenderFunction<
|
|||
loading={isSubmitting}
|
||||
data-testid="submitNewParticipant"
|
||||
>
|
||||
Save
|
||||
{t("save")}
|
||||
</Button>
|
||||
) : null}
|
||||
{scrollPosition < maxScrollPosition ? (
|
||||
|
@ -178,7 +180,7 @@ const ParticipantRowForm: React.ForwardRefRenderFunction<
|
|||
goToNextPage();
|
||||
}}
|
||||
>
|
||||
Next →
|
||||
{t("next")} →
|
||||
</Button>
|
||||
) : null}
|
||||
{onCancel ? <CompactButton onClick={onCancel} icon={X} /> : null}
|
||||
|
|
|
@ -55,13 +55,13 @@ const ManagePoll: React.VoidFunctionComponent<{
|
|||
openChangeOptionsModal,
|
||||
closeChangeOptionsModal,
|
||||
] = useModal({
|
||||
okText: "Save",
|
||||
okText: t("save"),
|
||||
okButtonProps: {
|
||||
form: "pollOptions",
|
||||
htmlType: "submit",
|
||||
loading: isUpdating,
|
||||
},
|
||||
cancelText: "Cancel",
|
||||
cancelText: t("cancel"),
|
||||
content: (
|
||||
<React.Suspense fallback={null}>
|
||||
<PollOptionsForm
|
||||
|
@ -116,7 +116,7 @@ const ManagePoll: React.VoidFunctionComponent<{
|
|||
|
||||
if (optionsToDeleteThatHaveVotes.length > 0) {
|
||||
modalContext.render({
|
||||
title: "Are you sure?",
|
||||
title: t("areYouSure"),
|
||||
description: (
|
||||
<Trans
|
||||
t={t}
|
||||
|
@ -128,8 +128,8 @@ const ManagePoll: React.VoidFunctionComponent<{
|
|||
okButtonProps: {
|
||||
type: "danger",
|
||||
},
|
||||
okText: "Delete",
|
||||
cancelText: "Cancel",
|
||||
okText: t("delete"),
|
||||
cancelText: t("cancel"),
|
||||
});
|
||||
} else {
|
||||
onOk();
|
||||
|
@ -145,13 +145,13 @@ const ManagePoll: React.VoidFunctionComponent<{
|
|||
openChangePollDetailsModa,
|
||||
closePollDetailsModal,
|
||||
] = useModal({
|
||||
okText: "Save changes",
|
||||
okText: t("save"),
|
||||
okButtonProps: {
|
||||
form: "updateDetails",
|
||||
loading: isUpdating,
|
||||
htmlType: "submit",
|
||||
},
|
||||
cancelText: "Cancel",
|
||||
cancelText: t("cancel"),
|
||||
content: (
|
||||
<PollDetailsForm
|
||||
name="updateDetails"
|
||||
|
@ -181,31 +181,35 @@ const ManagePoll: React.VoidFunctionComponent<{
|
|||
>
|
||||
<DropdownItem
|
||||
icon={Pencil}
|
||||
label="Edit details"
|
||||
label={t("editDetails")}
|
||||
onClick={openChangePollDetailsModa}
|
||||
/>
|
||||
<DropdownItem
|
||||
icon={Table}
|
||||
label="Edit options"
|
||||
label={t("editOptions")}
|
||||
onClick={handleChangeOptions}
|
||||
/>
|
||||
<DropdownItem icon={Save} label="Export to CSV" onClick={exportToCsv} />
|
||||
<DropdownItem
|
||||
icon={Save}
|
||||
label={t("exportToCsv")}
|
||||
onClick={exportToCsv}
|
||||
/>
|
||||
{poll.closed ? (
|
||||
<DropdownItem
|
||||
icon={LockOpen}
|
||||
label="Unlock poll"
|
||||
label={t("unlockPoll")}
|
||||
onClick={() => updatePollMutation({ urlId, closed: false })}
|
||||
/>
|
||||
) : (
|
||||
<DropdownItem
|
||||
icon={LockClosed}
|
||||
label="Lock poll"
|
||||
label={t("lockPoll")}
|
||||
onClick={() => updatePollMutation({ urlId, closed: true })}
|
||||
/>
|
||||
)}
|
||||
<DropdownItem
|
||||
icon={Trash}
|
||||
label="Delete poll"
|
||||
label={t("deletePoll")}
|
||||
onClick={() => {
|
||||
modalContext.render({
|
||||
overlayClosable: true,
|
||||
|
|
|
@ -26,7 +26,7 @@ const NotificationsToggle: React.VoidFunctionComponent = () => {
|
|||
poll.notifications ? (
|
||||
<div>
|
||||
<div className="font-medium text-primary-300">
|
||||
Notifications are on
|
||||
{t("notificationsOn")}
|
||||
</div>
|
||||
<div className="max-w-sm">
|
||||
<Trans
|
||||
|
@ -44,10 +44,10 @@ const NotificationsToggle: React.VoidFunctionComponent = () => {
|
|||
</div>
|
||||
</div>
|
||||
) : (
|
||||
"Notifications are off"
|
||||
t("notificationsOff")
|
||||
)
|
||||
) : (
|
||||
"You need to verify your email to turn on notifications"
|
||||
t("notificationsVerifyEmail")
|
||||
)
|
||||
}
|
||||
>
|
||||
|
|
|
@ -22,7 +22,9 @@ const Preferences: React.VoidFunctionComponent = () => {
|
|||
</div>
|
||||
<div className="grow">
|
||||
<div className="mb-2">
|
||||
<div className="mb-2 grow text-sm text-slate-500">Week starts on</div>
|
||||
<div className="mb-2 grow text-sm text-slate-500">
|
||||
{t("weekStartsOn")}
|
||||
</div>
|
||||
<div>
|
||||
<div className="segment-button inline-flex">
|
||||
<button
|
||||
|
@ -61,7 +63,9 @@ const Preferences: React.VoidFunctionComponent = () => {
|
|||
</div>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<div className="mb-2 grow text-sm text-slate-500">Time format</div>
|
||||
<div className="mb-2 grow text-sm text-slate-500">
|
||||
{t("timeFormat")}
|
||||
</div>
|
||||
<div className="segment-button inline-flex">
|
||||
<button
|
||||
className={clsx({
|
||||
|
|
|
@ -26,7 +26,7 @@ export const Profile: React.VoidFunctionComponent = () => {
|
|||
return (
|
||||
<div className="card my-4 p-0">
|
||||
<Head>
|
||||
<title>Profile - Login</title>
|
||||
<title>{t("profileLogin")}</title>
|
||||
</Head>
|
||||
<LoginForm />
|
||||
</div>
|
||||
|
@ -36,7 +36,11 @@ export const Profile: React.VoidFunctionComponent = () => {
|
|||
return (
|
||||
<div className="mx-auto max-w-3xl py-4 lg:mx-0">
|
||||
<Head>
|
||||
<title>Profile - {user.name}</title>
|
||||
<title>
|
||||
{t("profileUser", {
|
||||
username: user.name,
|
||||
})}
|
||||
</title>
|
||||
</Head>
|
||||
<div className="mb-4 flex items-center px-4">
|
||||
<div className="mr-4 inline-flex h-14 w-14 items-center justify-center rounded-lg bg-primary-50">
|
||||
|
@ -50,7 +54,7 @@ export const Profile: React.VoidFunctionComponent = () => {
|
|||
{user.shortName}
|
||||
</div>
|
||||
<div className="text-slate-500">
|
||||
{user.isGuest ? "Guest" : "User"}
|
||||
{user.isGuest ? t("guest") : t("user")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -92,7 +96,7 @@ export const Profile: React.VoidFunctionComponent = () => {
|
|||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<EmptyState icon={Pencil} text="No polls created" />
|
||||
<EmptyState icon={Pencil} text={t("pollsEmpty")} />
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
|
|
@ -32,13 +32,13 @@ const Sharing: React.VoidFunctionComponent<SharingProps> = ({
|
|||
<div className={clsx("card p-4", className)}>
|
||||
<div className="mb-1 flex items-center justify-between">
|
||||
<div className="text-lg font-semibold text-slate-700">
|
||||
Share via link
|
||||
{t("shareLink")}
|
||||
</div>
|
||||
<button
|
||||
onClick={onHide}
|
||||
className="h-8 items-center justify-center rounded-md px-3 text-slate-400 transition-colors hover:bg-slate-500/10 hover:text-slate-500 active:bg-slate-500/20"
|
||||
>
|
||||
Hide
|
||||
{t("hide")}
|
||||
</button>
|
||||
</div>
|
||||
<div className="mb-4 text-slate-600">
|
||||
|
@ -71,7 +71,7 @@ const Sharing: React.VoidFunctionComponent<SharingProps> = ({
|
|||
}}
|
||||
className="md:absolute md:top-1/2 md:right-3 md:-translate-y-1/2"
|
||||
>
|
||||
{didCopy ? "Copied" : "Copy Link"}
|
||||
{didCopy ? t("copied") : t("copyLink")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import clsx from "clsx";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import Link from "next/link";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import React from "react";
|
||||
|
||||
import Menu from "@/components/icons/menu.svg";
|
||||
|
@ -40,6 +41,7 @@ const MobileNavigation: React.VoidFunctionComponent<{
|
|||
openLoginModal: () => void;
|
||||
}> = ({ openLoginModal }) => {
|
||||
const { user } = useSession();
|
||||
const { t } = useTranslation("app");
|
||||
return (
|
||||
<div
|
||||
className="fixed top-0 z-40 flex h-12 w-full shrink-0 items-center justify-between border-b bg-gray-50
|
||||
|
@ -55,7 +57,7 @@ const MobileNavigation: React.VoidFunctionComponent<{
|
|||
className="flex w-full cursor-pointer items-center space-x-2 whitespace-nowrap rounded-md px-2 py-1 font-medium text-slate-600 transition-colors hover:bg-gray-200 hover:text-slate-600 hover:no-underline active:bg-gray-300"
|
||||
>
|
||||
<Login className="h-5 opacity-75" />
|
||||
<span className="inline-block">Login</span>
|
||||
<span className="inline-block">{t("login")}</span>
|
||||
</button>
|
||||
)}
|
||||
<AnimatePresence initial={false}>
|
||||
|
@ -93,7 +95,7 @@ const MobileNavigation: React.VoidFunctionComponent<{
|
|||
className="group flex items-center whitespace-nowrap rounded-md px-2 py-1 font-medium text-slate-600 transition-colors hover:bg-gray-200 hover:text-slate-600 hover:no-underline active:bg-gray-300"
|
||||
>
|
||||
<Adjustments className="h-5 opacity-75 group-hover:text-primary-500" />
|
||||
<span className="ml-2 hidden sm:block">Preferences</span>
|
||||
<span className="ml-2 hidden sm:block">{t("preferences")}</span>
|
||||
</button>
|
||||
}
|
||||
>
|
||||
|
@ -107,7 +109,7 @@ const MobileNavigation: React.VoidFunctionComponent<{
|
|||
className="group flex items-center rounded-md px-2 py-1 font-medium text-slate-600 transition-colors hover:bg-gray-200 hover:text-slate-600 hover:no-underline active:bg-gray-300"
|
||||
>
|
||||
<Menu className="w-5 group-hover:text-primary-500" />
|
||||
<span className="ml-2 hidden sm:block">Menu</span>
|
||||
<span className="ml-2 hidden sm:block">{t("menu")}</span>
|
||||
</button>
|
||||
}
|
||||
>
|
||||
|
@ -121,12 +123,13 @@ const MobileNavigation: React.VoidFunctionComponent<{
|
|||
const AppMenu: React.VoidFunctionComponent<{ className?: string }> = ({
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation("app");
|
||||
return (
|
||||
<div className={clsx("space-y-1", className)}>
|
||||
<Link href="/new">
|
||||
<a className="flex cursor-pointer items-center space-x-2 whitespace-nowrap rounded-md px-2 py-1 pr-4 font-medium text-slate-600 transition-colors hover:bg-gray-200 hover:text-slate-600 hover:no-underline active:bg-gray-300">
|
||||
<Pencil className="h-5 opacity-75 " />
|
||||
<span className="inline-block">New Poll</span>
|
||||
<span className="inline-block">{t("newPoll")}</span>
|
||||
</a>
|
||||
</Link>
|
||||
<a
|
||||
|
@ -136,7 +139,7 @@ const AppMenu: React.VoidFunctionComponent<{ className?: string }> = ({
|
|||
rel="noreferrer"
|
||||
>
|
||||
<Support className="h-5 opacity-75" />
|
||||
<span className="inline-block">Support</span>
|
||||
<span className="inline-block">{t("support")}</span>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
|
@ -146,6 +149,7 @@ const UserDropdown: React.VoidFunctionComponent<
|
|||
DropdownProps & { openLoginModal: () => void }
|
||||
> = ({ children, openLoginModal, ...forwardProps }) => {
|
||||
const { logout, user } = useSession();
|
||||
const { t } = useTranslation("app");
|
||||
const modalContext = useModalContext();
|
||||
if (!user) {
|
||||
return null;
|
||||
|
@ -175,17 +179,14 @@ const UserDropdown: React.VoidFunctionComponent<
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
You are using a guest session. This allows us to recognize
|
||||
you if you come back later so you can edit your votes.
|
||||
</p>
|
||||
<p>{t("guestSessionNotice")}</p>
|
||||
<div>
|
||||
<a
|
||||
href="https://support.rallly.co/guest-sessions"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Read more about guest sessions.
|
||||
{t("guestSessionReadMore")}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -204,20 +205,19 @@ const UserDropdown: React.VoidFunctionComponent<
|
|||
) : null}
|
||||
<DropdownItem
|
||||
icon={Logout}
|
||||
label={user.isGuest ? "Forget me" : "Logout"}
|
||||
label={user.isGuest ? t("forgetMe") : t("logout")}
|
||||
onClick={() => {
|
||||
if (user?.isGuest) {
|
||||
modalContext.render({
|
||||
title: "Are you sure?",
|
||||
description:
|
||||
"Once a guest session ends it cannot be resumed. You will not be able to edit any votes or comments you've made with this session.",
|
||||
title: t("areYouSure"),
|
||||
description: t("endingGuestSessionNotice"),
|
||||
|
||||
onOk: logout,
|
||||
okButtonProps: {
|
||||
type: "danger",
|
||||
},
|
||||
okText: "End session",
|
||||
cancelText: "Cancel",
|
||||
okText: t("endSession"),
|
||||
cancelText: t("cancel"),
|
||||
});
|
||||
} else {
|
||||
logout();
|
||||
|
@ -232,6 +232,7 @@ const StandardLayout: React.VoidFunctionComponent<{
|
|||
children?: React.ReactNode;
|
||||
}> = ({ children, ...rest }) => {
|
||||
const { user } = useSession();
|
||||
const { t } = useTranslation("app");
|
||||
const [loginModal, openLoginModal] = useModal({
|
||||
footer: null,
|
||||
overlayClosable: true,
|
||||
|
@ -255,7 +256,7 @@ const StandardLayout: React.VoidFunctionComponent<{
|
|||
<Link href="/new">
|
||||
<a className="group mb-1 flex items-center space-x-3 whitespace-nowrap rounded-md px-3 py-1 font-medium text-slate-600 transition-colors hover:bg-slate-500/10 hover:text-slate-600 hover:no-underline active:bg-slate-500/20">
|
||||
<Pencil className="h-5 opacity-75 group-hover:text-primary-500 group-hover:opacity-100" />
|
||||
<span className="grow text-left">New Poll</span>
|
||||
<span className="grow text-left">{t("newPoll")}</span>
|
||||
</a>
|
||||
</Link>
|
||||
<a
|
||||
|
@ -265,14 +266,14 @@ const StandardLayout: React.VoidFunctionComponent<{
|
|||
rel="noreferrer"
|
||||
>
|
||||
<Support className="h-5 opacity-75 group-hover:text-primary-500 group-hover:opacity-100" />
|
||||
<span className="grow text-left">Support</span>
|
||||
<span className="grow text-left">{t("support")}</span>
|
||||
</a>
|
||||
<Popover
|
||||
placement="right-start"
|
||||
trigger={
|
||||
<button className="group flex w-full items-center space-x-3 whitespace-nowrap rounded-md px-3 py-1 font-medium text-slate-600 transition-colors hover:bg-slate-500/10 hover:text-slate-600 hover:no-underline active:bg-slate-500/20">
|
||||
<Adjustments className="h-5 opacity-75 group-hover:text-primary-500 group-hover:opacity-100" />
|
||||
<span className="grow text-left">Preferences</span>
|
||||
<span className="grow text-left">{t("preferences")}</span>
|
||||
<DotsVertical className="h-4 text-slate-500 opacity-0 transition-opacity group-hover:opacity-100" />
|
||||
</button>
|
||||
}
|
||||
|
@ -285,7 +286,7 @@ const StandardLayout: React.VoidFunctionComponent<{
|
|||
className="group flex w-full items-center space-x-3 whitespace-nowrap rounded-md px-3 py-1 font-medium text-slate-600 transition-colors hover:bg-slate-500/10 hover:text-slate-600 hover:no-underline active:bg-slate-500/20"
|
||||
>
|
||||
<Login className="h-5 opacity-75 group-hover:text-primary-500 group-hover:opacity-100" />
|
||||
<span className="grow text-left">Login</span>
|
||||
<span className="grow text-left">{t("login")}</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
@ -311,7 +312,7 @@ const StandardLayout: React.VoidFunctionComponent<{
|
|||
{user.shortName}
|
||||
</div>
|
||||
<div className="truncate text-xs text-slate-500">
|
||||
{user.isGuest ? "Guest" : "User"}
|
||||
{user.isGuest ? t("guest") : t("user")}
|
||||
</div>
|
||||
</div>
|
||||
<DotsVertical className="h-4 text-slate-500 opacity-0 transition-opacity group-hover:opacity-100" />
|
||||
|
@ -343,16 +344,16 @@ const StandardLayout: React.VoidFunctionComponent<{
|
|||
className="text-sm text-slate-400 transition-colors hover:text-primary-500 hover:no-underline"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Support
|
||||
{t("support")}
|
||||
</a>
|
||||
<Link href="https://github.com/lukevella/rallly/discussions">
|
||||
<a className="text-sm text-slate-400 transition-colors hover:text-primary-500 hover:no-underline">
|
||||
Discussions
|
||||
{t("discussions")}
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="https://blog.rallly.co">
|
||||
<a className="text-sm text-slate-400 transition-colors hover:text-primary-500 hover:no-underline">
|
||||
Blog
|
||||
{t("blog")}
|
||||
</a>
|
||||
</Link>
|
||||
<div className="hidden text-slate-300 lg:block">•</div>
|
||||
|
@ -373,7 +374,7 @@ const StandardLayout: React.VoidFunctionComponent<{
|
|||
<Link href="https://www.paypal.com/donate/?hosted_button_id=7QXP2CUBLY88E">
|
||||
<a className="inline-flex h-8 items-center rounded-full bg-slate-100 pl-2 pr-3 text-sm text-slate-400 transition-colors hover:bg-primary-500 hover:text-white hover:no-underline focus:ring-2 focus:ring-primary-500 focus:ring-offset-1 active:bg-primary-600">
|
||||
<Cash className="mr-1 inline-block w-5" />
|
||||
<span>Donate</span>
|
||||
<span>{t("donate")}</span>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue