Update create twenty app scaffolded front component (#20733)

- Replace inline SVG icons with proper Avatar and icon components
(IconBox, IconHierarchy, IconLayout, IconSettingsAutomation) from
twenty-sdk/ui in the scaffolded front component
  template
- Strip trailing slashes from workspace/API URLs in both
create-twenty-app CLI and twenty-sdk remote commands to prevent
malformed requests
  - Fix the application settings link to navigate to #installed anchor
- Bump twenty-sdk, twenty-client-sdk, and create-twenty-app versions to
2.6.0

<img width="1512" height="824" alt="image"
src="https://github.com/user-attachments/assets/8561d7bb-3458-46c4-b01e-664321634b4c"
/>
This commit is contained in:
martmull
2026-05-20 10:23:10 +02:00
committed by GitHub
parent f630ce34fe
commit f1b7c21b6c
7 changed files with 59 additions and 39 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "create-twenty-app",
"version": "2.5.1",
"version": "2.6.0",
"description": "Command-line interface to create Twenty application",
"main": "dist/cli.cjs",
"bin": "dist/cli.cjs",
+6 -1
View File
@@ -74,12 +74,17 @@ const program = new Command(packageJson.name)
);
}
const workspaceUrl = (options?.workspaceUrl ?? options?.apiUrl)?.replace(
/\/+$/,
'',
);
await new CreateAppCommand().execute({
directory,
name: options?.name,
displayName: options?.displayName,
description: options?.description,
workspaceUrl: options?.workspaceUrl ?? options?.apiUrl,
workspaceUrl,
authenticationMethod: options?.authenticationMethod,
});
},
@@ -1,4 +1,11 @@
import { defineFrontComponent } from 'twenty-sdk/define';
import {
Avatar,
IconBox,
IconHierarchy,
IconLayout,
IconSettingsAutomation,
} from 'twenty-sdk/ui';
import { useState } from 'react';
import {
@@ -30,7 +37,7 @@ const CATEGORIES = [
href: `${DOCS_BASE_URL}/logic/logic-functions`,
},
{
label: 'SERVERLESS FUNCT.',
label: 'LOGIC FUNCTION',
href: `${DOCS_BASE_URL}/logic/logic-functions`,
},
{
@@ -59,15 +66,6 @@ const CATEGORIES = [
},
] as const;
const ItemIcon = ({ color }: { color: string }) => (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<rect x="2" y="2" width="5" height="5" rx="1" fill={color} />
<rect x="9" y="2" width="5" height="5" rx="1" fill={color} opacity="0.6" />
<rect x="2" y="9" width="5" height="5" rx="1" fill={color} opacity="0.6" />
<rect x="9" y="9" width="5" height="5" rx="1" fill={color} opacity="0.3" />
</svg>
);
const ArrowUpRight = ({ color = '#999' }: { color?: string }) => (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<path
@@ -93,6 +91,18 @@ const CategoryCard = ({
}) => {
const [hoveredItem, setHoveredItem] = useState<string | null>(null);
const CategoryIcon = () => {
if (title === 'Data model') {
return <IconHierarchy color={color} size={'20px'} />;
}
if (title === 'Logic') {
return <IconSettingsAutomation color={color} size={'20px'} />;
}
if (title === 'Layout') {
return <IconLayout color={color} size={'20px'} />;
}
};
return (
<div
style={{
@@ -111,8 +121,12 @@ const CategoryCard = ({
style={{
padding: '16px 20px',
background: `${color}22`,
display: 'flex',
alignItems: 'center',
gap: '12px',
}}
>
<CategoryIcon />
<span
style={{
fontSize: '16px',
@@ -154,7 +168,7 @@ const CategoryCard = ({
transition: 'background 0.15s',
}}
>
<ItemIcon color={color} />
<IconBox color={color} size={'20px'} />
<span
style={{
fontSize: '13px',
@@ -190,13 +204,11 @@ const MainPage = () => {
padding: '40px',
}}
>
{/*<Avatar
avatarUrl={getPublicAssetUrl('logo.svg')}
<Avatar
placeholder={APP_DISPLAY_NAME}
placeholderColorSeed={APP_DISPLAY_NAME}
type="squared"
size="xl"
/>*/}
/>
<span
style={{
fontSize: '24px',
@@ -220,7 +232,7 @@ const MainPage = () => {
You can now add content to your app.
</span>
<a
href="/settings/applications"
href="/settings/applications#installed"
style={{
display: 'inline-flex',
alignItems: 'center',
@@ -17,7 +17,7 @@ import {
DEV_API_URL,
serverStart,
} from 'twenty-sdk/cli';
import { isDefined } from 'twenty-shared/utils';
import { isDefined, normalizeUrl } from 'twenty-shared/utils';
import {
getDockerInstallInstructions,
isDockerInstalled,
@@ -389,7 +389,7 @@ export class CreateAppCommand {
const frontUrl = urls.customUrl ?? urls.subdomainUrl;
return frontUrl?.replace(/\/+$/, '') ?? null;
return frontUrl ? normalizeUrl(frontUrl) : null;
}
private async readMainPageLayoutUniversalIdentifier(
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "twenty-client-sdk",
"version": "2.5.1",
"version": "2.6.0",
"sideEffects": false,
"license": "AGPL-3.0",
"scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "twenty-sdk",
"version": "2.5.1",
"version": "2.6.0",
"sideEffects": false,
"bin": {
"twenty": "dist/cli.cjs"
+20 -17
View File
@@ -7,6 +7,7 @@ import { detectLocalServer } from '@/cli/utilities/server/detect-local-server';
import chalk from 'chalk';
import type { Command } from 'commander';
import inquirer from 'inquirer';
import { normalizeUrl } from 'twenty-shared/utils';
const deriveRemoteName = (url: string): string => {
try {
@@ -117,7 +118,7 @@ export const registerRemoteCommands = (program: Command): void => {
return;
}
let apiUrl = options.apiUrl;
let apiUrl = options.apiUrl ? normalizeUrl(options.apiUrl) : undefined;
if (!apiUrl) {
const detectedUrl = await detectLocalServer();
@@ -136,24 +137,26 @@ export const registerRemoteCommands = (program: Command): void => {
console.log(chalk.gray(`Found local server at ${detectedUrl}`));
apiUrl = detectedUrl;
} else {
apiUrl = (
await inquirer.prompt<{ apiUrl: string }>([
{
type: 'input',
name: 'apiUrl',
message: 'Twenty server URL:',
validate: (input: string) => {
try {
new URL(input);
apiUrl = normalizeUrl(
(
await inquirer.prompt<{ apiUrl: string }>([
{
type: 'input',
name: 'apiUrl',
message: 'Twenty server URL:',
validate: (input: string) => {
try {
new URL(input);
return true;
} catch {
return 'Please enter a valid URL';
}
return true;
} catch {
return 'Please enter a valid URL';
}
},
},
},
])
).apiUrl;
])
).apiUrl,
);
}
}