-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add calendar cache status and actions (#22532) #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: calendar-cache-foundation
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| "use client"; | ||
|
|
||
| import { useState } from "react"; | ||
|
|
||
| import { useLocale } from "@calcom/lib/hooks/useLocale"; | ||
| import { GOOGLE_CALENDAR_TYPE } from "@calcom/platform-constants"; | ||
| import { trpc } from "@calcom/trpc/react"; | ||
| import { Button } from "@calcom/ui/components/button"; | ||
| import { ConfirmationDialogContent } from "@calcom/ui/components/dialog"; | ||
| import { Dialog } from "@calcom/ui/components/dialog"; | ||
| import { | ||
| Dropdown, | ||
| DropdownItem, | ||
| DropdownMenuContent, | ||
| DropdownMenuItem, | ||
| DropdownMenuTrigger, | ||
| } from "@calcom/ui/components/dropdown"; | ||
| import { showToast } from "@calcom/ui/components/toast"; | ||
|
|
||
| interface CredentialActionsDropdownProps { | ||
| credentialId: number; | ||
| integrationType: string; | ||
| cacheUpdatedAt?: Date | null; | ||
| onSuccess?: () => void; | ||
| delegationCredentialId?: string | null; | ||
| disableConnectionModification?: boolean; | ||
| } | ||
|
|
||
| export default function CredentialActionsDropdown({ | ||
| credentialId, | ||
| integrationType, | ||
| cacheUpdatedAt, | ||
| onSuccess, | ||
| delegationCredentialId, | ||
| disableConnectionModification, | ||
| }: CredentialActionsDropdownProps) { | ||
| const { t } = useLocale(); | ||
| const [dropdownOpen, setDropdownOpen] = useState(false); | ||
| const [deleteModalOpen, setDeleteModalOpen] = useState(false); | ||
| const [disconnectModalOpen, setDisconnectModalOpen] = useState(false); | ||
|
|
||
| const deleteCacheMutation = trpc.viewer.calendars.deleteCache.useMutation({ | ||
| onSuccess: () => { | ||
| showToast(t("cache_deleted_successfully"), "success"); | ||
| onSuccess?.(); | ||
| }, | ||
| onError: () => { | ||
| showToast(t("error_deleting_cache"), "error"); | ||
| }, | ||
| }); | ||
|
|
||
| const utils = trpc.useUtils(); | ||
| const disconnectMutation = trpc.viewer.credentials.delete.useMutation({ | ||
| onSuccess: () => { | ||
| showToast(t("app_removed_successfully"), "success"); | ||
| onSuccess?.(); | ||
| }, | ||
| onError: () => { | ||
| showToast(t("error_removing_app"), "error"); | ||
| }, | ||
| async onSettled() { | ||
| await utils.viewer.calendars.connectedCalendars.invalidate(); | ||
| await utils.viewer.apps.integrations.invalidate(); | ||
| }, | ||
| }); | ||
|
|
||
| const isGoogleCalendar = integrationType === GOOGLE_CALENDAR_TYPE; | ||
| const canDisconnect = !delegationCredentialId && !disableConnectionModification; | ||
| const hasCache = isGoogleCalendar && cacheUpdatedAt; | ||
|
|
||
| if (!canDisconnect && !hasCache) { | ||
| return null; | ||
| } | ||
|
|
||
| return ( | ||
| <> | ||
| <Dropdown open={dropdownOpen} onOpenChange={setDropdownOpen}> | ||
| <DropdownMenuTrigger asChild> | ||
| <Button type="button" variant="icon" color="secondary" StartIcon="ellipsis" /> | ||
| </DropdownMenuTrigger> | ||
| <DropdownMenuContent> | ||
| {hasCache && ( | ||
| <> | ||
| <DropdownMenuItem className="focus:ring-muted"> | ||
| <div className="px-2 py-1"> | ||
| <div className="text-sm font-medium text-gray-900 dark:text-white">{t("cache_status")}</div> | ||
| <div className="text-xs text-gray-500 dark:text-white"> | ||
| {t("cache_last_updated", { | ||
| timestamp: new Intl.DateTimeFormat("en-US", { | ||
| dateStyle: "short", | ||
| timeStyle: "short", | ||
|
Comment on lines
+90
to
+91
|
||
| }).format(new Date(cacheUpdatedAt)), | ||
| interpolation: { escapeValue: false }, | ||
| })} | ||
| </div> | ||
| </div> | ||
| </DropdownMenuItem> | ||
| <DropdownMenuItem className="outline-none"> | ||
| <DropdownItem | ||
| type="button" | ||
| color="destructive" | ||
| StartIcon="trash" | ||
| onClick={() => { | ||
| setDeleteModalOpen(true); | ||
| setDropdownOpen(false); | ||
| }}> | ||
| {t("delete_cached_data")} | ||
| </DropdownItem> | ||
| </DropdownMenuItem> | ||
| </> | ||
| )} | ||
| {canDisconnect && hasCache && <hr className="my-1" />} | ||
| {canDisconnect && ( | ||
| <DropdownMenuItem className="outline-none"> | ||
| <DropdownItem | ||
| type="button" | ||
| color="destructive" | ||
| StartIcon="trash" | ||
| onClick={() => { | ||
| setDisconnectModalOpen(true); | ||
| setDropdownOpen(false); | ||
| }}> | ||
| {t("remove_app")} | ||
| </DropdownItem> | ||
| </DropdownMenuItem> | ||
| )} | ||
| </DropdownMenuContent> | ||
| </Dropdown> | ||
|
|
||
| <Dialog open={deleteModalOpen} onOpenChange={setDeleteModalOpen}> | ||
| <ConfirmationDialogContent | ||
| variety="danger" | ||
| title={t("delete_cached_data")} | ||
| confirmBtnText={t("yes_delete_cache")} | ||
| onConfirm={() => { | ||
| deleteCacheMutation.mutate({ credentialId }); | ||
| setDeleteModalOpen(false); | ||
| }}> | ||
| {t("confirm_delete_cache")} | ||
| </ConfirmationDialogContent> | ||
| </Dialog> | ||
|
|
||
| <Dialog open={disconnectModalOpen} onOpenChange={setDisconnectModalOpen}> | ||
| <ConfirmationDialogContent | ||
| variety="danger" | ||
| title={t("remove_app")} | ||
| confirmBtnText={t("yes_remove_app")} | ||
| onConfirm={() => { | ||
| disconnectMutation.mutate({ id: credentialId }); | ||
| setDisconnectModalOpen(false); | ||
| }}> | ||
| {t("are_you_sure_you_want_to_remove_this_app")} | ||
| </ConfirmationDialogContent> | ||
| </Dialog> | ||
| </> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -2,7 +2,7 @@ import Link from "next/link"; | |||||
| import React from "react"; | ||||||
|
|
||||||
| import AppListCard from "@calcom/features/apps/components/AppListCard"; | ||||||
| import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration"; | ||||||
| import CredentialActionsDropdown from "@calcom/features/apps/components/CredentialActionsDropdown"; | ||||||
| import AdditionalCalendarSelector from "@calcom/features/calendars/AdditionalCalendarSelector"; | ||||||
| import { CalendarSwitch } from "@calcom/features/calendars/CalendarSwitch"; | ||||||
| import { useLocale } from "@calcom/lib/hooks/useLocale"; | ||||||
|
|
@@ -67,18 +67,16 @@ const ConnectedCalendarList = ({ | |||||
| description={connectedCalendar.primary?.email ?? connectedCalendar.integration.description} | ||||||
| className="border-subtle mt-4 rounded-lg border" | ||||||
| actions={ | ||||||
| // Delegation credential can't be disconnected | ||||||
| !connectedCalendar.delegationCredentialId && | ||||||
| !disableConnectionModification && ( | ||||||
| <div className="flex w-32 justify-end"> | ||||||
| <DisconnectIntegration | ||||||
| credentialId={connectedCalendar.credentialId} | ||||||
| trashIcon | ||||||
| onSuccess={onChanged} | ||||||
| buttonProps={{ className: "border border-default" }} | ||||||
| /> | ||||||
| </div> | ||||||
| ) | ||||||
| <div className="flex w-32 justify-end"> | ||||||
| <CredentialActionsDropdown | ||||||
| credentialId={connectedCalendar.credentialId} | ||||||
| integrationType={connectedCalendar.integration.type} | ||||||
| cacheUpdatedAt={connectedCalendar.cacheUpdatedAt} | ||||||
| onSuccess={onChanged} | ||||||
| delegationCredentialId={connectedCalendar.delegationCredentialId} | ||||||
| disableConnectionModification={disableConnectionModification} | ||||||
| /> | ||||||
| </div> | ||||||
| }> | ||||||
| <div className="border-subtle border-t"> | ||||||
| {!fromOnboarding && ( | ||||||
|
|
@@ -97,7 +95,7 @@ const ConnectedCalendarList = ({ | |||||
| destination={cal.externalId === destinationCalendarId} | ||||||
| credentialId={cal.credentialId} | ||||||
| eventTypeId={shouldUseEventTypeScope ? eventTypeId : null} | ||||||
| delegationCredentialId={connectedCalendar.delegationCredentialId} | ||||||
| delegationCredentialId={connectedCalendar.delegationCredentialId || null} | ||||||
|
||||||
| delegationCredentialId={connectedCalendar.delegationCredentialId || null} | |
| delegationCredentialId={connectedCalendar.delegationCredentialId} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| /* | ||
| Warnings: | ||
|
|
||
| - Added the required column `updatedAt` to the `CalendarCache` table without a default value. This is not possible if the table is not empty. | ||
|
|
||
| */ | ||
| -- AlterTable | ||
| -- Add the column with a default value to safely handle existing rows | ||
| ALTER TABLE "CalendarCache" ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT NOW(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This DropdownMenuItem is used to display read-only information (cache status), but DropdownMenuItem typically implies an interactive element. Consider using a different component or adding appropriate ARIA attributes to indicate this is informational rather than actionable.