From 2e20d3e76f7afd922bb1d542f60994fbb8f87ee6 Mon Sep 17 00:00:00 2001 From: Giancarlo Buomprisco Date: Sun, 5 Oct 2025 17:54:16 +0800 Subject: [PATCH] 2.18.0: New Invitation flow, refactored Database Webhooks, new ShadCN UI Components (#384) * Streamlined invitations flow * Removed web hooks in favor of handling logic directly in server actions * Added new Shadcn UI Components --- .../components/button-group-story.tsx | 369 ++++++++++++++ .../components/components/docs-content.tsx | 2 +- .../components/empty-state-story.tsx | 9 +- .../app/components/components/field-story.tsx | 468 ++++++++++++++++++ .../components/input-group-story.tsx | 394 +++++++++++++++ .../app/components/components/item-story.tsx | 412 +++++++++++++++ .../app/components/components/kbd-story.tsx | 277 +++++++++++ .../components/components/spinner-story.tsx | 17 +- .../app/components/lib/components-data.tsx | 120 +++++ apps/dev-tool/app/components/page.tsx | 4 +- apps/dev-tool/next.config.ts | 3 + apps/e2e/tests/admin/admin.spec.ts | 32 +- apps/e2e/tests/authentication/auth.spec.ts | 158 +----- .../authentication/password-reset.spec.ts | 6 +- .../e2e/tests/invitations/invitations.spec.ts | 10 - .../tests/team-accounts/team-accounts.spec.ts | 12 +- .../team-accounts/team-invitation-mfa.spec.ts | 10 - .../app/(marketing)/docs/[...slug]/page.tsx | 8 +- apps/web/app/auth/sign-in/page.tsx | 14 +- apps/web/app/auth/sign-up/page.tsx | 17 +- apps/web/app/join/page.tsx | 10 +- apps/web/public/locales/en/account.json | 3 +- apps/web/supabase/seed.sql | 28 -- package.json | 2 +- packages/database-webhooks/package.json | 1 - .../database-webhook-router.service.ts | 38 -- .../src/emails/account-delete.email.tsx | 5 +- .../email/update-email-form.tsx | 93 ++-- .../link-accounts/link-accounts-list.tsx | 157 +++--- .../mfa/multi-factor-auth-list.tsx | 29 +- .../password/update-password-form.tsx | 116 +++-- .../update-account-details-form.tsx | 32 +- .../personal-accounts-server-actions.ts | 6 +- .../delete-personal-account.service.ts | 86 +++- .../components/magic-link-auth-container.tsx | 6 - .../auth/src/components/oauth-providers.tsx | 5 - .../src/components/otp-sign-in-container.tsx | 13 +- .../components/sign-in-methods-container.tsx | 27 +- .../components/sign-up-methods-container.tsx | 38 +- .../invite-members-dialog-container.tsx | 83 ++-- .../update-team-account-name-form.tsx | 30 +- .../team-invitations-server-actions.ts | 1 + .../features/team-accounts/src/server/api.ts | 7 +- .../account-invitations-dispatcher.service.ts | 253 ++++++++++ .../services/account-invitations.service.ts | 154 ++++++ .../account-invitations-webhook.service.ts | 175 ------- .../webhooks/account-webhooks.service.ts | 90 ---- .../src/server/services/webhooks/index.ts | 2 - .../supabase/src/auth-callback.service.ts | 103 ++-- packages/ui/package.json | 5 + packages/ui/src/makerkit/empty-state.tsx | 54 +- packages/ui/src/makerkit/global-loader.tsx | 2 +- .../makerkit/oauth-provider-logo-image.tsx | 4 +- packages/ui/src/makerkit/spinner.tsx | 44 +- packages/ui/src/shadcn/button-group.tsx | 83 ++++ packages/ui/src/shadcn/field.tsx | 245 +++++++++ packages/ui/src/shadcn/input-group.tsx | 171 +++++++ packages/ui/src/shadcn/item.tsx | 195 ++++++++ packages/ui/src/shadcn/kbd.tsx | 28 ++ pnpm-lock.yaml | 3 - 60 files changed, 3760 insertions(+), 1009 deletions(-) create mode 100644 apps/dev-tool/app/components/components/button-group-story.tsx create mode 100644 apps/dev-tool/app/components/components/field-story.tsx create mode 100644 apps/dev-tool/app/components/components/input-group-story.tsx create mode 100644 apps/dev-tool/app/components/components/item-story.tsx create mode 100644 apps/dev-tool/app/components/components/kbd-story.tsx create mode 100644 packages/features/team-accounts/src/server/services/account-invitations-dispatcher.service.ts delete mode 100644 packages/features/team-accounts/src/server/services/webhooks/account-invitations-webhook.service.ts delete mode 100644 packages/features/team-accounts/src/server/services/webhooks/account-webhooks.service.ts delete mode 100644 packages/features/team-accounts/src/server/services/webhooks/index.ts create mode 100644 packages/ui/src/shadcn/button-group.tsx create mode 100644 packages/ui/src/shadcn/field.tsx create mode 100644 packages/ui/src/shadcn/input-group.tsx create mode 100644 packages/ui/src/shadcn/item.tsx create mode 100644 packages/ui/src/shadcn/kbd.tsx diff --git a/apps/dev-tool/app/components/components/button-group-story.tsx b/apps/dev-tool/app/components/components/button-group-story.tsx new file mode 100644 index 000000000..b3670b300 --- /dev/null +++ b/apps/dev-tool/app/components/components/button-group-story.tsx @@ -0,0 +1,369 @@ +'use client'; + +import { useMemo } from 'react'; + +import { Filter, Plus, Settings } from 'lucide-react'; + +import { Button } from '@kit/ui/button'; +import { + ButtonGroup, + ButtonGroupSeparator, + ButtonGroupText, +} from '@kit/ui/button-group'; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@kit/ui/card'; +import { Label } from '@kit/ui/label'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@kit/ui/select'; +import { Separator } from '@kit/ui/separator'; +import { Switch } from '@kit/ui/switch'; +import { cn } from '@kit/ui/utils'; + +import { + formatCodeBlock, + generatePropsString, + useStoryControls, +} from '../lib/story-utils'; +import { ComponentStoryLayout } from './story-layout'; +import { SimpleStorySelect } from './story-select'; + +interface ButtonGroupControls { + orientation: 'horizontal' | 'vertical'; + size: 'sm' | 'default' | 'lg'; + withLabel: boolean; + withSeparator: boolean; + withFilter: boolean; + withPrimary: boolean; + withSelect: boolean; + fullWidth: boolean; +} + +const orientationOptions = [ + { + value: 'horizontal', + label: 'Horizontal', + description: 'Buttons arranged side-by-side', + }, + { + value: 'vertical', + label: 'Vertical', + description: 'Stack buttons vertically', + }, +] as const; + +const sizeOptions = [ + { + value: 'sm', + label: 'Small', + description: 'Compact 36px controls', + }, + { + value: 'default', + label: 'Default', + description: 'Standard 40px controls', + }, + { + value: 'lg', + label: 'Large', + description: 'Spacious 44px controls', + }, +] as const; + +export function ButtonGroupStory() { + const { controls, updateControl } = useStoryControls({ + orientation: 'horizontal', + size: 'sm', + withLabel: false, + withSeparator: false, + withFilter: false, + withPrimary: false, + withSelect: false, + fullWidth: false, + }); + + const buttonGroupPropsString = useMemo( + () => + generatePropsString( + { + orientation: controls.orientation, + className: controls.fullWidth ? 'w-full' : undefined, + }, + { + orientation: 'horizontal', + className: undefined, + }, + ), + [controls.fullWidth, controls.orientation], + ); + + const generatedCode = useMemo(() => { + const separatorOrientation = + controls.orientation === 'vertical' ? 'horizontal' : 'vertical'; + + const buttonSizeProp = + controls.size === 'default' ? '' : ` size="${controls.size}"`; + const selectTriggerClasses = [ + 'w-[140px] justify-between', + controls.size === 'sm' ? 'h-9 text-sm' : null, + controls.size === 'lg' ? 'h-11 text-base' : null, + ] + .filter(Boolean) + .join(' '); + const labelClasses = [ + 'min-w-[120px] justify-between', + controls.size === 'sm' ? 'text-sm' : null, + controls.size === 'lg' ? 'text-base' : null, + 'gap-2', + ] + .filter(Boolean) + .join(' '); + + let code = ``; + + if (controls.withLabel) { + code += `\n `; + code += `\n Views`; + code += `\n `; + code += `\n `; + } + + code += `\n `; + code += `\n `; + code += `\n `; + + if (controls.withSeparator) { + code += `\n `; + } + + if (controls.withFilter) { + code += `\n `; + } + + if (controls.withSelect) { + code += `\n `; + } + + if (controls.withPrimary) { + code += `\n `; + code += `\n `; + code += `\n New view`; + code += `\n `; + } + + code += `\n`; + + return formatCodeBlock(code, [ + "import { Filter, Plus, Settings } from 'lucide-react';", + "import { Button } from '@kit/ui/button';", + "import { ButtonGroup, ButtonGroupSeparator, ButtonGroupText } from '@kit/ui/button-group';", + "import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@kit/ui/select';", + ]); + }, [buttonGroupPropsString, controls]); + + const separatorOrientation = + controls.orientation === 'vertical' ? 'horizontal' : 'vertical'; + + const preview = ( +
+ + {controls.withLabel && ( + + Views + + + )} + + + + + + {controls.withSeparator && ( + + )} + + {controls.withFilter && ( + + )} + + {controls.withPrimary && ( + + )} + +
+ ); + + const controlsPanel = ( + <> +
+ + updateControl('orientation', value)} + options={orientationOptions} + /> +
+ +
+ + updateControl('size', value)} + options={sizeOptions} + /> +
+ + + +
+ + updateControl('withLabel', checked)} + /> +
+ +
+ + updateControl('withFilter', checked)} + /> +
+ +
+ + updateControl('withPrimary', checked)} + /> +
+ + ); + + const examples = ( + + + Button group sizes + + Mirror the documentation examples with small, default, and large + buttons. + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + + return ( + + ); +} + +export default ButtonGroupStory; diff --git a/apps/dev-tool/app/components/components/docs-content.tsx b/apps/dev-tool/app/components/components/docs-content.tsx index af0504fac..ad8de9d0b 100644 --- a/apps/dev-tool/app/components/components/docs-content.tsx +++ b/apps/dev-tool/app/components/components/docs-content.tsx @@ -19,7 +19,7 @@ export function DocsContent({ selectedComponent }: DocsContentProps) { } return ( -
+
}>
diff --git a/apps/dev-tool/app/components/components/empty-state-story.tsx b/apps/dev-tool/app/components/components/empty-state-story.tsx index ddb97276b..8b056e0fa 100644 --- a/apps/dev-tool/app/components/components/empty-state-story.tsx +++ b/apps/dev-tool/app/components/components/empty-state-story.tsx @@ -11,6 +11,7 @@ import { CardTitle, } from '@kit/ui/card'; import { + EmptyMedia, EmptyState, EmptyStateButton, EmptyStateHeading, @@ -290,7 +291,9 @@ export function EmptyStateStory() { - + + + No products Add your first product to start selling. @@ -299,7 +302,9 @@ export function EmptyStateStory() { - + + + No documents Upload or create your first document. diff --git a/apps/dev-tool/app/components/components/field-story.tsx b/apps/dev-tool/app/components/components/field-story.tsx new file mode 100644 index 000000000..a71004ac3 --- /dev/null +++ b/apps/dev-tool/app/components/components/field-story.tsx @@ -0,0 +1,468 @@ +'use client'; + +import { useMemo } from 'react'; + +import { Button } from '@kit/ui/button'; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from '@kit/ui/card'; +import { + Field, + FieldContent, + FieldDescription, + FieldError, + FieldGroup, + FieldLabel, + FieldLegend, + FieldSeparator, + FieldSet, + FieldTitle, +} from '@kit/ui/field'; +import { Input } from '@kit/ui/input'; +import { Label } from '@kit/ui/label'; +import { RadioGroup, RadioGroupItem } from '@kit/ui/radio-group'; +import { Separator } from '@kit/ui/separator'; +import { Switch } from '@kit/ui/switch'; +import { Textarea } from '@kit/ui/textarea'; + +import { + formatCodeBlock, + generatePropsString, + useStoryControls, +} from '../lib/story-utils'; +import { ComponentStoryLayout } from './story-layout'; +import { SimpleStorySelect } from './story-select'; + +interface FieldControls { + orientation: 'vertical' | 'horizontal'; + showDescriptions: boolean; + showErrors: boolean; + useLegend: boolean; + includeSeparator: boolean; +} + +const orientationOptions = [ + { + value: 'vertical', + label: 'Vertical', + description: 'Label and controls stacked', + }, + { + value: 'horizontal', + label: 'Horizontal', + description: 'Label inline with controls', + }, +] as const; + +export function FieldStory() { + const { controls, updateControl } = useStoryControls({ + orientation: 'horizontal', + showDescriptions: true, + showErrors: false, + useLegend: true, + includeSeparator: true, + }); + + const fieldPropsString = useMemo( + () => + generatePropsString( + { + orientation: controls.orientation, + }, + { orientation: 'vertical' }, + ), + [controls.orientation], + ); + + const generatedCode = useMemo(() => { + const separatorLine = controls.includeSeparator + ? '\n Preferences' + : ''; + + const descriptionLine = controls.showDescriptions + ? '\n The name that will appear on invoices.' + : ''; + + const errorLine = controls.showErrors + ? '\n ' + : ''; + + const code = `
+ ${controls.useLegend ? 'Account Details\n ' : ''} + + Full name + + ${descriptionLine}${errorLine} + + ${separatorLine} + + Email address + + ${ + controls.showDescriptions + ? '\n Used for sign-in and notifications.' + : '' + } + + + + Bio + +