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:
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
|
||||
+28
-16
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "twenty-client-sdk",
|
||||
"version": "2.5.1",
|
||||
"version": "2.6.0",
|
||||
"sideEffects": false,
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "twenty-sdk",
|
||||
"version": "2.5.1",
|
||||
"version": "2.6.0",
|
||||
"sideEffects": false,
|
||||
"bin": {
|
||||
"twenty": "dist/cli.cjs"
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user