Skip to content

Commit

Permalink
feat(ui): Display invite links on the staff page
Browse files Browse the repository at this point in the history
This adds the invitation URL to the server response for invitations and
it then leverages that URL on the client.  The hope here is to address
issue getmeli#247 and ideally just make it easier to use Meli without having
to have a working email server to send out invites.

Design note: I very deliberately pass a URL back from the server
instead of just the bare token.  The reason is that if I pass back
a bare token then the UI will have to formulate the URL which
means duplicating that logic both client and server side which
I have a visceral dislike for (very bad code smell, IMHO).  This
way, only the server needs to know how to construct the URLs
and it can unilaterally change the scheme if it wants with
_zero_ impact on the client.  Hypermedia for the win.
  • Loading branch information
mt35-rs committed Feb 2, 2022
1 parent bade3c5 commit b5cf2d9
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 15 deletions.
10 changes: 9 additions & 1 deletion server/src/entities/orgs/handlers/invites/add-invite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ const validators = [
})),
];

/**
* Ensure that the construction of these URLs is done
* in exactly one place only.
*/
export function inviteUrl(invite: Invite): string {
return `${env.MELI_URL}/invite?token=${invite.token}`;
}

async function handler(req: Request, res: Response): Promise<void> {
const { orgId } = req.params;
const { email, admin } = req.body;
Expand Down Expand Up @@ -53,7 +61,7 @@ async function handler(req: Request, res: Response): Promise<void> {

await sendInvite(email, {
org: org.name,
url: `${env.MELI_URL}/invite?token=${invite.token}`,
url: inviteUrl(invite),
});

emitEvent(EventType.org_invite_added, {
Expand Down
2 changes: 2 additions & 0 deletions server/src/entities/orgs/serialize-invite.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { inviteUrl } from './handlers/invites/add-invite';
import { Invite } from './invite';

export function serializeInvite(invite: Invite) {
return {
_id: invite._id,
email: invite.email,
expiresAt: invite.expiresAt,
url: inviteUrl(invite),
memberOptions: invite.memberOptions,
};
}
30 changes: 16 additions & 14 deletions ui/src/components/orgs/staff/invites/InviteView.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTimes } from '@fortawesome/free-solid-svg-icons';
import { Invite } from './invite';
import { DeleteInvite } from './DeleteInvite';
import { FromNow } from '../../../../commons/components/FromNow';
import { ButtonIcon } from '../../../../commons/components/ButtonIcon';
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faLink, faTimes } from "@fortawesome/free-solid-svg-icons";
import { Invite } from "./invite";
import { DeleteInvite } from "./DeleteInvite";
import { FromNow } from "../../../../commons/components/FromNow";
import { ButtonIcon } from "../../../../commons/components/ButtonIcon";

export function InviteView({ invite, onDelete }: {
export function InviteView({
invite,
onDelete,
}: {
invite: Invite;
onDelete: () => void;
}) {
Expand All @@ -15,6 +18,9 @@ export function InviteView({ invite, onDelete }: {
return (
<div className="list-group-item d-flex justify-content-between align-items-center">
<div className="flex-grow-1 d-flex align-items-center">
<a href={invite.url}>
<FontAwesomeIcon icon={faLink} />
</a>{" "}
<strong className="mr-3">{invite.email}</strong>
{invite.memberOptions.admin && (
<span className="badge badge-primary">admin</span>
Expand All @@ -23,14 +29,10 @@ export function InviteView({ invite, onDelete }: {

<div className="d-flex align-items-center">
{expired ? (
<div className="text-danger">
expired
</div>
<div className="text-danger">expired</div>
) : (
<>
<div className="badge badge-warning mr-3">
pending invitation
</div>
<div className="badge badge-warning mr-3">pending invitation</div>
<FromNow date={invite.expiresAt} label="Expires" />
</>
)}
Expand Down
1 change: 1 addition & 0 deletions ui/src/components/orgs/staff/invites/invite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export interface Invite {
_id: string;
email: string;
expiresAt: Date;
url: string;
memberOptions: {
admin: boolean;
};
Expand Down

0 comments on commit b5cf2d9

Please sign in to comment.