File diff suppressed because one or more lines are too long
@@ -0,0 +1,7 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const INSTALL_APPLICATION = gql`
|
||||
mutation InstallApplication($appRegistrationId: String!, $version: String) {
|
||||
installApplication(appRegistrationId: $appRegistrationId, version: $version)
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const UPLOAD_APP_TARBALL = gql`
|
||||
mutation UploadAppTarball($file: Upload!, $universalIdentifier: String) {
|
||||
uploadAppTarball(file: $file, universalIdentifier: $universalIdentifier) {
|
||||
id
|
||||
universalIdentifier
|
||||
name
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,61 @@
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useMutation } from '@apollo/client';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { UPLOAD_APP_TARBALL } from '~/modules/marketplace/graphql/mutations/uploadAppTarball';
|
||||
|
||||
type UploadResult =
|
||||
| {
|
||||
success: true;
|
||||
registrationId: string;
|
||||
universalIdentifier: string;
|
||||
}
|
||||
| {
|
||||
success: false;
|
||||
};
|
||||
|
||||
export const useUploadAppTarball = () => {
|
||||
const [uploadAppTarball] = useMutation(UPLOAD_APP_TARBALL);
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
|
||||
const upload = async (file: File): Promise<UploadResult> => {
|
||||
setIsUploading(true);
|
||||
|
||||
try {
|
||||
const result = await uploadAppTarball({
|
||||
variables: { file },
|
||||
});
|
||||
|
||||
const registration = result.data?.uploadAppTarball;
|
||||
|
||||
if (
|
||||
!isDefined(registration?.id) ||
|
||||
!isDefined(registration?.universalIdentifier)
|
||||
) {
|
||||
enqueueErrorSnackBar({ message: t`Upload failed.` });
|
||||
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
registrationId: registration.id,
|
||||
universalIdentifier: registration.universalIdentifier,
|
||||
};
|
||||
} catch (error) {
|
||||
const graphqlMessage = error instanceof Error ? error.message : undefined;
|
||||
|
||||
enqueueErrorSnackBar({
|
||||
message: graphqlMessage ?? t`Failed to upload tarball.`,
|
||||
});
|
||||
|
||||
return { success: false };
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return { upload, isUploading };
|
||||
};
|
||||
+4
-15
@@ -15,10 +15,7 @@ import { SearchInput } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { UndecoratedLink } from 'twenty-ui/navigation';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import {
|
||||
ApplicationRegistrationSourceType,
|
||||
type ApplicationRegistrationFragmentFragment,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { type ApplicationRegistrationFragmentFragment } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledTableContainer = styled.div`
|
||||
margin-top: ${themeCssVariables.spacing[3]};
|
||||
@@ -68,7 +65,7 @@ export const SettingsAdminApps = () => {
|
||||
<TableRow gridTemplateColumns={TABLE_GRID}>
|
||||
<TableHeader>{t`Name`}</TableHeader>
|
||||
<TableHeader>{t`Source`}</TableHeader>
|
||||
<TableHeader>{t`Type`}</TableHeader>
|
||||
<TableHeader>{t`Listed`}</TableHeader>
|
||||
<TableHeader>{t`Featured`}</TableHeader>
|
||||
</TableRow>
|
||||
</StyledTableHeaderRowContainer>
|
||||
@@ -89,16 +86,8 @@ export const SettingsAdminApps = () => {
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Status
|
||||
color={
|
||||
registration.sourceType ===
|
||||
ApplicationRegistrationSourceType.NPM
|
||||
? 'green'
|
||||
: registration.sourceType ===
|
||||
ApplicationRegistrationSourceType.TARBALL
|
||||
? 'blue'
|
||||
: 'gray'
|
||||
}
|
||||
text={registration.sourceType}
|
||||
color={registration.isListed ? 'green' : 'gray'}
|
||||
text={registration.isListed ? t`Yes` : t`No`}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
|
||||
+1
-2
@@ -16,9 +16,8 @@ export const APPLICATION_REGISTRATION_FRAGMENT = gql`
|
||||
latestAvailableVersion
|
||||
websiteUrl
|
||||
termsUrl
|
||||
isListed
|
||||
isFeatured
|
||||
provenanceRepositoryUrl
|
||||
isProvenanceVerified
|
||||
ownerWorkspaceId
|
||||
createdAt
|
||||
updatedAt
|
||||
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { useRef } from 'react';
|
||||
|
||||
import { FIND_MANY_APPLICATION_REGISTRATIONS } from '@/settings/application-registrations/graphql/queries/findManyApplicationRegistrations';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||
import { useApolloClient, useMutation } from '@apollo/client';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { H1Title, H1TitleFontColor } from 'twenty-ui/display';
|
||||
import { SectionAlignment, SectionFontColor } from 'twenty-ui/layout';
|
||||
import { INSTALL_APPLICATION } from '~/modules/marketplace/graphql/mutations/installApplication';
|
||||
import { useUploadAppTarball } from '~/modules/marketplace/hooks/useUploadAppTarball';
|
||||
import {
|
||||
StyledAppModal,
|
||||
StyledAppModalButton,
|
||||
StyledAppModalSection,
|
||||
StyledAppModalTitle,
|
||||
} from '~/pages/settings/applications/components/SettingsAppModalLayout';
|
||||
|
||||
export const UPLOAD_TARBALL_MODAL_ID = 'upload-tarball-modal';
|
||||
|
||||
const StyledFileInput = styled.input`
|
||||
display: none;
|
||||
`;
|
||||
|
||||
export const SettingsUploadTarballModal = () => {
|
||||
const { t: tFn } = useLingui();
|
||||
const { closeModal } = useModal();
|
||||
const { upload, isUploading } = useUploadAppTarball();
|
||||
const [installApplication] = useMutation(INSTALL_APPLICATION);
|
||||
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||
const apolloClient = useApolloClient();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleSelectFile = () => {
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
const handleFileChange = async (
|
||||
event: React.ChangeEvent<HTMLInputElement>,
|
||||
) => {
|
||||
const file = event.target.files?.[0];
|
||||
|
||||
if (!isDefined(file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const uploadResult = await upload(file);
|
||||
|
||||
if (!uploadResult.success) {
|
||||
return;
|
||||
}
|
||||
|
||||
const registrationId = uploadResult.registrationId;
|
||||
|
||||
if (isDefined(registrationId)) {
|
||||
try {
|
||||
await installApplication({
|
||||
variables: { appRegistrationId: registrationId },
|
||||
});
|
||||
enqueueSuccessSnackBar({
|
||||
message: t`Application installed successfully.`,
|
||||
});
|
||||
} catch {
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Tarball uploaded but installation failed.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await apolloClient.refetchQueries({
|
||||
include: [FIND_MANY_APPLICATION_REGISTRATIONS],
|
||||
});
|
||||
closeModal(UPLOAD_TARBALL_MODAL_ID);
|
||||
} finally {
|
||||
if (isDefined(fileInputRef.current)) {
|
||||
fileInputRef.current.value = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
closeModal(UPLOAD_TARBALL_MODAL_ID);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledAppModal
|
||||
modalId={UPLOAD_TARBALL_MODAL_ID}
|
||||
isClosable={true}
|
||||
padding="large"
|
||||
dataGloballyPreventClickOutside
|
||||
>
|
||||
<StyledAppModalTitle>
|
||||
<H1Title
|
||||
title={tFn`Upload tarball`}
|
||||
fontColor={H1TitleFontColor.Primary}
|
||||
/>
|
||||
</StyledAppModalTitle>
|
||||
<StyledAppModalSection
|
||||
alignment={SectionAlignment.Center}
|
||||
fontColor={SectionFontColor.Primary}
|
||||
>
|
||||
{tFn`Select a .tar.gz application package to upload and install.`}
|
||||
</StyledAppModalSection>
|
||||
|
||||
<StyledFileInput
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept=".tar.gz,.tgz"
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
|
||||
<StyledAppModalButton
|
||||
onClick={handleCancel}
|
||||
variant="secondary"
|
||||
title={tFn`Cancel`}
|
||||
fullWidth
|
||||
/>
|
||||
<StyledAppModalButton
|
||||
onClick={handleSelectFile}
|
||||
variant="secondary"
|
||||
accent="blue"
|
||||
title={tFn`Choose file`}
|
||||
disabled={isUploading}
|
||||
fullWidth
|
||||
/>
|
||||
</StyledAppModal>
|
||||
);
|
||||
};
|
||||
+66
-25
@@ -1,7 +1,11 @@
|
||||
import { SettingsAdminTableCard } from '@/settings/admin-panel/components/SettingsAdminTableCard';
|
||||
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
|
||||
import { UPDATE_APPLICATION_REGISTRATION } from '@/settings/application-registrations/graphql/mutations/updateApplicationRegistration';
|
||||
import { FIND_APPLICATION_REGISTRATION_STATS } from '@/settings/application-registrations/graphql/queries/findApplicationRegistrationStats';
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { FIND_MANY_APPLICATION_REGISTRATIONS } from '@/settings/application-registrations/graphql/queries/findManyApplicationRegistrations';
|
||||
import { FIND_ONE_APPLICATION_REGISTRATION } from '@/settings/application-registrations/graphql/queries/findOneApplicationRegistration';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useMutation, useQuery } from '@apollo/client';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import {
|
||||
@@ -31,6 +35,7 @@ export const SettingsApplicationRegistrationDistributionTab = ({
|
||||
registration: ApplicationRegistrationData;
|
||||
}) => {
|
||||
const { t } = useLingui();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const applicationRegistrationId = registration.id;
|
||||
|
||||
@@ -42,6 +47,32 @@ export const SettingsApplicationRegistrationDistributionTab = ({
|
||||
skip: !applicationRegistrationId,
|
||||
});
|
||||
|
||||
const [updateRegistration] = useMutation(UPDATE_APPLICATION_REGISTRATION, {
|
||||
refetchQueries: [
|
||||
FIND_ONE_APPLICATION_REGISTRATION,
|
||||
FIND_MANY_APPLICATION_REGISTRATIONS,
|
||||
],
|
||||
});
|
||||
|
||||
const handleToggleListed = async () => {
|
||||
try {
|
||||
await updateRegistration({
|
||||
variables: {
|
||||
input: {
|
||||
id: applicationRegistrationId,
|
||||
update: {
|
||||
isListed: !registration.isListed,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Error updating marketplace listing`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const marketplacePageUrl = getSettingsPath(
|
||||
SettingsPath.AvailableApplicationDetail,
|
||||
{
|
||||
@@ -80,31 +111,41 @@ export const SettingsApplicationRegistrationDistributionTab = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
{isNpmSource && (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Marketplace Listing`}
|
||||
description={t`This app is listed on the marketplace because it is published to npm.`}
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Marketplace Listing`}
|
||||
description={t`Control visibility on the marketplace. Unlisted apps are still accessible via direct link.`}
|
||||
/>
|
||||
<Card rounded>
|
||||
<SettingsOptionCardContentToggle
|
||||
title={t`Listed on marketplace`}
|
||||
description={
|
||||
isNpmSource
|
||||
? t`Managed by the marketplace catalog sync for npm packages`
|
||||
: t`When enabled, this app appears in the marketplace browse page`
|
||||
}
|
||||
checked={registration.isListed}
|
||||
onChange={handleToggleListed}
|
||||
disabled={isNpmSource}
|
||||
divider
|
||||
/>
|
||||
<Card rounded>
|
||||
<SettingsOptionCardContentToggle
|
||||
title={t`Featured`}
|
||||
description={t`Featured apps are curated. Open a PR to request featured status.`}
|
||||
checked={registration.isFeatured}
|
||||
onChange={() => {}}
|
||||
disabled
|
||||
/>
|
||||
</Card>
|
||||
<StyledButtonGroup>
|
||||
<Button
|
||||
Icon={IconExternalLink}
|
||||
title={t`View marketplace page`}
|
||||
variant="secondary"
|
||||
to={marketplacePageUrl}
|
||||
/>
|
||||
</StyledButtonGroup>
|
||||
</Section>
|
||||
)}
|
||||
<SettingsOptionCardContentToggle
|
||||
title={t`Featured`}
|
||||
description={t`Featured apps are curated. Open a PR to request featured status.`}
|
||||
checked={registration.isFeatured}
|
||||
onChange={() => {}}
|
||||
disabled
|
||||
/>
|
||||
</Card>
|
||||
<StyledButtonGroup>
|
||||
<Button
|
||||
Icon={IconExternalLink}
|
||||
title={t`View marketplace page`}
|
||||
variant="secondary"
|
||||
to={marketplacePageUrl}
|
||||
/>
|
||||
</StyledButtonGroup>
|
||||
</Section>
|
||||
|
||||
{hasStats && (
|
||||
<Section>
|
||||
|
||||
+53
-90
@@ -2,11 +2,12 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe
|
||||
import { FIND_MANY_APPLICATION_REGISTRATIONS } from '@/settings/application-registrations/graphql/queries/findManyApplicationRegistrations';
|
||||
import { SettingsListCard } from '@/settings/components/SettingsListCard';
|
||||
import { getDocumentationUrl } from '@/support/utils/getDocumentationUrl';
|
||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useContext } from 'react';
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import {
|
||||
@@ -16,128 +17,73 @@ import {
|
||||
IconChevronRight,
|
||||
IconCopy,
|
||||
IconFileInfo,
|
||||
IconUpload,
|
||||
} from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { useContext } from 'react';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { Tag } from 'twenty-ui/components';
|
||||
import {
|
||||
type ApplicationRegistrationFragmentFragment,
|
||||
ApplicationRegistrationSourceType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
|
||||
import {
|
||||
SettingsUploadTarballModal,
|
||||
UPLOAD_TARBALL_MODAL_ID,
|
||||
} from '~/pages/settings/applications/components/SettingsUploadTarballModal';
|
||||
|
||||
const StyledButtonContainer = styled.div`
|
||||
margin: ${themeCssVariables.spacing[2]} 0;
|
||||
`;
|
||||
|
||||
const StyledRowRightContainer = styled.div`
|
||||
align-items: center;
|
||||
const StyledButtonGroupContainer = styled.div`
|
||||
display: flex;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
justify-content: flex-end;
|
||||
margin-bottom: ${themeCssVariables.spacing[4]};
|
||||
`;
|
||||
|
||||
const SOURCE_TYPE_BADGE_CONFIG: Record<
|
||||
ApplicationRegistrationSourceType,
|
||||
{ label: string; color: 'gray' | 'blue' | 'green' }
|
||||
> = {
|
||||
[ApplicationRegistrationSourceType.LOCAL]: {
|
||||
label: 'Dev',
|
||||
color: 'gray',
|
||||
},
|
||||
[ApplicationRegistrationSourceType.NPM]: {
|
||||
label: 'Npm',
|
||||
color: 'blue',
|
||||
},
|
||||
[ApplicationRegistrationSourceType.TARBALL]: {
|
||||
label: 'Internal',
|
||||
color: 'green',
|
||||
},
|
||||
type ApplicationRegistration = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
};
|
||||
|
||||
export const SettingsApplicationsDeveloperTab = () => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const { t } = useLingui();
|
||||
const navigate = useNavigate();
|
||||
const currentWorkspaceMember = useAtomStateValue(currentWorkspaceMemberState);
|
||||
const { openModal } = useModal();
|
||||
|
||||
const { copyToClipboard } = useCopyToClipboard();
|
||||
|
||||
const { data, loading } = useQuery(FIND_MANY_APPLICATION_REGISTRATIONS);
|
||||
|
||||
const registrations: ApplicationRegistrationFragmentFragment[] =
|
||||
const registrations: ApplicationRegistration[] =
|
||||
data?.findManyApplicationRegistrations ?? [];
|
||||
|
||||
const createCommands = [
|
||||
const commands = [
|
||||
// oxlint-disable-next-line lingui/no-unlocalized-strings
|
||||
'npx create-twenty-app@latest my-twenty-app',
|
||||
// oxlint-disable-next-line lingui/no-unlocalized-strings
|
||||
'cd my-twenty-app',
|
||||
];
|
||||
|
||||
const createCopyButton = (
|
||||
const copyButton = (
|
||||
<Button
|
||||
onClick={() => {
|
||||
copyToClipboard(
|
||||
createCommands.join('\n'),
|
||||
t`Commands copied to clipboard`,
|
||||
);
|
||||
copyToClipboard(commands.join('\n'), t`Commands copied to clipboard`);
|
||||
}}
|
||||
ariaLabel={t`Copy commands`}
|
||||
Icon={IconCopy}
|
||||
/>
|
||||
);
|
||||
|
||||
const getRegistrationLink = (
|
||||
registration: ApplicationRegistrationFragmentFragment,
|
||||
) =>
|
||||
getSettingsPath(SettingsPath.ApplicationRegistrationDetail, {
|
||||
applicationRegistrationId: registration.id,
|
||||
});
|
||||
|
||||
const syncCommands = [
|
||||
// oxlint-disable-next-line lingui/no-unlocalized-strings
|
||||
'yarn twenty app:dev',
|
||||
];
|
||||
|
||||
const syncCopyButton = (
|
||||
<Button
|
||||
onClick={() => {
|
||||
copyToClipboard(
|
||||
syncCommands.join('\n'),
|
||||
t`Command copied to clipboard`,
|
||||
);
|
||||
}}
|
||||
ariaLabel={t`Copy command`}
|
||||
Icon={IconCopy}
|
||||
/>
|
||||
);
|
||||
|
||||
const RowRightWithBadge = ({
|
||||
item,
|
||||
}: {
|
||||
item: ApplicationRegistrationFragmentFragment;
|
||||
}) => {
|
||||
const badgeConfig = SOURCE_TYPE_BADGE_CONFIG[item.sourceType];
|
||||
|
||||
return (
|
||||
<StyledRowRightContainer>
|
||||
<Tag text={badgeConfig.label} color={badgeConfig.color} preventShrink />
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
</StyledRowRightContainer>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Create & Develop`}
|
||||
description={t`Scaffold a new app, then use the CLI to develop, publish, and distribute`}
|
||||
title={t`Create an application`}
|
||||
description={t`You can either create a private app or share it to others`}
|
||||
/>
|
||||
<CommandBlock commands={createCommands} button={createCopyButton} />
|
||||
<CommandBlock commands={commands} button={copyButton} />
|
||||
<StyledButtonContainer>
|
||||
<Button
|
||||
Icon={IconFileInfo}
|
||||
@@ -146,7 +92,7 @@ export const SettingsApplicationsDeveloperTab = () => {
|
||||
window.open(
|
||||
getDocumentationUrl({
|
||||
locale: currentWorkspaceMember?.locale,
|
||||
path: '/developers/extend/apps/getting-started',
|
||||
path: '/developers/extend/capabilities/apps',
|
||||
}),
|
||||
'_blank',
|
||||
)
|
||||
@@ -154,27 +100,44 @@ export const SettingsApplicationsDeveloperTab = () => {
|
||||
/>
|
||||
</StyledButtonContainer>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Your Apps`}
|
||||
description={t`All applications registered on this workspace`}
|
||||
title={t`My Apps`}
|
||||
description={t`Apps you've created, registered, or published`}
|
||||
/>
|
||||
{registrations.length > 0 ? (
|
||||
{registrations.length > 0 && (
|
||||
<SettingsListCard
|
||||
items={registrations}
|
||||
getItemLabel={(registration) => registration.name}
|
||||
isLoading={loading}
|
||||
RowIcon={IconApps}
|
||||
to={getRegistrationLink}
|
||||
RowRightComponent={RowRightWithBadge}
|
||||
onRowClick={(registration) => {
|
||||
navigate(
|
||||
getSettingsPath(SettingsPath.ApplicationRegistrationDetail, {
|
||||
applicationRegistrationId: registration.id,
|
||||
}),
|
||||
);
|
||||
}}
|
||||
RowRightComponent={() => (
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
!loading && (
|
||||
<CommandBlock commands={syncCommands} button={syncCopyButton} />
|
||||
)
|
||||
)}
|
||||
</Section>
|
||||
<StyledButtonGroupContainer>
|
||||
<Button
|
||||
Icon={IconUpload}
|
||||
title={t`Upload tarball`}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
onClick={() => openModal(UPLOAD_TARBALL_MODAL_ID)}
|
||||
/>
|
||||
</StyledButtonGroupContainer>
|
||||
|
||||
<SettingsUploadTarballModal />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+1
@@ -7,6 +7,7 @@ export type ApplicationRegistrationData = {
|
||||
universalIdentifier: string;
|
||||
sourceType: ApplicationRegistrationSourceType;
|
||||
sourcePackage?: string | null;
|
||||
isListed: boolean;
|
||||
isFeatured: boolean;
|
||||
oAuthClientId: string;
|
||||
oAuthScopes?: string[] | null;
|
||||
|
||||
Reference in New Issue
Block a user