import React from "react";
import { Link, useLocation, useParams } from "react-router";
import {
App,
AppPermissions,
UpdateAppRequest,
WalletCapabilities,
} from "src/types";
import { handleRequestError } from "src/utils/handleRequestError";
import { request } from "src/utils/request"; // build the project for this to appear
import {
CheckCircleIcon,
ChevronDownIcon,
EllipsisIcon,
InfoIcon,
PlusIcon,
SquarePenIcon,
SquareStackIcon,
UnplugIcon,
} from "lucide-react";
import { toast } from "sonner";
import AppAvatar from "src/components/AppAvatar";
import AppHeader from "src/components/AppHeader";
import { AboutAppCard } from "src/components/connections/AboutAppCard";
import { AppLinksCard } from "src/components/connections/AppLinksCard";
import { AppTransactionList } from "src/components/connections/AppTransactionList";
import { AppUsage } from "src/components/connections/AppUsage";
import { ConnectionDetailsModal } from "src/components/connections/ConnectionDetailsModal";
import { DisconnectApp } from "src/components/connections/DisconnectApp";
import { getAppStoreApp } from "src/components/connections/SuggestedAppData";
import Loading from "src/components/Loading";
import Permissions from "src/components/Permissions";
import ResponsiveButton from "src/components/ResponsiveButton";
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogTitle,
} from "src/components/ui/alert-dialog";
import { Badge } from "src/components/ui/badge";
import { Button } from "src/components/ui/button";
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "src/components/ui/card";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "src/components/ui/dropdown-menu";
import { Input } from "src/components/ui/input";
import {
ALBY_ACCOUNT_APP_NAME,
SUBWALLET_APPSTORE_APP_ID,
} from "src/constants";
import { useAlbyMe } from "src/hooks/useAlbyMe";
import { useApp } from "src/hooks/useApp";
import { useAppsForAppStoreApp } from "src/hooks/useApps";
import { useCapabilities } from "src/hooks/useCapabilities";
import { cn, getAppDisplayName } from "src/lib/utils";
function AppDetails() {
const { id } = useParams() as { id: string };
const { data: app, mutate: refetchApp, error } = useApp(parseInt(id));
const { data: capabilities } = useCapabilities();
if (error) {
return
{error.message}
;
}
if (!app || !capabilities) {
return ;
}
return (
);
}
type AppInternalProps = {
app: App;
capabilities: WalletCapabilities;
refetchApp: () => void;
};
function AppInternal({ app, refetchApp, capabilities }: AppInternalProps) {
const location = useLocation();
const [isEditingPermissions, setIsEditingPermissions] = React.useState(false);
const [showConfirmDialog, setShowConfirmDialog] = React.useState(false);
const [showConnectionDetails, setShowConnectionDetails] =
React.useState(false);
const [showDisconnectAppDialog, setShowDisconnectAppDialog] =
React.useState(false);
const { data: albyMe } = useAlbyMe();
React.useEffect(() => {
const queryParams = new URLSearchParams(location.search);
const editMode = queryParams.has("edit");
setIsEditingPermissions(editMode);
}, [location.search]);
const [name, setName] = React.useState(app.name);
const [permissions, setPermissions] = React.useState({
scopes: app.scopes,
maxAmountSat: app.maxAmountSat,
budgetRenewal: app.budgetRenewal,
expiresAt: app.expiresAt ? new Date(app.expiresAt) : undefined,
isolated: app.isolated,
});
const [savedPermissions, setSavedPermissions] =
React.useState(permissions);
const handleSave = async () => {
try {
const updateAppRequest: UpdateAppRequest = {
name,
scopes: Array.from(permissions.scopes),
budgetRenewal: permissions.budgetRenewal,
expiresAt: permissions.expiresAt?.toISOString(),
updateExpiresAt: true,
maxAmountSat: permissions.maxAmountSat,
isolated: permissions.isolated,
};
await request(`/api/apps/${app.appPubkey}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(updateAppRequest),
});
refetchApp();
setIsEditingPermissions(false);
setSavedPermissions(permissions);
toast("Successfully updated connection");
} catch (error) {
handleRequestError("Failed to update connection", error);
}
};
const handleConvertToSubwallet = async () => {
try {
// Only send the metadata since that's the only thing changing
const updateAppRequest: UpdateAppRequest = {
metadata: {
...app.metadata,
app_store_app_id: SUBWALLET_APPSTORE_APP_ID,
},
};
await request(`/api/apps/${app.appPubkey}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(updateAppRequest),
});
refetchApp();
toast("Successfully converted to sub-wallet", {
description: "This isolated app is now a sub-wallet.",
});
} catch (error) {
handleRequestError("Failed to convert to sub-wallet", error);
}
};
const appName = getAppDisplayName(app.name);
const appStoreApp = getAppStoreApp(app);
const connectedApps = useAppsForAppStoreApp(appStoreApp);
const needsConfirmation =
(app.isolated && !permissions.isolated) ||
(!app.scopes.includes("pay_invoice") &&
permissions.scopes.includes("pay_invoice"));
return (
<>
{(connectedApps?.length || 0) > 1 ? (
{`${connectedApps?.length} Connections`}{" "}
{connectedApps?.map((connectedApp) => (
{connectedApp.name}
))}
) : (
<>
Connected
>
)}
}
contentRight={
{!isEditingPermissions && (
<>
{app.isolated &&
!app.metadata?.app_store_app_id &&
albyMe?.subscription.plan_code && (
Convert to Sub-wallet
)}
{appStoreApp && (
Add Another
Connection
)}
setShowConnectionDetails(true)}
>
Connection Details
setShowDisconnectAppDialog(true)}
>
Disconnect{" "}
{appName}
setIsEditingPermissions(true)}
icon={SquarePenIcon}
text="Edit Connection"
/>
>
)}
{isEditingPermissions && (
<>
{isEditingPermissions && (
{
setIsEditingPermissions(false);
}}
>
Cancel
{needsConfirmation ? (
Save
Confirm Update App
{app.isolated && !permissions.isolated ? (
Are you sure you wish to remove the{" "}
isolated
{" "}
status from this connection?
) : (
Are you sure you wish to give this
connection{" "}
pay permissions
?
)}
⚠️ Warning: This applies to all apps that
have this connection secret. Only change
this if you know it is safe to do so,
otherwise you could potentially lose all
funds
{!!permissions.maxAmountSat &&
" up to the specified budget"}
{permissions.isolated &&
" that are deposited into this isolated app"}
.
Cancel
Save
) : (
Save
)}
)}
>
)}
}
description={""}
/>
{!isEditingPermissions && (
<>
{appStoreApp && (
)}
>
)}
{isEditingPermissions ? (
) : (
Permissions
)}
{!isEditingPermissions && (
<>
{showConnectionDetails && (
setShowConnectionDetails(false)}
/>
)}
{showDisconnectAppDialog && (
setShowDisconnectAppDialog(false)}
/>
)}
>
)}
>
);
}
export default AppDetails;