- {severity === 'error' && (
-
- )}
- {severity === 'info' && }
+ {severity === 'error' && }
+ {severity === 'info' && }
{title}
- {children}
+ {children}
{stackTrace && (
-
+
{stackTrace}
-
+
)}
diff --git a/apps/web/app/root.tsx b/apps/web/app/root.tsx
index 519a65f36..65d8e7bbb 100644
--- a/apps/web/app/root.tsx
+++ b/apps/web/app/root.tsx
@@ -21,6 +21,7 @@ import {
Outlet,
Scripts,
ScrollRestoration,
+ json,
useLoaderData,
useNavigate
} from '@remix-run/react';
@@ -86,7 +87,7 @@ export async function loader({ context, request }: LoaderFunctionArgs) {
'Unable to load application content. Please try again later.';
}
- return {
+ return json({
env,
loaderErrorMessage,
navigationTree,
@@ -98,7 +99,7 @@ export async function loader({ context, request }: LoaderFunctionArgs) {
}
},
siteSettings
- };
+ });
} catch (error) {
console.error('Failed to load root data:\n', error);
// Delegate to error boundary
@@ -143,15 +144,20 @@ export default function App() {
// Provide app opinionated context to Payload components
const context: PayloadValue = {
- navigate: (path: string) => {
- if (path.match(/^https?:\/\//)) {
- console.warn(
- 'Payload navigation to external URL is not supported:',
- path
- );
- } else {
- navigate(path);
+ navigate: (path, newTab) => {
+ const isExternal = path.startsWith('http');
+ // Open new tab
+ if (newTab) {
+ window.open(path, '_blank');
+ return;
}
+ // Native redirect external links
+ if (isExternal) {
+ window.location.href = path;
+ return;
+ }
+ // Invoke router event for internal links
+ navigate(path);
},
payloadUrl: loaderData.env.PAYLOAD_URL,
submitForm: async (formData) => {
diff --git a/apps/web/app/routes/_index.tsx b/apps/web/app/routes/_index.tsx
index 42c5e74c4..4b26518dd 100644
--- a/apps/web/app/routes/_index.tsx
+++ b/apps/web/app/routes/_index.tsx
@@ -50,7 +50,7 @@ export function ErrorBoundary() {
return (
- The page you're looking for could not be rendered.
+ The landing page could not be rendered.
);
}
diff --git a/apps/web/app/tailwind.css b/apps/web/app/tailwind.css
index 172142da5..0432ac044 100644
--- a/apps/web/app/tailwind.css
+++ b/apps/web/app/tailwind.css
@@ -1,5 +1,4 @@
@import 'tailwindcss';
-@plugin 'tailwindcss-animate';
@plugin "@tailwindcss/typography";
@config '../tailwind-typography.cjs';
@@ -22,11 +21,12 @@
--border: var(--color-zinc-100);
--ring: var(--color-zinc-900);
--card: var(--color-zinc-50);
- --card-foreground: var(--color-zinc-900);
+ --card-foreground: var(--color-zinc-600);
--input: var(--color-zinc-100);
--popover: var(--color-zinc-50);
--popover-foreground: var(--color-zinc-900);
--radius: 0.5rem;
+ --link: var(--color-teal-500);
}
.dark {
@@ -44,12 +44,13 @@
--destructive-foreground: var(--color-zinc-100);
--border: theme('colors.zinc.700/0.4');
--ring: var(--color-zinc-700);
- --card: var(--color-zinc-800);
- --card-foreground: var(--color-zinc-100);
+ --card: theme('colors.zinc.800/0.5');
+ --card-foreground: var(--color-zinc-400);
--input: theme('colors.zinc.700/0.4');
--popover: var(--color-zinc-800);
--popover-foreground: var(--color-zinc-100);
--radius: 0.5rem;
+ --link: var(--color-teal-400);
}
@theme inline {
@@ -72,6 +73,7 @@
--color-input: var(--input);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
+ --color-link: var(--link);
}
@layer base {
diff --git a/libs/app-cms/ui/blocks/src/index.ts b/libs/app-cms/ui/blocks/src/index.ts
index cd8a856dc..771f89990 100644
--- a/libs/app-cms/ui/blocks/src/index.ts
+++ b/libs/app-cms/ui/blocks/src/index.ts
@@ -1,3 +1,4 @@
+export { cardBlock } from './lib/card/card.block';
export { codeBlock } from './lib/code/code.block';
export { contentBlock } from './lib/content/content.block';
export { formBlock } from './lib/form/form.block';
diff --git a/libs/app-cms/ui/blocks/src/lib/card/card.block.ts b/libs/app-cms/ui/blocks/src/lib/card/card.block.ts
new file mode 100644
index 000000000..a4ffc066c
--- /dev/null
+++ b/libs/app-cms/ui/blocks/src/lib/card/card.block.ts
@@ -0,0 +1,59 @@
+import { cardGroupField } from '@codeware/app-cms/ui/fields';
+import type { Block } from 'payload';
+
+/**
+ * Card block that make it possible to select from a list of collection cards,
+ * or to create custom cards for the document implementing this block.
+ */
+export const cardBlock: Block = {
+ slug: 'card',
+ interfaceName: 'CardBlock',
+ fields: [
+ {
+ name: 'collectionCards',
+ type: 'relationship',
+ relationTo: 'cards',
+ hasMany: true,
+ label: {
+ en: 'Reusable cards',
+ sv: 'Återanvändbara kort'
+ },
+ admin: {
+ description: {
+ en: 'Select the reusable cards to be displayed',
+ sv: 'Välj vilka återanvändbara kort som ska visas'
+ }
+ }
+ },
+ {
+ name: 'customCards',
+ type: 'array',
+ label: {
+ en: 'Custom cards',
+ sv: 'Valfria kort'
+ },
+ labels: {
+ singular: {
+ en: 'Card',
+ sv: 'Kort'
+ },
+ plural: {
+ en: 'Cards',
+ sv: 'Kort'
+ }
+ },
+ fields: [cardGroupField],
+ admin: {
+ description: {
+ en: 'Complement with custom cards',
+ sv: 'Komplettera med valfria kort'
+ },
+ components: {
+ RowLabel:
+ '@codeware/app-cms/ui/fields/card-group/CardGroupArrayRowLabel.client'
+ },
+ initCollapsed: true
+ }
+ }
+ ]
+};
diff --git a/libs/app-cms/ui/blocks/src/lib/content/content.block.ts b/libs/app-cms/ui/blocks/src/lib/content/content.block.ts
index 3af9a45fc..35abbc5c8 100644
--- a/libs/app-cms/ui/blocks/src/lib/content/content.block.ts
+++ b/libs/app-cms/ui/blocks/src/lib/content/content.block.ts
@@ -49,7 +49,7 @@ export const contentBlock: Block = {
features: ({ rootFeatures }) => {
return [
...rootFeatures,
- BlocksFeature({ blocks: ['code', 'form', 'media'] }),
+ BlocksFeature({ blocks: ['card', 'code', 'form', 'media'] }),
HeadingFeature({
enabledHeadingSizes: ['h2', 'h3', 'h4', 'h5']
})
diff --git a/libs/app-cms/ui/fields/src/index.ts b/libs/app-cms/ui/fields/src/index.ts
index 80fc9b4a2..706ae8c21 100644
--- a/libs/app-cms/ui/fields/src/index.ts
+++ b/libs/app-cms/ui/fields/src/index.ts
@@ -1,3 +1,6 @@
export { defaultLexical } from './lib/default-lexical';
+export { cardGroupField } from './lib/card-group/card-group.field';
export { codeField } from './lib/code/code.field';
+export { iconPickerField } from './lib/icon-picker/icon-picker.field';
+export { linkGroupField } from './lib/link-group/link-group.field';
export { slugField } from './lib/slug/slug.field';
diff --git a/libs/app-cms/ui/fields/src/lib/card-group/CardGroupArrayRowLabel.client.tsx b/libs/app-cms/ui/fields/src/lib/card-group/CardGroupArrayRowLabel.client.tsx
new file mode 100644
index 000000000..e66b26cab
--- /dev/null
+++ b/libs/app-cms/ui/fields/src/lib/card-group/CardGroupArrayRowLabel.client.tsx
@@ -0,0 +1,24 @@
+'use client';
+
+import type { CardGroup } from '@codeware/shared/util/payload-types';
+import { type RowLabelProps, useRowLabel } from '@payloadcms/ui';
+
+import { cardGroupFieldName } from './card-group.field';
+
+/**
+ * Custom array row label for any array field that contains CardGroup fields.
+ *
+ * Displays the card title instead of the default row number.
+ */
+export const CardGroupArrayRowLabel: React.FC
= () => {
+ // The cards group fields have a parent group type that is not included in `CardGroup`
+ // so we need to use a record to access the card data
+ const { data, rowNumber } = useRowLabel>();
+
+ const { title } = data[cardGroupFieldName] ?? {};
+
+ // TODO: Language support
+ return title ?? `Card ${String(rowNumber).padStart(2, '0')}`;
+};
+
+export default CardGroupArrayRowLabel;
diff --git a/libs/app-cms/ui/fields/src/lib/card-group/card-group.field.ts b/libs/app-cms/ui/fields/src/lib/card-group/card-group.field.ts
new file mode 100644
index 000000000..a7a20a8dd
--- /dev/null
+++ b/libs/app-cms/ui/fields/src/lib/card-group/card-group.field.ts
@@ -0,0 +1,158 @@
+import type {
+ CardGroup,
+ CardGroupLink
+} from '@codeware/shared/util/payload-types';
+import type { Condition, GroupField, TypeWithID } from 'payload';
+
+import { iconPickerField } from '../icon-picker/icon-picker.field';
+import { linkGroupField } from '../link-group/link-group.field';
+
+const link = linkGroupField({
+ disableLabel: true
+});
+
+const isLinkEnabled: Condition = (_, siblingData) =>
+ siblingData?.enableLink === true;
+
+const isNavTrigger =
+ (
+ trigger: CardGroupLink['navTrigger']
+ ): Condition =>
+ (_, siblingData) =>
+ siblingData?.navTrigger === trigger;
+
+export const cardGroupFieldName = 'card';
+
+/**
+ * Card group field that defines the card fields.
+ *
+ * It can be used as a group field or just the fields of the group.
+ */
+export const cardGroupField: GroupField = {
+ name: cardGroupFieldName,
+ type: 'group',
+ interfaceName: 'CardGroup',
+ label: false,
+ admin: {
+ hideGutter: true
+ },
+ fields: [
+ {
+ ...iconPickerField,
+ label: false,
+ admin: {
+ ...(iconPickerField.admin ?? {}),
+ description: {
+ en: 'Select an icon that represent the card',
+ sv: 'Välj en ikon som representerar kortet'
+ },
+ disableListColumn: true,
+ disableListFilter: true
+ }
+ },
+ {
+ name: 'title',
+ type: 'text',
+ label: {
+ en: 'Title',
+ sv: 'Titel'
+ },
+ localized: true,
+ required: true
+ },
+ {
+ name: 'description',
+ type: 'text',
+ label: {
+ en: 'Description',
+ sv: 'Beskrivning'
+ },
+ admin: {
+ description: {
+ en: 'A text that will complement the title',
+ sv: 'En text som ska komplettera titeln'
+ }
+ },
+ localized: true
+ },
+ {
+ name: 'content',
+ type: 'textarea',
+ label: {
+ en: 'Main content',
+ sv: 'Huvudinnehåll'
+ },
+ localized: true,
+ required: true
+ },
+ {
+ name: 'enableLink',
+ type: 'checkbox',
+ label: {
+ en: 'Card link',
+ sv: 'Länk på kortet'
+ },
+ admin: {
+ description: {
+ en: 'Let the card link to a page or external URL',
+ sv: 'Låt kortet länka till en sida eller en extern URL'
+ }
+ }
+ },
+ {
+ ...link,
+ interfaceName: 'CardGroupLink',
+ label: false,
+ admin: {
+ ...link.admin,
+ condition: isLinkEnabled,
+ disableListColumn: true,
+ disableListFilter: true
+ },
+ fields: [
+ ...link.fields,
+ {
+ name: 'navTrigger',
+ type: 'radio',
+ label: {
+ en: 'Navigation trigger',
+ sv: 'Navigering aktiveras'
+ },
+ admin: {
+ layout: 'horizontal'
+ },
+ options: [
+ {
+ label: {
+ en: 'Click on card',
+ sv: 'Klicka på kortet'
+ },
+ value: 'card'
+ },
+ {
+ label: {
+ en: 'Click on link',
+ sv: 'Klicka på en länk'
+ },
+ value: 'link'
+ }
+ ],
+ defaultValue: 'card'
+ },
+ {
+ name: 'label',
+ type: 'text',
+ label: {
+ en: 'Link label',
+ sv: 'Länk text'
+ },
+ admin: {
+ condition: isNavTrigger('link')
+ },
+ localized: true,
+ required: true
+ }
+ ]
+ }
+ ]
+};
diff --git a/libs/app-cms/ui/fields/src/lib/icon-picker/IconPickerField.client.tsx b/libs/app-cms/ui/fields/src/lib/icon-picker/IconPickerField.client.tsx
new file mode 100644
index 000000000..ac894f75e
--- /dev/null
+++ b/libs/app-cms/ui/fields/src/lib/icon-picker/IconPickerField.client.tsx
@@ -0,0 +1,34 @@
+'use client';
+
+import {
+ type HeroIcon,
+ IconPicker
+} from '@codeware/shared/ui/react-components';
+import { FieldDescription, FieldLabel, useField } from '@payloadcms/ui';
+import type { TextFieldClientComponent } from 'payload';
+
+/**
+ * Icon picker field component for client-side rendering.
+ *
+ * Displays a picker field with options for all available heroicons.
+ * The selected icon is displayed next to the picker field.
+ */
+export const IconPickerField: TextFieldClientComponent = ({ path, field }) => {
+ const { setValue, value } = useField({ path });
+
+ return (
+
+
+ setValue(icon)} />
+ {field.admin?.description && (
+
+ )}
+
+ );
+};
+
+export default IconPickerField;
diff --git a/libs/app-cms/ui/fields/src/lib/icon-picker/icon-picker.field.ts b/libs/app-cms/ui/fields/src/lib/icon-picker/icon-picker.field.ts
new file mode 100644
index 000000000..f85f28496
--- /dev/null
+++ b/libs/app-cms/ui/fields/src/lib/icon-picker/icon-picker.field.ts
@@ -0,0 +1,17 @@
+import type { TextField } from 'payload';
+
+/**
+ * Icon picker field configuration for Payload CMS.
+ *
+ * This field allows users to select an icon from a list of available heroicons.
+ * The selected icon is displayed next to the picker field.
+ */
+export const iconPickerField: TextField = {
+ name: 'icon',
+ type: 'text',
+ admin: {
+ components: {
+ Field: '@codeware/app-cms/ui/fields/icon-picker/IconPickerField.client'
+ }
+ }
+};
diff --git a/libs/app-cms/ui/fields/src/lib/link-group/link-group.field.ts b/libs/app-cms/ui/fields/src/lib/link-group/link-group.field.ts
new file mode 100644
index 000000000..a53f77ced
--- /dev/null
+++ b/libs/app-cms/ui/fields/src/lib/link-group/link-group.field.ts
@@ -0,0 +1,130 @@
+import type { Field, GroupField } from 'payload';
+
+type LinkType = (options?: {
+ disableLabel?: boolean;
+ overrides?: Partial;
+}) => GroupField;
+
+/**
+ * Link group field that can be used to link to a document or a custom URL.
+ *
+ * @param options - Options for the link field
+ */
+export const linkGroupField: LinkType = ({ disableLabel = false } = {}) => {
+ const linkResult: GroupField = {
+ name: 'link',
+ type: 'group',
+ admin: {
+ hideGutter: true
+ },
+ fields: [
+ {
+ type: 'row',
+ fields: [
+ {
+ name: 'type',
+ type: 'radio',
+ admin: {
+ layout: 'horizontal',
+ width: '50%'
+ },
+ defaultValue: 'reference',
+ options: [
+ {
+ label: {
+ en: 'Internal link',
+ sv: 'Intern länk'
+ },
+ value: 'reference'
+ },
+ {
+ label: {
+ en: 'Custom URL',
+ sv: 'Anpassad URL'
+ },
+ value: 'custom'
+ }
+ ]
+ },
+ {
+ name: 'newTab',
+ type: 'checkbox',
+ admin: {
+ style: {
+ alignSelf: 'flex-end'
+ },
+ width: '50%'
+ },
+ label: {
+ en: 'Open in new tab',
+ sv: 'Öppna i ny flik'
+ }
+ }
+ ]
+ }
+ ]
+ };
+
+ const linkTypes: Field[] = [
+ {
+ name: 'reference',
+ type: 'relationship',
+ admin: {
+ condition: (_, siblingData) => siblingData?.type === 'reference'
+ },
+ label: {
+ en: 'Document to link to',
+ sv: 'Dokument att länka till'
+ },
+ relationTo: ['pages', 'posts'],
+ required: true
+ },
+ {
+ name: 'url',
+ type: 'text',
+ admin: {
+ description: {
+ en: 'Add protocol (http:// or https://) if the link is external',
+ sv: 'Lägg till protokoll (http:// eller https://) om länken är extern'
+ },
+ condition: (_, siblingData) => siblingData?.type === 'custom'
+ },
+ label: {
+ en: 'Custom URL',
+ sv: 'Anpassad URL'
+ },
+ required: true
+ }
+ ];
+
+ if (disableLabel) {
+ linkResult.fields = [...linkResult.fields, ...linkTypes];
+ return linkResult;
+ }
+
+ linkTypes.map((linkType) => ({
+ ...linkType,
+ admin: {
+ ...linkType.admin,
+ width: '50%'
+ }
+ }));
+
+ linkResult.fields.push({
+ type: 'row',
+ fields: [
+ ...linkTypes,
+ {
+ name: 'label',
+ type: 'text',
+ admin: {
+ width: '50%'
+ },
+ label: 'Label',
+ required: true
+ }
+ ]
+ });
+
+ return linkResult;
+};
diff --git a/libs/app-cms/util/plugins/src/lib/plugins/get-multi-tenant-plugin.ts b/libs/app-cms/util/plugins/src/lib/plugins/get-multi-tenant-plugin.ts
index 447facb5a..2cf27f6cb 100644
--- a/libs/app-cms/util/plugins/src/lib/plugins/get-multi-tenant-plugin.ts
+++ b/libs/app-cms/util/plugins/src/lib/plugins/get-multi-tenant-plugin.ts
@@ -12,6 +12,7 @@ export const getMultiTenantPlugin = () =>
name: 'tenant'
},
collections: {
+ cards: {},
categories: {},
forms: {},
'form-submissions': {},
diff --git a/libs/shared/ui/payload-components/src/lib/RenderBlocks.tsx b/libs/shared/ui/payload-components/src/lib/RenderBlocks.tsx
index c37d5baba..4b359c755 100644
--- a/libs/shared/ui/payload-components/src/lib/RenderBlocks.tsx
+++ b/libs/shared/ui/payload-components/src/lib/RenderBlocks.tsx
@@ -2,6 +2,7 @@ import type { BlockSlug, Page } from '@codeware/shared/util/payload-types';
import React, { Fragment } from 'react';
// import { CallToActionBlock } from '@/blocks/CallToAction/Component'
+import { CardBlock } from './blocks/CardBlock';
import { CodeBlock } from './blocks/CodeBlock';
import { ContentBlock } from './blocks/ContentBlock';
import { FormBlock } from './blocks/FormBlock';
@@ -12,6 +13,7 @@ const blocksMap: Record<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
React.JSXElementConstructor
> = {
+ card: CardBlock,
code: CodeBlock,
content: ContentBlock,
form: FormBlock,
@@ -36,7 +38,7 @@ export const RenderBlocks: React.FC = ({ blocks }) => {
if (Block) {
return (
-
+
);
diff --git a/libs/shared/ui/payload-components/src/lib/blocks/CardBlock.tsx b/libs/shared/ui/payload-components/src/lib/blocks/CardBlock.tsx
new file mode 100644
index 000000000..eb7b8ba11
--- /dev/null
+++ b/libs/shared/ui/payload-components/src/lib/blocks/CardBlock.tsx
@@ -0,0 +1,133 @@
+import { HeroIcon, IconRenderer } from '@codeware/shared/ui/react-components';
+import { Button } from '@codeware/shared/ui/shadcn/components/button';
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle
+} from '@codeware/shared/ui/shadcn/components/card';
+import type {
+ CardBlock as CardBlockProps,
+ CardGroup
+} from '@codeware/shared/util/payload-types';
+import { cn } from '@codeware/shared/util/ui';
+import { ExternalLinkIcon, LinkIcon } from 'lucide-react';
+
+import { useColumnSize } from '../providers/ColumnSizeProvider';
+import { usePayload } from '../providers/PayloadProvider';
+import { extractLink } from '../utils/extract-link';
+
+type Props = CardBlockProps;
+
+/**
+ * Render Payload card block cards in a responsive grid.
+ * Features subtle hover effects and modern styling.
+ */
+export const CardBlock: React.FC
= ({
+ collectionCards,
+ customCards
+}) => {
+ const { navigate } = usePayload();
+ const { effectiveFraction = 1 } = useColumnSize({ silent: true }) ?? {};
+
+ const cards =
+ collectionCards?.reduce((acc, card) => {
+ if (typeof card === 'object') {
+ acc.push(card);
+ }
+ return acc;
+ }, [] as Array) ?? [];
+
+ if (customCards) {
+ cards.push(...customCards.map((c) => c.card));
+ }
+
+ if (cards.length === 0) {
+ return null;
+ }
+
+ // Number of columns should not exceed the number of cards
+ // or be more than will fit in the container
+ const maxColumns = Math.min(
+ cards.length,
+ effectiveFraction < 0.5 ? 1 : effectiveFraction < 0.75 ? 2 : 3
+ );
+
+ return (
+ 1 },
+ { 'lg:grid-cols-3 lg:gap-8': maxColumns === 3 }
+ )}
+ >
+ {cards.map((card, index) => {
+ const { title, description, content, enableLink, link, icon } = card;
+
+ const hasHeader = title || description || icon;
+ const linkDetails = enableLink ? extractLink(link) : null;
+
+ return (
+
+ linkDetails &&
+ linkDetails.navTrigger === 'card' &&
+ navigate(linkDetails.url, linkDetails.newTab)
+ }
+ className={cn(
+ 'text-card-foreground group bg-card/50 hover:bg-card overflow-hidden rounded-lg border transition-all duration-300 ease-in-out',
+ {
+ 'cursor-pointer':
+ linkDetails && linkDetails.navTrigger === 'card'
+ }
+ )}
+ >
+ {hasHeader && (
+
+ {icon && (
+
+
+
+ )}
+ {title && (
+
+ {title}
+
+ )}
+ {description && (
+
+ {description}
+
+ )}
+
+ )}
+
+ {content}
+
+ {/* Add the link to card footer */}
+ {linkDetails && linkDetails.navTrigger === 'link' && (
+
+
+
+ )}
+
+ );
+ })}
+
+ );
+};
diff --git a/libs/shared/ui/payload-components/src/lib/blocks/ContentBlock.tsx b/libs/shared/ui/payload-components/src/lib/blocks/ContentBlock.tsx
index 7f78ccf12..599025d69 100644
--- a/libs/shared/ui/payload-components/src/lib/blocks/ContentBlock.tsx
+++ b/libs/shared/ui/payload-components/src/lib/blocks/ContentBlock.tsx
@@ -2,6 +2,8 @@ import type { ContentBlock as ContentBlockProps } from '@codeware/shared/util/pa
import { cn } from '@codeware/shared/util/ui';
import React from 'react';
+import { ColumnSizeProvider } from '../providers/ColumnSizeProvider';
+
import { RichText } from './RichText';
type Props = ContentBlockProps;
@@ -21,16 +23,18 @@ export const ContentBlock: React.FC = ({ columns }) => {
const size = col.size ?? 'full';
return (
-
- {richText && }
-
+
+
+ {richText && }
+
+
);
})}
diff --git a/libs/shared/ui/payload-components/src/lib/blocks/FormBlock.tsx b/libs/shared/ui/payload-components/src/lib/blocks/FormBlock.tsx
index e2444def3..d243352ef 100644
--- a/libs/shared/ui/payload-components/src/lib/blocks/FormBlock.tsx
+++ b/libs/shared/ui/payload-components/src/lib/blocks/FormBlock.tsx
@@ -3,12 +3,14 @@ import {
DialogContent,
DialogDescription,
DialogHeader,
- DialogTitle,
+ DialogTitle
+} from '@codeware/shared/ui/shadcn/components/dialog';
+import {
Form,
FormField,
FormItem,
FormMessage
-} from '@codeware/shared/ui/shadcn';
+} from '@codeware/shared/ui/shadcn/components/form';
import type {
FormBlock as FormBlockProps,
FormSubmissionData,
@@ -184,7 +186,7 @@ export const FormBlock: React.FC = ({