satellite/console,web/satellite: configure whether free user can invite
This change adds a flag to the satellite config indicating whether free tier users should be able to send project invitations. Change-Id: I9c030c88dbef136ba4a9bf2d8f027a8dcd77fd33
This commit is contained in:
parent
e5fd061e70
commit
a6222afdd0
@ -22,6 +22,7 @@ type Config struct {
|
|||||||
FailedLoginPenalty float64 `help:"incremental duration of penalty for failed login attempts in minutes" default:"2.0"`
|
FailedLoginPenalty float64 `help:"incremental duration of penalty for failed login attempts in minutes" default:"2.0"`
|
||||||
ProjectInvitationExpiration time.Duration `help:"duration that project member invitations are valid for" default:"168h"`
|
ProjectInvitationExpiration time.Duration `help:"duration that project member invitations are valid for" default:"168h"`
|
||||||
UnregisteredInviteEmailsEnabled bool `help:"indicates whether invitation emails can be sent to unregistered email addresses" default:"false"`
|
UnregisteredInviteEmailsEnabled bool `help:"indicates whether invitation emails can be sent to unregistered email addresses" default:"false"`
|
||||||
|
FreeTierInvitesEnabled bool `help:"indicates whether free tier users can send project invitations" default:"false"`
|
||||||
UserBalanceForUpgrade int64 `help:"amount of base units of US micro dollars needed to upgrade user's tier status" default:"10000000"`
|
UserBalanceForUpgrade int64 `help:"amount of base units of US micro dollars needed to upgrade user's tier status" default:"10000000"`
|
||||||
PlacementEdgeURLOverrides PlacementEdgeURLOverrides `help:"placement-specific edge service URL overrides in the format {\"placementID\": {\"authService\": \"...\", \"publicLinksharing\": \"...\", \"internalLinksharing\": \"...\"}, \"placementID2\": ...}"`
|
PlacementEdgeURLOverrides PlacementEdgeURLOverrides `help:"placement-specific edge service URL overrides in the format {\"placementID\": {\"authService\": \"...\", \"publicLinksharing\": \"...\", \"internalLinksharing\": \"...\"}, \"placementID2\": ...}"`
|
||||||
BlockExplorerURL string `help:"url of the transaction block explorer" default:"https://etherscan.io/"`
|
BlockExplorerURL string `help:"url of the transaction block explorer" default:"https://etherscan.io/"`
|
||||||
|
@ -52,6 +52,7 @@ type FrontendConfig struct {
|
|||||||
ObjectBrowserPaginationEnabled bool `json:"objectBrowserPaginationEnabled"`
|
ObjectBrowserPaginationEnabled bool `json:"objectBrowserPaginationEnabled"`
|
||||||
BillingFeaturesEnabled bool `json:"billingFeaturesEnabled"`
|
BillingFeaturesEnabled bool `json:"billingFeaturesEnabled"`
|
||||||
UnregisteredInviteEmailsEnabled bool `json:"unregisteredInviteEmailsEnabled"`
|
UnregisteredInviteEmailsEnabled bool `json:"unregisteredInviteEmailsEnabled"`
|
||||||
|
FreeTierInvitesEnabled bool `json:"freeTierInvitesEnabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Satellites is a configuration value that contains a list of satellite names and addresses.
|
// Satellites is a configuration value that contains a list of satellite names and addresses.
|
||||||
|
@ -753,6 +753,7 @@ func (server *Server) frontendConfigHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
ObjectBrowserPaginationEnabled: server.config.ObjectBrowserPaginationEnabled,
|
ObjectBrowserPaginationEnabled: server.config.ObjectBrowserPaginationEnabled,
|
||||||
BillingFeaturesEnabled: server.config.BillingFeaturesEnabled,
|
BillingFeaturesEnabled: server.config.BillingFeaturesEnabled,
|
||||||
UnregisteredInviteEmailsEnabled: server.config.UnregisteredInviteEmailsEnabled,
|
UnregisteredInviteEmailsEnabled: server.config.UnregisteredInviteEmailsEnabled,
|
||||||
|
FreeTierInvitesEnabled: server.config.FreeTierInvitesEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := json.NewEncoder(w).Encode(&cfg)
|
err := json.NewEncoder(w).Encode(&cfg)
|
||||||
|
@ -3775,7 +3775,7 @@ func (s *Service) inviteProjectMembers(ctx context.Context, sender *User, projec
|
|||||||
}
|
}
|
||||||
projectID = isMember.project.ID
|
projectID = isMember.project.ID
|
||||||
|
|
||||||
if !sender.PaidTier {
|
if !(s.config.FreeTierInvitesEnabled || sender.PaidTier) {
|
||||||
return nil, ErrNotPaidTier.New(paidTierInviteErrMsg)
|
return nil, ErrNotPaidTier.New(paidTierInviteErrMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
3
scripts/testdata/satellite-config.yaml.lock
vendored
3
scripts/testdata/satellite-config.yaml.lock
vendored
@ -280,6 +280,9 @@ compensation.withheld-percents: 75,75,75,50,50,50,25,25,25,0,0,0,0,0,0
|
|||||||
# allow domains to embed the satellite in a frame, space separated
|
# allow domains to embed the satellite in a frame, space separated
|
||||||
# console.frame-ancestors: tardigrade.io storj.io
|
# console.frame-ancestors: tardigrade.io storj.io
|
||||||
|
|
||||||
|
# indicates whether free tier users can send project invitations
|
||||||
|
# console.free-tier-invites-enabled: false
|
||||||
|
|
||||||
# server address of the front-end app
|
# server address of the front-end app
|
||||||
# console.frontend-address: :10200
|
# console.frontend-address: :10200
|
||||||
|
|
||||||
|
@ -8,21 +8,21 @@
|
|||||||
<div class="modal__header">
|
<div class="modal__header">
|
||||||
<TeamMembersIcon />
|
<TeamMembersIcon />
|
||||||
<h1 class="modal__header__title">
|
<h1 class="modal__header__title">
|
||||||
{{ isPaidTier ? 'Invite team member' : 'Upgrade to Pro' }}
|
{{ needsUpgrade ? 'Upgrade to Pro' : 'Invite team member' }}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="modal__info">
|
<p class="modal__info">
|
||||||
<template v-if="isPaidTier">
|
<template v-if="needsUpgrade">
|
||||||
Add a team member to contribute to this project.
|
Upgrade now to unlock collaboration and bring your team together in this project.
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
Upgrade now to unlock collaboration and bring your team together in this project.
|
Add a team member to contribute to this project.
|
||||||
</template>
|
</template>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<VInput
|
<VInput
|
||||||
v-if="isPaidTier"
|
v-if="!needsUpgrade"
|
||||||
class="modal__input"
|
class="modal__input"
|
||||||
label="Email"
|
label="Email"
|
||||||
height="38px"
|
height="38px"
|
||||||
@ -43,14 +43,14 @@
|
|||||||
:on-press="closeModal"
|
:on-press="closeModal"
|
||||||
/>
|
/>
|
||||||
<VButton
|
<VButton
|
||||||
:label="isPaidTier ? 'Invite' : 'Upgrade'"
|
:label="needsUpgrade ? 'Upgrade' : 'Invite'"
|
||||||
height="48px"
|
height="48px"
|
||||||
font-size="14px"
|
font-size="14px"
|
||||||
border-radius="10px"
|
border-radius="10px"
|
||||||
:on-press="onPrimaryClick"
|
:on-press="onPrimaryClick"
|
||||||
:is-disabled="!!formError || isLoading"
|
:is-disabled="!!formError || isLoading"
|
||||||
>
|
>
|
||||||
<template v-if="!isPaidTier" #icon-right>
|
<template v-if="needsUpgrade" #icon-right>
|
||||||
<ArrowIcon />
|
<ArrowIcon />
|
||||||
</template>
|
</template>
|
||||||
</VButton>
|
</VButton>
|
||||||
@ -101,7 +101,7 @@ const email = ref<string>('');
|
|||||||
* or a message describing the validation error.
|
* or a message describing the validation error.
|
||||||
*/
|
*/
|
||||||
const formError = computed<string | boolean>(() => {
|
const formError = computed<string | boolean>(() => {
|
||||||
if (!isPaidTier.value) return false;
|
if (needsUpgrade.value) return false;
|
||||||
if (!email.value) return true;
|
if (!email.value) return true;
|
||||||
if (email.value.toLocaleLowerCase() === usersStore.state.user.email.toLowerCase()) {
|
if (email.value.toLocaleLowerCase() === usersStore.state.user.email.toLowerCase()) {
|
||||||
return `You can't add yourself to the project.`;
|
return `You can't add yourself to the project.`;
|
||||||
@ -113,17 +113,17 @@ const formError = computed<string | boolean>(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns user's paid tier status from store.
|
* Returns whether the user should upgrade to pro tier before inviting.
|
||||||
*/
|
*/
|
||||||
const isPaidTier = computed<boolean>(() => {
|
const needsUpgrade = computed<boolean>(() => {
|
||||||
return usersStore.state.user.paidTier;
|
return !(usersStore.state.user.paidTier || configStore.state.config.freeTierInvitesEnabled);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles primary button click.
|
* Handles primary button click.
|
||||||
*/
|
*/
|
||||||
async function onPrimaryClick(): Promise<void> {
|
async function onPrimaryClick(): Promise<void> {
|
||||||
if (!isPaidTier.value) {
|
if (needsUpgrade.value) {
|
||||||
appStore.updateActiveModal(MODALS.upgradeAccount);
|
appStore.updateActiveModal(MODALS.upgradeAccount);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,7 @@ export class FrontendConfig {
|
|||||||
objectBrowserPaginationEnabled: boolean;
|
objectBrowserPaginationEnabled: boolean;
|
||||||
billingFeaturesEnabled: boolean;
|
billingFeaturesEnabled: boolean;
|
||||||
unregisteredInviteEmailsEnabled: boolean;
|
unregisteredInviteEmailsEnabled: boolean;
|
||||||
|
freeTierInvitesEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MultiCaptchaConfig {
|
export class MultiCaptchaConfig {
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// See LICENSE for copying information.
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<v-overlay v-model="model" persistent />
|
||||||
|
|
||||||
<v-dialog
|
<v-dialog
|
||||||
:model-value="model && !isUpgradeDialogShown"
|
:model-value="model && !isUpgradeDialogShown"
|
||||||
width="auto"
|
width="auto"
|
||||||
@ -18,7 +20,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<v-card-title class="font-weight-bold">
|
<v-card-title class="font-weight-bold">
|
||||||
{{ isPaidTier ? 'Add Member' : 'Upgrade to Pro' }}
|
{{ needsUpgrade ? 'Upgrade to Pro' : 'Add Member' }}
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
|
|
||||||
<template #append>
|
<template #append>
|
||||||
@ -37,7 +39,10 @@
|
|||||||
|
|
||||||
<v-form v-model="valid" class="pa-7 pb-4" @submit.prevent="onPrimaryClick">
|
<v-form v-model="valid" class="pa-7 pb-4" @submit.prevent="onPrimaryClick">
|
||||||
<v-row>
|
<v-row>
|
||||||
<template v-if="isPaidTier">
|
<v-col v-if="needsUpgrade">
|
||||||
|
Upgrade now to unlock collaboration and bring your team together in this project.
|
||||||
|
</v-col>
|
||||||
|
<template v-else>
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
<p class="mb-5">Invite a team member to join you in this project.</p>
|
<p class="mb-5">Invite a team member to join you in this project.</p>
|
||||||
<v-alert
|
<v-alert
|
||||||
@ -63,9 +68,6 @@
|
|||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
</template>
|
</template>
|
||||||
<v-col v-else>
|
|
||||||
Upgrade now to unlock collaboration and bring your team together in this project.
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-form>
|
</v-form>
|
||||||
|
|
||||||
@ -82,10 +84,10 @@
|
|||||||
variant="flat"
|
variant="flat"
|
||||||
block
|
block
|
||||||
:loading="isLoading"
|
:loading="isLoading"
|
||||||
:append-icon="!isPaidTier ? 'mdi-arrow-right' : undefined"
|
:append-icon="needsUpgrade ? 'mdi-arrow-right' : undefined"
|
||||||
@click="onPrimaryClick"
|
@click="onPrimaryClick"
|
||||||
>
|
>
|
||||||
{{ isPaidTier ? 'Send Invite' : 'Upgrade' }}
|
{{ needsUpgrade ? 'Upgrade' : 'Send Invite' }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@ -98,12 +100,6 @@
|
|||||||
:model-value="model && isUpgradeDialogShown"
|
:model-value="model && isUpgradeDialogShown"
|
||||||
@update:model-value="v => model = isUpgradeDialogShown = v"
|
@update:model-value="v => model = isUpgradeDialogShown = v"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<teleport to="body">
|
|
||||||
<v-fade-transition>
|
|
||||||
<div v-show="model" class="v-overlay__scrim custom-scrim" />
|
|
||||||
</v-fade-transition>
|
|
||||||
</teleport>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -121,7 +117,7 @@ import {
|
|||||||
VAlert,
|
VAlert,
|
||||||
VTextField,
|
VTextField,
|
||||||
VCardActions,
|
VCardActions,
|
||||||
VFadeTransition,
|
VOverlay,
|
||||||
} from 'vuetify/components';
|
} from 'vuetify/components';
|
||||||
|
|
||||||
import { RequiredRule, ValidationRule } from '@poc/types/common';
|
import { RequiredRule, ValidationRule } from '@poc/types/common';
|
||||||
@ -167,17 +163,17 @@ const emailRules: ValidationRule<string>[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns user's paid tier status from store.
|
* Returns whether the user should upgrade to pro tier before inviting.
|
||||||
*/
|
*/
|
||||||
const isPaidTier = computed<boolean>(() => {
|
const needsUpgrade = computed<boolean>(() => {
|
||||||
return usersStore.state.user.paidTier;
|
return !(usersStore.state.user.paidTier || configStore.state.config.freeTierInvitesEnabled);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles primary button click.
|
* Handles primary button click.
|
||||||
*/
|
*/
|
||||||
async function onPrimaryClick(): Promise<void> {
|
async function onPrimaryClick(): Promise<void> {
|
||||||
if (!isPaidTier.value) {
|
if (needsUpgrade.value) {
|
||||||
isUpgradeDialogShown.value = true;
|
isUpgradeDialogShown.value = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// See LICENSE for copying information.
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<v-overlay v-model="model" persistent />
|
||||||
|
|
||||||
<v-dialog
|
<v-dialog
|
||||||
:model-value="model && !isUpgradeDialogShown"
|
:model-value="model && !isUpgradeDialogShown"
|
||||||
width="410px"
|
width="410px"
|
||||||
@ -113,16 +115,10 @@
|
|||||||
:model-value="model && isUpgradeDialogShown"
|
:model-value="model && isUpgradeDialogShown"
|
||||||
@update:model-value="v => model = isUpgradeDialogShown = v"
|
@update:model-value="v => model = isUpgradeDialogShown = v"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<teleport to="body">
|
|
||||||
<v-fade-transition>
|
|
||||||
<div v-show="model" class="v-overlay__scrim custom-scrim" />
|
|
||||||
</v-fade-transition>
|
|
||||||
</teleport>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch, Teleport } from 'vue';
|
import { ref, computed, watch } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import {
|
import {
|
||||||
VDialog,
|
VDialog,
|
||||||
@ -136,7 +132,7 @@ import {
|
|||||||
VRow,
|
VRow,
|
||||||
VCol,
|
VCol,
|
||||||
VTextField,
|
VTextField,
|
||||||
VFadeTransition,
|
VOverlay,
|
||||||
} from 'vuetify/components';
|
} from 'vuetify/components';
|
||||||
|
|
||||||
import { RequiredRule, ValidationRule } from '@poc/types/common';
|
import { RequiredRule, ValidationRule } from '@poc/types/common';
|
||||||
|
@ -122,10 +122,6 @@ html {
|
|||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-scrim {
|
|
||||||
z-index: 2000;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Align the checkboxes in the tables
|
// Align the checkboxes in the tables
|
||||||
.v-selection-control {
|
.v-selection-control {
|
||||||
contain: inherit;
|
contain: inherit;
|
||||||
|
Loading…
Reference in New Issue
Block a user