Add affiliate prompt branding option
This commit is contained in:
@@ -70,6 +70,7 @@ let toolbar_toggle = () => {
|
|||||||
let downloader
|
let downloader
|
||||||
let list_updater
|
let list_updater
|
||||||
let details_window
|
let details_window
|
||||||
|
let affiliate_prompt
|
||||||
let details_visible = false
|
let details_visible = false
|
||||||
let qr_window
|
let qr_window
|
||||||
let qr_visible = false
|
let qr_visible = false
|
||||||
@@ -256,6 +257,9 @@ const apply_customizations = file => {
|
|||||||
if (file.branding.footer_link) {
|
if (file.branding.footer_link) {
|
||||||
custom_footer_link = file.branding.footer_link
|
custom_footer_link = file.branding.footer_link
|
||||||
}
|
}
|
||||||
|
if (file.branding.affiliate_prompt) {
|
||||||
|
affiliate_prompt.prompt(file.branding.affiliate_prompt)
|
||||||
|
}
|
||||||
if (file.branding.disable_download_button && !file.can_edit) {
|
if (file.branding.disable_download_button && !file.can_edit) {
|
||||||
disable_download_button = true
|
disable_download_button = true
|
||||||
}
|
}
|
||||||
@@ -619,7 +623,7 @@ const keyboard_event = evt => {
|
|||||||
<!-- At the bottom so it renders over everything else -->
|
<!-- At the bottom so it renders over everything else -->
|
||||||
<LoadingIndicator loading={loading}/>
|
<LoadingIndicator loading={loading}/>
|
||||||
|
|
||||||
<AffiliatePrompt/>
|
<AffiliatePrompt bind:this={affiliate_prompt}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@@ -3,10 +3,18 @@ export let on = false
|
|||||||
export let group_first = false
|
export let group_first = false
|
||||||
export let group_middle = false
|
export let group_middle = false
|
||||||
export let group_last = false
|
export let group_last = false
|
||||||
|
export let action = (e: MouseEvent) => {}
|
||||||
|
|
||||||
|
const click = (e: MouseEvent) => {
|
||||||
|
on = !on
|
||||||
|
if (typeof action === "function") {
|
||||||
|
action(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
on:click={() => on = !on}
|
on:click={click}
|
||||||
type="button"
|
type="button"
|
||||||
class="button"
|
class="button"
|
||||||
class:button_highlight={on}
|
class:button_highlight={on}
|
||||||
|
107
svelte/src/lib/PixeldrainAPI.mts
Normal file
107
svelte/src/lib/PixeldrainAPI.mts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// Response types
|
||||||
|
// ==============
|
||||||
|
|
||||||
|
export type GenericResponse = {
|
||||||
|
value: string,
|
||||||
|
message: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type User = {
|
||||||
|
username: string,
|
||||||
|
email: string,
|
||||||
|
subscription: Subscription,
|
||||||
|
storage_space_used: number,
|
||||||
|
filesystem_storage_used: number,
|
||||||
|
is_admin: boolean,
|
||||||
|
balance_micro_eur: number,
|
||||||
|
hotlinking_enabled: boolean,
|
||||||
|
monthly_transfer_cap: number,
|
||||||
|
monthly_transfer_used: number,
|
||||||
|
file_viewer_branding: Map<string, string>,
|
||||||
|
file_embed_domains: string,
|
||||||
|
skip_file_viewer: boolean,
|
||||||
|
affiliate_user_name: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Subscription = {
|
||||||
|
id: string,
|
||||||
|
name: string,
|
||||||
|
type: string,
|
||||||
|
file_size_limit: number,
|
||||||
|
file_expiry_days: number,
|
||||||
|
storage_space: number,
|
||||||
|
price_per_tb_storage: number,
|
||||||
|
price_per_tb_bandwidth: number,
|
||||||
|
monthly_transfer_cap: number,
|
||||||
|
file_viewer_branding: boolean,
|
||||||
|
filesystem_access: boolean,
|
||||||
|
filesystem_storage_limit: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility funcs
|
||||||
|
// =============
|
||||||
|
|
||||||
|
export const get_endpoint = () => {
|
||||||
|
if ((window as any).api_endpoint !== undefined) {
|
||||||
|
return (window as any).api_endpoint as string
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn("api_endpoint property is not defined on window")
|
||||||
|
|
||||||
|
return "/api"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const check_response = async (resp: Response) => {
|
||||||
|
let text = await resp.text()
|
||||||
|
if (resp.status >= 400) {
|
||||||
|
let error: any
|
||||||
|
try {
|
||||||
|
error = JSON.parse(text) as GenericResponse
|
||||||
|
} catch (err) {
|
||||||
|
error = text
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
return JSON.parse(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dict_to_form = (dict: Object) => {
|
||||||
|
let form = new FormData()
|
||||||
|
for (let key of Object.keys(dict)) {
|
||||||
|
if (dict[key] === undefined) {
|
||||||
|
continue
|
||||||
|
} else if (dict[key] instanceof Date) {
|
||||||
|
form.append(key, new Date(dict[key]).toISOString())
|
||||||
|
} else if (typeof dict[key] === "object") {
|
||||||
|
form.append(key, JSON.stringify(dict[key]))
|
||||||
|
} else {
|
||||||
|
form.append(key, dict[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return form
|
||||||
|
}
|
||||||
|
|
||||||
|
// API methods
|
||||||
|
// ===========
|
||||||
|
|
||||||
|
export const get_user = async () => {
|
||||||
|
if ((window as any).user !== undefined) {
|
||||||
|
return (window as any).user as User
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn("user property is not defined on window")
|
||||||
|
|
||||||
|
return await check_response(await fetch(get_endpoint() + "/user")) as User
|
||||||
|
}
|
||||||
|
|
||||||
|
export const put_user = async (data: Object) => {
|
||||||
|
check_response(await fetch(
|
||||||
|
get_endpoint() + "/user",
|
||||||
|
{ method: "PUT", body: dict_to_form(data) },
|
||||||
|
))
|
||||||
|
|
||||||
|
// Update the window.user variable
|
||||||
|
for (let key of Object.keys(data)) {
|
||||||
|
((window as any).user as User)[key] = data[key]
|
||||||
|
}
|
||||||
|
}
|
@@ -1,8 +1,18 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { onMount } from "svelte";
|
||||||
import CopyButton from "../layout/CopyButton.svelte";
|
import CopyButton from "../layout/CopyButton.svelte";
|
||||||
import Form from "./../util/Form.svelte";
|
import Form from "./../util/Form.svelte";
|
||||||
|
import Button from "../layout/Button.svelte";
|
||||||
|
|
||||||
let affiliate_link = window.location.protocol+"//"+window.location.host + "?ref=" + window.user.username
|
let affiliate_link = window.location.protocol+"//"+window.location.host + "?ref=" + encodeURIComponent(window.user.username)
|
||||||
|
let affiliate_deny = false
|
||||||
|
onMount(() => {
|
||||||
|
affiliate_deny = localStorage.getItem("affiliate_deny") === "1"
|
||||||
|
})
|
||||||
|
const enable_affiliate_prompt = () => {
|
||||||
|
affiliate_deny = false
|
||||||
|
localStorage.removeItem("affiliate_deny")
|
||||||
|
}
|
||||||
|
|
||||||
let account_settings = {
|
let account_settings = {
|
||||||
name: "account_settings",
|
name: "account_settings",
|
||||||
@@ -144,15 +154,27 @@ let delete_account = {
|
|||||||
Your own affiliate link is
|
Your own affiliate link is
|
||||||
<a href="{affiliate_link}">{affiliate_link}</a>
|
<a href="{affiliate_link}">{affiliate_link}</a>
|
||||||
<CopyButton small_icon text={affiliate_link}/>. Share this link
|
<CopyButton small_icon text={affiliate_link}/>. Share this link
|
||||||
with premium pixeldrain users to gain commissions. For a
|
with premium pixeldrain users to gain commissions. You can use
|
||||||
detailed description of the affiliate program please check out
|
the "?ref={encodeURIComponent(window.user.username)}" referral
|
||||||
the <a href="/about#toc_12">Q&A page</a>.
|
code on download pages too. For a detailed description of the
|
||||||
|
affiliate program please check out the <a
|
||||||
|
href="/about#toc_12">Q&A page</a>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Note that the link includes the name of your pixeldrain
|
Note that the link includes the name of your pixeldrain
|
||||||
account. If you change your account name the link will stop
|
account. If you change your account name the link will stop
|
||||||
working and you might stop receiving commissions.
|
working and you might stop receiving commissions.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{#if affiliate_deny}
|
||||||
|
<div class="highlight_blue">
|
||||||
|
You currently have affiliate prompts disabled. You will not
|
||||||
|
see affiliate requests from other users. If you wish to
|
||||||
|
enable it again, click here:
|
||||||
|
<br/>
|
||||||
|
<Button click={enable_affiliate_prompt} label="Enable affiliate prompts"/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
@@ -1,22 +1,35 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import Modal from "../util/Modal.svelte";
|
import Modal from "../util/Modal.svelte";
|
||||||
import LoadingIndicator from "../util/LoadingIndicator.svelte";
|
import LoadingIndicator from "../util/LoadingIndicator.svelte";
|
||||||
|
import { get_user, put_user } from "../lib/PixeldrainAPI.mjs";
|
||||||
|
|
||||||
// When the always flag is set then the pop-up will also show if the user
|
// When the always flag is set then the pop-up will also show if the user
|
||||||
// already has an affiliate ID set
|
// already has an affiliate ID set
|
||||||
export let always = false
|
export let always = false
|
||||||
let modal
|
let modal: Modal
|
||||||
let loading
|
let loading: boolean
|
||||||
let ref
|
let referral: string
|
||||||
|
let shown = false
|
||||||
|
|
||||||
|
export const prompt = async (ref: string) => {
|
||||||
|
referral = ref
|
||||||
|
const user = await get_user()
|
||||||
|
|
||||||
|
if (referral === null) {
|
||||||
|
return
|
||||||
|
} else if (referral === user.affiliate_user_name) {
|
||||||
|
return // User is already supporting this affiliate ID
|
||||||
|
} else if (referral === user.username) {
|
||||||
|
return // This is your own referral link
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if (!always) {
|
if (!always) {
|
||||||
if (window.user.subscription.id === "") {
|
if (user.subscription.id === "") {
|
||||||
// User does not have an active subscription, setting referral will
|
// User does not have an active subscription, setting referral will
|
||||||
// not have effect
|
// not have effect
|
||||||
return
|
return
|
||||||
} else if (window.user.affiliate_user_name !== "") {
|
} else if (user.affiliate_user_name !== "") {
|
||||||
// User is already sponsoring someone
|
// User is already sponsoring someone
|
||||||
return
|
return
|
||||||
} else if (localStorage.getItem("affiliate_deny") === "1") {
|
} else if (localStorage.getItem("affiliate_deny") === "1") {
|
||||||
@@ -25,28 +38,21 @@ onMount(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ref = new URLSearchParams(document.location.search).get("ref")
|
// The prompt can only be shown once per page. This should prevent it from
|
||||||
if (ref === null) {
|
// showing up every time someone loads a new file.
|
||||||
|
if (shown === true) {
|
||||||
return
|
return
|
||||||
} else if (ref === window.user.affiliate_user_name) {
|
}
|
||||||
return // User is already supporting this affiliate ID
|
shown = true
|
||||||
|
modal.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
modal.show()
|
onMount(() => prompt(new URLSearchParams(document.location.search).get("ref")))
|
||||||
})
|
|
||||||
|
|
||||||
const allow = async () => {
|
const allow = async () => {
|
||||||
loading = true
|
loading = true
|
||||||
try {
|
try {
|
||||||
const form = new FormData()
|
await put_user({affiliate_user_name: referral})
|
||||||
form.append("affiliate_user_name", ref)
|
|
||||||
const resp = await fetch(window.api_endpoint+"/user", { method: "PUT", body: form });
|
|
||||||
if(resp.status >= 400) {
|
|
||||||
throw (await resp.json()).message
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the window.user variable
|
|
||||||
window.user.affiliate_user_name = ref
|
|
||||||
|
|
||||||
// Close the popup
|
// Close the popup
|
||||||
modal.hide()
|
modal.hide()
|
||||||
@@ -67,10 +73,10 @@ const deny = () => {
|
|||||||
<LoadingIndicator bind:loading={loading} />
|
<LoadingIndicator bind:loading={loading} />
|
||||||
<section>
|
<section>
|
||||||
<p>
|
<p>
|
||||||
Hi! {ref} wants you to sponsor their pixeldrain account. This will
|
Hi! {referral} wants you to sponsor their pixeldrain account. This
|
||||||
give them €0.50 every month in pixeldrain prepaid credit. They can
|
will give them €0.50 every month in pixeldrain prepaid credit. They
|
||||||
use this credit to get a discount on their file sharing and storage
|
can use this credit to get a discount on their file storage and
|
||||||
efforts. Here is a short summary of what this entails:
|
sharing costs. Here is a short summary of what this entails:
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
|
@@ -6,6 +6,7 @@ import SuccessMessage from "../util/SuccessMessage.svelte";
|
|||||||
import ThemePicker from "../util/ThemePicker.svelte";
|
import ThemePicker from "../util/ThemePicker.svelte";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import Persistence from "../icons/Persistence.svelte";
|
import Persistence from "../icons/Persistence.svelte";
|
||||||
|
import ToggleButton from "../layout/ToggleButton.svelte";
|
||||||
|
|
||||||
let loading = false
|
let loading = false
|
||||||
let success_message
|
let success_message
|
||||||
@@ -19,6 +20,7 @@ let header_link = ""
|
|||||||
let background_image = ""
|
let background_image = ""
|
||||||
let footer_image = ""
|
let footer_image = ""
|
||||||
let footer_link = ""
|
let footer_link = ""
|
||||||
|
let affiliate_prompt = false
|
||||||
let disable_download_button = false
|
let disable_download_button = false
|
||||||
let disable_share_button = false
|
let disable_share_button = false
|
||||||
|
|
||||||
@@ -59,6 +61,9 @@ let save = async () => {
|
|||||||
form.append("footer_link", footer_link)
|
form.append("footer_link", footer_link)
|
||||||
form.append("disable_download_button", disable_download_button)
|
form.append("disable_download_button", disable_download_button)
|
||||||
form.append("disable_share_button", disable_share_button)
|
form.append("disable_share_button", disable_share_button)
|
||||||
|
if (affiliate_prompt) {
|
||||||
|
form.append("affiliate_prompt", window.user.username)
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(
|
const resp = await fetch(
|
||||||
@@ -90,6 +95,7 @@ onMount(() => {
|
|||||||
background_image = b.background_image ? b.background_image : ""
|
background_image = b.background_image ? b.background_image : ""
|
||||||
footer_image = b.footer_image ? b.footer_image : ""
|
footer_image = b.footer_image ? b.footer_image : ""
|
||||||
footer_link = b.footer_link ? b.footer_link : ""
|
footer_link = b.footer_link ? b.footer_link : ""
|
||||||
|
affiliate_prompt = b.affiliate_prompt === window.user.username ? true : false
|
||||||
disable_download_button = b.disable_download_button ? b.disable_download_button : false
|
disable_download_button = b.disable_download_button ? b.disable_download_button : false
|
||||||
disable_share_button = b.disable_share_button ? b.disable_share_button : false
|
disable_share_button = b.disable_share_button ? b.disable_share_button : false
|
||||||
}
|
}
|
||||||
@@ -105,12 +111,6 @@ onMount(() => {
|
|||||||
Sharing settings are not available for your account. Subscribe to
|
Sharing settings are not available for your account. Subscribe to
|
||||||
the Persistence plan or higher to enable these features.
|
the Persistence plan or higher to enable these features.
|
||||||
</div>
|
</div>
|
||||||
{:else if !window.user.hotlinking_enabled}
|
|
||||||
<div class="highlight_yellow">
|
|
||||||
To use custom file viewer branding hotlinking needs to be
|
|
||||||
enabled. Enable hotlinking on the
|
|
||||||
<a href="/user/sharing/bandwidth">sharing settings page</a>.
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
<SuccessMessage bind:this={success_message}></SuccessMessage>
|
<SuccessMessage bind:this={success_message}></SuccessMessage>
|
||||||
|
|
||||||
@@ -125,7 +125,8 @@ onMount(() => {
|
|||||||
should use APNG or WebP. Avoid using animated GIFs as they are very slow
|
should use APNG or WebP. Avoid using animated GIFs as they are very slow
|
||||||
to load.
|
to load.
|
||||||
</p>
|
</p>
|
||||||
<h3>Theme</h3>
|
<fieldset>
|
||||||
|
<legend>Theme</legend>
|
||||||
<p>
|
<p>
|
||||||
Choose a theme for your download pages. This theme will override the
|
Choose a theme for your download pages. This theme will override the
|
||||||
theme preference of the person viewing the file. Set to 'None' to let
|
theme preference of the person viewing the file. Set to 'None' to let
|
||||||
@@ -135,8 +136,10 @@ onMount(() => {
|
|||||||
theme={theme}
|
theme={theme}
|
||||||
on:theme_change={e => {theme = e.detail; save()}}>
|
on:theme_change={e => {theme = e.detail; save()}}>
|
||||||
</ThemePicker>
|
</ThemePicker>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
<h3>Header image</h3>
|
<fieldset>
|
||||||
|
<legend>Header image</legend>
|
||||||
<p>
|
<p>
|
||||||
Will be shown above the file. Maximum height is 90px. Will be shrunk if
|
Will be shown above the file. Maximum height is 90px. Will be shrunk if
|
||||||
larger. You can also add a link to open when the visitor clicks the
|
larger. You can also add a link to open when the visitor clicks the
|
||||||
@@ -162,8 +165,10 @@ onMount(() => {
|
|||||||
<CustomBanner src={"/api/file/"+header_image} link={header_link}></CustomBanner>
|
<CustomBanner src={"/api/file/"+header_image} link={header_link}></CustomBanner>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
<h3>Background image</h3>
|
<fieldset>
|
||||||
|
<legend>Background image</legend>
|
||||||
<p>
|
<p>
|
||||||
This image will be shown behind the file which is being viewed. I
|
This image will be shown behind the file which is being viewed. I
|
||||||
recommend choosing something dark and not too distracting. Try to keep
|
recommend choosing something dark and not too distracting. Try to keep
|
||||||
@@ -183,8 +188,10 @@ onMount(() => {
|
|||||||
<img class="background_preview" src="/api/file/{background_image}" alt="Custom file viewer background"/>
|
<img class="background_preview" src="/api/file/{background_image}" alt="Custom file viewer background"/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
<h3>Footer image</h3>
|
<fieldset>
|
||||||
|
<legend>Footer image</legend>
|
||||||
<p>
|
<p>
|
||||||
Will be shown below the file. Maximum height is 90px. Will be shrunk if
|
Will be shown below the file. Maximum height is 90px. Will be shrunk if
|
||||||
larger.
|
larger.
|
||||||
@@ -208,8 +215,22 @@ onMount(() => {
|
|||||||
<CustomBanner src={"/api/file/"+footer_image} link={footer_link}></CustomBanner>
|
<CustomBanner src={"/api/file/"+footer_image} link={footer_link}></CustomBanner>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
<h3>Toolbar buttons</h3>
|
<fieldset>
|
||||||
|
<legend>Affiliate prompt</legend>
|
||||||
|
<p>
|
||||||
|
When this is enabled premium users on your download pages will be
|
||||||
|
asked to support you through pixeldrain's <a
|
||||||
|
href="/about#toc_12">affiliate program</a>.
|
||||||
|
</p>
|
||||||
|
<ToggleButton bind:on={affiliate_prompt} action={save}>
|
||||||
|
Enable affiliate prompt
|
||||||
|
</ToggleButton>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>Toolbar buttons</legend>
|
||||||
<p>
|
<p>
|
||||||
If you don't want to make it obvious that your files can be downloaded
|
If you don't want to make it obvious that your files can be downloaded
|
||||||
or shared while still allowing people to view them through the site you
|
or shared while still allowing people to view them through the site you
|
||||||
@@ -224,23 +245,15 @@ onMount(() => {
|
|||||||
files. The buttons are still available to you. If you want to see the
|
files. The buttons are still available to you. If you want to see the
|
||||||
effects you can open your file in an incognito window.
|
effects you can open your file in an incognito window.
|
||||||
</p>
|
</p>
|
||||||
Disable download button:
|
|
||||||
<button on:click={() => {disable_download_button = !disable_download_button; save()}}>
|
<ToggleButton bind:on={disable_download_button} action={save}>
|
||||||
{#if disable_download_button}
|
Disable download button
|
||||||
<i class="icon">check</i> ON (click to turn off)
|
</ToggleButton>
|
||||||
{:else}
|
|
||||||
<i class="icon">close</i> OFF (click to turn on)
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
<br/>
|
<br/>
|
||||||
Disable share button:
|
<ToggleButton bind:on={disable_share_button} action={save}>
|
||||||
<button on:click={() => {disable_share_button = !disable_share_button; save()}}>
|
Disable share button
|
||||||
{#if disable_share_button}
|
</ToggleButton>
|
||||||
<i class="icon">check</i> ON (click to turn off)
|
</fieldset>
|
||||||
{:else}
|
|
||||||
<i class="icon">close</i> OFF (click to turn on)
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<FilePicker
|
<FilePicker
|
||||||
|
@@ -141,6 +141,7 @@ onMount(() => {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="feat_label" class:feat_highlight={subscription === "prepaid"}>
|
<div class="feat_label" class:feat_highlight={subscription === "prepaid"}>
|
||||||
Prepaid<br/>
|
Prepaid<br/>
|
||||||
@@ -188,6 +189,7 @@ onMount(() => {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="feat_label" class:feat_highlight={subscription === ""}>
|
<div class="feat_label" class:feat_highlight={subscription === ""}>
|
||||||
Free<br/>
|
Free<br/>
|
||||||
|
Reference in New Issue
Block a user