refactor(web): fold final-review polish into join modal feature
This commit is contained in:
@@ -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 '));
|
||||||
|
|||||||
Reference in New Issue
Block a user