mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-22 03:33:06 +08:00
feat(core): support install license for self hosted client (#12287)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added the ability to upload and replace license files for self-hosted team workspaces via a new modal dialog. - Introduced clearer UI flows for activating, deactivating, and managing team licenses, including one-time purchase licenses. - Provided direct links and guidance for requesting licenses and managing payments. - **Enhancements** - Improved license management interface with updated button labels and descriptions. - Added new styles for better layout and clarity in license dialogs. - Updated internationalization with new and revised texts for license operations. - **Bug Fixes** - Prevented duplicate opening of license or plan dialogs when already open. - **Chores** - Updated support and pricing links for clarity and accuracy. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -36,9 +36,9 @@ const links = [
|
|||||||
label: 'Self-host Document',
|
label: 'Self-host Document',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: 'https://affine.pro/pricing',
|
href: 'https://affine.pro/pricing/?type=selfhost#table',
|
||||||
icon: <UploadCloudIcon size={20} />,
|
icon: <UploadCloudIcon size={20} />,
|
||||||
label: 'Upgrade to Pro',
|
label: 'Upgrade to Team',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
+51
@@ -8,6 +8,7 @@ import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hoo
|
|||||||
import { useMutation } from '@affine/core/components/hooks/use-mutation';
|
import { useMutation } from '@affine/core/components/hooks/use-mutation';
|
||||||
import {
|
import {
|
||||||
AuthService,
|
AuthService,
|
||||||
|
SelfhostLicenseService,
|
||||||
WorkspaceSubscriptionService,
|
WorkspaceSubscriptionService,
|
||||||
} from '@affine/core/modules/cloud';
|
} from '@affine/core/modules/cloud';
|
||||||
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
||||||
@@ -18,14 +19,17 @@ import {
|
|||||||
createSelfhostCustomerPortalMutation,
|
createSelfhostCustomerPortalMutation,
|
||||||
SubscriptionPlan,
|
SubscriptionPlan,
|
||||||
SubscriptionRecurring,
|
SubscriptionRecurring,
|
||||||
|
SubscriptionVariant,
|
||||||
} from '@affine/graphql';
|
} from '@affine/graphql';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { FrameworkScope, useLiveData, useService } from '@toeverything/infra';
|
import { FrameworkScope, useLiveData, useService } from '@toeverything/infra';
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { EnableCloudPanel } from '../preference/enable-cloud';
|
import { EnableCloudPanel } from '../preference/enable-cloud';
|
||||||
import { SelfHostTeamCard } from './self-host-team-card';
|
import { SelfHostTeamCard } from './self-host-team-card';
|
||||||
import { SelfHostTeamPlan } from './self-host-team-plan';
|
import { SelfHostTeamPlan } from './self-host-team-plan';
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
|
import { UploadLicenseModal } from './upload-license-modal';
|
||||||
|
|
||||||
export const WorkspaceSettingLicense = ({
|
export const WorkspaceSettingLicense = ({
|
||||||
onCloseSetting,
|
onCloseSetting,
|
||||||
@@ -52,6 +56,7 @@ export const WorkspaceSettingLicense = ({
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<SelfHostTeamCard />
|
<SelfHostTeamCard />
|
||||||
|
<ReplaceLicenseModal />
|
||||||
<TypeFormLink />
|
<TypeFormLink />
|
||||||
<PaymentMethodUpdater />
|
<PaymentMethodUpdater />
|
||||||
</>
|
</>
|
||||||
@@ -60,6 +65,52 @@ export const WorkspaceSettingLicense = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ReplaceLicenseModal = () => {
|
||||||
|
const t = useI18n();
|
||||||
|
const selfhostLicenseService = useService(SelfhostLicenseService);
|
||||||
|
const license = useLiveData(selfhostLicenseService.license$);
|
||||||
|
const isOneTimePurchase = license?.variant === SubscriptionVariant.Onetime;
|
||||||
|
const permission = useService(WorkspacePermissionService).permission;
|
||||||
|
const isTeam = useLiveData(permission.isTeam$);
|
||||||
|
const [openUploadModal, setOpenUploadModal] = useState(false);
|
||||||
|
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
setOpenUploadModal(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
selfhostLicenseService.revalidate();
|
||||||
|
}, [selfhostLicenseService]);
|
||||||
|
|
||||||
|
if (!isTeam || !isOneTimePurchase) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SettingRow
|
||||||
|
className={styles.paymentMethod}
|
||||||
|
name={t[
|
||||||
|
'com.affine.settings.workspace.license.self-host-team.replace-license.title'
|
||||||
|
]()}
|
||||||
|
desc={t[
|
||||||
|
'com.affine.settings.workspace.license.self-host-team.replace-license.description'
|
||||||
|
]()}
|
||||||
|
>
|
||||||
|
<Button onClick={handleClick}>
|
||||||
|
{t[
|
||||||
|
'com.affine.settings.workspace.license.self-host-team.replace-license.upload'
|
||||||
|
]()}
|
||||||
|
</Button>
|
||||||
|
</SettingRow>
|
||||||
|
<UploadLicenseModal
|
||||||
|
open={openUploadModal}
|
||||||
|
onOpenChange={setOpenUploadModal}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const TypeFormLink = () => {
|
const TypeFormLink = () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const workspaceSubscriptionService = useService(WorkspaceSubscriptionService);
|
const workspaceSubscriptionService = useService(WorkspaceSubscriptionService);
|
||||||
|
|||||||
+193
-59
@@ -1,20 +1,29 @@
|
|||||||
import { Button, ConfirmModal, Input, notify } from '@affine/component';
|
import { Button, ConfirmModal, Input, Modal, notify } from '@affine/component';
|
||||||
import { SettingRow } from '@affine/component/setting-components';
|
import { SettingRow } from '@affine/component/setting-components';
|
||||||
import { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-cloud';
|
import { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-cloud';
|
||||||
|
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||||
|
import { useMutation } from '@affine/core/components/hooks/use-mutation';
|
||||||
import {
|
import {
|
||||||
SelfhostLicenseService,
|
SelfhostLicenseService,
|
||||||
WorkspaceSubscriptionService,
|
WorkspaceSubscriptionService,
|
||||||
} from '@affine/core/modules/cloud';
|
} from '@affine/core/modules/cloud';
|
||||||
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
||||||
import { WorkspaceQuotaService } from '@affine/core/modules/quota';
|
import { WorkspaceQuotaService } from '@affine/core/modules/quota';
|
||||||
|
import { UrlService } from '@affine/core/modules/url';
|
||||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||||
import { UserFriendlyError } from '@affine/error';
|
import { UserFriendlyError } from '@affine/error';
|
||||||
|
import {
|
||||||
|
createSelfhostCustomerPortalMutation,
|
||||||
|
SubscriptionVariant,
|
||||||
|
} from '@affine/graphql';
|
||||||
import { Trans, useI18n } from '@affine/i18n';
|
import { Trans, useI18n } from '@affine/i18n';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
|
import { UploadLicenseModal } from './upload-license-modal';
|
||||||
|
|
||||||
export const SelfHostTeamCard = () => {
|
export const SelfHostTeamCard = () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
@@ -30,11 +39,13 @@ export const SelfHostTeamCard = () => {
|
|||||||
const isLocalWorkspace = workspace.flavour === 'local';
|
const isLocalWorkspace = workspace.flavour === 'local';
|
||||||
|
|
||||||
const [openModal, setOpenModal] = useState(false);
|
const [openModal, setOpenModal] = useState(false);
|
||||||
|
const [openUploadModal, setOpenUploadModal] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const selfhostLicenseService = useService(SelfhostLicenseService);
|
const selfhostLicenseService = useService(SelfhostLicenseService);
|
||||||
const license = useLiveData(selfhostLicenseService.license$);
|
const license = useLiveData(selfhostLicenseService.license$);
|
||||||
|
const isOneTimePurchase = license?.variant === SubscriptionVariant.Onetime;
|
||||||
|
|
||||||
useEffect(() => {
|
const revalidate = useCallback(() => {
|
||||||
permission.revalidate();
|
permission.revalidate();
|
||||||
workspaceQuotaService.quota.revalidate();
|
workspaceQuotaService.quota.revalidate();
|
||||||
workspaceSubscriptionService.subscription.revalidate();
|
workspaceSubscriptionService.subscription.revalidate();
|
||||||
@@ -46,24 +57,44 @@ export const SelfHostTeamCard = () => {
|
|||||||
workspaceSubscriptionService,
|
workspaceSubscriptionService,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
revalidate();
|
||||||
|
}, [revalidate]);
|
||||||
|
|
||||||
const description = useMemo(() => {
|
const description = useMemo(() => {
|
||||||
if (isTeam) {
|
if (isTeam) {
|
||||||
return t[
|
return (
|
||||||
'com.affine.settings.workspace.license.self-host-team.team.description'
|
<div>
|
||||||
]({
|
<p>
|
||||||
expirationDate: new Date(license?.expiredAt || 0).toLocaleDateString(),
|
{t[
|
||||||
leftDays: Math.floor(
|
'com.affine.settings.workspace.license.self-host-team.team.description'
|
||||||
(new Date(license?.expiredAt || 0).getTime() - Date.now()) /
|
]({
|
||||||
(1000 * 60 * 60 * 24)
|
expirationDate: new Date(
|
||||||
).toLocaleString(),
|
license?.expiredAt || 0
|
||||||
});
|
).toLocaleDateString(),
|
||||||
|
leftDays: Math.floor(
|
||||||
|
(new Date(license?.expiredAt || 0).getTime() - Date.now()) /
|
||||||
|
(1000 * 60 * 60 * 24)
|
||||||
|
).toLocaleString(),
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
{isOneTimePurchase ? (
|
||||||
|
<p>
|
||||||
|
<Trans
|
||||||
|
i18nKey="com.affine.settings.workspace.license.self-host-team.team.license"
|
||||||
|
components={{ 1: <strong /> }}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return t[
|
return t[
|
||||||
'com.affine.settings.workspace.license.self-host-team.free.description'
|
'com.affine.settings.workspace.license.self-host-team.free.description'
|
||||||
]({
|
]({
|
||||||
memberCount: workspaceQuota?.humanReadable.memberLimit || '10',
|
memberCount: workspaceQuota?.humanReadable.memberLimit || '10',
|
||||||
});
|
});
|
||||||
}, [isTeam, license, t, workspaceQuota]);
|
}, [isOneTimePurchase, isTeam, license, t, workspaceQuota]);
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
if (isLocalWorkspace) {
|
if (isLocalWorkspace) {
|
||||||
confirmEnableCloud(workspace);
|
confirmEnableCloud(workspace);
|
||||||
@@ -72,6 +103,10 @@ export const SelfHostTeamCard = () => {
|
|||||||
setOpenModal(true);
|
setOpenModal(true);
|
||||||
}, [confirmEnableCloud, isLocalWorkspace, workspace]);
|
}, [confirmEnableCloud, isLocalWorkspace, workspace]);
|
||||||
|
|
||||||
|
const handleOpenUploadModal = useCallback(() => {
|
||||||
|
setOpenUploadModal(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const onActivate = useCallback(
|
const onActivate = useCallback(
|
||||||
(license: string) => {
|
(license: string) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -80,8 +115,7 @@ export const SelfHostTeamCard = () => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setOpenModal(false);
|
setOpenModal(false);
|
||||||
permission.revalidate();
|
revalidate();
|
||||||
selfhostLicenseService.revalidate();
|
|
||||||
notify.success({
|
notify.success({
|
||||||
title:
|
title:
|
||||||
t['com.affine.settings.workspace.license.activate-success'](),
|
t['com.affine.settings.workspace.license.activate-success'](),
|
||||||
@@ -99,7 +133,7 @@ export const SelfHostTeamCard = () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[permission, selfhostLicenseService, t, workspace.id]
|
[revalidate, selfhostLicenseService, t, workspace.id]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onDeactivate = useCallback(() => {
|
const onDeactivate = useCallback(() => {
|
||||||
@@ -109,7 +143,7 @@ export const SelfHostTeamCard = () => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setOpenModal(false);
|
setOpenModal(false);
|
||||||
permission.revalidate();
|
revalidate();
|
||||||
notify.success({
|
notify.success({
|
||||||
title:
|
title:
|
||||||
t['com.affine.settings.workspace.license.deactivate-success'](),
|
t['com.affine.settings.workspace.license.deactivate-success'](),
|
||||||
@@ -126,7 +160,7 @@ export const SelfHostTeamCard = () => {
|
|||||||
message: error.message,
|
message: error.message,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [permission, selfhostLicenseService, t, workspace.id]);
|
}, [revalidate, selfhostLicenseService, t, workspace.id]);
|
||||||
|
|
||||||
const handleConfirm = useCallback(
|
const handleConfirm = useCallback(
|
||||||
(license: string) => {
|
(license: string) => {
|
||||||
@@ -163,7 +197,7 @@ export const SelfHostTeamCard = () => {
|
|||||||
]()}
|
]()}
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
{isTeam
|
{isTeam && !isOneTimePurchase
|
||||||
? license?.quantity || ''
|
? license?.quantity || ''
|
||||||
: `${workspaceQuota?.memberCount}/${workspaceQuota?.memberLimit}`}
|
: `${workspaceQuota?.memberCount}/${workspaceQuota?.memberLimit}`}
|
||||||
</span>
|
</span>
|
||||||
@@ -174,13 +208,24 @@ export const SelfHostTeamCard = () => {
|
|||||||
left: isTeam || isLocalWorkspace,
|
left: isTeam || isLocalWorkspace,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
{!isTeam && !isLocalWorkspace ? (
|
||||||
|
<Button
|
||||||
|
variant="plain"
|
||||||
|
className={styles.uploadButton}
|
||||||
|
onClick={handleOpenUploadModal}
|
||||||
|
>
|
||||||
|
{t[
|
||||||
|
'com.affine.settings.workspace.license.self-host-team.upload-license-file'
|
||||||
|
]()}
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
className={styles.activeButton}
|
className={styles.activeButton}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
{t[
|
{t[
|
||||||
`com.affine.settings.workspace.license.self-host-team.${isTeam ? 'deactivate-license' : 'active-key'}`
|
`com.affine.settings.workspace.license.self-host-team.${isTeam ? 'deactivate-license' : 'use-purchased-key'}`
|
||||||
]()}
|
]()}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -191,6 +236,11 @@ export const SelfHostTeamCard = () => {
|
|||||||
isTeam={!!isTeam}
|
isTeam={!!isTeam}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onConfirm={handleConfirm}
|
onConfirm={handleConfirm}
|
||||||
|
isOneTimePurchase={isOneTimePurchase}
|
||||||
|
/>
|
||||||
|
<UploadLicenseModal
|
||||||
|
open={openUploadModal}
|
||||||
|
onOpenChange={setOpenUploadModal}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -202,16 +252,41 @@ const ActionModal = ({
|
|||||||
isTeam,
|
isTeam,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
loading,
|
loading,
|
||||||
|
isOneTimePurchase,
|
||||||
}: {
|
}: {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
isTeam: boolean;
|
isTeam: boolean;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
isOneTimePurchase: boolean;
|
||||||
onConfirm: (key: string) => void;
|
onConfirm: (key: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const [key, setKey] = useState('');
|
const [key, setKey] = useState('');
|
||||||
|
|
||||||
|
const workspace = useService(WorkspaceService).workspace;
|
||||||
|
|
||||||
|
const { isMutating, trigger } = useMutation({
|
||||||
|
mutation: createSelfhostCustomerPortalMutation,
|
||||||
|
});
|
||||||
|
const urlService = useService(UrlService);
|
||||||
|
|
||||||
|
const update = useAsyncCallback(async () => {
|
||||||
|
await trigger(
|
||||||
|
{
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: data => {
|
||||||
|
urlService.openExternal(data.createSelfhostWorkspaceCustomerPortal);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
).catch(e => {
|
||||||
|
const userFriendlyError = UserFriendlyError.fromAny(e);
|
||||||
|
notify.error(userFriendlyError);
|
||||||
|
});
|
||||||
|
}, [trigger, urlService, workspace.id]);
|
||||||
|
|
||||||
const handleConfirm = useCallback(() => {
|
const handleConfirm = useCallback(() => {
|
||||||
onConfirm(key);
|
onConfirm(key);
|
||||||
setKey('');
|
setKey('');
|
||||||
@@ -225,23 +300,96 @@ const ActionModal = ({
|
|||||||
[onOpenChange]
|
[onOpenChange]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleCancel = useCallback(() => {
|
||||||
|
setKey('');
|
||||||
|
onOpenChange(false);
|
||||||
|
}, [onOpenChange]);
|
||||||
|
|
||||||
|
if (isTeam && isOneTimePurchase) {
|
||||||
|
return (
|
||||||
|
<ConfirmModal
|
||||||
|
width={480}
|
||||||
|
open={open}
|
||||||
|
onOpenChange={handleOpenChange}
|
||||||
|
title={t[
|
||||||
|
`com.affine.settings.workspace.license.deactivate-modal.title`
|
||||||
|
]()}
|
||||||
|
description={t[
|
||||||
|
'com.affine.settings.workspace.license.deactivate-modal.description-license'
|
||||||
|
]()}
|
||||||
|
cancelText={t['Cancel']()}
|
||||||
|
cancelButtonOptions={{
|
||||||
|
variant: 'secondary',
|
||||||
|
}}
|
||||||
|
confirmText={t['Confirm']()}
|
||||||
|
confirmButtonOptions={{
|
||||||
|
loading: loading,
|
||||||
|
disabled: loading,
|
||||||
|
variant: 'primary',
|
||||||
|
}}
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
childrenContentClassName={styles.activateModalContent}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTeam) {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
width={480}
|
||||||
|
open={open}
|
||||||
|
onOpenChange={handleOpenChange}
|
||||||
|
title={t[
|
||||||
|
`com.affine.settings.workspace.license.deactivate-modal.title`
|
||||||
|
]()}
|
||||||
|
description={
|
||||||
|
<Trans
|
||||||
|
i18nKey="com.affine.settings.workspace.license.deactivate-modal.description"
|
||||||
|
components={{
|
||||||
|
1: <strong />,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className={styles.footer}>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={update}
|
||||||
|
loading={isMutating}
|
||||||
|
disabled={isMutating}
|
||||||
|
>
|
||||||
|
{t[
|
||||||
|
'com.affine.settings.workspace.license.deactivate-modal.manage-payment'
|
||||||
|
]()}
|
||||||
|
</Button>
|
||||||
|
<div className={styles.rightActions}>
|
||||||
|
<Button variant="secondary" onClick={handleCancel}>
|
||||||
|
{t['Cancel']()}
|
||||||
|
</Button>
|
||||||
|
<Button variant="primary" onClick={handleConfirm}>
|
||||||
|
{t['Confirm']()}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
width={480}
|
width={480}
|
||||||
open={open}
|
open={open}
|
||||||
onOpenChange={handleOpenChange}
|
onOpenChange={handleOpenChange}
|
||||||
title={t[
|
title={t['com.affine.settings.workspace.license.activate-modal.title']()}
|
||||||
`com.affine.settings.workspace.license.${isTeam ? 'deactivate' : 'activate'}-modal.title`
|
|
||||||
]()}
|
|
||||||
description={t[
|
description={t[
|
||||||
`com.affine.settings.workspace.license.${isTeam ? 'deactivate' : 'activate'}-modal.description`
|
'com.affine.settings.workspace.license.activate-modal.description'
|
||||||
]()}
|
]()}
|
||||||
cancelText={t['Cancel']()}
|
cancelText={t['Cancel']()}
|
||||||
cancelButtonOptions={{
|
cancelButtonOptions={{
|
||||||
variant: 'secondary',
|
variant: 'secondary',
|
||||||
}}
|
}}
|
||||||
contentOptions={{
|
contentOptions={{
|
||||||
['data-testid' as string]: 'invite-modal',
|
['data-testid' as string]: 'license-modal',
|
||||||
style: {
|
style: {
|
||||||
padding: '20px 24px',
|
padding: '20px 24px',
|
||||||
},
|
},
|
||||||
@@ -249,46 +397,32 @@ const ActionModal = ({
|
|||||||
confirmText={t['Confirm']()}
|
confirmText={t['Confirm']()}
|
||||||
confirmButtonOptions={{
|
confirmButtonOptions={{
|
||||||
loading: loading,
|
loading: loading,
|
||||||
variant: isTeam ? 'error' : 'primary',
|
variant: 'primary',
|
||||||
disabled: loading || (!isTeam && !key),
|
disabled: loading || (!isTeam && !key),
|
||||||
}}
|
}}
|
||||||
onConfirm={handleConfirm}
|
onConfirm={handleConfirm}
|
||||||
childrenContentClassName={styles.activateModalContent}
|
childrenContentClassName={styles.activateModalContent}
|
||||||
>
|
>
|
||||||
{isTeam ? null : (
|
<Input
|
||||||
<>
|
value={key}
|
||||||
<Input
|
onChange={setKey}
|
||||||
value={key}
|
placeholder="AAAA-AAAA-AAAA-AAAA-AAAA"
|
||||||
onChange={setKey}
|
/>
|
||||||
placeholder="AAAA-AAAA-AAAA-AAAA-AAAA"
|
<span className={styles.tips}>
|
||||||
/>
|
<Trans
|
||||||
<span>
|
i18nKey="com.affine.settings.workspace.license.activate-modal.tips"
|
||||||
<Trans
|
components={{
|
||||||
i18nKey="com.affine.settings.workspace.license.activate-modal.tips"
|
1: (
|
||||||
components={{
|
<a
|
||||||
1: (
|
href="https://affine.pro/pricing/?type=selfhost#table"
|
||||||
<a
|
target="_blank"
|
||||||
href="mailto:support@toeverything.info"
|
rel="noreferrer"
|
||||||
style={{ color: 'var(--affine-link-color)' }}
|
style={{ color: cssVarV2('text/link') }}
|
||||||
>
|
/>
|
||||||
customer support
|
),
|
||||||
</a>
|
}}
|
||||||
),
|
/>
|
||||||
2: (
|
</span>
|
||||||
<a
|
|
||||||
href="https://affine.pro/pricing/?type=selfhost#table"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
style={{ color: 'var(--affine-link-color)' }}
|
|
||||||
>
|
|
||||||
click to purchase
|
|
||||||
</a>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+18
@@ -53,6 +53,10 @@ export const buttonContainer = style({
|
|||||||
export const activeButton = style({
|
export const activeButton = style({
|
||||||
marginTop: '8px',
|
marginTop: '8px',
|
||||||
});
|
});
|
||||||
|
export const uploadButton = style({
|
||||||
|
marginTop: '8px',
|
||||||
|
marginRight: '9px',
|
||||||
|
});
|
||||||
|
|
||||||
export const seat = style({
|
export const seat = style({
|
||||||
fontSize: cssVar('fontXs'),
|
fontSize: cssVar('fontXs'),
|
||||||
@@ -72,3 +76,17 @@ export const tips = style({
|
|||||||
color: cssVarV2('text/secondary'),
|
color: cssVarV2('text/secondary'),
|
||||||
fontSize: cssVar('fontSm'),
|
fontSize: cssVar('fontSm'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const footer = style({
|
||||||
|
marginTop: 'auto',
|
||||||
|
marginBottom: '0',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingTop: '20px',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const rightActions = style({
|
||||||
|
display: 'flex',
|
||||||
|
gap: '20px',
|
||||||
|
});
|
||||||
|
|||||||
+109
@@ -0,0 +1,109 @@
|
|||||||
|
import { cssVar } from '@toeverything/theme';
|
||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const activateModalContent = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '12px',
|
||||||
|
marginTop: '12px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tipsContainer = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '8px',
|
||||||
|
padding: '8px',
|
||||||
|
color: cssVarV2('text/secondary'),
|
||||||
|
fontSize: cssVar('fontSm'),
|
||||||
|
borderRadius: '8px',
|
||||||
|
backgroundColor: cssVarV2('layer/background/tertiary'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tipsTitle = style({
|
||||||
|
fontWeight: 500,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tipsContent = style({
|
||||||
|
fontSize: cssVar('fontSm'),
|
||||||
|
});
|
||||||
|
export const textLink = style({
|
||||||
|
color: cssVarV2('text/link'),
|
||||||
|
selectors: {
|
||||||
|
'&:visited': {
|
||||||
|
color: cssVarV2('text/link'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const workspaceIdContainer = style({
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
fontSize: cssVar('fontXs'),
|
||||||
|
alignItems: 'center',
|
||||||
|
'@media': {
|
||||||
|
'screen and (max-width: 768px)': {
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const workspaceIdLabel = style({
|
||||||
|
fontWeight: 500,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const copyButton = style({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
padding: '4px 12px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: '300px',
|
||||||
|
});
|
||||||
|
export const copyButtonContent = style({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '4px',
|
||||||
|
});
|
||||||
|
export const copyButtonText = style({
|
||||||
|
fontSize: cssVar('fontXs'),
|
||||||
|
padding: '0 4px',
|
||||||
|
color: cssVarV2('text/secondary'),
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
maxWidth: '100%',
|
||||||
|
flex: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const copyIcon = style({
|
||||||
|
width: '16px',
|
||||||
|
height: '16px',
|
||||||
|
color: cssVarV2('icon/primary'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const uploadButton = style({
|
||||||
|
width: '100%',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const uploadButtonContent = style({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
justifyContent: 'center',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const uploadButtonIcon = style({
|
||||||
|
width: '16px',
|
||||||
|
height: '16px',
|
||||||
|
color: cssVarV2('layer/pureWhite'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const footer = style({
|
||||||
|
display: 'flex',
|
||||||
|
fontSize: cssVar('fontSm'),
|
||||||
|
fontWeight: 400,
|
||||||
|
color: cssVarV2('text/secondary'),
|
||||||
|
});
|
||||||
+203
@@ -0,0 +1,203 @@
|
|||||||
|
import { Button, Modal, notify, useConfirmModal } from '@affine/component';
|
||||||
|
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||||
|
import { Upload } from '@affine/core/components/pure/file-upload';
|
||||||
|
import {
|
||||||
|
SelfhostLicenseService,
|
||||||
|
WorkspaceSubscriptionService,
|
||||||
|
} from '@affine/core/modules/cloud';
|
||||||
|
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
||||||
|
import { WorkspaceQuotaService } from '@affine/core/modules/quota';
|
||||||
|
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||||
|
import { copyTextToClipboard } from '@affine/core/utils/clipboard';
|
||||||
|
import { UserFriendlyError } from '@affine/error';
|
||||||
|
import { Trans, useI18n } from '@affine/i18n';
|
||||||
|
import { CopyIcon, FileIcon } from '@blocksuite/icons/rc';
|
||||||
|
import { useService } from '@toeverything/infra';
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import * as styles from './upload-license-modal.css';
|
||||||
|
|
||||||
|
export const UploadLicenseModal = ({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
}) => {
|
||||||
|
const t = useI18n();
|
||||||
|
const workspaceService = useService(WorkspaceService);
|
||||||
|
const workspace = workspaceService.workspace;
|
||||||
|
const licenseService = useService(SelfhostLicenseService);
|
||||||
|
const quotaService = useService(WorkspaceQuotaService);
|
||||||
|
const workspaceSubscriptionService = useService(WorkspaceSubscriptionService);
|
||||||
|
const permission = useService(WorkspacePermissionService).permission;
|
||||||
|
const { openConfirmModal } = useConfirmModal();
|
||||||
|
const [isInstalling, setIsInstalling] = useState(false);
|
||||||
|
|
||||||
|
const revalidate = useCallback(() => {
|
||||||
|
permission.revalidate();
|
||||||
|
quotaService.quota.revalidate();
|
||||||
|
workspaceSubscriptionService.subscription.revalidate();
|
||||||
|
licenseService.revalidate();
|
||||||
|
}, [licenseService, permission, quotaService, workspaceSubscriptionService]);
|
||||||
|
|
||||||
|
const handleInstallLicense = useAsyncCallback(
|
||||||
|
async (file: File) => {
|
||||||
|
setIsInstalling(true);
|
||||||
|
try {
|
||||||
|
await licenseService.installLicense(workspace.id, file);
|
||||||
|
revalidate();
|
||||||
|
onOpenChange(false);
|
||||||
|
openConfirmModal({
|
||||||
|
title:
|
||||||
|
t[
|
||||||
|
'com.affine.settings.workspace.license.self-host-team.upload-license-file.success.title'
|
||||||
|
](),
|
||||||
|
description:
|
||||||
|
t[
|
||||||
|
'com.affine.settings.workspace.license.self-host-team.upload-license-file.success.description'
|
||||||
|
](),
|
||||||
|
confirmText: t['Confirm'](),
|
||||||
|
cancelButtonOptions: {
|
||||||
|
style: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
confirmButtonOptions: {
|
||||||
|
variant: 'primary',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
const err = UserFriendlyError.fromAny(e);
|
||||||
|
onOpenChange(false);
|
||||||
|
console.error(err);
|
||||||
|
openConfirmModal({
|
||||||
|
title:
|
||||||
|
t[
|
||||||
|
'com.affine.settings.workspace.license.self-host-team.upload-license-file.failed'
|
||||||
|
](),
|
||||||
|
description: err.message,
|
||||||
|
confirmText: t['Confirm'](),
|
||||||
|
cancelButtonOptions: {
|
||||||
|
style: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
confirmButtonOptions: {
|
||||||
|
variant: 'primary',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setIsInstalling(false);
|
||||||
|
},
|
||||||
|
[
|
||||||
|
licenseService,
|
||||||
|
onOpenChange,
|
||||||
|
openConfirmModal,
|
||||||
|
revalidate,
|
||||||
|
t,
|
||||||
|
workspace.id,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOpenChange = useCallback(
|
||||||
|
(open: boolean) => {
|
||||||
|
onOpenChange(open);
|
||||||
|
},
|
||||||
|
[onOpenChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const copyWorkspaceId = useCallback(() => {
|
||||||
|
copyTextToClipboard(workspace.id)
|
||||||
|
.then(success => {
|
||||||
|
if (success) {
|
||||||
|
notify.success({ title: t['Copied link to clipboard']() });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}, [t, workspace.id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
revalidate();
|
||||||
|
}, [revalidate]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
width={480}
|
||||||
|
open={open}
|
||||||
|
onOpenChange={handleOpenChange}
|
||||||
|
title={t[
|
||||||
|
'com.affine.settings.workspace.license.self-host-team.upload-license-file'
|
||||||
|
]()}
|
||||||
|
description={t[
|
||||||
|
'com.affine.settings.workspace.license.self-host-team.upload-license-file.description'
|
||||||
|
]()}
|
||||||
|
>
|
||||||
|
<div className={styles.activateModalContent}>
|
||||||
|
<div className={styles.tipsContainer}>
|
||||||
|
<div className={styles.tipsTitle}>
|
||||||
|
{t[
|
||||||
|
'com.affine.settings.workspace.license.self-host-team.upload-license-file.tips.title'
|
||||||
|
]()}
|
||||||
|
</div>
|
||||||
|
<div className={styles.tipsContent}>
|
||||||
|
<Trans
|
||||||
|
i18nKey="com.affine.settings.workspace.license.self-host-team.upload-license-file.tips.content"
|
||||||
|
components={{
|
||||||
|
1: (
|
||||||
|
<a
|
||||||
|
href={`${BUILD_CONFIG.requestLicenseUrl}?usp=pp_url&entry.1000023=${workspace.id}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className={styles.textLink}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.workspaceIdContainer}>
|
||||||
|
<div className={styles.workspaceIdLabel}>
|
||||||
|
{t[
|
||||||
|
'com.affine.settings.workspace.license.self-host-team.upload-license-file.tips.workspace-id'
|
||||||
|
]()}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={copyWorkspaceId}
|
||||||
|
className={styles.copyButton}
|
||||||
|
>
|
||||||
|
<span className={styles.copyButtonContent}>
|
||||||
|
<span className={styles.copyButtonText} title={workspace.id}>
|
||||||
|
{workspace.id}
|
||||||
|
</span>
|
||||||
|
<CopyIcon className={styles.copyIcon} />
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Upload accept=".lic, .license" fileChange={handleInstallLicense}>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
className={styles.uploadButton}
|
||||||
|
loading={isInstalling}
|
||||||
|
disabled={isInstalling}
|
||||||
|
>
|
||||||
|
<span className={styles.uploadButtonContent}>
|
||||||
|
<FileIcon className={styles.uploadButtonIcon} />
|
||||||
|
{t[
|
||||||
|
'com.affine.settings.workspace.license.self-host-team.upload-license-file.click-to-upload'
|
||||||
|
]()}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
</Upload>
|
||||||
|
<div className={styles.footer}>
|
||||||
|
{t[
|
||||||
|
'com.affine.settings.workspace.license.self-host-team.upload-license-file.help'
|
||||||
|
]()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -56,6 +56,10 @@ export class SelfhostLicenseService extends Service {
|
|||||||
return await this.store.deactivate(workspaceId);
|
return await this.store.deactivate(workspaceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async installLicense(workspaceId: string, licenseFile: File) {
|
||||||
|
return await this.store.installLicense(workspaceId, licenseFile);
|
||||||
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.license$.next(null);
|
this.license$.next(null);
|
||||||
this.error$.next(null);
|
this.error$.next(null);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
activateLicenseMutation,
|
activateLicenseMutation,
|
||||||
deactivateLicenseMutation,
|
deactivateLicenseMutation,
|
||||||
getLicenseQuery,
|
getLicenseQuery,
|
||||||
|
installLicenseMutation,
|
||||||
} from '@affine/graphql';
|
} from '@affine/graphql';
|
||||||
import { Store } from '@toeverything/infra';
|
import { Store } from '@toeverything/infra';
|
||||||
|
|
||||||
@@ -63,4 +64,26 @@ export class SelfhostLicenseStore extends Store {
|
|||||||
|
|
||||||
return data.deactivateLicense;
|
return data.deactivateLicense;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async installLicense(
|
||||||
|
workspaceId: string,
|
||||||
|
license: File,
|
||||||
|
signal?: AbortSignal
|
||||||
|
) {
|
||||||
|
if (!this.workspaceServerService.server) {
|
||||||
|
throw new Error('No Server');
|
||||||
|
}
|
||||||
|
const data = await this.workspaceServerService.server.gql({
|
||||||
|
query: installLicenseMutation,
|
||||||
|
variables: {
|
||||||
|
workspaceId: workspaceId,
|
||||||
|
license: license,
|
||||||
|
},
|
||||||
|
context: {
|
||||||
|
signal,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return data.installLicense;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,17 +37,27 @@ export const QuotaCheck = ({
|
|||||||
const isOwner = profile?.isOwner;
|
const isOwner = profile?.isOwner;
|
||||||
const isTeam = profile?.isTeam;
|
const isTeam = profile?.isTeam;
|
||||||
const workspaceDialogService = useService(WorkspaceDialogService);
|
const workspaceDialogService = useService(WorkspaceDialogService);
|
||||||
|
const dialog = useLiveData(workspaceDialogService.dialogs$);
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
const onConfirm = useCallback(() => {
|
const onConfirm = useCallback(() => {
|
||||||
if (!isOwner) {
|
if (!isOwner) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
dialog.some(
|
||||||
|
d =>
|
||||||
|
(d.type === 'setting' && d.props.activeTab === 'plans') ||
|
||||||
|
(d.type === 'setting' && d.props.activeTab === 'workspace:license')
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
workspaceDialogService.open('setting', {
|
workspaceDialogService.open('setting', {
|
||||||
activeTab: 'plans',
|
activeTab: 'plans',
|
||||||
scrollAnchor: 'cloudPricingPlan',
|
scrollAnchor: 'cloudPricingPlan',
|
||||||
});
|
});
|
||||||
}, [workspaceDialogService, isOwner]);
|
}, [dialog, isOwner, workspaceDialogService]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
workspaceQuota?.revalidate();
|
workspaceQuota?.revalidate();
|
||||||
|
|||||||
@@ -4454,7 +4454,7 @@ export function useAFFiNEI18N(): {
|
|||||||
*/
|
*/
|
||||||
["com.affine.payment.license-success.text-1"](): string;
|
["com.affine.payment.license-success.text-1"](): string;
|
||||||
/**
|
/**
|
||||||
* `You can use this key to upgrade in Settings > Workspace > Billing > Upgrade`
|
* `You can use this key to upgrade in Settings > Workspace > License > Use purchased key`
|
||||||
*/
|
*/
|
||||||
["com.affine.payment.license-success.hint"](): string;
|
["com.affine.payment.license-success.hint"](): string;
|
||||||
/**
|
/**
|
||||||
@@ -6025,13 +6025,61 @@ export function useAFFiNEI18N(): {
|
|||||||
*/
|
*/
|
||||||
["com.affine.settings.workspace.license.self-host-team.seats"](): string;
|
["com.affine.settings.workspace.license.self-host-team.seats"](): string;
|
||||||
/**
|
/**
|
||||||
* `Active key`
|
* `Use purchased key`
|
||||||
*/
|
*/
|
||||||
["com.affine.settings.workspace.license.self-host-team.active-key"](): string;
|
["com.affine.settings.workspace.license.self-host-team.use-purchased-key"](): string;
|
||||||
|
/**
|
||||||
|
* `Upload license file`
|
||||||
|
*/
|
||||||
|
["com.affine.settings.workspace.license.self-host-team.upload-license-file"](): string;
|
||||||
|
/**
|
||||||
|
* `Upload license file locally and verify the license information.`
|
||||||
|
*/
|
||||||
|
["com.affine.settings.workspace.license.self-host-team.upload-license-file.description"](): string;
|
||||||
|
/**
|
||||||
|
* `To purchase a license:`
|
||||||
|
*/
|
||||||
|
["com.affine.settings.workspace.license.self-host-team.upload-license-file.tips.title"](): string;
|
||||||
|
/**
|
||||||
|
* `Workspace id`
|
||||||
|
*/
|
||||||
|
["com.affine.settings.workspace.license.self-host-team.upload-license-file.tips.workspace-id"](): string;
|
||||||
|
/**
|
||||||
|
* `Click to upload`
|
||||||
|
*/
|
||||||
|
["com.affine.settings.workspace.license.self-host-team.upload-license-file.click-to-upload"](): string;
|
||||||
|
/**
|
||||||
|
* `Activation failed`
|
||||||
|
*/
|
||||||
|
["com.affine.settings.workspace.license.self-host-team.upload-license-file.failed"](): string;
|
||||||
|
/**
|
||||||
|
* `Activation Success`
|
||||||
|
*/
|
||||||
|
["com.affine.settings.workspace.license.self-host-team.upload-license-file.success.title"](): string;
|
||||||
|
/**
|
||||||
|
* `License has been successfully applied`
|
||||||
|
*/
|
||||||
|
["com.affine.settings.workspace.license.self-host-team.upload-license-file.success.description"](): string;
|
||||||
|
/**
|
||||||
|
* `If you encounter any issues, contact support@toeverything.info.`
|
||||||
|
*/
|
||||||
|
["com.affine.settings.workspace.license.self-host-team.upload-license-file.help"](): string;
|
||||||
/**
|
/**
|
||||||
* `Deactivate`
|
* `Deactivate`
|
||||||
*/
|
*/
|
||||||
["com.affine.settings.workspace.license.self-host-team.deactivate-license"](): string;
|
["com.affine.settings.workspace.license.self-host-team.deactivate-license"](): string;
|
||||||
|
/**
|
||||||
|
* `Replace your license file`
|
||||||
|
*/
|
||||||
|
["com.affine.settings.workspace.license.self-host-team.replace-license.title"](): string;
|
||||||
|
/**
|
||||||
|
* `Replace the existing license file with a new, updated version.`
|
||||||
|
*/
|
||||||
|
["com.affine.settings.workspace.license.self-host-team.replace-license.description"](): string;
|
||||||
|
/**
|
||||||
|
* `Upload license file`
|
||||||
|
*/
|
||||||
|
["com.affine.settings.workspace.license.self-host-team.replace-license.upload"](): string;
|
||||||
/**
|
/**
|
||||||
* `Buy more seat`
|
* `Buy more seat`
|
||||||
*/
|
*/
|
||||||
@@ -6049,13 +6097,17 @@ export function useAFFiNEI18N(): {
|
|||||||
*/
|
*/
|
||||||
["com.affine.settings.workspace.license.activate-success"](): string;
|
["com.affine.settings.workspace.license.activate-success"](): string;
|
||||||
/**
|
/**
|
||||||
* `Deactivate License`
|
* `Confirm deactivation?`
|
||||||
*/
|
*/
|
||||||
["com.affine.settings.workspace.license.deactivate-modal.title"](): string;
|
["com.affine.settings.workspace.license.deactivate-modal.title"](): string;
|
||||||
/**
|
/**
|
||||||
* `Are you sure you want to deactivate this license?`
|
* `After deactivation, you will need to upload a new license to continue using team feature`
|
||||||
*/
|
*/
|
||||||
["com.affine.settings.workspace.license.deactivate-modal.description"](): string;
|
["com.affine.settings.workspace.license.deactivate-modal.description-license"](): string;
|
||||||
|
/**
|
||||||
|
* `Manage Payment`
|
||||||
|
*/
|
||||||
|
["com.affine.settings.workspace.license.deactivate-modal.manage-payment"](): string;
|
||||||
/**
|
/**
|
||||||
* `License deactivated successfully.`
|
* `License deactivated successfully.`
|
||||||
*/
|
*/
|
||||||
@@ -9123,11 +9175,28 @@ export const TypedTrans: {
|
|||||||
["1"]: JSX.Element;
|
["1"]: JSX.Element;
|
||||||
}>>;
|
}>>;
|
||||||
/**
|
/**
|
||||||
* `If you encounter any issues, please contact our <1>customer support</1>. No license yet? <2>Click to purchase</2>.`
|
* `Activate using the local key from <1>Toeverything.Inc</1>`
|
||||||
|
*/
|
||||||
|
["com.affine.settings.workspace.license.self-host-team.team.license"]: ComponentType<TypedTransProps<Readonly<{}>, {
|
||||||
|
["1"]: JSX.Element;
|
||||||
|
}>>;
|
||||||
|
/**
|
||||||
|
* `Copy your workspace id and <1>reach out to us</1>.`
|
||||||
|
*/
|
||||||
|
["com.affine.settings.workspace.license.self-host-team.upload-license-file.tips.content"]: ComponentType<TypedTransProps<Readonly<{}>, {
|
||||||
|
["1"]: JSX.Element;
|
||||||
|
}>>;
|
||||||
|
/**
|
||||||
|
* `If you encounter any issues, contact support@toeverything.info. No license yet? <1>Click to purchase</1>.`
|
||||||
*/
|
*/
|
||||||
["com.affine.settings.workspace.license.activate-modal.tips"]: ComponentType<TypedTransProps<Readonly<{}>, {
|
["com.affine.settings.workspace.license.activate-modal.tips"]: ComponentType<TypedTransProps<Readonly<{}>, {
|
||||||
["1"]: JSX.Element;
|
["1"]: JSX.Element;
|
||||||
["2"]: JSX.Element;
|
}>>;
|
||||||
|
/**
|
||||||
|
* `This will make the workspace read-only. Your key remains usable elsewhere. Deactivation doesn't cancel your Team plan. To cancel, go to <1>Manage Payment</1>.`
|
||||||
|
*/
|
||||||
|
["com.affine.settings.workspace.license.deactivate-modal.description"]: ComponentType<TypedTransProps<Readonly<{}>, {
|
||||||
|
["1"]: JSX.Element;
|
||||||
}>>;
|
}>>;
|
||||||
/**
|
/**
|
||||||
* `The "<1>{{ name }}</1>" property will be removed. This action cannot be undone.`
|
* `The "<1>{{ name }}</1>" property will be removed. This action cannot be undone.`
|
||||||
|
|||||||
@@ -1103,7 +1103,7 @@
|
|||||||
"com.affine.payment.license-success.title": "Thank you for your purchase!",
|
"com.affine.payment.license-success.title": "Thank you for your purchase!",
|
||||||
"com.affine.payment.license-success.text-1": "Thank you for purchasing the AFFiNE self-hosted license.",
|
"com.affine.payment.license-success.text-1": "Thank you for purchasing the AFFiNE self-hosted license.",
|
||||||
"com.affine.payment.license-success.text-2": "If you have any questions, please contact our <1>customer support</1>.",
|
"com.affine.payment.license-success.text-2": "If you have any questions, please contact our <1>customer support</1>.",
|
||||||
"com.affine.payment.license-success.hint": "You can use this key to upgrade in Settings > Workspace > Billing > Upgrade",
|
"com.affine.payment.license-success.hint": "You can use this key to upgrade in Settings > Workspace > License > Use purchased key",
|
||||||
"com.affine.payment.license-success.open-affine": "Open AFFiNE",
|
"com.affine.payment.license-success.open-affine": "Open AFFiNE",
|
||||||
"com.affine.payment.license-success.copy": "Copied key to clipboard",
|
"com.affine.payment.license-success.copy": "Copied key to clipboard",
|
||||||
"com.affine.peek-view-controls.close": "Close",
|
"com.affine.peek-view-controls.close": "Close",
|
||||||
@@ -1498,17 +1498,33 @@
|
|||||||
"com.affine.settings.workspace.license.self-host": "Selfhosted workspace",
|
"com.affine.settings.workspace.license.self-host": "Selfhosted workspace",
|
||||||
"com.affine.settings.workspace.license.self-host-team": "Self-host Team Workspace",
|
"com.affine.settings.workspace.license.self-host-team": "Self-host Team Workspace",
|
||||||
"com.affine.settings.workspace.license.self-host-team.team.description": "This license will expire on {{expirationDate}}, with {{leftDays}} days remaining.",
|
"com.affine.settings.workspace.license.self-host-team.team.description": "This license will expire on {{expirationDate}}, with {{leftDays}} days remaining.",
|
||||||
|
"com.affine.settings.workspace.license.self-host-team.team.license": "Activate using the local key from <1>Toeverything.Inc</1>",
|
||||||
"com.affine.settings.workspace.license.self-host-team.free.description": "Basic version: {{memberCount}} seats. For more, purchase or use activation key.",
|
"com.affine.settings.workspace.license.self-host-team.free.description": "Basic version: {{memberCount}} seats. For more, purchase or use activation key.",
|
||||||
"com.affine.settings.workspace.license.self-host-team.seats": "Seats",
|
"com.affine.settings.workspace.license.self-host-team.seats": "Seats",
|
||||||
"com.affine.settings.workspace.license.self-host-team.active-key": "Active key",
|
"com.affine.settings.workspace.license.self-host-team.use-purchased-key": "Use purchased key",
|
||||||
|
"com.affine.settings.workspace.license.self-host-team.upload-license-file": "Upload license file",
|
||||||
|
"com.affine.settings.workspace.license.self-host-team.upload-license-file.description": "Upload license file locally and verify the license information.",
|
||||||
|
"com.affine.settings.workspace.license.self-host-team.upload-license-file.tips.title": "To purchase a license:",
|
||||||
|
"com.affine.settings.workspace.license.self-host-team.upload-license-file.tips.content": "Copy your workspace id and <1>reach out to us</1>.",
|
||||||
|
"com.affine.settings.workspace.license.self-host-team.upload-license-file.tips.workspace-id": "Workspace id",
|
||||||
|
"com.affine.settings.workspace.license.self-host-team.upload-license-file.click-to-upload": "Click to upload",
|
||||||
|
"com.affine.settings.workspace.license.self-host-team.upload-license-file.failed": "Activation failed",
|
||||||
|
"com.affine.settings.workspace.license.self-host-team.upload-license-file.success.title": "Activation Success",
|
||||||
|
"com.affine.settings.workspace.license.self-host-team.upload-license-file.success.description": "License has been successfully applied",
|
||||||
|
"com.affine.settings.workspace.license.self-host-team.upload-license-file.help": "If you encounter any issues, contact support@toeverything.info.",
|
||||||
"com.affine.settings.workspace.license.self-host-team.deactivate-license": "Deactivate",
|
"com.affine.settings.workspace.license.self-host-team.deactivate-license": "Deactivate",
|
||||||
|
"com.affine.settings.workspace.license.self-host-team.replace-license.title": "Replace your license file",
|
||||||
|
"com.affine.settings.workspace.license.self-host-team.replace-license.description": "Replace the existing license file with a new, updated version.",
|
||||||
|
"com.affine.settings.workspace.license.self-host-team.replace-license.upload": "Upload license file",
|
||||||
"com.affine.settings.workspace.license.buy-more-seat": "Buy more seat",
|
"com.affine.settings.workspace.license.buy-more-seat": "Buy more seat",
|
||||||
"com.affine.settings.workspace.license.activate-modal.title": "Activate License",
|
"com.affine.settings.workspace.license.activate-modal.title": "Activate License",
|
||||||
"com.affine.settings.workspace.license.activate-modal.description": "Enter license key to activate this self host workspace.",
|
"com.affine.settings.workspace.license.activate-modal.description": "Enter license key to activate this self host workspace.",
|
||||||
"com.affine.settings.workspace.license.activate-modal.tips": "If you encounter any issues, please contact our <1>customer support</1>. No license yet? <2>Click to purchase</2>.",
|
"com.affine.settings.workspace.license.activate-modal.tips": "If you encounter any issues, contact support@toeverything.info. No license yet? <1>Click to purchase</1>.",
|
||||||
"com.affine.settings.workspace.license.activate-success": "License activated successfully.",
|
"com.affine.settings.workspace.license.activate-success": "License activated successfully.",
|
||||||
"com.affine.settings.workspace.license.deactivate-modal.title": "Deactivate License",
|
"com.affine.settings.workspace.license.deactivate-modal.title": "Confirm deactivation?",
|
||||||
"com.affine.settings.workspace.license.deactivate-modal.description": "Are you sure you want to deactivate this license?",
|
"com.affine.settings.workspace.license.deactivate-modal.description": "This will make the workspace read-only. Your key remains usable elsewhere. Deactivation doesn't cancel your Team plan. To cancel, go to <1>Manage Payment</1>.",
|
||||||
|
"com.affine.settings.workspace.license.deactivate-modal.description-license": "After deactivation, you will need to upload a new license to continue using team feature",
|
||||||
|
"com.affine.settings.workspace.license.deactivate-modal.manage-payment": "Manage Payment",
|
||||||
"com.affine.settings.workspace.license.deactivate-success": "License deactivated successfully.",
|
"com.affine.settings.workspace.license.deactivate-success": "License deactivated successfully.",
|
||||||
"com.affine.settings.workspace.state.local": "Local",
|
"com.affine.settings.workspace.state.local": "Local",
|
||||||
"com.affine.settings.workspace.state.sync-affine-cloud": "Sync with AFFiNE Cloud",
|
"com.affine.settings.workspace.state.sync-affine-cloud": "Sync with AFFiNE Cloud",
|
||||||
|
|||||||
Vendored
+1
@@ -30,6 +30,7 @@ declare interface BUILD_CONFIG_TYPE {
|
|||||||
pricingUrl: string;
|
pricingUrl: string;
|
||||||
downloadUrl: string;
|
downloadUrl: string;
|
||||||
discordUrl: string;
|
discordUrl: string;
|
||||||
|
requestLicenseUrl: string;
|
||||||
// see: tools/workers
|
// see: tools/workers
|
||||||
imageProxyUrl: string;
|
imageProxyUrl: string;
|
||||||
linkPreviewUrl: string;
|
linkPreviewUrl: string;
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export function getBuildConfig(
|
|||||||
downloadUrl: 'https://affine.pro/download',
|
downloadUrl: 'https://affine.pro/download',
|
||||||
pricingUrl: 'https://affine.pro/pricing',
|
pricingUrl: 'https://affine.pro/pricing',
|
||||||
discordUrl: 'https://affine.pro/redirect/discord',
|
discordUrl: 'https://affine.pro/redirect/discord',
|
||||||
|
requestLicenseUrl: 'https://affine.pro/redirect/license',
|
||||||
imageProxyUrl: '/api/worker/image-proxy',
|
imageProxyUrl: '/api/worker/image-proxy',
|
||||||
linkPreviewUrl: '/api/worker/link-preview',
|
linkPreviewUrl: '/api/worker/link-preview',
|
||||||
CAPTCHA_SITE_KEY: process.env.CAPTCHA_SITE_KEY ?? '',
|
CAPTCHA_SITE_KEY: process.env.CAPTCHA_SITE_KEY ?? '',
|
||||||
|
|||||||
Reference in New Issue
Block a user