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:
+4
@@ -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;
|
||||
}
|
||||
|
||||
+4
@@ -18,6 +18,10 @@ export class UpdateApplicationRegistrationVariablePayload {
|
||||
@IsOptional()
|
||||
value?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@IsOptional()
|
||||
resetValue?: boolean;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@IsString()
|
||||
@MaxLength(2000)
|
||||
|
||||
-1
@@ -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)
|
||||
|
||||
+36
-16
@@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+11
-4
@@ -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})`,
|
||||
);
|
||||
|
||||
-17
@@ -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({
|
||||
|
||||
+12
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user