create-twenty-app 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" fill="none">
2
+ <rect width="80" height="80" rx="16" fill="#141414"/>
3
+ <rect x="20" y="20" width="16" height="16" rx="4" fill="#FAFAFA"/>
4
+ <rect x="44" y="20" width="16" height="16" rx="4" fill="#FAFAFA" opacity="0.6"/>
5
+ <rect x="20" y="44" width="16" height="16" rx="4" fill="#FAFAFA" opacity="0.6"/>
6
+ <rect x="44" y="44" width="16" height="16" rx="4" fill="#FAFAFA" opacity="0.3"/>
7
+ </svg>
@@ -2,3 +2,10 @@ export const APP_DISPLAY_NAME = 'DISPLAY-NAME-TO-BE-GENERATED';
2
2
  export const APP_DESCRIPTION = 'DESCRIPTION-TO-BE-GENERATED';
3
3
  export const APPLICATION_UNIVERSAL_IDENTIFIER = 'UUID-TO-BE-GENERATED';
4
4
  export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = 'UUID-TO-BE-GENERATED';
5
+ export const MAIN_PAGE_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER =
6
+ 'UUID-TO-BE-GENERATED';
7
+ export const MAIN_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER = 'UUID-TO-BE-GENERATED';
8
+ export const MAIN_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER = 'UUID-TO-BE-GENERATED';
9
+ export const MAIN_PAGE_WIDGET_UNIVERSAL_IDENTIFIER = 'UUID-TO-BE-GENERATED';
10
+ export const MAIN_PAGE_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER =
11
+ 'UUID-TO-BE-GENERATED';
@@ -0,0 +1,291 @@
1
+ import { defineFrontComponent } from 'twenty-sdk/define';
2
+ import {
3
+ Avatar,
4
+ IconBox,
5
+ IconHierarchy,
6
+ IconLayout,
7
+ IconSettingsAutomation,
8
+ } from 'twenty-sdk/ui';
9
+ import { useState } from 'react';
10
+
11
+ import {
12
+ APP_DISPLAY_NAME,
13
+ MAIN_PAGE_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
14
+ } from 'src/constants/universal-identifiers';
15
+
16
+ const DOCS_BASE_URL = 'https://docs.twenty.com/developers/extend/apps';
17
+
18
+ const CATEGORIES = [
19
+ {
20
+ title: 'Data model',
21
+ color: '#73D08D',
22
+ items: [
23
+ { label: 'CUSTOM OBJECT', href: `${DOCS_BASE_URL}/data/objects` },
24
+ {
25
+ label: 'CUSTOM FIELDS',
26
+ href: `${DOCS_BASE_URL}/data/extending-objects`,
27
+ },
28
+ ],
29
+ rotation: '2.4deg',
30
+ },
31
+ {
32
+ title: 'Logic',
33
+ color: '#F4D345',
34
+ items: [
35
+ {
36
+ label: 'TOOLS',
37
+ href: `${DOCS_BASE_URL}/logic/logic-functions`,
38
+ },
39
+ {
40
+ label: 'LOGIC FUNCTION',
41
+ href: `${DOCS_BASE_URL}/logic/logic-functions`,
42
+ },
43
+ {
44
+ label: 'SKILLS',
45
+ href: `${DOCS_BASE_URL}/logic/skills-and-agents`,
46
+ },
47
+ ],
48
+ rotation: '0deg',
49
+ },
50
+ {
51
+ title: 'Layout',
52
+ color: '#C4A2E0',
53
+ items: [
54
+ { label: 'VIEWS', href: `${DOCS_BASE_URL}/layout/views` },
55
+ { label: 'WIDGETS', href: `${DOCS_BASE_URL}/layout/page-layouts` },
56
+ {
57
+ label: 'LAYOUT PAGES',
58
+ href: `${DOCS_BASE_URL}/layout/page-layouts`,
59
+ },
60
+ {
61
+ label: 'COMMANDS',
62
+ href: `${DOCS_BASE_URL}/layout/command-menu-items`,
63
+ },
64
+ ],
65
+ rotation: '-2.8deg',
66
+ },
67
+ ] as const;
68
+
69
+ const ArrowUpRight = ({ color = '#999' }: { color?: string }) => (
70
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
71
+ <path
72
+ d="M4.5 3.5H10.5V9.5M10.5 3.5L3.5 10.5"
73
+ stroke={color}
74
+ strokeWidth="1.2"
75
+ strokeLinecap="round"
76
+ strokeLinejoin="round"
77
+ />
78
+ </svg>
79
+ );
80
+
81
+ const CategoryCard = ({
82
+ title,
83
+ color,
84
+ items,
85
+ rotation,
86
+ }: {
87
+ title: string;
88
+ color: string;
89
+ items: ReadonlyArray<{ label: string; href: string }>;
90
+ rotation: string;
91
+ }) => {
92
+ const [hoveredItem, setHoveredItem] = useState<string | null>(null);
93
+
94
+ const CategoryIcon = () => {
95
+ if (title === 'Data model') {
96
+ return <IconHierarchy color={color} size={'20px'} />;
97
+ }
98
+ if (title === 'Logic') {
99
+ return <IconSettingsAutomation color={color} size={'20px'} />;
100
+ }
101
+ if (title === 'Layout') {
102
+ return <IconLayout color={color} size={'20px'} />;
103
+ }
104
+ };
105
+
106
+ return (
107
+ <div
108
+ style={{
109
+ display: 'flex',
110
+ flexDirection: 'column',
111
+ border: `1px solid ${color}80`,
112
+ borderRadius: '12px',
113
+ overflow: 'hidden',
114
+ width: '240px',
115
+ background: '#FFFFFF',
116
+ transform: `rotate(${rotation})`,
117
+ boxShadow: '0 2px 8px rgba(0, 0, 0, 0.04)',
118
+ }}
119
+ >
120
+ <div
121
+ style={{
122
+ padding: '16px 20px',
123
+ background: `${color}22`,
124
+ display: 'flex',
125
+ alignItems: 'center',
126
+ gap: '12px',
127
+ }}
128
+ >
129
+ <CategoryIcon />
130
+ <span
131
+ style={{
132
+ fontSize: '16px',
133
+ fontWeight: 600,
134
+ color: color,
135
+ }}
136
+ >
137
+ {title}
138
+ </span>
139
+ </div>
140
+ <div
141
+ style={{
142
+ display: 'flex',
143
+ flexDirection: 'column',
144
+ padding: '8px',
145
+ gap: '4px',
146
+ }}
147
+ >
148
+ {items.map((item) => {
149
+ const isHovered = hoveredItem === item.label;
150
+
151
+ return (
152
+ <a
153
+ key={item.label}
154
+ href={item.href}
155
+ target="_blank"
156
+ rel="noopener noreferrer"
157
+ onMouseEnter={() => setHoveredItem(item.label)}
158
+ onMouseLeave={() => setHoveredItem(null)}
159
+ style={{
160
+ display: 'flex',
161
+ alignItems: 'center',
162
+ gap: '10px',
163
+ textDecoration: 'none',
164
+ cursor: 'pointer',
165
+ padding: '10px 12px',
166
+ borderRadius: '8px',
167
+ background: isHovered ? '#0000000A' : 'transparent',
168
+ transition: 'background 0.15s',
169
+ }}
170
+ >
171
+ <IconBox color={color} size={'20px'} />
172
+ <span
173
+ style={{
174
+ fontSize: '13px',
175
+ fontWeight: 300,
176
+ color: '#333',
177
+ letterSpacing: '0.5px',
178
+ flex: 1,
179
+ }}
180
+ >
181
+ {item.label}
182
+ </span>
183
+ {isHovered && <ArrowUpRight />}
184
+ </a>
185
+ );
186
+ })}
187
+ </div>
188
+ </div>
189
+ );
190
+ };
191
+
192
+ const MainPage = () => {
193
+ return (
194
+ <div
195
+ style={{
196
+ display: 'flex',
197
+ flexDirection: 'column',
198
+ alignItems: 'center',
199
+ justifyContent: 'center',
200
+ height: '100%',
201
+ fontFamily:
202
+ 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
203
+ gap: '8px',
204
+ padding: '40px',
205
+ }}
206
+ >
207
+ <Avatar
208
+ placeholder={APP_DISPLAY_NAME}
209
+ placeholderColorSeed={APP_DISPLAY_NAME}
210
+ size="xl"
211
+ />
212
+ <span
213
+ style={{
214
+ fontSize: '24px',
215
+ fontWeight: 600,
216
+ color: '#333',
217
+ marginTop: '8px',
218
+ }}
219
+ >
220
+ {APP_DISPLAY_NAME}
221
+ </span>
222
+ <span
223
+ style={{
224
+ fontSize: '13px',
225
+ color: '#888',
226
+ textAlign: 'center',
227
+ lineHeight: '1.5',
228
+ }}
229
+ >
230
+ Was installed successfully.
231
+ <br />
232
+ You can now add content to your app.
233
+ </span>
234
+ <a
235
+ href="/settings/applications#installed"
236
+ style={{
237
+ display: 'inline-flex',
238
+ alignItems: 'center',
239
+ gap: '6px',
240
+ marginTop: '16px',
241
+ fontSize: '13px',
242
+ color: '#333',
243
+ textDecoration: 'none',
244
+ padding: '8px 16px',
245
+ borderRadius: '8px',
246
+ border: '1px solid #e0e0e0',
247
+ background: '#fafafa',
248
+ transition: 'background 0.15s, border-color 0.15s',
249
+ }}
250
+ onMouseEnter={(e) => {
251
+ e.currentTarget.style.background = '#f0f0f0';
252
+ e.currentTarget.style.borderColor = '#ccc';
253
+ }}
254
+ onMouseLeave={(e) => {
255
+ e.currentTarget.style.background = '#fafafa';
256
+ e.currentTarget.style.borderColor = '#e0e0e0';
257
+ }}
258
+ >
259
+ Open app settings
260
+ <ArrowUpRight color="#333" />
261
+ </a>
262
+ <div
263
+ style={{
264
+ display: 'flex',
265
+ gap: '16px',
266
+ marginTop: '32px',
267
+ flexWrap: 'wrap',
268
+ justifyContent: 'center',
269
+ alignItems: 'flex-start',
270
+ }}
271
+ >
272
+ {CATEGORIES.map((category) => (
273
+ <CategoryCard
274
+ key={category.title}
275
+ title={category.title}
276
+ color={category.color}
277
+ items={category.items}
278
+ rotation={category.rotation}
279
+ />
280
+ ))}
281
+ </div>
282
+ </div>
283
+ );
284
+ };
285
+
286
+ export default defineFrontComponent({
287
+ universalIdentifier: MAIN_PAGE_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
288
+ name: APP_DISPLAY_NAME,
289
+ description: `${APP_DISPLAY_NAME} front component displaying the app logo and name`,
290
+ component: MainPage,
291
+ });
@@ -0,0 +1,19 @@
1
+ import {
2
+ defineNavigationMenuItem,
3
+ NavigationMenuItemType,
4
+ } from 'twenty-sdk/define';
5
+
6
+ import {
7
+ APP_DISPLAY_NAME,
8
+ MAIN_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER,
9
+ MAIN_PAGE_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER,
10
+ } from 'src/constants/universal-identifiers';
11
+
12
+ export default defineNavigationMenuItem({
13
+ universalIdentifier: MAIN_PAGE_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER,
14
+ name: APP_DISPLAY_NAME,
15
+ icon: 'IconFile',
16
+ position: -1,
17
+ type: NavigationMenuItemType.PAGE_LAYOUT,
18
+ pageLayoutUniversalIdentifier: MAIN_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER,
19
+ });
@@ -0,0 +1,37 @@
1
+ import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define';
2
+
3
+ import {
4
+ APP_DISPLAY_NAME,
5
+ MAIN_PAGE_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
6
+ MAIN_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER,
7
+ MAIN_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER,
8
+ MAIN_PAGE_WIDGET_UNIVERSAL_IDENTIFIER,
9
+ } from 'src/constants/universal-identifiers';
10
+
11
+ export default definePageLayout({
12
+ universalIdentifier: MAIN_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER,
13
+ name: APP_DISPLAY_NAME,
14
+ type: 'STANDALONE_PAGE',
15
+ tabs: [
16
+ {
17
+ universalIdentifier: MAIN_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER,
18
+ title: 'Overview',
19
+ position: 0,
20
+ icon: 'IconApps',
21
+ layoutMode: PageLayoutTabLayoutMode.CANVAS,
22
+ widgets: [
23
+ {
24
+ universalIdentifier: MAIN_PAGE_WIDGET_UNIVERSAL_IDENTIFIER,
25
+ title: ' ',
26
+ type: 'FRONT_COMPONENT',
27
+ gridPosition: { row: 0, column: 0, rowSpan: 12, columnSpan: 12 },
28
+ configuration: {
29
+ configurationType: 'FRONT_COMPONENT',
30
+ frontComponentUniversalIdentifier:
31
+ MAIN_PAGE_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
32
+ },
33
+ },
34
+ ],
35
+ },
36
+ ],
37
+ });
@@ -0,0 +1 @@
1
+ nodeLinker: node-modules
@@ -1,11 +1,10 @@
1
1
  export type AuthenticationMethod = 'oauth' | 'apiKey';
2
2
  type CreateAppOptions = {
3
3
  directory?: string;
4
- example?: string;
5
4
  name?: string;
6
5
  displayName?: string;
7
6
  description?: string;
8
- apiUrl?: string;
7
+ workspaceUrl?: string;
9
8
  authenticationMethod?: AuthenticationMethod;
10
9
  };
11
10
  export declare class CreateAppCommand {
@@ -15,12 +14,19 @@ export declare class CreateAppCommand {
15
14
  private computeTotalSteps;
16
15
  private getAppInfos;
17
16
  private validateDirectory;
18
- private tryDownloadExample;
19
17
  private logPlan;
20
18
  private logNextStep;
21
19
  private logDetail;
22
20
  private pullImageInBackground;
23
21
  private ensureDockerServer;
22
+ private openMainPage;
23
+ private resolveWorkspaceFrontUrl;
24
+ private readMainPageLayoutUniversalIdentifier;
25
+ private resolvePageLayoutId;
26
+ private sanitizeBrowserUrl;
27
+ private openInBrowser;
28
+ private syncApplication;
29
+ private tryExistingAuth;
24
30
  private authenticateWithDevKey;
25
31
  private deriveRemoteName;
26
32
  private authenticateWithOAuth;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-twenty-app",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "description": "Command-line interface to create Twenty application",
5
5
  "main": "dist/cli.cjs",
6
6
  "bin": "dist/cli.cjs",
@@ -35,7 +35,7 @@
35
35
  "lodash.camelcase": "^4.3.0",
36
36
  "lodash.kebabcase": "^4.1.1",
37
37
  "lodash.startcase": "^4.4.0",
38
- "twenty-sdk": "2.5.0",
38
+ "twenty-sdk": "2.6.0",
39
39
  "uuid": "^13.0.0"
40
40
  },
41
41
  "devDependencies": {
@@ -1 +0,0 @@
1
- export declare const downloadExample: (exampleName: string, targetDirectory: string) => Promise<void>;