Skip to content

Commit

Permalink
enhancement(Webhooks): quick design update (#10615)
Browse files Browse the repository at this point in the history
  • Loading branch information
Betree committed Aug 27, 2024
1 parent 7ca4a8b commit 29a7224
Show file tree
Hide file tree
Showing 20 changed files with 159 additions and 139 deletions.
184 changes: 83 additions & 101 deletions components/edit-collective/sections/Webhooks.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { graphql } from '@apollo/client/react/hoc';
import { InfoCircle } from '@styled-icons/boxicons-regular/InfoCircle';
import { Add } from '@styled-icons/material/Add';
import { Close } from '@styled-icons/material/Close';
import { cloneDeep, difference, get, pick } from 'lodash';
import { Info, PlusCircle, Save, Trash, WebhookIcon } from 'lucide-react';
import memoizeOne from 'memoize-one';
import { FormattedMessage, injectIntl } from 'react-intl';
import { isURL } from 'validator';
Expand All @@ -17,15 +15,15 @@ import { gqlV1 } from '../../../lib/graphql/helpers';
import { i18nWebhookEventType } from '../../../lib/i18n/webhook-event-type';
import { compose } from '../../../lib/utils';

import { Box, Flex } from '../../Grid';
import { getI18nLink } from '../../I18nFormatters';
import Loading from '../../Loading';
import MessageBox from '../../MessageBox';
import StyledButton from '../../StyledButton';
import StyledHr from '../../StyledHr';
import StyledInputGroup from '../../StyledInputGroup';
import StyledSelect from '../../StyledSelect';
import { Label, P, Span } from '../../Text';
import { Button } from '../../ui/Button';
import { Label } from '../../ui/Label';
import { Separator } from '../../ui/Separator';
import { toast } from '../../ui/useToast';

import WebhookActivityInfoModal, { hasWebhookEventInfo } from './WebhookActivityInfoModal';

Expand All @@ -49,7 +47,6 @@ class Webhooks extends React.Component {
webhooks: cloneDeep(this.getWebhooksFromProps(props)),
isLoaded: false,
status: null,
error: '',
};
}

Expand Down Expand Up @@ -170,62 +167,58 @@ class Webhooks extends React.Component {
}, 3000);
} catch (e) {
const message = getErrorFromGraphqlException(e).message;
this.setState({ status: 'error', error: message });
toast({ variant: 'error', message });
this.setState({ status: null });
}
};

renderWebhook = (webhook, index) => {
const { intl, data } = this.props;

return (
<Flex
py={4}
key={index}
width={[0.9, 1]}
mx={['auto', 0]}
px={[0, 3, 0]}
flexWrap="wrap"
flexDirection="row-reverse"
justifyContent="space-between"
>
<Box my={[0, 3]}>
<StyledButton
width={1}
py={1}
px={3}
buttonSize="small"
buttonStyle="standard"
<div key={index} className="rounded-lg border bg-white p-6 text-card-foreground shadow-sm">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="rounded-full bg-slate-100 p-2">
<WebhookIcon size={24} />
</div>
<span className="text-lg font-bold">
<FormattedMessage defaultMessage="Webhook #{index}" id="webhook.index" values={{ index: index + 1 }} />
</span>
</div>
<Button
variant="outlineDestructive"
onClick={() => this.removeWebhook(index)}
title={intl.formatMessage({ id: 'webhooks.remove', defaultMessage: 'Remove webhook' })}
>
<Close size="1.2em" />
{' '}
<FormattedMessage id="webhooks.remove" defaultMessage="Remove webhook" />
</StyledButton>
</Box>

<Box width={[1, 0.75]}>
<Box mb={4}>
<Label fontSize="14px" mb={1}>
<Trash size={14} alt={intl.formatMessage({ id: 'webhooks.remove', defaultMessage: 'Remove webhook' })} />
</Button>
</div>

<div className="mt-4 flex flex-col gap-2">
<div>
<Label htmlFor={`webhook-url-${index}`} className="text-sm font-medium">
<FormattedMessage id="webhooks.url.label" defaultMessage="URL" />
</Label>
<StyledInputGroup
id={`webhook-url-${index}`}
type="type"
name="webhookUrl"
prepend="https://"
error={!this.validateWebhookUrl(webhook.webhookUrl)}
value={this.cleanWebhookUrl(webhook.webhookUrl)}
onChange={({ target }) => this.editWebhook(index, 'webhookUrl', target.value)}
/>
</Box>
<Box>
<Label fontSize="14px" mb={1}>
</div>
<div>
<Label htmlFor={`event-type-select-${index}`}>
<FormattedMessage defaultMessage="Activity" id="ZmlNQ3" />
</Label>
<Flex alignItems="center">
<div className="flex items-center">
<StyledSelect
inputId={`event-type-select-${index}`}
minWidth={300}
isSearchable={false}
inputId="event-type-select"
options={this.getEventTypes(data.Collective).map(eventType => ({
label: i18nWebhookEventType(intl, eventType),
value: eventType,
Expand All @@ -234,18 +227,18 @@ class Webhooks extends React.Component {
onChange={({ value }) => this.editWebhook(index, 'type', value)}
/>
{hasWebhookEventInfo(webhook.type) && (
<StyledButton
buttonSize="tiny"
isBorderless
<Button
variant="ghost"
size="icon"
className="ml-2"
title={intl.formatMessage({ id: 'moreInfo', defaultMessage: 'More info' })}
onClick={() => this.setState({ moreInfoModal: webhook.type })}
ml={2}
>
<InfoCircle size={24} color="#a3a3a3" />
</StyledButton>
<Info className="text-slate-400" size={18} />
</Button>
)}
</Flex>
</Box>
</div>
</div>
{data.Collective.isHost &&
[WebhookEvents.COLLECTIVE_EXPENSE_CREATED, WebhookEvents.COLLECTIVE_TRANSACTION_CREATED].includes(
webhook.type,
Expand All @@ -264,23 +257,22 @@ class Webhooks extends React.Component {
onClose={() => this.setState({ moreInfoModal: null })}
/>
)}
</Box>
</Flex>
</div>
</div>
);
};

render() {
const { webhooks, status, error } = this.state;
const { webhooks, status } = this.state;
const { data } = this.props;
const webhooksCount = webhooks.length;

if (data.loading) {
return <Loading />;
}

return (
<div>
<P fontSize="14px" lineHeight="18px">
<p className="text-sm text-muted-foreground">
<FormattedMessage
defaultMessage="You can use Webhooks to build custom integrations with Open Collective. Slack and Discord webhooks are natively supported. You can also integrate them with tools like Zapier, IFTTT, or Huginn. Learn more about this from <DocLink>the documentation</DocLink> or see how you can go further using our <GraphqlAPILink>public GraphQL API</GraphqlAPILink>."
id="gN829M"
Expand All @@ -295,60 +287,49 @@ class Webhooks extends React.Component {
}),
}}
/>
</P>
</p>

<div>{webhooks.map(this.renderWebhook)}</div>
<Separator className="my-6" />

{webhooksCount > 0 && <StyledHr borderColor="black.300" />}

<Box width={[0.9, 0.75]} mx={['auto', 0]} my={3}>
<StyledButton
width={1}
px={[0, 3, 0]}
borderRadius={6}
buttonSize="medium"
buttonStyle="standard"
css={'border-style: dashed'}
onClick={() => this.addWebhook()}
>
<Add size="1.2em" />
{' '}
<div className="mb-6 mt-8 flex items-center justify-between">
<h3 className="text-xl font-bold">
<FormattedMessage
defaultMessage="Add {existingWebhooksCount, select, 0 {your first} other {another}} webhook"
id="M3IHfl"
values={{ existingWebhooksCount: webhooksCount }}
defaultMessage="Webhooks for {collective}"
id="RHr16v"
values={{ collective: data.Collective.name || `@${data.Collective.slug}` }}
/>
</StyledButton>
</Box>

{status === 'error' && (
<Box my={3}>
<MessageBox type="error">{error}</MessageBox>
</Box>
</h3>
<Button onClick={this.addWebhook}>
<PlusCircle className="mr-2 h-5 w-5" /> <FormattedMessage defaultMessage="New Webhook" id="q7eF+t" />
</Button>
</div>

{webhooks.length === 0 ? (
<div className="rounded-lg border bg-card py-12 text-center text-card-foreground shadow-sm">
<WebhookIcon className="mx-auto mb-4 h-16 w-16 text-gray-400" />
<h4 className="text-lg font-semibold">
<FormattedMessage defaultMessage="No webhooks configured" id="prsPHX" />
</h4>
</div>
) : (
<div className="flex flex-col gap-6">{webhooks.map(this.renderWebhook)}</div>
)}

<Box mr={3}>
<StyledButton
px={4}
buttonSize="medium"
buttonStyle="primary"
onClick={this.handleSubmit}
loading={status === 'loading'}
disabled={data.loading || !this.state.modified || status === 'invalid'}
>
{status === 'saved' ? (
<Span textTransform="capitalize">
<FormattedMessage id="saved" defaultMessage="Saved" />
</Span>
) : (
<FormattedMessage
id="webhooks.save"
defaultMessage="Save {count} webhooks"
values={{ count: webhooksCount }}
/>
)}
</StyledButton>
</Box>
<Button
className="mt-8 w-full"
onClick={this.handleSubmit}
loading={status === 'loading'}
disabled={data.loading || !this.state.modified || status === 'invalid'}
>
<Save size={16} className="mr-2" />
{status === 'saved' ? (
<span>
<FormattedMessage id="saved" defaultMessage="Saved" />
</span>
) : (
<FormattedMessage id="save" defaultMessage="Save" />
)}
</Button>
</div>
);
}
Expand All @@ -358,6 +339,7 @@ const editCollectiveWebhooksQuery = gqlV1/* GraphQL */ `
query EditCollectiveWebhooks($collectiveSlug: String) {
Collective(slug: $collectiveSlug) {
id
name
type
slug
isHost
Expand Down
6 changes: 4 additions & 2 deletions lang/ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -2360,7 +2360,6 @@
"M0vCGv": "Overdue",
"M0vlyv": "Admin of {account} since {date}",
"m2cP6g": "Create and manage webhooks.",
"M3IHfl": "Add {existingWebhooksCount, select, 0 {your first} other {another}} webhook",
"M4DHuK": "Expense <Expense>{expenseDescription}</Expense> payment failed",
"m52qTZ": "{parentName}'s {childType, select, EVENT {Event} FUND {Fund} PROJECT {Project} other {Account}}: {childName}",
"M5aWJU": "Name ascending",
Expand Down Expand Up @@ -2960,6 +2959,7 @@
"profile.incognito": "Incognito",
"project.created": "Your Project has been created.",
"Projects": "Projects",
"prsPHX": "No webhooks configured",
"PSWufs": "Use this to communicate with your contributors about the reason of this change. If leaving Open Collective, you can also provide instructions on how to continue supporting your collective.",
"PtUfDA": "A unique 32 character identifier (previously names as Contribution ID)",
"Public": "Public",
Expand All @@ -2975,6 +2975,7 @@
"Q/kLys": "Agreement created",
"Q0lxqm": "CVV/CVC",
"q1szYC": "Vendor <Vendor></Vendor> edited",
"q7eF+t": "New Webhook",
"QaaW8s": "The page might take a few seconds to fully update",
"qAF9zY": "Two factor authentication removed",
"qaIW32": "Strong password recommended. Short or weak one restricted. <link>The strength of a password is a function of length, complexity, and unpredictability.</link>",
Expand Down Expand Up @@ -3078,6 +3079,7 @@
"rFP53b": "Fields will be exported in the order they're displayed in below. Drag and drop them to reorder them.",
"rg47YZ": "eg: VAT, GST, etc.",
"RHApk3": "About contribution tiers",
"RHr16v": "Webhooks for {collective}",
"RilevA": "Vendors",
"rj9VjD": "Expense moved",
"RJt89q": "Agreement deleted successfully",
Expand Down Expand Up @@ -3770,6 +3772,7 @@
"wdcTBA": "Read through our documentation",
"wdqJMo": "Manually credit Collective budgets with funds received outside the platform, such as other e-commerce or fundraising tools.",
"wDruhW": "Has no receipts missing",
"webhook.index": "Webhook #{index}",
"WebhookEvents.All": "All",
"WebhookEvents.COLLECTIVE_APPLY": "New collective application",
"WebhookEvents.COLLECTIVE_APPROVED": "Collective application approved",
Expand All @@ -3784,7 +3787,6 @@
"WebhookEvents.TICKET_CONFIRMED": "Ticket confirmed",
"WebhookEvents.VIRTUAL_CARD_PURCHASE": "New virtual card purchase",
"webhooks.remove": "Remove webhook",
"webhooks.save": "Save {count} webhooks",
"webhooks.url.label": "URL",
"WEDTW5": "The tax information you provided for {maxExpiredYear} has expired. Please fill out the form again.",
"week": "Weekly",
Expand Down
6 changes: 4 additions & 2 deletions lang/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -2360,7 +2360,6 @@
"M0vCGv": "Overdue",
"M0vlyv": "Admin of {account} since {date}",
"m2cP6g": "Create and manage webhooks.",
"M3IHfl": "Add {existingWebhooksCount, select, 0 {your first} other {another}} webhook",
"M4DHuK": "Expense <Expense>{expenseDescription}</Expense> payment failed",
"m52qTZ": "{parentName}'s {childType, select, EVENT {Event} FUND {Fund} PROJECT {Project} other {Account}}: {childName}",
"M5aWJU": "Name ascending",
Expand Down Expand Up @@ -2960,6 +2959,7 @@
"profile.incognito": "Anonymně",
"project.created": "Your Project has been created.",
"Projects": "Projects",
"prsPHX": "No webhooks configured",
"PSWufs": "Use this to communicate with your contributors about the reason of this change. If leaving Open Collective, you can also provide instructions on how to continue supporting your collective.",
"PtUfDA": "A unique 32 character identifier (previously names as Contribution ID)",
"Public": "Public",
Expand All @@ -2975,6 +2975,7 @@
"Q/kLys": "Agreement created",
"Q0lxqm": "CVV/CVC",
"q1szYC": "Vendor <Vendor></Vendor> edited",
"q7eF+t": "New Webhook",
"QaaW8s": "The page might take a few seconds to fully update",
"qAF9zY": "Two factor authentication removed",
"qaIW32": "Strong password recommended. Short or weak one restricted. <link>The strength of a password is a function of length, complexity, and unpredictability.</link>",
Expand Down Expand Up @@ -3078,6 +3079,7 @@
"rFP53b": "Fields will be exported in the order they're displayed in below. Drag and drop them to reorder them.",
"rg47YZ": "eg: VAT, GST, etc.",
"RHApk3": "About contribution tiers",
"RHr16v": "Webhooks for {collective}",
"RilevA": "Vendors",
"rj9VjD": "Expense moved",
"RJt89q": "Agreement deleted successfully",
Expand Down Expand Up @@ -3770,6 +3772,7 @@
"wdcTBA": "Read through our documentation",
"wdqJMo": "Manually credit Collective budgets with funds received outside the platform, such as other e-commerce or fundraising tools.",
"wDruhW": "Has no receipts missing",
"webhook.index": "Webhook #{index}",
"WebhookEvents.All": "All",
"WebhookEvents.COLLECTIVE_APPLY": "New collective application",
"WebhookEvents.COLLECTIVE_APPROVED": "Collective application approved",
Expand All @@ -3784,7 +3787,6 @@
"WebhookEvents.TICKET_CONFIRMED": "Ticket confirmed",
"WebhookEvents.VIRTUAL_CARD_PURCHASE": "New virtual card purchase",
"webhooks.remove": "Odstranit webhook",
"webhooks.save": "Uložit {count} webhooků",
"webhooks.url.label": "URL",
"WEDTW5": "The tax information you provided for {maxExpiredYear} has expired. Please fill out the form again.",
"week": "Weekly",
Expand Down
Loading

0 comments on commit 29a7224

Please sign in to comment.