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:
JimmFly
2025-05-27 04:56:13 +00:00
parent 382c237dac
commit 1b715e588c
13 changed files with 714 additions and 75 deletions

View File

@@ -56,6 +56,10 @@ export class SelfhostLicenseService extends Service {
return await this.store.deactivate(workspaceId);
}
async installLicense(workspaceId: string, licenseFile: File) {
return await this.store.installLicense(workspaceId, licenseFile);
}
clear() {
this.license$.next(null);
this.error$.next(null);

View File

@@ -2,6 +2,7 @@ import {
activateLicenseMutation,
deactivateLicenseMutation,
getLicenseQuery,
installLicenseMutation,
} from '@affine/graphql';
import { Store } from '@toeverything/infra';
@@ -63,4 +64,26 @@ export class SelfhostLicenseStore extends Store {
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;
}
}

View File

@@ -37,17 +37,27 @@ export const QuotaCheck = ({
const isOwner = profile?.isOwner;
const isTeam = profile?.isTeam;
const workspaceDialogService = useService(WorkspaceDialogService);
const dialog = useLiveData(workspaceDialogService.dialogs$);
const t = useI18n();
const onConfirm = useCallback(() => {
if (!isOwner) {
return;
}
if (
dialog.some(
d =>
(d.type === 'setting' && d.props.activeTab === 'plans') ||
(d.type === 'setting' && d.props.activeTab === 'workspace:license')
)
) {
return;
}
workspaceDialogService.open('setting', {
activeTab: 'plans',
scrollAnchor: 'cloudPricingPlan',
});
}, [workspaceDialogService, isOwner]);
}, [dialog, isOwner, workspaceDialogService]);
useEffect(() => {
workspaceQuota?.revalidate();