Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions frontend/components/group/group-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
Clock,
UserPlus,
Trash2,
LogOut,
} from "lucide-react";
import { useStellar } from "@/components/web3-provider";
import {
Expand All @@ -31,6 +32,7 @@ import {
useUnpausePool,
useAddPoolMember,
useRemovePoolMember,
useLeavePool,
fetchRotationalState,
fetchPoolMembers,
} from "@/hooks/useJointSaveContracts";
Expand Down Expand Up @@ -113,6 +115,7 @@ export function GroupActions({
const [members, setMembers] = useState<string[]>([]);
const [newMember, setNewMember] = useState("");
const [memberToRemove, setMemberToRemove] = useState<string | null>(null);
const [showLeaveDialog, setShowLeaveDialog] = useState(false);
const isPending = !poolAddress || poolAddress === "pending_deployment";
// Token display metadata (persisted on the pool row; defaults to native XLM)
const tokenSymbol: string = poolData?.token_symbol ?? "XLM";
Expand Down Expand Up @@ -160,6 +163,7 @@ export function GroupActions({
const unpausePool = useUnpausePool(poolAddress);
const addPoolMember = useAddPoolMember(poolAddress);
const removePoolMember = useRemovePoolMember(poolAddress);
const leavePool = useLeavePool(poolAddress);

const { optimisticState, registerOptimistic, updateTxHash, markFailed } =
useOptimisticTransactions(poolAddress);
Expand Down Expand Up @@ -848,6 +852,23 @@ export function GroupActions({
</div>
)}

{!isAdmin && address && !isPending && (
<div className="border-t border-border pt-6 space-y-3">
<p className="text-xs text-muted-foreground font-medium">
Leave Pool
</p>
<Button
variant="outline"
className="w-full bg-transparent text-destructive border-destructive/50 hover:bg-destructive/10"
onClick={() => setShowLeaveDialog(true)}
disabled={leavePool.isLoading || isPaused}
>
<LogOut className="mr-2 h-4 w-4" />
Leave Pool
</Button>
</div>
)}

<div className="border-t border-border pt-6">
<p className="text-xs text-muted-foreground mb-2">
Your Stellar address
Expand Down Expand Up @@ -947,6 +968,64 @@ export function GroupActions({
</DialogContent>
</Dialog>

<Dialog
open={showLeaveDialog}
onOpenChange={(open) => {
if (!open) setShowLeaveDialog(false);
}}
>
<DialogContent className="sm:max-w-[425px] bg-background border border-border">
<DialogHeader>
<DialogTitle>Leave this pool?</DialogTitle>
<DialogDescription>
{poolType === "rotational" &&
"You will lose your position in the rotation. This action cannot be undone."}
{poolType === "target" &&
"Your deposited balance will be refunded before the target is reached."}
{poolType === "flexible" &&
"Your current balance will be refunded to your wallet."}
</DialogDescription>
</DialogHeader>
<DialogFooter className="gap-2 sm:gap-0">
<Button
variant="outline"
onClick={() => setShowLeaveDialog(false)}
disabled={leavePool.isLoading}
>
Cancel
</Button>
<Button
variant="destructive"
onClick={async () => {
setError("");
setSuccessMsg("");
try {
const txHash = await leavePool.leavePool();
if (txHash && address) {
await logActivity(groupId, "member_left", address, null, txHash);
setSuccessMsg("You have left the pool.");
setShowLeaveDialog(false);
}
} catch (e: any) {
setError(e.message || "Transaction failed");
setShowLeaveDialog(false);
}
}}
disabled={leavePool.isLoading}
>
{leavePool.isLoading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Leaving...
</>
) : (
"Leave Pool"
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>

<Dialog
open={!!memberToRemove}
onOpenChange={(open) => {
Expand Down
30 changes: 30 additions & 0 deletions frontend/hooks/useJointSaveContracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1205,6 +1205,36 @@ export function useRemovePoolMember(contractId: string) {
return { removeMember, isLoading }
}

export function useLeavePool(contractId: string) {
const { kit, address } = useStellar()
const [isLoading, setIsLoading] = useState(false)

const leavePool = async (): Promise<string | undefined> => {
if (!kit || !address || !contractId) return
setIsLoading(true)
try {
const account = await getRpc().getAccount(address)
const tx = new TransactionBuilder(account, {
fee: BASE_FEE,
networkPassphrase: STELLAR_NETWORK_PASSPHRASE,
})
.addOperation(
new Contract(normalizeId(contractId)).call(
"leave_pool",
addressVal(address)
)
)
.setTimeout(TX_TIMEOUT)
.build()
return await submitTx(kit, tx)
} finally {
setIsLoading(false)
}
}

return { leavePool, isLoading }
}

export function usePausePool(contractId: string) {
const { kit, address } = useStellar()
const [isLoading, setIsLoading] = useState(false)
Expand Down
Loading
Loading