mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 07:43:35 +00:00
[AC-1512] Feature: Secrets Manager billing - round 2 (#5854)
* [AC-1423] Update organization subscription cloud page (#5614) * [AC-1423] Add ProgressModule to shared.module.ts * [AC-1423] Update cloud subscription page styles - Remove bootstrap styles - Use CL components where applicable - Use CL typography directives - Update heading levels to prepare for new SM sections * [AC-1423] Add usePasswordManager boolean to organization domain * [AC-1423] Introduce BitwardenProductType enum * [AC-1423] Update Organization subscription line items - Add product type prefix - Indent addon services like additional storage and service accounts - Show line items for free plans * [AC-1423] Simply sort function * [AC-1423] Remove header border * [AC-1423] Make "Password Manager" the default fallback for product name * [AC-1420] Add Secrets Manager subscribe component (#5617) * [AC-1418] Add secrets manager manage subscription component (#5661) * [AC-1423] Add minWidth input to bit-progress component * [AC-1423] Add ProgressModule to shared.module.ts * [AC-1423] Update cloud subscription page styles - Remove bootstrap styles - Use CL components where applicable - Use CL typography directives - Update heading levels to prepare for new SM sections * [AC-1423] Add usePasswordManager boolean to organization domain * [AC-1423] Introduce BitwardenProductType enum * [AC-1423] Update Organization subscription line items - Add product type prefix - Indent addon services like additional storage and service accounts - Show line items for free plans * [AC-1423] Simply sort function * [AC-1423] Remove header border * [AC-1423] Remove redundant condition * [AC-1423] Remove ineffective div * [AC-1423] Make "Password Manager" the default fallback for product name * Revert "[AC-1423] Add minWidth input to bit-progress component" This reverts commit95b2223a30. * [AC-1423] Remove minWidth attribute * [AC-1423] Switch to AddonProductType enum instead of boolean * Revert "[AC-1423] Switch to AddonProductType enum instead of boolean" This reverts commit204f64b4e7. * [AC-1423] Tweak sorting comment * [AC-1418] Add initial SecretsManagerAdjustSubscription component * [AC-1418] Add initial SM adjustment form * [AC-1418] Adjust organization-subscription-update.request.ts to support both PM and SM * [AC-1418] Rename service account fields in the options interface * [AC-1418] Add api service call to update SM subscription * [AC-1418] Cleanup form html * [AC-1418] Add missing SM plan properties * [AC-1418] Add SM subscription adjust form and logic to hide it * [AC-1418] Add better docs to options interface * [AC-1418] Fix conflicting required/optional labels for auto-scaling limits * [AC-1418] Adjust labels and appearance to better match design * [AC-1418] Use the SM plan for billing interval * [AC-1418] Hide SM billing adjustment component behind feature flag * [AC-1418] Update request model to match server * [AC-1418] Cleanup BitwardenProductType after merge Add to barrel file and update applicable imports. * [AC-1418] Revert change to update PM subscription request model * [AC-1418] Add new update SM subscription request model * [AC-1418] Add new service method to update SM subscription * [AC-1418] Use new model and service method * [AC-1418] Cleanup SM subscription UI flags * [AC-1418] Move SM adjust subscription component into SM billing module * [AC-1418] Update SM seat count minimum to 1 * [AC-1418] Add missing currency codes * [AC-1418] Simplify monthly price calculation * [AC-1418] Increase PM adjust subscription form input width * [AC-1418] Add check for null subscription --------- Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> * add the additional properties (#5743) * Allow autoscale limits to be removed, update naming (#5781) * [AC-1488] Store Organization.SmServiceAccounts as total not additional (#5784) * Allow autoscale limits to be removed, update naming * Display additional service accounts only --------- Co-authored-by: Shane Melton <smelton@bitwarden.com> * [AC-1473] SM beta ending callout (#5719) * [AC-1423] Add minWidth input to bit-progress component * [AC-1423] Add ProgressModule to shared.module.ts * [AC-1423] Update cloud subscription page styles - Remove bootstrap styles - Use CL components where applicable - Use CL typography directives - Update heading levels to prepare for new SM sections * [AC-1423] Add usePasswordManager boolean to organization domain * [AC-1423] Introduce BitwardenProductType enum * [AC-1423] Update Organization subscription line items - Add product type prefix - Indent addon services like additional storage and service accounts - Show line items for free plans * [AC-1423] Simply sort function * [AC-1423] Remove header border * [AC-1423] Remove redundant condition * [AC-1423] Remove ineffective div * [AC-1423] Make "Password Manager" the default fallback for product name * Revert "[AC-1423] Add minWidth input to bit-progress component" This reverts commit95b2223a30. * [AC-1423] Remove minWidth attribute * [AC-1423] Switch to AddonProductType enum instead of boolean * Revert "[AC-1423] Switch to AddonProductType enum instead of boolean" This reverts commit204f64b4e7. * [AC-1423] Tweak sorting comment * [AC-1418] Add initial SecretsManagerAdjustSubscription component * [AC-1418] Add initial SM adjustment form * [AC-1418] Adjust organization-subscription-update.request.ts to support both PM and SM * [AC-1418] Rename service account fields in the options interface * [AC-1418] Add api service call to update SM subscription * [AC-1418] Cleanup form html * [AC-1418] Add missing SM plan properties * [AC-1418] Add SM subscription adjust form and logic to hide it * [AC-1418] Add better docs to options interface * [AC-1418] Fix conflicting required/optional labels for auto-scaling limits * [AC-1418] Adjust labels and appearance to better match design * [AC-1418] Use the SM plan for billing interval * [AC-1418] Hide SM billing adjustment component behind feature flag * [AC-1418] Update request model to match server * [AC-1418] Cleanup BitwardenProductType after merge Add to barrel file and update applicable imports. * [AC-1418] Revert change to update PM subscription request model * [AC-1418] Add new update SM subscription request model * [AC-1418] Add new service method to update SM subscription * [AC-1418] Use new model and service method * [AC-1418] Cleanup SM subscription UI flags * [AC-1418] Move SM adjust subscription component into SM billing module * [AC-1418] Update SM seat count minimum to 1 * [AC-1418] Add missing currency codes * [AC-1418] Simplify monthly price calculation * add daysRemaining util function and unit tests * [AC-1474] update organization models to include SM beta flag * add SM beta callout to org subscription page * update messages.json * remove beta field from profile org response * improve daysRemaining code clarity * set SM beta in org model constructor * tweak free SM row visibility * refactor callout description * Revert "remove beta field from profile org response" This reverts commit6c6249e1ec. * fix dates * [AC-1468]: hide adjust SM component if beta user * add sm beta field to org sub response; remove everywhere else * fix copy --------- Co-authored-by: Shane Melton <smelton@bitwarden.com> Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Co-authored-by: Thomas Rittson <trittson@bitwarden.com> * [AC-1531] Fix SM subscribe component not showing in free org billing tab (#5848) Also: * Fix spacing in layout * Send zero values for free plans * Fix: properly delete enroll component * remove the beta end message for free org (#5877) * [AC-1458] Update local organization data after subscribing to Secrets Manager (#5888) * [AC-1567] Fix max additional service account cost estimate (#5923) * Fix max additional service account cost estimate * Update i18n string ref * Make i18n string keys consistent * [AC-1461] Secrets Manager seat autoscaling cleanup (#5924) * Remove unused return value from putOrganizationUserBulkEnableSecretsManager * Fix service account limit validator (#5926) * Updated Utils.daysRemaining method to calculate result using Math.floor and updated unit tests. --------- Co-authored-by: Shane Melton <smelton@bitwarden.com> Co-authored-by: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Co-authored-by: Rui Tome <rtome@bitwarden.com> Co-authored-by: Will Martin <contact@willmartian.com> Co-authored-by: Álison Fernandes <vvolkgang@users.noreply.github.com> Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com>
This commit is contained in:
@@ -9,7 +9,7 @@ import { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common
|
||||
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/abstractions/totp.service";
|
||||
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
|
||||
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
|
||||
import { InternalOrganizationService as InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||
import { InternalPolicyService as InternalPolicyServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { ProviderService as ProviderServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||
|
||||
@@ -87,13 +87,32 @@
|
||||
<td bitCell>{{ "passwordManager" | i18n }} - {{ "freeOrganization" | i18n }}</td>
|
||||
<td bitCell class="tw-text-right">{{ "free" | i18n }}</td>
|
||||
</tr>
|
||||
<tr bitRow *ngIf="userOrg.useSecretsManager">
|
||||
<tr bitRow *ngIf="userOrg.useSecretsManager && !sub.secretsManagerBeta">
|
||||
<td bitCell>{{ "secretsManager" | i18n }} - {{ "freeOrganization" | i18n }}</td>
|
||||
<td bitCell class="tw-text-right">{{ "free" | i18n }}</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<tr bitRow *ngIf="sub.secretsManagerBeta">
|
||||
<td bitCell>
|
||||
{{ "secretsManager" | i18n }} -
|
||||
{{ "beta" | i18n }}
|
||||
({{ "annually" | i18n }}) @
|
||||
{{ 0 | currency : "$" }}
|
||||
<span bitBadge badgeType="warning" class="tw-ml-2">{{
|
||||
"betaEnding" | i18n | uppercase
|
||||
}}</span>
|
||||
</td>
|
||||
<td bitCell class="tw-text-right">{{ 0 | currency : "$" }} /{{ "year" | i18n }}</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
<bit-callout
|
||||
*ngIf="sub.secretsManagerBeta && !userOrg.isFreeOrg"
|
||||
type="warning"
|
||||
class="tw-mt-4 tw-block"
|
||||
>
|
||||
{{ smBetaEndedDesc }}
|
||||
</bit-callout>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DatePipe } from "@angular/common";
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { concatMap, Subject, takeUntil } from "rxjs";
|
||||
@@ -17,6 +18,7 @@ import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstraction
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
|
||||
import {
|
||||
BillingSyncApiKeyComponent,
|
||||
@@ -45,6 +47,9 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
firstLoaded = false;
|
||||
loading: boolean;
|
||||
|
||||
private readonly _smBetaEndingDate = new Date(2023, 7, 25);
|
||||
private readonly _smGracePeriodEndingDate = new Date(2023, 9, 24);
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
@@ -57,7 +62,8 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private route: ActivatedRoute,
|
||||
private dialogService: DialogServiceAbstraction,
|
||||
private configService: ConfigServiceAbstraction
|
||||
private configService: ConfigServiceAbstraction,
|
||||
private datePipe: DatePipe
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -122,6 +128,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
this.userOrg.useSecretsManager &&
|
||||
this.subscription != null &&
|
||||
this.sub.secretsManagerPlan?.hasAdditionalSeatsOption &&
|
||||
!this.sub.secretsManagerBeta &&
|
||||
!this.subscription.cancelled &&
|
||||
!this.subscriptionMarkedForCancel;
|
||||
|
||||
@@ -256,6 +263,14 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
);
|
||||
}
|
||||
|
||||
get smBetaEndedDesc() {
|
||||
return this.i18nService.translate(
|
||||
"smBetaEndedDesc",
|
||||
this.datePipe.transform(this._smBetaEndingDate),
|
||||
Utils.daysRemaining(this._smGracePeriodEndingDate).toString()
|
||||
);
|
||||
}
|
||||
|
||||
cancel = async () => {
|
||||
if (this.loading) {
|
||||
return;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<bit-hint>
|
||||
<strong>{{ "total" | i18n }}:</strong>
|
||||
{{ formGroup.value.seatCount || 0 }} × {{ options.seatPrice | currency : "$" }} =
|
||||
{{ seatTotal | currency : "$" }} / {{ options.interval | i18n }}
|
||||
{{ seatTotalCost | currency : "$" }} / {{ options.interval | i18n }}
|
||||
</bit-hint>
|
||||
</bit-form-field>
|
||||
<bit-form-control>
|
||||
@@ -28,7 +28,7 @@
|
||||
<bit-hint>
|
||||
<strong>{{ "maxSeatCost" | i18n }}:</strong>
|
||||
{{ formGroup.value.maxAutoscaleSeats || 0 }} ×
|
||||
{{ options.seatPrice | currency : "$" }} = {{ maxSeatTotal | currency : "$" }} /
|
||||
{{ options.seatPrice | currency : "$" }} = {{ maxSeatTotalCost | currency : "$" }} /
|
||||
{{ options.interval | i18n }}
|
||||
</bit-hint>
|
||||
</bit-form-field>
|
||||
@@ -44,16 +44,14 @@
|
||||
/>
|
||||
<bit-hint>
|
||||
<div>
|
||||
{{
|
||||
"additionalServiceAccountsDesc"
|
||||
| i18n : options.baseServiceAccountCount : (monthlyServiceAccountPrice | currency : "$")
|
||||
}}
|
||||
{{ "includedServiceAccounts" | i18n : options.baseServiceAccountCount }}
|
||||
{{ "addAdditionalServiceAccounts" | i18n : (monthlyServiceAccountPrice | currency : "$") }}
|
||||
</div>
|
||||
<div>
|
||||
<strong>{{ "total" | i18n }}:</strong>
|
||||
{{ formGroup.value.additionalServiceAccounts || 0 }} ×
|
||||
{{ options.additionalServiceAccountPrice | currency : "$" }} =
|
||||
{{ serviceAccountTotal | currency : "$" }} / {{ options.interval | i18n }}
|
||||
{{ serviceAccountTotalCost | currency : "$" }} / {{ options.interval | i18n }}
|
||||
</div>
|
||||
</bit-hint>
|
||||
</bit-form-field>
|
||||
@@ -80,10 +78,13 @@
|
||||
[min]="formGroup.value.additionalServiceAccounts"
|
||||
/>
|
||||
<bit-hint>
|
||||
<div>
|
||||
{{ "includedServiceAccounts" | i18n : options.baseServiceAccountCount }}
|
||||
</div>
|
||||
<strong>{{ "maxServiceAccountCost" | i18n }}:</strong>
|
||||
{{ formGroup.value.maxAutoscaleServiceAccounts || 0 }} ×
|
||||
{{ maxAdditionalServiceAccounts }} ×
|
||||
{{ options.additionalServiceAccountPrice | currency : "$" }} =
|
||||
{{ maxServiceAccountTotal | currency : "$" }} / {{ options.interval | i18n }}
|
||||
{{ maxServiceAccountTotalCost | currency : "$" }} / {{ options.interval | i18n }}
|
||||
</bit-hint>
|
||||
</bit-form-field>
|
||||
<button type="submit" bitButton buttonType="primary" bitFormButton>
|
||||
|
||||
@@ -72,24 +72,26 @@ export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDest
|
||||
: this.options.additionalServiceAccountPrice / 12;
|
||||
}
|
||||
|
||||
get serviceAccountTotal(): number {
|
||||
get serviceAccountTotalCost(): number {
|
||||
return Math.abs(
|
||||
this.formGroup.value.additionalServiceAccounts * this.options.additionalServiceAccountPrice
|
||||
);
|
||||
}
|
||||
|
||||
get seatTotal(): number {
|
||||
get seatTotalCost(): number {
|
||||
return Math.abs(this.formGroup.value.seatCount * this.options.seatPrice);
|
||||
}
|
||||
|
||||
get maxServiceAccountTotal(): number {
|
||||
return Math.abs(
|
||||
(this.formGroup.value.maxAutoscaleServiceAccounts ?? 0) *
|
||||
this.options.additionalServiceAccountPrice
|
||||
);
|
||||
get maxAdditionalServiceAccounts(): number {
|
||||
const maxTotalServiceAccounts = this.formGroup.value.maxAutoscaleServiceAccounts ?? 0;
|
||||
return Math.max(0, maxTotalServiceAccounts - this.options.baseServiceAccountCount);
|
||||
}
|
||||
|
||||
get maxSeatTotal(): number {
|
||||
get maxServiceAccountTotalCost(): number {
|
||||
return this.maxAdditionalServiceAccounts * this.options.additionalServiceAccountPrice;
|
||||
}
|
||||
|
||||
get maxSeatTotalCost(): number {
|
||||
return Math.abs((this.formGroup.value.maxAutoscaleSeats ?? 0) * this.options.seatPrice);
|
||||
}
|
||||
|
||||
@@ -115,7 +117,7 @@ export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDest
|
||||
|
||||
if (value.limitServiceAccounts) {
|
||||
maxAutoscaleServiceAccountsControl.setValidators([
|
||||
Validators.min(value.additionalServiceAccounts),
|
||||
Validators.min(value.additionalServiceAccounts + this.options.baseServiceAccountCount),
|
||||
]);
|
||||
maxAutoscaleServiceAccountsControl.enable({ emitEvent: false });
|
||||
} else {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { OrganizationData } from "@bitwarden/common/admin-console/models/data/organization.data";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { SecretsManagerSubscribeRequest } from "@bitwarden/common/billing/models/request/sm-subscribe.request";
|
||||
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
|
||||
@@ -25,7 +27,8 @@ export class SecretsManagerSubscribeStandaloneComponent {
|
||||
private formBuilder: FormBuilder,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private organizationService: InternalOrganizationServiceAbstraction
|
||||
) {}
|
||||
|
||||
submit = async () => {
|
||||
@@ -37,7 +40,15 @@ export class SecretsManagerSubscribeStandaloneComponent {
|
||||
? this.formGroup.value.additionalServiceAccounts
|
||||
: 0;
|
||||
|
||||
await this.organizationApiService.subscribeToSecretsManager(this.organization.id, request);
|
||||
const profileOrganization = await this.organizationApiService.subscribeToSecretsManager(
|
||||
this.organization.id,
|
||||
request
|
||||
);
|
||||
const organizationData = new OrganizationData(profileOrganization, {
|
||||
isMember: this.organization.isMember,
|
||||
isProviderUser: this.organization.isProviderUser,
|
||||
});
|
||||
await this.organizationService.upsert(organizationData);
|
||||
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("subscriptionUpdated"));
|
||||
|
||||
|
||||
@@ -56,10 +56,13 @@
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "additionalServiceAccounts" | i18n }}</bit-label>
|
||||
<input bitInput formControlName="additionalServiceAccounts" type="number" />
|
||||
<bit-hint>{{
|
||||
"additionalServiceAccountsDesc"
|
||||
| i18n : serviceAccountsIncluded : (monthlyCostPerServiceAccount | currency : "$")
|
||||
}}</bit-hint>
|
||||
<bit-hint>
|
||||
{{ "includedServiceAccounts" | i18n : serviceAccountsIncluded }}
|
||||
{{
|
||||
"addAdditionalServiceAccounts"
|
||||
| i18n : (monthlyCostPerServiceAccount | currency : "$")
|
||||
}}
|
||||
</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -7027,15 +7027,20 @@
|
||||
"additionalServiceAccounts": {
|
||||
"message": "Additional service accounts"
|
||||
},
|
||||
"additionalServiceAccountsDesc": {
|
||||
"message": "Your plan comes with $COUNT$ service accounts. You can add additional service accounts for $COST$ per month.",
|
||||
"includedServiceAccounts": {
|
||||
"message": "Your plan comes with $COUNT$ service accounts.",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"content": "$1",
|
||||
"example": "50"
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"addAdditionalServiceAccounts": {
|
||||
"message": "You can add additional service accounts for $COST$ per month.",
|
||||
"placeholders": {
|
||||
"cost": {
|
||||
"content": "$2",
|
||||
"content": "$1",
|
||||
"example": "$0.50"
|
||||
}
|
||||
}
|
||||
@@ -7063,5 +7068,24 @@
|
||||
},
|
||||
"maxServiceAccountCost": {
|
||||
"message": "Max potential service account cost"
|
||||
},
|
||||
"smBetaEndedDesc": {
|
||||
"message": "The Secrets Manager Beta ended $BETA_ENDING_DATE$. You have $DAYS$ days left to add Secrets Manager to your paid subscription and maintain access to Secrets Manager data. Contact Customer Success to add Secrets Manager to your subscription.",
|
||||
"placeholders": {
|
||||
"beta_ending_date": {
|
||||
"content": "$1",
|
||||
"example": "August 1, 2023"
|
||||
},
|
||||
"days": {
|
||||
"content": "$2",
|
||||
"example": "11"
|
||||
}
|
||||
}
|
||||
},
|
||||
"betaEnding": {
|
||||
"message": "Beta Ending"
|
||||
},
|
||||
"beta": {
|
||||
"message": "Beta"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user