Skip to content

Commit d3ffe9d

Browse files
committed
feat(dashboard): better support for deleted apps
1 parent 0018432 commit d3ffe9d

File tree

8 files changed

+581
-65
lines changed

8 files changed

+581
-65
lines changed

packages/apps/app-dashboard/src/components/user-dashboard/connect/ConnectPageWraper.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { AppVersionNotInRegistryConnect } from './AppVersionNotInRegistry';
77
import { AuthConnectScreen } from './AuthConnectScreen';
88
import { BadRedirectUriError } from './BadRedirectUriError';
99
import { ConnectPage } from './ConnectPage';
10+
import { DeletedAppConnect } from './DeletedAppConnect';
1011
import { DisabledVersionConnect } from './DisabledVersionConnect';
1112
import { EditPermissionsCard } from './EditPermissionsCard';
1213
import { GeneralErrorScreen } from './GeneralErrorScreen';
@@ -128,6 +129,16 @@ export function ConnectPageWrapper() {
128129
<BadRedirectUriError redirectUri={redirectUri} authorizedUris={data.app?.redirectUris} />
129130
);
130131
}
132+
// Check if app is deleted
133+
else if (data.app?.isDeleted) {
134+
content = (
135+
<DeletedAppConnect
136+
appData={data.app}
137+
hasPermission={isPermitted}
138+
readAuthInfo={{ authInfo, sessionSigs, isProcessing, error }}
139+
/>
140+
);
141+
}
131142
// Check for unpublished app version (check this early, before auth)
132143
else if (!data.app?.activeVersion) {
133144
// App has no active version set (version 1 not published)
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import { useCallback, useState } from 'react';
2+
import { useNavigate } from 'react-router-dom';
3+
import { ArrowLeft, Wallet, Shield, Loader2, AlertCircle, CheckCircle } from 'lucide-react';
4+
import { PKPEthersWallet } from '@lit-protocol/pkp-ethers';
5+
import { getClient } from '@lit-protocol/vincent-contracts-sdk';
6+
import * as Sentry from '@sentry/react';
7+
8+
import { theme } from './ui/theme';
9+
import { InfoBanner } from './ui/InfoBanner';
10+
import { ActionCard } from './ui/ActionCard';
11+
import { ConnectPageHeader } from './ui/ConnectPageHeader';
12+
import { useCanGoBack } from '@/hooks/user-dashboard/connect/useCanGoBack';
13+
import { ReadAuthInfo } from '@/hooks/user-dashboard/useAuthInfo';
14+
import { useAgentPkpForApp } from '@/hooks/user-dashboard/useAgentPkpForApp';
15+
import { litNodeClient } from '@/utils/user-dashboard/lit';
16+
import { App } from '@/types/developer-dashboard/appTypes';
17+
18+
type DeletedAppConnectProps = {
19+
appData: App;
20+
hasPermission: boolean;
21+
readAuthInfo: ReadAuthInfo;
22+
};
23+
24+
export function DeletedAppConnect({
25+
appData,
26+
hasPermission,
27+
readAuthInfo,
28+
}: DeletedAppConnectProps) {
29+
const navigate = useNavigate();
30+
const canGoBack = useCanGoBack();
31+
const { authInfo, sessionSigs } = readAuthInfo;
32+
const userAddress = authInfo?.userPKP?.ethAddress || '';
33+
34+
const [isUnpermitting, setIsUnpermitting] = useState(false);
35+
const [unpermitError, setUnpermitError] = useState<string | null>(null);
36+
const [unpermitSuccess, setUnpermitSuccess] = useState(false);
37+
38+
const { agentPKP, permittedVersion } = useAgentPkpForApp(userAddress, appData.appId);
39+
40+
const handleGoBack = () => {
41+
navigate(-1);
42+
};
43+
44+
const handleViewWallet = () => {
45+
navigate(`/user/appId/${appData.appId}/wallet`);
46+
};
47+
48+
const handleUnpermit = useCallback(async () => {
49+
if (!agentPKP || !authInfo?.userPKP || !sessionSigs) {
50+
setUnpermitError('Missing authentication information');
51+
return;
52+
}
53+
54+
if (permittedVersion === null) {
55+
setUnpermitError('App is not currently permitted');
56+
return;
57+
}
58+
59+
try {
60+
setIsUnpermitting(true);
61+
setUnpermitError(null);
62+
63+
const agentPkpWallet = new PKPEthersWallet({
64+
controllerSessionSigs: sessionSigs,
65+
pkpPubKey: authInfo.userPKP.publicKey,
66+
litNodeClient: litNodeClient,
67+
});
68+
await agentPkpWallet.init();
69+
70+
const client = getClient({ signer: agentPkpWallet });
71+
await client.unPermitApp({
72+
pkpEthAddress: agentPKP.ethAddress,
73+
appId: Number(appData.appId),
74+
appVersion: Number(permittedVersion),
75+
});
76+
77+
setUnpermitSuccess(true);
78+
// Force a full page reload to refresh all data
79+
window.location.href = '/user/apps';
80+
} catch (err) {
81+
const errorMessage = err instanceof Error ? err.message : 'Failed to revoke permissions';
82+
setUnpermitError(errorMessage);
83+
Sentry.captureException(err, {
84+
extra: {
85+
appId: appData.appId,
86+
agentPKPAddress: agentPKP?.ethAddress,
87+
permittedVersion,
88+
context: 'DeletedAppConnect.handleUnpermit',
89+
},
90+
});
91+
} finally {
92+
setIsUnpermitting(false);
93+
}
94+
}, [agentPKP, authInfo, sessionSigs, appData.appId, permittedVersion, navigate]);
95+
96+
return (
97+
<div
98+
className={`w-full max-w-md mx-auto ${theme.mainCard} border ${theme.mainCardBorder} rounded-2xl shadow-2xl overflow-hidden relative z-10 origin-center`}
99+
>
100+
{/* Header */}
101+
<ConnectPageHeader authInfo={authInfo || undefined} />
102+
103+
{/* Main Content */}
104+
<div className="px-3 sm:px-4 py-6 sm:py-8 space-y-6">
105+
{/* Status Banner */}
106+
{unpermitSuccess ? (
107+
<InfoBanner
108+
type="success"
109+
title="Permissions Revoked"
110+
message="The app no longer has access to your wallet."
111+
/>
112+
) : (
113+
<InfoBanner
114+
type="red"
115+
title="App Deleted"
116+
message="This app has been deleted by the developer and is no longer available."
117+
/>
118+
)}
119+
120+
{/* Error Message */}
121+
{unpermitError && (
122+
<div className={`p-3 rounded-lg bg-red-500/10 border border-red-500/30`}>
123+
<div className="flex items-start gap-2">
124+
<AlertCircle className="w-4 h-4 text-red-500 flex-shrink-0 mt-0.5" />
125+
<p className={`text-xs text-red-500`}>{unpermitError}</p>
126+
</div>
127+
</div>
128+
)}
129+
130+
{/* Success Message */}
131+
{unpermitSuccess && (
132+
<div className={`p-3 rounded-lg bg-green-500/10 border border-green-500/30`}>
133+
<div className="flex items-start gap-2">
134+
<CheckCircle className="w-4 h-4 text-green-500 flex-shrink-0 mt-0.5" />
135+
<p className={`text-xs text-green-500`}>Redirecting to your permitted apps...</p>
136+
</div>
137+
</div>
138+
)}
139+
140+
{!unpermitSuccess && (
141+
<>
142+
{/* Dividing line */}
143+
<div className={`border-b ${theme.cardBorder}`}></div>
144+
145+
{/* Options */}
146+
<div className="space-y-3">
147+
{/* Revoke Permissions - primary action if permitted */}
148+
{hasPermission && (
149+
<ActionCard
150+
icon={
151+
isUnpermitting ? (
152+
<Loader2
153+
className="w-4 h-4 animate-spin"
154+
style={{ color: theme.brandOrange }}
155+
/>
156+
) : (
157+
<Shield className="w-4 h-4" style={{ color: theme.brandOrange }} />
158+
)
159+
}
160+
iconBg="bg-orange-500/20"
161+
title={isUnpermitting ? 'Revoking...' : 'Revoke Permissions'}
162+
description={isUnpermitting ? '' : "Remove this app's access to your wallet"}
163+
onClick={handleUnpermit}
164+
disabled={isUnpermitting}
165+
/>
166+
)}
167+
168+
{/* View Wallet Option - only if permitted */}
169+
{hasPermission && (
170+
<ActionCard
171+
icon={<Wallet className="w-4 h-4" style={{ color: theme.brandOrange }} />}
172+
iconBg="bg-orange-500/20"
173+
title="View Wallet"
174+
description="Access your wallet to withdraw funds"
175+
onClick={handleViewWallet}
176+
disabled={isUnpermitting}
177+
/>
178+
)}
179+
180+
{/* Go Back Option */}
181+
<ActionCard
182+
icon={<ArrowLeft className="w-4 h-4 text-gray-500" />}
183+
iconBg="bg-gray-500/20"
184+
title="Go Back"
185+
description=""
186+
onClick={handleGoBack}
187+
disabled={!canGoBack || isUnpermitting}
188+
/>
189+
</div>
190+
</>
191+
)}
192+
</div>
193+
</div>
194+
);
195+
}

0 commit comments

Comments
 (0)