Skip to content

Commit cee54f5

Browse files
committed
Update team member layout and show member count footer bar
1 parent d9f210b commit cee54f5

1 file changed

Lines changed: 120 additions & 95 deletions

File tree

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team

apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx

Lines changed: 120 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useForm } from "@conform-to/react";
22
import { parse } from "@conform-to/zod";
3-
import { EnvelopeIcon, LockOpenIcon, TrashIcon, UserPlusIcon } from "@heroicons/react/20/solid";
3+
import { EnvelopeIcon, NoSymbolIcon, UserPlusIcon } from "@heroicons/react/20/solid";
44
import { Form, type MetaFunction, useActionData } from "@remix-run/react";
55
import { type ActionFunction, type LoaderFunctionArgs, json } from "@remix-run/server-runtime";
66
import { useState } from "react";
@@ -9,11 +9,7 @@ import invariant from "tiny-invariant";
99
import { z } from "zod";
1010
import { UserAvatar } from "~/components/UserProfilePhoto";
1111
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
12-
import {
13-
MainHorizontallyCenteredContainer,
14-
PageBody,
15-
PageContainer,
16-
} from "~/components/layout/AppLayout";
12+
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
1713
import {
1814
Alert,
1915
AlertCancel,
@@ -27,7 +23,6 @@ import {
2723
import { Button, ButtonContent, LinkButton } from "~/components/primitives/Buttons";
2824
import { DateTime } from "~/components/primitives/DateTime";
2925
import { Header2, Header3 } from "~/components/primitives/Headers";
30-
import { InfoPanel } from "~/components/primitives/InfoPanel";
3126
import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader";
3227
import { Paragraph } from "~/components/primitives/Paragraph";
3328
import * as Property from "~/components/primitives/PropertyTable";
@@ -159,97 +154,120 @@ export default function Page() {
159154
))}
160155
</Property.Table>
161156
</AdminDebugTooltip>
157+
{!requiresUpgrade && (
158+
<LinkButton
159+
to={inviteTeamMemberPath(organization)}
160+
variant="primary/small"
161+
LeadingIcon={UserPlusIcon}
162+
>
163+
Invite a team member
164+
</LinkButton>
165+
)}
162166
</PageAccessories>
163167
</NavBar>
164-
<PageBody>
165-
<MainHorizontallyCenteredContainer>
166-
<Header2>
167-
Members{" "}
168-
<span className="font-normal text-text-dimmed">
169-
({limits.used}/{limits.limit})
170-
</span>
171-
</Header2>
172-
<ul className="divide-ui-border mt-3 flex w-full flex-col divide-y border-y border-grid-bright">
173-
{members.map((member) => (
174-
<li key={member.user.id} className="flex items-center gap-x-4 py-4">
175-
<UserAvatar
176-
avatarUrl={member.user.avatarUrl}
177-
name={member.user.name}
178-
className="size-10"
179-
/>
180-
<div className="flex flex-col gap-0.5">
181-
<Header3>
182-
{member.user.name}{" "}
183-
{member.user.id === user.id && <span className="text-text-dimmed">(You)</span>}
184-
</Header3>
185-
<Paragraph variant="small">{member.user.email}</Paragraph>
186-
</div>
187-
<div className="flex grow items-center justify-end gap-4">
188-
<LeaveRemoveButton
189-
userId={user.id}
190-
member={member}
191-
memberCount={members.length}
192-
/>
193-
</div>
194-
</li>
195-
))}
196-
</ul>
197-
198-
{invites.length > 0 && (
199-
<>
200-
<Header2 className="mb-3 mt-4">Pending invites</Header2>
201-
<ul className="flex w-full flex-col divide-y divide-charcoal-800 border-b border-grid-bright">
202-
{invites.map((invite) => (
203-
<li key={invite.id} className="flex items-center gap-4 py-4">
204-
<div className="rounded-md border border-charcoal-750 bg-charcoal-800 p-1.5">
205-
<EnvelopeIcon className="size-7 text-cyan-500" />
206-
</div>
168+
<PageBody scrollable={false}>
169+
<div className="grid max-h-full min-h-full grid-rows-[1fr_auto]">
170+
<div className="overflow-y-auto">
171+
<div className="mx-auto max-w-3xl px-4 pb-4 pt-20">
172+
{invites.length > 0 && (
173+
<>
174+
<Header2 className="mb-3 mt-4">Pending invites</Header2>
175+
<ul className="divide-ui-border mb-6 flex w-full flex-col divide-y border-y">
176+
{invites.map((invite) => (
177+
<li key={invite.id} className="flex items-center gap-4 py-4">
178+
<div className="rounded-md border border-charcoal-750 bg-charcoal-800 p-1.5">
179+
<EnvelopeIcon className="size-7 text-text-dimmed" />
180+
</div>
181+
<div className="flex flex-col gap-0.5">
182+
<Header3>{invite.email}</Header3>
183+
<Paragraph variant="small">
184+
Invite sent {<DateTime date={invite.updatedAt} />}
185+
</Paragraph>
186+
</div>
187+
<div className="flex grow items-center justify-end gap-x-2">
188+
<ResendButton invite={invite} />
189+
<RevokeButton invite={invite} />
190+
</div>
191+
</li>
192+
))}
193+
</ul>
194+
</>
195+
)}
196+
<Header2>Active team members</Header2>
197+
<ul className="divide-ui-border mt-3 flex w-full flex-col divide-y border-y border-grid-bright">
198+
{members.map((member) => (
199+
<li key={member.user.id} className="flex items-center gap-x-4 py-4">
200+
<UserAvatar
201+
avatarUrl={member.user.avatarUrl}
202+
name={member.user.name}
203+
className="size-10"
204+
/>
207205
<div className="flex flex-col gap-0.5">
208-
<Header3>{invite.email}</Header3>
209-
<Paragraph variant="small">
210-
Invite sent {<DateTime date={invite.updatedAt} />}
211-
</Paragraph>
206+
<Header3>
207+
{member.user.name}{" "}
208+
{member.user.id === user.id && (
209+
<span className="text-text-dimmed">(You)</span>
210+
)}
211+
</Header3>
212+
<Paragraph variant="small">{member.user.email}</Paragraph>
212213
</div>
213-
<div className="flex grow items-center justify-end gap-x-2">
214-
<ResendButton invite={invite} />
215-
<RevokeButton invite={invite} />
214+
<div className="flex grow items-center justify-end gap-4">
215+
<LeaveRemoveButton
216+
userId={user.id}
217+
member={member}
218+
memberCount={members.length}
219+
/>
216220
</div>
217221
</li>
218222
))}
219223
</ul>
220-
</>
221-
)}
222-
223-
{requiresUpgrade ? (
224-
<InfoPanel
225-
variant="upgrade"
226-
icon={LockOpenIcon}
227-
iconClassName="text-indigo-500"
228-
title="Unlock more team members"
229-
accessory={
230-
<LinkButton to={v3BillingPath(organization)} variant="secondary/small">
231-
Upgrade
232-
</LinkButton>
224+
</div>
225+
</div>
226+
227+
<div className="flex h-fit w-full items-center gap-4 border-t border-grid-bright bg-background-bright p-[0.86rem] pl-4">
228+
<SimpleTooltip
229+
button={
230+
<div className="size-6">
231+
<svg className="h-full w-full -rotate-90 overflow-visible">
232+
<circle
233+
className="fill-none stroke-grid-bright"
234+
strokeWidth="4"
235+
r="10"
236+
cx="12"
237+
cy="12"
238+
/>
239+
<circle
240+
className={`fill-none ${requiresUpgrade ? "stroke-error" : "stroke-success"}`}
241+
strokeWidth="4"
242+
r="10"
243+
cx="12"
244+
cy="12"
245+
strokeDasharray={`${(limits.used / limits.limit) * 62.8} 62.8`}
246+
strokeDashoffset="0"
247+
strokeLinecap="round"
248+
/>
249+
</svg>
250+
</div>
233251
}
234-
panelClassName="mt-4"
235-
>
236-
<Paragraph variant="small">
237-
You've used all {limits.limit} of your available team members. Upgrade your plan to
238-
enable more.
239-
</Paragraph>
240-
</InfoPanel>
241-
) : (
242-
<div className="mt-4 flex justify-end">
243-
<LinkButton
244-
to={inviteTeamMemberPath(organization)}
245-
variant={"secondary/small"}
246-
LeadingIcon={UserPlusIcon}
247-
>
248-
Invite a team member
252+
content={`${Math.round((limits.used / limits.limit) * 100)}%`}
253+
/>
254+
<div className="flex w-full items-center justify-between gap-6">
255+
{requiresUpgrade ? (
256+
<Header3 className="text-error">
257+
You've used all {limits.limit} of your team members. Upgrade your plan to enable
258+
more.
259+
</Header3>
260+
) : (
261+
<Header3>
262+
You've used {limits.used}/{limits.limit} of your team members
263+
</Header3>
264+
)}
265+
<LinkButton to={v3BillingPath(organization)} variant="primary/small">
266+
Upgrade
249267
</LinkButton>
250268
</div>
251-
)}
252-
</MainHorizontallyCenteredContainer>
269+
</div>
270+
</div>
253271
</PageBody>
254272
</PageContainer>
255273
);
@@ -275,6 +293,7 @@ function LeaveRemoveButton({
275293
Leave team
276294
</ButtonContent>
277295
}
296+
disableHoverableContent
278297
content="An organization requires at least 1 team member"
279298
/>
280299
);
@@ -332,7 +351,7 @@ function LeaveTeamModal({
332351
return (
333352
<Alert open={open} onOpenChange={(o) => setOpen(o)}>
334353
<AlertTrigger asChild>
335-
<Button variant="tertiary/small">{buttonText}</Button>
354+
<Button variant="secondary/small">{buttonText}</Button>
336355
</AlertTrigger>
337356
<AlertContent>
338357
<AlertHeader>
@@ -341,7 +360,7 @@ function LeaveTeamModal({
341360
</AlertHeader>
342361
<AlertFooter>
343362
<AlertCancel asChild>
344-
<Button variant="tertiary/small">Cancel</Button>
363+
<Button variant="secondary/small">Cancel</Button>
345364
</AlertCancel>
346365
<Form method="post" {...form.props} onSubmit={() => setOpen(false)}>
347366
<input type="hidden" value={member.id} name="memberId" />
@@ -359,7 +378,7 @@ function ResendButton({ invite }: { invite: Invite }) {
359378
return (
360379
<Form method="post" action={resendInvitePath()} className="flex">
361380
<input type="hidden" value={invite.id} name="inviteId" />
362-
<Button type="submit" variant="tertiary/small">
381+
<Button type="submit" variant="secondary/small">
363382
Resend invite
364383
</Button>
365384
</Form>
@@ -373,11 +392,17 @@ function RevokeButton({ invite }: { invite: Invite }) {
373392
<Form method="post" action={revokeInvitePath()} className="flex">
374393
<input type="hidden" value={invite.id} name="inviteId" />
375394
<input type="hidden" value={organization.slug} name="slug" />
376-
<Button
377-
type="submit"
378-
variant="danger/small"
379-
LeadingIcon={TrashIcon}
380-
leadingIconClassName="text-white"
395+
<SimpleTooltip
396+
button={
397+
<Button
398+
type="submit"
399+
variant="danger/small"
400+
LeadingIcon={NoSymbolIcon}
401+
leadingIconClassName="text-white"
402+
/>
403+
}
404+
content="Revoke invite"
405+
disableHoverableContent
381406
/>
382407
</Form>
383408
);

0 commit comments

Comments
 (0)