Skip to content

Commit 9a72bad

Browse files
committed
Revert "improvement(org): move the People page onto the new design system (#9720)"
This reverts commit 7625929.
1 parent 99a029d commit 9a72bad

70 files changed

Lines changed: 2687 additions & 4045 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/sim/app/o/[organizationId]/_redesign/redesign-host.tsx‎

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,3 @@ interface RedesignSurfaceProps {
1818
export function RedesignSurface({ children }: RedesignSurfaceProps) {
1919
return <PlatformSurface theme='system'>{children}</PlatformSurface>
2020
}
21-
22-
/** Fills the app's content slot, and the grid stretches the surface so its canvas reaches the end. */
23-
export function RedesignPageSurface({ children }: RedesignSurfaceProps) {
24-
return (
25-
<div className='grid h-full min-h-0 min-w-0 flex-1 grid-cols-[minmax(0,1fr)] overflow-y-auto'>
26-
<RedesignSurface>{children}</RedesignSurface>
27-
</div>
28-
)
29-
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { ReactNode } from 'react'
2+
import { RedesignSurface } from '@/app/o/[organizationId]/_redesign/redesign-host'
3+
4+
interface LinksSurfaceProps {
5+
children: ReactNode
6+
}
7+
8+
/** Fills the app's content slot, and the grid stretches the surface so its canvas reaches the end. */
9+
export function LinksSurface({ children }: LinksSurfaceProps) {
10+
return (
11+
<div className='grid h-full min-h-0 min-w-0 flex-1 grid-cols-[minmax(0,1fr)] overflow-y-auto'>
12+
<RedesignSurface>{children}</RedesignSurface>
13+
</div>
14+
)
15+
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { RedesignPageSurface } from '@/app/o/[organizationId]/_redesign/redesign-host'
1+
import { LinksSurface } from '@/app/o/[organizationId]/links/links-surface'
22
import {
33
LinkRowsSkeleton,
44
LinksFrame,
55
} from '@/platform/app/(shell)/links/_components/links-page/links-frame'
66

77
export default function SimLinksLoading() {
88
return (
9-
<RedesignPageSurface>
9+
<LinksSurface>
1010
<LinksFrame loading>
1111
<LinkRowsSkeleton />
1212
</LinksFrame>
13-
</RedesignPageSurface>
13+
</LinksSurface>
1414
)
1515
}

‎apps/sim/app/o/[organizationId]/links/page.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { getSession } from '@/lib/auth'
55
import { organizationRoutes } from '@/lib/navigation/paths'
66
import { isSimLinksEnabled } from '@/lib/sim-links/feature-flag'
77
import { buildAuthCrossLink } from '@/app/(auth)/auth-redirect'
8-
import { RedesignPageSurface } from '@/app/o/[organizationId]/_redesign/redesign-host'
8+
import { LinksSurface } from '@/app/o/[organizationId]/links/links-surface'
99
import { loadSimLinksListView } from '@/app/o/[organizationId]/links/search-params'
1010
import { LinksPage } from '@/platform/app/(shell)/links/_components/links-page/links-page'
1111
import { prefetchSimLinks } from '@/platform/lib/data/prefetch'
@@ -34,10 +34,10 @@ export default async function SimLinksPage({ params, searchParams }: SimLinksPag
3434
loadSimLinksListView(query)
3535
)
3636
return (
37-
<RedesignPageSurface>
37+
<LinksSurface>
3838
<HydrationBoundary state={state}>
3939
<LinksPage organizationId={organizationId} />
4040
</HydrationBoundary>
41-
</RedesignPageSurface>
41+
</LinksSurface>
4242
)
4343
}
Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
1-
import { RedesignPageSurface } from '@/app/o/[organizationId]/_redesign/redesign-host'
2-
import { PersonSkeleton } from '@/platform/app/(shell)/people/_components/person/person-skeleton'
1+
import { ApplicationLoading } from '@/app/_shell/application-loading'
32

4-
export default function PersonLoading() {
5-
return (
6-
<RedesignPageSurface>
7-
<PersonSkeleton />
8-
</RedesignPageSurface>
9-
)
3+
export default function Loading() {
4+
return <ApplicationLoading />
105
}

‎apps/sim/app/o/[organizationId]/people/[memberId]/page.tsx‎

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@ import type { Metadata } from 'next'
33
import { notFound, redirect } from 'next/navigation'
44
import { getSession } from '@/lib/auth'
55
import { organizationRoutes } from '@/lib/navigation/paths'
6-
import { getOrganizationSurfaceContext } from '@/lib/organizations/surface'
7-
import { canBrowsePeople } from '@/lib/people/feature-flag'
6+
import { isPeopleEnabled } from '@/lib/people/feature-flag'
87
import { buildAuthCrossLink } from '@/app/(auth)/auth-redirect'
9-
import { RedesignPageSurface } from '@/app/o/[organizationId]/_redesign/redesign-host'
10-
import { PersonProfile } from '@/platform/app/(shell)/people/_components/person/person'
11-
import { PersonSkeleton } from '@/platform/app/(shell)/people/_components/person/person-skeleton'
8+
import PageLoading from '@/app/o/[organizationId]/people/[memberId]/loading'
9+
import { Person } from '@/app/o/[organizationId]/people/[memberId]/person'
1210

1311
export const metadata: Metadata = { title: 'People' }
1412

@@ -26,13 +24,10 @@ export default async function PersonPage({ params }: PersonPageProps) {
2624
isInviteFlow: false,
2725
})
2826
)
29-
const context = await getOrganizationSurfaceContext(organizationId, session.user.id)
30-
if (!context || !(await canBrowsePeople(organizationId, context.viewer.role))) notFound()
27+
if (!(await isPeopleEnabled(organizationId))) notFound()
3128
return (
32-
<RedesignPageSurface>
33-
<Suspense fallback={<PersonSkeleton />}>
34-
<PersonProfile organizationId={organizationId} memberId={memberId} />
35-
</Suspense>
36-
</RedesignPageSurface>
29+
<Suspense fallback={<PageLoading />}>
30+
<Person organizationId={organizationId} memberId={memberId} />
31+
</Suspense>
3732
)
3833
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
'use client'
2+
3+
import { useState } from 'react'
4+
import {
5+
Chip,
6+
ChipCombobox,
7+
ChipDatePicker,
8+
ChipModal,
9+
ChipModalBody,
10+
ChipModalError,
11+
ChipModalField,
12+
ChipModalFooter,
13+
ChipModalHeader,
14+
} from '@sim/emcn'
15+
import type { Person, PersonBody } from '@/lib/api/contracts/people'
16+
import { memberProfileFieldsSchema, PROFILE_TEXT_MAX_LENGTH } from '@/lib/people/validation'
17+
import { usePeoplePicker } from '@/app/o/[organizationId]/people/use-people-picker'
18+
import { useUpdatePerson } from '@/hooks/queries/people'
19+
20+
type TextField = 'title' | 'department' | 'location'
21+
22+
/** The picker's value for no manager; membership ids are never this. */
23+
const NO_MANAGER = 'no-manager'
24+
25+
const TEXT_FIELDS: readonly { field: TextField; title: string; placeholder: string }[] = [
26+
{ field: 'title', title: 'Title', placeholder: 'Staff Engineer' },
27+
{ field: 'department', title: 'Department', placeholder: 'Engineering' },
28+
{ field: 'location', title: 'Location', placeholder: 'San Francisco' },
29+
]
30+
31+
interface PersonModalProps {
32+
organizationId: string
33+
person: Person
34+
onClose: () => void
35+
}
36+
37+
/** Edits one person's profile; managers are chosen from the organization's people. */
38+
export function PersonModal({ organizationId, person, onClose }: PersonModalProps) {
39+
const update = useUpdatePerson(organizationId)
40+
const [draft, setDraft] = useState<PersonBody>({
41+
title: person.title,
42+
department: person.department,
43+
location: person.location,
44+
startDate: person.startDate,
45+
managerMemberId: person.managerMemberId,
46+
})
47+
const [errors, setErrors] = useState<Partial<Record<keyof PersonBody, string>>>({})
48+
/**
49+
* The chosen manager's name, kept with the choice: the search results may no longer include them.
50+
* Only a stored manager who is suspended has an id and no name.
51+
*/
52+
const [managerName, setManagerName] = useState(person.manager?.name ?? null)
53+
const { people, pickerProps } = usePeoplePicker(organizationId, 'members', person.memberId)
54+
const managerLabel = draft.managerMemberId ? (managerName ?? 'Suspended member') : 'No manager'
55+
56+
const change = <K extends keyof PersonBody>(field: K, value: PersonBody[K]) => {
57+
setDraft((current) => ({ ...current, [field]: value }))
58+
setErrors((current) => ({ ...current, [field]: undefined }))
59+
if (update.isError) update.reset()
60+
}
61+
62+
const save = () => {
63+
const parsed = memberProfileFieldsSchema.safeParse(draft)
64+
if (!parsed.success) {
65+
setErrors(
66+
Object.fromEntries(
67+
parsed.error.issues.map((issue) => [String(issue.path[0]), issue.message])
68+
)
69+
)
70+
return
71+
}
72+
update.mutate({ memberId: person.memberId, body: parsed.data }, { onSuccess: onClose })
73+
}
74+
75+
const title = `Edit ${person.name}`
76+
return (
77+
<ChipModal
78+
open
79+
onOpenChange={(open) => !open && onClose()}
80+
dismissDisabled={update.isPending}
81+
srTitle={title}
82+
>
83+
<ChipModalHeader onClose={onClose}>{title}</ChipModalHeader>
84+
<ChipModalBody>
85+
{TEXT_FIELDS.map(({ field, title: label, placeholder }) => (
86+
<ChipModalField
87+
key={field}
88+
type='input'
89+
disabled={update.isPending}
90+
title={label}
91+
placeholder={placeholder}
92+
maxLength={PROFILE_TEXT_MAX_LENGTH}
93+
value={draft[field] ?? ''}
94+
onChange={(value) => change(field, value || null)}
95+
error={errors[field]}
96+
/>
97+
))}
98+
<ChipModalField type='custom' title='Start date' error={errors.startDate}>
99+
{() => (
100+
<div className='flex items-center gap-2'>
101+
<ChipDatePicker
102+
value={draft.startDate ?? undefined}
103+
onChange={(value) => change('startDate', value)}
104+
placeholder='Not recorded'
105+
disabled={update.isPending}
106+
fullWidth
107+
/>
108+
{draft.startDate && (
109+
<Chip disabled={update.isPending} onClick={() => change('startDate', null)}>
110+
Clear
111+
</Chip>
112+
)}
113+
</div>
114+
)}
115+
</ChipModalField>
116+
<ChipModalField type='custom' title='Manager' error={errors.managerMemberId}>
117+
{() => (
118+
<ChipCombobox
119+
{...pickerProps}
120+
aria-label='Manager'
121+
options={[{ value: NO_MANAGER, label: 'No manager' }, ...pickerProps.options]}
122+
value={draft.managerMemberId ?? NO_MANAGER}
123+
onChange={(value) => {
124+
const manager = people.find((candidate) => candidate.memberId === value)
125+
setManagerName(manager?.name ?? null)
126+
change('managerMemberId', manager?.memberId ?? null)
127+
}}
128+
overlayContent={managerLabel}
129+
searchPlaceholder='Search people...'
130+
emptyMessage='No one matches'
131+
disabled={update.isPending}
132+
className='w-full'
133+
/>
134+
)}
135+
</ChipModalField>
136+
<ChipModalError>{update.error?.message}</ChipModalError>
137+
</ChipModalBody>
138+
<ChipModalFooter
139+
onCancel={onClose}
140+
primaryAction={{
141+
label: update.isPending ? 'Saving' : 'Save',
142+
disabled: update.isPending,
143+
onClick: save,
144+
}}
145+
/>
146+
</ChipModal>
147+
)
148+
}

0 commit comments

Comments
 (0)