diff --git a/src/components/OnboardingGate.tsx b/src/components/OnboardingGate.tsx index dec868cd..7a2ade43 100644 --- a/src/components/OnboardingGate.tsx +++ b/src/components/OnboardingGate.tsx @@ -553,7 +553,7 @@ interface VerifierStatementStepProps { * One header and one combined subtext sit above a borderless * {@link VerifierStatementEditor}. There's no separate publish button: the * primary button publishes the statement (when there's content) and then - * advances. A returning verifier can withdraw inline. + * advances. Withdrawing happens later from the profile's "How We Verify" card. */ function VerifierStatementStep({ onContinue, @@ -563,14 +563,12 @@ function VerifierStatementStep({ const { mutateAsync: setStatement, isPending } = useSetVerifierStatement(); const [value, setValue] = useState(''); - const [alreadyPublished, setAlreadyPublished] = useState(false); const trimmed = value.trim(); const handleContinue = useCallback(async () => { try { await setStatement(trimmed); - toast({ title: t('verifier.publishedToast') }); onContinue(); } catch (error) { toast({ @@ -581,21 +579,6 @@ function VerifierStatementStep({ } }, [setStatement, trimmed, toast, t, onContinue]); - const handleWithdraw = useCallback(async () => { - try { - await setStatement(''); - setValue(''); - setAlreadyPublished(false); - toast({ title: t('verifier.withdrawnToast') }); - } catch (error) { - toast({ - title: t('verifier.errorToast'), - description: error instanceof Error ? error.message : String(error), - variant: 'destructive', - }); - } - }, [setStatement, toast, t]); - return (
@@ -610,10 +593,6 @@ function VerifierStatementStep({ setAlreadyPublished(!!s)} - showWithdraw={alreadyPublished} - onWithdraw={handleWithdraw} - isWithdrawing={isPending} />
- - {showWithdraw && onWithdraw && ( - - )}
); } diff --git a/src/components/profile/ProfileVerifiedTab.tsx b/src/components/profile/ProfileVerifiedTab.tsx index 0416b811..c65492be 100644 --- a/src/components/profile/ProfileVerifiedTab.tsx +++ b/src/components/profile/ProfileVerifiedTab.tsx @@ -9,6 +9,7 @@ import { useVerifiedCampaigns } from '@/hooks/useVerifiedCampaigns'; interface ProfileVerifiedTabProps { pubkey: string; displayName: string; + isOwnProfile?: boolean; } /** @@ -20,14 +21,14 @@ interface ProfileVerifiedTabProps { * immediately see how the organization vets campaigns and what it * stands behind. */ -export function ProfileVerifiedTab({ pubkey, displayName }: ProfileVerifiedTabProps) { +export function ProfileVerifiedTab({ pubkey, displayName, isOwnProfile = false }: ProfileVerifiedTabProps) { const { t } = useTranslation(); const { campaigns, isLoading } = useVerifiedCampaigns(pubkey); if (isLoading && campaigns.length === 0) { return (
- +
{Array.from({ length: 3 }).map((_, i) => ( @@ -40,7 +41,7 @@ export function ProfileVerifiedTab({ pubkey, displayName }: ProfileVerifiedTabPr if (campaigns.length === 0) { return (
- +
@@ -55,7 +56,7 @@ export function ProfileVerifiedTab({ pubkey, displayName }: ProfileVerifiedTabPr return (
- +

{t('profile.verified.count', { count: campaigns.length })}

diff --git a/src/components/profile/ProfileVerifierSection.tsx b/src/components/profile/ProfileVerifierSection.tsx index bc19f696..c2176d74 100644 --- a/src/components/profile/ProfileVerifierSection.tsx +++ b/src/components/profile/ProfileVerifierSection.tsx @@ -1,12 +1,32 @@ +import { useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { Loader2, Trash2 } from 'lucide-react'; import { PolicyMarkdown } from '@/components/PolicyMarkdown'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@/components/ui/alert-dialog'; +import { Button } from '@/components/ui/button'; import { Skeleton } from '@/components/ui/skeleton'; -import { useVerifierStatement } from '@/hooks/useVerifierStatement'; +import { useToast } from '@/hooks/useToast'; +import { useSetVerifierStatement, useVerifierStatement } from '@/hooks/useVerifierStatement'; import { cn } from '@/lib/utils'; interface ProfileVerifierSectionProps { pubkey: string; + /** + * Whether the viewer owns this profile. When true, a Withdraw control is + * surfaced in the card's top-right corner (mirroring the "Edit Profile" + * affordance), letting the verifier retract their statement. + */ + isOwnProfile?: boolean; className?: string; } @@ -22,9 +42,12 @@ interface ProfileVerifierSectionProps { * * Renders nothing when the profile has no statement (or has withdrawn it). */ -export function ProfileVerifierSection({ pubkey, className }: ProfileVerifierSectionProps) { +export function ProfileVerifierSection({ pubkey, isOwnProfile = false, className }: ProfileVerifierSectionProps) { const { t } = useTranslation(); + const { toast } = useToast(); const { statement, isLoading } = useVerifierStatement(pubkey); + const { mutateAsync: setStatement, isPending } = useSetVerifierStatement(); + const [confirmOpen, setConfirmOpen] = useState(false); if (isLoading) { return ( @@ -37,14 +60,72 @@ export function ProfileVerifierSection({ pubkey, className }: ProfileVerifierSec if (!statement) return null; + const handleWithdraw = async () => { + try { + await setStatement(''); + setConfirmOpen(false); + toast({ title: t('verifier.withdrawnToast') }); + } catch (error) { + toast({ + title: t('verifier.errorToast'), + description: error instanceof Error ? error.message : String(error), + variant: 'destructive', + }); + } + }; + return (
-

- {t('verifier.howWeVerifyTitle')} -

+
+

+ {t('verifier.howWeVerifyTitle')} +

+ {isOwnProfile && ( + + )} +
+ + + + + {t('verifier.withdrawConfirmTitle')} + + {t('verifier.withdrawConfirmBody')} + + + + {t('common.cancel')} + { + e.preventDefault(); + handleWithdraw(); + }} + disabled={isPending} + className="bg-destructive text-destructive-foreground hover:bg-destructive/90" + > + {isPending && } + {t('verifier.withdraw')} + + + +
); } diff --git a/src/locales/en.json b/src/locales/en.json index 69fe1f77..4d784d12 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1514,6 +1514,8 @@ "publish": "Become a verifier", "update": "Update statement", "withdraw": "Withdraw", + "withdrawConfirmTitle": "Withdraw your verifier statement?", + "withdrawConfirmBody": "Your \"How We Verify\" statement will be removed from your profile. You can publish a new one anytime.", "loading": "Loading your statement…", "publishedToast": "Your verifier statement is live.", "withdrawnToast": "Your verifier statement has been withdrawn.", diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index 2a3e9200..97907542 100644 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -914,7 +914,7 @@ function ProfileTabContent({ } if (activeTab === 'verified') { - return ; + return ; } if (activeTab === 'campaigns') {