Fix app design 1/2 (#19735)

comply with
https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?node-id=96977-349627&m=dev

## After
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 40
37"
src="https://github.com/user-attachments/assets/6d80191a-79a9-4f0f-aa4f-0e447fff4f6d"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 40
22"
src="https://github.com/user-attachments/assets/4f763272-027e-4246-b455-7d46babf7d8c"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 39
11"
src="https://github.com/user-attachments/assets/b9b35e18-8068-447e-821d-5ec28bb5bd16"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 39
05"
src="https://github.com/user-attachments/assets/57d9318a-902f-4fd7-a2a3-5795ebe0b9dc"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 39
02"
src="https://github.com/user-attachments/assets/78a33fa8-6bdd-484e-a82d-bd0f7592a623"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 38
58"
src="https://github.com/user-attachments/assets/f7987aed-c6e1-4032-a611-86817655137d"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 38
55"
src="https://github.com/user-attachments/assets/d1c451ab-1d2d-41e4-a059-cf4303ecabe7"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 38
48"
src="https://github.com/user-attachments/assets/593cae36-2320-443f-a955-93b211a6ee3f"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 37
40"
src="https://github.com/user-attachments/assets/c9f602b1-8de3-4e82-a3a6-344594a0c153"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 37
34"
src="https://github.com/user-attachments/assets/b54ddddf-5dda-46c8-ace3-cffe6015825a"
/>

## before

<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 42
18"
src="https://github.com/user-attachments/assets/c0976a0a-0124-48ec-8e7c-78627cea7063"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 42
16"
src="https://github.com/user-attachments/assets/d2db926c-4040-411d-9091-8b60e7c519e6"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 42
13"
src="https://github.com/user-attachments/assets/2d69f2ff-f26e-4249-91a3-2cf3d261e840"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 42
07"
src="https://github.com/user-attachments/assets/1028aabc-77ac-4c51-a8c3-9a194faba87f"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 42
01"
src="https://github.com/user-attachments/assets/1caa9f5e-3eaa-433c-9d3b-e0f094f16e8e"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 41
56"
src="https://github.com/user-attachments/assets/f42b6976-3a8f-4591-9283-bda79bdb424b"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 41
53"
src="https://github.com/user-attachments/assets/93d00df8-0091-4dfa-9ac0-f6f376be5962"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 41
43"
src="https://github.com/user-attachments/assets/9deae7e5-39c1-4518-a463-6d79bc5bf132"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 41
37"
src="https://github.com/user-attachments/assets/3e21b521-c47d-482c-ad41-66abfe973772"
/>
This commit is contained in:
martmull
2026-04-16 11:47:44 +02:00
committed by GitHub
parent 64470baa1e
commit 381f3ba7d9
66 changed files with 2058 additions and 1537 deletions
@@ -90,6 +90,10 @@ export class ApplicationRegistrationVariableService {
updateData.encryptedValue = this.encryptionService.encrypt(update.value);
}
if (isDefined(update.resetValue) && update.resetValue) {
updateData.encryptedValue = '';
}
if (isDefined(update.description)) {
updateData.description = update.description;
}
@@ -18,6 +18,10 @@ export class UpdateApplicationRegistrationVariablePayload {
@IsOptional()
value?: string;
@Field({ nullable: true })
@IsOptional()
resetValue?: boolean;
@Field({ nullable: true })
@IsString()
@MaxLength(2000)
@@ -60,7 +60,6 @@ export class ApplicationRegistrationResolver {
private readonly applicationRegistrationVariableService: ApplicationRegistrationVariableService,
private readonly applicationTarballService: ApplicationTarballService,
private readonly fileUrlService: FileUrlService,
private readonly domainServerConfigService: DomainServerConfigService,
) {}
@UseGuards(PublicEndpointGuard, NoPermissionGuard)
@@ -24,6 +24,7 @@ import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/appli
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { validateRedirectUri } from 'src/engine/core-modules/auth/utils/validate-redirect-uri.util';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { ApplicationRegistrationVariableService } from 'src/engine/core-modules/application/application-registration-variable/application-registration-variable.service';
const BCRYPT_SALT_ROUNDS = 10;
@Injectable()
@@ -35,6 +36,7 @@ export class ApplicationRegistrationService {
private readonly applicationRepository: Repository<ApplicationEntity>,
@InjectRepository(WorkspaceEntity)
private readonly workspaceRepository: Repository<WorkspaceEntity>,
private readonly applicationRegistrationVariableService: ApplicationRegistrationVariableService,
) {}
async findMany(
@@ -288,26 +290,41 @@ export class ApplicationRegistrationService {
isListed: params.isListed,
isFeatured: params.isFeatured,
});
} else {
const registration = this.applicationRegistrationRepository.create({
universalIdentifier: params.universalIdentifier,
name: params.name,
sourceType: params.sourceType,
sourcePackage: params.sourcePackage,
latestAvailableVersion: params.latestAvailableVersion,
isListed: params.isListed,
isFeatured: params.isFeatured,
manifest: params.manifest,
oAuthClientId: v4(),
oAuthRedirectUris: [],
oAuthScopes: [],
ownerWorkspaceId: params.ownerWorkspaceId,
});
await this.applicationRegistrationRepository.save(registration);
}
if (!isDefined(params.manifest?.application?.serverVariables)) {
return;
}
const registration = this.applicationRegistrationRepository.create({
universalIdentifier: params.universalIdentifier,
name: params.name,
sourceType: params.sourceType,
sourcePackage: params.sourcePackage,
latestAvailableVersion: params.latestAvailableVersion,
isListed: params.isListed,
isFeatured: params.isFeatured,
manifest: params.manifest,
oAuthClientId: v4(),
oAuthRedirectUris: [],
oAuthScopes: [],
ownerWorkspaceId: params.ownerWorkspaceId,
});
const registration = await this.findOneByUniversalIdentifier(
params.universalIdentifier,
);
await this.applicationRegistrationRepository.save(registration);
if (!isDefined(registration)) {
return;
}
await this.applicationRegistrationVariableService.syncVariableSchemas(
registration.id,
params.manifest.application.serverVariables,
);
}
async createCliRegistrationIfNotExists(): Promise<ApplicationRegistrationEntity | null> {
@@ -337,7 +354,10 @@ export class ApplicationRegistrationService {
async findManyListed(): Promise<ApplicationRegistrationEntity[]> {
return this.applicationRegistrationRepository.find({
where: { isListed: true },
where: {
isListed: true,
sourceType: ApplicationRegistrationSourceType.NPM,
},
});
}
@@ -22,6 +22,8 @@ import { readJsonFile } from 'src/engine/core-modules/application/application-pa
import { resolvePackageContentDir } from 'src/engine/core-modules/application/application-package/utils/tarball-utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import type { ApplicationManifest } from 'twenty-shared/application';
import { ApplicationRegistrationVariableService } from 'src/engine/core-modules/application/application-registration-variable/application-registration-variable.service';
export const MAX_TARBALL_UPLOAD_SIZE_BYTES = 50 * 1024 * 1024;
@@ -34,6 +36,7 @@ export class ApplicationTarballService {
private readonly appRegistrationRepository: Repository<ApplicationRegistrationEntity>,
private readonly fileStorageService: FileStorageService,
private readonly applicationService: ApplicationService,
private readonly applicationRegistrationVariableService: ApplicationRegistrationVariableService,
) {}
async uploadTarball(params: {
@@ -58,10 +61,7 @@ export class ApplicationTarballService {
const contentDir = await resolvePackageContentDir(extractDir);
const manifest = await readJsonFile<{
application?: {
universalIdentifier?: string;
displayName?: string;
};
application?: ApplicationManifest;
}>(contentDir, 'manifest.json');
const packageJson = await readJsonFile<{
@@ -181,6 +181,13 @@ export class ApplicationTarballService {
ownerWorkspaceId: params.ownerWorkspaceId,
});
if (manifest.application?.serverVariables) {
await this.applicationRegistrationVariableService.syncVariableSchemas(
appRegistration.id,
manifest.application.serverVariables,
);
}
this.logger.log(
`Tarball uploaded for app ${universalIdentifier} (registration ${appRegistration.id})`,
);
@@ -168,23 +168,6 @@ export class LocalDriver implements LogicFunctionDriver {
async delete() {}
async build({
flatApplication,
applicationUniversalIdentifier,
}: {
flatApplication: FlatApplication;
applicationUniversalIdentifier: string;
}) {
await this.createLayerIfNotExist({
flatApplication,
applicationUniversalIdentifier,
});
await this.ensureSdkLayer({
flatApplication,
applicationUniversalIdentifier,
});
}
// Symlinks everything from the deps layer except twenty-client-sdk,
// which comes from the SDK layer (workspace-specific generated client).
private async assembleNodeModules({
@@ -76,3 +76,15 @@ export class PublicWorkspaceDataDTO {
@Field(() => WorkspaceUrlsDTO)
workspaceUrls: WorkspaceUrlsDTO;
}
@ObjectType('PublicWorkspaceDataSummary')
export class PublicWorkspaceDataSummaryDTO {
@Field(() => UUIDScalarType)
id: string;
@Field(() => String, { nullable: true })
logo: WorkspaceEntity['logo'];
@Field(() => String, { nullable: true })
displayName: WorkspaceEntity['displayName'];
}
@@ -820,4 +820,10 @@ export class WorkspaceService extends TypeOrmQueryService<WorkspaceEntity> {
this.exceptionHandlerService.captureExceptions([error as Error]);
}
}
async findOneWorkspaceById(id: string) {
return await this.workspaceRepository.findOneBy({
id,
});
}
}
@@ -39,6 +39,7 @@ import { ActivateWorkspaceInput } from 'src/engine/core-modules/workspace/dtos/a
import {
type AuthProvidersDTO,
PublicWorkspaceDataDTO,
PublicWorkspaceDataSummaryDTO,
} from 'src/engine/core-modules/workspace/dtos/public-workspace-data.dto';
import { UpdateWorkspaceInput } from 'src/engine/core-modules/workspace/dtos/update-workspace-input';
import { WorkspaceUrlsDTO } from 'src/engine/core-modules/workspace/dtos/workspace-urls.dto';
@@ -69,6 +70,7 @@ import { fromRoleEntityToRoleDto } from 'src/engine/metadata-modules/role/utils/
import { ViewDTO } from 'src/engine/metadata-modules/view/dtos/view.dto';
import { ViewService } from 'src/engine/metadata-modules/view/services/view.service';
import { getRequest } from 'src/utils/extract-request';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
const OriginHeader = createParamDecorator(
(_: unknown, ctx: ExecutionContext) => {
const request = getRequest(ctx);
@@ -418,6 +420,39 @@ export class WorkspaceResolver {
}
}
@Query(() => PublicWorkspaceDataSummaryDTO)
@UseGuards(PublicEndpointGuard, NoPermissionGuard)
async getPublicWorkspaceDataById(
@Args({
name: 'id',
type: () => UUIDScalarType,
nullable: false,
})
id: string,
): Promise<PublicWorkspaceDataSummaryDTO | undefined> {
try {
const workspace = await this.workspaceService.findOneWorkspaceById(id);
assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError);
const logo = isDefined(workspace.logoFileId)
? this.fileUrlService.signFileByIdUrl({
fileId: workspace.logoFileId,
workspaceId: workspace.id,
fileFolder: FileFolder.CorePicture,
})
: (workspace.logo ?? '');
return {
id: workspace.id,
logo,
displayName: workspace.displayName,
};
} catch (err) {
workspaceGraphqlApiExceptionHandler(err);
}
}
@Mutation(() => DomainValidRecords, { nullable: true })
@UseGuards(
WorkspaceAuthGuard,