Tweak subscriptions some more
This commit is contained in:
@@ -1,353 +0,0 @@
|
|||||||
<script>
|
|
||||||
import Tooltip from "layout/Tooltip.svelte";
|
|
||||||
import Euro from "util/Euro.svelte";
|
|
||||||
import OtherPlans from "layout/checkout/SubscriptionPlans.svelte";
|
|
||||||
import { user } from "lib/UserStore";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<p>
|
|
||||||
Nova features two different payment modes. We offer a monthly
|
|
||||||
subscription which is managed by Patreon, and a Prepaid subscription
|
|
||||||
which supports a dozen different payment providers. With Prepaid you pay
|
|
||||||
in advance to charge your credit, then usage will be subtracted from
|
|
||||||
your account credit. For low usage Prepaid is usually better as the
|
|
||||||
monthly fee is much lower.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="horizontal_scroll">
|
|
||||||
<div class="grid">
|
|
||||||
<div></div>
|
|
||||||
<div class="top_row free_feat">
|
|
||||||
<span class="bold">Free</span>
|
|
||||||
</div>
|
|
||||||
<div class="top_row pro_feat">
|
|
||||||
<span class="bold">Pro</span>
|
|
||||||
</div>
|
|
||||||
<div class="top_row prepaid_feat">
|
|
||||||
<span class="bold">Prepaid</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="left_col">
|
|
||||||
Recurring price
|
|
||||||
</div>
|
|
||||||
<div class="feature_cell free_feat">
|
|
||||||
<span class="bold">None</span>
|
|
||||||
</div>
|
|
||||||
<div class="feature_cell pro_feat">
|
|
||||||
<Tooltip>
|
|
||||||
{#snippet label()}
|
|
||||||
<span>
|
|
||||||
<span class="bold">€4 / month</span> or
|
|
||||||
<span class="bold">€40 / year</span>
|
|
||||||
</span>
|
|
||||||
{/snippet}
|
|
||||||
The Pro subscription is managed by Patreon. Patreon's own fees
|
|
||||||
and sales tax will be added to this price. After paying you need
|
|
||||||
to link your Nova account to Patreon to activate the plan.
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
<div class="feature_cell prepaid_feat">
|
|
||||||
<Tooltip>
|
|
||||||
{#snippet label()}
|
|
||||||
<span class="bold">€1 / month minimum</span>
|
|
||||||
{/snippet}
|
|
||||||
<p>
|
|
||||||
The minimum fee is only charged when usage is less than €1.
|
|
||||||
This calculation is per day, the €1 amount is divided by the
|
|
||||||
number of days in the month and if your usage is lower than
|
|
||||||
that, you will be charged the minimum fee.
|
|
||||||
</p>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="left_col">
|
|
||||||
<a href="/about#toc_3">Transfer limit</a>
|
|
||||||
</div>
|
|
||||||
<div class="feature_cell free_feat">
|
|
||||||
<Tooltip>
|
|
||||||
{#snippet label()}
|
|
||||||
<span class="bold">6 GB per day</span>
|
|
||||||
{/snippet}
|
|
||||||
<p>
|
|
||||||
Free users are limited to downloading 6 GB per day, this
|
|
||||||
limit is linked to your IP address, even if you are logged
|
|
||||||
in to an account.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
When the download limit is exceeded the download speed is
|
|
||||||
reduced to 1 MiB/s. You can have at most three concurrent
|
|
||||||
downloads running.
|
|
||||||
</p>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
<div class="feature_cell pro_feat">
|
|
||||||
<Tooltip>
|
|
||||||
{#snippet label()}
|
|
||||||
<span class="bold">4 TB per 30 days</span>
|
|
||||||
{/snippet}
|
|
||||||
<p>
|
|
||||||
The transfer limit is used for downloading, sharing and
|
|
||||||
hotlinking files.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Paid plans do not have a concurrent connection limit.
|
|
||||||
</p>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
<div class="feature_cell prepaid_feat">
|
|
||||||
<Tooltip>
|
|
||||||
{#snippet label()}
|
|
||||||
<span class="bold">€1 per TB transferred</span>
|
|
||||||
{/snippet}
|
|
||||||
<p>
|
|
||||||
Prepaid does not have a transfer limit, instead you are
|
|
||||||
charged for what you use at a rate of €1 per terabyte
|
|
||||||
downloaded. This applies to downloading your own files,
|
|
||||||
other people's files and others downloading your files (if
|
|
||||||
you have hotlinking enabled in your account settings).
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Paid plans do not have a concurrent connection limit.
|
|
||||||
</p>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="left_col">
|
|
||||||
<a href="/about#toc_6">Hotlinking</a>
|
|
||||||
</div>
|
|
||||||
<div class="feature_cell free_feat">
|
|
||||||
<span class="bold">Hotlinking not supported</span><br/>
|
|
||||||
Hotlinked files get blocked
|
|
||||||
</div>
|
|
||||||
<div class="feature_cell span2 right pro_feat">
|
|
||||||
<span class="bold">Hotlinking supported</span><br/>
|
|
||||||
Hotlinking uses your transfer limit
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="left_col">
|
|
||||||
Storage
|
|
||||||
</div>
|
|
||||||
<div class="feature_cell span2 left pro_feat">
|
|
||||||
<span class="bold">No limit</span><br/>
|
|
||||||
Files expire when they are not downloaded
|
|
||||||
</div>
|
|
||||||
<div class="feature_cell prepaid_feat">
|
|
||||||
<span class="bold">€4 / TB / month</span><br/>
|
|
||||||
No limit, you pay for what you use
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="left_col">
|
|
||||||
<a href="/about#toc_1">File expiry</a>
|
|
||||||
</div>
|
|
||||||
<div class="feature_cell free_feat">
|
|
||||||
<Tooltip>
|
|
||||||
{#snippet label()}
|
|
||||||
<span class="bold">120 days (4 months)</span>
|
|
||||||
{/snippet}
|
|
||||||
<p>
|
|
||||||
Files expire when they have not been downloaded in the last
|
|
||||||
120 days. A download is counted when more than 10% of the
|
|
||||||
whole file was downloaded in a single request.
|
|
||||||
</p>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
<div class="feature_cell pro_feat">
|
|
||||||
<Tooltip>
|
|
||||||
{#snippet label()}
|
|
||||||
<span class="bold">240 days (8 months)</span>
|
|
||||||
{/snippet}
|
|
||||||
<p>
|
|
||||||
The Pro plan has 240 day file expiry. The same rules apply
|
|
||||||
as the free plan. Higher Patreon subscription plans are
|
|
||||||
available where files do not expire at all.
|
|
||||||
</p>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
<div class="feature_cell prepaid_feat">
|
|
||||||
<Tooltip>
|
|
||||||
{#snippet label()}
|
|
||||||
<span class="bold">Files do not expire</span>
|
|
||||||
{/snippet}
|
|
||||||
<p>
|
|
||||||
Files don't expire while your Prepaid plan is active. If
|
|
||||||
your credit runs out you have one week to top up your
|
|
||||||
balance. If you fail to do so your files will revert to the
|
|
||||||
regular file expiry rules.
|
|
||||||
</p>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="left_col">
|
|
||||||
Download page customization
|
|
||||||
</div>
|
|
||||||
<div class="feature_cell free_feat">
|
|
||||||
<span class="bold">None</span>
|
|
||||||
</div>
|
|
||||||
<div class="feature_cell pro_feat">
|
|
||||||
None
|
|
||||||
</div>
|
|
||||||
<div class="feature_cell prepaid_feat">
|
|
||||||
Theme, background, header and footer image can be customized
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="left_col">
|
|
||||||
Max file size
|
|
||||||
</div>
|
|
||||||
<div class="feature_cell free_feat">
|
|
||||||
<span class="bold">20 GB</span> per file
|
|
||||||
</div>
|
|
||||||
<div class="feature_cell span2 right pro_feat">
|
|
||||||
<span class="bold">100 GB</span> per file
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="left_col">
|
|
||||||
Payment processors
|
|
||||||
</div>
|
|
||||||
<div class="feature_cell free_feat">
|
|
||||||
<span class="bold">None</span>
|
|
||||||
</div>
|
|
||||||
<div class="feature_cell pro_feat">
|
|
||||||
<span class="bold">PayPal</span>, <span class="bold">Credit/debit</span>
|
|
||||||
</div>
|
|
||||||
<div class="feature_cell prepaid_feat">
|
|
||||||
<span class="bold">PayPal</span>,
|
|
||||||
<span class="bold">Bitcoin</span>,
|
|
||||||
<span class="bold">Litecoin</span>,
|
|
||||||
<span class="bold">Monero</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div></div>
|
|
||||||
<div class="bottom_row free_feat">
|
|
||||||
Free
|
|
||||||
</div>
|
|
||||||
<div class="bottom_row pro_feat">
|
|
||||||
{#if $user !== null && $user.subscription.id === "patreon_1"}
|
|
||||||
You have this plan<br/>
|
|
||||||
Thank you for supporting Nova!
|
|
||||||
{:else}
|
|
||||||
<a href="https://www.patreon.com/join/nova/checkout?rid=5736701&cadence=1" class="button button_highlight round">
|
|
||||||
€ 4 per month
|
|
||||||
</a>
|
|
||||||
or
|
|
||||||
<a href="https://www.patreon.com/join/nova/checkout?rid=5736701&cadence=12" class="button button_highlight round">
|
|
||||||
€ 40 per year!
|
|
||||||
</a>
|
|
||||||
<br/>
|
|
||||||
(Excluding tax)
|
|
||||||
<br/>
|
|
||||||
Subscription managed by Patreon
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div class="bottom_row prepaid_feat">
|
|
||||||
{#if $user === null}
|
|
||||||
<!-- User is not logged in -->
|
|
||||||
Account required<br/>
|
|
||||||
<a href="/login?redirect=/user/subscription/manage" class="button button_highlight round">
|
|
||||||
<i class="icon">login</i>
|
|
||||||
Log in
|
|
||||||
</a>
|
|
||||||
or
|
|
||||||
<a href="/register?redirect=/user/subscription/manage" class="button button_highlight round">
|
|
||||||
<i class="icon">how_to_reg</i>
|
|
||||||
Register
|
|
||||||
</a>
|
|
||||||
<br/>
|
|
||||||
Payments processed by Mollie<br/>
|
|
||||||
No recurring payments
|
|
||||||
{:else}
|
|
||||||
<!-- User is logged in -->
|
|
||||||
{#if $user.subscription.type === ""}
|
|
||||||
<a href="/user/subscription/manage#deposit" class="button button_highlight round">
|
|
||||||
Deposit credit
|
|
||||||
</a>
|
|
||||||
to activate Prepaid
|
|
||||||
{:else if $user.subscription.type === "patreon"}
|
|
||||||
Patreon subscription active. Prepaid cannot be activated
|
|
||||||
{:else if $user.subscription.type === "prepaid"}
|
|
||||||
Prepaid subscription is active<br/>
|
|
||||||
Current balance <Euro amount={$user.balance_micro_eur}/>
|
|
||||||
<br/>
|
|
||||||
<a href="/user/subscription/manage#deposit" class="button button_highlight round">
|
|
||||||
Top up my credit
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2>Other plans available on Patreon</h2>
|
|
||||||
<OtherPlans/>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.bold {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.horizontal_scroll {
|
|
||||||
overflow-x: scroll;
|
|
||||||
overflow-y: hidden;
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid {
|
|
||||||
display: grid;
|
|
||||||
grid-auto-flow: row;
|
|
||||||
grid-template-columns: 9em 1fr 1fr 1fr;
|
|
||||||
min-width: 50em;
|
|
||||||
max-width: 70em;
|
|
||||||
gap: 5px;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
.grid > div {
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
align-content: center;
|
|
||||||
text-align: center;
|
|
||||||
padding: 0.25em;
|
|
||||||
min-height: 3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_col {
|
|
||||||
border-top-left-radius: 6px;
|
|
||||||
border-bottom-left-radius: 6px;
|
|
||||||
border: 1px solid var(--separator);
|
|
||||||
}
|
|
||||||
.top_row {
|
|
||||||
border-top-left-radius: 6px;
|
|
||||||
border-top-right-radius: 6px;
|
|
||||||
}
|
|
||||||
.bottom_row {
|
|
||||||
border-bottom-left-radius: 6px;
|
|
||||||
border-bottom-right-radius: 6px;
|
|
||||||
border: 1px solid var(--separator);
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.feature_cell {
|
|
||||||
background-color: var(--card_color);
|
|
||||||
border: 1px solid var(--background_color);
|
|
||||||
}
|
|
||||||
.pro_feat {
|
|
||||||
border: 1px solid var(--accent_color);
|
|
||||||
}
|
|
||||||
.free_feat {
|
|
||||||
border: 1px solid #ebcb8b;
|
|
||||||
}
|
|
||||||
.prepaid_feat {
|
|
||||||
border: 1px solid #ec2cfa;
|
|
||||||
}
|
|
||||||
.span2 {
|
|
||||||
grid-column: span 2;
|
|
||||||
}
|
|
||||||
.span2.left {
|
|
||||||
border-image: linear-gradient(to right, #ebcb8b 0%, var(--accent_color) 100%) 1;
|
|
||||||
}
|
|
||||||
.span2.right {
|
|
||||||
border-image: linear-gradient(to right, var(--accent_color) 0%, #ec2cfa 100%) 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -208,6 +208,20 @@ export const subscription_periods: SubscriptionPeriod[] = [
|
|||||||
// same value the server divides the monthly fee by
|
// same value the server divides the monthly fee by
|
||||||
export const days_per_month = 365.25 / 12
|
export const days_per_month = 365.25 / 12
|
||||||
|
|
||||||
|
// The price of the subscription period the user is in. This is also how far
|
||||||
|
// the balance can go below zero before usage above the plan is refused
|
||||||
|
export const period_price = (u: User) => {
|
||||||
|
const period = subscription_periods.find(p => p.months === u.subscription_months)
|
||||||
|
return period === undefined ? 0 :
|
||||||
|
u.subscription.monthly_fee * period.months * period.discount
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whether usage above a limit of the plan is billed instead of refused. price
|
||||||
|
// is what the plan charges for that usage. The server does the same check in
|
||||||
|
// billsExtra
|
||||||
|
export const bills_extra = (u: User, price: number) =>
|
||||||
|
u.usage_based_billing && price > 0 && u.balance_micro_eur >= -period_price(u)
|
||||||
|
|
||||||
export const get_misc_subscriptions = async () => {
|
export const get_misc_subscriptions = async () => {
|
||||||
return await check_response(
|
return await check_response(
|
||||||
await fetch(get_endpoint() + "/misc/subscriptions")
|
await fetch(get_endpoint() + "/misc/subscriptions")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import ProgressBar from "util/ProgressBar.svelte";
|
import ProgressBar from "util/ProgressBar.svelte";
|
||||||
import { formatDataVolume, formatEuros } from "util/Formatting"
|
import { formatDataVolume, formatEuros } from "util/Formatting"
|
||||||
import { user } from "lib/UserStore";
|
import { user } from "lib/UserStore";
|
||||||
|
import { bills_extra } from "lib/NovaAPI";
|
||||||
|
|
||||||
let { total = 0, used = 0 }: {
|
let { total = 0, used = 0 }: {
|
||||||
total?: number;
|
total?: number;
|
||||||
@@ -10,8 +11,11 @@ let { total = 0, used = 0 }: {
|
|||||||
|
|
||||||
let frac = $derived(used / total)
|
let frac = $derived(used / total)
|
||||||
let bill_shock = $derived($user !== null && $user.monthly_transfer_cap === total)
|
let bill_shock = $derived($user !== null && $user.monthly_transfer_cap === total)
|
||||||
let billable = $derived($user !== null && $user.subscription.price_per_tb_bandwidth > 0)
|
let price = $derived($user === null ? 0 : $user.subscription.price_per_tb_bandwidth)
|
||||||
let billed = $derived(billable && $user.usage_based_billing)
|
let billed = $derived($user !== null && bills_extra($user, price))
|
||||||
|
|
||||||
|
// Usage based billing is on, but the balance is too far below zero for it
|
||||||
|
let overdrawn = $derived($user !== null && $user.usage_based_billing && price > 0 && !billed)
|
||||||
let included = $derived($user === null ? 0 : $user.subscription.monthly_transfer_cap)
|
let included = $derived($user === null ? 0 : $user.subscription.monthly_transfer_cap)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -50,6 +54,14 @@ let included = $derived($user === null ? 0 : $user.subscription.monthly_transfer
|
|||||||
This is your bill shock limit. <a href="/user/sharing">Increase
|
This is your bill shock limit. <a href="/user/sharing">Increase
|
||||||
or disable the limit</a> to continue sharing files.
|
or disable the limit</a> to continue sharing files.
|
||||||
</p>
|
</p>
|
||||||
|
{:else if overdrawn}
|
||||||
|
<p>
|
||||||
|
Your balance is too far below zero to bill more data transfer.
|
||||||
|
Top up your credit to continue sharing files.
|
||||||
|
</p>
|
||||||
|
<a class="button button_highlight" href="/user/subscription/manage">
|
||||||
|
<i class="icon">paid</i> Top up credit
|
||||||
|
</a>
|
||||||
{:else}
|
{:else}
|
||||||
<p>
|
<p>
|
||||||
Upgrade your plan or enable
|
Upgrade your plan or enable
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ import { onMount } from "svelte";
|
|||||||
import { formatDataVolume, formatEuros } from "util/Formatting";
|
import { formatDataVolume, formatEuros } from "util/Formatting";
|
||||||
import ProgressBar from "util/ProgressBar.svelte";
|
import ProgressBar from "util/ProgressBar.svelte";
|
||||||
import { loading_run } from "lib/Loading";
|
import { loading_run } from "lib/Loading";
|
||||||
import { get_endpoint, put_user } from 'lib/NovaAPI';
|
import { get_endpoint, period_price, put_user } from 'lib/NovaAPI';
|
||||||
import SuccessMessage from 'util/SuccessMessage.svelte';
|
import SuccessMessage from 'util/SuccessMessage.svelte';
|
||||||
import { user } from "lib/UserStore";
|
import { user } from "lib/UserStore";
|
||||||
|
|
||||||
let success_message: SuccessMessage = $state()
|
let success_message: SuccessMessage = $state()
|
||||||
|
|
||||||
let transfer_cap = $state($user === null ? 0 : $user.monthly_transfer_cap / 1e12)
|
let transfer_cap = $state($user === null ? 0 : $user.monthly_transfer_cap / 1e12)
|
||||||
|
|
||||||
const update_user = async (e: SubmitEvent) => {
|
const update_user = async (e: SubmitEvent) => {
|
||||||
@@ -99,6 +100,12 @@ onMount(async () => {
|
|||||||
{formatEuros($user.subscription.price_per_tb_bandwidth, 2)} per TB
|
{formatEuros($user.subscription.price_per_tb_bandwidth, 2)} per TB
|
||||||
for data transfer.
|
for data transfer.
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
This usage is charged from your account credit afterwards. When
|
||||||
|
your balance is more than {formatEuros(period_price($user), 2)} below zero
|
||||||
|
(the price of your subscription period) the limits of your plan
|
||||||
|
apply again, until you top up your credit.
|
||||||
|
</p>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" checked={$user.usage_based_billing} onchange={set_usage_based_billing}/>
|
<input type="checkbox" checked={$user.usage_based_billing} onchange={set_usage_based_billing}/>
|
||||||
Enable usage based billing
|
Enable usage based billing
|
||||||
@@ -125,10 +132,10 @@ onMount(async () => {
|
|||||||
<ProgressBar used={transfer_used} total={transfer_cap*1e12} warn></ProgressBar>
|
<ProgressBar used={transfer_used} total={transfer_cap*1e12} warn></ProgressBar>
|
||||||
<p>
|
<p>
|
||||||
The billshock limit limits how much bandwidth your account can use in a
|
The billshock limit limits how much bandwidth your account can use in a
|
||||||
30 day window. When this limit is reached hotlinking will be disabled
|
30 day window. When this limit is reached your files can't be downloaded
|
||||||
and you will no longer be charged for bandwidth usage. This is mostly
|
until your usage drops below it, and you will no longer be charged for
|
||||||
useful for the Prepaid subscription, but it works for Patreon plans too.
|
bandwidth usage. This is mostly useful with usage based billing, but it
|
||||||
Set to 0 to disable the limit.
|
works without it too. Set to 0 to disable the limit.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Embedding controls</h2>
|
<h2>Embedding controls</h2>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { formatDataVolume, formatEuros } from "util/Formatting"
|
import { formatDataVolume, formatEuros } from "util/Formatting"
|
||||||
import ProgressBar from "util/ProgressBar.svelte";
|
import ProgressBar from "util/ProgressBar.svelte";
|
||||||
import { user } from "lib/UserStore";
|
import { user } from "lib/UserStore";
|
||||||
|
import { bills_extra } from "lib/NovaAPI";
|
||||||
|
|
||||||
let { total = 0, used = 0 }: {
|
let { total = 0, used = 0 }: {
|
||||||
total?: number;
|
total?: number;
|
||||||
@@ -9,8 +10,11 @@ let { total = 0, used = 0 }: {
|
|||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let frac = $derived(total <= 0 ? 0 : used / total)
|
let frac = $derived(total <= 0 ? 0 : used / total)
|
||||||
let billable = $derived($user !== null && $user.subscription.price_per_tb_storage > 0)
|
let price = $derived($user === null ? 0 : $user.subscription.price_per_tb_storage)
|
||||||
let billed = $derived(billable && $user.usage_based_billing)
|
let billed = $derived($user !== null && bills_extra($user, price))
|
||||||
|
|
||||||
|
// Usage based billing is on, but the balance is too far below zero for it
|
||||||
|
let overdrawn = $derived($user !== null && $user.usage_based_billing && price > 0 && !billed)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ProgressBar total={total} used={used} warn></ProgressBar>
|
<ProgressBar total={total} used={used} warn></ProgressBar>
|
||||||
@@ -35,6 +39,17 @@ let billed = $derived(billable && $user.usage_based_billing)
|
|||||||
month.
|
month.
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
{:else if frac > 1 && overdrawn}
|
||||||
|
<div class="highlight_yellow">
|
||||||
|
<p>
|
||||||
|
You are storing more than your plan includes, and your balance is
|
||||||
|
too far below zero to bill more storage. You can't upload new files
|
||||||
|
until you top up your credit. Your existing files stay available.
|
||||||
|
</p>
|
||||||
|
<a class="button button_highlight" href="/user/subscription/manage">
|
||||||
|
<i class="icon">paid</i> Top up credit
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
{:else if frac > 1}
|
{:else if frac > 1}
|
||||||
<div class="highlight_yellow">
|
<div class="highlight_yellow">
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -99,11 +99,13 @@ onMount(() => {
|
|||||||
<h2>Transaction log</h2>
|
<h2>Transaction log</h2>
|
||||||
<p>
|
<p>
|
||||||
Here is a log of all transactions on your account balance. Usage is
|
Here is a log of all transactions on your account balance. Usage is
|
||||||
calculated per day. The storage charge is divided by the average number
|
calculated per day. Storage and bandwidth are only charged for what you
|
||||||
of days in a month (30.4375).
|
use above the amounts included in your plan, and that is the usage which
|
||||||
|
is listed here. The storage charge is divided by the average number of
|
||||||
|
days in a month (30.4375).
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Example: If you have 2 TB stored on your Nova account at €4 per TB
|
Example: If you store 2 TB more than your plan includes at €4 per TB
|
||||||
then the daily charge will be:<br/>
|
then the daily charge will be:<br/>
|
||||||
|
|
||||||
2 TB * € 4 = € 8<br/>
|
2 TB * € 4 = € 8<br/>
|
||||||
@@ -129,11 +131,11 @@ onMount(() => {
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Storage charge: <Euro amount={transactions.total_storage_charge} precision="4"/>
|
Storage charge: <Euro amount={transactions.total_storage_charge} precision="4"/>
|
||||||
(used {formatDataVolume(transactions.total_storage_used, 3)})
|
({formatDataVolume(transactions.total_storage_used, 3)} above your plan)
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Bandwidth charge: <Euro amount={transactions.total_bandwidth_charge} precision="4"/>
|
Bandwidth charge: <Euro amount={transactions.total_bandwidth_charge} precision="4"/>
|
||||||
(used {formatDataVolume(transactions.total_bandwidth_used, 3)})
|
({formatDataVolume(transactions.total_bandwidth_used, 3)} above your plan)
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Total charge: <Euro amount={transactions.total_deducted} precision="4"/>
|
Total charge: <Euro amount={transactions.total_deducted} precision="4"/>
|
||||||
|
|||||||
@@ -137,14 +137,14 @@ onMount(() => {
|
|||||||
<td>Storage charge</td>
|
<td>Storage charge</td>
|
||||||
<td>
|
<td>
|
||||||
<Euro amount={transactions.total_storage_charge} precision="4"/>
|
<Euro amount={transactions.total_storage_charge} precision="4"/>
|
||||||
(used {formatDataVolume(transactions.total_storage_used, 3)})
|
({formatDataVolume(transactions.total_storage_used, 3)} above your plan)
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Bandwidth charge</td>
|
<td>Bandwidth charge</td>
|
||||||
<td>
|
<td>
|
||||||
<Euro amount={transactions.total_bandwidth_charge} precision="4"/>
|
<Euro amount={transactions.total_bandwidth_charge} precision="4"/>
|
||||||
(used {formatDataVolume(transactions.total_bandwidth_used, 3)})
|
({formatDataVolume(transactions.total_bandwidth_used, 3)} above your plan)
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -1,7 +1,33 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { days_per_month, subscription_periods } from "lib/NovaAPI";
|
||||||
import { user } from "lib/UserStore";
|
import { user } from "lib/UserStore";
|
||||||
import Euro from "util/Euro.svelte";
|
import Euro from "util/Euro.svelte";
|
||||||
import { formatDataVolume } from "util/Formatting";
|
import { formatDataVolume, formatDate, formatEuros } from "util/Formatting";
|
||||||
|
|
||||||
|
// What the account costs per day at its current usage, the same sum the server
|
||||||
|
// charges every night. Data transfer above the plan is left out, it can't be
|
||||||
|
// predicted
|
||||||
|
let daily_cost = $derived.by(() => {
|
||||||
|
if ($user === null || $user.subscription.type !== "subscription") {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const sub = $user.subscription
|
||||||
|
const discount = subscription_periods.find(
|
||||||
|
p => p.months === $user.subscription_months,
|
||||||
|
)?.discount ?? 1
|
||||||
|
const extra_storage = $user.usage_based_billing ?
|
||||||
|
Math.max(0, $user.storage_space_used - sub.storage_limit) *
|
||||||
|
sub.price_per_tb_storage / 1e12 : 0
|
||||||
|
|
||||||
|
return (sub.monthly_fee * discount + extra_storage) / days_per_month
|
||||||
|
})
|
||||||
|
|
||||||
|
let credit_until = $derived.by(() => {
|
||||||
|
const date = new Date()
|
||||||
|
date.setDate(date.getDate() + Math.floor($user.balance_micro_eur / daily_cost))
|
||||||
|
return formatDate(date, false, false, false)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $user !== null}
|
{#if $user !== null}
|
||||||
@@ -16,6 +42,18 @@ import { formatDataVolume } from "util/Formatting";
|
|||||||
<li>
|
<li>
|
||||||
Current account balance: <Euro amount={$user.balance_micro_eur}></Euro><br/>
|
Current account balance: <Euro amount={$user.balance_micro_eur}></Euro><br/>
|
||||||
|
|
||||||
|
{#if daily_cost > 0}
|
||||||
|
Your plan costs {formatEuros(daily_cost, 4)} per day at your
|
||||||
|
current usage.
|
||||||
|
{#if $user.balance_micro_eur > 0}
|
||||||
|
Your credit lasts until about {credit_until}.
|
||||||
|
{/if}
|
||||||
|
{#if $user.usage_based_billing}
|
||||||
|
Data transfer above your plan is not included.
|
||||||
|
{/if}
|
||||||
|
<br/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<i class="icon">account_balance_wallet</i>
|
<i class="icon">account_balance_wallet</i>
|
||||||
<a href="/user/subscription/manage">Deposit credit</a><br/>
|
<a href="/user/subscription/manage">Deposit credit</a><br/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { get_endpoint, get_user, type User } from "lib/NovaAPI";
|
import { bills_extra, get_endpoint, get_user, type User } from "lib/NovaAPI";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import HotlinkProgressBar from "user_home/HotlinkProgressBar.svelte";
|
import HotlinkProgressBar from "user_home/HotlinkProgressBar.svelte";
|
||||||
import StorageProgressBar from "user_home/StorageProgressBar.svelte";
|
import StorageProgressBar from "user_home/StorageProgressBar.svelte";
|
||||||
@@ -37,7 +37,7 @@ onMount(async () => {
|
|||||||
// The lowest limit which blocks downloads. With usage based billing the
|
// The lowest limit which blocks downloads. With usage based billing the
|
||||||
// plan's limit is billed instead, only the bill shock limit blocks
|
// plan's limit is billed instead, only the bill shock limit blocks
|
||||||
let caps = [user.monthly_transfer_cap]
|
let caps = [user.monthly_transfer_cap]
|
||||||
if (!user.usage_based_billing || user.subscription.price_per_tb_bandwidth <= 0) {
|
if (!bills_extra(user, user.subscription.price_per_tb_bandwidth)) {
|
||||||
caps.push(user.subscription.monthly_transfer_cap)
|
caps.push(user.subscription.monthly_transfer_cap)
|
||||||
}
|
}
|
||||||
caps = caps.filter(cap => cap > 0)
|
caps = caps.filter(cap => cap > 0)
|
||||||
|
|||||||
Reference in New Issue
Block a user