Skip to content

Commit 0f8f3cf

Browse files
committed
chore(dashboard): work on UI feedback
1 parent 6395a6c commit 0f8f3cf

File tree

5 files changed

+166
-58
lines changed

5 files changed

+166
-58
lines changed

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

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -99,17 +99,10 @@ export function ReturningUserConnect({
9999
message={`Your permitted version (${version}) has been disabled by the app developer. You must update your permissions to continue using this app.`}
100100
/>
101101
) : version !== appData.activeVersion ? (
102-
<>
103-
<InfoBanner
104-
type="orange"
105-
title="App Already Permitted"
106-
message="You've previously granted permissions to this app."
107-
/>
108-
<InfoBanner
109-
title="Version Update Available"
110-
message={`You're using version ${version}, but the app has updated to version ${appData.activeVersion}. Update your permissions to access the latest features.`}
111-
/>
112-
</>
102+
<InfoBanner
103+
title="Version Update Available"
104+
message={`You're using version ${version}, but the app has updated to version ${appData.activeVersion}. Update your permissions to access the latest features.`}
105+
/>
113106
) : (
114107
<InfoBanner
115108
type="orange"
@@ -183,22 +176,43 @@ export function ReturningUserConnect({
183176
</>
184177
) : versionData && !versionData.enabled ? (
185178
/* Update Version Option - Primary action when version is disabled */
186-
<ActionCard
187-
icon={<RefreshCw className="w-4 h-4 text-orange-500" />}
188-
iconBg="bg-orange-500/20"
189-
title="Update Version"
190-
description=""
191-
onClick={handleUpdateVersion}
192-
/>
179+
<>
180+
<ActionCard
181+
icon={<RefreshCw className="w-4 h-4 text-orange-500" />}
182+
iconBg="bg-orange-500/20"
183+
title="Update to Active Version"
184+
description="Required to continue using this app"
185+
onClick={handleUpdateVersion}
186+
/>
187+
<ActionCard
188+
icon={<Settings className="w-4 h-4 text-gray-500" />}
189+
iconBg="bg-gray-500/20"
190+
title="Manage App"
191+
description="View settings or unpermit this app"
192+
onClick={handleEditParameters}
193+
/>
194+
</>
193195
) : (
194-
/* Edit Parameters Option - Show when version is enabled */
195-
<ActionCard
196-
icon={<Settings className="w-4 h-4 text-gray-500" />}
197-
iconBg="bg-gray-500/20"
198-
title="Edit Permissions"
199-
description=""
200-
onClick={handleEditParameters}
201-
/>
196+
<>
197+
{/* Show Update Version button when version update is available */}
198+
{version !== appData.activeVersion && (
199+
<ActionCard
200+
icon={<RefreshCw className="w-4 h-4 text-orange-500" />}
201+
iconBg="bg-orange-500/20"
202+
title="Update to Latest Version"
203+
description=""
204+
onClick={handleUpdateVersion}
205+
/>
206+
)}
207+
{/* Edit Parameters Option - Show when version is enabled */}
208+
<ActionCard
209+
icon={<Settings className="w-4 h-4 text-gray-500" />}
210+
iconBg="bg-gray-500/20"
211+
title="Edit Permissions"
212+
description=""
213+
onClick={handleEditParameters}
214+
/>
215+
</>
202216
)}
203217

204218
{/* Continue Option - Only show if version is enabled and not both versions disabled */}

packages/apps/app-dashboard/src/components/user-dashboard/dashboard/PermittedAppsPage.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { theme, fonts } from '@/components/user-dashboard/connect/ui/theme';
77
import { Package, ChevronDown, Check, Filter } from 'lucide-react';
88
import { AgentAppPermission } from '@/utils/user-dashboard/getAgentPkps';
99
import { PermittedAppCard } from './ui/PermittedAppCard';
10+
import { VincentYieldPromotionCard } from './ui/VincentYieldPromotionCard';
1011

1112
type FilterState = 'permitted' | 'unpermitted' | 'all';
1213

@@ -17,6 +18,7 @@ type PermittedAppsPageProps = {
1718
filterState: FilterState;
1819
setFilterState: (state: FilterState) => void;
1920
appVersionsMap: Record<string, AppVersion[]>;
21+
hasVincentYieldPKP: boolean;
2022
};
2123

2224
export function PermittedAppsPage({
@@ -26,6 +28,7 @@ export function PermittedAppsPage({
2628
unpermittedPkps,
2729
filterState,
2830
setFilterState,
31+
hasVincentYieldPKP,
2932
}: PermittedAppsPageProps) {
3033
const [showDropdown, setShowDropdown] = useState(false);
3134
const [showContent, setShowContent] = useState(false);
@@ -174,7 +177,12 @@ export function PermittedAppsPage({
174177
)}
175178
</div>
176179
</div>
177-
{apps.length === 0 ? (
180+
{apps.length === 0 && !hasVincentYieldPKP && filterState === 'permitted' ? (
181+
<div className="w-full grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-6">
182+
{/* Show only Vincent Yield card when no other apps and on permitted filter */}
183+
<VincentYieldPromotionCard index={0} />
184+
</div>
185+
) : apps.length === 0 ? (
178186
<div className="flex items-center justify-center min-h-[400px] w-full lg:mr-48">
179187
<div className="text-center max-w-md mx-auto px-6">
180188
<div
@@ -205,18 +213,25 @@ export function PermittedAppsPage({
205213
</div>
206214
) : (
207215
<div className="w-full grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-6">
216+
{/* Show Vincent Yield promotion card first if not permitted and on permitted filter */}
217+
{!hasVincentYieldPKP && filterState === 'permitted' && (
218+
<VincentYieldPromotionCard index={0} />
219+
)}
208220
{apps.map((app, index) => {
209221
const permittedPermission = permittedPkps.find((p) => p.appId === app.appId);
210222
const unpermittedPermission = unpermittedPkps.find((p) => p.appId === app.appId);
211223
const permission = permittedPermission || unpermittedPermission;
212224
const isUnpermitted = !!unpermittedPermission && !permittedPermission;
225+
// Adjust index for animation delay if Vincent Yield card is shown
226+
const cardIndex =
227+
!hasVincentYieldPKP && filterState === 'permitted' ? index + 1 : index;
213228
return (
214229
<PermittedAppCard
215230
key={app.appId}
216231
app={app}
217232
permission={permission}
218233
isUnpermitted={isUnpermitted}
219-
index={index}
234+
index={cardIndex}
220235
appVersionsMap={appVersionsMap}
221236
/>
222237
);

packages/apps/app-dashboard/src/components/user-dashboard/dashboard/PermittedAppsWrapper.tsx

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { reactClient as vincentApiClient } from '@lit-protocol/vincent-registry-
44
import { useReadAuthInfo } from '@/hooks/user-dashboard/useAuthInfo';
55
import { AuthenticationErrorScreen } from '../connect/AuthenticationErrorScreen';
66
import { GeneralErrorScreen } from '@/components/user-dashboard/connect/GeneralErrorScreen';
7-
import { VincentYieldModal } from '../landing/VincentYieldModal';
87
import { ConnectToVincentYieldModal } from '../landing/ConnectToVincentYieldModal';
98
import { env } from '@/config/env';
109
import { useAllAgentApps } from '@/hooks/user-dashboard/useAllAgentApps';
@@ -18,8 +17,6 @@ export function PermittedAppsWrapper() {
1817
const { authInfo, sessionSigs, isProcessing, error } = readAuthInfo;
1918

2019
const userAddress = authInfo?.userPKP?.ethAddress || '';
21-
const [showVincentYieldModal, setShowVincentYieldModal] = useState(false);
22-
const [hasUserDismissedModal, setHasUserDismissedModal] = useState(false);
2320
const [showConnectModal, setShowConnectModal] = useState(false);
2421
const [filterState, setFilterState] = useState<FilterState>('permitted');
2522

@@ -78,13 +75,6 @@ export function PermittedAppsWrapper() {
7875
// Find PKPs with appId = -1 (unconnected PKPs)
7976
const unconnectedPKP = permittedPkps.find((pkp) => pkp.appId === -1);
8077

81-
// Show Vincent Yield modal when user has no Vincent Yield PKP
82-
React.useEffect(() => {
83-
if (isUserAuthed && !showVincentYieldModal && !hasUserDismissedModal && !vincentYieldPKP) {
84-
setShowVincentYieldModal(true);
85-
}
86-
}, [isUserAuthed, showVincentYieldModal, hasUserDismissedModal, vincentYieldPKP]);
87-
8878
// Show connect modal for unconnected PKPs (but not when there are no PKPs at all)
8979
React.useEffect(() => {
9080
if (
@@ -137,15 +127,8 @@ export function PermittedAppsWrapper() {
137127
filterState={filterState}
138128
setFilterState={setFilterState}
139129
appVersionsMap={appVersionsMap}
130+
hasVincentYieldPKP={!!vincentYieldPKP}
140131
/>
141-
{showVincentYieldModal && !vincentYieldPKP && (
142-
<VincentYieldModal
143-
onClose={() => {
144-
setShowVincentYieldModal(false);
145-
setHasUserDismissedModal(true);
146-
}}
147-
/>
148-
)}
149132
{showConnectModal && unconnectedPKP && (
150133
<ConnectToVincentYieldModal agentPKP={unconnectedPKP.pkp} />
151134
)}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { motion } from 'framer-motion';
2+
import { theme, fonts } from '@/components/user-dashboard/connect/ui/theme';
3+
import { Card, CardContent } from '@/components/shared/ui/card';
4+
import { ExternalLink, Sparkles } from 'lucide-react';
5+
6+
type VincentYieldPromotionCardProps = {
7+
index?: number;
8+
};
9+
10+
export function VincentYieldPromotionCard({ index = 0 }: VincentYieldPromotionCardProps) {
11+
return (
12+
<motion.div
13+
initial={{ opacity: 0, y: 20 }}
14+
animate={{ opacity: 1, y: 0 }}
15+
transition={{ delay: index * 0.1 }}
16+
className="w-full"
17+
>
18+
<Card
19+
className={`py-0 gap-0 backdrop-blur-xl border-2 transition-all duration-200 hover:shadow-lg w-full flex flex-col overflow-hidden relative`}
20+
style={{
21+
borderColor: theme.brandOrange,
22+
backgroundColor: `${theme.mainCard}`,
23+
backgroundImage: `linear-gradient(135deg, ${theme.mainCard} 0%, rgba(224, 90, 26, 0.05) 100%)`,
24+
}}
25+
>
26+
{/* Promotional badge */}
27+
<div
28+
className="absolute top-3 right-3 px-2 py-1 rounded-md text-xs font-bold tracking-wide"
29+
style={{
30+
backgroundColor: theme.brandOrange,
31+
color: 'white',
32+
...fonts.heading,
33+
}}
34+
>
35+
NEW
36+
</div>
37+
38+
<CardContent className="p-4 flex flex-col gap-3">
39+
{/* Top section - Logo and Title */}
40+
<div className="flex items-start justify-between gap-3">
41+
<div className="flex items-center gap-3 min-w-0 flex-1">
42+
<div
43+
className="w-16 h-16 rounded-lg flex items-center justify-center flex-shrink-0"
44+
style={{
45+
backgroundColor: theme.brandOrange,
46+
}}
47+
>
48+
<Sparkles className="w-8 h-8 text-white" />
49+
</div>
50+
<div className="flex flex-col justify-center min-w-0 flex-1">
51+
<h3
52+
className={`text-base font-semibold leading-tight ${theme.text}`}
53+
style={fonts.heading}
54+
>
55+
Vincent Yield
56+
</h3>
57+
<span className={`text-sm leading-tight`} style={{ color: theme.brandOrange }}>
58+
Earn on your USDC
59+
</span>
60+
</div>
61+
</div>
62+
</div>
63+
64+
{/* Description */}
65+
<div className={`text-xs ${theme.textMuted} leading-relaxed`} style={fonts.body}>
66+
Vincent powers the next wave of user-owned finance and agent-driven automation for Web3.
67+
Deposit at least 50 USDC on Base Mainnet to get started.
68+
</div>
69+
70+
{/* Bottom section - Button */}
71+
<div className="flex flex-col gap-2 w-full">
72+
<button
73+
onClick={() => {
74+
window.open('https://yield.heyvincent.ai', '_blank');
75+
}}
76+
className="w-full flex items-center justify-center gap-1.5 px-3 py-2 rounded-lg text-xs font-medium transition-colors text-white"
77+
style={{
78+
...fonts.heading,
79+
backgroundColor: theme.brandOrange,
80+
}}
81+
onMouseEnter={(e) => {
82+
e.currentTarget.style.backgroundColor = theme.brandOrangeDarker;
83+
}}
84+
onMouseLeave={(e) => {
85+
e.currentTarget.style.backgroundColor = theme.brandOrange;
86+
}}
87+
>
88+
<ExternalLink className="w-4 h-4 flex-shrink-0 -mt-px" />
89+
<span className="leading-none">Visit Vincent Yield</span>
90+
</button>
91+
</div>
92+
</CardContent>
93+
</Card>
94+
</motion.div>
95+
);
96+
}

packages/apps/app-dashboard/src/utils/user-dashboard/getAppVersionStatus.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,15 @@ export function getAppVersionStatus({
6363
detailText = 'Your permissions are current';
6464
}
6565

66+
// Check if both versions are disabled (highest priority - worst case)
67+
if (permittedVersionEnabled === false && activeVersionEnabled === false) {
68+
warningType = 'red';
69+
statusText = 'Versions Disabled';
70+
statusColor = 'text-red-600 dark:text-red-400';
71+
bgColor = 'bg-red-500/10';
72+
detailText = 'Both your version and the latest version are disabled';
73+
}
74+
6675
// Check for version mismatch OR if permitted version is disabled but active is enabled
6776
// This gives priority to updating when an update can fix the problem
6877
if (hasVersionMismatch || (permittedVersionEnabled === false && activeVersionEnabled === true)) {
@@ -72,11 +81,11 @@ export function getAppVersionStatus({
7281
bgColor = 'bg-yellow-500/10';
7382

7483
if (hasVersionMismatch && permittedVersionEnabled === false) {
75-
detailText = 'Your version is disabled. Update to the latest version';
84+
detailText = 'Your version is disabled. Click here to update to the latest version!';
7685
} else if (permittedVersionEnabled === false) {
77-
detailText = 'Your version is disabled. Please update';
86+
detailText = 'Your version is disabled. Click here to update to the latest version!';
7887
} else {
79-
detailText = 'A newer version is available';
88+
detailText = 'A newer version is available. Click here to update!';
8089
}
8190
}
8291

@@ -89,15 +98,6 @@ export function getAppVersionStatus({
8998
detailText = 'The latest version has been disabled';
9099
}
91100

92-
// Check if both versions are disabled (highest priority - worst case)
93-
if (permittedVersionEnabled === false && activeVersionEnabled === false) {
94-
warningType = 'red';
95-
statusText = 'Versions Disabled';
96-
statusColor = 'text-red-600 dark:text-red-400';
97-
bgColor = 'bg-red-500/10';
98-
detailText = 'Both your version and the latest version are disabled';
99-
}
100-
101101
const result: VersionStatusResult = {
102102
warningType,
103103
statusText,

0 commit comments

Comments
 (0)