From ae5ebb5af5e25ffbf94efbe978291049cc04209b Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Mon, 4 Aug 2025 14:06:22 +0530 Subject: [PATCH] add create space --- src/app/features/create-space/CreateSpace.tsx | 249 ++++++++++++++++++ src/app/features/create-space/index.ts | 1 + src/app/hooks/router/useCreateSelected.ts | 12 + src/app/pages/Router.tsx | 3 + src/app/pages/client/SidebarNav.tsx | 18 +- src/app/pages/client/create/Create.tsx | 35 +++ src/app/pages/client/create/index.ts | 1 + src/app/pages/client/sidebar/CreateTab.tsx | 111 ++++++++ src/app/pages/pathUtils.ts | 3 + src/app/pages/paths.ts | 2 + 10 files changed, 420 insertions(+), 15 deletions(-) create mode 100644 src/app/features/create-space/CreateSpace.tsx create mode 100644 src/app/features/create-space/index.ts create mode 100644 src/app/hooks/router/useCreateSelected.ts create mode 100644 src/app/pages/client/create/Create.tsx create mode 100644 src/app/pages/client/create/index.ts create mode 100644 src/app/pages/client/sidebar/CreateTab.tsx diff --git a/src/app/features/create-space/CreateSpace.tsx b/src/app/features/create-space/CreateSpace.tsx new file mode 100644 index 00000000..d964152a --- /dev/null +++ b/src/app/features/create-space/CreateSpace.tsx @@ -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( + 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 = (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 ( + + + Access + + + + Name + } + name="nameInput" + autoFocus + size="500" + variant="SurfaceVariant" + radii="400" + autoComplete="off" + disabled={disabled} + /> + + + Topic (Optional) +