import { AppInstallationWithManifest, ProjectRef } from "@vertesia/common"; import { Center, useFetch, SelectBox } from "@vertesia/ui/core"; import { LastSelectedAccountId_KEY, LastSelectedProjectId_KEY, useUserSession } from "@vertesia/ui/session"; import { LockIcon } from "lucide-react"; import { ComponentType, ReactNode, useEffect, useMemo, useState } from "react"; import { useUITranslation } from '../../i18n/index.js'; import { AppInstallationProvider } from "./AppInstallationProvider"; interface StandaloneAppProps { /** * The app name. * The name must be the name used to register the app in Vertesia. It will be used to check if thre user has access to the app. * * Also, this component is providing an AppInfo context that can be retrieved using the useAppInfo() hook. */ name: string; /** * A react element to display if the access is denied to the app. * If not specified a simple message will be displayed */ AccessDenied?: ComponentType; children: ReactNode; } export function StandaloneApp({ name, AccessDenied = AccessDeniedMessage, children }: StandaloneAppProps) { return name ? ( {children} ) : ( ) } export function StandaloneAppImpl({ name, AccessDenied = AccessDeniedMessage, children }: StandaloneAppProps) { const { authToken, client } = useUserSession(); const [installation, setInstallation] = useState(null) const [state, setState] = useState<"loading" | "error" | "loaded">("loading"); useEffect(() => { if (!authToken) { setState("loading"); } else { const isAppVisible = authToken.apps.includes(name); if (isAppVisible) { client.apps.getAppInstallationByName(name).then(inst => { if (!inst) { console.log(`App ${name} not found!`); setState("error"); } else { setState("loaded"); setInstallation(inst); } }); } else { setState("error"); } } }, [name, authToken]); if (state === "loading") { return null; } else if (state === "error") { return } else if (installation) { return {children} } } interface AccessDeniedMessageProps { name: string; } function AccessDeniedMessage({ name }: AccessDeniedMessageProps) { const { project, accounts, client } = useUserSession(); const { t } = useUITranslation(); const [selectedAccountId, setSelectedAccountId] = useState(); // Fetch all projects where the app is installed across all organizations const { data: allProjects } = useFetch(() => { return client.apps.getAppInstallationProjects({ name }); }, [name]); // Group projects by organization const { projectsByOrg, orgOptions } = useMemo(() => { if (!allProjects || !accounts) return { projectsByOrg: {}, orgOptions: [] }; const grouped: Record = {}; for (const p of allProjects) { if (!grouped[p.account]) { grouped[p.account] = []; } grouped[p.account].push(p); } // Only show orgs that have projects with the app installed const orgsWithProjects = accounts.filter(a => grouped[a.id]?.length > 0); return { projectsByOrg: grouped, orgOptions: orgsWithProjects }; }, [allProjects, accounts]); // Auto-select first org if not selected useEffect(() => { if (!selectedAccountId && orgOptions.length > 0) { setSelectedAccountId(orgOptions[0].id); } }, [orgOptions, selectedAccountId]); const onProjectChange = (selected: ProjectRef) => { localStorage.setItem(LastSelectedAccountId_KEY, selected.account); localStorage.setItem(LastSelectedProjectId_KEY + '-' + selected.account, selected.id); window.location.reload(); }; const filteredProjects = selectedAccountId ? (projectsByOrg[selectedAccountId] || []) : []; const selectedOrg = orgOptions.find(a => a.id === selectedAccountId); return (
{t('shell.accessDenied')}
You don't have permission to view the {name} app in project: «{project?.name}».
{orgOptions.length === 0 && allProjects !== undefined && (
This app is not installed in any project you have access to.
)} {orgOptions.length > 0 && (
{orgOptions.length > 1 && (
{t('shell.organization')}
option.name} placeholder={t('shell.selectOrganization')} onChange={(org) => setSelectedAccountId(org.id)} />
)}
{orgOptions.length > 1 &&
{t('login.terminal.project')}
} option.name} placeholder={t('shell.selectProject')} onChange={onProjectChange} />
)}
) } function UnknownAppName() { const { t } = useUITranslation(); return (
{t('shell.applicationNotRegistered')}
Before starting to code a Vertesia application you must register an application manifest in Vertesia Studio then install it in one or more projects.

Then use the created app name as a parameter to <StandaloneApp name="your-app-name"> in the src/main.tsx file.

) }