From 5fdc5078cc93ed398b54c078eae0f26be314d0f6 Mon Sep 17 00:00:00 2001 From: Alex van Andel Date: Thu, 14 Apr 2022 15:58:23 +0100 Subject: [PATCH] Styling tweaks to inputs and Select (+ TimezoneSelect) (#2453) Co-authored-by: Peer Richelsen Co-authored-by: Bailey Pumfleet --- .../DestinationCalendarSelector.tsx | 2 +- .../availability/NewScheduleButton.tsx | 14 +- .../pages/eventtypes/CustomInputTypeForm.tsx | 9 +- .../security/ChangePasswordSection.tsx | 4 +- .../security/DisableTwoFactorModal.tsx | 2 +- .../security/EnableTwoFactorModal.tsx | 4 +- .../components/team/MemberChangeRoleModal.tsx | 38 ++- .../components/team/MemberInvitationModal.tsx | 31 ++- apps/web/components/team/TeamCreateModal.tsx | 2 +- apps/web/components/team/TeamSettings.tsx | 6 +- apps/web/components/ui/Scheduler.tsx | 156 ------------ apps/web/components/ui/colorpicker.tsx | 2 +- .../components/ui/form/DateRangePicker.tsx | 2 +- apps/web/components/ui/form/MinutesField.tsx | 2 +- apps/web/components/ui/form/Select.tsx | 37 ++- .../web/components/ui/form/TimezoneSelect.tsx | 43 ++++ .../web/components/ui/modal/SetTimesModal.tsx | 222 ------------------ .../availability/TeamAvailabilityModal.tsx | 6 +- .../availability/TeamAvailabilityScreen.tsx | 5 +- apps/web/pages/apps/installed.tsx | 4 +- apps/web/pages/availability/[schedule].tsx | 4 +- apps/web/pages/event-types/[type].tsx | 141 +++++------ apps/web/pages/settings/profile.tsx | 33 ++- apps/web/styles/globals.css | 57 +++-- 24 files changed, 265 insertions(+), 561 deletions(-) delete mode 100644 apps/web/components/ui/Scheduler.tsx create mode 100644 apps/web/components/ui/form/TimezoneSelect.tsx delete mode 100644 apps/web/components/ui/modal/SetTimesModal.tsx diff --git a/apps/web/components/DestinationCalendarSelector.tsx b/apps/web/components/DestinationCalendarSelector.tsx index 7f6933bd..8d14a0c8 100644 --- a/apps/web/components/DestinationCalendarSelector.tsx +++ b/apps/web/components/DestinationCalendarSelector.tsx @@ -68,7 +68,7 @@ const DestinationCalendarSelector = ({ placeholder={!hidePlaceholder ? `${t("select_destination_calendar")}:` : undefined} options={options} isSearchable={false} - className="focus:ring-primary-500 focus:border-primary-500 mt-1 mb-2 block w-full min-w-0 flex-1 rounded-none rounded-r-md border-gray-300 sm:text-sm" + className="mt-1 mb-2 block w-full min-w-0 flex-1 rounded-none rounded-r-md border-gray-300 sm:text-sm" onChange={(option) => { setSelectedOption(option); if (!option) { diff --git a/apps/web/components/availability/NewScheduleButton.tsx b/apps/web/components/availability/NewScheduleButton.tsx index fceef26e..8dc84a9e 100644 --- a/apps/web/components/availability/NewScheduleButton.tsx +++ b/apps/web/components/availability/NewScheduleButton.tsx @@ -60,7 +60,19 @@ export function NewScheduleButton({ name = "new-schedule" }: { name?: string }) createMutation.mutate(values); }}>
- + +
+ +
@@ -91,7 +91,7 @@ const ChangePasswordSection = () => { value={newPassword} required onInput={(e) => setNewPassword(e.currentTarget.value)} - className="focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black sm:text-sm" + className="block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm" placeholder={t("super_secure_new_password")} /> diff --git a/apps/web/components/security/DisableTwoFactorModal.tsx b/apps/web/components/security/DisableTwoFactorModal.tsx index d86f8df3..12bc6f83 100644 --- a/apps/web/components/security/DisableTwoFactorModal.tsx +++ b/apps/web/components/security/DisableTwoFactorModal.tsx @@ -70,7 +70,7 @@ const DisableTwoFactorAuthModal = ({ onDisable, onCancel }: DisableTwoFactorAuth required value={password} onInput={(e) => setPassword(e.currentTarget.value)} - className="block w-full rounded-sm border-gray-300 shadow-sm focus:border-neutral-900 focus:ring-neutral-900 sm:text-sm" + className="block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm" /> diff --git a/apps/web/components/security/EnableTwoFactorModal.tsx b/apps/web/components/security/EnableTwoFactorModal.tsx index 280f3600..4c613123 100644 --- a/apps/web/components/security/EnableTwoFactorModal.tsx +++ b/apps/web/components/security/EnableTwoFactorModal.tsx @@ -139,7 +139,7 @@ const EnableTwoFactorModal = ({ onEnable, onCancel }: EnableTwoFactorModalProps) required value={password} onInput={(e) => setPassword(e.currentTarget.value)} - className="block w-full rounded-sm border-gray-300 shadow-sm focus:border-neutral-900 focus:ring-neutral-900 sm:text-sm" + className="block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm" /> @@ -172,7 +172,7 @@ const EnableTwoFactorModal = ({ onEnable, onCancel }: EnableTwoFactorModalProps) minLength={6} inputMode="numeric" onInput={(e) => setTotpCode(e.currentTarget.value)} - className="block w-full rounded-sm border-gray-300 shadow-sm focus:border-neutral-900 focus:ring-neutral-900 sm:text-sm" + className="block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm" /> diff --git a/apps/web/components/team/MemberChangeRoleModal.tsx b/apps/web/components/team/MemberChangeRoleModal.tsx index 48eda0a9..e3917899 100644 --- a/apps/web/components/team/MemberChangeRoleModal.tsx +++ b/apps/web/components/team/MemberChangeRoleModal.tsx @@ -1,6 +1,6 @@ import { MembershipRole } from "@prisma/client"; import { useState } from "react"; -import React, { SyntheticEvent } from "react"; +import React, { SyntheticEvent, useEffect } from "react"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import Button from "@calcom/ui/Button"; @@ -8,6 +8,14 @@ import Button from "@calcom/ui/Button"; import { trpc } from "@lib/trpc"; import ModalContainer from "@components/ui/ModalContainer"; +import Select from "@components/ui/form/Select"; + +type MembershipRoleOption = { + value: MembershipRole; + label?: string; +}; + +const options: MembershipRoleOption[] = [{ value: "MEMBER" }, { value: "ADMIN" }]; export default function MemberChangeRoleModal(props: { isOpen: boolean; @@ -16,7 +24,16 @@ export default function MemberChangeRoleModal(props: { initialRole: MembershipRole; onExit: () => void; }) { - const [role, setRole] = useState(props.initialRole || MembershipRole.MEMBER); + useEffect(() => { + options.forEach((option, i) => { + options[i].label = t(option.value.toLowerCase()); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const [role, setRole] = useState( + options.find((option) => option.value === props.initialRole || MembershipRole.MEMBER)! + ); const [errorMessage, setErrorMessage] = useState(""); const { t } = useLocale(); const utils = trpc.useContext(); @@ -37,7 +54,7 @@ export default function MemberChangeRoleModal(props: { changeRoleMutation.mutate({ teamId: props.teamId, memberId: props.memberId, - role, + role: role.value, }); } @@ -56,17 +73,16 @@ export default function MemberChangeRoleModal(props: { - setRole(e.target.value as MembershipRole)} + onChange={(option) => option && setRole(option)} id="role" - className="focus:border-brand mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:ring-black sm:text-sm"> - - - {/* - needs dialog to confirm change of ownership */} - + className="mt-1 block w-full rounded-md border-gray-300 shadow-sm sm:text-sm" + /> - {errorMessage && (

Error: diff --git a/apps/web/components/team/MemberInvitationModal.tsx b/apps/web/components/team/MemberInvitationModal.tsx index 86303d51..7a67de33 100644 --- a/apps/web/components/team/MemberInvitationModal.tsx +++ b/apps/web/components/team/MemberInvitationModal.tsx @@ -1,8 +1,7 @@ import { UserIcon } from "@heroicons/react/outline"; import { InformationCircleIcon } from "@heroicons/react/solid"; import { MembershipRole } from "@prisma/client"; -import { useState } from "react"; -import React, { SyntheticEvent } from "react"; +import React, { useState, useEffect, SyntheticEvent } from "react"; import Button from "@calcom/ui/Button"; import { Dialog, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui/Dialog"; @@ -12,17 +11,33 @@ import { useLocale } from "@lib/hooks/useLocale"; import { TeamWithMembers } from "@lib/queries/teams"; import { trpc } from "@lib/trpc"; +import Select from "@components/ui/form/Select"; + type MemberInvitationModalProps = { isOpen: boolean; team: TeamWithMembers | null; onExit: () => void; }; +type MembershipRoleOption = { + value: MembershipRole; + label?: string; +}; + +const options: MembershipRoleOption[] = [{ value: "MEMBER" }, { value: "ADMIN" }]; + export default function MemberInvitationModal(props: MemberInvitationModalProps) { const [errorMessage, setErrorMessage] = useState(""); const { t, i18n } = useLocale(); const utils = trpc.useContext(); + useEffect(() => { + options.forEach((option, i) => { + options[i].label = t(option.value.toLowerCase()); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const inviteMemberMutation = trpc.useMutation("viewer.teams.inviteMember", { async onSuccess() { await utils.invalidateQueries(["viewer.teams.get"]); @@ -83,12 +98,12 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps) - + name="role" + className="mt-1 block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm" + />

@@ -97,7 +112,7 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps) name="sendInviteEmail" defaultChecked id="sendInviteEmail" - className="focus:border-brand rounded-sm border-gray-300 text-black shadow-sm focus:ring-black sm:text-sm" + className="rounded-sm border-gray-300 text-black shadow-sm sm:text-sm" />
diff --git a/apps/web/components/team/TeamCreateModal.tsx b/apps/web/components/team/TeamCreateModal.tsx index 66a1dfa7..7afb9911 100644 --- a/apps/web/components/team/TeamCreateModal.tsx +++ b/apps/web/components/team/TeamCreateModal.tsx @@ -63,7 +63,7 @@ export default function TeamCreate(props: Props) { id="name" placeholder="Acme Inc." required - className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 shadow-sm focus:border-neutral-500 focus:outline-none focus:ring-neutral-500 sm:text-sm" + className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 shadow-sm sm:text-sm" />
{errorMessage && } diff --git a/apps/web/components/team/TeamSettings.tsx b/apps/web/components/team/TeamSettings.tsx index 1d46392c..17829892 100644 --- a/apps/web/components/team/TeamSettings.tsx +++ b/apps/web/components/team/TeamSettings.tsx @@ -112,7 +112,7 @@ export default function TeamSettings(props: Props) { id="name" placeholder={t("your_team_name")} required - className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 shadow-sm focus:border-neutral-800 focus:outline-none focus:ring-neutral-800 sm:text-sm" + className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 shadow-sm sm:text-sm" defaultValue={team?.name as string} /> } @@ -131,7 +131,7 @@ export default function TeamSettings(props: Props) { name="about" rows={3} defaultValue={team?.bio as string} - className="mt-1 block w-full rounded-sm border-gray-300 shadow-sm focus:border-neutral-800 focus:ring-neutral-800 sm:text-sm"> + className="mt-1 block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm">

{t("team_description")}

} @@ -151,7 +151,7 @@ export default function TeamSettings(props: Props) { name="avatar" id="avatar" placeholder="URL" - className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 shadow-sm focus:border-neutral-800 focus:outline-none focus:ring-neutral-800 sm:text-sm" + className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 shadow-sm sm:text-sm" defaultValue={team?.logo ?? undefined} /> ; - -type Props = { - timeZone: string; - availability: Availability[]; - setTimeZone: (timeZone: string) => void; - setAvailability: (schedule: { - openingHours: AvailabilityInput[]; - dateOverrides: AvailabilityInput[]; - }) => void; -}; - -/** - * @deprecated - */ -export const Scheduler = ({ availability, setAvailability, timeZone, setTimeZone }: Props) => { - const { t, i18n } = useLocale(); - const [editSchedule, setEditSchedule] = useState(-1); - const [openingHours, setOpeningHours] = useState([]); - - useEffect(() => { - setOpeningHours(availability.filter((item: Availability) => item.days.length !== 0)); - }, []); - - useEffect(() => { - setAvailability({ openingHours, dateOverrides: [] }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [openingHours]); - - const addNewSchedule = () => setEditSchedule(openingHours.length); - - const applyEditSchedule = (changed: Availability) => { - // new entry - if (!changed.days) { - changed.days = [1, 2, 3, 4, 5]; // Mon - Fri - setOpeningHours(openingHours.concat(changed)); - } else { - // update - const replaceWith = { ...openingHours[editSchedule], ...changed }; - openingHours.splice(editSchedule, 1, replaceWith); - setOpeningHours([...openingHours]); - } - }; - - const removeScheduleAt = (toRemove: number) => { - openingHours.splice(toRemove, 1); - setOpeningHours([...openingHours]); - }; - - const OpeningHours = ({ idx, item }: { idx: number; item: Availability }) => ( -
  • -
    - (item.days = selected)} /> - -
    - -
  • - ); - - return ( -
    -
    -
    -
    - -
    - setTimeZone(tz.value)} - className="focus:border-brand mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:ring-black sm:text-sm" - /> -
    -
    -
      - {openingHours.map((item, idx) => ( - - ))} -
    - -
    -
    - {editSchedule >= 0 && ( - - applyEditSchedule({ - ...(openingHours[editSchedule] || {}), - startTime: new Date( - new Date().setUTCHours(Math.floor(times.startTime / 60), times.startTime % 60, 0, 0) - ), - endTime: new Date( - new Date().setUTCHours(Math.floor(times.endTime / 60), times.endTime % 60, 0, 0) - ), - }) - } - onExit={() => setEditSchedule(-1)} - /> - )} -
    - ); -}; diff --git a/apps/web/components/ui/colorpicker.tsx b/apps/web/components/ui/colorpicker.tsx index 28bcb694..62e3c3cd 100644 --- a/apps/web/components/ui/colorpicker.tsx +++ b/apps/web/components/ui/colorpicker.tsx @@ -87,7 +87,7 @@ const ColorPicker = (props: ColorPickerProps) => {
    )} { setColor(val); diff --git a/apps/web/components/ui/form/DateRangePicker.tsx b/apps/web/components/ui/form/DateRangePicker.tsx index 53b5df60..6e7ac083 100644 --- a/apps/web/components/ui/form/DateRangePicker.tsx +++ b/apps/web/components/ui/form/DateRangePicker.tsx @@ -14,7 +14,7 @@ type Props = { export const DateRangePicker = ({ startDate, endDate, onDatesChange }: Props) => { return ( } rangeDivider={} diff --git a/apps/web/components/ui/form/MinutesField.tsx b/apps/web/components/ui/form/MinutesField.tsx index 5a691211..7a0387bc 100644 --- a/apps/web/components/ui/form/MinutesField.tsx +++ b/apps/web/components/ui/form/MinutesField.tsx @@ -22,7 +22,7 @@ const MinutesField = forwardRef(({ label, ...rest }, re ref={ref} type="number" className={classNames( - "focus:border-primary-500 focus:ring-primary-500 block w-full rounded-sm border-gray-300 pl-2 pr-12 sm:text-sm", + "block w-full rounded-sm border-gray-300 pl-2 pr-12 sm:text-sm", rest.className )} /> diff --git a/apps/web/components/ui/form/Select.tsx b/apps/web/components/ui/form/Select.tsx index 48f78483..f3e32443 100644 --- a/apps/web/components/ui/form/Select.tsx +++ b/apps/web/components/ui/form/Select.tsx @@ -1,13 +1,32 @@ import React from "react"; -import ReactSelect, { components, GroupBase, Props } from "react-select"; +import ReactSelect, { components, GroupBase, Props, InputProps } from "react-select"; import classNames from "@lib/classNames"; +export type SelectProps< + Option, + IsMulti extends boolean = false, + Group extends GroupBase