1
0
mirror of https://github.com/bitwarden/web synced 2025-12-06 00:03:28 +00:00

Compare commits

...

9 Commits

Author SHA1 Message Date
Vince Grassia
d10dc94a48 Remove old 'release' ref in workflow (#1328)
(cherry picked from commit 75984a2e37)
2021-12-07 22:52:00 -05:00
github-actions[bot]
c85051d6e2 Bumped version to 2.25.0 (#1327)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit 1cba6dc3b9)
2021-12-07 19:13:44 -08:00
Matt Gibson
778700f399 Fix families sponsorship redeem page (#1321)
* Display sponsorship warning when sponsoring an org

Move actions to drop down menu

Fix revoke cancel success popup

* Only show warning when sponsorship exists

(cherry picked from commit d9231ae3f3)
2021-12-01 20:48:44 -05:00
Justin Baur
23048d46d6 Fix basePrice to reflect the sponsorship (#1311)
* Fix basePrice to reflect the sponsorship

* Ran linter

* Add latest copy

* Remove unneeded if

* Fix times

* Stopped hardcoding basePrice

* Stopped hardcoding 40 in UI

* Switch to single small block

* Update jslib

* Revert "Update jslib"

This reverts commit 28534f2230.

* Revert "Remove unneeded if"

This reverts commit 5540b19998.

* Fix revert issue

(cherry picked from commit 4b856d9016)
2021-11-24 15:12:49 -06:00
Matt Gibson
e54586c7d2 Update jslib 2021-11-24 15:48:20 -05:00
Matt Gibson
494fc4b194 Fix formatting and title of sponsoring org drop down (#1317)
(cherry picked from commit 4029554658)
2021-11-24 15:48:00 -05:00
Matt Gibson
48b9393a48 Add sponsorship pre validate to families redeem page (#1315)
* Add sponsorship pre validate to families redeem page

* Update messaging

* update jslib

(cherry picked from commit 6ec22a9408)
2021-11-24 15:47:11 -05:00
Matt Gibson
b6b3184a7b Force sponsorship friendly name to recipient address (#1316)
(cherry picked from commit 9cc7dfb884)
2021-11-24 15:46:45 -05:00
Matt Gibson
66be24a1f5 Display sponsored status for sponsored org subscription (#1312)
* Display sponsored status for sponsored org subscription

* Linter fixes

(cherry picked from commit f8c943c042)
2021-11-24 15:46:23 -05:00
16 changed files with 99 additions and 72 deletions

View File

@@ -116,8 +116,6 @@ jobs:
- name: Checkout Repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
with:
ref: release
- name: Setup git config
run: |

2
jslib

Submodule jslib updated: b4f475251a...c65e7db6e0

6
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "bitwarden-web",
"version": "2.24.4",
"version": "2.25.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "bitwarden-web",
"version": "2.24.4",
"version": "2.25.0",
"hasInstallScript": true,
"license": "GPL-3.0",
"dependencies": {
@@ -20626,4 +20626,4 @@
"integrity": "sha1-KOwXzwl0PtyrBW3dixsGJizHPDA="
}
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "bitwarden-web",
"version": "2.24.4",
"version": "2.25.0",
"license": "GPL-3.0",
"repository": "https://github.com/bitwarden/web",
"scripts": {
@@ -85,4 +85,4 @@
"node": "~14",
"npm": "~7"
}
}
}

View File

@@ -4,7 +4,7 @@ import {
ViewContainerRef,
} from '@angular/core';
import {
import {
ActivatedRoute,
Router
} from '@angular/router';

View File

@@ -1,4 +1,4 @@
import {
import {
Component,
EventEmitter,
Output,

View File

@@ -32,7 +32,7 @@
<ng-container *ngIf="subscription">
<dt>{{'status' | i18n}}</dt>
<dd>
<span class="text-capitalize">{{subscription.status || '-'}}</span>
<span class="text-capitalize">{{isSponsoredSubscription ? 'sponsored' : subscription.status || '-'}}</span>
<span class="badge badge-warning"
*ngIf="subscriptionMarkedForCancel">{{'pendingCancellation' |
i18n}}</span>

View File

@@ -6,7 +6,10 @@
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'loading' | i18n}}</span>
</div>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate *ngIf="!loading">
<div *ngIf="!loading && badToken" class="mt-5 d-flex justify-content-center">
<span>{{'badToken' | i18n}}</span>
</div>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate *ngIf="!loading && !badToken">
<p>
<span>{{'acceptBitwardenFamiliesHelp' | i18n}}</span>
</p>

View File

@@ -40,7 +40,7 @@ import { OrganizationPlansComponent } from 'src/app/settings/organization-plans.
templateUrl: 'families-for-enterprise-setup.component.html',
})
export class FamiliesForEnterpriseSetupComponent implements OnInit {
@ViewChild(OrganizationPlansComponent, { static: false })
@ViewChild(OrganizationPlansComponent, { static: false })
set organizationPlansComponent(value: OrganizationPlansComponent) {
if (!value) {
return;
@@ -55,6 +55,7 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit {
@ViewChild('deleteOrganizationTemplate', { read: ViewContainerRef, static: true }) deleteModalRef: ViewContainerRef;
loading = true;
badToken = false;
formPromise: Promise<any>;
token: string;
@@ -89,6 +90,7 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit {
this.token = qParams.token;
await this.syncService.fullSync(true);
this.badToken = !await this.apiService.postPreValidateSponsorshipToken(this.token);
this.loading = false;
this.existingFamilyOrganizations = (await this.userService.getAllOrganizations())
@@ -109,7 +111,7 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit {
get selectedFamilyOrganizationId() {
return this._selectedFamilyOrganizationId;
}
set selectedFamilyOrganizationId(value: string) {
this._selectedFamilyOrganizationId = value;
this.showNewOrganization = value === 'createNew';

View File

@@ -92,7 +92,7 @@
</small>
</ng-template>
<span *ngIf="selectableProduct.product != productTypes.Free">
<ng-container *ngIf="selectableProduct.basePrice">
<ng-container *ngIf="selectableProduct.basePrice && !acceptingSponsorship">
{{selectableProduct.basePrice / 12 | currency:'$'}} /{{'month' | i18n}},
{{'includesXUsers' | i18n : selectableProduct.baseSeats}}
<ng-container *ngIf="selectableProduct.hasAdditionalSeatsOption">
@@ -162,8 +162,14 @@
{{'basePrice' | i18n}}: {{ selectablePlan.basePrice / 12 | currency:'$'}} &times; 12
{{'monthAbbr' | i18n}}
=
{{selectablePlan.basePrice | currency:'$'}}
/{{'year' | i18n}}
<ng-container *ngIf="acceptingSponsorship; else notAcceptingSponsorship">
<span style="text-decoration: line-through;">{{selectablePlan.basePrice | currency:'$'}}</span>
{{'freeWithSponsorship' | i18n}}
</ng-container>
<ng-template #notAcceptingSponsorship>
{{selectablePlan.basePrice | currency:'$'}}
/{{'year' | i18n}}
</ng-template>
</small>
<small *ngIf="selectablePlan.hasAdditionalSeatsOption">
<span *ngIf="selectablePlan.baseSeats">{{'additionalUsers' | i18n}}:</span>

View File

@@ -68,6 +68,7 @@ export class OrganizationPlansComponent implements OnInit {
productTypes = ProductType;
formPromise: Promise<any>;
singleOrgPolicyBlock: boolean = false;
discount = 0;
plans: PlanResponse[];
@@ -120,14 +121,18 @@ export class OrganizationPlansComponent implements OnInit {
validPlans = validPlans.filter(plan => plan.product !== ProductType.Free);
}
if (this.acceptingSponsorship) {
validPlans = validPlans.filter(plan => plan.product === ProductType.Families);
}
validPlans = validPlans
.filter(plan => !plan.legacyYear
&& !plan.disabled
&& (plan.isAnnual || plan.product === this.productTypes.Free));
.filter(plan => !plan.legacyYear
&& !plan.disabled
&& (plan.isAnnual || plan.product === this.productTypes.Free));
if (this.acceptingSponsorship) {
const familyPlan = this.plans.find(plan => plan.type === PlanType.FamiliesAnnually);
this.discount = familyPlan.basePrice;
validPlans = [
familyPlan,
];
}
return validPlans;
}
@@ -177,7 +182,7 @@ export class OrganizationPlansComponent implements OnInit {
if (this.selectedPlan.hasPremiumAccessOption && this.premiumAccessAddon) {
subTotal += this.selectedPlan.premiumAccessOptionPrice;
}
return subTotal;
return subTotal - this.discount;
}
get freeTrial() {

View File

@@ -17,43 +17,41 @@
</ul>
</div>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate *ngIf="anyOrgsAvailable">
<div *ngIf="moreThanOneOrgAvailable" class="form-group col-6">
<label for="availableSponsorshipOrg">{{ 'sponsoredFamiliesSelectOffer' | i18n}}</label>
<div *ngIf="moreThanOneOrgAvailable" class="form-group col-7">
<label for="availableSponsorshipOrg">{{ 'familiesSponsoringOrgSelect' | i18n}}</label>
<select id="availableSponsorshipOrg" name="Available Sponsorship Organization"
[(ngModel)]="selectedSponsorshipOrgId" class="form-control" required>
<option value="">-- {{'select' | i18n}} --</option>
<option *ngFor="let o of availableSponsorshipOrgs" [ngValue]="o.id">{{o.name}}</option>
</select>
<small>{{'sponsoredFamiliesLeaveCopy' | i18n}}</small>
</div>
<div class="form-group col-6">
<div class="form-group col-7">
<label for="accountEmail">{{'sponsoredFamiliesEmail' | i18n}}:</label>
<input id="accountEmail" class="form-control" inputmode="email" [(ngModel)]="sponsorshipEmail"
name="sponsorshipEmail" required>
</div>
<div class="form-group col-6">
<label for="friendlyName">{{'friendlyName' | i18n}}:</label>
<input id="friendlyName" class="form-control" [(ngModel)]="friendlyName" name="friendlyName" required>
<button class="btn btn-primary btn-submit mt-4" type="submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'redeem' | i18n}}</span>
</button>
</div>
</form>
<div *ngIf="anyActiveSponsorships">
<table class="table table-hover table-list">
<thead>
<tr>
<th>{{'friendlyName' | i18n}}</th>
<th>{{'sponsoringOrg' | i18n}}</th>
<th></th>
</tr>
</thead>
<tbody>
<ng-container *ngFor="let o of activeSponsorshipOrgs">
<tr sponsoring-org-row [sponsoringOrg]="o" (sponsorshipRemoved)="load(true)"></tr>
</ng-container>
</tbody>
</table>
</div>
<ng-container *ngIf="anyActiveSponsorships">
<div class="border-bottom">
<table class="table table-hover table-list">
<thead>
<tr>
<th>{{'recipient' | i18n}}</th>
<th>{{'sponsoringOrg' | i18n}}</th>
<th></th>
</tr>
</thead>
<tbody>
<ng-container *ngFor="let o of activeSponsorshipOrgs">
<tr sponsoring-org-row [sponsoringOrg]="o" (sponsorshipRemoved)="load(true)"></tr>
</ng-container>
</tbody>
</table>
</div>
<small>{{'sponsoredFamiliesLeaveCopy' | i18n}}</small>
</ng-container>
</ng-container>

View File

@@ -21,7 +21,6 @@ export class SponsoredFamiliesComponent implements OnInit {
activeSponsorshipOrgs: Organization[] = [];
selectedSponsorshipOrgId: string = '';
sponsorshipEmail: string = '';
friendlyName: string = '';
// Conditional display properties
formPromise: Promise<any>;
@@ -38,7 +37,7 @@ export class SponsoredFamiliesComponent implements OnInit {
this.formPromise = this.apiService.postCreateSponsorship(this.selectedSponsorshipOrgId, {
sponsoredEmail: this.sponsorshipEmail,
planSponsorshipType: PlanSponsorshipType.FamiliesForEnterprise,
friendlyName: this.friendlyName,
friendlyName: this.sponsorshipEmail,
});
await this.formPromise;
@@ -72,7 +71,6 @@ export class SponsoredFamiliesComponent implements OnInit {
private async resetForm() {
this.sponsorshipEmail = '';
this.friendlyName = '';
this.selectedSponsorshipOrgId = '';
}

View File

@@ -3,16 +3,24 @@
</td>
<td>{{sponsoringOrg.name}}</td>
<td class="table-action-right">
<button #resendEmailBtn [appApiAction]="resendEmailPromise" class="btn btn-outline-primary btn-submit"
[disabled]="resendEmailBtn.loading" (click)="resendEmail()"
[attr.aria-label]="'resendEmailLabel' | i18n : sponsoringOrg.familySponsorshipFriendlyName">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'resendEmail' | i18n }}</span>
</button>
<button #revokeSponsorshipBtn [appApiAction]="revokeSponsorshipPromise" class="btn btn-outline-danger btn-submit"
[disabled]="revokeSponsorshipBtn.loading" (click)="revokeSponsorship()"
[attr.aria-label]="'revokeAccount' | i18n : sponsoringOrg.familySponsorshipFriendlyName">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'remove' | i18n}}</span>
</button>
<div class="dropdown" appListDropdown>
<button class="btn btn-outline-secondary dropdown-toggle" type="button" id="dropdownMenuButton"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" appA11yTitle="{{'options' | i18n}}">
<i class="fa fa-cog fa-lg" aria-hidden="true"></i>
</button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
<button #resendEmailBtn [appApiAction]="resendEmailPromise" class="dropdown-item btn-submit"
[disabled]="resendEmailBtn.loading" (click)="resendEmail()"
[attr.aria-label]="'resendEmailLabel' | i18n : sponsoringOrg.familySponsorshipFriendlyName">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'resendEmail' | i18n }}</span>
</button>
<button #revokeSponsorshipBtn [appApiAction]="revokeSponsorshipPromise" class="dropdown-item text-danger btn-submit"
[disabled]="revokeSponsorshipBtn.loading" (click)="revokeSponsorship()"
[attr.aria-label]="'revokeAccount' | i18n : sponsoringOrg.familySponsorshipFriendlyName">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'remove' | i18n}}</span>
</button>
</div>
</div>
</td>

View File

@@ -32,8 +32,6 @@ export class SponsoringOrgRowComponent {
try {
this.revokeSponsorshipPromise = this.doRevokeSponsorship();
await this.revokeSponsorshipPromise;
this.toasterService.popAsync('success', null, this.i18nService.t('reclaimedFreePlan'));
this.sponsorshipRemoved.emit();
} catch (e) {
this.logService.error(e);
}
@@ -50,7 +48,7 @@ export class SponsoringOrgRowComponent {
private async doRevokeSponsorship() {
const isConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('revokeSponsorshipConfirmation'),
this.i18nService.t('revokeSponsorshipConfirmation'),
`${this.i18nService.t('remove')} ${this.sponsoringOrg.familySponsorshipFriendlyName}?`,
this.i18nService.t('remove'), this.i18nService.t('cancel'), 'warning');
@@ -59,5 +57,7 @@ export class SponsoringOrgRowComponent {
}
await this.apiService.deleteRevokeSponsorship(this.sponsoringOrg.id);
this.toasterService.popAsync('success', null, this.i18nService.t('reclaimedFreePlan'));
this.sponsorshipRemoved.emit();
}
}

View File

@@ -4506,6 +4506,9 @@
"sponsoredFamiliesSharedCollections": {
"message": "Shared collections for Family secrets"
},
"badToken": {
"message": "The link is no longer valid. Please have the sponsor resend the offer."
},
"reclaimedFreePlan": {
"message": "Reclaimed free plan"
},
@@ -4515,11 +4518,14 @@
"sponsoredFamiliesSelectOffer": {
"message": "Select the organization you would like sponsored"
},
"familiesSponsoringOrgSelect": {
"message": "Which Free Families offer would you like to redeem?"
},
"sponsoredFamiliesEmail": {
"message": "Enter your personal email to redeem Bitwarden Families"
},
"sponsoredFamiliesLeaveCopy": {
"message": "If you leave or are removed from this organization, your Families plan will expire at the end of the billing period."
"message": "If you leave or are removed from the sponsoring organization, your Families plan will expire at the end of the billing period."
},
"acceptBitwardenFamiliesHelp": {
"message": "Accept offer for an existing organization or create a new Families organization."
@@ -4540,7 +4546,7 @@
}
},
"sponsoredFamiliesOffer": {
"message": "Redeem Free Bitwarden Families Organization Offer"
"message": "Accept Free Bitwarden Families"
},
"sponsoredFamiliesOfferRedeemed": {
"message": "Free Bitwarden Families offer successfully redeemed"
@@ -4575,8 +4581,8 @@
"redeemNow": {
"message": "Redeem Now"
},
"friendlyName": {
"message": "Friendly Name"
"recipient": {
"message": "Recipient"
},
"removeSponsorship": {
"message": "Remove Sponsorship"
@@ -4702,7 +4708,7 @@
"message": "Please provide a payment method to associate with the organization. Don't worry, we won't charge you anything unless you select additional features or your sponsorship expires. "
},
"orgCreatedSponsorshipInvalid": {
"message": "The sponsorship offer has expired you may delete the organization you created to avoid a charge at the end of your 7 day trial. Otherwise you may close this prompt to keep the organization and assume billing responsibility."
"message": "The sponsorship offer has expired. You may delete the organization you created to avoid a charge at the end of your 7 day trial. Otherwise you may close this prompt to keep the organization and assume billing responsibility."
},
"newFamiliesOrganization": {
"message": "New Families Organization"
@@ -4724,5 +4730,8 @@
},
"sponsorshipTokenHasExpired": {
"message": "The sponsorship offer has expired."
},
"freeWithSponsorship": {
"message": "FREE with sponsorship"
}
}