Add affiliate prompt branding option
This commit is contained in:
@@ -70,6 +70,7 @@ let toolbar_toggle = () => {
|
||||
let downloader
|
||||
let list_updater
|
||||
let details_window
|
||||
let affiliate_prompt
|
||||
let details_visible = false
|
||||
let qr_window
|
||||
let qr_visible = false
|
||||
@@ -256,6 +257,9 @@ const apply_customizations = file => {
|
||||
if (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) {
|
||||
disable_download_button = true
|
||||
}
|
||||
@@ -619,7 +623,7 @@ const keyboard_event = evt => {
|
||||
<!-- At the bottom so it renders over everything else -->
|
||||
<LoadingIndicator loading={loading}/>
|
||||
|
||||
<AffiliatePrompt/>
|
||||
<AffiliatePrompt bind:this={affiliate_prompt}/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
@@ -3,10 +3,18 @@ export let on = false
|
||||
export let group_first = false
|
||||
export let group_middle = false
|
||||
export let group_last = false
|
||||
export let action = (e: MouseEvent) => {}
|
||||
|
||||
const click = (e: MouseEvent) => {
|
||||
on = !on
|
||||
if (typeof action === "function") {
|
||||
action(e)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<button
|
||||
on:click={() => on = !on}
|
||||
on:click={click}
|
||||
type="button"
|
||||
class="button"
|
||||
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>
|
||||
import { onMount } from "svelte";
|
||||
import CopyButton from "../layout/CopyButton.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 = {
|
||||
name: "account_settings",
|
||||
@@ -144,15 +154,27 @@ let delete_account = {
|
||||
Your own affiliate link is
|
||||
<a href="{affiliate_link}">{affiliate_link}</a>
|
||||
<CopyButton small_icon text={affiliate_link}/>. Share this link
|
||||
with premium pixeldrain users to gain commissions. For a
|
||||
detailed description of the affiliate program please check out
|
||||
the <a href="/about#toc_12">Q&A page</a>.
|
||||
with premium pixeldrain users to gain commissions. You can use
|
||||
the "?ref={encodeURIComponent(window.user.username)}" referral
|
||||
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>
|
||||
Note that the link includes the name of your pixeldrain
|
||||
account. If you change your account name the link will stop
|
||||
working and you might stop receiving commissions.
|
||||
</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>
|
||||
</fieldset>
|
||||
|
||||
|
@@ -1,22 +1,35 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import Modal from "../util/Modal.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
|
||||
// already has an affiliate ID set
|
||||
export let always = false
|
||||
let modal
|
||||
let loading
|
||||
let ref
|
||||
let modal: Modal
|
||||
let loading: boolean
|
||||
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 (window.user.subscription.id === "") {
|
||||
if (user.subscription.id === "") {
|
||||
// User does not have an active subscription, setting referral will
|
||||
// not have effect
|
||||
return
|
||||
} else if (window.user.affiliate_user_name !== "") {
|
||||
} else if (user.affiliate_user_name !== "") {
|
||||
// User is already sponsoring someone
|
||||
return
|
||||
} else if (localStorage.getItem("affiliate_deny") === "1") {
|
||||
@@ -25,28 +38,21 @@ onMount(() => {
|
||||
}
|
||||
}
|
||||
|
||||
ref = new URLSearchParams(document.location.search).get("ref")
|
||||
if (ref === null) {
|
||||
// The prompt can only be shown once per page. This should prevent it from
|
||||
// showing up every time someone loads a new file.
|
||||
if (shown === true) {
|
||||
return
|
||||
} else if (ref === window.user.affiliate_user_name) {
|
||||
return // User is already supporting this affiliate ID
|
||||
}
|
||||
|
||||
shown = true
|
||||
modal.show()
|
||||
})
|
||||
}
|
||||
|
||||
onMount(() => prompt(new URLSearchParams(document.location.search).get("ref")))
|
||||
|
||||
const allow = async () => {
|
||||
loading = true
|
||||
try {
|
||||
const form = new FormData()
|
||||
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
|
||||
await put_user({affiliate_user_name: referral})
|
||||
|
||||
// Close the popup
|
||||
modal.hide()
|
||||
@@ -67,10 +73,10 @@ const deny = () => {
|
||||
<LoadingIndicator bind:loading={loading} />
|
||||
<section>
|
||||
<p>
|
||||
Hi! {ref} wants you to sponsor their pixeldrain account. This will
|
||||
give them €0.50 every month in pixeldrain prepaid credit. They can
|
||||
use this credit to get a discount on their file sharing and storage
|
||||
efforts. Here is a short summary of what this entails:
|
||||
Hi! {referral} wants you to sponsor their pixeldrain account. This
|
||||
will give them €0.50 every month in pixeldrain prepaid credit. They
|
||||
can use this credit to get a discount on their file storage and
|
||||
sharing costs. Here is a short summary of what this entails:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
|
@@ -6,6 +6,7 @@ import SuccessMessage from "../util/SuccessMessage.svelte";
|
||||
import ThemePicker from "../util/ThemePicker.svelte";
|
||||
import { onMount } from "svelte";
|
||||
import Persistence from "../icons/Persistence.svelte";
|
||||
import ToggleButton from "../layout/ToggleButton.svelte";
|
||||
|
||||
let loading = false
|
||||
let success_message
|
||||
@@ -19,6 +20,7 @@ let header_link = ""
|
||||
let background_image = ""
|
||||
let footer_image = ""
|
||||
let footer_link = ""
|
||||
let affiliate_prompt = false
|
||||
let disable_download_button = false
|
||||
let disable_share_button = false
|
||||
|
||||
@@ -59,6 +61,9 @@ let save = async () => {
|
||||
form.append("footer_link", footer_link)
|
||||
form.append("disable_download_button", disable_download_button)
|
||||
form.append("disable_share_button", disable_share_button)
|
||||
if (affiliate_prompt) {
|
||||
form.append("affiliate_prompt", window.user.username)
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await fetch(
|
||||
@@ -90,6 +95,7 @@ onMount(() => {
|
||||
background_image = b.background_image ? b.background_image : ""
|
||||
footer_image = b.footer_image ? b.footer_image : ""
|
||||
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_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
|
||||
the Persistence plan or higher to enable these features.
|
||||
</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}
|
||||
<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
|
||||
to load.
|
||||
</p>
|
||||
<h3>Theme</h3>
|
||||
<fieldset>
|
||||
<legend>Theme</legend>
|
||||
<p>
|
||||
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
|
||||
@@ -135,8 +136,10 @@ onMount(() => {
|
||||
theme={theme}
|
||||
on:theme_change={e => {theme = e.detail; save()}}>
|
||||
</ThemePicker>
|
||||
</fieldset>
|
||||
|
||||
<h3>Header image</h3>
|
||||
<fieldset>
|
||||
<legend>Header image</legend>
|
||||
<p>
|
||||
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
|
||||
@@ -162,8 +165,10 @@ onMount(() => {
|
||||
<CustomBanner src={"/api/file/"+header_image} link={header_link}></CustomBanner>
|
||||
</div>
|
||||
{/if}
|
||||
</fieldset>
|
||||
|
||||
<h3>Background image</h3>
|
||||
<fieldset>
|
||||
<legend>Background image</legend>
|
||||
<p>
|
||||
This image will be shown behind the file which is being viewed. I
|
||||
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"/>
|
||||
</div>
|
||||
{/if}
|
||||
</fieldset>
|
||||
|
||||
<h3>Footer image</h3>
|
||||
<fieldset>
|
||||
<legend>Footer image</legend>
|
||||
<p>
|
||||
Will be shown below the file. Maximum height is 90px. Will be shrunk if
|
||||
larger.
|
||||
@@ -208,8 +215,22 @@ onMount(() => {
|
||||
<CustomBanner src={"/api/file/"+footer_image} link={footer_link}></CustomBanner>
|
||||
</div>
|
||||
{/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>
|
||||
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
|
||||
@@ -224,23 +245,15 @@ onMount(() => {
|
||||
files. The buttons are still available to you. If you want to see the
|
||||
effects you can open your file in an incognito window.
|
||||
</p>
|
||||
Disable download button:
|
||||
<button on:click={() => {disable_download_button = !disable_download_button; save()}}>
|
||||
{#if disable_download_button}
|
||||
<i class="icon">check</i> ON (click to turn off)
|
||||
{:else}
|
||||
<i class="icon">close</i> OFF (click to turn on)
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<ToggleButton bind:on={disable_download_button} action={save}>
|
||||
Disable download button
|
||||
</ToggleButton>
|
||||
<br/>
|
||||
Disable share button:
|
||||
<button on:click={() => {disable_share_button = !disable_share_button; save()}}>
|
||||
{#if disable_share_button}
|
||||
<i class="icon">check</i> ON (click to turn off)
|
||||
{:else}
|
||||
<i class="icon">close</i> OFF (click to turn on)
|
||||
{/if}
|
||||
</button>
|
||||
<ToggleButton bind:on={disable_share_button} action={save}>
|
||||
Disable share button
|
||||
</ToggleButton>
|
||||
</fieldset>
|
||||
</section>
|
||||
|
||||
<FilePicker
|
||||
|
@@ -141,6 +141,7 @@ onMount(() => {
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="feat_label" class:feat_highlight={subscription === "prepaid"}>
|
||||
Prepaid<br/>
|
||||
@@ -188,6 +189,7 @@ onMount(() => {
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="feat_label" class:feat_highlight={subscription === ""}>
|
||||
Free<br/>
|
||||
|
Reference in New Issue
Block a user