Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into improve-multilingua…
Browse files Browse the repository at this point in the history
…lism

# Conflicts:
#	apps/web/public/static/locales/en/common.json
  • Loading branch information
maxi committed Nov 3, 2022
2 parents 8fe2972 + 4c24614 commit 58e2c98
Show file tree
Hide file tree
Showing 22 changed files with 390 additions and 148 deletions.
179 changes: 101 additions & 78 deletions apps/web/components/v2/eventtype/AvailabilityTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { weekdayNames } from "@calcom/lib/weekday";
import { trpc } from "@calcom/trpc/react";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import { Icon } from "@calcom/ui";
import { Badge } from "@calcom/ui/v2";
import { Badge, SettingsToggle } from "@calcom/ui/v2";
import Button from "@calcom/ui/v2/core/Button";
import Select from "@calcom/ui/v2/core/form/select";
import { SkeletonText } from "@calcom/ui/v2/core/skeleton";
Expand Down Expand Up @@ -99,91 +99,114 @@ const format = (date: Date, hour12: boolean) =>
new Date(dayjs.utc(date).format("YYYY-MM-DDTHH:mm:ss"))
);

export const AvailabilityTab = () => {
export const AvailabilityTab = ({ isTeamEvent }: { isTeamEvent: boolean }) => {
const { t, i18n } = useLocale();
const { watch } = useFormContext<FormValues>();
const me = useMeQuery();
const timeFormat = me?.data?.timeFormat;

const EventTypeSchedule = () => {
const me = useMeQuery();
const timeFormat = me?.data?.timeFormat;
return (
<div className="space-y-4">
<div>
<div className="min-w-4 mb-2">
<label htmlFor="availability" className="mt-0 flex text-sm font-medium text-neutral-700">
{t("availability")}
</label>
</div>
<Controller
name="schedule"
render={({ field }) => (
<AvailabilitySelect
value={field.value}
onBlur={field.onBlur}
name={field.name}
onChange={(selected) => {
field.onChange(selected?.value || null);
}}
/>
)}
/>
</div>
<div className="space-y-4 rounded border p-4 py-6 pt-2 md:p-8">
<ol className="table border-collapse text-sm">
{weekdayNames(i18n.language, 1, "long").map((day, index) => {
const isAvailable = !!filterDays(index).length;
return (
<li key={day} className="my-6 flex border-transparent last:mb-2">
<span
className={classNames(
"w-20 font-medium sm:w-32",
!isAvailable && "text-gray-500 opacity-50"
)}>
{day}
</span>
{isLoading ? (
<SkeletonText className="block h-5 w-60" />
) : isAvailable ? (
<div className="space-y-3 text-right">
{filterDays(index).map((dayRange, i) => (
<div key={i} className="flex items-center leading-4">
<span className="w-16 sm:w-28 sm:text-left">
{format(dayRange.startTime, timeFormat === 12)}
</span>
<span className="ml-4">-</span>
<div className="ml-6">{format(dayRange.endTime, timeFormat === 12)}</div>
</div>
))}
</div>
) : (
<span className="ml-6 text-gray-500 opacity-50 sm:ml-0">{t("unavailable")}</span>
)}
</li>
);
})}
</ol>
<hr />
<div className="flex flex-col justify-center gap-2 sm:flex-row sm:justify-between">
<span className="flex items-center justify-center text-sm text-gray-600 sm:justify-start">
<Icon.FiGlobe className="mr-2" />
{schedule?.timeZone || <SkeletonText className="block h-5 w-32" />}
</span>
<Button
href={`/availability/${schedule?.schedule.id}`}
color="minimal"
EndIcon={Icon.FiExternalLink}
target="_blank"
className="justify-center border sm:border-0"
rel="noopener noreferrer">
{t("edit_availability")}
</Button>
</div>
</div>
</div>
);
};

const scheduleId = watch("schedule");
const { isLoading, data: schedule } = trpc.useQuery(["viewer.availability.schedule", { scheduleId }]);

const filterDays = (dayNum: number) =>
schedule?.schedule.availability.filter((item) => item.days.includes((dayNum + 1) % 7)) || [];

return (
<>
<div>
<div className="min-w-4 mb-2">
<label htmlFor="availability" className="mt-0 flex text-sm font-medium text-neutral-700">
{t("availability")}
</label>
</div>
<Controller
name="schedule"
render={({ field }) => (
<AvailabilitySelect
value={field.value}
onBlur={field.onBlur}
name={field.name}
onChange={(selected) => {
field.onChange(selected?.value || null);
}}
/>
)}
/>
</div>
if (!isTeamEvent) {
return <EventTypeSchedule />;
}

<div className="space-y-4 rounded border p-4 py-6 pt-2 md:p-8">
<ol className="table border-collapse text-sm">
{weekdayNames(i18n.language, 1, "long").map((day, index) => {
const isAvailable = !!filterDays(index).length;
return (
<li key={day} className="my-6 flex border-transparent last:mb-2">
<span
className={classNames(
"w-20 font-medium sm:w-32",
!isAvailable && "text-gray-500 opacity-50"
)}>
{day}
</span>
{isLoading ? (
<SkeletonText className="block h-5 w-60" />
) : isAvailable ? (
<div className="space-y-3 text-right">
{filterDays(index).map((dayRange, i) => (
<div key={i} className="flex items-center leading-4">
<span className="w-16 sm:w-28 sm:text-left">
{format(dayRange.startTime, timeFormat === 12)}
</span>
<span className="ml-4 sm:ml-0">-</span>
<div className="ml-6">{format(dayRange.endTime, timeFormat === 12)}</div>
</div>
))}
</div>
) : (
<span className="ml-6 text-gray-500 opacity-50 sm:ml-0">{t("unavailable")}</span>
)}
</li>
);
})}
</ol>
<hr />
<div className="flex flex-col justify-center gap-2 sm:flex-row sm:justify-between">
<span className="flex items-center justify-center text-sm text-gray-600 sm:justify-start">
<Icon.FiGlobe className="mr-2" />
{schedule?.timeZone || <SkeletonText className="block h-5 w-32" />}
</span>
<Button
href={`/availability/${schedule?.schedule.id}`}
color="minimal"
EndIcon={Icon.FiExternalLink}
target="_blank"
className="justify-center border sm:border-0"
rel="noopener noreferrer">
{t("edit_availability")}
</Button>
</div>
</div>
</>
return (
<Controller
name="metadata.config.useHostSchedulesForTeamEvent"
render={({ field: { value, onChange } }) => (
<SettingsToggle
checked={!value}
onCheckedChange={(checked) => {
onChange(!checked);
}}
title={t("choose_common_schedule_team_event")}
description={t("choose_common_schedule_team_event_description")}>
<EventTypeSchedule />
</SettingsToggle>
)}
/>
);
};
4 changes: 4 additions & 0 deletions apps/web/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ const middleware: NextMiddleware = async (req) => {
return NextResponse.next();
};

export const config = {
matcher: ["/api/collect-events/:path*", "/api/auth/:path*", "/apps/routing_forms/:path*", "/:path*/embed"],
};

export default collectEvents({
middleware,
...nextCollectBasicSettings,
Expand Down
5 changes: 4 additions & 1 deletion apps/web/pages/apps/[slug]/[...pages].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { AppGetServerSideProps } from "@calcom/types/AppGetServerSideProps";
import { AppProps } from "@lib/app-providers";
import { getSession } from "@lib/auth";

import { ssrInit } from "@server/lib/ssr";

type AppPageType = {
getServerSideProps: AppGetServerSideProps;
// A component than can accept any properties
Expand Down Expand Up @@ -128,7 +130,8 @@ export async function getServerSideProps(
appPages: string[];
}>,
prisma,
user
user,
ssrInit
);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
Expand Down
2 changes: 1 addition & 1 deletion apps/web/pages/event-types/[type]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
teamMembers={teamMembers}
/>
),
availability: <AvailabilityTab />,
availability: <AvailabilityTab isTeamEvent={!!team} />,
team: (
<EventTeamTab
eventType={eventType}
Expand Down
8 changes: 6 additions & 2 deletions apps/web/public/static/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -1225,6 +1225,8 @@
"exchange_authentication_ntlm": "NTLM authentication",
"exchange_compression": "GZip compression",
"routing_forms_description": "You can see all forms and routes you have created here.",
"routing_forms_send_email_owner": "Send Email to Owner",
"routing_forms_send_email_owner_description": "Sends an email to the owner when the form is submitted",
"add_new_form": "Add new form",
"form_description": "Create your form to route a booker",
"copy_link_to_form": "Copy link to form",
Expand Down Expand Up @@ -1339,8 +1341,10 @@
"number_sms_notifications": "Phone number (SMS\u00a0notifications)",
"attendee_email_workflow": "Attendee email",
"attendee_email_info": "The person booking's email",
"invalid_credential": "Oh no! Looks like permission expired or was revoked. Please reinstall again.",
"kbar_search_placeholder": "Type a command or search...",
"free_to_use_apps": "Free",
"enterprise_license": "This is an enterprise feature"
"enterprise_license": "This is an enterprise feature",
"invalid_credential": "Oh no! Looks like permission expired or was revoked. Please reinstall again.",
"choose_common_schedule_team_event": "Choose a common schedule",
"choose_common_schedule_team_event_description": "Enable this if you want to use a common schedule between hosts. When disabled, each host will be booked based on their default schedule."
}
21 changes: 20 additions & 1 deletion packages/app-store/ee/routing-forms/components/SingleForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { App_RoutingForms_Form } from "@prisma/client";
import { useEffect } from "react";
import { useForm, UseFormReturn } from "react-hook-form";
import { useForm, UseFormReturn, Controller } from "react-hook-form";

import useApp from "@calcom/lib/hooks/useApp";
import { useLocale } from "@calcom/lib/hooks/useLocale";
Expand All @@ -10,6 +10,7 @@ import { Form } from "@calcom/ui/form/fields";
import { showToast, DropdownMenuSeparator } from "@calcom/ui/v2";
import { ButtonGroup, TextAreaField, TextField, Tooltip, Button, VerticalDivider } from "@calcom/ui/v2";
import Meta from "@calcom/ui/v2/core/Meta";
import SettingsToggle from "@calcom/ui/v2/core/SettingsToggle";
import { ShellMain } from "@calcom/ui/v2/core/Shell";
import Banner from "@calcom/ui/v2/core/banner";

Expand Down Expand Up @@ -187,6 +188,7 @@ type SingleFormComponentProps = {

function SingleForm({ form, appUrl, Page }: SingleFormComponentProps) {
const utils = trpc.useContext();
const { t } = useLocale();

const hookForm = useForm({
defaultValues: form,
Expand Down Expand Up @@ -241,6 +243,23 @@ function SingleForm({ form, appUrl, Page }: SingleFormComponentProps) {
{...hookForm.register("description")}
defaultValue={form.description || ""}
/>

<div className="mt-6">
<Controller
name="settings.emailOwnerOnSubmission"
control={hookForm.control}
render={({ field: { value, onChange } }) => {
return (
<SettingsToggle
title={t("routing_forms_send_email_owner")}
description={t("routing_forms_send_email_owner_description")}
checked={value}
onCheckedChange={(val) => onChange(val)}
/>
);
}}
/>
</div>
{!form._count?.responses && (
<Banner
className="mt-6"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { BaseEmailHtml, Info } from "@calcom/emails/src/components";
import { WEBAPP_URL } from "@calcom/lib/constants";

import { Response } from "../../types/types";
import { App_RoutingForms_Form } from ".prisma/client";

export const ResponseEmail = ({
form,
response,
...props
}: {
form: Pick<App_RoutingForms_Form, "id" | "name">;
response: Response;
subject: string;
} & Partial<React.ComponentProps<typeof BaseEmailHtml>>) => {
return (
<BaseEmailHtml
callToAction={
<div
style={{
fontFamily: "Roboto, Helvetica, sans-serif",
fontSize: "16px",
fontWeight: 500,
lineHeight: "0px",
textAlign: "left",
color: "#3e3e3e",
}}>
<p style={{ fontWeight: 400, lineHeight: "24px" }}>
<a href={`${WEBAPP_URL}/apps/routing-forms/form-edit/${form.id}`} style={{ color: "#3e3e3e" }}>
<>Manage this form</>
</a>
</p>
</div>
}
title={form.name}
subtitle="New Response Received"
{...props}>
{Object.entries(response).map(([fieldId, fieldResponse]) => {
return (
<Info
withSpacer
key={fieldId}
label={fieldResponse.label}
description={
fieldResponse.value instanceof Array ? fieldResponse.value.join(",") : fieldResponse.value
}
/>
);
})}
</BaseEmailHtml>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ResponseEmail } from "./ResponseEmail";
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { renderEmail } from "@calcom/emails";
import BaseEmail from "@calcom/emails/templates/_base-email";

import { Response } from "../../types/types";
import { App_RoutingForms_Form } from ".prisma/client";

type Form = Pick<App_RoutingForms_Form, "id" | "name">;
export default class ResponseEmail extends BaseEmail {
response: Response;
toAddresses: string[];
form: Form;
constructor({ toAddresses, response, form }: { form: Form; toAddresses: string[]; response: Response }) {
super();
this.form = form;
this.response = response;
this.toAddresses = toAddresses;
}

protected getNodeMailerPayload(): Record<string, unknown> {
const toAddresses = this.toAddresses;
const subject = `${this.form.name} has a new response`;
return {
from: `Cal.com <${this.getMailerOptions().from}>`,
to: toAddresses.join(","),
subject,
html: renderEmail("ResponseEmail", {
form: this.form,
response: this.response,
subject,
}),
};
}
}
Loading

0 comments on commit 58e2c98

Please sign in to comment.