Add affiliate system
This commit is contained in:
@@ -185,6 +185,38 @@ be public. For that reason I disabled search indexing on all pixeldrain files.
|
|||||||
This protects the privacy of pixeldrain users and helps with preventing
|
This protects the privacy of pixeldrain users and helps with preventing
|
||||||
information leaks.
|
information leaks.
|
||||||
|
|
||||||
|
## How does the affiliate program work?
|
||||||
|
|
||||||
|
Pixeldrain's affiliate program is a way to earn pixeldrain credit by driving
|
||||||
|
traffic to pixeldrain. The way it works is that you send people your affiliate
|
||||||
|
link, if someone accepts to be your affiliate then their active subscription
|
||||||
|
will earn you pixeldrain credit. The affiliate program is opt-in and fully
|
||||||
|
transparent. Users will always be notified when their affiliate account is
|
||||||
|
updated. You can update who you are sponsoring by editing the affiliate name on
|
||||||
|
your [user settings page](/user/settings).
|
||||||
|
|
||||||
|
Here is a summary of all the rules and limitations:
|
||||||
|
|
||||||
|
* Each paying customer using your affiliate code will earn you €0.50 in prepaid
|
||||||
|
credit every month. The credit is added to your account on a daily basis, as
|
||||||
|
you can see on the [transactions page](/user/prepaid/transactions).
|
||||||
|
* Sponsoring someone with an affiliate code does not cost you any extra money.
|
||||||
|
The resulting fee comes out of pixeldrain's pockets.
|
||||||
|
* You can only earn pixeldrain credit with the affiliate program. There is no
|
||||||
|
cash out feature.
|
||||||
|
* When someone who is using your affiliate code cancels their plan, you will
|
||||||
|
also stop receiving rewards.
|
||||||
|
* You don't need an active subscription to gain credit through the affiliate
|
||||||
|
program. You need a positive balance of at least €1 to activate the prepaid
|
||||||
|
plan.
|
||||||
|
|
||||||
|
Some fun facts:
|
||||||
|
|
||||||
|
* You only need two affiliates to offset pixeldrain's base subscription fee.
|
||||||
|
* Each sponsoring user is effectively equal to 125 GB of storage space or 500 GB
|
||||||
|
of bandwidth usage per month.
|
||||||
|
* You cannot sponsor yourself.
|
||||||
|
|
||||||
## Is there a clean pixeldrain logo I can use?
|
## Is there a clean pixeldrain logo I can use?
|
||||||
|
|
||||||
Yes, here's a high resolution pixeldrain logo with text. The font is called
|
Yes, here's a high resolution pixeldrain logo with text. The font is called
|
||||||
|
@@ -20,6 +20,7 @@ import ListStats from "./ListStats.svelte";
|
|||||||
import ListUpdater from "./ListUpdater.svelte";
|
import ListUpdater from "./ListUpdater.svelte";
|
||||||
import CopyButton from "../layout/CopyButton.svelte";
|
import CopyButton from "../layout/CopyButton.svelte";
|
||||||
import Menu from "../filesystem/Menu.svelte"
|
import Menu from "../filesystem/Menu.svelte"
|
||||||
|
import AffiliatePrompt from "../user_home/AffiliatePrompt.svelte";
|
||||||
|
|
||||||
let loading = true
|
let loading = true
|
||||||
let embedded = false
|
let embedded = false
|
||||||
@@ -617,6 +618,8 @@ 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/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@@ -14,6 +14,7 @@ import { writable } from 'svelte/store';
|
|||||||
import TransferLimit from '../file_viewer/TransferLimit.svelte';
|
import TransferLimit from '../file_viewer/TransferLimit.svelte';
|
||||||
import { stats } from "../lib/StatsSocket.js"
|
import { stats } from "../lib/StatsSocket.js"
|
||||||
import { css_from_path } from './edit_window/Branding';
|
import { css_from_path } from './edit_window/Branding';
|
||||||
|
import AffiliatePrompt from '../user_home/AffiliatePrompt.svelte';
|
||||||
|
|
||||||
let file_viewer
|
let file_viewer
|
||||||
let file_preview
|
let file_preview
|
||||||
@@ -198,6 +199,8 @@ const download = () => {
|
|||||||
even when the user navigates to a different directory -->
|
even when the user navigates to a different directory -->
|
||||||
<FSUploadWidget nav={nav} bind:this={upload_widget} />
|
<FSUploadWidget nav={nav} bind:this={upload_widget} />
|
||||||
|
|
||||||
|
<AffiliatePrompt/>
|
||||||
|
|
||||||
<LoadingIndicator loading={$loading}/>
|
<LoadingIndicator loading={$loading}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@ import { copy_text } from "../util/Util.svelte";
|
|||||||
export let text = ""
|
export let text = ""
|
||||||
export let style = ""
|
export let style = ""
|
||||||
export let large_icon = false
|
export let large_icon = false
|
||||||
|
export let small_icon = false
|
||||||
let failed = false
|
let failed = false
|
||||||
let success = false
|
let success = false
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ export const copy = () => {
|
|||||||
class:button_highlight={success}
|
class:button_highlight={success}
|
||||||
class:button_red={failed}
|
class:button_red={failed}
|
||||||
class:large_icon
|
class:large_icon
|
||||||
|
class:small_icon
|
||||||
title="Copy text to clipboard"
|
title="Copy text to clipboard"
|
||||||
disabled={text === ""}
|
disabled={text === ""}
|
||||||
>
|
>
|
||||||
@@ -55,4 +57,8 @@ export const copy = () => {
|
|||||||
font-size: 40px;
|
font-size: 40px;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
.small_icon>.icon {
|
||||||
|
font-size: 1.2em;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import CopyButton from "../layout/CopyButton.svelte";
|
||||||
import Form from "./../util/Form.svelte";
|
import Form from "./../util/Form.svelte";
|
||||||
|
|
||||||
|
let affiliate_link = window.location.protocol+"//"+window.location.host + "?ref=" + window.user.username
|
||||||
|
|
||||||
let account_settings = {
|
let account_settings = {
|
||||||
name: "account_settings",
|
name: "account_settings",
|
||||||
fields: [
|
fields: [
|
||||||
@@ -40,6 +43,7 @@ let account_settings = {
|
|||||||
description: `Changing your username also changes the name used to
|
description: `Changing your username also changes the name used to
|
||||||
log in. If you forget your username you can still log in using
|
log in. If you forget your username you can still log in using
|
||||||
your e-mail address if you have one configured`,
|
your e-mail address if you have one configured`,
|
||||||
|
separator: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
submit_label: `<i class="icon">save</i> Save`,
|
submit_label: `<i class="icon">save</i> Save`,
|
||||||
@@ -57,10 +61,35 @@ let account_settings = {
|
|||||||
form.append("password_new", fields.password_new1)
|
form.append("password_new", fields.password_new1)
|
||||||
form.append("username", fields.username)
|
form.append("username", fields.username)
|
||||||
|
|
||||||
const resp = await fetch(
|
const resp = await fetch(window.api_endpoint+"/user", { method: "PUT", body: form });
|
||||||
window.api_endpoint+"/user",
|
if(resp.status >= 400) {
|
||||||
{ method: "PUT", body: form }
|
return {error_json: await resp.json()}
|
||||||
);
|
}
|
||||||
|
return {success: true, message: "Success! Your changes have been saved"}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const affiliate_settings = {
|
||||||
|
name: "affiliate_settings",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "affiliate_user_name",
|
||||||
|
label: "Affiliate user name",
|
||||||
|
type: "text",
|
||||||
|
default_value: window.user.affiliate_user_name,
|
||||||
|
description: `The affiliate user name can be the name of a
|
||||||
|
pixeldrain account you wish to support with your subscription.
|
||||||
|
The account will receive a fee of €0.50 for every month that
|
||||||
|
your premium plan is active. This does not cost you anything
|
||||||
|
extra.`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
submit_label: `<i class="icon">save</i> Save`,
|
||||||
|
on_submit: async fields => {
|
||||||
|
const form = new FormData()
|
||||||
|
form.append("affiliate_user_name", fields.affiliate_user_name)
|
||||||
|
|
||||||
|
const resp = await fetch(window.api_endpoint+"/user", { method: "PUT", body: form });
|
||||||
if(resp.status >= 400) {
|
if(resp.status >= 400) {
|
||||||
return {error_json: await resp.json()}
|
return {error_json: await resp.json()}
|
||||||
}
|
}
|
||||||
@@ -102,15 +131,33 @@ let delete_account = {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<br/>
|
<fieldset>
|
||||||
<div class="highlight_border">
|
<legend>Account settings</legend>
|
||||||
<h3>Account settings</h3>
|
|
||||||
<Form config={account_settings}></Form>
|
<Form config={account_settings}></Form>
|
||||||
</div>
|
</fieldset>
|
||||||
<br/>
|
|
||||||
<div class="highlight_border">
|
<fieldset>
|
||||||
<h3>Delete account</h3>
|
<legend>Affiliate settings</legend>
|
||||||
|
<Form config={affiliate_settings}></Form>
|
||||||
|
<div class="form">
|
||||||
|
<p>
|
||||||
|
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>.
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>Delete account</legend>
|
||||||
<Form config={delete_account}></Form>
|
<Form config={delete_account}></Form>
|
||||||
</div>
|
</fieldset>
|
||||||
<br/>
|
|
||||||
</section>
|
</section>
|
||||||
|
127
svelte/src/user_home/AffiliatePrompt.svelte
Normal file
127
svelte/src/user_home/AffiliatePrompt.svelte
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<script>
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import Modal from "../util/Modal.svelte";
|
||||||
|
import LoadingIndicator from "../util/LoadingIndicator.svelte";
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (!always) {
|
||||||
|
if (window.user.subscription.id === "") {
|
||||||
|
// User does not have an active subscription, setting referral will
|
||||||
|
// not have effect
|
||||||
|
return
|
||||||
|
} else if (window.user.affiliate_user_name !== "") {
|
||||||
|
// User is already sponsoring someone
|
||||||
|
return
|
||||||
|
} else if (localStorage.getItem("affiliate_deny") === "1") {
|
||||||
|
// User has dismissed the pop-up in the past
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ref = new URLSearchParams(document.location.search).get("ref")
|
||||||
|
if (ref === null) {
|
||||||
|
return
|
||||||
|
} else if (ref === window.user.affiliate_user_name) {
|
||||||
|
return // User is already supporting this affiliate ID
|
||||||
|
}
|
||||||
|
|
||||||
|
modal.show()
|
||||||
|
})
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
// Close the popup
|
||||||
|
modal.hide()
|
||||||
|
} catch (err) {
|
||||||
|
alert(err)
|
||||||
|
} finally {
|
||||||
|
loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deny = () => {
|
||||||
|
localStorage.setItem("affiliate_deny", "1")
|
||||||
|
modal.hide()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Modal bind:this={modal} title="Affiliate sponsoring request" width="700px">
|
||||||
|
<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:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Sponsoring only works while you have an active subscription
|
||||||
|
plan. When your subscription deactivates the creator will no
|
||||||
|
longer receive commissions.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Pixeldrain credit cannot be cashed out. So they are not earning
|
||||||
|
real money with this.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
This does not cost you any extra money. The commissions paid out
|
||||||
|
to the creator are paid for by pixeldrain itself.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
You can change who you are sponsoring at any time on your <a
|
||||||
|
href="/user/settings">account settings page</a>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
If you want to know more about the affiliate program check out
|
||||||
|
the <a href="/about#toc_12">Q&A page</a>.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
If you click 'Accept' then the requested affiliate code will be
|
||||||
|
added to your account and the creator will start earning. If you
|
||||||
|
choose 'Deny' then we will never show this pop-up again.
|
||||||
|
</p>
|
||||||
|
<div class="buttons">
|
||||||
|
<button class="button button_red" on:click={e => deny()}>
|
||||||
|
Deny
|
||||||
|
</button>
|
||||||
|
<button class="button button_highlight" on:click={e => allow()}>
|
||||||
|
Accept
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-around;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.2em;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
@@ -11,6 +11,7 @@ import BandwidthSharing from "./BandwidthSharing.svelte";
|
|||||||
import EmbeddingControls from "./EmbeddingControls.svelte";
|
import EmbeddingControls from "./EmbeddingControls.svelte";
|
||||||
import PageBranding from "./PageBranding.svelte";
|
import PageBranding from "./PageBranding.svelte";
|
||||||
import Dashboard from "./dashboard/Dashboard.svelte";
|
import Dashboard from "./dashboard/Dashboard.svelte";
|
||||||
|
import AffiliatePrompt from "./AffiliatePrompt.svelte";
|
||||||
|
|
||||||
let pages = [
|
let pages = [
|
||||||
{
|
{
|
||||||
@@ -88,3 +89,5 @@ let pages = [
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<TabMenu pages={pages} title="Welcome, {window.user.username}!"/>
|
<TabMenu pages={pages} title="Welcome, {window.user.username}!"/>
|
||||||
|
|
||||||
|
<AffiliatePrompt always/>
|
||||||
|
@@ -16,6 +16,7 @@ let transactions = {
|
|||||||
total_storage_charge: 0,
|
total_storage_charge: 0,
|
||||||
total_bandwidth_used: 0,
|
total_bandwidth_used: 0,
|
||||||
total_bandwidth_charge: 0,
|
total_bandwidth_charge: 0,
|
||||||
|
total_affiliate_amount: 0,
|
||||||
total_deposited: 0,
|
total_deposited: 0,
|
||||||
total_deducted: 0,
|
total_deducted: 0,
|
||||||
}
|
}
|
||||||
@@ -42,6 +43,7 @@ const load_transactions = async () => {
|
|||||||
total_storage_charge: 0,
|
total_storage_charge: 0,
|
||||||
total_bandwidth_used: 0,
|
total_bandwidth_used: 0,
|
||||||
total_bandwidth_charge: 0,
|
total_bandwidth_charge: 0,
|
||||||
|
total_affiliate_amount: 0,
|
||||||
total_deposited: 0,
|
total_deposited: 0,
|
||||||
total_deducted: 0,
|
total_deducted: 0,
|
||||||
}
|
}
|
||||||
@@ -54,6 +56,7 @@ const load_transactions = async () => {
|
|||||||
month.total_storage_charge += row.storage_charge
|
month.total_storage_charge += row.storage_charge
|
||||||
month.total_bandwidth_used += row.bandwidth_used
|
month.total_bandwidth_used += row.bandwidth_used
|
||||||
month.total_bandwidth_charge += row.bandwidth_charge
|
month.total_bandwidth_charge += row.bandwidth_charge
|
||||||
|
month.total_affiliate_amount += row.affiliate_amount
|
||||||
month.total_deducted += row.subscription_charge + row.storage_charge + row.bandwidth_charge
|
month.total_deducted += row.subscription_charge + row.storage_charge + row.bandwidth_charge
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -139,6 +142,9 @@ onMount(() => {
|
|||||||
<li>
|
<li>
|
||||||
Total charge: <Euro amount={transactions.total_deducted} precision="4"/>
|
Total charge: <Euro amount={transactions.total_deducted} precision="4"/>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
Affiliate rewards: <Euro amount={transactions.total_affiliate_amount} precision="4"/>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Deposited: <Euro amount={transactions.total_deposited} precision="4"/>
|
Deposited: <Euro amount={transactions.total_deposited} precision="4"/>
|
||||||
</li>
|
</li>
|
||||||
@@ -153,6 +159,7 @@ onMount(() => {
|
|||||||
<td>Subscription</td>
|
<td>Subscription</td>
|
||||||
<td>Storage</td>
|
<td>Storage</td>
|
||||||
<td>Bandwidth</td>
|
<td>Bandwidth</td>
|
||||||
|
<td>Affiliate</td>
|
||||||
<td>Deposit</td>
|
<td>Deposit</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -176,6 +183,10 @@ onMount(() => {
|
|||||||
<Euro amount={row.bandwidth_charge} precision="6"/>
|
<Euro amount={row.bandwidth_charge} precision="6"/>
|
||||||
({formatDataVolume(row.bandwidth_used, 3)})
|
({formatDataVolume(row.bandwidth_used, 3)})
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<Euro amount={row.affiliate_amount} precision="6"/>
|
||||||
|
({row.affiliate_count})
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<Euro amount={row.deposit_amount}/>
|
<Euro amount={row.deposit_amount}/>
|
||||||
</td>
|
</td>
|
||||||
|
@@ -17,6 +17,7 @@ let transactions = {
|
|||||||
total_storage_charge: 0,
|
total_storage_charge: 0,
|
||||||
total_bandwidth_used: 0,
|
total_bandwidth_used: 0,
|
||||||
total_bandwidth_charge: 0,
|
total_bandwidth_charge: 0,
|
||||||
|
total_affiliate_amount: 0,
|
||||||
total_deposited: 0,
|
total_deposited: 0,
|
||||||
total_deducted: 0,
|
total_deducted: 0,
|
||||||
balance_start: 0,
|
balance_start: 0,
|
||||||
@@ -45,6 +46,7 @@ const load_transactions = async () => {
|
|||||||
total_storage_charge: 0,
|
total_storage_charge: 0,
|
||||||
total_bandwidth_used: 0,
|
total_bandwidth_used: 0,
|
||||||
total_bandwidth_charge: 0,
|
total_bandwidth_charge: 0,
|
||||||
|
total_affiliate_amount: 0,
|
||||||
total_deposited: 0,
|
total_deposited: 0,
|
||||||
total_deducted: 0,
|
total_deducted: 0,
|
||||||
balance_start: 0,
|
balance_start: 0,
|
||||||
@@ -66,6 +68,7 @@ const load_transactions = async () => {
|
|||||||
month.total_storage_charge += row.storage_charge
|
month.total_storage_charge += row.storage_charge
|
||||||
month.total_bandwidth_used += row.bandwidth_used
|
month.total_bandwidth_used += row.bandwidth_used
|
||||||
month.total_bandwidth_charge += row.bandwidth_charge
|
month.total_bandwidth_charge += row.bandwidth_charge
|
||||||
|
month.total_affiliate_amount += row.affiliate_amount
|
||||||
month.total_deducted += row.subscription_charge + row.storage_charge + row.bandwidth_charge
|
month.total_deducted += row.subscription_charge + row.storage_charge + row.bandwidth_charge
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -148,6 +151,12 @@ onMount(() => {
|
|||||||
(used {formatDataVolume(transactions.total_bandwidth_used, 3)})
|
(used {formatDataVolume(transactions.total_bandwidth_used, 3)})
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Affiliate rewards</td>
|
||||||
|
<td>
|
||||||
|
<Euro amount={transactions.total_affiliate_amount} precision="4"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Deposited</td>
|
<td>Deposited</td>
|
||||||
<td><Euro amount={transactions.total_deposited} precision="4"/></td>
|
<td><Euro amount={transactions.total_deposited} precision="4"/></td>
|
||||||
|
Reference in New Issue
Block a user