refactor(web): SpaceCard uses shared useSpaceJoin hook

This commit is contained in:
Jannis Braun
2026-07-01 18:47:16 +02:00
parent 6c66af7a88
commit e840dcbe40
@@ -7,13 +7,13 @@ import { Mascot } from '../ui/Mascot';
import { getSpaceGradient } from '../../utils/gradients'; import { getSpaceGradient } from '../../utils/gradients';
import { extractDominantColors, colorsToGradient } from '../../utils/colorExtractor'; import { extractDominantColors, colorsToGradient } from '../../utils/colorExtractor';
import { MemberListToggleButton } from '../layout/MemberListToggleButton'; import { MemberListToggleButton } from '../layout/MemberListToggleButton';
import { useSpaceJoin } from '../../hooks/useSpaceJoin';
export function ExplorePage() { export function ExplorePage() {
const navigate = useNavigate(); const navigate = useNavigate();
const setCurrentSpace = useSpaceStore((s) => s.setCurrentSpace); const setCurrentSpace = useSpaceStore((s) => s.setCurrentSpace);
const spaces = useExploreStore((s) => s.spaces); const spaces = useExploreStore((s) => s.spaces);
const myRequests = useExploreStore((s) => s.myRequests);
const isLoading = useExploreStore((s) => s.isLoading); const isLoading = useExploreStore((s) => s.isLoading);
const discoveryEnabled = useExploreStore((s) => s.discoveryEnabled); const discoveryEnabled = useExploreStore((s) => s.discoveryEnabled);
const error = useExploreStore((s) => s.error); const error = useExploreStore((s) => s.error);
@@ -150,7 +150,6 @@ export function ExplorePage() {
<SpaceCard <SpaceCard
key={`${space.id}:${space._instanceOrigin}`} key={`${space.id}:${space._instanceOrigin}`}
space={space} space={space}
isPending={myRequests.some(r => r.spaceId === space.id && r.status === 'pending')}
onJoinSuccess={handleJoinSuccess} onJoinSuccess={handleJoinSuccess}
/> />
))} ))}
@@ -187,7 +186,6 @@ export function ExplorePage() {
<SpaceCard <SpaceCard
key={`${space.id}:${space._instanceOrigin}`} key={`${space.id}:${space._instanceOrigin}`}
space={space} space={space}
isPending={false}
onJoinSuccess={handleJoinSuccess} onJoinSuccess={handleJoinSuccess}
/> />
))} ))}
@@ -204,26 +202,29 @@ export function ExplorePage() {
function SpaceCard({ function SpaceCard({
space, space,
isPending,
onJoinSuccess, onJoinSuccess,
}: { }: {
space: TaggedExploreSpace; space: TaggedExploreSpace;
isPending: boolean;
onJoinSuccess: (spaceId: string) => void; onJoinSuccess: (spaceId: string) => void;
}) { }) {
const publicJoin = useExploreStore((s) => s.publicJoin); const {
const requestJoin = useExploreStore((s) => s.requestJoin); isJoined,
isPublic,
isPending,
joining,
joinError,
showRequestForm,
requestMessage,
setRequestMessage,
openRequestForm,
cancelRequestForm,
join,
sendRequest,
} = useSpaceJoin(space);
const [joining, setJoining] = useState(false);
const [showRequestForm, setShowRequestForm] = useState(false);
const [requestMessage, setRequestMessage] = useState('');
const [requestSent, setRequestSent] = useState(isPending);
const [joinError, setJoinError] = useState('');
const [iconGradient, setIconGradient] = useState<string | null>(null); const [iconGradient, setIconGradient] = useState<string | null>(null);
const fallbackGradient = getSpaceGradient(space.id, space.name, space.avatarColor).gradient; const fallbackGradient = getSpaceGradient(space.id, space.name, space.avatarColor).gradient;
const isPublic = space.visibility === 'public';
const isJoined = space.joined === true;
const originLabel = space._instanceOrigin const originLabel = space._instanceOrigin
? (() => { try { return new URL(space._instanceOrigin).host; } catch { return space._instanceOrigin; } })() ? (() => { try { return new URL(space._instanceOrigin).host; } catch { return space._instanceOrigin; } })()
: null; : null;
@@ -248,29 +249,8 @@ function SpaceCard({
}, [iconUrl, bannerUrl]); }, [iconUrl, bannerUrl]);
const handlePublicJoin = async () => { const handlePublicJoin = async () => {
setJoining(true); const full = await join();
setJoinError(''); if (full) onJoinSuccess(full.id);
try {
const fullSpace = await publicJoin(space);
onJoinSuccess(fullSpace.id);
} catch (err) {
setJoinError(err instanceof Error ? err.message : 'Failed to join');
setJoining(false);
}
};
const handleRequestJoin = async () => {
setJoining(true);
setJoinError('');
try {
await requestJoin(space, requestMessage.trim() || undefined);
setRequestSent(true);
setShowRequestForm(false);
} catch (err) {
setJoinError(err instanceof Error ? err.message : 'Failed to send request');
} finally {
setJoining(false);
}
}; };
const handleViewSpace = () => { const handleViewSpace = () => {
@@ -397,7 +377,7 @@ function SpaceCard({
'Join Space' 'Join Space'
)} )}
</button> </button>
) : requestSent ? ( ) : isPending ? (
<button <button
disabled disabled
className="w-full py-2 bg-interactive-muted text-txt-tertiary text-sm font-medium rounded cursor-default" className="w-full py-2 bg-interactive-muted text-txt-tertiary text-sm font-medium rounded cursor-default"
@@ -415,14 +395,14 @@ function SpaceCard({
/> />
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
onClick={handleRequestJoin} onClick={sendRequest}
disabled={joining} disabled={joining}
className="flex-1 py-1.5 bg-accent-amber hover:bg-accent-amber/80 text-[#13131a] text-sm font-medium rounded transition-colors disabled:opacity-50" className="flex-1 py-1.5 bg-accent-amber hover:bg-accent-amber/80 text-[#13131a] text-sm font-medium rounded transition-colors disabled:opacity-50"
> >
{joining ? 'Sending...' : 'Send Request'} {joining ? 'Sending...' : 'Send Request'}
</button> </button>
<button <button
onClick={() => setShowRequestForm(false)} onClick={cancelRequestForm}
className="px-3 py-1.5 text-sm text-txt-tertiary hover:text-txt-secondary transition-colors" className="px-3 py-1.5 text-sm text-txt-tertiary hover:text-txt-secondary transition-colors"
> >
Cancel Cancel
@@ -431,7 +411,7 @@ function SpaceCard({
</div> </div>
) : ( ) : (
<button <button
onClick={() => setShowRequestForm(true)} onClick={openRequestForm}
className="w-full py-2 bg-accent-amber/20 hover:bg-accent-amber/30 text-accent-amber text-sm font-medium rounded transition-colors" className="w-full py-2 bg-accent-amber/20 hover:bg-accent-amber/30 text-accent-amber text-sm font-medium rounded transition-colors"
> >
Request to Join Request to Join