11import { useForm } from "@conform-to/react" ;
22import { 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" ;
44import { Form , type MetaFunction , useActionData } from "@remix-run/react" ;
55import { type ActionFunction , type LoaderFunctionArgs , json } from "@remix-run/server-runtime" ;
66import { useState } from "react" ;
@@ -9,11 +9,7 @@ import invariant from "tiny-invariant";
99import { z } from "zod" ;
1010import { UserAvatar } from "~/components/UserProfilePhoto" ;
1111import { 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" ;
1713import {
1814 Alert ,
1915 AlertCancel ,
@@ -27,7 +23,6 @@ import {
2723import { Button , ButtonContent , LinkButton } from "~/components/primitives/Buttons" ;
2824import { DateTime } from "~/components/primitives/DateTime" ;
2925import { Header2 , Header3 } from "~/components/primitives/Headers" ;
30- import { InfoPanel } from "~/components/primitives/InfoPanel" ;
3126import { NavBar , PageAccessories , PageTitle } from "~/components/primitives/PageHeader" ;
3227import { Paragraph } from "~/components/primitives/Paragraph" ;
3328import * 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