Move metered billing settings to the subscription tab

This commit is contained in:
2026-10-06 01:00:53 +02:00
parent a50ac7310f
commit e3e8a1db1a
6 changed files with 114 additions and 108 deletions

View File

@@ -51,7 +51,7 @@ let included = $derived($user === null ? 0 : $user.subscription.monthly_transfer
</p> </p>
{#if bill_shock} {#if bill_shock}
<p> <p>
This is your bill shock limit. <a href="/user/sharing">Increase This is your bill shock limit. <a href="/user/subscription/metered_billing">Increase
or disable the limit</a> to continue sharing files. or disable the limit</a> to continue sharing files.
</p> </p>
{:else if overdrawn} {:else if overdrawn}
@@ -65,7 +65,7 @@ let included = $derived($user === null ? 0 : $user.subscription.monthly_transfer
{:else} {:else}
<p> <p>
Upgrade your plan or enable Upgrade your plan or enable
<a href="/user/sharing">usage based billing</a> to continue <a href="/user/subscription/metered_billing">usage based billing</a> to continue
sharing files. sharing files.
</p> </p>
<a class="button button_highlight" href="/user/subscription/manage"> <a class="button button_highlight" href="/user/subscription/manage">
@@ -82,7 +82,7 @@ let included = $derived($user === null ? 0 : $user.subscription.monthly_transfer
{#if bill_shock} {#if bill_shock}
<p> <p>
This is your bill shock limit, you can change it in the This is your bill shock limit, you can change it in the
<a href="/user/sharing">sharing settings</a>. <a href="/user/subscription/metered_billing">subscription settings</a>.
</p> </p>
{:else} {:else}
<a class="button button_highlight" href="/user/subscription/manage"> <a class="button button_highlight" href="/user/subscription/manage">

View File

@@ -11,6 +11,7 @@ import { onMount } from "svelte";
import { get_user, type User } from "lib/NovaAPI"; import { get_user, type User } from "lib/NovaAPI";
import SharingSettings from "./SharingSettings.svelte"; import SharingSettings from "./SharingSettings.svelte";
import SubscriptionManage from "./SubscriptionManage.svelte"; import SubscriptionManage from "./SubscriptionManage.svelte";
import SubscriptionUsage from "./SubscriptionUsage.svelte";
let pages: Tab[] = [ let pages: Tab[] = [
{ {
@@ -39,6 +40,11 @@ let pages: Tab[] = [
title: "Transactions", title: "Transactions",
icon: "receipt", icon: "receipt",
component: Transactions, component: Transactions,
}, {
path: "/user/subscription/metered_billing",
title: "Metered billing",
icon: "data_usage",
component: SubscriptionUsage,
}, },
], ],
}, { }, {

View File

@@ -1,26 +1,18 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte";
import { formatDataVolume, formatEuros } from "util/Formatting";
import ProgressBar from "util/ProgressBar.svelte";
import { loading_run } from "lib/Loading"; import { loading_run } from "lib/Loading";
import { get_endpoint, period_price, put_user } from 'lib/NovaAPI'; import { 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)
const update_user = async (e: SubmitEvent) => { const update_user = async (e: SubmitEvent) => {
if (e !== null) { if (e !== null) {
e.preventDefault() e.preventDefault()
} }
loading_run(async () => { loading_run(async () => {
try { try {
await put_user({ await put_user({embed_domains: embed_domains.join(" ")})
transfer_cap: transfer_cap*1e12,
embed_domains: embed_domains.join(" "),
})
success_message.set(true, "Sharing settings updated") success_message.set(true, "Sharing settings updated")
} catch (err) { } catch (err) {
@@ -29,40 +21,6 @@ const update_user = async (e: SubmitEvent) => {
}) })
} }
const set_usage_based_billing = (e: Event) => {
const input = e.target as HTMLInputElement
loading_run(async () => {
try {
await put_user({usage_based_billing: input.checked})
success_message.set(true, "Usage based billing updated")
} catch (err) {
input.checked = !input.checked
success_message.set(false, "Failed to update user: "+err)
}
})
}
let transfer_used = $state(0)
let load_transfer_used = () => {
let today = new Date()
let start = new Date()
start.setDate(start.getDate() - 30)
fetch(
get_endpoint() + "/user/time_series/egress" +
"?start=" + start.toISOString() +
"&end=" + today.toISOString() +
"&interval=60"
).then(resp => {
if (!resp.ok) { return Promise.reject("Error: " + resp.status); }
return resp.json();
}).then(resp => {
transfer_used = resp.amounts.reduce((acc, cur) => { return acc + cur }, 0)
}).catch(e => {
console.error("Error requesting time series: " + e);
})
}
// Embedding settings // Embedding settings
let embed_domains: string[] = $state($user === null ? [] : $user.embed_domains.split(" ")) let embed_domains: string[] = $state($user === null ? [] : $user.embed_domains.split(" "))
let new_domain: string = $state("") let new_domain: string = $state("")
@@ -80,64 +38,11 @@ const del_domain = (index: number) => {
embed_domains = embed_domains embed_domains = embed_domains
update_user(null) update_user(null)
} }
onMount(async () => {
load_transfer_used()
})
</script> </script>
<section> <section>
<SuccessMessage bind:this={success_message}/> <SuccessMessage bind:this={success_message}/>
{#if $user !== null && $user.subscription.type === "subscription"}
<h2>Usage based billing</h2>
<p>
When your storage or data transfer goes over the limits of your
plan, uploads and downloads are blocked. With usage based billing
they keep working and the usage above the limits is billed at
{formatEuros($user.subscription.price_per_tb_storage, 2)} per TB per
month for storage and
{formatEuros($user.subscription.price_per_tb_bandwidth, 2)} per TB
for data transfer.
</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>
<input type="checkbox" checked={$user.usage_based_billing} onchange={set_usage_based_billing}/>
Enable usage based billing
</label>
{/if}
<h2>Bill shock limit</h2>
<p>
Billshock limit in terabytes per month (1 TB = 1000 GB). Set to 0 to
disable.
</p>
<form onsubmit={update_user} class="billshock_container">
<input type="number" bind:value={transfer_cap} step="0.1" min="0" style="width: 5em;"/>
<div style="margin: 0.5em;">TB</div>
<button type="submit">
<i class="icon">save</i> Save
</button>
</form>
<p>
Bandwidth used in the last 30 days: {formatDataVolume(transfer_used, 3)},
new limit: {formatDataVolume(transfer_cap*1e12, 3)}
</p>
<ProgressBar used={transfer_used} total={transfer_cap*1e12} warn></ProgressBar>
<p>
The billshock limit limits how much bandwidth your account can use in a
30 day window. When this limit is reached your files can't be downloaded
until your usage drops below it, and you will no longer be charged for
bandwidth usage. This is mostly useful with usage based billing, but it
works without it too. Set to 0 to disable the limit.
</p>
<h2>Embedding controls</h2> <h2>Embedding controls</h2>
<p> <p>
Here you can control which websites are allowed to embed your files in Here you can control which websites are allowed to embed your files in
@@ -172,12 +77,6 @@ onMount(async () => {
</section> </section>
<style> <style>
.billshock_container {
display: flex;
flex-direction: row;
align-items: center;
}
.highlight_border { .highlight_border {
text-align: initial; text-align: initial;
} }

View File

@@ -56,7 +56,7 @@ let overdrawn = $derived($user !== null && $user.usage_based_billing && price >
You are storing more than your plan allows, so you can't upload new You are storing more than your plan allows, so you can't upload new
files. Your existing files stay available. Delete some files or files. Your existing files stay available. Delete some files or
upgrade your plan to continue uploading, or enable upgrade your plan to continue uploading, or enable
<a href="/user/sharing">usage based billing</a> to pay for the <a href="/user/subscription/metered_billing">usage based billing</a> to pay for the
storage above the limit. storage above the limit.
</p> </p>
<a class="button button_highlight" href="/user/subscription/manage"> <a class="button button_highlight" href="/user/subscription/manage">

View File

@@ -0,0 +1,101 @@
<script lang="ts">
import { formatDataVolume, formatEuros } from "util/Formatting";
import ProgressBar from "util/ProgressBar.svelte";
import { loading_run } from "lib/Loading";
import { period_price, put_user } from "lib/NovaAPI";
import SuccessMessage from "util/SuccessMessage.svelte";
import { user } from "lib/UserStore";
import ToggleButton from "layout/ToggleButton.svelte";
let success_message: SuccessMessage = $state()
// The toggle button has already flipped the value in the user store when this
// runs. put_user reloads the store from the server, so only a failed request
// has to flip it back
const set_usage_based_billing = () => {
loading_run(async () => {
try {
await put_user({usage_based_billing: $user.usage_based_billing})
success_message.set(true, "Usage based billing updated")
} catch (err) {
$user.usage_based_billing = !$user.usage_based_billing
success_message.set(false, "Failed to update user: "+(err?.message ?? err))
}
})
}
let transfer_cap = $state($user === null ? 0 : $user.monthly_transfer_cap / 1e12)
let transfer_used = $derived($user === null ? 0 : $user.monthly_transfer_used)
const set_transfer_cap = (e: SubmitEvent) => {
e.preventDefault()
loading_run(async () => {
try {
await put_user({transfer_cap: transfer_cap*1e12})
success_message.set(true, "Bill shock limit updated")
} catch (err) {
success_message.set(false, "Failed to update user: "+(err?.message ?? err))
}
})
}
</script>
<section>
<SuccessMessage bind:this={success_message}/>
{#if $user !== null && $user.subscription.type === "subscription"}
<h2>Usage based billing</h2>
<p>
When your storage or data transfer goes over the limits of your
plan, uploads and downloads are blocked. With usage based billing
they keep working and the usage above the limits is billed at
{formatEuros($user.subscription.price_per_tb_storage, 2)} per TB per
month for storage and
{formatEuros($user.subscription.price_per_tb_bandwidth, 2)} per TB
for data transfer.
</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>
<ToggleButton bind:on={$user.usage_based_billing} action={set_usage_based_billing}>
Usage based billing
</ToggleButton>
{/if}
<h2>Bill shock limit</h2>
<p>
Billshock limit in terabytes per month (1 TB = 1000 GB). Set to 0 to
disable.
</p>
<form onsubmit={set_transfer_cap} class="billshock_container">
<input type="number" bind:value={transfer_cap} step="0.1" min="0" style="width: 5em;"/>
<div style="margin: 0.5em;">TB</div>
<button type="submit">
<i class="icon">save</i> Save
</button>
</form>
<p>
Bandwidth used in the last 30 days: {formatDataVolume(transfer_used, 3)},
new limit: {formatDataVolume(transfer_cap*1e12, 3)}
</p>
<ProgressBar used={transfer_used} total={transfer_cap*1e12} warn></ProgressBar>
<p>
The billshock limit limits how much bandwidth your account can use in a
30 day window. When this limit is reached your files can't be downloaded
until your usage drops below it, and you will no longer be charged for
bandwidth usage. This is mostly useful with usage based billing, but it
works without it too. Set to 0 to disable the limit.
</p>
</section>
<style>
.billshock_container {
display: flex;
flex-direction: row;
align-items: center;
}
</style>

View File

@@ -59,6 +59,6 @@ onMount(async () => {
<br/><br/> <br/><br/>
Egress used (30 days): Egress used (30 days):
(<a href="/user/sharing">set custom limit</a>) (<a href="/user/subscription/metered_billing">set custom limit</a>)
<HotlinkProgressBar used={transfer_used} total={transfer_cap}></HotlinkProgressBar> <HotlinkProgressBar used={transfer_used} total={transfer_cap}></HotlinkProgressBar>
{/if} {/if}