| "use client"; |
|
|
| import { apps as appStoreApps } from "@midday/app-store"; |
| import type { UnifiedApp } from "@midday/app-store/types"; |
| import { Button } from "@midday/ui/button"; |
| import { useQueryClient, useSuspenseQuery } from "@tanstack/react-query"; |
| import { useRouter, useSearchParams } from "next/navigation"; |
| import { useEffect } from "react"; |
| import { AppConnectionToast } from "@/components/app-connection-toast"; |
| import { ConnectWhatsApp } from "@/components/inbox/connect-whatsapp"; |
| import { useUserQuery } from "@/hooks/use-user"; |
| import { useTRPC } from "@/trpc/client"; |
| import { isOAuthMessage } from "@/utils/oauth-message"; |
| import { UnifiedAppComponent } from "./unified-app"; |
|
|
| export function Apps() { |
| const trpc = useTRPC(); |
| const queryClient = useQueryClient(); |
| const { data: user } = useUserQuery(); |
| const router = useRouter(); |
|
|
| |
| useEffect(() => { |
| const handleMessage = (e: MessageEvent) => { |
| if (isOAuthMessage(e.data) && e.data.type === "app_oauth_completed") { |
| queryClient.invalidateQueries({ |
| queryKey: trpc.apps.get.queryKey(), |
| }); |
| queryClient.invalidateQueries({ |
| queryKey: trpc.inboxAccounts.get.queryKey(), |
| }); |
| queryClient.invalidateQueries({ |
| queryKey: trpc.invoicePayments.stripeStatus.queryKey(), |
| }); |
| } |
| }; |
|
|
| window.addEventListener("message", handleMessage); |
| return () => window.removeEventListener("message", handleMessage); |
| }, [queryClient, trpc]); |
|
|
| |
| const { data: installedOfficialApps } = useSuspenseQuery( |
| trpc.apps.get.queryOptions(), |
| ); |
|
|
| const { data: externalAppsData } = useSuspenseQuery( |
| trpc.oauthApplications.list.queryOptions(), |
| ); |
|
|
| const { data: authorizedExternalApps } = useSuspenseQuery( |
| trpc.oauthApplications.authorized.queryOptions(), |
| ); |
|
|
| |
| const { data: inboxAccounts } = useSuspenseQuery( |
| trpc.inboxAccounts.get.queryOptions(), |
| ); |
|
|
| |
| const { data: stripeStatus } = useSuspenseQuery( |
| trpc.invoicePayments.stripeStatus.queryOptions(), |
| ); |
|
|
| const searchParams = useSearchParams(); |
| const isInstalledPage = searchParams.get("tab") === "installed"; |
| const search = searchParams.get("q"); |
|
|
| |
| const getInboxAccount = (providerId: string) => { |
| return inboxAccounts?.find((account) => account.provider === providerId); |
| }; |
|
|
| |
| const transformedOfficialApps: UnifiedApp[] = appStoreApps.map((app) => { |
| |
| const isInboxApp = app.id === "gmail" || app.id === "outlook"; |
| const inboxAccount = isInboxApp ? getInboxAccount(app.id) : null; |
| |
| const isStripePaymentsApp = app.id === "stripe-payments"; |
| const installed = isInboxApp |
| ? !!inboxAccount |
| : isStripePaymentsApp |
| ? (stripeStatus?.connected ?? false) |
| : (installedOfficialApps?.some( |
| (installed) => installed.app_id === app.id, |
| ) ?? false); |
|
|
| return { |
| id: app.id, |
| name: app.name, |
| category: "category" in app ? app.category : "Integration", |
| active: app.active, |
| beta: |
| "beta" in app && typeof app.beta === "boolean" ? app.beta : undefined, |
| logo: app.logo, |
| short_description: app.short_description, |
| description: app.description || undefined, |
| images: app.images || [], |
| installed, |
| type: "official" as const, |
| onInitialize: |
| "onInitialize" in app && typeof app.onInitialize === "function" |
| ? async ({ |
| accessToken, |
| onComplete, |
| }: { |
| accessToken: string; |
| onComplete?: () => void; |
| }) => { |
| const result = app.onInitialize({ accessToken, onComplete }); |
| return result instanceof Promise |
| ? result |
| : Promise.resolve(result); |
| } |
| : undefined, |
| settings: |
| "settings" in app && Array.isArray(app.settings) |
| ? app.settings |
| : undefined, |
| userSettings: |
| (installedOfficialApps?.find((inst) => inst.app_id === app.id) |
| ?.settings as Record<string, any>) || undefined, |
| |
| inboxAccountId: inboxAccount?.id, |
| |
| installUrl: |
| "installUrl" in app && typeof app.installUrl === "string" |
| ? app.installUrl |
| : undefined, |
| }; |
| }); |
|
|
| |
| const approvedExternalApps = |
| externalAppsData?.data?.filter((app) => app.status === "approved") || []; |
| const transformedExternalApps: UnifiedApp[] = approvedExternalApps.map( |
| (app) => ({ |
| id: app.id, |
| name: app.name, |
| category: "Integration", |
| active: app.active ?? false, |
| logo: app.logoUrl || undefined, |
| short_description: app.description || undefined, |
| description: app.overview || app.description || undefined, |
| images: app.screenshots || [], |
| installed: |
| authorizedExternalApps?.data?.some( |
| (authorized) => authorized.id === app.id, |
| ) ?? false, |
| type: "external" as const, |
| clientId: app.clientId || undefined, |
| scopes: app.scopes || undefined, |
| developerName: app.developerName || undefined, |
| website: app.website || undefined, |
| installUrl: app.installUrl || undefined, |
| screenshots: app.screenshots || undefined, |
| overview: app.overview || undefined, |
| createdAt: app.createdAt || undefined, |
| status: app.status || undefined, |
| lastUsedAt: |
| authorizedExternalApps?.data?.find( |
| (authorized) => authorized.id === app.id, |
| )?.lastUsedAt || undefined, |
| }), |
| ); |
|
|
| |
| const allApps = [...transformedOfficialApps, ...transformedExternalApps]; |
|
|
| |
| const filteredApps = allApps |
| .filter((app) => !isInstalledPage || app.installed) |
| .filter( |
| (app) => !search || app.name.toLowerCase().includes(search.toLowerCase()), |
| ); |
|
|
| return ( |
| <> |
| <AppConnectionToast /> |
| |
| <div className="grid gap-6 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 mx-auto mt-8"> |
| {filteredApps.map((app) => ( |
| <UnifiedAppComponent |
| key={app.id} |
| app={app} |
| userEmail={user?.email || undefined} |
| /> |
| ))} |
| |
| {!search && !filteredApps.length && ( |
| <div className="col-span-full flex flex-col items-center justify-center h-[calc(100vh-400px)]"> |
| <h3 className="text-lg font-semibold text-[#1D1D1D] dark:text-[#F2F1EF]"> |
| No apps installed |
| </h3> |
| <p className="mt-2 text-sm text-[#878787] text-center max-w-md"> |
| You haven't installed any apps yet. Go to the 'All Apps' tab to |
| browse available apps. |
| </p> |
| </div> |
| )} |
| |
| {search && !filteredApps.length && ( |
| <div className="col-span-full flex flex-col items-center justify-center h-[calc(100vh-400px)]"> |
| <h3 className="text-lg font-semibold text-[#1D1D1D] dark:text-[#F2F1EF]"> |
| No apps found |
| </h3> |
| <p className="mt-2 text-sm text-[#878787] text-center max-w-md"> |
| No apps found for your search, let us know if you want to see a |
| specific app in the app store. |
| </p> |
| |
| <Button |
| onClick={() => router.push("/apps")} |
| className="mt-4" |
| variant="outline" |
| > |
| Clear search |
| </Button> |
| </div> |
| )} |
| </div> |
| |
| <ConnectWhatsApp showTrigger={false} /> |
| </> |
| ); |
| } |
|
|