Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions packages/app-builder/src/components/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface SidebarLinkProps {
labelTKey: ParseKeys<['navigation']>;
to: string;
children?: React.ReactNode;
className?: string;
}

export const sidebarLink = cva(
Expand All @@ -31,11 +32,11 @@ export const sidebarLink = cva(
},
);

export function SidebarLink({ Icon, labelTKey, to, children }: SidebarLinkProps) {
export function SidebarLink({ Icon, labelTKey, to, children, className }: SidebarLinkProps) {
const { t } = useTranslation(navigationI18n);

return (
<NavLink className={({ isActive }) => sidebarLink({ isActive })} to={to}>
<NavLink className={({ isActive }) => sidebarLink({ isActive, className })} to={to}>
<Icon className="size-6 shrink-0" />
<span className="line-clamp-1 text-start opacity-0 transition-opacity group-aria-expanded/nav:opacity-100">
{t(labelTKey)}
Expand Down
73 changes: 52 additions & 21 deletions packages/app-builder/src/components/Nudge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
HoverCardPortal,
HoverCardTrigger,
} from '@radix-ui/react-hover-card';
import { cva } from 'class-variance-authority';
import { type FeatureAccessLevelDto } from 'marble-api/generated/feature-access-api';
import { useTranslation } from 'react-i18next';
import { match } from 'ts-pattern';
Expand All @@ -16,32 +17,61 @@ type NudgeProps = {
iconClass?: string;
link?: string;
kind?: Exclude<FeatureAccessLevelDto, 'allowed'>;
collapsed?: boolean;
};

export const Nudge = ({ content, link, className, kind = 'restricted', iconClass }: NudgeProps) => {
const { t } = useTranslation(['common']);
const triggerClassName = cva('flex items-center justify-center text-white ', {
variants: {
kind: {
test: 'bg-purple-65',
restricted: 'bg-purple-82',
missing_configuration: 'bg-yellow-50',
},
collapsed: {
true: 'absolute top-v2-sm right-v2-sm translate-x-[50%] -translate-y-[50%] rounded-full',
false: 'rounded-sm size-6',
},
},
defaultVariants: {
collapsed: false,
},
});

const iconClassName = cva('', {
variants: {
collapsed: {
true: 'size-2.5',
false: 'size-3',
},
},
defaultVariants: {
collapsed: false,
},
});

export const Nudge = ({
content,
link,
className,
kind = 'restricted',
iconClass,
collapsed = false,
}: NudgeProps) => {
const { t } = useTranslation(['common']);
return (
<HoverCard>
<HoverCardTrigger
tabIndex={-1}
className={cn(
'text-grey-100 flex flex-row items-center justify-center rounded-sm',
{ 'bg-purple-65': kind === 'test' },
{ 'bg-purple-82': kind === 'restricted' },
{ 'bg-yellow-50': kind === 'missing_configuration' },
className,
)}
>
<Icon
icon={match<typeof kind, IconName>(kind)
.with('restricted', () => 'lock')
.with('test', () => 'unlock-right')
.with('missing_configuration', () => 'warning')
.exhaustive()}
className={cn('size-3.5', iconClass)}
aria-hidden
/>
<HoverCardTrigger tabIndex={-1} asChild>
<span className={triggerClassName({ kind, collapsed, className })}>
<Icon
icon={match<typeof kind, IconName>(kind)
.with('restricted', () => 'lock')
.with('test', () => 'unlock-right')
.with('missing_configuration', () => 'warning')
.exhaustive()}
className={iconClassName({ collapsed, className: iconClass })}
aria-hidden
/>
</span>
</HoverCardTrigger>
<HoverCardPortal>
<HoverCardContent
Expand All @@ -56,6 +86,7 @@ export const Nudge = ({ content, link, className, kind = 'restricted', iconClass
'border-yellow-50': kind === 'missing_configuration',
},
)}
onClick={(e) => e.stopPropagation()}
>
<span className="text-m font-bold">
{match<typeof kind, string>(kind)
Expand Down
6 changes: 5 additions & 1 deletion packages/app-builder/src/routes/_builder+/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export default function Builder() {
role={user.role}
orgOrPartnerName={organization.name}
isAutoAssignmentAvailable={featuresAccess.isAutoAssignmentAvailable}
></UserInfo>
/>
</div>
<nav className="flex flex-1 flex-col overflow-y-auto overflow-x-hidden p-2">
<ul className="flex flex-col gap-2">
Expand Down Expand Up @@ -174,6 +174,7 @@ export default function Builder() {
{t('navigation:analytics')}
</span>
<Nudge
collapsed={!leftSidebarSharp.value.expanded}
className="size-6"
content={t('navigation:analytics.nudge')}
/>
Expand All @@ -186,6 +187,7 @@ export default function Builder() {
{t('navigation:analytics')}
</span>
<Nudge
collapsed={!leftSidebarSharp.value.expanded}
kind="missing_configuration"
className="size-6"
content={t('navigation:analytics.nudge')}
Expand All @@ -197,9 +199,11 @@ export default function Builder() {
<SidebarLink
labelTKey="navigation:analytics"
to={getRoute('/analytics')}
className="relative"
Icon={(props) => <Icon icon="analytics" {...props} />}
>
<Nudge
collapsed={!leftSidebarSharp.value.expanded}
className="size-6"
content={t('navigation:analytics.nudge')}
kind="test"
Expand Down
97 changes: 82 additions & 15 deletions packages/app-builder/src/routes/_builder+/settings+/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { type CurrentUser, isAdmin } from '@app-builder/models';
import { type Inbox } from '@app-builder/models/inbox';
import {
canAccessInboxesSettings,
isAccessible,
isReadApiKeyAvailable,
isReadTagAvailable,
isReadUserAvailable,
Expand All @@ -22,6 +21,7 @@ import clsx from 'clsx';
import { type Namespace } from 'i18next';
import { useTranslation } from 'react-i18next';
import * as R from 'remeda';
import { match } from 'ts-pattern';
import { Icon } from 'ui-icons';

export const handle = {
Expand Down Expand Up @@ -141,18 +141,85 @@ export default function Settings() {
<p className="font-bold">{t(`settings:${section}`)}</p>
</div>
<ul className="flex flex-col gap-1 pb-6">
{settings.map((setting) =>
setting.title === 'webhooks' && !isAccessible(entitlements.webhooks) ? (
<span
key={setting.title}
className="text-s bg-grey-100 text-grey-80 inline-flex w-full gap-2 p-2 font-medium first-letter:capitalize"
>
{t(`settings:${setting.title}`)}
{entitlements.webhooks !== 'allowed' ? (
<Nudge content="" kind="restricted" className="size-5" />
) : null}
</span>
) : (
{settings.map((setting) => {
if (setting.title === 'webhooks') {
return match(entitlements.webhooks)
.with('allowed', () => (
<NavLink
key={setting.title}
className={({ isActive }) =>
clsx(
'text-s flex w-full cursor-pointer flex-row rounded-sm p-2 font-medium first-letter:capitalize',
isActive
? 'bg-purple-96 text-purple-65'
: 'bg-grey-100 text-grey-00 hover:bg-purple-96 hover:text-purple-65',
)
}
to={setting.to}
>
{t(`settings:${setting.title}`)}
</NavLink>
))
.with('restricted', () => (
<div
key={setting.title}
className="text-s bg-grey-100 text-grey-80 relative flex w-full justify-between items-center p-2 font-medium first-letter:capitalize min-w-full"
>
<span className="flex-shrink-0">
{t(`settings:${setting.title}`)}
</span>
<div className="flex-1"></div>
<Nudge
className="size-6 flex-shrink-0"
content={t('settings:webhooks.nudge')}
/>
</div>
))
.with('missing_configuration', () => (
<div
key={setting.title}
className="text-s bg-grey-100 text-grey-80 relative flex w-full justify-between items-center p-2 font-medium first-letter:capitalize min-w-full"
>
<span className="flex-shrink-0">
{t(`settings:${setting.title}`)}
</span>
<div className="flex-1"></div>
<Nudge
kind="missing_configuration"
className="size-6 flex-shrink-0"
content=""
/>
</div>
))
.with('test', () => (
<div className="relative">
<NavLink
key={setting.title}
to={setting.to}
className={({ isActive }) =>
clsx(
'text-s relative flex w-full justify-between items-center p-2 font-medium first-letter:capitalize min-w-full cursor-pointer rounded-sm transition-colors',
isActive
? 'bg-purple-96 text-purple-65'
: 'bg-grey-100 text-grey-00 hover:bg-purple-96 hover:text-purple-65',
)
}
>
<span className="flex-shrink-0">
{t(`settings:${setting.title}`)}
</span>
<Nudge
className="size-6 shrink-0"
content={t('settings:webhooks.nudge')}
kind="test"
/>
</NavLink>
</div>
))
.exhaustive();
}

return (
<NavLink
key={setting.title}
className={({ isActive }) =>
Expand All @@ -167,8 +234,8 @@ export default function Settings() {
>
{t(`settings:${setting.title}`)}
</NavLink>
),
)}
);
})}
</ul>
</nav>
);
Expand Down