more strings extractions (#963)
This commit is contained in:
parent
e1f4ba06d8
commit
c4e2b6b458
20 changed files with 201 additions and 112 deletions
|
@ -1,7 +1,10 @@
|
|||
import { XIcon } from "@heroicons/react/outline";
|
||||
import { useState } from "react";
|
||||
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
|
||||
export default function AddToHomescreen() {
|
||||
const { t } = useLocale();
|
||||
const [closeBanner, setCloseBanner] = useState(false);
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
|
@ -27,9 +30,7 @@ export default function AddToHomescreen() {
|
|||
</svg>
|
||||
</span>
|
||||
<p className="ml-3 text-xs font-medium text-white">
|
||||
<span className="inline">
|
||||
Add this app to your home screen for faster access and improved experience.
|
||||
</span>
|
||||
<span className="inline">{t("add_to_homescreen")}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -38,7 +39,7 @@ export default function AddToHomescreen() {
|
|||
onClick={() => setCloseBanner(true)}
|
||||
type="button"
|
||||
className="-mr-1 flex p-2 rounded-md hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-white">
|
||||
<span className="sr-only">Dismiss</span>
|
||||
<span className="sr-only">{t("dismiss")}</span>
|
||||
<XIcon className="h-6 w-6 text-white" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
import React from "react";
|
||||
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
|
||||
import NavTabs from "./NavTabs";
|
||||
|
||||
export default function BookingsShell({ children }: { children: React.ReactNode }) {
|
||||
const { t } = useLocale();
|
||||
const tabs = [
|
||||
{
|
||||
name: "Upcoming",
|
||||
name: t("upcoming"),
|
||||
href: "/bookings/upcoming",
|
||||
},
|
||||
{
|
||||
name: "Past",
|
||||
name: t("past"),
|
||||
href: "/bookings/past",
|
||||
},
|
||||
{
|
||||
name: "Cancelled",
|
||||
name: t("cancelled"),
|
||||
href: "/bookings/cancelled",
|
||||
},
|
||||
];
|
||||
|
|
|
@ -3,6 +3,7 @@ import Cropper from "react-easy-crop";
|
|||
|
||||
import { Area, getCroppedImg } from "@lib/cropImage";
|
||||
import { useFileReader } from "@lib/hooks/useFileReader";
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
|
||||
import { DialogClose, DialogTrigger, Dialog, DialogContent } from "@components/Dialog";
|
||||
import Slider from "@components/Slider";
|
||||
|
@ -24,6 +25,7 @@ function CropContainer({
|
|||
imageSrc: string;
|
||||
onCropComplete: (croppedAreaPixels: Area) => void;
|
||||
}) {
|
||||
const { t } = useLocale();
|
||||
const [crop, setCrop] = useState({ x: 0, y: 0 });
|
||||
const [zoom, setZoom] = useState(1);
|
||||
|
||||
|
@ -49,7 +51,7 @@ function CropContainer({
|
|||
min={1}
|
||||
max={3}
|
||||
step={0.1}
|
||||
label="Slide to zoom, drag to reposition"
|
||||
label={t("slide_zoom_drag_instructions")}
|
||||
changeHandler={handleZoomSliderChange}
|
||||
/>
|
||||
</div>
|
||||
|
@ -63,6 +65,7 @@ export default function ImageUploader({
|
|||
handleAvatarChange,
|
||||
...props
|
||||
}: ImageUploaderProps) {
|
||||
const { t } = useLocale();
|
||||
const [imageSrc, setImageSrc] = useState<string | null>();
|
||||
const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>();
|
||||
|
||||
|
@ -110,7 +113,7 @@ export default function ImageUploader({
|
|||
<div className="sm:flex sm:items-start mb-4">
|
||||
<div className="mt-3 text-center sm:mt-0 sm:text-left">
|
||||
<h3 className="font-cal text-lg leading-6 font-bold text-gray-900" id="modal-title">
|
||||
Upload {target}
|
||||
{t("upload_target", { target })}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -118,7 +121,11 @@ export default function ImageUploader({
|
|||
<div className="cropper mt-6 flex flex-col justify-center items-center p-8 bg-gray-50">
|
||||
{!result && (
|
||||
<div className="flex justify-start items-center bg-gray-500 max-h-20 h-20 w-20 rounded-full">
|
||||
{!imageSrc && <p className="sm:text-xs text-sm text-white w-full text-center">No {target}</p>}
|
||||
{!imageSrc && (
|
||||
<p className="sm:text-xs text-sm text-white w-full text-center">
|
||||
{t("no_target", { target })}
|
||||
</p>
|
||||
)}
|
||||
{imageSrc && <img className="h-20 w-20 rounded-full" src={imageSrc} alt={target} />}
|
||||
</div>
|
||||
)}
|
||||
|
@ -128,20 +135,20 @@ export default function ImageUploader({
|
|||
onInput={onInputFile}
|
||||
type="file"
|
||||
name={id}
|
||||
placeholder="Upload image"
|
||||
placeholder={t("upload_image")}
|
||||
className="mt-4 pointer-events-none opacity-0 absolute"
|
||||
accept="image/*"
|
||||
/>
|
||||
Choose a file...
|
||||
{t("choose_a_file")}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse gap-x-2">
|
||||
<DialogClose asChild>
|
||||
<Button onClick={() => showCroppedImage(croppedAreaPixels)}>Save</Button>
|
||||
<Button onClick={() => showCroppedImage(croppedAreaPixels)}>{t("save")}</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button color="secondary">Cancel</Button>
|
||||
<Button color="secondary">{t("cancel")}</Button>
|
||||
</DialogClose>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
|
|
@ -232,7 +232,7 @@ export default function Shell(props: {
|
|||
</Link>
|
||||
<div className="flex items-center self-center gap-3">
|
||||
<button className="p-2 text-gray-400 bg-white rounded-full hover:text-gray-500 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black">
|
||||
<span className="sr-only">View notifications</span>
|
||||
<span className="sr-only">{t("view_notifications")}</span>
|
||||
<Link href="/settings/profile">
|
||||
<a>
|
||||
<CogIcon className="w-6 h-6" aria-hidden="true" />
|
||||
|
@ -294,6 +294,7 @@ export default function Shell(props: {
|
|||
}
|
||||
|
||||
function UserDropdown({ small }: { small?: boolean }) {
|
||||
const { t } = useLocale();
|
||||
const query = useMeQuery();
|
||||
const user = query.data;
|
||||
|
||||
|
@ -331,7 +332,7 @@ function UserDropdown({ small }: { small?: boolean }) {
|
|||
rel="noopener noreferrer"
|
||||
href={`${process.env.NEXT_PUBLIC_APP_URL}/${user?.username || ""}`}
|
||||
className="flex px-4 py-2 text-sm text-neutral-500">
|
||||
View public page <ExternalLinkIcon className="w-3 h-3 mt-1 ml-1 text-neutral-400" />
|
||||
{t("view_public_page")} <ExternalLinkIcon className="w-3 h-3 mt-1 ml-1 text-neutral-400" />
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator className="h-px bg-gray-200" />
|
||||
|
@ -363,7 +364,7 @@ function UserDropdown({ small }: { small?: boolean }) {
|
|||
fill="#9BA6B6"></path>
|
||||
</g>
|
||||
</svg>
|
||||
Join our Slack
|
||||
{t("join_our_slack")}
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
<HelpMenuItemDynamic />
|
||||
|
@ -379,7 +380,7 @@ function UserDropdown({ small }: { small?: boolean }) {
|
|||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Sign out
|
||||
{t("sign_out")}
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
|
|
|
@ -4,6 +4,7 @@ import dayjs from "dayjs";
|
|||
import { useMutation } from "react-query";
|
||||
|
||||
import { HttpError } from "@lib/core/http/error";
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
import { inferQueryOutput, trpc } from "@lib/trpc";
|
||||
|
||||
import TableActions, { ActionType } from "@components/ui/TableActions";
|
||||
|
@ -11,6 +12,7 @@ import TableActions, { ActionType } from "@components/ui/TableActions";
|
|||
type BookingItem = inferQueryOutput<"viewer.bookings">[number];
|
||||
|
||||
function BookingListItem(booking: BookingItem) {
|
||||
const { t } = useLocale();
|
||||
const utils = trpc.useContext();
|
||||
const mutation = useMutation(
|
||||
async (confirm: boolean) => {
|
||||
|
@ -37,14 +39,14 @@ function BookingListItem(booking: BookingItem) {
|
|||
const pendingActions: ActionType[] = [
|
||||
{
|
||||
id: "reject",
|
||||
label: "Reject",
|
||||
label: t("reject"),
|
||||
onClick: () => mutation.mutate(false),
|
||||
icon: BanIcon,
|
||||
disabled: mutation.isLoading,
|
||||
},
|
||||
{
|
||||
id: "confirm",
|
||||
label: "Confirm",
|
||||
label: t("confirm"),
|
||||
onClick: () => mutation.mutate(true),
|
||||
icon: CheckIcon,
|
||||
disabled: mutation.isLoading,
|
||||
|
@ -55,13 +57,13 @@ function BookingListItem(booking: BookingItem) {
|
|||
const bookedActions: ActionType[] = [
|
||||
{
|
||||
id: "cancel",
|
||||
label: "Cancel",
|
||||
label: t("cancel"),
|
||||
href: `/cancel/${booking.uid}`,
|
||||
icon: XIcon,
|
||||
},
|
||||
{
|
||||
id: "reschedule",
|
||||
label: "Reschedule",
|
||||
label: t("reschedule"),
|
||||
href: `/reschedule/${booking.uid}`,
|
||||
icon: ClockIcon,
|
||||
},
|
||||
|
@ -78,7 +80,7 @@ function BookingListItem(booking: BookingItem) {
|
|||
</div>
|
||||
{!booking.confirmed && !booking.rejected && (
|
||||
<span className="mb-2 inline-flex items-center px-1.5 py-0.5 rounded-sm text-xs font-medium bg-yellow-100 text-yellow-800">
|
||||
Unconfirmed
|
||||
{t("unconfirmed")}
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
|
@ -86,7 +88,7 @@ function BookingListItem(booking: BookingItem) {
|
|||
<div className="sm:hidden">
|
||||
{!booking.confirmed && !booking.rejected && (
|
||||
<span className="mb-2 inline-flex items-center px-1.5 py-0.5 rounded-sm text-xs font-medium bg-yellow-100 text-yellow-800">
|
||||
Unconfirmed
|
||||
{t("unconfirmed")}
|
||||
</span>
|
||||
)}
|
||||
<div className="text-sm text-gray-900 font-medium">
|
||||
|
@ -117,7 +119,9 @@ function BookingListItem(booking: BookingItem) {
|
|||
<>
|
||||
{!booking.confirmed && !booking.rejected && <TableActions actions={pendingActions} />}
|
||||
{booking.confirmed && !booking.rejected && <TableActions actions={bookedActions} />}
|
||||
{!booking.confirmed && booking.rejected && <div className="text-sm text-gray-500">Rejected</div>}
|
||||
{!booking.confirmed && booking.rejected && (
|
||||
<div className="text-sm text-gray-500">{t("rejected")}</div>
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
</td>
|
||||
|
|
|
@ -151,7 +151,7 @@ const BookingPage = (props: BookingPageProps) => {
|
|||
|
||||
if (payload["location"]) {
|
||||
if (payload["location"].includes("integration")) {
|
||||
params.location = "Web conferencing details to follow.";
|
||||
params.location = t("web_conferencing_details_to_follow");
|
||||
} else {
|
||||
params.location = payload["location"];
|
||||
}
|
||||
|
@ -398,7 +398,7 @@ const BookingPage = (props: BookingPageProps) => {
|
|||
<label
|
||||
htmlFor="guests"
|
||||
className="block mb-1 text-sm font-medium text-gray-700 dark:text-white">
|
||||
Guests
|
||||
{t("guests")}
|
||||
</label>
|
||||
<ReactMultiEmail
|
||||
className="relative"
|
||||
|
|
|
@ -3,12 +3,7 @@ import React, { FC } from "react";
|
|||
import { Controller, SubmitHandler, useForm, useWatch } from "react-hook-form";
|
||||
import Select, { OptionTypeBase } from "react-select";
|
||||
|
||||
const inputOptions: OptionTypeBase[] = [
|
||||
{ value: EventTypeCustomInputType.TEXT, label: "Text" },
|
||||
{ value: EventTypeCustomInputType.TEXTLONG, label: "Multiline Text" },
|
||||
{ value: EventTypeCustomInputType.NUMBER, label: "Number" },
|
||||
{ value: EventTypeCustomInputType.BOOL, label: "Checkbox" },
|
||||
];
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
|
||||
interface Props {
|
||||
onSubmit: SubmitHandler<IFormInput>;
|
||||
|
@ -19,6 +14,13 @@ interface Props {
|
|||
type IFormInput = EventTypeCustomInput;
|
||||
|
||||
const CustomInputTypeForm: FC<Props> = (props) => {
|
||||
const { t } = useLocale();
|
||||
const inputOptions: OptionTypeBase[] = [
|
||||
{ value: EventTypeCustomInputType.TEXT, label: t("text") },
|
||||
{ value: EventTypeCustomInputType.TEXTLONG, label: t("multiline_text") },
|
||||
{ value: EventTypeCustomInputType.NUMBER, label: t("number") },
|
||||
{ value: EventTypeCustomInputType.BOOL, label: t("checkbox") },
|
||||
];
|
||||
const { selectedCustomInput } = props;
|
||||
const defaultValues = selectedCustomInput || { type: inputOptions[0].value };
|
||||
const { register, control, handleSubmit } = useForm<IFormInput>({
|
||||
|
@ -35,7 +37,7 @@ const CustomInputTypeForm: FC<Props> = (props) => {
|
|||
<form onSubmit={handleSubmit(props.onSubmit)}>
|
||||
<div className="mb-2">
|
||||
<label htmlFor="type" className="block text-sm font-medium text-gray-700">
|
||||
Input type
|
||||
{t("input_type")}
|
||||
</label>
|
||||
<Controller
|
||||
name="type"
|
||||
|
@ -57,7 +59,7 @@ const CustomInputTypeForm: FC<Props> = (props) => {
|
|||
</div>
|
||||
<div className="mb-2">
|
||||
<label htmlFor="label" className="block text-sm font-medium text-gray-700">
|
||||
Label
|
||||
{t("label")}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<input
|
||||
|
@ -74,7 +76,7 @@ const CustomInputTypeForm: FC<Props> = (props) => {
|
|||
selectedInputType === EventTypeCustomInputType.TEXTLONG) && (
|
||||
<div className="mb-2">
|
||||
<label htmlFor="placeholder" className="block text-sm font-medium text-gray-700">
|
||||
Placeholder
|
||||
{t("placeholder")}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<input
|
||||
|
@ -96,7 +98,7 @@ const CustomInputTypeForm: FC<Props> = (props) => {
|
|||
{...register("required")}
|
||||
/>
|
||||
<label htmlFor="required" className="block text-sm font-medium text-gray-700">
|
||||
Is required
|
||||
{t("is_required")}
|
||||
</label>
|
||||
</div>
|
||||
<input
|
||||
|
@ -113,10 +115,10 @@ const CustomInputTypeForm: FC<Props> = (props) => {
|
|||
/>
|
||||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<button type="submit" className="btn btn-primary">
|
||||
Save
|
||||
{t("save")}
|
||||
</button>
|
||||
<button onClick={onCancel} type="button" className="mr-2 btn btn-white">
|
||||
Cancel
|
||||
{t("cancel")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -79,7 +79,7 @@ const EventTypeList = ({ readOnly, types, profile }: Props): JSX.Element => {
|
|||
}))}
|
||||
/>
|
||||
)}
|
||||
<Tooltip content="Preview">
|
||||
<Tooltip content={t("preview")}>
|
||||
<a
|
||||
href={`${process.env.NEXT_PUBLIC_APP_URL}/${profile.slug}/${type.slug}`}
|
||||
target="_blank"
|
||||
|
@ -89,10 +89,10 @@ const EventTypeList = ({ readOnly, types, profile }: Props): JSX.Element => {
|
|||
</a>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content="Copy link">
|
||||
<Tooltip content={t("copy_link")}>
|
||||
<button
|
||||
onClick={() => {
|
||||
showToast("Link copied!", "success");
|
||||
showToast(t("link_copied"), "success");
|
||||
navigator.clipboard.writeText(
|
||||
`${process.env.NEXT_PUBLIC_APP_URL}/${profile.slug}/${type.slug}`
|
||||
);
|
||||
|
|
|
@ -27,13 +27,6 @@ enum SetupStep {
|
|||
EnterTotpCode,
|
||||
}
|
||||
|
||||
const setupDescriptions = {
|
||||
[SetupStep.ConfirmPassword]: "Confirm your current password to get started.",
|
||||
[SetupStep.DisplayQrCode]:
|
||||
"Scan the image below with the authenticator app on your phone or manually enter the text code instead.",
|
||||
[SetupStep.EnterTotpCode]: "Enter the six-digit code from your authenticator app below.",
|
||||
};
|
||||
|
||||
const WithStep = ({
|
||||
step,
|
||||
current,
|
||||
|
@ -47,6 +40,12 @@ const WithStep = ({
|
|||
};
|
||||
|
||||
const EnableTwoFactorModal = ({ onEnable, onCancel }: EnableTwoFactorModalProps) => {
|
||||
const { t } = useLocale();
|
||||
const setupDescriptions = {
|
||||
[SetupStep.ConfirmPassword]: t("2fa_confirm_current_password"),
|
||||
[SetupStep.DisplayQrCode]: t("2fa_scan_image_or_use_code"),
|
||||
[SetupStep.EnterTotpCode]: t("2fa_enter_six_digit_code"),
|
||||
};
|
||||
const [step, setStep] = useState(SetupStep.ConfirmPassword);
|
||||
const [password, setPassword] = useState("");
|
||||
const [totpCode, setTotpCode] = useState("");
|
||||
|
@ -54,7 +53,6 @@ const EnableTwoFactorModal = ({ onEnable, onCancel }: EnableTwoFactorModalProps)
|
|||
const [secret, setSecret] = useState("");
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
const { t } = useLocale();
|
||||
|
||||
async function handleSetup(e: SyntheticEvent) {
|
||||
e.preventDefault();
|
||||
|
|
|
@ -100,7 +100,7 @@ export default function TeamListItem(props: {
|
|||
<span className="self-center h-6 px-3 py-1 text-xs text-gray-700 capitalize rounded-md bg-gray-50">
|
||||
{t("owner")}
|
||||
</span>
|
||||
<Tooltip content="Copy link">
|
||||
<Tooltip content={t("copy_link")}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
|
@ -155,7 +155,7 @@ export default function TeamListItem(props: {
|
|||
</DialogTrigger>
|
||||
<ConfirmationDialogContent
|
||||
variety="danger"
|
||||
title="Disband Team"
|
||||
title={t("disband_team")}
|
||||
confirmBtnText={t("confirm_disband_team")}
|
||||
onConfirm={() => props.onActionSelect("disband")}>
|
||||
{t("disband_team_confirmation_message")}
|
||||
|
|
|
@ -1,23 +1,28 @@
|
|||
import Link from "next/link";
|
||||
|
||||
const PoweredByCal = () => (
|
||||
<div className="text-xs text-center sm:text-right p-1">
|
||||
<Link href={`https://cal.com?utm_source=embed&utm_medium=powered-by-button`}>
|
||||
<a target="_blank" className="dark:text-white text-gray-500 opacity-50 hover:opacity-100">
|
||||
powered by{" "}
|
||||
<img
|
||||
className="dark:hidden w-auto inline h-[10px] relative -mt-px"
|
||||
src="https://cal.com/logo.svg"
|
||||
alt="Cal.com Logo"
|
||||
/>
|
||||
<img
|
||||
className="hidden dark:inline w-auto h-[10px] relativ -mt-px"
|
||||
src="https://cal.com/logo-white.svg"
|
||||
alt="Cal.com Logo"
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
|
||||
const PoweredByCal = () => {
|
||||
const { t } = useLocale();
|
||||
return (
|
||||
<div className="text-xs text-center sm:text-right p-1">
|
||||
<Link href={`https://cal.com?utm_source=embed&utm_medium=powered-by-button`}>
|
||||
<a target="_blank" className="dark:text-white text-gray-500 opacity-50 hover:opacity-100">
|
||||
{t("powered_by")}{" "}
|
||||
<img
|
||||
className="dark:hidden w-auto inline h-[10px] relative -mt-px"
|
||||
src="https://cal.com/logo.svg"
|
||||
alt="Cal.com Logo"
|
||||
/>
|
||||
<img
|
||||
className="hidden dark:inline w-auto h-[10px] relativ -mt-px"
|
||||
src="https://cal.com/logo-white.svg"
|
||||
alt="Cal.com Logo"
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PoweredByCal;
|
||||
|
|
|
@ -3,6 +3,8 @@ import classnames from "classnames";
|
|||
import dayjs, { Dayjs } from "dayjs";
|
||||
import React from "react";
|
||||
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
|
||||
import Text from "@components/ui/Text";
|
||||
|
||||
export const SCHEDULE_FORM_ID = "SCHEDULE_FORM_ID";
|
||||
|
@ -79,6 +81,7 @@ type Props = {
|
|||
};
|
||||
|
||||
const SchedulerForm = ({ schedule = DEFAULT_SCHEDULE, onSubmit }: Props) => {
|
||||
const { t } = useLocale();
|
||||
const ref = React.useRef<HTMLFormElement>(null);
|
||||
|
||||
const transformElementsToSchedule = (elements: HTMLFormControlsCollection): Schedule => {
|
||||
|
@ -299,7 +302,7 @@ const SchedulerForm = ({ schedule = DEFAULT_SCHEDULE, onSubmit }: Props) => {
|
|||
))
|
||||
) : (
|
||||
<Text key={`${day}`} variant="caption">
|
||||
Unavailable
|
||||
{t("unavailable")}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -6,6 +6,8 @@ import utc from "dayjs/plugin/utc";
|
|||
import React, { useEffect, useState } from "react";
|
||||
import TimezoneSelect from "react-timezone-select";
|
||||
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
|
||||
import { WeekdaySelect } from "./WeekdaySelect";
|
||||
import SetTimesModal from "./modal/SetTimesModal";
|
||||
|
||||
|
@ -24,6 +26,7 @@ export const Scheduler = ({
|
|||
timeZone: selectedTimeZone,
|
||||
setTimeZone,
|
||||
}: Props) => {
|
||||
const { t } = useLocale();
|
||||
const [editSchedule, setEditSchedule] = useState(-1);
|
||||
const [dateOverrides, setDateOverrides] = useState([]);
|
||||
const [openingHours, setOpeningHours] = useState([]);
|
||||
|
@ -81,7 +84,7 @@ export const Scheduler = ({
|
|||
.startOf("day")
|
||||
.add(item.startTime, "minutes")
|
||||
.format(item.startTime % 60 === 0 ? "ha" : "h:mma")}
|
||||
until
|
||||
{t("until")}
|
||||
{dayjs()
|
||||
.startOf("day")
|
||||
.add(item.endTime, "minutes")
|
||||
|
@ -103,7 +106,7 @@ export const Scheduler = ({
|
|||
<div className="w-full">
|
||||
<div>
|
||||
<label htmlFor="timeZone" className="block text-sm font-medium text-gray-700">
|
||||
Timezone
|
||||
{t("timezone")}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<TimezoneSelect
|
||||
|
@ -120,7 +123,7 @@ export const Scheduler = ({
|
|||
))}
|
||||
</ul>
|
||||
<button type="button" onClick={addNewSchedule} className="btn-white btn-sm mt-2">
|
||||
Add another
|
||||
{t("add_another")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,6 +3,7 @@ import { DotsHorizontalIcon } from "@heroicons/react/solid";
|
|||
import React, { FC, Fragment } from "react";
|
||||
|
||||
import classNames from "@lib/classNames";
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
import { SVGComponent } from "@lib/types/SVGComponent";
|
||||
|
||||
import Button from "./Button";
|
||||
|
@ -20,6 +21,7 @@ interface Props {
|
|||
}
|
||||
|
||||
const TableActions: FC<Props> = ({ actions }) => {
|
||||
const { t } = useLocale();
|
||||
return (
|
||||
<>
|
||||
<div className="space-x-2 hidden lg:block">
|
||||
|
@ -41,7 +43,7 @@ const TableActions: FC<Props> = ({ actions }) => {
|
|||
<>
|
||||
<div>
|
||||
<Menu.Button className="text-neutral-400 mt-1 p-2 border border-transparent hover:border-gray-200">
|
||||
<span className="sr-only">Open options</span>
|
||||
<span className="sr-only">{t("open_options")}</span>
|
||||
<DotsHorizontalIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</Menu.Button>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { CheckIcon, XIcon } from "@heroicons/react/outline";
|
||||
import React, { ForwardedRef, useEffect, useState } from "react";
|
||||
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
|
||||
import Avatar from "@components/ui/Avatar";
|
||||
import Select from "@components/ui/form/Select";
|
||||
|
||||
|
@ -21,6 +23,7 @@ export type CheckedSelectProps = {
|
|||
|
||||
export const CheckedSelect = React.forwardRef((props: CheckedSelectProps, ref: ForwardedRef<unknown>) => {
|
||||
const [selectedOptions, setSelectedOptions] = useState<CheckedSelectValue>(props.defaultValue || []);
|
||||
const { t } = useLocale();
|
||||
|
||||
useEffect(() => {
|
||||
props.onChange(selectedOptions);
|
||||
|
@ -66,12 +69,12 @@ export const CheckedSelect = React.forwardRef((props: CheckedSelectProps, ref: F
|
|||
}),
|
||||
}}
|
||||
name={props.name}
|
||||
placeholder={props.placeholder || "Select..."}
|
||||
placeholder={props.placeholder || t("select")}
|
||||
isSearchable={false}
|
||||
formatOptionLabel={formatOptionLabel}
|
||||
options={options}
|
||||
isMulti
|
||||
value={props.placeholder || "Select..."}
|
||||
value={props.placeholder || t("select")}
|
||||
onChange={changeHandler}
|
||||
/>
|
||||
{selectedOptions.map((option) => (
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/r
|
|||
import React from "react";
|
||||
|
||||
import classNames from "@lib/classNames";
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
|
||||
import { RadioArea, RadioAreaGroup } from "@components/ui/form/radio-area/RadioAreaGroup";
|
||||
|
||||
|
@ -15,10 +16,11 @@ type RadioAreaSelectProps = React.SelectHTMLAttributes<HTMLSelectElement> & {
|
|||
};
|
||||
|
||||
export const Select = function RadioAreaSelect(props: RadioAreaSelectProps) {
|
||||
const { t } = useLocale();
|
||||
const {
|
||||
options,
|
||||
disabled = !options.length, // if not explicitly disabled and the options length is empty, disable anyway
|
||||
placeholder = "Select...",
|
||||
placeholder = t("select"),
|
||||
} = props;
|
||||
|
||||
const getLabel = (value: string | ReadonlyArray<string> | number) =>
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { ClockIcon } from "@heroicons/react/outline";
|
||||
import { useRef } from "react";
|
||||
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
|
||||
export default function SetTimesModal(props) {
|
||||
const { t } = useLocale();
|
||||
const [startHours, startMinutes] = [Math.floor(props.startTime / 60), props.startTime % 60];
|
||||
const [endHours, endMinutes] = [Math.floor(props.endTime / 60), props.endTime % 60];
|
||||
|
||||
|
@ -48,18 +51,18 @@ export default function SetTimesModal(props) {
|
|||
</div>
|
||||
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900" id="modal-title">
|
||||
Change when you are available for bookings
|
||||
{t("change_bookings_availability")}
|
||||
</h3>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Set your work schedule</p>
|
||||
<p className="text-sm text-gray-500">{t("set_work_schedule")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex mb-4">
|
||||
<label className="w-1/4 pt-2 block text-sm font-medium text-gray-700">Start time</label>
|
||||
<label className="w-1/4 pt-2 block text-sm font-medium text-gray-700">{t("start_time")}</label>
|
||||
<div>
|
||||
<label htmlFor="startHours" className="sr-only">
|
||||
Hours
|
||||
{t("hours")}
|
||||
</label>
|
||||
<input
|
||||
ref={startHoursRef}
|
||||
|
@ -77,7 +80,7 @@ export default function SetTimesModal(props) {
|
|||
<span className="mx-2 pt-1">:</span>
|
||||
<div>
|
||||
<label htmlFor="startMinutes" className="sr-only">
|
||||
Minutes
|
||||
{t("minutes")}
|
||||
</label>
|
||||
<input
|
||||
ref={startMinsRef}
|
||||
|
@ -95,10 +98,10 @@ export default function SetTimesModal(props) {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<label className="w-1/4 pt-2 block text-sm font-medium text-gray-700">End time</label>
|
||||
<label className="w-1/4 pt-2 block text-sm font-medium text-gray-700">{t("end_time")}</label>
|
||||
<div>
|
||||
<label htmlFor="endHours" className="sr-only">
|
||||
Hours
|
||||
{t("hours")}
|
||||
</label>
|
||||
<input
|
||||
ref={endHoursRef}
|
||||
|
@ -116,7 +119,7 @@ export default function SetTimesModal(props) {
|
|||
<span className="mx-2 pt-1">:</span>
|
||||
<div>
|
||||
<label htmlFor="endMinutes" className="sr-only">
|
||||
Minutes
|
||||
{t("minutes")}
|
||||
</label>
|
||||
<input
|
||||
ref={endMinsRef}
|
||||
|
@ -135,10 +138,10 @@ export default function SetTimesModal(props) {
|
|||
</div>
|
||||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<button onClick={updateStartEndTimesHandler} type="submit" className="btn btn-primary">
|
||||
Save
|
||||
{t("save")}
|
||||
</button>
|
||||
<button onClick={props.onExit} type="button" className="btn btn-white mr-2">
|
||||
Cancel
|
||||
{t("cancel")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { ArrowLeftIcon } from "@heroicons/react/solid";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
import showToast from "@lib/notification";
|
||||
import { Webhook } from "@lib/webhook";
|
||||
|
||||
|
@ -8,6 +9,7 @@ import Button from "@components/ui/Button";
|
|||
import Switch from "@components/ui/Switch";
|
||||
|
||||
export default function EditTeam(props: { webhook: Webhook; onCloseEdit: () => void }) {
|
||||
const { t } = useLocale();
|
||||
const [bookingCreated, setBookingCreated] = useState(
|
||||
props.webhook.eventTriggers.includes("booking_created")
|
||||
);
|
||||
|
@ -58,7 +60,7 @@ export default function EditTeam(props: { webhook: Webhook; onCloseEdit: () => v
|
|||
})
|
||||
.then(handleErrors)
|
||||
.then(() => {
|
||||
showToast("Webhook updated successfully!", "success");
|
||||
showToast(t("webhook_updated_successfully"), "success");
|
||||
setBtnLoading(false);
|
||||
});
|
||||
};
|
||||
|
@ -74,12 +76,12 @@ export default function EditTeam(props: { webhook: Webhook; onCloseEdit: () => v
|
|||
loading={btnLoading}
|
||||
StartIcon={ArrowLeftIcon}
|
||||
onClick={() => props.onCloseEdit()}>
|
||||
Back
|
||||
{t("back")}
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
<div className="pb-5 pr-4 sm:pb-6">
|
||||
<h3 className="text-lg font-bold leading-6 text-gray-900">Manage your webhook</h3>
|
||||
<h3 className="text-lg font-bold leading-6 text-gray-900">{t("manage_your_webhook")}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<hr className="mt-2" />
|
||||
|
@ -87,7 +89,7 @@ export default function EditTeam(props: { webhook: Webhook; onCloseEdit: () => v
|
|||
<div className="my-4">
|
||||
<div className="mb-4">
|
||||
<label htmlFor="subUrl" className="block text-sm font-medium text-gray-700">
|
||||
Subscriber Url
|
||||
{t("subscriber_url")}
|
||||
</label>
|
||||
<input
|
||||
ref={subUrlRef}
|
||||
|
@ -99,11 +101,14 @@ export default function EditTeam(props: { webhook: Webhook; onCloseEdit: () => v
|
|||
required
|
||||
className="block w-full px-3 py-2 mt-1 border border-gray-300 rounded-sm shadow-sm focus:outline-none focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm"
|
||||
/>
|
||||
<legend className="block pt-4 mb-2 text-sm font-medium text-gray-700"> Event Triggers </legend>
|
||||
<legend className="block pt-4 mb-2 text-sm font-medium text-gray-700">
|
||||
{" "}
|
||||
{t("event_triggers")}{" "}
|
||||
</legend>
|
||||
<div className="p-2 bg-white border border-gray-300 rounded-sm">
|
||||
<div className="flex p-2">
|
||||
<div className="w-10/12">
|
||||
<h2 className="text-sm text-gray-800">Booking Created</h2>
|
||||
<h2 className="text-sm text-gray-800">{t("booking_created")}</h2>
|
||||
</div>
|
||||
<div className="flex items-center justify-end w-2/12 text-right">
|
||||
<Switch
|
||||
|
@ -116,7 +121,7 @@ export default function EditTeam(props: { webhook: Webhook; onCloseEdit: () => v
|
|||
</div>
|
||||
<div className="flex px-2 py-1">
|
||||
<div className="w-10/12">
|
||||
<h2 className="text-sm text-gray-800">Booking Rescheduled</h2>
|
||||
<h2 className="text-sm text-gray-800">{t("booking_rescheduled")}</h2>
|
||||
</div>
|
||||
<div className="flex items-center justify-end w-2/12 text-right">
|
||||
<Switch
|
||||
|
@ -129,7 +134,7 @@ export default function EditTeam(props: { webhook: Webhook; onCloseEdit: () => v
|
|||
</div>
|
||||
<div className="flex p-2">
|
||||
<div className="w-10/12">
|
||||
<h2 className="text-sm text-gray-800">Booking Cancelled</h2>
|
||||
<h2 className="text-sm text-gray-800">{t("booking_cancelled")}</h2>
|
||||
</div>
|
||||
<div className="flex items-center justify-end w-2/12 text-right">
|
||||
<Switch
|
||||
|
@ -141,11 +146,14 @@ export default function EditTeam(props: { webhook: Webhook; onCloseEdit: () => v
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<legend className="block pt-4 mb-2 text-sm font-medium text-gray-700"> Webhook Status </legend>
|
||||
<legend className="block pt-4 mb-2 text-sm font-medium text-gray-700">
|
||||
{" "}
|
||||
{t("webhook_status")}{" "}
|
||||
</legend>
|
||||
<div className="p-2 bg-white border border-gray-300 rounded-sm">
|
||||
<div className="flex p-2">
|
||||
<div className="w-10/12">
|
||||
<h2 className="text-sm text-gray-800">Webhook Enabled</h2>
|
||||
<h2 className="text-sm text-gray-800">{t("webhook_enabled")}</h2>
|
||||
</div>
|
||||
<div className="flex items-center justify-end w-2/12 text-right">
|
||||
<Switch
|
||||
|
@ -160,7 +168,7 @@ export default function EditTeam(props: { webhook: Webhook; onCloseEdit: () => v
|
|||
</div>
|
||||
<div className="gap-2 mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<Button type="submit" color="primary" className="ml-2" loading={btnLoading}>
|
||||
Save
|
||||
{t("save")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { TrashIcon, PencilAltIcon } from "@heroicons/react/outline";
|
||||
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
import showToast from "@lib/notification";
|
||||
import { Webhook } from "@lib/webhook";
|
||||
|
||||
|
@ -14,6 +15,7 @@ export default function WebhookListItem(props: {
|
|||
webhook: Webhook;
|
||||
onEditWebhook: () => void;
|
||||
}) {
|
||||
const { t } = useLocale();
|
||||
const handleErrors = async (resp: Response) => {
|
||||
if (!resp.ok) {
|
||||
const err = await resp.json();
|
||||
|
@ -31,7 +33,7 @@ export default function WebhookListItem(props: {
|
|||
})
|
||||
.then(handleErrors)
|
||||
.then(() => {
|
||||
showToast("Webhook removed successfully!", "success");
|
||||
showToast(t("webhook_removed_successfully"), "success");
|
||||
props.onChange();
|
||||
});
|
||||
};
|
||||
|
@ -43,7 +45,7 @@ export default function WebhookListItem(props: {
|
|||
<span className="flex flex-col space-y-2 text-xs">
|
||||
{props.webhook.eventTriggers.map((eventTrigger, ind) => (
|
||||
<span key={ind} className="px-1 text-xs text-blue-700 rounded-md w-max bg-blue-50">
|
||||
{eventTrigger}
|
||||
{t(`${eventTrigger}`)}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
|
@ -56,16 +58,16 @@ export default function WebhookListItem(props: {
|
|||
<div className="flex">
|
||||
{!props.webhook.active && (
|
||||
<span className="self-center h-6 px-3 py-1 text-xs text-red-700 capitalize rounded-md bg-red-50">
|
||||
Disabled
|
||||
{t("disabled")}
|
||||
</span>
|
||||
)}
|
||||
{!!props.webhook.active && (
|
||||
<span className="self-center h-6 px-3 py-1 text-xs text-green-700 capitalize rounded-md bg-green-50">
|
||||
Enabled
|
||||
{t("enabled")}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<Tooltip content="Edit Webhook">
|
||||
<Tooltip content={t("edit_webhook")}>
|
||||
<Button
|
||||
onClick={() => props.onEditWebhook()}
|
||||
color="minimal"
|
||||
|
@ -74,7 +76,7 @@ export default function WebhookListItem(props: {
|
|||
className="self-center w-full p-2 ml-4"></Button>
|
||||
</Tooltip>
|
||||
<Dialog>
|
||||
<Tooltip content="Delete Webhook">
|
||||
<Tooltip content={t("delete_webhook")}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
|
@ -88,14 +90,13 @@ export default function WebhookListItem(props: {
|
|||
</Tooltip>
|
||||
<ConfirmationDialogContent
|
||||
variety="danger"
|
||||
title="Delete Webhook"
|
||||
confirmBtnText="Yes, delete webhook"
|
||||
cancelBtnText="Cancel"
|
||||
title={t("delete_webhook")}
|
||||
confirmBtnText={t("confirm_delete_webhook")}
|
||||
cancelBtnText={t("cancel")}
|
||||
onConfirm={() => {
|
||||
deleteWebhook(props.webhook.id);
|
||||
}}>
|
||||
Are you sure you want to delete this webhook? You will no longer receive Cal.com meeting data at
|
||||
a specified URL, in real-time, when an event is scheduled or canceled .
|
||||
{t("delete_webhook_confirmation_message")}
|
||||
</ConfirmationDialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,46 @@
|
|||
{
|
||||
"delete_webhook_confirmation_message": "Are you sure you want to delete this webhook? You will no longer receive Cal.com meeting data at a specified URL, in real-time, when an event is scheduled or canceled.",
|
||||
"confirm_delete_webhook": "Yes, delete webhook",
|
||||
"edit_webhook": "Edit Webhook",
|
||||
"delete_webhook": "Delete Webhook",
|
||||
"webhook_status": "Webhook Status",
|
||||
"webhook_enabled": "Webhook Enabled",
|
||||
"manage_your_webhook": "Manage your webhook",
|
||||
"webhook_updated_successfully": "Webhook updated successfully!",
|
||||
"webhook_removed_successfully": "Webhook removed successfully!",
|
||||
"dismiss": "Dismiss",
|
||||
"add_to_homescreen": "Add this app to your home screen for faster access and improved experience.",
|
||||
"upcoming": "Upcoming",
|
||||
"past": "Past",
|
||||
"choose_a_file": "Choose a file...",
|
||||
"upload_image": "Upload image",
|
||||
"upload_target": "Upload {{target}}",
|
||||
"no_target": "No {{target}}",
|
||||
"slide_zoom_drag_instructions": "Slide to zoom, drag to reposition",
|
||||
"view_notifications": "View notifications",
|
||||
"view_public_page": "View public page",
|
||||
"sign_out": "Sign out",
|
||||
"add_another": "Add another",
|
||||
"until": "until",
|
||||
"powered_by": "powered by",
|
||||
"unavailable": "Unavailable",
|
||||
"set_work_schedule": "Set your work schedule",
|
||||
"change_bookings_availability": "Change when you are available for bookings",
|
||||
"select": "Select...",
|
||||
"2fa_confirm_current_password": "Confirm your current password to get started.",
|
||||
"2fa_scan_image_or_use_code": "Scan the image below with the authenticator app on your phone or manually enter the text code instead.",
|
||||
"text": "Text",
|
||||
"multiline_text": "Multiline Text",
|
||||
"number": "Number",
|
||||
"checkbox": "Checkbox",
|
||||
"is_required": "Is required",
|
||||
"input_type": "Input type",
|
||||
"rejected": "Rejected",
|
||||
"unconfirmed": "Unconfirmed",
|
||||
"guests": "Guests",
|
||||
"web_conferencing_details_to_follow": "Web conferencing details to follow.",
|
||||
"the_username": "The username",
|
||||
"username": "Username",
|
||||
"is_still_available": "is still available.",
|
||||
"documentation": "Documentation",
|
||||
"documentation_description": "Learn how to integrate our tools with your app",
|
||||
|
@ -8,6 +49,7 @@
|
|||
"blog": "Blog",
|
||||
"blog_description": "Read our latest news and articles",
|
||||
"join_our_community": "Join our community",
|
||||
"join_our_slack": "Join our Slack",
|
||||
"claim_username_and_schedule_events": "Claim your username and schedule events",
|
||||
"popular_pages": "Popular pages",
|
||||
"register_now": "Register now",
|
||||
|
@ -31,6 +73,7 @@
|
|||
"incorrect_2fa_code": "Two-factor code is incorrect.",
|
||||
"no_account_exists": "No account exists matching that email address.",
|
||||
"2fa_enabled_instructions": "Two-factor authentication enabled. Please enter the six-digit code from your authenticator app.",
|
||||
"2fa_enter_six_digit_code": "Enter the six-digit code from your authenticator app below.",
|
||||
"create_an_account": "Create an account",
|
||||
"dont_have_an_account": "Don't have an account?",
|
||||
"2fa_code": "Two-Factor Code",
|
||||
|
@ -139,7 +182,7 @@
|
|||
"booking_cancelled": "Booking Cancelled",
|
||||
"booking_rescheduled": "Booking Rescheduled",
|
||||
"booking_created": "Booking Created",
|
||||
"event_triggers": "Event triggers",
|
||||
"event_triggers": "Event Triggers",
|
||||
"subscriber_url": "Subscriber Url",
|
||||
"create_new_webhook": "Create a new webhook",
|
||||
"create_new_webhook_to_account": "Create a new webhook to your account",
|
||||
|
|
Loading…
Reference in a new issue