Add affiliate system
This commit is contained in:
@@ -20,6 +20,7 @@ import ListStats from "./ListStats.svelte";
|
||||
import ListUpdater from "./ListUpdater.svelte";
|
||||
import CopyButton from "../layout/CopyButton.svelte";
|
||||
import Menu from "../filesystem/Menu.svelte"
|
||||
import AffiliatePrompt from "../user_home/AffiliatePrompt.svelte";
|
||||
|
||||
let loading = true
|
||||
let embedded = false
|
||||
@@ -617,6 +618,8 @@ const keyboard_event = evt => {
|
||||
|
||||
<!-- At the bottom so it renders over everything else -->
|
||||
<LoadingIndicator loading={loading}/>
|
||||
|
||||
<AffiliatePrompt/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
@@ -14,6 +14,7 @@ import { writable } from 'svelte/store';
|
||||
import TransferLimit from '../file_viewer/TransferLimit.svelte';
|
||||
import { stats } from "../lib/StatsSocket.js"
|
||||
import { css_from_path } from './edit_window/Branding';
|
||||
import AffiliatePrompt from '../user_home/AffiliatePrompt.svelte';
|
||||
|
||||
let file_viewer
|
||||
let file_preview
|
||||
@@ -198,6 +199,8 @@ const download = () => {
|
||||
even when the user navigates to a different directory -->
|
||||
<FSUploadWidget nav={nav} bind:this={upload_widget} />
|
||||
|
||||
<AffiliatePrompt/>
|
||||
|
||||
<LoadingIndicator loading={$loading}/>
|
||||
</div>
|
||||
|
||||
|
@@ -4,6 +4,7 @@ import { copy_text } from "../util/Util.svelte";
|
||||
export let text = ""
|
||||
export let style = ""
|
||||
export let large_icon = false
|
||||
export let small_icon = false
|
||||
let failed = false
|
||||
let success = false
|
||||
|
||||
@@ -31,6 +32,7 @@ export const copy = () => {
|
||||
class:button_highlight={success}
|
||||
class:button_red={failed}
|
||||
class:large_icon
|
||||
class:small_icon
|
||||
title="Copy text to clipboard"
|
||||
disabled={text === ""}
|
||||
>
|
||||
@@ -55,4 +57,8 @@ export const copy = () => {
|
||||
font-size: 40px;
|
||||
display: block;
|
||||
}
|
||||
.small_icon>.icon {
|
||||
font-size: 1.2em;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,6 +1,9 @@
|
||||
<script>
|
||||
import CopyButton from "../layout/CopyButton.svelte";
|
||||
import Form from "./../util/Form.svelte";
|
||||
|
||||
let affiliate_link = window.location.protocol+"//"+window.location.host + "?ref=" + window.user.username
|
||||
|
||||
let account_settings = {
|
||||
name: "account_settings",
|
||||
fields: [
|
||||
@@ -40,6 +43,7 @@ let account_settings = {
|
||||
description: `Changing your username also changes the name used to
|
||||
log in. If you forget your username you can still log in using
|
||||
your e-mail address if you have one configured`,
|
||||
separator: true,
|
||||
},
|
||||
],
|
||||
submit_label: `<i class="icon">save</i> Save`,
|
||||
@@ -57,10 +61,35 @@ let account_settings = {
|
||||
form.append("password_new", fields.password_new1)
|
||||
form.append("username", fields.username)
|
||||
|
||||
const resp = await fetch(
|
||||
window.api_endpoint+"/user",
|
||||
{ method: "PUT", body: form }
|
||||
);
|
||||
const resp = await fetch(window.api_endpoint+"/user", { method: "PUT", body: form });
|
||||
if(resp.status >= 400) {
|
||||
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) {
|
||||
return {error_json: await resp.json()}
|
||||
}
|
||||
@@ -102,15 +131,33 @@ let delete_account = {
|
||||
</script>
|
||||
|
||||
<section>
|
||||
<br/>
|
||||
<div class="highlight_border">
|
||||
<h3>Account settings</h3>
|
||||
<fieldset>
|
||||
<legend>Account settings</legend>
|
||||
<Form config={account_settings}></Form>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="highlight_border">
|
||||
<h3>Delete account</h3>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<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>
|
||||
</div>
|
||||
<br/>
|
||||
</fieldset>
|
||||
</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 PageBranding from "./PageBranding.svelte";
|
||||
import Dashboard from "./dashboard/Dashboard.svelte";
|
||||
import AffiliatePrompt from "./AffiliatePrompt.svelte";
|
||||
|
||||
let pages = [
|
||||
{
|
||||
@@ -88,3 +89,5 @@ let pages = [
|
||||
</script>
|
||||
|
||||
<TabMenu pages={pages} title="Welcome, {window.user.username}!"/>
|
||||
|
||||
<AffiliatePrompt always/>
|
||||
|
@@ -16,6 +16,7 @@ let transactions = {
|
||||
total_storage_charge: 0,
|
||||
total_bandwidth_used: 0,
|
||||
total_bandwidth_charge: 0,
|
||||
total_affiliate_amount: 0,
|
||||
total_deposited: 0,
|
||||
total_deducted: 0,
|
||||
}
|
||||
@@ -42,6 +43,7 @@ const load_transactions = async () => {
|
||||
total_storage_charge: 0,
|
||||
total_bandwidth_used: 0,
|
||||
total_bandwidth_charge: 0,
|
||||
total_affiliate_amount: 0,
|
||||
total_deposited: 0,
|
||||
total_deducted: 0,
|
||||
}
|
||||
@@ -54,6 +56,7 @@ const load_transactions = async () => {
|
||||
month.total_storage_charge += row.storage_charge
|
||||
month.total_bandwidth_used += row.bandwidth_used
|
||||
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
|
||||
})
|
||||
|
||||
@@ -139,6 +142,9 @@ onMount(() => {
|
||||
<li>
|
||||
Total charge: <Euro amount={transactions.total_deducted} precision="4"/>
|
||||
</li>
|
||||
<li>
|
||||
Affiliate rewards: <Euro amount={transactions.total_affiliate_amount} precision="4"/>
|
||||
</li>
|
||||
<li>
|
||||
Deposited: <Euro amount={transactions.total_deposited} precision="4"/>
|
||||
</li>
|
||||
@@ -153,6 +159,7 @@ onMount(() => {
|
||||
<td>Subscription</td>
|
||||
<td>Storage</td>
|
||||
<td>Bandwidth</td>
|
||||
<td>Affiliate</td>
|
||||
<td>Deposit</td>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -176,6 +183,10 @@ onMount(() => {
|
||||
<Euro amount={row.bandwidth_charge} precision="6"/>
|
||||
({formatDataVolume(row.bandwidth_used, 3)})
|
||||
</td>
|
||||
<td>
|
||||
<Euro amount={row.affiliate_amount} precision="6"/>
|
||||
({row.affiliate_count})
|
||||
</td>
|
||||
<td>
|
||||
<Euro amount={row.deposit_amount}/>
|
||||
</td>
|
||||
|
@@ -17,6 +17,7 @@ let transactions = {
|
||||
total_storage_charge: 0,
|
||||
total_bandwidth_used: 0,
|
||||
total_bandwidth_charge: 0,
|
||||
total_affiliate_amount: 0,
|
||||
total_deposited: 0,
|
||||
total_deducted: 0,
|
||||
balance_start: 0,
|
||||
@@ -45,6 +46,7 @@ const load_transactions = async () => {
|
||||
total_storage_charge: 0,
|
||||
total_bandwidth_used: 0,
|
||||
total_bandwidth_charge: 0,
|
||||
total_affiliate_amount: 0,
|
||||
total_deposited: 0,
|
||||
total_deducted: 0,
|
||||
balance_start: 0,
|
||||
@@ -66,6 +68,7 @@ const load_transactions = async () => {
|
||||
month.total_storage_charge += row.storage_charge
|
||||
month.total_bandwidth_used += row.bandwidth_used
|
||||
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
|
||||
})
|
||||
|
||||
@@ -148,6 +151,12 @@ onMount(() => {
|
||||
(used {formatDataVolume(transactions.total_bandwidth_used, 3)})
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Affiliate rewards</td>
|
||||
<td>
|
||||
<Euro amount={transactions.total_affiliate_amount} precision="4"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Deposited</td>
|
||||
<td><Euro amount={transactions.total_deposited} precision="4"/></td>
|
||||
|
Reference in New Issue
Block a user