Skip to content

Commit 6ff3135

Browse files
authored
add selection for private spaces in connect (#344)
1 parent c792450 commit 6ff3135

2 files changed

Lines changed: 90 additions & 67 deletions

File tree

apps/connect/src/components/SpacesCard.tsx

Lines changed: 72 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,20 @@ import { Popover } from '@base-ui-components/react/popover';
77
interface SpacesCardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> {
88
spaces: (PublicSpaceData | PrivateSpaceData)[];
99
status?: 'loading' | { error: boolean | string } | undefined;
10+
selected?: Set<string>;
11+
onSelected?: (spaceId: string, selected: boolean) => void;
12+
currentAppId?: string;
1013
}
1114

12-
export function SpacesCard({ spaces, status, className, ...props }: SpacesCardProps) {
15+
export function SpacesCard({
16+
spaces,
17+
status,
18+
selected,
19+
onSelected,
20+
currentAppId,
21+
className,
22+
...props
23+
}: SpacesCardProps) {
1324
const error =
1425
typeof status === 'object' && 'error' in status
1526
? typeof status.error === 'boolean'
@@ -59,53 +70,68 @@ export function SpacesCard({ spaces, status, className, ...props }: SpacesCardPr
5970
}
6071
return (
6172
<ul className="grid-cols-auto-fill-36 grid gap-4">
62-
{spaces.map((space) => (
63-
<li key={space.id} className="group/list-item">
64-
<Popover.Root openOnHover delay={50}>
65-
<Popover.Trigger
66-
className={`
67-
group-nth-[5n]/list-item:bg-gradient-violet
68-
group-nth-[5n+1]/list-item:bg-gradient-lavender
69-
group-nth-[5n+2]/list-item:bg-gradient-aqua
70-
group-nth-[5n+3]/list-item:bg-gradient-peach
71-
group-nth-[5n+4]/list-item:bg-gradient-clearmint
72-
flex aspect-video w-full items-end overflow-clip rounded-lg px-3 py-2
73-
`}
74-
>
75-
<span className="text-sm leading-tight font-semibold">{space.name || space.id}</span>
76-
</Popover.Trigger>
77-
<Popover.Portal>
78-
<Popover.Positioner side="bottom" sideOffset={12}>
79-
<Popover.Popup className="c-popover">
80-
<Popover.Arrow className="c-popover-arrow">
81-
<ArrowSvg />
82-
</Popover.Arrow>
83-
{!('apps' in space) ? (
84-
<Popover.Title className="font-semibold">Public space</Popover.Title>
85-
) : space.apps.length === 0 ? (
86-
<Popover.Title className="font-semibold">
87-
No app has access to this private space
88-
</Popover.Title>
89-
) : (
90-
<>
73+
{spaces.map((space) => {
74+
// Determine if space is selected
75+
const isPublicSpace = !('apps' in space);
76+
const isSelected = isPublicSpace ? true : (selected?.has(space.id) ?? false);
77+
const isDisabled =
78+
!isPublicSpace && 'apps' in space && space.apps.some((app) => app.id === currentAppId);
79+
80+
return (
81+
<li key={space.id} className="group/list-item">
82+
<Popover.Root openOnHover delay={50}>
83+
<Popover.Trigger
84+
className={`
85+
group-nth-[5n]/list-item:bg-gradient-violet
86+
group-nth-[5n+1]/list-item:bg-gradient-lavender
87+
group-nth-[5n+2]/list-item:bg-gradient-aqua
88+
group-nth-[5n+3]/list-item:bg-gradient-peach
89+
group-nth-[5n+4]/list-item:bg-gradient-clearmint
90+
flex aspect-video w-full items-end overflow-clip rounded-lg px-3 py-2
91+
${isSelected ? 'ring-2 ring-primary ring-offset-2' : ''}
92+
${isDisabled ? 'ring-2 ring-primary ring-offset-2 cursor-not-allowed' : 'cursor-pointer'}
93+
`}
94+
onClick={() => {
95+
if (!isDisabled && onSelected) {
96+
onSelected(space.id, !isSelected);
97+
}
98+
}}
99+
>
100+
<span className="text-sm leading-tight font-semibold">{space.name || space.id}</span>
101+
</Popover.Trigger>
102+
<Popover.Portal>
103+
<Popover.Positioner side="bottom" sideOffset={12}>
104+
<Popover.Popup className="c-popover">
105+
<Popover.Arrow className="c-popover-arrow">
106+
<ArrowSvg />
107+
</Popover.Arrow>
108+
{!('apps' in space) ? (
109+
<Popover.Title className="font-semibold">Public space</Popover.Title>
110+
) : space.apps.length === 0 ? (
91111
<Popover.Title className="font-semibold">
92-
Apps with access to this private space
112+
No app has access to this private space
93113
</Popover.Title>
94-
<Popover.Description>
95-
<ul className="list-disc">
96-
{space.apps.map((app) => (
97-
<li key={app.id}>{app.name || app.id}</li>
98-
))}
99-
</ul>
100-
</Popover.Description>
101-
</>
102-
)}
103-
</Popover.Popup>
104-
</Popover.Positioner>
105-
</Popover.Portal>
106-
</Popover.Root>
107-
</li>
108-
))}
114+
) : (
115+
<>
116+
<Popover.Title className="font-semibold">
117+
Apps with access to this private space
118+
</Popover.Title>
119+
<Popover.Description>
120+
<ul className="list-disc">
121+
{space.apps.map((app) => (
122+
<li key={app.id}>{app.name || app.id}</li>
123+
))}
124+
</ul>
125+
</Popover.Description>
126+
</>
127+
)}
128+
</Popover.Popup>
129+
</Popover.Positioner>
130+
</Popover.Portal>
131+
</Popover.Root>
132+
</li>
133+
);
134+
})}
109135
</ul>
110136
);
111137
})()}

apps/connect/src/routes/authenticate.tsx

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { createStore } from '@xstate/store';
1111
import { useSelector } from '@xstate/store/react';
1212
import { Effect, Schema } from 'effect';
1313
import { TriangleAlert } from 'lucide-react';
14-
import { useEffect } from 'react';
14+
import { useEffect, useState } from 'react';
1515
import { createWalletClient, custom } from 'viem';
1616
import { privateKeyToAccount } from 'viem/accounts';
1717

@@ -140,6 +140,7 @@ function AuthenticateComponent() {
140140
const embeddedWallet = wallets.find((wallet) => wallet.walletClientType === 'privy') || wallets[0];
141141

142142
const state = useSelector(componentStore, (state) => state.context);
143+
const [selectedPrivateSpaces, setSelectedPrivateSpaces] = useState<Set<string>>(new Set());
143144

144145
const { isPending: privateSpacesPending, error: privateSpacesError, data: privateSpacesData } = usePrivateSpaces();
145146
const {
@@ -148,25 +149,6 @@ function AuthenticateComponent() {
148149
data: publicSpacesData,
149150
} = usePublicSpaces(`${Graph.TESTNET_API_ORIGIN}/graphql`);
150151

151-
const selectedPrivateSpaces = new Set<string>();
152-
const selectedPublicSpaces = new Set<string>();
153-
154-
const handlePrivateSpaceToggle = (spaceId: string, checked: boolean) => {
155-
if (checked) {
156-
selectedPrivateSpaces.add(spaceId);
157-
} else {
158-
selectedPrivateSpaces.delete(spaceId);
159-
}
160-
};
161-
162-
const handlePublicSpaceToggle = (spaceId: string, checked: boolean) => {
163-
if (checked) {
164-
selectedPublicSpaces.add(spaceId);
165-
} else {
166-
selectedPublicSpaces.delete(spaceId);
167-
}
168-
};
169-
170152
useEffect(() => {
171153
const run = async () => {
172154
if (!identityToken || !accountAddress || !keys || !embeddedWallet) {
@@ -247,7 +229,7 @@ function AuthenticateComponent() {
247229

248230
const privateSpacesInput = privateSpacesData
249231
? privateSpacesData
250-
// .filter((space) => selectedPrivateSpaces.has(space.id))
232+
.filter((space) => selectedPrivateSpaces.has(space.id))
251233
.map((space) => {
252234
// TODO: currently without checking we assume all keyboxes exists and we don't create any - we should check if the keyboxes exist and create them if they don't
253235
if (space.appIdentities.some((spaceAppIdentity) => spaceAppIdentity.address === appIdentity.address))
@@ -518,6 +500,18 @@ function AuthenticateComponent() {
518500
});
519501
};
520502

503+
const handleSpaceSelection = (spaceId: string, selected: boolean) => {
504+
setSelectedPrivateSpaces((prev) => {
505+
const newSet = new Set(prev);
506+
if (selected) {
507+
newSet.add(spaceId);
508+
} else {
509+
newSet.delete(spaceId);
510+
}
511+
return newSet;
512+
});
513+
};
514+
521515
return (
522516
<div className="flex grow flex-col items-center justify-center">
523517
{(() => {
@@ -587,6 +581,9 @@ function AuthenticateComponent() {
587581
? { error: privateSpacesError.message }
588582
: undefined
589583
}
584+
selected={selectedPrivateSpaces}
585+
onSelected={handleSpaceSelection}
586+
currentAppId={state.appInfo?.appId}
590587
className="lg:absolute lg:inset-0"
591588
/>
592589
</div>

0 commit comments

Comments
 (0)