Skip to content

Commit 7f5b0b5

Browse files
committed
refactor: simplify garden joining logic and remove DevConnect references
- Updated Login and ProfileAccount components to remove DevConnect-related logic, streamlining the user experience. - Refactored useAutoJoinRootGarden hook to eliminate unnecessary checks for DevConnect membership. - Adjusted deployment scripts and contract configurations to support open joining based on garden settings rather than token ID. - Enhanced error handling and user feedback in garden joining processes, ensuring clearer communication of membership status.
1 parent 65be36b commit 7f5b0b5

File tree

16 files changed

+140
-250
lines changed

16 files changed

+140
-250
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
"npx @biomejs/biome format --write"
8181
],
8282
"*.sol": [
83-
"npx forge fmt"
83+
"forge fmt"
8484
]
8585
},
8686
"workspaces": [

packages/client/src/views/Login/index.tsx

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,7 @@ export function Login() {
1616
const [loadingMessage, setLoadingMessage] = useState<string | undefined>(undefined);
1717
const [loginError, setLoginError] = useState<string | null>(null);
1818

19-
// Check if DevConnect is enabled via environment variable
20-
const isDevConnectEnabled = import.meta.env.VITE_DEVCONNECT === "true";
21-
22-
const {
23-
joinGarden,
24-
isLoading: isJoiningGarden,
25-
isGardener: _isGardener,
26-
devConnect,
27-
} = useAutoJoinRootGarden();
19+
const { joinGarden, isLoading: isJoiningGarden } = useAutoJoinRootGarden();
2820

2921
// Check if we're on a nested route (like /login/recover)
3022
const isNestedRoute = location.pathname !== "/login";
@@ -100,25 +92,6 @@ export function Login() {
10092
localStorage.setItem(onboardingKey, "true");
10193
}
10294
}
103-
104-
// 2. DevConnect Join (New)
105-
if (isDevConnectEnabled && devConnect.isEnabled && !devConnect.isMember) {
106-
// Check local storage to avoid re-prompting if they skipped or are pending
107-
const dcKey = `greengoods_devconnect_onboarded:${session.address.toLowerCase()}`;
108-
const isDcOnboarded = localStorage.getItem(dcKey) === "true";
109-
110-
if (!isDcOnboarded) {
111-
setLoadingState("joining-garden"); // Re-use loading state
112-
setLoadingMessage("Joining DevConnect Garden...");
113-
try {
114-
await devConnect.join(session);
115-
toastService.success({ title: "Joined DevConnect!", context: "devconnect" });
116-
} catch (e) {
117-
console.error("DevConnect join failed", e);
118-
// Non-blocking failure
119-
}
120-
}
121-
}
12295
} catch (err) {
12396
setLoadingState(null);
12497
console.error("Passkey authentication failed", err);

packages/client/src/views/Profile/Account.tsx

Lines changed: 37 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@ import { toastService } from "@green-goods/shared";
22
import {
33
checkGardenOpenJoining,
44
isGardenMember,
5-
useAutoJoinRootGarden,
65
useEnsName,
76
useGardens,
87
useJoinGarden,
98
} from "@green-goods/shared/hooks";
109
import { useClientAuth } from "@green-goods/shared/providers";
1110
import { type Locale, useApp } from "@green-goods/shared/providers/app";
12-
import { capitalize, parseAndFormatError } from "@green-goods/shared/utils";
11+
import { capitalize, isAlreadyGardenerError, parseAndFormatError } from "@green-goods/shared/utils";
1312
import {
1413
RiCheckLine,
1514
RiEarthFill,
@@ -51,23 +50,17 @@ export const ProfileAccount: React.FC<ProfileAccountProps> = () => {
5150
const { locale, switchLanguage, availableLocales } = useApp();
5251
const intl = useIntl();
5352

54-
// Check if DevConnect is enabled via environment variable
55-
const isDevConnectEnabled = import.meta.env.VITE_DEVCONNECT === "true";
56-
5753
// Fetch all gardens
5854
const { data: gardens = [], isLoading: gardensLoading } = useGardens();
5955

6056
// Join garden hook
6157
const { joinGarden, isJoining, joiningGardenId } = useJoinGarden();
6258

63-
// Root garden membership check (for DevConnect and legacy support)
64-
const { devConnect } = useAutoJoinRootGarden();
65-
66-
// Track which gardens have openJoining enabled
59+
// Track which gardens have openJoining enabled (for join button state)
6760
const [openGardensMap, setOpenGardensMap] = useState<Map<string, boolean>>(new Map());
6861
const [checkingOpenJoining, setCheckingOpenJoining] = useState(false);
6962

70-
// Check openJoining status for all gardens (cached)
63+
// Check openJoining status for all gardens
7164
useEffect(() => {
7265
const checkOpenJoiningStatus = async () => {
7366
if (gardens.length === 0) return;
@@ -90,15 +83,15 @@ export const ProfileAccount: React.FC<ProfileAccountProps> = () => {
9083
checkOpenJoiningStatus();
9184
}, [gardens]);
9285

93-
// Consolidated list: all open gardens with membership status
94-
const openGardens = useMemo(() => {
86+
// Show only open gardens or gardens where user is a member
87+
const allGardens = useMemo(() => {
9588
if (!primaryAddress || !gardens.length) return [];
9689

9790
return gardens
9891
.filter((garden) => {
99-
// Show gardens that are either open for joining OR user is already a member
10092
const isOpen = openGardensMap.get(garden.id) === true;
10193
const isMember = isGardenMember(primaryAddress, garden.gardeners, garden.operators);
94+
// Only show gardens that are open OR user is already a member
10295
return isOpen || isMember;
10396
})
10497
.map((garden) => ({
@@ -126,6 +119,25 @@ export const ProfileAccount: React.FC<ProfileAccountProps> = () => {
126119
context: "joinGarden",
127120
});
128121
} catch (err) {
122+
// Handle "already a member" as success, not error
123+
if (isAlreadyGardenerError(err)) {
124+
toastService.success({
125+
title: intl.formatMessage({
126+
id: "app.account.alreadyMember",
127+
defaultMessage: "Already a member",
128+
}),
129+
message: intl.formatMessage(
130+
{
131+
id: "app.account.alreadyMemberMessage",
132+
defaultMessage: "You're already a member of {gardenName}",
133+
},
134+
{ gardenName: garden.name }
135+
),
136+
context: "joinGarden",
137+
});
138+
return;
139+
}
140+
129141
console.error(`Failed to join garden ${garden.id}`, err);
130142

131143
const { title, message } = parseAndFormatError(err);
@@ -139,15 +151,6 @@ export const ProfileAccount: React.FC<ProfileAccountProps> = () => {
139151
}
140152
};
141153

142-
const handleJoinDevConnect = async () => {
143-
try {
144-
await devConnect.join();
145-
toastService.success({ title: "Joined DevConnect", context: "account" });
146-
} catch (err) {
147-
toastService.error({ title: "Failed to join", error: err, context: "account" });
148-
}
149-
};
150-
151154
const handleLogout = async () => {
152155
try {
153156
await signOut();
@@ -242,13 +245,13 @@ export const ProfileAccount: React.FC<ProfileAccountProps> = () => {
242245
</Card>
243246
))}
244247

245-
{/* Open Gardens Section - Consolidated list with membership status */}
248+
{/* Gardens Section - All available gardens with membership status */}
246249
{primaryAddress && (
247250
<>
248251
<h5 className="text-label-md text-slate-900">
249252
{intl.formatMessage({
250-
id: "app.profile.openGardens",
251-
defaultMessage: "Open Gardens",
253+
id: "app.profile.gardens",
254+
defaultMessage: "Gardens",
252255
})}
253256
</h5>
254257

@@ -263,9 +266,9 @@ export const ProfileAccount: React.FC<ProfileAccountProps> = () => {
263266
</span>
264267
</div>
265268
</Card>
266-
) : openGardens.length > 0 ? (
269+
) : allGardens.length > 0 ? (
267270
<div className="flex flex-col gap-2">
268-
{openGardens.map((garden) => {
271+
{allGardens.map((garden) => {
269272
const isJoiningThis = isJoining && joiningGardenId === garden.id;
270273

271274
return (
@@ -303,18 +306,11 @@ export const ProfileAccount: React.FC<ProfileAccountProps> = () => {
303306
mode="filled"
304307
size="xsmall"
305308
onClick={() => handleJoinGarden(garden)}
306-
label={
307-
isJoiningThis
308-
? intl.formatMessage({
309-
id: "app.profile.joining",
310-
defaultMessage: "Joining...",
311-
})
312-
: intl.formatMessage({
313-
id: "app.profile.join",
314-
defaultMessage: "Join",
315-
})
316-
}
317-
disabled={isJoining}
309+
label={intl.formatMessage({
310+
id: "app.profile.join",
311+
defaultMessage: "Join",
312+
})}
313+
disabled={isJoiningThis}
318314
className="shrink-0"
319315
/>
320316
)}
@@ -329,26 +325,13 @@ export const ProfileAccount: React.FC<ProfileAccountProps> = () => {
329325
<RiPlantLine className="w-5 text-slate-400" />
330326
<span className="text-sm text-slate-500">
331327
{intl.formatMessage({
332-
id: "app.profile.noJoinableGardens",
333-
defaultMessage: "No open gardens available to join",
328+
id: "app.profile.noGardens",
329+
defaultMessage: "No gardens available",
334330
})}
335331
</span>
336332
</div>
337333
</Card>
338334
)}
339-
340-
{/* DevConnect Button - Optional */}
341-
{isDevConnectEnabled && devConnect.isEnabled && !devConnect.isMember && (
342-
<Button
343-
variant="primary"
344-
mode="filled"
345-
onClick={handleJoinDevConnect}
346-
label={devConnect.isLoading ? "Joining..." : "Join DevConnect"}
347-
leadingIcon={<RiPlantLine className="w-4" />}
348-
disabled={devConnect.isLoading}
349-
className="w-full"
350-
/>
351-
)}
352335
</>
353336
)}
354337

packages/client/src/vite-env.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ interface ImportMetaEnv {
55
readonly VITE_WALLET_CONNECT_PROJECT_ID: string;
66
readonly VITE_ALCHEMY_API_KEY: string;
77
readonly VITE_ENVIO_INDEXER_URL: string;
8-
readonly VITE_DEVCONNECT?: string;
98
}
109

1110
interface ImportMeta {

packages/contracts/script/Deploy.s.sol

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ contract Deploy is Script, DeploymentBase {
145145
abi.decode(vm.parseJson(gardensJson, string.concat(basePath, ".bannerImage")), (string));
146146

147147
string memory metadata = "";
148-
try vm.parseJson(gardensJson, string.concat(basePath, ".metadata")) returns (bytes memory metadataBytes) {
148+
try vm.parseJson(gardensJson, string.concat(basePath, ".metadata")) returns (bytes memory metadataBytes)
149+
{
149150
metadata = abi.decode(metadataBytes, (string));
150151
} catch {}
151152

@@ -168,6 +169,7 @@ contract Deploy is Script, DeploymentBase {
168169
location: location,
169170
bannerImage: bannerImage,
170171
metadata: metadata,
172+
openJoining: openJoining,
171173
gardeners: gardeners,
172174
gardenOperators: operators
173175
});
@@ -176,11 +178,6 @@ contract Deploy is Script, DeploymentBase {
176178
gardenAddresses.push(gardenAddress);
177179
gardenTokenIds.push(i + 1);
178180

179-
// Only the root garden gets open joining automatically from tokenId == 1. Others require operator call.
180-
if (openJoining && GardenAccount(payable(gardenAddress)).gardenOperators(msg.sender)) {
181-
GardenAccount(payable(gardenAddress)).setOpenJoining(true);
182-
}
183-
184181
console.log("Garden created");
185182
console.log(" name", name);
186183
console.log(" tokenId", i + 1);
@@ -258,7 +255,7 @@ contract Deploy is Script, DeploymentBase {
258255
}
259256

260257
/// @notice Handle IPFS upload mismatch scenarios
261-
function _handleIPFSMismatch(uint256 expectedCount, uint256 /* actualCount */)
258+
function _handleIPFSMismatch(uint256 expectedCount, uint256 /* actualCount */ )
262259
internal
263260
view
264261
returns (string[] memory)

packages/contracts/script/DeployAdditionalGardens.s.sol

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.25;
33

4-
import { Script } from "forge-std/Script.sol";
5-
import { console } from "forge-std/console.sol";
4+
import {Script} from "forge-std/Script.sol";
5+
import {console} from "forge-std/console.sol";
66

7-
import { GardenToken } from "../src/tokens/Garden.sol";
8-
import { GardenAccount } from "../src/accounts/Garden.sol";
7+
import {GardenToken} from "../src/tokens/Garden.sol";
8+
import {GardenAccount} from "../src/accounts/Garden.sol";
99

1010
/**
1111
* @title Deploy Additional Gardens
@@ -83,9 +83,7 @@ contract DeployAdditionalGardens is Script {
8383
string memory json,
8484
string memory basePath,
8585
uint256 index
86-
)
87-
internal
88-
{
86+
) internal {
8987
console.log("\n--- Deploying Garden", index, "---");
9088

9189
// Parse garden config
@@ -119,31 +117,23 @@ contract DeployAdditionalGardens is Script {
119117
console.log("Gardeners:", gardeners.length);
120118
console.log("Operators:", operators.length);
121119

122-
// Mint the garden
120+
// Mint the garden with openJoining from config
123121
GardenToken.GardenConfig memory config = GardenToken.GardenConfig({
124122
communityToken: communityToken,
125123
name: name,
126124
description: description,
127125
location: location,
128126
bannerImage: bannerImage,
129127
metadata: metadata,
128+
openJoining: openJoining,
130129
gardeners: gardeners,
131130
gardenOperators: operators
132131
});
133132
address gardenAddress = gardenToken.mintGarden(config);
134133

135134
console.log("Garden Address:", gardenAddress);
136-
137-
// Set openJoining if specified in config
138135
if (openJoining) {
139-
GardenAccount garden = GardenAccount(payable(gardenAddress));
140-
// Check if caller is operator before trying to set
141-
if (garden.gardenOperators(msg.sender)) {
142-
garden.setOpenJoining(true);
143-
console.log("Open joining enabled");
144-
} else {
145-
console.log("WARNING: Cannot enable open joining - caller not an operator");
146-
}
136+
console.log("Open joining: enabled");
147137
}
148138
}
149139
}

0 commit comments

Comments
 (0)