mirror of
https://github.com/ilyakooo0/urbit.git
synced 2025-01-03 04:40:50 +03:00
metadata: surface icon and show interstitial on invite
This commit is contained in:
parent
752c279c83
commit
4cb8339bf1
@ -2,7 +2,7 @@
|
||||
::
|
||||
:: allow syncing group data from foreign paths to local paths
|
||||
::
|
||||
/- *group, *invite-store, *metadata-store
|
||||
/- *group, invite-store, *metadata-store
|
||||
/+ default-agent, verb, dbug, store=group-store, grpl=group, pull-hook
|
||||
/+ resource, mdl=metadata
|
||||
~% %group-hook-top ..part ~
|
||||
@ -29,43 +29,86 @@
|
||||
^- (pull-hook:pull-hook config)
|
||||
=| state-zero
|
||||
=* state -
|
||||
=> |_ =bowl:gall
|
||||
++ def ~(. (default-agent state %|) bowl)
|
||||
++ watch-preview
|
||||
|= rid=resource
|
||||
^- card
|
||||
=/ =path
|
||||
preview+(en-path:resource rid)
|
||||
=/ =dock
|
||||
[entity.rid %metadata-push-hook]
|
||||
[%pass path %agent dock %watch path]
|
||||
::
|
||||
++ watch-invites
|
||||
^- card
|
||||
[%pass /invites %agent [our.bowl %invite-store] %watch /updates]
|
||||
::
|
||||
++ take-invites
|
||||
|= =sign:agent:gall
|
||||
^- (quip card _state)
|
||||
?+ -.sign (on-agent:def /invites sign)
|
||||
%fact
|
||||
?> ?=(%invite-update p.cage.sign)
|
||||
=+ !<(=update:invite-store q.cage.sign)
|
||||
:_ state
|
||||
?. ?=(%invite -.update) ~
|
||||
?. =(%contacts term.update) ~
|
||||
(watch-preview resource.invite.update)^~
|
||||
::
|
||||
%kick [watch-invites^~ state]
|
||||
==
|
||||
::
|
||||
++ take-preview
|
||||
|= [=wire =sign:agent:gall]
|
||||
^- (quip card _state)
|
||||
?> ?=([%preview @ *] wire)
|
||||
=/ rid=resource
|
||||
(de-path:resource t.wire)
|
||||
?+ -.sign (on-agent:def wire sign)
|
||||
%fact
|
||||
?> =(%metadata-update p.cage.sign)
|
||||
=+ !<(upd=metadata-update q.cage.sign)
|
||||
?> ?=(%preview -.upd)
|
||||
:_ state(previews (~(put by previews) rid +.upd))
|
||||
:~ [%give %fact ~[wire] cage.sign]
|
||||
[%give %kick ~[wire] ~]
|
||||
==
|
||||
::
|
||||
%watch-ack
|
||||
:_ state
|
||||
?~ p.sign ~
|
||||
:~ [%give %fact ~[wire] tang+!>(u.p.sign)]
|
||||
[%give %kick ~[wire] ~]
|
||||
==
|
||||
==
|
||||
--
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
dep ~(. (default:pull-hook this config) bowl)
|
||||
met ~(. mdl bowl)
|
||||
hc ~(. +> bowl)
|
||||
::
|
||||
++ on-init on-init:def
|
||||
++ on-save !>(state)
|
||||
++ on-load
|
||||
|= =vase
|
||||
?: =(1 1) `this
|
||||
=+ !<(old=state-zero vase)
|
||||
`this(state old)
|
||||
:_ this(state old)
|
||||
?: (~(has by wex.bowl) [/invites our.bowl %invite-store]) ~
|
||||
watch-invites^~
|
||||
::
|
||||
++ on-poke on-poke:def
|
||||
++ on-agent
|
||||
|= [=wire =sign:agent:gall]
|
||||
?. ?=([%preview @ @ @ ~] wire)
|
||||
(on-agent:def wire sign)
|
||||
=/ rid=resource
|
||||
(de-path:resource t.wire)
|
||||
?+ -.sign `this
|
||||
%fact
|
||||
?> =(%metadata-update p.cage.sign)
|
||||
=+ !<(upd=metadata-update q.cage.sign)
|
||||
?> ?=(%preview -.upd)
|
||||
:_ this(previews (~(put by previews) rid +.upd))
|
||||
:~ [%give %fact ~[wire] cage.sign]
|
||||
[%give %kick ~[wire] ~]
|
||||
=^ cards state
|
||||
?+ wire (on-agent:def:hc wire sign)
|
||||
[%invites ~] (take-invites:hc sign)
|
||||
[%preview @ @ @ ~] (take-preview:hc wire sign)
|
||||
==
|
||||
::
|
||||
%watch-ack
|
||||
:_ this
|
||||
?~ p.sign ~
|
||||
:~ [%give %fact ~[wire] tang+!>(u.p.sign)]
|
||||
[%give %kick ~[wire] ~]
|
||||
==
|
||||
==
|
||||
[cards this]
|
||||
::
|
||||
++ on-watch
|
||||
|= =path
|
||||
|
103
pkg/interface/src/logic/lib/useModal.tsx
Normal file
103
pkg/interface/src/logic/lib/useModal.tsx
Normal file
@ -0,0 +1,103 @@
|
||||
import React, {
|
||||
useState,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
SyntheticEvent,
|
||||
useMemo,
|
||||
useEffect,
|
||||
} from "react";
|
||||
|
||||
import { Box } from "@tlon/indigo-react";
|
||||
|
||||
type ModalFunc = (dismiss: () => void) => JSX.Element;
|
||||
interface UseModalProps {
|
||||
modal: JSX.Element | ModalFunc;
|
||||
}
|
||||
|
||||
interface UseModalResult {
|
||||
modal: ReactNode;
|
||||
showModal: () => void;
|
||||
}
|
||||
|
||||
const stopPropagation = (e: SyntheticEvent) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
export function useModal(props: UseModalProps): UseModalResult {
|
||||
const [modalShown, setModalShown] = useState(false);
|
||||
|
||||
const dismiss = useCallback(() => {
|
||||
setModalShown(false);
|
||||
}, [setModalShown]);
|
||||
|
||||
const showModal = useCallback(() => {
|
||||
setModalShown(true);
|
||||
}, [setModalShown]);
|
||||
|
||||
const inner = useMemo(
|
||||
() =>
|
||||
!modalShown
|
||||
? null
|
||||
: typeof props.modal === "function"
|
||||
? props.modal(dismiss)
|
||||
: props.modal,
|
||||
[modalShown, props.modal, dismiss]
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(event) => {
|
||||
if (event.key === "Escape") {
|
||||
dismiss();
|
||||
}
|
||||
},
|
||||
[dismiss]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, [modalShown]);
|
||||
|
||||
const modal = useMemo(
|
||||
() =>
|
||||
!inner ? null : (
|
||||
<Box
|
||||
backgroundColor="scales.black30"
|
||||
left="0px"
|
||||
top="0px"
|
||||
width="100%"
|
||||
height="100%"
|
||||
zIndex={10}
|
||||
position="fixed"
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
onClick={dismiss}
|
||||
>
|
||||
<Box
|
||||
maxWidth="500px"
|
||||
width="100%"
|
||||
bg="white"
|
||||
borderRadius={2}
|
||||
border={[0, 1]}
|
||||
borderColor={["washedGray", "washedGray"]}
|
||||
onClick={stopPropagation}
|
||||
display="flex"
|
||||
alignItems="stretch"
|
||||
flexDirection="column"
|
||||
>
|
||||
{inner}
|
||||
</Box>
|
||||
</Box>
|
||||
),
|
||||
[inner, dismiss]
|
||||
);
|
||||
|
||||
return {
|
||||
showModal,
|
||||
modal,
|
||||
};
|
||||
}
|
@ -1,69 +1,24 @@
|
||||
import React, { useState, useEffect } from "react"
|
||||
import React from "react"
|
||||
import { Box, Button, Icon, Text } from "@tlon/indigo-react"
|
||||
import { NewGroup } from "~/views/landscape/components/NewGroup";
|
||||
import { JoinGroup } from "~/views/landscape/components/JoinGroup";
|
||||
import {useModal} from "~/logic/lib/useModal";
|
||||
|
||||
const ModalButton = (props) => {
|
||||
const {
|
||||
childen,
|
||||
children,
|
||||
icon,
|
||||
text,
|
||||
bg,
|
||||
color,
|
||||
...rest
|
||||
} = props;
|
||||
const [modalShown, setModalShown] = useState(false);
|
||||
const { modal, showModal } = useModal({ modal: props.children });
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
if (event.key === 'Escape') {
|
||||
setModalShown(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [modalShown]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{modalShown && (
|
||||
<Box
|
||||
backgroundColor='scales.black30'
|
||||
left="0px"
|
||||
top="0px"
|
||||
width="100%"
|
||||
height="100%"
|
||||
zIndex={4}
|
||||
position="fixed"
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
onClick={() => setModalShown(false)}
|
||||
>
|
||||
<Box
|
||||
maxWidth="500px"
|
||||
width="100%"
|
||||
bg="white"
|
||||
borderRadius={2}
|
||||
border={[0, 1]}
|
||||
borderColor={["washedGray", "washedGray"]}
|
||||
onClick={e => e.stopPropagation()}
|
||||
display="flex"
|
||||
alignItems="stretch"
|
||||
flexDirection="column"
|
||||
>
|
||||
{typeof props.children === 'function'
|
||||
? props.children(() => setModalShown(false))
|
||||
: props.children}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
{modal}
|
||||
<Box
|
||||
onClick={() => setModalShown(true)}
|
||||
onClick={showModal}
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
cursor="pointer"
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useCallback } from "react";
|
||||
import React, { useEffect, useCallback, useState, useMemo } from "react";
|
||||
import f from "lodash/fp";
|
||||
import _ from "lodash";
|
||||
import { Icon, Col, Row, Box, Text, Anchor, Rule } from "@tlon/indigo-react";
|
||||
@ -13,6 +13,8 @@ import { cite } from '~/logic/lib/util';
|
||||
import { InviteItem } from '~/views/components/Invite';
|
||||
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import {useModal} from "~/logic/lib/useModal";
|
||||
import {JoinGroup} from "~/views/landscape/components/JoinGroup";
|
||||
|
||||
type DatedTimebox = [BigInteger, Timebox];
|
||||
|
||||
@ -101,6 +103,27 @@ export default function Inbox(props: {
|
||||
}
|
||||
}, [props.showArchive]);
|
||||
|
||||
const [joining, setJoining] = useState<[string, string] | null>(null);
|
||||
|
||||
const { modal, showModal } = useModal(
|
||||
{ modal: useCallback(
|
||||
(dismiss) => (
|
||||
<JoinGroup
|
||||
groups={props.groups}
|
||||
contacts={props.contacts}
|
||||
api={props.api}
|
||||
autojoin={joining?.[0]?.slice(6)}
|
||||
inviteUid={joining?.[1]}
|
||||
/>
|
||||
),
|
||||
[props.contacts, props.groups, props.api, joining]
|
||||
)})
|
||||
|
||||
const joinGroup = useCallback((group: string, uid: string) => {
|
||||
setJoining([group, uid]);
|
||||
showModal();
|
||||
}, [setJoining, showModal]);
|
||||
|
||||
const acceptInvite = (app: string, uid: string) => async (invite) => {
|
||||
const resource = {
|
||||
ship: `~${invite.resource.ship}`,
|
||||
@ -109,10 +132,7 @@ export default function Inbox(props: {
|
||||
|
||||
const resourcePath = resourceAsPath(invite.resource);
|
||||
if(app === 'contacts') {
|
||||
await api.contacts.join(resource);
|
||||
await waiter(p => resourcePath in p.associations?.contacts);
|
||||
await api.invite.accept(app, uid);
|
||||
history.push(`/~landscape${resourcePath}`);
|
||||
joinGroup(resourcePath, uid);
|
||||
} else if ( app === 'chat') {
|
||||
await api.invite.accept(app, uid);
|
||||
history.push(`/~landscape/home/resource/chat${resourcePath.slice(5)}`);
|
||||
@ -122,6 +142,9 @@ export default function Inbox(props: {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
const inviteItems = (invites, api) => {
|
||||
const returned = [];
|
||||
Object.keys(invites).map((appKey) => {
|
||||
@ -143,6 +166,7 @@ export default function Inbox(props: {
|
||||
|
||||
return (
|
||||
<Col position="relative" height="100%" overflowY="auto" onScroll={onScroll} >
|
||||
{modal}
|
||||
<Col zIndex={4} gapY={2} bg="white" top="0px" position="sticky">
|
||||
{inviteItems(invites, api)}
|
||||
</Col>
|
||||
|
@ -0,0 +1,42 @@
|
||||
import React, { ReactNode } from "react";
|
||||
import { Metadata } from "~/types";
|
||||
import { Col, Row, Text } from "@tlon/indigo-react";
|
||||
import { MetadataIcon } from "./MetadataIcon";
|
||||
|
||||
interface GroupSummaryProps {
|
||||
metadata: Metadata;
|
||||
memberCount: number;
|
||||
channelCount: number;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export function GroupSummary(props: GroupSummaryProps) {
|
||||
const { channelCount, memberCount, metadata, children } = props;
|
||||
return (
|
||||
<Col maxWidth="300px" gapY="4">
|
||||
<Row gapX="2">
|
||||
<MetadataIcon
|
||||
borderRadius="1"
|
||||
border="1"
|
||||
borderColor="lightGray"
|
||||
width="40px"
|
||||
height="40px"
|
||||
metadata={metadata}
|
||||
/>
|
||||
<Col justifyContent="space-between">
|
||||
<Text fontSize="1">{metadata.title}</Text>
|
||||
<Row gapX="2" justifyContent="space-between">
|
||||
<Text fontSize="1" gray>
|
||||
{memberCount} participants
|
||||
</Text>
|
||||
<Text fontSize="1" gray>
|
||||
{channelCount} channels
|
||||
</Text>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
{metadata.description && <Text fontSize="1">{metadata.description}</Text>}
|
||||
{children}
|
||||
</Col>
|
||||
);
|
||||
}
|
@ -13,6 +13,7 @@ import { Associations } from '~/types/metadata-update';
|
||||
import { Dropdown } from '~/views/components/Dropdown';
|
||||
import { Workspace } from '~/types';
|
||||
import { getTitleFromWorkspace } from '~/logic/lib/workspace';
|
||||
import {MetadataIcon} from './MetadataIcon';
|
||||
|
||||
const GroupSwitcherItem = ({ to, children, bottom = false, ...rest }) => (
|
||||
<Link to={to}>
|
||||
@ -77,15 +78,18 @@ export function GroupSwitcher(props: {
|
||||
}) {
|
||||
const { associations, workspace, isAdmin } = props;
|
||||
const title = getTitleFromWorkspace(associations, workspace);
|
||||
const metadata = workspace.type === 'home' ? undefined : associations.contacts[workspace.group].metadata;
|
||||
const navTo = (to: string) => `${props.baseUrl}${to}`;
|
||||
return (
|
||||
<Box height='48px' backgroundColor="white" zIndex="2" position="sticky" top="0px" py={3} pl='3' borderBottom='1px solid' borderColor='washedGray'>
|
||||
<Row width="100%" alignItems="center" height='48px' backgroundColor="white" zIndex="2" position="sticky" top="0px" pl='3' borderBottom='1px solid' borderColor='washedGray'>
|
||||
<Col
|
||||
bg="white"
|
||||
width="100%"
|
||||
height="100%"
|
||||
>
|
||||
<Row justifyContent="space-between">
|
||||
<Row flexGrow={1} alignItems="center" justifyContent="space-between">
|
||||
<Dropdown
|
||||
width="100%"
|
||||
width="auto"
|
||||
dropWidth="231px"
|
||||
alignY="top"
|
||||
options={
|
||||
@ -160,8 +164,9 @@ export function GroupSwitcher(props: {
|
||||
</Col>
|
||||
}
|
||||
>
|
||||
<Row width='100%' minWidth='0' flexShrink={0}>
|
||||
<Row justifyContent="space-between" mr={1} flexShrink={0} width='100%' minWidth='0'>
|
||||
<Row flexGrow={1} alignItems="center" width='100%' minWidth='0' flexShrink={0}>
|
||||
{ metadata && <MetadataIcon mr="2" border="1" borderColor="lightGray" borderRadius="1" metadata={metadata} height="24px" width="24px" /> }
|
||||
<Row justifyContent="space-between" mr={1} flexShrink={0} flexGrow={1} minWidth='0'>
|
||||
<Text lineHeight="1.1" fontSize='2' fontWeight="700" overflow='hidden' display='inline-block' flexShrink='1' style={{ textOverflow: 'ellipsis', whiteSpace: 'pre' }}>{title}</Text>
|
||||
</Row>
|
||||
</Row>
|
||||
@ -185,6 +190,6 @@ export function GroupSwitcher(props: {
|
||||
</Row>
|
||||
</Row>
|
||||
</Col>
|
||||
</Box>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, ReactNode } from "react";
|
||||
import {
|
||||
Switch,
|
||||
Route,
|
||||
@ -27,6 +27,7 @@ import "~/views/apps/links/css/custom.css";
|
||||
import "~/views/apps/publish/css/custom.css";
|
||||
import { Workspace } from "~/types";
|
||||
import { getGroupFromWorkspace } from "~/logic/lib/workspace";
|
||||
import {GroupSummary} from "./GroupSummary";
|
||||
|
||||
type GroupsPaneProps = StoreState & {
|
||||
baseUrl: string;
|
||||
@ -192,8 +193,21 @@ export function GroupsPane(props: GroupsPaneProps) {
|
||||
path={relativePath("")}
|
||||
render={(routeProps) => {
|
||||
const hasDescription = groupAssociation?.metadata?.description;
|
||||
const description = (hasDescription && hasDescription !== "")
|
||||
? hasDescription : "Create or select a channel to get started"
|
||||
let summary: ReactNode;
|
||||
if(groupAssociation?.group) {
|
||||
const memberCount = props.groups[groupAssociation.group].members.size;
|
||||
summary = <GroupSummary
|
||||
memberCount={memberCount}
|
||||
channelCount={0}
|
||||
metadata={groupAssociation.metadata}
|
||||
/>
|
||||
} else {
|
||||
summary = (<Box p="4"><Text fontSize="0" color='gray'>
|
||||
Create or select a channel to get started
|
||||
</Text></Box>);
|
||||
|
||||
|
||||
}
|
||||
const title = groupAssociation?.metadata?.title ?? 'Landscape';
|
||||
return (
|
||||
<>
|
||||
@ -207,9 +221,7 @@ export function GroupsPane(props: GroupsPaneProps) {
|
||||
display={["none", "flex"]}
|
||||
p='4'
|
||||
>
|
||||
<Box p="4"><Text fontSize="0" color='gray'>
|
||||
{description}
|
||||
</Text></Box>
|
||||
{summary}
|
||||
</Col>
|
||||
{popovers(routeProps, baseUrl)}
|
||||
</Skeleton>
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
Icon,
|
||||
Box,
|
||||
Text,
|
||||
ManagedTextInputField as Input
|
||||
ManagedTextInputField as Input,
|
||||
} from "@tlon/indigo-react";
|
||||
import { Formik, Form, FormikHelpers, useFormikContext } from "formik";
|
||||
import { AsyncButton } from "~/views/components/AsyncButton";
|
||||
@ -14,12 +14,14 @@ import * as Yup from "yup";
|
||||
import { Groups, Rolodex } from "~/types";
|
||||
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
import { RouteComponentProps, useHistory } from "react-router-dom";
|
||||
import urbitOb from "urbit-ob";
|
||||
import {resourceFromPath} from "~/logic/lib/group";
|
||||
import { resourceFromPath } from "~/logic/lib/group";
|
||||
import { StatelessAsyncButton } from "~/views/components/StatelessAsyncButton";
|
||||
import { uxToHex, getModuleIcon } from "~/logic/lib/util";
|
||||
import {FormError} from "~/views/components/FormError";
|
||||
import { FormError } from "~/views/components/FormError";
|
||||
import { MetadataIcon } from "./MetadataIcon";
|
||||
import { GroupSummary } from "./GroupSummary";
|
||||
|
||||
const formSchema = Yup.object({
|
||||
group: Yup.string()
|
||||
@ -41,23 +43,25 @@ interface JoinGroupProps {
|
||||
groups: Groups;
|
||||
contacts: Rolodex;
|
||||
api: GlobalApi;
|
||||
autojoin: string | null;
|
||||
autojoin?: string;
|
||||
inviteUid?: string;
|
||||
}
|
||||
|
||||
function Autojoin(props: { autojoin: string | null; }) {
|
||||
function Autojoin(props: { autojoin: string | null }) {
|
||||
const { submitForm } = useFormikContext();
|
||||
|
||||
useEffect(() => {
|
||||
if(props.autojoin) {
|
||||
if (props.autojoin) {
|
||||
submitForm();
|
||||
}
|
||||
},[]);
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function JoinGroup(props: JoinGroupProps & RouteComponentProps) {
|
||||
const { api, history, autojoin } = props;
|
||||
export function JoinGroup(props: JoinGroupProps) {
|
||||
const { api, autojoin } = props;
|
||||
const history = useHistory();
|
||||
const initialValues: FormSchema = {
|
||||
group: autojoin || "",
|
||||
};
|
||||
@ -67,12 +71,14 @@ export function JoinGroup(props: JoinGroupProps & RouteComponentProps) {
|
||||
|
||||
const onConfirm = useCallback(async () => {
|
||||
const { group } = preview;
|
||||
await api.contacts.join(resourceFromPath(group))
|
||||
await api.contacts.join(resourceFromPath(group));
|
||||
if (props.inviteUid) {
|
||||
api.invite.accept("contacts", props.inviteUid);
|
||||
}
|
||||
await waiter(({ contacts, groups }) => {
|
||||
return group in contacts && group in groups;
|
||||
});
|
||||
history.push(`/~landscape${group}`);
|
||||
|
||||
}, [api, preview, waiter]);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
@ -86,12 +92,17 @@ export function JoinGroup(props: JoinGroupProps & RouteComponentProps) {
|
||||
setPreview(prev);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
if(!(e instanceof Error)) {
|
||||
if (!(e instanceof Error)) {
|
||||
actions.setStatus({ error: "Unknown error" });
|
||||
} else if (e.message === "no-permissions" ) {
|
||||
actions.setStatus({ error: "Unable to join group, you do not have the correct permissions" })
|
||||
} else if (e.message === "offline" ) {
|
||||
actions.setStatus({ error: "Group host is offline, please try again later" });
|
||||
} else if (e.message === "no-permissions") {
|
||||
actions.setStatus({
|
||||
error:
|
||||
"Unable to join group, you do not have the correct permissions",
|
||||
});
|
||||
} else if (e.message === "offline") {
|
||||
actions.setStatus({
|
||||
error: "Group host is offline, please try again later",
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -100,64 +111,65 @@ export function JoinGroup(props: JoinGroupProps & RouteComponentProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Col overflowY="auto" p="4">
|
||||
<Col width="100%" alignItems="center" overflowY="auto" p="4">
|
||||
<Box mb={3}>
|
||||
<Text fontSize="2" fontWeight="bold">Join a Group</Text>
|
||||
<Text fontSize="2" fontWeight="bold">
|
||||
Join a Group
|
||||
</Text>
|
||||
</Box>
|
||||
{ preview ? (
|
||||
<Col gapY="4">
|
||||
<Row gapX="2">
|
||||
<Box
|
||||
borderRadius="1"
|
||||
width="36px" height="36px"
|
||||
bg={`#${uxToHex(preview.metadata.color)}`}
|
||||
/>
|
||||
<Col justifyContent="space-between">
|
||||
<Text fontSize="1">{preview.metadata.title}</Text>
|
||||
<Row gapX="2" justifyContent="space-between">
|
||||
<Text fontSize="1" gray>{preview.members} participants</Text>
|
||||
<Text fontSize="1" gray>{preview["channel-count"]} channels</Text>
|
||||
{preview ? (
|
||||
<GroupSummary
|
||||
metadata={preview.metadata}
|
||||
memberCount={preview?.members}
|
||||
channelCount={preview?.["channel-count"]}
|
||||
>
|
||||
<Col
|
||||
gapY="2"
|
||||
p="2"
|
||||
borderRadius="2"
|
||||
border="1"
|
||||
borderColor="washedGray"
|
||||
bg="washedBlue"
|
||||
>
|
||||
<Text gray fontSize="1">
|
||||
Channels
|
||||
</Text>
|
||||
{Object.values(preview.channels).map(({ metadata }: any) => (
|
||||
<Row>
|
||||
<Icon
|
||||
mr="2"
|
||||
color="blue"
|
||||
icon={getModuleIcon(metadata.module) as any}
|
||||
/>
|
||||
<Text color="blue">{metadata.title} </Text>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
{preview.metadata.description && (
|
||||
<Text gray fontSize="1">{preview.metadata.description}</Text>
|
||||
)}
|
||||
<Col gapY="2" p="2" borderRadius="2" border="1" borderColor="washedGray" bg="washedBlue">
|
||||
<Text gray fontSize="1">Channels</Text>
|
||||
{Object.values(preview.channels).map(({ metadata }: any) => (
|
||||
<Row>
|
||||
<Icon mr="2" color="blue" icon={getModuleIcon(metadata.module) as any} />
|
||||
<Text color="blue">{metadata.title} </Text>
|
||||
</Row>
|
||||
))}
|
||||
))}
|
||||
</Col>
|
||||
<StatelessAsyncButton primary name="join" onClick={onConfirm}>
|
||||
Join {preview.metadata.title}
|
||||
</StatelessAsyncButton>
|
||||
</Col>
|
||||
|
||||
</GroupSummary>
|
||||
) : (
|
||||
<Formik
|
||||
validationSchema={formSchema}
|
||||
initialValues={initialValues}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Form>
|
||||
<Autojoin autojoin={autojoin} />
|
||||
<Col gapY="4">
|
||||
<Input
|
||||
id="group"
|
||||
label="Group"
|
||||
caption="What group are you joining?"
|
||||
placeholder="~sampel-palnet/test-group"
|
||||
/>
|
||||
<AsyncButton>Join Group</AsyncButton>
|
||||
<FormError />
|
||||
</Col>
|
||||
</Form>
|
||||
</Formik>
|
||||
)}
|
||||
<Col width="100%" maxWidth="300px" gapY="4">
|
||||
<Formik
|
||||
validationSchema={formSchema}
|
||||
initialValues={initialValues}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Form style={{ display: "contents" }}>
|
||||
<Autojoin autojoin={autojoin} />
|
||||
<Input
|
||||
id="group"
|
||||
label="Group"
|
||||
caption="What group are you joining?"
|
||||
placeholder="~sampel-palnet/test-group"
|
||||
/>
|
||||
<AsyncButton mt="4">Join Group</AsyncButton>
|
||||
<FormError mt="4" />
|
||||
</Form>
|
||||
</Formik>
|
||||
</Col>
|
||||
)}
|
||||
</Col>
|
||||
</>
|
||||
);
|
||||
|
@ -0,0 +1,22 @@
|
||||
import React from "react";
|
||||
import { Box, Image } from "@tlon/indigo-react";
|
||||
|
||||
import { uxToHex } from "~/logic/lib/util";
|
||||
import { Metadata } from "~/types";
|
||||
import { PropFunc } from "~/types/util";
|
||||
|
||||
type MetadataIconProps = PropFunc<typeof Box> & {
|
||||
metadata: Metadata;
|
||||
};
|
||||
|
||||
export function MetadataIcon(props: MetadataIconProps) {
|
||||
const { metadata, ...rest } = props;
|
||||
|
||||
const bgColor = metadata.picture ? {} : { bg: `#${uxToHex(metadata.color)}` };
|
||||
|
||||
return (
|
||||
<Box {...bgColor} {...rest}>
|
||||
{metadata.picture && <Image height="100%" src={metadata.picture} />}
|
||||
</Box>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user