mirror of
				https://github.com/cinnyapp/cinny.git
				synced 2025-11-04 14:30:29 +03:00 
			
		
		
		
	add create space
This commit is contained in:
		
							parent
							
								
									8a72f40514
								
							
						
					
					
						commit
						ae5ebb5af5
					
				
					 10 changed files with 420 additions and 15 deletions
				
			
		
							
								
								
									
										249
									
								
								src/app/features/create-space/CreateSpace.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										249
									
								
								src/app/features/create-space/CreateSpace.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,249 @@
 | 
			
		|||
import React, { FormEventHandler, useCallback, useState } from 'react';
 | 
			
		||||
import { MatrixError, Room } from 'matrix-js-sdk';
 | 
			
		||||
import {
 | 
			
		||||
  Box,
 | 
			
		||||
  Button,
 | 
			
		||||
  Chip,
 | 
			
		||||
  color,
 | 
			
		||||
  config,
 | 
			
		||||
  Icon,
 | 
			
		||||
  Icons,
 | 
			
		||||
  Input,
 | 
			
		||||
  Spinner,
 | 
			
		||||
  Switch,
 | 
			
		||||
  Text,
 | 
			
		||||
  TextArea,
 | 
			
		||||
} from 'folds';
 | 
			
		||||
import { SettingTile } from '../../components/setting-tile';
 | 
			
		||||
import { SequenceCard } from '../../components/sequence-card';
 | 
			
		||||
import { knockRestrictedSupported, knockSupported, restrictedSupported } from '../../utils/matrix';
 | 
			
		||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
 | 
			
		||||
import { millisecondsToMinutes, replaceSpaceWithDash } from '../../utils/common';
 | 
			
		||||
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
 | 
			
		||||
import { useCapabilities } from '../../hooks/useCapabilities';
 | 
			
		||||
import { useAlive } from '../../hooks/useAlive';
 | 
			
		||||
import { ErrorCode } from '../../cs-errorcode';
 | 
			
		||||
import {
 | 
			
		||||
  createRoom,
 | 
			
		||||
  CreateRoomAliasInput,
 | 
			
		||||
  CreateRoomData,
 | 
			
		||||
  CreateRoomKind,
 | 
			
		||||
  CreateRoomKindSelector,
 | 
			
		||||
  RoomVersionSelector,
 | 
			
		||||
} from '../../components/create-room';
 | 
			
		||||
import { RoomType } from '../../../types/matrix/room';
 | 
			
		||||
 | 
			
		||||
const getCreateSpaceKindToIcon = (kind: CreateRoomKind) => {
 | 
			
		||||
  if (kind === CreateRoomKind.Private) return Icons.SpaceLock;
 | 
			
		||||
  if (kind === CreateRoomKind.Restricted) return Icons.Space;
 | 
			
		||||
  return Icons.SpaceGlobe;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type CreateSpaceFormProps = {
 | 
			
		||||
  defaultKind?: CreateRoomKind;
 | 
			
		||||
  space?: Room;
 | 
			
		||||
  onCreate?: (roomId: string) => void;
 | 
			
		||||
};
 | 
			
		||||
export function CreateSpaceForm({ defaultKind, space, onCreate }: CreateSpaceFormProps) {
 | 
			
		||||
  const mx = useMatrixClient();
 | 
			
		||||
  const alive = useAlive();
 | 
			
		||||
 | 
			
		||||
  const capabilities = useCapabilities();
 | 
			
		||||
  const roomVersions = capabilities['m.room_versions'];
 | 
			
		||||
  const [selectedRoomVersion, selectRoomVersion] = useState(roomVersions?.default ?? '1');
 | 
			
		||||
 | 
			
		||||
  const allowRestricted = space && restrictedSupported(selectedRoomVersion);
 | 
			
		||||
 | 
			
		||||
  const [kind, setKind] = useState(
 | 
			
		||||
    defaultKind ?? allowRestricted ? CreateRoomKind.Restricted : CreateRoomKind.Private
 | 
			
		||||
  );
 | 
			
		||||
  const [federation, setFederation] = useState(true);
 | 
			
		||||
  const [knock, setKnock] = useState(false);
 | 
			
		||||
  const [advance, setAdvance] = useState(false);
 | 
			
		||||
 | 
			
		||||
  const allowKnock = kind === CreateRoomKind.Private && knockSupported(selectedRoomVersion);
 | 
			
		||||
  const allowKnockRestricted =
 | 
			
		||||
    kind === CreateRoomKind.Restricted && knockRestrictedSupported(selectedRoomVersion);
 | 
			
		||||
 | 
			
		||||
  const handleRoomVersionChange = (version: string) => {
 | 
			
		||||
    if (!restrictedSupported(version)) {
 | 
			
		||||
      setKind(CreateRoomKind.Private);
 | 
			
		||||
    }
 | 
			
		||||
    selectRoomVersion(version);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const [createState, create] = useAsyncCallback<string, Error | MatrixError, [CreateRoomData]>(
 | 
			
		||||
    useCallback((data) => createRoom(mx, data), [mx])
 | 
			
		||||
  );
 | 
			
		||||
  const loading = createState.status === AsyncStatus.Loading;
 | 
			
		||||
  const error = createState.status === AsyncStatus.Error ? createState.error : undefined;
 | 
			
		||||
  const disabled = createState.status === AsyncStatus.Loading;
 | 
			
		||||
 | 
			
		||||
  const handleSubmit: FormEventHandler<HTMLFormElement> = (evt) => {
 | 
			
		||||
    evt.preventDefault();
 | 
			
		||||
    if (disabled) return;
 | 
			
		||||
    const form = evt.currentTarget;
 | 
			
		||||
 | 
			
		||||
    const nameInput = form.nameInput as HTMLInputElement | undefined;
 | 
			
		||||
    const topicTextArea = form.topicTextAria as HTMLTextAreaElement | undefined;
 | 
			
		||||
    const aliasInput = form.aliasInput as HTMLInputElement | undefined;
 | 
			
		||||
    const roomName = nameInput?.value.trim();
 | 
			
		||||
    const roomTopic = topicTextArea?.value.trim();
 | 
			
		||||
    const aliasLocalPart =
 | 
			
		||||
      aliasInput && aliasInput.value ? replaceSpaceWithDash(aliasInput.value) : undefined;
 | 
			
		||||
 | 
			
		||||
    if (!roomName) return;
 | 
			
		||||
    const publicRoom = kind === CreateRoomKind.Public;
 | 
			
		||||
    let roomKnock = false;
 | 
			
		||||
    if (allowKnock && kind === CreateRoomKind.Private) {
 | 
			
		||||
      roomKnock = knock;
 | 
			
		||||
    }
 | 
			
		||||
    if (allowKnockRestricted && kind === CreateRoomKind.Restricted) {
 | 
			
		||||
      roomKnock = knock;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    create({
 | 
			
		||||
      version: selectedRoomVersion,
 | 
			
		||||
      type: RoomType.Space,
 | 
			
		||||
      parent: space,
 | 
			
		||||
      kind,
 | 
			
		||||
      name: roomName,
 | 
			
		||||
      topic: roomTopic || undefined,
 | 
			
		||||
      aliasLocalPart: publicRoom ? aliasLocalPart : undefined,
 | 
			
		||||
      knock: roomKnock,
 | 
			
		||||
      allowFederation: federation,
 | 
			
		||||
    }).then((roomId) => {
 | 
			
		||||
      if (alive()) {
 | 
			
		||||
        onCreate?.(roomId);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Box as="form" onSubmit={handleSubmit} grow="Yes" direction="Column" gap="500">
 | 
			
		||||
      <Box direction="Column" gap="100">
 | 
			
		||||
        <Text size="L400">Access</Text>
 | 
			
		||||
        <CreateRoomKindSelector
 | 
			
		||||
          value={kind}
 | 
			
		||||
          onSelect={setKind}
 | 
			
		||||
          canRestrict={allowRestricted}
 | 
			
		||||
          disabled={disabled}
 | 
			
		||||
          getIcon={getCreateSpaceKindToIcon}
 | 
			
		||||
        />
 | 
			
		||||
      </Box>
 | 
			
		||||
      <Box shrink="No" direction="Column" gap="100">
 | 
			
		||||
        <Text size="L400">Name</Text>
 | 
			
		||||
        <Input
 | 
			
		||||
          required
 | 
			
		||||
          before={<Icon size="100" src={getCreateSpaceKindToIcon(kind)} />}
 | 
			
		||||
          name="nameInput"
 | 
			
		||||
          autoFocus
 | 
			
		||||
          size="500"
 | 
			
		||||
          variant="SurfaceVariant"
 | 
			
		||||
          radii="400"
 | 
			
		||||
          autoComplete="off"
 | 
			
		||||
          disabled={disabled}
 | 
			
		||||
        />
 | 
			
		||||
      </Box>
 | 
			
		||||
      <Box shrink="No" direction="Column" gap="100">
 | 
			
		||||
        <Text size="L400">Topic (Optional)</Text>
 | 
			
		||||
        <TextArea
 | 
			
		||||
          name="topicTextAria"
 | 
			
		||||
          size="500"
 | 
			
		||||
          variant="SurfaceVariant"
 | 
			
		||||
          radii="400"
 | 
			
		||||
          disabled={disabled}
 | 
			
		||||
        />
 | 
			
		||||
      </Box>
 | 
			
		||||
 | 
			
		||||
      {kind === CreateRoomKind.Public && <CreateRoomAliasInput disabled={disabled} />}
 | 
			
		||||
 | 
			
		||||
      <Box shrink="No" direction="Column" gap="100">
 | 
			
		||||
        <Box gap="200" alignItems="End">
 | 
			
		||||
          <Text size="L400">Options</Text>
 | 
			
		||||
          <Box grow="Yes" justifyContent="End">
 | 
			
		||||
            <Chip
 | 
			
		||||
              radii="Pill"
 | 
			
		||||
              before={<Icon src={advance ? Icons.ChevronTop : Icons.ChevronBottom} size="50" />}
 | 
			
		||||
              onClick={() => setAdvance(!advance)}
 | 
			
		||||
              type="button"
 | 
			
		||||
            >
 | 
			
		||||
              <Text size="T200">Advance Options</Text>
 | 
			
		||||
            </Chip>
 | 
			
		||||
          </Box>
 | 
			
		||||
        </Box>
 | 
			
		||||
        {kind !== CreateRoomKind.Public && advance && (allowKnock || allowKnockRestricted) && (
 | 
			
		||||
          <SequenceCard
 | 
			
		||||
            style={{ padding: config.space.S300 }}
 | 
			
		||||
            variant="SurfaceVariant"
 | 
			
		||||
            direction="Column"
 | 
			
		||||
            gap="500"
 | 
			
		||||
          >
 | 
			
		||||
            <SettingTile
 | 
			
		||||
              title="Knock to Join"
 | 
			
		||||
              description="Anyone can send request to join this space."
 | 
			
		||||
              after={
 | 
			
		||||
                <Switch variant="Primary" value={knock} onChange={setKnock} disabled={disabled} />
 | 
			
		||||
              }
 | 
			
		||||
            />
 | 
			
		||||
          </SequenceCard>
 | 
			
		||||
        )}
 | 
			
		||||
 | 
			
		||||
        <SequenceCard
 | 
			
		||||
          style={{ padding: config.space.S300 }}
 | 
			
		||||
          variant="SurfaceVariant"
 | 
			
		||||
          direction="Column"
 | 
			
		||||
          gap="500"
 | 
			
		||||
        >
 | 
			
		||||
          <SettingTile
 | 
			
		||||
            title="Allow Federation"
 | 
			
		||||
            description="Users from other servers can join."
 | 
			
		||||
            after={
 | 
			
		||||
              <Switch
 | 
			
		||||
                variant="Primary"
 | 
			
		||||
                value={federation}
 | 
			
		||||
                onChange={setFederation}
 | 
			
		||||
                disabled={disabled}
 | 
			
		||||
              />
 | 
			
		||||
            }
 | 
			
		||||
          />
 | 
			
		||||
        </SequenceCard>
 | 
			
		||||
        {advance && (
 | 
			
		||||
          <RoomVersionSelector
 | 
			
		||||
            versions={roomVersions?.available ? Object.keys(roomVersions.available) : ['1']}
 | 
			
		||||
            value={selectedRoomVersion}
 | 
			
		||||
            onChange={handleRoomVersionChange}
 | 
			
		||||
            disabled={disabled}
 | 
			
		||||
          />
 | 
			
		||||
        )}
 | 
			
		||||
      </Box>
 | 
			
		||||
 | 
			
		||||
      {error && (
 | 
			
		||||
        <Box style={{ color: color.Critical.Main }} alignItems="Center" gap="200">
 | 
			
		||||
          <Icon src={Icons.Warning} filled size="100" />
 | 
			
		||||
          <Text size="T300" style={{ color: color.Critical.Main }}>
 | 
			
		||||
            <b>
 | 
			
		||||
              {error instanceof MatrixError && error.name === ErrorCode.M_LIMIT_EXCEEDED
 | 
			
		||||
                ? `Server rate-limited your request for ${millisecondsToMinutes(
 | 
			
		||||
                    (error.data.retry_after_ms as number | undefined) ?? 0
 | 
			
		||||
                  )} minutes!`
 | 
			
		||||
                : error.message}
 | 
			
		||||
            </b>
 | 
			
		||||
          </Text>
 | 
			
		||||
        </Box>
 | 
			
		||||
      )}
 | 
			
		||||
      <Box shrink="No" direction="Column" gap="200">
 | 
			
		||||
        <Button
 | 
			
		||||
          type="submit"
 | 
			
		||||
          size="500"
 | 
			
		||||
          variant="Primary"
 | 
			
		||||
          radii="400"
 | 
			
		||||
          disabled={disabled}
 | 
			
		||||
          before={loading && <Spinner variant="Primary" fill="Solid" size="200" />}
 | 
			
		||||
        >
 | 
			
		||||
          <Text size="B500">Create</Text>
 | 
			
		||||
        </Button>
 | 
			
		||||
      </Box>
 | 
			
		||||
    </Box>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								src/app/features/create-space/index.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/app/features/create-space/index.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
export * from './CreateSpace';
 | 
			
		||||
							
								
								
									
										12
									
								
								src/app/hooks/router/useCreateSelected.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/app/hooks/router/useCreateSelected.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
import { useMatch } from 'react-router-dom';
 | 
			
		||||
import { getCreatePath } from '../../pages/pathUtils';
 | 
			
		||||
 | 
			
		||||
export const useCreateSelected = (): boolean => {
 | 
			
		||||
  const match = useMatch({
 | 
			
		||||
    path: getCreatePath(),
 | 
			
		||||
    caseSensitive: true,
 | 
			
		||||
    end: false,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return !!match;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -28,6 +28,7 @@ import {
 | 
			
		|||
  _ROOM_PATH,
 | 
			
		||||
  _SEARCH_PATH,
 | 
			
		||||
  _SERVER_PATH,
 | 
			
		||||
  CREATE_PATH,
 | 
			
		||||
} from './paths';
 | 
			
		||||
import { isAuthenticated } from '../../client/state/auth';
 | 
			
		||||
import {
 | 
			
		||||
| 
						 | 
				
			
			@ -63,6 +64,7 @@ import { ClientRoomsNotificationPreferences } from './client/ClientRoomsNotifica
 | 
			
		|||
import { SpaceSettingsRenderer } from '../features/space-settings';
 | 
			
		||||
import { CreateRoomModalRenderer } from '../features/create-room';
 | 
			
		||||
import { HomeCreateRoom } from './client/home/CreateRoom';
 | 
			
		||||
import { Create } from './client/create';
 | 
			
		||||
 | 
			
		||||
export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) => {
 | 
			
		||||
  const { hashRouter } = clientConfig;
 | 
			
		||||
| 
						 | 
				
			
			@ -256,6 +258,7 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
 | 
			
		|||
          <Route path={_FEATURED_PATH} element={<FeaturedRooms />} />
 | 
			
		||||
          <Route path={_SERVER_PATH} element={<PublicRooms />} />
 | 
			
		||||
        </Route>
 | 
			
		||||
        <Route path={CREATE_PATH} element={<Create />} />
 | 
			
		||||
        <Route
 | 
			
		||||
          path={INBOX_PATH}
 | 
			
		||||
          element={
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,7 +19,8 @@ import {
 | 
			
		|||
  SettingsTab,
 | 
			
		||||
  UnverifiedTab,
 | 
			
		||||
} from './sidebar';
 | 
			
		||||
import { openCreateRoom, openSearch } from '../../../client/action/navigation';
 | 
			
		||||
import { openSearch } from '../../../client/action/navigation';
 | 
			
		||||
import { CreateTab } from './sidebar/CreateTab';
 | 
			
		||||
 | 
			
		||||
export function SidebarNav() {
 | 
			
		||||
  const scrollRef = useRef<HTMLDivElement>(null);
 | 
			
		||||
| 
						 | 
				
			
			@ -37,20 +38,7 @@ export function SidebarNav() {
 | 
			
		|||
            <SidebarStackSeparator />
 | 
			
		||||
            <SidebarStack>
 | 
			
		||||
              <ExploreTab />
 | 
			
		||||
              <SidebarItem>
 | 
			
		||||
                <SidebarItemTooltip tooltip="Create Space">
 | 
			
		||||
                  {(triggerRef) => (
 | 
			
		||||
                    <SidebarAvatar
 | 
			
		||||
                      as="button"
 | 
			
		||||
                      ref={triggerRef}
 | 
			
		||||
                      outlined
 | 
			
		||||
                      onClick={() => openCreateRoom(true)}
 | 
			
		||||
                    >
 | 
			
		||||
                      <Icon src={Icons.Plus} />
 | 
			
		||||
                    </SidebarAvatar>
 | 
			
		||||
                  )}
 | 
			
		||||
                </SidebarItemTooltip>
 | 
			
		||||
              </SidebarItem>
 | 
			
		||||
              <CreateTab />
 | 
			
		||||
            </SidebarStack>
 | 
			
		||||
          </Scroll>
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										35
									
								
								src/app/pages/client/create/Create.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/app/pages/client/create/Create.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,35 @@
 | 
			
		|||
import React from 'react';
 | 
			
		||||
import { Box, Icon, Icons, Scroll } from 'folds';
 | 
			
		||||
import {
 | 
			
		||||
  Page,
 | 
			
		||||
  PageContent,
 | 
			
		||||
  PageContentCenter,
 | 
			
		||||
  PageHero,
 | 
			
		||||
  PageHeroSection,
 | 
			
		||||
} from '../../../components/page';
 | 
			
		||||
import { CreateSpaceForm } from '../../../features/create-space';
 | 
			
		||||
 | 
			
		||||
export function Create() {
 | 
			
		||||
  return (
 | 
			
		||||
    <Page>
 | 
			
		||||
      <Box grow="Yes">
 | 
			
		||||
        <Scroll hideTrack visibility="Hover">
 | 
			
		||||
          <PageContent>
 | 
			
		||||
            <PageContentCenter>
 | 
			
		||||
              <PageHeroSection>
 | 
			
		||||
                <Box direction="Column" gap="700">
 | 
			
		||||
                  <PageHero
 | 
			
		||||
                    icon={<Icon size="600" src={Icons.Space} />}
 | 
			
		||||
                    title="Create Space"
 | 
			
		||||
                    subTitle="Build a space for your community."
 | 
			
		||||
                  />
 | 
			
		||||
                  <CreateSpaceForm />
 | 
			
		||||
                </Box>
 | 
			
		||||
              </PageHeroSection>
 | 
			
		||||
            </PageContentCenter>
 | 
			
		||||
          </PageContent>
 | 
			
		||||
        </Scroll>
 | 
			
		||||
      </Box>
 | 
			
		||||
    </Page>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								src/app/pages/client/create/index.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/app/pages/client/create/index.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
export * from './Create';
 | 
			
		||||
							
								
								
									
										111
									
								
								src/app/pages/client/sidebar/CreateTab.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								src/app/pages/client/sidebar/CreateTab.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,111 @@
 | 
			
		|||
import React, { MouseEventHandler, useState } from 'react';
 | 
			
		||||
import { Box, config, Icon, Icons, Menu, PopOut, RectCords, Text } from 'folds';
 | 
			
		||||
import FocusTrap from 'focus-trap-react';
 | 
			
		||||
import { useNavigate } from 'react-router-dom';
 | 
			
		||||
import { SidebarAvatar, SidebarItem, SidebarItemTooltip } from '../../../components/sidebar';
 | 
			
		||||
import { stopPropagation } from '../../../utils/keyboard';
 | 
			
		||||
import { SequenceCard } from '../../../components/sequence-card';
 | 
			
		||||
import { SettingTile } from '../../../components/setting-tile';
 | 
			
		||||
import { ContainerColor } from '../../../styles/ContainerColor.css';
 | 
			
		||||
import { openJoinAlias } from '../../../../client/action/navigation';
 | 
			
		||||
import { getCreatePath } from '../../pathUtils';
 | 
			
		||||
import { useCreateSelected } from '../../../hooks/router/useCreateSelected';
 | 
			
		||||
 | 
			
		||||
export function CreateTab() {
 | 
			
		||||
  const createSelected = useCreateSelected();
 | 
			
		||||
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const [menuCords, setMenuCords] = useState<RectCords>();
 | 
			
		||||
 | 
			
		||||
  const handleMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
 | 
			
		||||
    setMenuCords(menuCords ? undefined : evt.currentTarget.getBoundingClientRect());
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleCreateSpace = () => {
 | 
			
		||||
    navigate(getCreatePath());
 | 
			
		||||
    setMenuCords(undefined);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleJoinWithAddress = () => {
 | 
			
		||||
    openJoinAlias();
 | 
			
		||||
    setMenuCords(undefined);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <SidebarItem active={createSelected}>
 | 
			
		||||
      <SidebarItemTooltip tooltip="Add Space">
 | 
			
		||||
        {(triggerRef) => (
 | 
			
		||||
          <PopOut
 | 
			
		||||
            anchor={menuCords}
 | 
			
		||||
            position="Right"
 | 
			
		||||
            align="Center"
 | 
			
		||||
            content={
 | 
			
		||||
              <FocusTrap
 | 
			
		||||
                focusTrapOptions={{
 | 
			
		||||
                  returnFocusOnDeactivate: false,
 | 
			
		||||
                  initialFocus: false,
 | 
			
		||||
                  onDeactivate: () => setMenuCords(undefined),
 | 
			
		||||
                  clickOutsideDeactivates: true,
 | 
			
		||||
                  isKeyForward: (evt: KeyboardEvent) =>
 | 
			
		||||
                    evt.key === 'ArrowDown' || evt.key === 'ArrowRight',
 | 
			
		||||
                  isKeyBackward: (evt: KeyboardEvent) =>
 | 
			
		||||
                    evt.key === 'ArrowUp' || evt.key === 'ArrowLeft',
 | 
			
		||||
                  escapeDeactivates: stopPropagation,
 | 
			
		||||
                }}
 | 
			
		||||
              >
 | 
			
		||||
                <Menu>
 | 
			
		||||
                  <Box direction="Column">
 | 
			
		||||
                    <SequenceCard
 | 
			
		||||
                      style={{ padding: config.space.S300 }}
 | 
			
		||||
                      variant="Surface"
 | 
			
		||||
                      direction="Column"
 | 
			
		||||
                      gap="100"
 | 
			
		||||
                      radii="0"
 | 
			
		||||
                      as="button"
 | 
			
		||||
                      type="button"
 | 
			
		||||
                      onClick={handleCreateSpace}
 | 
			
		||||
                    >
 | 
			
		||||
                      <SettingTile before={<Icon size="400" src={Icons.Space} />}>
 | 
			
		||||
                        <Text size="H6">Create Space</Text>
 | 
			
		||||
                        <Text size="T300" priority="300">
 | 
			
		||||
                          Build a space for your community.
 | 
			
		||||
                        </Text>
 | 
			
		||||
                      </SettingTile>
 | 
			
		||||
                    </SequenceCard>
 | 
			
		||||
                    <SequenceCard
 | 
			
		||||
                      style={{ padding: config.space.S300 }}
 | 
			
		||||
                      variant="Surface"
 | 
			
		||||
                      direction="Column"
 | 
			
		||||
                      gap="100"
 | 
			
		||||
                      radii="0"
 | 
			
		||||
                      as="button"
 | 
			
		||||
                      type="button"
 | 
			
		||||
                      onClick={handleJoinWithAddress}
 | 
			
		||||
                    >
 | 
			
		||||
                      <SettingTile before={<Icon size="400" src={Icons.Link} />}>
 | 
			
		||||
                        <Text size="H6">Join with Address</Text>
 | 
			
		||||
                        <Text size="T300" priority="300">
 | 
			
		||||
                          Become a part of existing community.
 | 
			
		||||
                        </Text>
 | 
			
		||||
                      </SettingTile>
 | 
			
		||||
                    </SequenceCard>
 | 
			
		||||
                  </Box>
 | 
			
		||||
                </Menu>
 | 
			
		||||
              </FocusTrap>
 | 
			
		||||
            }
 | 
			
		||||
          >
 | 
			
		||||
            <SidebarAvatar
 | 
			
		||||
              className={menuCords ? ContainerColor({ variant: 'Surface' }) : undefined}
 | 
			
		||||
              as="button"
 | 
			
		||||
              ref={triggerRef}
 | 
			
		||||
              outlined
 | 
			
		||||
              onClick={handleMenu}
 | 
			
		||||
            >
 | 
			
		||||
              <Icon src={Icons.Plus} />
 | 
			
		||||
            </SidebarAvatar>
 | 
			
		||||
          </PopOut>
 | 
			
		||||
        )}
 | 
			
		||||
      </SidebarItemTooltip>
 | 
			
		||||
    </SidebarItem>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -22,6 +22,7 @@ import {
 | 
			
		|||
  SPACE_PATH,
 | 
			
		||||
  SPACE_ROOM_PATH,
 | 
			
		||||
  SPACE_SEARCH_PATH,
 | 
			
		||||
  CREATE_PATH,
 | 
			
		||||
} from './paths';
 | 
			
		||||
import { trimLeadingSlash, trimTrailingSlash } from '../utils/common';
 | 
			
		||||
import { HashRouterConfig } from '../hooks/useClientConfig';
 | 
			
		||||
| 
						 | 
				
			
			@ -152,6 +153,8 @@ export const getExploreServerPath = (server: string): string => {
 | 
			
		|||
  return generatePath(EXPLORE_SERVER_PATH, params);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getCreatePath = (): string => CREATE_PATH;
 | 
			
		||||
 | 
			
		||||
export const getInboxPath = (): string => INBOX_PATH;
 | 
			
		||||
export const getInboxNotificationsPath = (): string => INBOX_NOTIFICATIONS_PATH;
 | 
			
		||||
export const getInboxInvitesPath = (): string => INBOX_INVITES_PATH;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -74,6 +74,8 @@ export type ExploreServerPathSearchParams = {
 | 
			
		|||
};
 | 
			
		||||
export const EXPLORE_SERVER_PATH = `/explore/${_SERVER_PATH}`;
 | 
			
		||||
 | 
			
		||||
export const CREATE_PATH = '/create';
 | 
			
		||||
 | 
			
		||||
export const _NOTIFICATIONS_PATH = 'notifications/';
 | 
			
		||||
export const _INVITES_PATH = 'invites/';
 | 
			
		||||
export const INBOX_PATH = '/inbox/';
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue