mirror of
https://github.com/coder/coder.git
synced 2026-10-31 02:43:21 +08:00
fix(site): align settings header style and paywall spacing (#28418)
Settings pages rendered the premium upsell banner at different vertical
offsets and the IdP sync pages used a lighter heading style than the
rest of the settings UI.
Three causes:
1. The IdP Sync (`/organizations/:org/idp-sync`) and Organization IdP
Sync (`/deployment/idp-org-sync`) pages hand-rolled their headers with a
bare `<h1 className="text-3xl m-0">` (missing `font-semibold`) and a
`flex flex-col gap-12` wrapper (48px above the banner).
2. Workspace Proxies and Appearance wrapped `SettingsHeader` in flex
columns with their own `gap` (`gap-4`, `gap-8`), which stacked on top of
the header's built-in `pb-6`, pushing the banner to 40px and 56px
offsets respectively while Groups sits at 24px.
3. `SettingsHeaderDescription` used `leading-normal` (21px lines) while
the inline `Link` component ("View docs") renders a 24px-tall
`inline-flex leading-6` box, inflating the line box on pages whose
description contains a docs link and shifting content below by ~3px.
Changes:
- Replace the custom header markup on both IdP sync pages with the
shared `SettingsHeader`, `SettingsHeaderTitle`, and
`SettingsHeaderDescription` components; the Export Policy button moves
into the `actions` slot.
- Hoist `SettingsHeader` out of the gap wrappers on Workspace Proxies
and Appearance so the header's `pb-6` is the single source of
header-to-content spacing; the gap now applies only within the entitled
content.
- Set `SettingsHeaderDescription` to `leading-6` so description line
height matches the inline `Link`, whether or not a docs link is present.
All affected pages now start content, including the premium paywall,
24px below the header description.
_Created by Coder Agents on behalf of @tracyjohnsonux._
This commit is contained in:
@@ -97,7 +97,7 @@ export const SettingsHeaderDescription: FC<SettingsHeaderDescriptionProps> = ({
|
||||
return (
|
||||
<p
|
||||
className={cn(
|
||||
"m-0 text-content-secondary font-medium leading-normal",
|
||||
"m-0 text-content-secondary font-medium leading-6",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
|
||||
+3
-3
@@ -48,7 +48,7 @@ export const AppearanceSettingsPageView: FC<
|
||||
const getFieldHelpers = getFormHelpers(form);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-8">
|
||||
<div>
|
||||
<SettingsHeader
|
||||
actions={
|
||||
<SettingsHeaderDocsLink href={docs("/admin/setup/appearance")} />
|
||||
@@ -73,7 +73,7 @@ export const AppearanceSettingsPageView: FC<
|
||||
canViewPremium={canViewPremium}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex flex-col gap-8">
|
||||
<VerticalForm
|
||||
onSubmit={form.handleSubmit}
|
||||
aria-label="Appearance settings"
|
||||
@@ -122,7 +122,7 @@ export const AppearanceSettingsPageView: FC<
|
||||
onSaveAppearance({ announcement_banners: announcementBanners })
|
||||
}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,12 @@ import {
|
||||
} from "#/api/queries/idpsync";
|
||||
import { Loader } from "#/components/Loader/Loader";
|
||||
import { PaywallPremium } from "#/components/Paywall/PaywallPremium";
|
||||
import { SettingsHeaderDocsLink } from "#/components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderDocsLink,
|
||||
SettingsHeaderTitle,
|
||||
} from "#/components/SettingsHeader/SettingsHeader";
|
||||
import { useAuthenticated } from "#/hooks/useAuthenticated";
|
||||
import { useDashboard } from "#/modules/dashboard/useDashboard";
|
||||
import { useFeatureVisibility } from "#/modules/dashboard/useFeatureVisibility";
|
||||
@@ -63,22 +68,23 @@ const IdpOrgSyncPage: FC = () => {
|
||||
<>
|
||||
<title>{pageTitle("Organization IdP Sync")}</title>
|
||||
|
||||
<div className="flex flex-col gap-12">
|
||||
<header className="flex flex-row items-baseline justify-between">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-3xl m-0">Organization IdP Sync</h1>
|
||||
<p className="flex flex-row gap-1 text-sm text-content-secondary font-medium m-0">
|
||||
Automatically assign users to an organization based on their IdP
|
||||
claims.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<SettingsHeaderDocsLink
|
||||
href={docs("/admin/users/idp-sync#organization-sync")}
|
||||
/>
|
||||
<ExportPolicyButton syncSettings={settingsQuery.data} />
|
||||
</div>
|
||||
</header>
|
||||
<div>
|
||||
<SettingsHeader
|
||||
actions={
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<SettingsHeaderDocsLink
|
||||
href={docs("/admin/users/idp-sync#organization-sync")}
|
||||
/>
|
||||
<ExportPolicyButton syncSettings={settingsQuery.data} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<SettingsHeaderTitle>Organization IdP Sync</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Automatically assign users to an organization based on their IdP
|
||||
claims.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
{!isIdpSyncEnabled ? (
|
||||
<PaywallPremium
|
||||
message="IdP Organization Sync"
|
||||
|
||||
@@ -14,7 +14,12 @@ import {
|
||||
import { organizationRoles } from "#/api/queries/roles";
|
||||
import { EmptyState } from "#/components/EmptyState/EmptyState";
|
||||
import { PaywallPremium } from "#/components/Paywall/PaywallPremium";
|
||||
import { SettingsHeaderDocsLink } from "#/components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderDocsLink,
|
||||
SettingsHeaderTitle,
|
||||
} from "#/components/SettingsHeader/SettingsHeader";
|
||||
import { useAuthenticated } from "#/hooks/useAuthenticated";
|
||||
import { useFeatureVisibility } from "#/modules/dashboard/useFeatureVisibility";
|
||||
import { useOrganizationSettings } from "#/modules/management/OrganizationSettingsLayout";
|
||||
@@ -118,75 +123,74 @@ const IdpSyncPage: FC = () => {
|
||||
<div className="w-full max-w-screen-2xl pb-10">
|
||||
{title}
|
||||
|
||||
<div className="flex flex-col gap-12">
|
||||
<header className="flex flex-row items-baseline justify-between">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-3xl m-0">IdP Sync</h1>
|
||||
<p className="flex flex-row gap-1 text-sm text-content-secondary font-medium m-0">
|
||||
Automatically assign groups or roles to a user based on their IdP
|
||||
claims.
|
||||
</p>
|
||||
</div>
|
||||
<SettingsHeader
|
||||
actions={
|
||||
<SettingsHeaderDocsLink href={docs("/admin/users/idp-sync")} />
|
||||
</header>
|
||||
{!isIdpSyncEnabled ? (
|
||||
<PaywallPremium
|
||||
message="IdP Sync"
|
||||
description="Auto-sync groups & roles from your IdP."
|
||||
features={[
|
||||
"Sync groups & roles automatically",
|
||||
"Configured per organization",
|
||||
"No manual user assignment",
|
||||
"Works with your OIDC provider",
|
||||
]}
|
||||
canViewPremium={permissions.viewAllLicenses}
|
||||
/>
|
||||
) : (
|
||||
<IdpSyncPageView
|
||||
tab={tab}
|
||||
groupSyncSettings={groupIdpSyncSettingsQuery.data}
|
||||
roleSyncSettings={roleIdpSyncSettingsQuery.data}
|
||||
claimFieldValues={fieldValuesQuery.data}
|
||||
groups={groupsQuery.data}
|
||||
groupsMap={groupsMap}
|
||||
roles={rolesQuery.data}
|
||||
organization={organization}
|
||||
onGroupSyncFieldChange={setGroupField}
|
||||
onRoleSyncFieldChange={setRoleField}
|
||||
error={error}
|
||||
onSubmitGroupSyncSettings={async (data) => {
|
||||
const mutation = patchGroupSyncSettingsMutation.mutateAsync(data);
|
||||
toast.promise(mutation, {
|
||||
loading: "Updating IdP group sync settings...",
|
||||
success: "IdP group sync settings updated.",
|
||||
error: (error) => ({
|
||||
message: getErrorMessage(
|
||||
error,
|
||||
"Failed to update IdP group sync settings.",
|
||||
),
|
||||
}
|
||||
>
|
||||
<SettingsHeaderTitle>IdP Sync</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Automatically assign groups or roles to a user based on their IdP
|
||||
claims.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
{!isIdpSyncEnabled ? (
|
||||
<PaywallPremium
|
||||
message="IdP Sync"
|
||||
description="Auto-sync groups & roles from your IdP."
|
||||
features={[
|
||||
"Sync groups & roles automatically",
|
||||
"Configured per organization",
|
||||
"No manual user assignment",
|
||||
"Works with your OIDC provider",
|
||||
]}
|
||||
canViewPremium={permissions.viewAllLicenses}
|
||||
/>
|
||||
) : (
|
||||
<IdpSyncPageView
|
||||
tab={tab}
|
||||
groupSyncSettings={groupIdpSyncSettingsQuery.data}
|
||||
roleSyncSettings={roleIdpSyncSettingsQuery.data}
|
||||
claimFieldValues={fieldValuesQuery.data}
|
||||
groups={groupsQuery.data}
|
||||
groupsMap={groupsMap}
|
||||
roles={rolesQuery.data}
|
||||
organization={organization}
|
||||
onGroupSyncFieldChange={setGroupField}
|
||||
onRoleSyncFieldChange={setRoleField}
|
||||
error={error}
|
||||
onSubmitGroupSyncSettings={async (data) => {
|
||||
const mutation = patchGroupSyncSettingsMutation.mutateAsync(data);
|
||||
toast.promise(mutation, {
|
||||
loading: "Updating IdP group sync settings...",
|
||||
success: "IdP group sync settings updated.",
|
||||
error: (error) => ({
|
||||
message: getErrorMessage(
|
||||
error,
|
||||
"Failed to update IdP group sync settings.",
|
||||
),
|
||||
description: getErrorDetail(error),
|
||||
}),
|
||||
});
|
||||
}}
|
||||
onSubmitRoleSyncSettings={async (data) => {
|
||||
try {
|
||||
await patchRoleSyncSettingsMutation.mutateAsync(data);
|
||||
toast.success("IdP Role sync settings updated.");
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
getErrorMessage(
|
||||
error,
|
||||
"Failed to update IdP role sync settings.",
|
||||
),
|
||||
{
|
||||
description: getErrorDetail(error),
|
||||
}),
|
||||
});
|
||||
}}
|
||||
onSubmitRoleSyncSettings={async (data) => {
|
||||
try {
|
||||
await patchRoleSyncSettingsMutation.mutateAsync(data);
|
||||
toast.success("IdP Role sync settings updated.");
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
getErrorMessage(
|
||||
error,
|
||||
"Failed to update IdP role sync settings.",
|
||||
),
|
||||
{
|
||||
description: getErrorDetail(error),
|
||||
},
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
},
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -45,7 +45,7 @@ export const WorkspaceProxyView: FC<WorkspaceProxyViewProps> = ({
|
||||
permissions,
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<SettingsHeader
|
||||
actions={
|
||||
<SettingsHeaderDocsLink
|
||||
@@ -73,7 +73,7 @@ export const WorkspaceProxyView: FC<WorkspaceProxyViewProps> = ({
|
||||
canViewPremium={permissions.viewAllLicenses}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex flex-col gap-4">
|
||||
{Boolean(getWorkspaceProxiesError) && (
|
||||
<ErrorAlert error={getWorkspaceProxiesError} />
|
||||
)}
|
||||
@@ -96,7 +96,7 @@ export const WorkspaceProxyView: FC<WorkspaceProxyViewProps> = ({
|
||||
/>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user