refactor(web): fold final-review polish into join modal feature

This commit is contained in:
Jannis Braun
2026-07-01 19:06:10 +02:00
parent cf9bcfbf1a
commit 807b2f6234
3 changed files with 33 additions and 6 deletions
@@ -1,4 +1,3 @@
import { useState } from 'react';
import { useSpaceJoin } from '../../hooks/useSpaceJoin'; import { useSpaceJoin } from '../../hooks/useSpaceJoin';
import { getSpaceGradient } from '../../utils/gradients'; import { getSpaceGradient } from '../../utils/gradients';
import type { TaggedExploreSpace } from '../../stores/exploreStore'; import type { TaggedExploreSpace } from '../../stores/exploreStore';
@@ -24,8 +23,6 @@ export function ExploreSpacePreviewCard({
sendRequest, sendRequest,
} = useSpaceJoin(space); } = useSpaceJoin(space);
const [expanded, setExpanded] = useState(false);
const fallbackGradient = getSpaceGradient(space.id, space.name, space.avatarColor).gradient; const fallbackGradient = getSpaceGradient(space.id, space.name, space.avatarColor).gradient;
const iconUrl = space.icon const iconUrl = space.icon
? (space.icon.startsWith('http') || space.icon.startsWith('/') ? space.icon : `/api/uploads/${space.icon}`) ? (space.icon.startsWith('http') || space.icon.startsWith('/') ? space.icon : `/api/uploads/${space.icon}`)
@@ -79,7 +76,7 @@ export function ExploreSpacePreviewCard({
) : ( ) : (
<button <button
type="button" type="button"
onClick={() => { openRequestForm(); setExpanded(true); }} onClick={openRequestForm}
className="px-3 py-1.5 bg-accent-amber/20 hover:bg-accent-amber/30 text-accent-amber text-xs font-medium rounded-full transition-colors" className="px-3 py-1.5 bg-accent-amber/20 hover:bg-accent-amber/30 text-accent-amber text-xs font-medium rounded-full transition-colors"
> >
Request Request
@@ -89,7 +86,7 @@ export function ExploreSpacePreviewCard({
</div> </div>
{/* Inline request form */} {/* Inline request form */}
{showRequestForm && expanded && ( {showRequestForm && (
<div className="mt-2.5 space-y-2"> <div className="mt-2.5 space-y-2">
<textarea <textarea
value={requestMessage} value={requestMessage}
@@ -109,7 +106,7 @@ export function ExploreSpacePreviewCard({
</button> </button>
<button <button
type="button" type="button"
onClick={() => { cancelRequestForm(); setExpanded(false); }} onClick={cancelRequestForm}
className="px-3 py-1.5 text-xs text-txt-tertiary hover:text-txt-secondary transition-colors" className="px-3 py-1.5 text-xs text-txt-tertiary hover:text-txt-secondary transition-colors"
> >
Cancel Cancel
@@ -174,6 +174,27 @@ describe('JoinSpaceModal', () => {
expect(mockNavigate).not.toHaveBeenCalledWith('/explore'); expect(mockNavigate).not.toHaveBeenCalledWith('/explore');
}); });
it('shows loading skeletons while discovery is fetching with no spaces yet', () => {
useExploreStore.setState({ isLoading: true, spaces: [] });
useUIStore.setState({ activeModal: 'joinSpace' });
renderModal();
// The skeleton branch (discoveryLoading && previewSpaces.length === 0)
// renders placeholder rows marked with `animate-pulse`.
expect(document.querySelectorAll('.animate-pulse').length).toBeGreaterThan(0);
});
it('degrades to the invite path when discovery fails to load', () => {
useExploreStore.setState({ error: 'boom', spaces: [], isLoading: false });
useUIStore.setState({ activeModal: 'joinSpace' });
renderModal();
// Discovery failure shows a degrade note...
expect(screen.getByText(/Couldn.t load spaces/i)).toBeInTheDocument();
// ...but the invite input stays usable so the user can still join.
expect(
screen.getByPlaceholderText('e.g. abc123 or https://instance.com/join/abc123')
).toBeInTheDocument();
});
it('hides discovery and shows a notice when discovery is disabled', () => { it('hides discovery and shows a notice when discovery is disabled', () => {
useExploreStore.setState({ discoveryEnabled: false }); useExploreStore.setState({ discoveryEnabled: false });
useUIStore.setState({ activeModal: 'joinSpace' }); useUIStore.setState({ activeModal: 'joinSpace' });
@@ -75,6 +75,15 @@ describe('useSpaceJoin', () => {
expect(result.current.joining).toBe(false); expect(result.current.joining).toBe(false);
}); });
it('openRequestForm/cancelRequestForm toggle showRequestForm', () => {
const { result } = renderHook(() => useSpaceJoin(makeSpace({ visibility: 'request' })));
expect(result.current.showRequestForm).toBe(false);
act(() => result.current.openRequestForm());
expect(result.current.showRequestForm).toBe(true);
act(() => result.current.cancelRequestForm());
expect(result.current.showRequestForm).toBe(false);
});
it('sendRequest() calls requestJoin and flips to pending', async () => { it('sendRequest() calls requestJoin and flips to pending', async () => {
const { result } = renderHook(() => useSpaceJoin(makeSpace({ visibility: 'request' }))); const { result } = renderHook(() => useSpaceJoin(makeSpace({ visibility: 'request' })));
act(() => result.current.setRequestMessage(' please ')); act(() => result.current.setRequestMessage(' please '));