Seed Front Components (#19220)
https://github.com/user-attachments/assets/6b0887e8-8ba4-49c9-9371-e3db3e9fdde8
This commit is contained in:
committed by
GitHub
parent
268543a08e
commit
885ab8b444
+107
File diff suppressed because one or more lines are too long
+120
@@ -0,0 +1,120 @@
|
||||
import {
|
||||
defineFrontComponent,
|
||||
useFrontComponentExecutionContext,
|
||||
useUserId,
|
||||
} from 'twenty-sdk';
|
||||
import { useState } from 'react';
|
||||
|
||||
const HelloWorld = () => {
|
||||
const [renderCount, setRenderCount] = useState(0);
|
||||
|
||||
const userId = useUserId();
|
||||
const fullContext = useFrontComponentExecutionContext((context) => context);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: 24,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 16,
|
||||
fontFamily: 'system-ui, sans-serif',
|
||||
background: '#f0f9ff',
|
||||
borderRadius: 12,
|
||||
border: '2px solid #38bdf8',
|
||||
maxWidth: 400,
|
||||
}}
|
||||
>
|
||||
<h2
|
||||
style={{
|
||||
color: '#0369a1',
|
||||
fontWeight: 700,
|
||||
fontSize: 18,
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
Hello World
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
<p
|
||||
style={{
|
||||
fontSize: 12,
|
||||
fontWeight: 600,
|
||||
color: '#64748b',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 1,
|
||||
}}
|
||||
>
|
||||
User ID
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
fontSize: 16,
|
||||
fontWeight: 700,
|
||||
color: '#0c4a6e',
|
||||
wordBreak: 'break-all',
|
||||
}}
|
||||
>
|
||||
{userId ?? 'null'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p
|
||||
style={{
|
||||
fontSize: 12,
|
||||
fontWeight: 600,
|
||||
color: '#64748b',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 1,
|
||||
}}
|
||||
>
|
||||
Execution Context
|
||||
</p>
|
||||
<pre
|
||||
style={{
|
||||
fontSize: 13,
|
||||
fontWeight: 700,
|
||||
color: '#0c4a6e',
|
||||
background: '#e0f2fe',
|
||||
padding: 12,
|
||||
borderRadius: 8,
|
||||
overflow: 'auto',
|
||||
wordBreak: 'break-all',
|
||||
}}
|
||||
>
|
||||
{JSON.stringify(fullContext, null, 2) ?? 'undefined'}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<button
|
||||
onClick={() => setRenderCount((c) => c + 1)}
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
backgroundColor: '#0284c7',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: 6,
|
||||
fontWeight: 600,
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
Re-render
|
||||
</button>
|
||||
<span style={{ fontSize: 14, color: '#64748b' }}>
|
||||
Render count: {renderCount}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: 'seed-front-component-hello-world',
|
||||
name: 'Hello World',
|
||||
description:
|
||||
'A sample visual front component that displays execution context',
|
||||
component: HelloWorld,
|
||||
});
|
||||
+107
File diff suppressed because one or more lines are too long
+32
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
defineFrontComponent,
|
||||
enqueueSnackbar,
|
||||
unmountFrontComponent,
|
||||
} from 'twenty-sdk';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const ShowNotification = () => {
|
||||
useEffect(() => {
|
||||
const run = async () => {
|
||||
await enqueueSnackbar({
|
||||
message: 'Hello from a headless front component!',
|
||||
variant: 'success',
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
await unmountFrontComponent();
|
||||
};
|
||||
|
||||
run();
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: 'seed-front-component-show-notification',
|
||||
name: 'Show Notification',
|
||||
description: 'A sample headless front component that displays a notification',
|
||||
isHeadless: true,
|
||||
component: ShowNotification,
|
||||
});
|
||||
+12
-1
@@ -1,6 +1,12 @@
|
||||
import { Field, HideField, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@@ -16,6 +22,11 @@ export class CreateFrontComponentInput {
|
||||
@HideField()
|
||||
universalIdentifier?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@HideField()
|
||||
isHeadless?: boolean;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Field()
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
import { ASSET_PATH } from 'src/constants/assets-path';
|
||||
|
||||
export type FrontComponentSeedProjectFile = {
|
||||
name: string;
|
||||
path: string;
|
||||
content: Buffer;
|
||||
};
|
||||
|
||||
const getAllFiles = async (
|
||||
rootDir: string,
|
||||
dir: string = rootDir,
|
||||
files: FrontComponentSeedProjectFile[] = [],
|
||||
): Promise<FrontComponentSeedProjectFile[]> => {
|
||||
const dirEntries = await fs.readdir(dir, { withFileTypes: true });
|
||||
|
||||
for (const entry of dirEntries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
await getAllFiles(rootDir, fullPath, files);
|
||||
} else {
|
||||
files.push({
|
||||
path: path.relative(rootDir, dir),
|
||||
name: entry.name,
|
||||
content: await fs.readFile(fullPath),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
};
|
||||
|
||||
export const getFrontComponentSeedProjectFiles = async (
|
||||
subdirectory: string,
|
||||
): Promise<FrontComponentSeedProjectFile[]> => {
|
||||
const seedProjectPath = path.join(
|
||||
ASSET_PATH,
|
||||
'engine/metadata-modules/front-component/constants/seed-project',
|
||||
subdirectory,
|
||||
);
|
||||
|
||||
return getAllFiles(seedProjectPath);
|
||||
};
|
||||
Reference in New Issue
Block a user