hark-fe: toggle mute in group settings

This commit is contained in:
Liam Fitzgerald 2020-10-28 16:00:14 +10:00
parent 0510b723d7
commit ab781cfbc3
4 changed files with 272 additions and 173 deletions

View File

@ -1,173 +0,0 @@
import React, { useEffect } from "react";
import { AsyncButton } from "~/views/components/AsyncButton";
import * as Yup from "yup";
import {
Box,
ManagedTextInputField as Input,
ManagedCheckboxField as Checkbox,
Col,
Label,
Button,
} from "@tlon/indigo-react";
import { Formik, Form, useFormikContext, FormikHelpers } from "formik";
import { FormError } from "~/views/components/FormError";
import { Group, GroupPolicy } from "~/types/group-update";
import { Enc } from "~/types/noun";
import { Association } from "~/types/metadata-update";
import GlobalApi from "~/logic/api/global";
import { resourceFromPath, roleForShip } from "~/logic/lib/group";
import { StatelessAsyncButton } from "~/views/components/StatelessAsyncButton";
import { ColorInput } from "~/views/components/ColorInput";
import { useHistory } from "react-router-dom";
import { uxToHex } from '~/logic/lib/util';
interface FormSchema {
title: string;
description: string;
color: string;
isPrivate: boolean;
}
const formSchema = Yup.object({
title: Yup.string().required("Group must have a name"),
description: Yup.string(),
color: Yup.string(),
isPrivate: Yup.boolean(),
});
interface GroupSettingsProps {
group: Group;
association: Association;
api: GlobalApi;
}
export function GroupSettings(props: GroupSettingsProps) {
const { group, association } = props;
const { metadata } = association;
const history = useHistory();
const currentPrivate = "invite" in props.group.policy;
const initialValues: FormSchema = {
title: metadata?.title,
description: metadata?.description,
color: metadata?.color,
isPrivate: currentPrivate,
};
const onSubmit = async (
values: FormSchema,
actions: FormikHelpers<FormSchema>
) => {
try {
const { title, description, color, isPrivate } = values;
const uxColor = uxToHex(color);
await props.api.metadata.update(props.association, {
title,
description,
color: uxColor
});
if (isPrivate !== currentPrivate) {
const resource = resourceFromPath(props.association["group-path"]);
const newPolicy: Enc<GroupPolicy> = isPrivate
? { invite: { pending: [] } }
: { open: { banRanks: [], banned: [] } };
const diff = { replace: newPolicy };
await props.api.groups.changePolicy(resource, diff);
}
actions.setStatus({ success: null });
} catch (e) {
console.log(e);
actions.setStatus({ error: e.message });
}
};
const onDelete = async () => {
await props.api.contacts.delete(association["group-path"]);
history.push("/");
};
const disabled =
resourceFromPath(association["group-path"]).ship.slice(1) !== window.ship &&
roleForShip(group, window.ship) !== "admin";
return (
<Box height="100%" overflowY="auto">
<Formik
validationSchema={formSchema}
initialValues={initialValues}
onSubmit={onSubmit}
>
<Form style={{ display: "contents" }}>
<Box
maxWidth="300px"
gridTemplateColumns="1fr"
gridAutoRows="auto"
display="grid"
gridRowGap={4}
my={3}
mx={4}
>
{!disabled ? (
<Col>
<Label>Delete Group</Label>
<Label gray mt="2">
Permanently delete this group. (All current members will no
longer see this group.)
</Label>
<StatelessAsyncButton onClick={onDelete} mt={2} destructive>
Delete this group
</StatelessAsyncButton>
</Col>
) : (
<Col>
<Label>Leave Group</Label>
<Label gray mt="2">
Leave this group. You can rejoin if it is an open group, or if
you are reinvited
</Label>
<StatelessAsyncButton onClick={onDelete} mt={2} destructive>
Leave this group
</StatelessAsyncButton>
</Col>
)}
<Box borderBottom={1} borderBottomColor="washedGray" />
<Input
id="title"
label="Group Name"
caption="The name for your group to be called by"
disabled={disabled}
/>
<Input
id="description"
label="Group Description"
caption="The description of your group"
disabled={disabled}
/>
<ColorInput
id="color"
label="Group color"
caption="A color to represent your group"
disabled={disabled}
/>
<Checkbox
id="isPrivate"
label="Private group"
caption="If enabled, users must be invited to join the group"
disabled={disabled}
/>
<AsyncButton
disabled={disabled}
primary
loadingText="Updating.."
border
>
Save
</AsyncButton>
<FormError message="Failed to update settings" />
</Box>
</Form>
</Formik>
</Box>
);
}

View File

@ -0,0 +1,134 @@
import React, { useEffect } from "react";
import { AsyncButton } from "~/views/components/AsyncButton";
import * as Yup from "yup";
import {
Box,
ManagedTextInputField as Input,
ManagedToggleSwitchField as Checkbox,
Col,
Label,
Button,
} from "@tlon/indigo-react";
import { Formik, Form, useFormikContext, FormikHelpers } from "formik";
import { FormError } from "~/views/components/FormError";
import { Group, GroupPolicy } from "~/types/group-update";
import { Enc } from "~/types/noun";
import { Association } from "~/types/metadata-update";
import GlobalApi from "~/logic/api/global";
import { resourceFromPath, roleForShip } from "~/logic/lib/group";
import { StatelessAsyncButton } from "~/views/components/StatelessAsyncButton";
import { ColorInput } from "~/views/components/ColorInput";
import { useHistory } from "react-router-dom";
import { uxToHex } from "~/logic/lib/util";
interface FormSchema {
title: string;
description: string;
color: string;
isPrivate: boolean;
}
const formSchema = Yup.object({
title: Yup.string().required("Group must have a name"),
description: Yup.string(),
color: Yup.string(),
isPrivate: Yup.boolean(),
});
interface GroupAdminSettingsProps {
group: Group;
association: Association;
api: GlobalApi;
}
export function GroupAdminSettings(props: GroupAdminSettingsProps) {
const { group, association } = props;
const { metadata } = association;
const history = useHistory();
const currentPrivate = "invite" in props.group.policy;
const initialValues: FormSchema = {
title: metadata?.title,
description: metadata?.description,
color: metadata?.color,
isPrivate: currentPrivate,
};
const onSubmit = async (
values: FormSchema,
actions: FormikHelpers<FormSchema>
) => {
try {
const { title, description, color, isPrivate } = values;
const uxColor = uxToHex(color);
await props.api.metadata.update(props.association, {
title,
description,
color: uxColor,
});
if (isPrivate !== currentPrivate) {
const resource = resourceFromPath(props.association["group-path"]);
const newPolicy: Enc<GroupPolicy> = isPrivate
? { invite: { pending: [] } }
: { open: { banRanks: [], banned: [] } };
const diff = { replace: newPolicy };
await props.api.groups.changePolicy(resource, diff);
}
actions.setStatus({ success: null });
} catch (e) {
console.log(e);
actions.setStatus({ error: e.message });
}
};
const disabled =
resourceFromPath(association["group-path"]).ship.slice(1) !== window.ship &&
roleForShip(group, window.ship) !== "admin";
return (
<Formik
validationSchema={formSchema}
initialValues={initialValues}
onSubmit={onSubmit}
>
<Form>
<Col gapY={4}>
<Input
id="title"
label="Group Name"
caption="The name for your group to be called by"
disabled={disabled}
/>
<Input
id="description"
label="Group Description"
caption="The description of your group"
disabled={disabled}
/>
<ColorInput
id="color"
label="Group color"
caption="A color to represent your group"
disabled={disabled}
/>
<Checkbox
id="isPrivate"
label="Private group"
caption="If enabled, users must be invited to join the group"
disabled={disabled}
/>
<AsyncButton
disabled={disabled}
primary
loadingText="Updating.."
border
>
Save
</AsyncButton>
<FormError message="Failed to update settings" />
</Col>
</Form>
</Formik>
);
}

View File

@ -0,0 +1,44 @@
import React, { useEffect } from "react";
import { AsyncButton } from "~/views/components/AsyncButton";
import * as Yup from "yup";
import {
Box,
ManagedTextInputField as Input,
ManagedToggleSwitchField as Checkbox,
Col,
Label,
Button,
} from "@tlon/indigo-react";
import { Formik, Form, useFormikContext, FormikHelpers } from "formik";
import { FormError } from "~/views/components/FormError";
import { Group, GroupPolicy } from "~/types/group-update";
import { Enc } from "~/types/noun";
import { Association } from "~/types/metadata-update";
import GlobalApi from "~/logic/api/global";
import { resourceFromPath, roleForShip } from "~/logic/lib/group";
import { StatelessAsyncButton } from "~/views/components/StatelessAsyncButton";
import { ColorInput } from "~/views/components/ColorInput";
import { useHistory } from "react-router-dom";
import { uxToHex } from "~/logic/lib/util";
import { GroupAdminSettings } from "./Admin";
import { GroupPersonalSettings } from "./Personal";
import {GroupNotificationsConfig} from "~/types";
interface GroupSettingsProps {
group: Group;
association: Association;
api: GlobalApi;
notificationsGroupConfig: GroupNotificationsConfig;
}
export function GroupSettings(props: GroupSettingsProps) {
return (
<Box height="100%" overflowY="auto">
<Col maxWidth="384px" p="4" gapY="4">
<GroupPersonalSettings {...props} />
<Box borderBottom="1" borderBottomColor="washedGray" />
<GroupAdminSettings {...props} />
</Col>
</Box>
);
}

View File

@ -0,0 +1,94 @@
import React, { useCallback } from "react";
import { AsyncButton } from "~/views/components/AsyncButton";
import * as Yup from "yup";
import {
Box,
ManagedTextInputField as Input,
ManagedToggleSwitchField as Toggle,
Col,
Label,
Button,
} from "@tlon/indigo-react";
import { Formik, Form, useFormikContext, FormikHelpers } from "formik";
import { FormError } from "~/views/components/FormError";
import { Group, GroupPolicy } from "~/types/group-update";
import { Enc } from "~/types/noun";
import { Association } from "~/types/metadata-update";
import GlobalApi from "~/logic/api/global";
import { resourceFromPath, roleForShip } from "~/logic/lib/group";
import { StatelessAsyncButton } from "~/views/components/StatelessAsyncButton";
import { ColorInput } from "~/views/components/ColorInput";
import { useHistory } from "react-router-dom";
import { uxToHex } from "~/logic/lib/util";
import { FormikOnBlur } from "~/views/components/FormikOnBlur";
import {GroupNotificationsConfig} from "~/types";
function DeleteGroup(props: {
owner: boolean;
api: GlobalApi;
association: Association;
}) {
const history = useHistory();
const onDelete = async () => {
await props.api.contacts.delete(props.association["group-path"]);
history.push("/");
};
const action = props.owner ? "Delete" : "Leave";
const description = props.owner
? "Permanently delete this group. (All current members will no longer see this group.)"
: "Leave this group. You can rejoin if it is an open group, or if you are reinvited";
return (
<Col>
<Label>{action} Group</Label>
<Label gray mt="2">
{description}
</Label>
<StatelessAsyncButton onClick={onDelete} mt={2} destructive>
{action} this group
</StatelessAsyncButton>
</Col>
);
}
interface FormSchema {
watching: boolean;
}
export function GroupPersonalSettings(props: {
api: GlobalApi;
association: Association;
notificationsGroupConfig: GroupNotificationsConfig;
}) {
const groupPath = props.association['group-path'];
const watching = props.notificationsGroupConfig.findIndex(g => g === groupPath) !== -1;
const initialValues: FormSchema = {
watching
};
const onSubmit = async (values: FormSchema) => {
if(values.watching === watching) {
return;
}
const func = values.watching ? 'listenGroup' : 'ignoreGroup';
await props.api.hark[func](groupPath);
};
return (
<Col gapY="4">
<FormikOnBlur initialValues={initialValues} onSubmit={onSubmit}>
<Toggle
id="watching"
label="Notify me on group activity"
caption="Send me notifications when this group changes"
/>
</FormikOnBlur>
<DeleteGroup association={props.association} owner api={props.api} />
</Col>
);
}