Skip to content

Commit bd46630

Browse files
authored
Merge pull request Xoulomon#526 from Dami24-hub/feature/create-group-form-439
feat: wire CreateGroupPage to Soroban contractClient with wallet auth…
2 parents b340fd6 + 45182f8 commit bd46630

1 file changed

Lines changed: 116 additions & 72 deletions

File tree

Lines changed: 116 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,143 @@
11
import { useEffect, useState } from 'react';
22
import { useNavigate } from 'react-router-dom';
3-
import { Stack, Typography } from '@mui/material';
3+
import { Stack, Typography, Alert, Box } from '@mui/material';
44
import { AppCard, AppLayout } from '../ui';
55
import { CreateGroupForm } from '../components/CreateGroupForm';
6-
import { createGroup } from '../utils/groupApi';
6+
import { ToastProvider } from '../components/Toast/ToastProvider';
7+
import { useToast } from '../components/Toast/useToast';
8+
import { useWallet } from '../hooks/useWallet';
9+
import { createGroup, parseContractError } from '../lib/contractClient';
710
import type { GroupData } from '../utils/groupApi';
811
import { ROUTES, buildRoute } from '../routing/constants';
912

1013
type SubmitStatus = 'idle' | 'loading' | 'success' | 'error';
1114

12-
interface PageState {
13-
status: SubmitStatus;
14-
groupId: string | null;
15-
errorMessage: string | null;
16-
groupName: string | null;
17-
}
18-
19-
export default function CreateGroupPage() {
15+
function CreateGroupContent() {
2016
const navigate = useNavigate();
21-
const [pageState, setPageState] = useState<PageState>({
22-
status: 'idle',
23-
groupId: null,
24-
errorMessage: null,
25-
groupName: null,
26-
});
27-
28-
// Task 10: redirect after success
29-
useEffect(() => {
30-
if (pageState.status !== 'success') return;
31-
const timer = setTimeout(() => {
32-
if (pageState.groupId) {
33-
navigate(buildRoute.groupDetail(pageState.groupId));
34-
} else {
35-
navigate(ROUTES.GROUPS);
36-
}
37-
}, 2000);
38-
return () => clearTimeout(timer);
39-
}, [pageState.status, pageState.groupId, navigate]);
17+
const { addToast } = useToast();
18+
const { status: walletStatus, activeAddress, connect } = useWallet();
4019

41-
const handleCancel = () => {
42-
navigate(ROUTES.GROUPS);
43-
};
20+
const [submitStatus, setSubmitStatus] = useState<SubmitStatus>('idle');
21+
const [errorMessage, setErrorMessage] = useState<string | null>(null);
22+
const [createdGroupId, setCreatedGroupId] = useState<string | null>(null);
23+
24+
// Redirect to group detail after successful creation
25+
useEffect(() => {
26+
if (submitStatus !== 'success') return;
27+
const t = setTimeout(() => {
28+
navigate(createdGroupId ? buildRoute.groupDetail(createdGroupId) : ROUTES.GROUPS);
29+
}, 2500);
30+
return () => clearTimeout(t);
31+
}, [submitStatus, createdGroupId, navigate]);
4432

4533
const handleSubmit = async (data: GroupData) => {
46-
setPageState(prev => ({ ...prev, status: 'loading', errorMessage: null }));
34+
// Wallet must be connected
35+
if (walletStatus !== 'connected' || !activeAddress) {
36+
setErrorMessage('Please connect your Freighter wallet before creating a group.');
37+
return;
38+
}
39+
40+
setSubmitStatus('loading');
41+
setErrorMessage(null);
42+
4743
try {
48-
const groupId = await createGroup(data);
49-
setPageState({
50-
status: 'success',
51-
groupId,
52-
errorMessage: null,
53-
groupName: data.name,
44+
const groupId = await createGroup({
45+
creator: activeAddress,
46+
contributionAmount: BigInt(data.contribution_amount), // already in stroops
47+
cycleDuration: BigInt(data.cycle_duration),
48+
maxMembers: data.max_members,
49+
});
50+
51+
const groupIdStr = groupId.toString();
52+
setCreatedGroupId(groupIdStr);
53+
setSubmitStatus('success');
54+
55+
addToast({
56+
message: `Group "${data.name}" created! Group ID: ${groupIdStr}`,
57+
type: 'success',
58+
duration: 6000,
5459
});
5560
} catch (err) {
56-
const errorMessage =
57-
err instanceof Error && err.message
58-
? err.message
59-
: 'Failed to create group. Please try again.';
60-
setPageState(prev => ({ ...prev, status: 'error', errorMessage }));
61+
const contractErr = parseContractError(err);
62+
63+
// Map known rejection/funds errors to friendly messages
64+
let msg = contractErr.message;
65+
if (msg.toLowerCase().includes('user declined') || msg.toLowerCase().includes('rejected')) {
66+
msg = 'Transaction rejected. You cancelled the signing request in Freighter.';
67+
} else if (msg.toLowerCase().includes('insufficient')) {
68+
msg = 'Insufficient funds. Please ensure your wallet has enough XLM to cover the transaction fee.';
69+
}
70+
71+
setErrorMessage(msg);
72+
setSubmitStatus('error');
73+
addToast({ message: msg, type: 'error', duration: 6000 });
6174
}
6275
};
6376

77+
const isWalletConnected = walletStatus === 'connected' && Boolean(activeAddress);
78+
6479
return (
65-
<AppLayout
66-
title="Create Group"
67-
subtitle="Set up your savings circle"
68-
footerText="Stellar Save - Built for transparent, on-chain savings"
69-
>
70-
<AppCard>
71-
<Stack spacing={2}>
72-
{/* aria-live region for status announcements */}
73-
<div aria-live="polite" aria-atomic="true">
74-
{pageState.status === 'success' && (
75-
<Typography color="success.main">
76-
Group created successfully! Redirecting...
77-
</Typography>
78-
)}
79-
{pageState.status === 'error' && pageState.errorMessage && (
80-
<Typography color="error.main">{pageState.errorMessage}</Typography>
81-
)}
82-
</div>
80+
<AppCard>
81+
<Stack spacing={3}>
82+
{/* Wallet connection warning */}
83+
{!isWalletConnected && (
84+
<Alert
85+
severity="warning"
86+
action={
87+
<Box
88+
component="button"
89+
onClick={connect}
90+
sx={{ cursor: 'pointer', fontWeight: 'bold', background: 'none', border: 'none', color: 'warning.dark', fontSize: '0.85rem' }}
91+
>
92+
Connect Wallet
93+
</Box>
94+
}
95+
>
96+
Connect your Freighter wallet to deploy a group on-chain.
97+
</Alert>
98+
)}
8399

84-
{pageState.status === 'success' ? (
85-
<Typography variant="h6">
86-
"{pageState.groupName}" has been created! You will be redirected shortly.
100+
{/* Success state */}
101+
{submitStatus === 'success' ? (
102+
<Stack spacing={1} alignItems="center" sx={{ py: 4 }}>
103+
<Typography variant="h5" fontWeight="bold" color="success.main">
104+
Group Created!
105+
</Typography>
106+
<Typography variant="body2" color="text.secondary">
107+
Redirecting to your group...
87108
</Typography>
88-
) : (
109+
</Stack>
110+
) : (
111+
<>
112+
{/* Inline error (also shown as toast) */}
113+
{submitStatus === 'error' && errorMessage && (
114+
<Alert severity="error" onClose={() => setErrorMessage(null)}>
115+
{errorMessage}
116+
</Alert>
117+
)}
118+
89119
<CreateGroupForm
90120
onSubmit={handleSubmit}
91-
onCancel={handleCancel}
92-
isSubmitting={pageState.status === 'loading'}
121+
onCancel={() => navigate(ROUTES.GROUPS)}
122+
isSubmitting={submitStatus === 'loading'}
93123
/>
94-
)}
95-
</Stack>
96-
</AppCard>
97-
</AppLayout>
124+
</>
125+
)}
126+
</Stack>
127+
</AppCard>
128+
);
129+
}
130+
131+
export default function CreateGroupPage() {
132+
return (
133+
<ToastProvider>
134+
<AppLayout
135+
title="Create Group"
136+
subtitle="Deploy a new savings circle on Stellar"
137+
footerText="Stellar Save - Built for transparent, on-chain savings"
138+
>
139+
<CreateGroupContent />
140+
</AppLayout>
141+
</ToastProvider>
98142
);
99143
}

0 commit comments

Comments
 (0)