Merge branch 'main' into testE2E-timezone
This commit is contained in:
commit
9a711cb7e9
13
README.md
13
README.md
|
@ -253,6 +253,8 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
|
|||
|
||||
#### Setting up your first user
|
||||
|
||||
##### Approach 1
|
||||
|
||||
1. Open [Prisma Studio](https://prisma.io/studio) to look at or modify the database content:
|
||||
|
||||
```sh
|
||||
|
@ -264,6 +266,17 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
|
|||
> New users are set on a `TRIAL` plan by default. You might want to adjust this behavior to your needs in the `packages/prisma/schema.prisma` file.
|
||||
1. Open a browser to [http://localhost:3000](http://localhost:3000) and login with your just created, first user.
|
||||
|
||||
##### Approach 2
|
||||
|
||||
Seed the local db by running
|
||||
|
||||
```sh
|
||||
cd packages/prisma
|
||||
yarn db-seed
|
||||
```
|
||||
|
||||
The above command will populate the local db with dummy users.
|
||||
|
||||
### E2E-Testing
|
||||
|
||||
Be sure to set the environment variable `NEXTAUTH_URL` to the correct value. If you are running locally, as the documentation within `.env.example` mentions, the value should be `http://localhost:3000`.
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
|||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import { createContext } from "@calcom/trpc/server/createContext";
|
||||
import { viewerRouter } from "@calcom/trpc/server/routers/viewer/_router";
|
||||
import { slotsRouter } from "@calcom/trpc/server/routers/viewer/slots/_router";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { getHTTPStatusCodeFromError } from "@trpc/server/http";
|
||||
|
@ -11,10 +11,10 @@ import { getHTTPStatusCodeFromError } from "@trpc/server/http";
|
|||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
/** @see https://trpc.io/docs/server-side-calls */
|
||||
const ctx = await createContext({ req, res });
|
||||
const caller = viewerRouter.createCaller(ctx);
|
||||
const caller = slotsRouter.createCaller(ctx);
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return await caller.slots.getSchedule(req.query as any /* Let tRPC handle this */);
|
||||
return await caller.getSchedule(req.query as any /* Let tRPC handle this */);
|
||||
} catch (cause) {
|
||||
if (cause instanceof TRPCError) {
|
||||
const statusCode = getHTTPStatusCodeFromError(cause);
|
||||
|
|
|
@ -15,7 +15,7 @@ interface Props {
|
|||
|
||||
export default function AuthContainer(props: React.PropsWithChildren<Props>) {
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col justify-center bg-[#f3f4f6] py-12 sm:px-6 lg:px-8">
|
||||
<div className="bg-subtle dark:bg-darkgray-50 flex min-h-screen flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<HeadSeo title={props.title} description={props.description} />
|
||||
{props.showLogo && <Logo small inline={false} className="mx-auto mb-auto" />}
|
||||
|
||||
|
@ -28,7 +28,7 @@ export default function AuthContainer(props: React.PropsWithChildren<Props>) {
|
|||
</div>
|
||||
)}
|
||||
<div className="mb-auto mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="bg-default border-subtle mx-2 rounded-md border px-4 py-10 sm:px-10">
|
||||
<div className="bg-default dark:bg-muted border-subtle mx-2 rounded-md border px-4 py-10 sm:px-10">
|
||||
{props.children}
|
||||
</div>
|
||||
<div className="text-default mt-8 text-center text-sm">{props.footerText}</div>
|
||||
|
|
|
@ -7,7 +7,7 @@ import prisma from "@calcom/prisma";
|
|||
import { UserPermissionRole } from "@calcom/prisma/enums";
|
||||
import { TRPCError } from "@calcom/trpc/server";
|
||||
import { createContext } from "@calcom/trpc/server/createContext";
|
||||
import { viewerRouter } from "@calcom/trpc/server/routers/viewer/_router";
|
||||
import { bookingsRouter } from "@calcom/trpc/server/routers/viewer/bookings/_router";
|
||||
|
||||
enum DirectAction {
|
||||
ACCEPT = "accept",
|
||||
|
@ -55,13 +55,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse<Response>) {
|
|||
try {
|
||||
/** @see https://trpc.io/docs/server-side-calls */
|
||||
const ctx = await createContext({ req, res }, sessionGetter);
|
||||
const caller = viewerRouter.createCaller({
|
||||
const caller = bookingsRouter.createCaller({
|
||||
...ctx,
|
||||
req,
|
||||
res,
|
||||
user: { ...user, locale: user?.locale ?? "en" },
|
||||
});
|
||||
await caller.bookings.confirm({
|
||||
await caller.confirm({
|
||||
bookingId: booking.id,
|
||||
recurringEventId: booking.recurringEventId || undefined,
|
||||
confirmed: action === DirectAction.ACCEPT,
|
||||
|
|
|
@ -141,7 +141,6 @@ export default function Page({ requestId, isRequestExpired, csrfToken }: Props)
|
|||
);
|
||||
}
|
||||
|
||||
Page.isThemeSupported = false;
|
||||
Page.PageWrapper = PageWrapper;
|
||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
const id = context.params?.id as string;
|
||||
|
|
|
@ -126,8 +126,9 @@ export default function ForgotPassword({ csrfToken }: { csrfToken: string }) {
|
|||
/>
|
||||
<div className="space-y-2">
|
||||
<Button
|
||||
className="w-full justify-center"
|
||||
className="w-full justify-center dark:bg-white dark:text-black"
|
||||
type="submit"
|
||||
color="primary"
|
||||
disabled={loading}
|
||||
aria-label={t("request_password_reset")}
|
||||
loading={loading}>
|
||||
|
@ -141,7 +142,6 @@ export default function ForgotPassword({ csrfToken }: { csrfToken: string }) {
|
|||
);
|
||||
}
|
||||
|
||||
ForgotPassword.isThemeSupported = false;
|
||||
ForgotPassword.PageWrapper = PageWrapper;
|
||||
|
||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||
|
|
|
@ -15,11 +15,12 @@ import { SAMLLogin } from "@calcom/features/auth/SAMLLogin";
|
|||
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { isSAMLLoginEnabled, samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml";
|
||||
import { WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants";
|
||||
import { WEBAPP_URL, WEBSITE_URL, HOSTED_CAL_FEATURES } from "@calcom/lib/constants";
|
||||
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Alert, Button, EmailField, PasswordField } from "@calcom/ui";
|
||||
import { ArrowLeft, Lock } from "@calcom/ui/components/icon";
|
||||
|
||||
|
@ -160,6 +161,16 @@ export default function Login({
|
|||
else setErrorMessage(errorMessages[res.error] || t("something_went_wrong"));
|
||||
};
|
||||
|
||||
const { data, isLoading } = trpc.viewer.public.ssoConnections.useQuery(undefined, {
|
||||
onError: (err) => {
|
||||
setErrorMessage(err.message);
|
||||
},
|
||||
});
|
||||
|
||||
const displaySSOLogin = HOSTED_CAL_FEATURES
|
||||
? true
|
||||
: isSAMLLoginEnabled && !isLoading && data?.connectionExists;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={
|
||||
|
@ -225,14 +236,14 @@ export default function Login({
|
|||
type="submit"
|
||||
color="primary"
|
||||
disabled={formState.isSubmitting}
|
||||
className="w-full justify-center">
|
||||
className="w-full justify-center dark:bg-white dark:text-black">
|
||||
{twoFactorRequired ? t("submit") : t("sign_in")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
{!twoFactorRequired && (
|
||||
<>
|
||||
{(isGoogleLoginEnabled || isSAMLLoginEnabled) && <hr className="border-subtle my-8" />}
|
||||
{(isGoogleLoginEnabled || displaySSOLogin) && <hr className="border-subtle my-8" />}
|
||||
<div className="space-y-3">
|
||||
{isGoogleLoginEnabled && (
|
||||
<Button
|
||||
|
@ -247,7 +258,7 @@ export default function Login({
|
|||
{t("signin_with_google")}
|
||||
</Button>
|
||||
)}
|
||||
{isSAMLLoginEnabled && (
|
||||
{displaySSOLogin && (
|
||||
<SAMLLogin
|
||||
samlTenantID={samlTenantID}
|
||||
samlProductID={samlProductID}
|
||||
|
@ -337,7 +348,6 @@ const _getServerSideProps = async function getServerSideProps(context: GetServer
|
|||
};
|
||||
};
|
||||
|
||||
Login.isThemeSupported = false;
|
||||
Login.PageWrapper = PageWrapper;
|
||||
|
||||
export const getServerSideProps = withNonce(_getServerSideProps);
|
||||
|
|
|
@ -57,7 +57,6 @@ export function Logout(props: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
Logout.isThemeSupported = false;
|
||||
Logout.PageWrapper = PageWrapper;
|
||||
export default Logout;
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ test.describe("Availablity tests", () => {
|
|||
await page.locator('[data-testid="day"][data-disabled="false"]').nth(0).click();
|
||||
await page.locator('[data-testid="date-override-mark-unavailable"]').click();
|
||||
await page.locator('[data-testid="add-override-submit-btn"]').click();
|
||||
await page.locator('[data-testid="dialog-rejection"]').click();
|
||||
await expect(page.locator('[data-testid="date-overrides-list"] > li')).toHaveCount(1);
|
||||
await page.locator('[form="availability-form"][type="submit"]').click();
|
||||
});
|
||||
|
|
|
@ -15,7 +15,8 @@ test("Should display SAML Login button", async ({ page }) => {
|
|||
// eslint-disable-next-line playwright/no-skipped-test
|
||||
test.skip(!IS_SAML_LOGIN_ENABLED, "It should only run if SAML Login is installed");
|
||||
|
||||
await page.goto(`/auth/login`);
|
||||
|
||||
await expect(page.locator(`[data-testid=saml]`)).toBeVisible();
|
||||
// TODO: Fix this later
|
||||
// Button is visible only if there is a SAML connection exists (self-hosted)
|
||||
// await page.goto(`/auth/login`);
|
||||
// await expect(page.locator(`[data-testid=saml]`)).toBeVisible();
|
||||
});
|
||||
|
|
|
@ -288,6 +288,7 @@
|
|||
"when": "Wann",
|
||||
"where": "Wo",
|
||||
"add_to_calendar": "Zum Kalender hinzufügen",
|
||||
"add_to_calendar_description": "Legen Sie fest, wo neue Termine hinzugefügt werden sollen, wenn Sie gebucht werden.",
|
||||
"add_another_calendar": "Einen weiteren Kalender hinzufügen",
|
||||
"other": "Sonstige",
|
||||
"email_sign_in_subject": "Ihr Anmelde-Link für {{appName}}",
|
||||
|
@ -599,6 +600,7 @@
|
|||
"hide_book_a_team_member": "„Ein Teammitglied buchen“-Schaltfläche ausblenden",
|
||||
"hide_book_a_team_member_description": "Blendet die „Ein Teammitglied buchen“-Schaltflächen auf Ihren öffentlichen Seiten aus.",
|
||||
"danger_zone": "Achtung",
|
||||
"account_deletion_cannot_be_undone": "Vorsicht. Löschen eines Kontos kann nicht rückgängig gemacht werden.",
|
||||
"back": "Zurück",
|
||||
"cancel": "Absagen",
|
||||
"cancel_all_remaining": "Alle verbleibenden absagen",
|
||||
|
@ -688,6 +690,7 @@
|
|||
"people": "Personen",
|
||||
"your_email": "Ihre E-Mail-Adresse",
|
||||
"change_avatar": "Profilbild ändern",
|
||||
"upload_avatar": "Avatar hochladen",
|
||||
"language": "Sprache",
|
||||
"timezone": "Zeitzone",
|
||||
"first_day_of_week": "Erster Tag der Woche",
|
||||
|
@ -1276,6 +1279,7 @@
|
|||
"personal_cal_url": "Meine persönliche {{appName}}-URL",
|
||||
"bio_hint": "Schreiben Sie eine kurze Beschreibung, welche auf Ihrer persönlichen Profil-Seite erscheinen wird.",
|
||||
"user_has_no_bio": "Dieser Benutzer hat noch keine Bio hinzugefügt.",
|
||||
"bio": "Biografie",
|
||||
"delete_account_modal_title": "Account löschen",
|
||||
"confirm_delete_account_modal": "Sind Sie sicher, dass Sie Ihr {{appName}}-Konto löschen möchten?",
|
||||
"delete_my_account": "Meinen Account löschen",
|
||||
|
@ -1530,6 +1534,7 @@
|
|||
"problem_registering_domain": "Es gab ein Problem bei der Registrierung der Subdomain, bitte versuchen Sie es erneut oder kontaktieren Sie einen Administrator",
|
||||
"team_publish": "Team veröffentlichen",
|
||||
"number_text_notifications": "Telefonnummer (Textbenachrichtigungen)",
|
||||
"number_sms_notifications": "Telefonnummer (SMS-Benachrichtigungen)",
|
||||
"attendee_email_variable": "Teilnehmer E-Mail",
|
||||
"attendee_email_info": "Die E-Mail-Adresse der buchenden Person",
|
||||
"kbar_search_placeholder": "Geben Sie einen Befehl ein oder suchen Sie ...",
|
||||
|
@ -1639,6 +1644,7 @@
|
|||
"minimum_round_robin_hosts_count": "Anzahl der Veranstalter, die teilnehmen müssen",
|
||||
"hosts": "Veranstalter",
|
||||
"upgrade_to_enable_feature": "Sie müssen ein Team erstellen, um diese Funktion zu aktivieren. Klicken Sie hier, um ein Team zu erstellen.",
|
||||
"orgs_upgrade_to_enable_feature": "Sie müssen auf unsere Enterprise-Lizenz aktualisieren, um diese Funktion zu aktivieren.",
|
||||
"new_attendee": "Neuer Teilnehmer",
|
||||
"awaiting_approval": "Wartet auf Genehmigung",
|
||||
"requires_google_calendar": "Diese App erfordert eine Verbindung mit Google Calendar",
|
||||
|
@ -1743,6 +1749,7 @@
|
|||
"show_on_booking_page": "Auf der Buchungsseite anzeigen",
|
||||
"get_started_zapier_templates": "Legen Sie mit Zapier-Vorlagen los",
|
||||
"team_is_unpublished": "{{team}} ist unveröffentlicht",
|
||||
"org_is_unpublished_description": "Dieser Organisations-Link ist derzeit nicht verfügbar. Bitte kontaktieren Sie den Organisations-Besitzer oder fragen Sie ihn, ob er ihn veröffentlicht.",
|
||||
"team_is_unpublished_description": "Dieser {{entity}}-Link ist derzeit nicht verfügbar. Bitte kontaktieren Sie den {{entity}}-Besitzer oder fragen Sie ihn, ob er ihn veröffentlicht.",
|
||||
"team_member": "Teammitglied",
|
||||
"a_routing_form": "Ein Weiterleitungsformular",
|
||||
|
@ -1877,6 +1884,7 @@
|
|||
"edit_invite_link": "Linkeinstellungen bearbeiten",
|
||||
"invite_link_copied": "Einladungslink kopiert",
|
||||
"invite_link_deleted": "Einladungslink gelöscht",
|
||||
"api_key_deleted": "API-Schlüssel gelöscht",
|
||||
"invite_link_updated": "Einladungslink-Einstellungen gespeichert",
|
||||
"link_expires_after": "Links verfallen nach...",
|
||||
"one_day": "1 Tag",
|
||||
|
@ -2011,7 +2019,13 @@
|
|||
"attendee_last_name_variable": "Nachname des Teilnehmers",
|
||||
"attendee_first_name_info": "Vorname der buchenden Person",
|
||||
"attendee_last_name_info": "Nachname der buchenden Person",
|
||||
"your_monthly_digest": "Ihre monatliche Statistik",
|
||||
"member_name": "Mitgliedsname",
|
||||
"most_popular_events": "Beliebteste Termine",
|
||||
"summary_of_events_for_your_team_for_the_last_30_days": "Hier ist Ihre Zusammenfassung der beliebten Termine für Ihr Team {{teamName}} der letzten 30 Tage",
|
||||
"me": "Ich",
|
||||
"monthly_digest_email": "Monatliche Statistik E-Mail",
|
||||
"monthly_digest_email_for_teams": "Monatliche Statistik E-Mail für Teams",
|
||||
"verify_team_tooltip": "Verifizieren Sie Ihr Team, um das Senden von Nachrichten an Teilnehmer zu aktivieren",
|
||||
"member_removed": "Mitglied entfernt",
|
||||
"my_availability": "Meine Verfügbarkeit",
|
||||
|
@ -2041,12 +2055,28 @@
|
|||
"team_no_event_types": "Dieses Team hat keine Ereignistypen",
|
||||
"seat_options_doesnt_multiple_durations": "Platzoption unterstützt mehrere Dauern nicht",
|
||||
"include_calendar_event": "Kalenderereignis hinzufügen",
|
||||
"oAuth": "OAuth",
|
||||
"recently_added": "Kürzlich hinzugefügt",
|
||||
"no_members_found": "Keine Mitglieder gefunden",
|
||||
"event_setup_length_error": "Ereignis-Einrichtung: Die Dauer muss mindestens 1 Minute betragen.",
|
||||
"availability_schedules": "Verfügbarkeitspläne",
|
||||
"unauthorized": "Nicht authorisiert",
|
||||
"access_cal_account": "{{clientName}} möchte auf Ihr {{appName}} Konto zugreifen",
|
||||
"select_account_team": "Konto oder Team auswählen",
|
||||
"allow_client_to": "Dies wird {{clientName}} erlauben",
|
||||
"see_personal_info": "Ihre persönlichen Daten einzusehen, einschließlich persönlicher Informationen, die Sie öffentlich zugänglich gemacht haben",
|
||||
"see_primary_email_address": "Ihre primäre E-Mail-Adresse einzusehen",
|
||||
"connect_installed_apps": "Sich mit Ihren installierten Apps zu verbinden",
|
||||
"access_event_type": "Lesen, Bearbeiten, Löschen Ihrer Ereignis-Typen",
|
||||
"access_availability": "Lesen, Bearbeiten, Löschen Ihrer Verfügbarkeiten",
|
||||
"access_bookings": "Lesen, Bearbeiten, Löschen Ihrer Termine",
|
||||
"allow_client_to_do": "{{clientName}} zulassen, dies zu tun?",
|
||||
"allow": "Zulassen",
|
||||
"view_only_edit_availability_not_onboarded": "Dieser Benutzer hat das Onboarding noch nicht abgeschlossen. Sie können seine Verfügbarkeit erst festlegen, wenn er das Onboarding abgeschlossen hat.",
|
||||
"view_only_edit_availability": "Sie sehen die Verfügbarkeit dieses Benutzers. Sie können nur Ihre eigene Verfügbarkeit bearbeiten.",
|
||||
"edit_users_availability": "Benutzerverfügbarkeit bearbeiten: {{username}}",
|
||||
"resend_invitation": "Einladung erneut senden",
|
||||
"invitation_resent": "Die Einladung wurde erneut gesendet.",
|
||||
"this_app_is_not_setup_already": "Diese App wurde noch nicht eingerichtet",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Fügen Sie Ihre neuen Code-Zeilen über dieser hinzu ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
|
|
@ -1619,6 +1619,7 @@
|
|||
"date_overrides_mark_all_day_unavailable_other": "Mark unavailable on selected dates",
|
||||
"date_overrides_add_btn": "Add Override",
|
||||
"date_overrides_update_btn": "Update Override",
|
||||
"date_successfully_added": "Date override added successfully",
|
||||
"event_type_duplicate_copy_text": "{{slug}}-copy",
|
||||
"set_as_default": "Set as default",
|
||||
"hide_eventtype_details": "Hide event type details",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -122,9 +122,9 @@ function AlbySetupPage(props: IAlbySetupProps) {
|
|||
|
||||
const albyIcon = (
|
||||
<>
|
||||
<img className="h-16 w-16 dark:hidden" src="/api/app-store/alby/icon-borderless.svg" alt="Alby Icon" />
|
||||
<img className="h-12 w-12 dark:hidden" src="/api/app-store/alby/icon-borderless.svg" alt="Alby Icon" />
|
||||
<img
|
||||
className="hidden h-16 w-16 dark:block"
|
||||
className="hidden h-12 w-12 dark:block"
|
||||
src="/api/app-store/alby/icon-borderless-dark.svg"
|
||||
alt="Alby Icon"
|
||||
/>
|
||||
|
|
|
@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
|||
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import { createContext } from "@calcom/trpc/server/createContext";
|
||||
import { viewerRouter } from "@calcom/trpc/server/routers/viewer/_router";
|
||||
import { apiKeysRouter } from "@calcom/trpc/server/routers/viewer/apiKeys/_router";
|
||||
|
||||
import checkSession from "../../_utils/auth";
|
||||
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
|
||||
|
@ -15,9 +15,9 @@ export async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
|||
const appType = appConfig.type;
|
||||
|
||||
const ctx = await createContext({ req, res });
|
||||
const caller = viewerRouter.createCaller(ctx);
|
||||
const caller = apiKeysRouter.createCaller(ctx);
|
||||
|
||||
const apiKey = await caller.apiKeys.create({
|
||||
const apiKey = await caller.create({
|
||||
note: "Cal.ai",
|
||||
expiresAt: null,
|
||||
appId: "cal-ai",
|
||||
|
|
|
@ -3,7 +3,6 @@ import { useForm } from "react-hook-form";
|
|||
|
||||
import type { Dayjs } from "@calcom/dayjs";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { yyyymmdd } from "@calcom/lib/date-fns";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
|
||||
|
@ -15,6 +14,7 @@ import {
|
|||
DialogHeader,
|
||||
DialogClose,
|
||||
Switch,
|
||||
showToast,
|
||||
Form,
|
||||
Button,
|
||||
} from "@calcom/ui";
|
||||
|
@ -23,15 +23,11 @@ import DatePicker from "../../calendars/DatePicker";
|
|||
import type { TimeRange } from "./Schedule";
|
||||
import { DayRanges } from "./Schedule";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
const noop = () => {};
|
||||
|
||||
const DateOverrideForm = ({
|
||||
value,
|
||||
workingHours,
|
||||
excludedDates,
|
||||
onChange,
|
||||
onClose = noop,
|
||||
}: {
|
||||
workingHours?: WorkingHours[];
|
||||
onChange: (newValue: TimeRange[]) => void;
|
||||
|
@ -137,14 +133,10 @@ const DateOverrideForm = ({
|
|||
})
|
||||
: datesInRanges
|
||||
);
|
||||
onClose();
|
||||
setSelectedDates([]);
|
||||
}}
|
||||
className="p-6 sm:flex sm:p-0 md:flex-col lg:flex-col xl:flex-row">
|
||||
<div
|
||||
className={classNames(
|
||||
selectedDates[0] && "sm:border-subtle w-full sm:border-r sm:pr-6",
|
||||
"sm:p-4 md:p-8"
|
||||
)}>
|
||||
<div className="sm:border-subtle w-full sm:border-r sm:p-4 sm:pr-6 md:p-8">
|
||||
<DialogHeader title={t("date_overrides_dialog_title")} />
|
||||
<DatePicker
|
||||
excludedDates={excludedDates}
|
||||
|
@ -160,39 +152,48 @@ const DateOverrideForm = ({
|
|||
locale={isLocaleReady ? i18n.language : "en"}
|
||||
/>
|
||||
</div>
|
||||
{selectedDates[0] && (
|
||||
<div className="relative mt-8 flex w-full flex-col sm:mt-0 sm:p-4 md:p-8">
|
||||
<div className="mb-4 flex-grow space-y-4">
|
||||
<p className="text-medium text-emphasis text-sm">{t("date_overrides_dialog_which_hours")}</p>
|
||||
<div>
|
||||
{datesUnavailable ? (
|
||||
<p className="text-subtle border-default rounded border p-2 text-sm">
|
||||
{t("date_overrides_unavailable")}
|
||||
</p>
|
||||
) : (
|
||||
<DayRanges name="range" />
|
||||
)}
|
||||
<div className="relative mt-8 flex w-full flex-col sm:mt-0 sm:p-4 md:p-8">
|
||||
{selectedDates[0] ? (
|
||||
<>
|
||||
<div className="mb-4 flex-grow space-y-4">
|
||||
<p className="text-medium text-emphasis text-sm">{t("date_overrides_dialog_which_hours")}</p>
|
||||
<div>
|
||||
{datesUnavailable ? (
|
||||
<p className="text-subtle border-default rounded border p-2 text-sm">
|
||||
{t("date_overrides_unavailable")}
|
||||
</p>
|
||||
) : (
|
||||
<DayRanges name="range" />
|
||||
)}
|
||||
</div>
|
||||
<Switch
|
||||
label={t("date_overrides_mark_all_day_unavailable_one")}
|
||||
checked={datesUnavailable}
|
||||
onCheckedChange={setDatesUnavailable}
|
||||
data-testid="date-override-mark-unavailable"
|
||||
/>
|
||||
</div>
|
||||
<Switch
|
||||
label={t("date_overrides_mark_all_day_unavailable_one")}
|
||||
checked={datesUnavailable}
|
||||
onCheckedChange={setDatesUnavailable}
|
||||
data-testid="date-override-mark-unavailable"
|
||||
/>
|
||||
<div className="mt-4 flex flex-row-reverse sm:mt-0">
|
||||
<Button
|
||||
className="ml-2"
|
||||
color="primary"
|
||||
type="submit"
|
||||
onClick={() => {
|
||||
showToast(t("date_successfully_added"), "success");
|
||||
}}
|
||||
disabled={selectedDates.length === 0}
|
||||
data-testid="add-override-submit-btn">
|
||||
{value ? t("date_overrides_update_btn") : t("date_overrides_add_btn")}
|
||||
</Button>
|
||||
<DialogClose />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="bottom-7 right-8 flex flex-row-reverse sm:absolute">
|
||||
<DialogClose />
|
||||
</div>
|
||||
<div className="mt-4 flex flex-row-reverse sm:mt-0">
|
||||
<Button
|
||||
className="ml-2"
|
||||
color="primary"
|
||||
type="submit"
|
||||
disabled={selectedDates.length === 0}
|
||||
data-testid="add-override-submit-btn">
|
||||
{value ? t("date_overrides_update_btn") : t("date_overrides_add_btn")}
|
||||
</Button>
|
||||
<DialogClose onClick={onClose} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
@ -220,7 +221,7 @@ const DateOverrideInputDialog = ({
|
|||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>{Trigger}</DialogTrigger>
|
||||
|
||||
<DialogContent enableOverflow={enableOverflow} size="md" className="p-0 md:w-auto">
|
||||
<DialogContent enableOverflow={enableOverflow} size="md" className="p-0">
|
||||
<DateOverrideForm
|
||||
excludedDates={excludedDates}
|
||||
{...passThroughProps}
|
||||
|
|
|
@ -1008,7 +1008,7 @@ function MainContainer({
|
|||
<main className="bg-default relative z-0 flex-1 focus:outline-none">
|
||||
{/* show top navigation for md and smaller (tablet and phones) */}
|
||||
{TopNavContainerProp}
|
||||
<div className="max-w-full px-2 py-4 md:py-12 lg:px-6">
|
||||
<div className="max-w-full px-2 py-4 lg:px-6">
|
||||
<ErrorBoundary>
|
||||
{!props.withoutMain ? <ShellMain {...props}>{props.children}</ShellMain> : props.children}
|
||||
</ErrorBoundary>
|
||||
|
|
|
@ -7,7 +7,7 @@ export const useUrlMatchesCurrentUrl = (url: string) => {
|
|||
// It can certainly have null value https://nextjs.org/docs/app/api-reference/functions/use-pathname#:~:text=usePathname%20can%20return%20null%20when%20a%20fallback%20route%20is%20being%20rendered%20or%20when%20a%20pages%20directory%20page%20has%20been%20automatically%20statically%20optimized%20by%20Next.js%20and%20the%20router%20is%20not%20ready.
|
||||
const pathname = usePathname() as null | string;
|
||||
const searchParams = useSearchParams();
|
||||
const query = searchParams.toString();
|
||||
const query = searchParams?.toString();
|
||||
let pathnameWithQuery;
|
||||
if (query) {
|
||||
pathnameWithQuery = `${pathname}?${query}`;
|
||||
|
|
|
@ -49,4 +49,11 @@ export const publicViewerRouter = router({
|
|||
const handler = await importHandler(namespaced("event"), () => import("./event.handler"));
|
||||
return handler(opts);
|
||||
}),
|
||||
ssoConnections: publicProcedure.query(async () => {
|
||||
const handler = await importHandler(
|
||||
namespaced("ssoConnections"),
|
||||
() => import("./ssoConnections.handler")
|
||||
);
|
||||
return handler();
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import jackson from "@calcom/features/ee/sso/lib/jackson";
|
||||
import { samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml";
|
||||
import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
export const handler = async () => {
|
||||
try {
|
||||
if (HOSTED_CAL_FEATURES) {
|
||||
return {
|
||||
connectionExists: null,
|
||||
};
|
||||
}
|
||||
|
||||
const { connectionController } = await jackson();
|
||||
|
||||
const connections = await connectionController.getConnections({
|
||||
tenant: samlTenantID,
|
||||
product: samlProductID,
|
||||
});
|
||||
|
||||
return {
|
||||
connectionExists: connections.length > 0,
|
||||
};
|
||||
} catch (err) {
|
||||
console.error("Error getting SSO connections", err);
|
||||
throw new TRPCError({ code: "BAD_REQUEST", message: "Fetching SSO connections failed." });
|
||||
}
|
||||
};
|
||||
|
||||
export default handler;
|
|
@ -187,7 +187,7 @@ export function DialogClose(
|
|||
return (
|
||||
<DialogPrimitive.Close asChild {...props.dialogCloseProps}>
|
||||
{/* This will require the i18n string passed in */}
|
||||
<Button color={props.color || "minimal"} {...props}>
|
||||
<Button data-testid="dialog-rejection" color={props.color || "minimal"} {...props}>
|
||||
{props.children ? props.children : t("Close")}
|
||||
</Button>
|
||||
</DialogPrimitive.Close>
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
import { Canvas, Meta, Story, ArgsTable } from "@storybook/addon-docs";
|
||||
import { Canvas, Meta, Story } from "@storybook/addon-docs";
|
||||
|
||||
import {
|
||||
Examples,
|
||||
Example,
|
||||
Note,
|
||||
Title,
|
||||
CustomArgsTable,
|
||||
VariantRow,
|
||||
VariantsTable,
|
||||
} from "@calcom/storybook/components";
|
||||
|
||||
import { Select, UnstyledSelect } from "../select";
|
||||
import { InputFieldWithSelect } from "./Input";
|
||||
import { InputField } from "./Input";
|
||||
import { InputFieldWithSelect } from "./InputFieldWithSelect";
|
||||
import { InputField } from "./TextField";
|
||||
|
||||
<Meta title="UI/Form/Input Field" component={InputField} />
|
||||
|
||||
<Title title="Inputs" suffix="Brief" subtitle="Version 2.0 — Last Update: 22 Aug 2022" />
|
||||
<Title title="Inputs" suffix="Brief" subtitle="Version 2.0 — Last Update: 24 Aug 2023" />
|
||||
|
||||
## Definition
|
||||
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
import { Canvas, Meta, Story } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import {
|
||||
Title,
|
||||
VariantRow,
|
||||
VariantsTable,
|
||||
CustomArgsTable,
|
||||
Examples,
|
||||
Example,
|
||||
} from "@calcom/storybook/components";
|
||||
import { Plus } from "@calcom/ui/components/icon";
|
||||
|
||||
import HorizontalTabs from "../HorizontalTabs";
|
||||
|
||||
<Meta title="UI/Navigation/HorizontalTabs" component={HorizontalTabs} />
|
||||
|
||||
<Title title="Horizontal Tabs" suffix="Brief" subtitle="Version 1.0 — Last Update: 17 Aug 2023" />
|
||||
|
||||
## Definition
|
||||
|
||||
The HorizontalTabs component is a user interface element used for displaying a horizontal set of tabs, often employed for navigation or organization purposes within a web application.
|
||||
|
||||
## Structure
|
||||
|
||||
The HorizontalTabs component is designed to work alongside the HorizontalTabItem component, which represents individual tabs within the tab bar.
|
||||
|
||||
export const tabs = [
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "?path=/story/ui-navigation-horizontaltabs--horizontal-tabs/tab1",
|
||||
disabled: false,
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
icon: Plus,
|
||||
},
|
||||
{
|
||||
name: "Tab 2",
|
||||
href: "?path=/story/ui-navigation-horizontaltabs--horizontal-tabs/tab2",
|
||||
disabled: false,
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
avatar: "Avatar",
|
||||
},
|
||||
{
|
||||
name: "Tab 3",
|
||||
href: "?path=/story/ui-navigation-horizontaltabs--horizontal-tabs/tab3",
|
||||
disabled: true,
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
},
|
||||
];
|
||||
|
||||
<CustomArgsTable of={HorizontalTabs} />
|
||||
|
||||
<Examples title="Default">
|
||||
<Example title="Default">
|
||||
<HorizontalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "tab 1",
|
||||
href: "?path=/story/ui-navigation-horizontaltabs--horizontal-tabs/tab1",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
<Example title="With avatar">
|
||||
<HorizontalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "?path=/story/ui-navigation-horizontaltabs--horizontal-tabs/tab1",
|
||||
avatar: "Avatar",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
<Example title="With icon">
|
||||
<HorizontalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "?path=/story/ui-navigation-horizontaltabs--horizontal-tabs/tab1",
|
||||
icon: Plus,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
<Example title="Disabled">
|
||||
<HorizontalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "?path=/story/ui-navigation-horizontaltabs--horizontal-tabs/tab1",
|
||||
disabled: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
</Examples>
|
||||
|
||||
## HorizontalTabs Story
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name="Horizontal Tabs"
|
||||
args={{
|
||||
name: "Tab 1",
|
||||
href: "/tab1",
|
||||
disabled: false,
|
||||
className: "",
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
icon: "",
|
||||
avatar: "",
|
||||
}}
|
||||
argTypes={{
|
||||
name: { control: "text", description: "Tab name" },
|
||||
href: { control: "text", description: "Tab link" },
|
||||
disabled: { control: "boolean", description: "Whether the tab is disabled" },
|
||||
className: { control: "text", description: "Additional CSS class" },
|
||||
linkShallow: { control: "boolean", description: "Whether to use shallow links" },
|
||||
linkScroll: { control: "boolean", description: "Whether to scroll to links" },
|
||||
icon: { control: "text", description: "SVGComponent icon" },
|
||||
avatar: { control: "text", description: "Avatar image URL" },
|
||||
}}>
|
||||
{(...props) => (
|
||||
<VariantsTable titles={["Default"]} columnMinWidth={150}>
|
||||
<VariantRow>
|
||||
<HorizontalTabs tabs={tabs} className="overflow-hidden" actions={<button>Click me</button>} />
|
||||
</VariantRow>
|
||||
</VariantsTable>
|
||||
)}
|
||||
</Story>
|
||||
</Canvas>
|
|
@ -0,0 +1,158 @@
|
|||
import { Meta, Story } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import {
|
||||
Title,
|
||||
CustomArgsTable,
|
||||
Examples,
|
||||
Example,
|
||||
VariantsTable,
|
||||
VariantsRow,
|
||||
} from "@calcom/storybook/components";
|
||||
import { Plus } from "@calcom/ui/components/icon";
|
||||
|
||||
import VerticalTabs from "../VerticalTabs";
|
||||
|
||||
<Meta title="UI/Navigation/VerticalTabs" component={VerticalTabs} />
|
||||
|
||||
<Title title="Vertical Tabs Brief" subtitle="Version 1.0 — Last Update: 17 Aug 2023" />
|
||||
|
||||
## Definition
|
||||
|
||||
The VerticalTabs component is a user interface element utilized to present a vertical set of tabs, commonly employed for navigation or organizing content within a web application.
|
||||
|
||||
## Structure
|
||||
|
||||
The VerticalTabs component is designed to complement the HorizontalTabItem component, which represents individual tabs within the tab bar. This combination allows for creating intuitive navigation experiences and organized content presentation.
|
||||
|
||||
export const tabs = [
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "/tab1",
|
||||
disabled: false,
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
disableChevron: true,
|
||||
icon: Plus,
|
||||
},
|
||||
{
|
||||
name: "Tab 2",
|
||||
href: "/tab2",
|
||||
disabled: false,
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
avatar: "Avatar",
|
||||
},
|
||||
{
|
||||
name: "Tab 3",
|
||||
href: "/tab3",
|
||||
disabled: true,
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
},
|
||||
];
|
||||
|
||||
<CustomArgsTable of={VerticalTabs} />
|
||||
|
||||
<Examples title="Default">
|
||||
<Example title="Default">
|
||||
<VerticalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "tab 1",
|
||||
href: "/tab1",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
<Example title="Disabled chevron">
|
||||
<VerticalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "/tab1",
|
||||
disableChevron: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
<Example title="With icon">
|
||||
<VerticalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "/tab1",
|
||||
icon: Plus,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
<Example title="Disabled">
|
||||
<VerticalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "/tab1",
|
||||
disabled: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
</Examples>
|
||||
|
||||
## VerticalTabs Story
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name="Vertical Tabs"
|
||||
args={{
|
||||
name: "Tab 1",
|
||||
info: "Tab information",
|
||||
icon: Plus,
|
||||
disabled: false,
|
||||
children: [
|
||||
{
|
||||
name: "Sub Tab 1",
|
||||
href: "/sub-tab1",
|
||||
disabled: false,
|
||||
className: "sub-tab",
|
||||
},
|
||||
],
|
||||
textClassNames: "",
|
||||
className: "",
|
||||
isChild: false,
|
||||
hidden: false,
|
||||
disableChevron: true,
|
||||
href: "/tab1",
|
||||
isExternalLink: true,
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
avatar: "",
|
||||
iconClassName: "",
|
||||
}}
|
||||
argTypes={{
|
||||
name: { control: "text", description: "Tab name" },
|
||||
info: { control: "text", description: "Tab information" },
|
||||
icon: { control: "object", description: "SVGComponent icon" },
|
||||
disabled: { control: "boolean", description: "Whether the tab is disabled" },
|
||||
children: { control: "object", description: "Array of child tabs" },
|
||||
textClassNames: { control: "text", description: "Additional text class names" },
|
||||
className: { control: "text", description: "Additional CSS class" },
|
||||
isChild: { control: "boolean", description: "Whether the tab is a child tab" },
|
||||
hidden: { control: "boolean", description: "Whether the tab is hidden" },
|
||||
disableChevron: { control: "boolean", description: "Whether to disable the chevron" },
|
||||
href: { control: "text", description: "Tab link" },
|
||||
isExternalLink: { control: "boolean", description: "Whether the link is external" },
|
||||
linkShallow: { control: "boolean", description: "Whether to use shallow links" },
|
||||
linkScroll: { control: "boolean", description: "Whether to scroll to links" },
|
||||
avatar: { control: "text", description: "Avatar image URL" },
|
||||
iconClassName: { control: "text", description: "Additional icon class name" },
|
||||
}}>
|
||||
{(...props) => (
|
||||
<VariantsTable titles={["Default"]} columnMinWidth={150}>
|
||||
<VariantRow>
|
||||
<VerticalTabs tabs={tabs} className="overflow-hidden" />
|
||||
</VariantRow>
|
||||
</VariantsTable>
|
||||
)}
|
||||
</Story>
|
||||
</Canvas>
|
Loading…
Reference in New Issue
Block a user