Add coupon management functions
This commit is contained in:
2
go.mod
2
go.mod
@@ -5,7 +5,7 @@ go 1.17
|
||||
require (
|
||||
fornaxian.tech/config v0.0.0-20211108212237-6133aed90586
|
||||
fornaxian.tech/log v0.0.0-20211102185326-552e9b1f8640
|
||||
fornaxian.tech/pixeldrain_api_client v0.0.0-20211123215854-be6e25030ad8
|
||||
fornaxian.tech/pixeldrain_api_client v0.0.0-20211128192613-093515ebeaa7
|
||||
fornaxian.tech/util v0.0.0-20211102152345-9a486dee9787
|
||||
github.com/julienschmidt/httprouter v1.3.0
|
||||
github.com/microcosm-cc/bluemonday v1.0.16
|
||||
|
4
go.sum
4
go.sum
@@ -3,8 +3,8 @@ fornaxian.tech/config v0.0.0-20211108212237-6133aed90586/go.mod h1:ULIXF4J1DbBw4
|
||||
fornaxian.tech/log v0.0.0-20190617093801-1c7ce9a7c9b3/go.mod h1:OyWUNsNPlo5AmlOHvJ4s6WcStQw+9rQyBMwmTz0buEM=
|
||||
fornaxian.tech/log v0.0.0-20211102185326-552e9b1f8640 h1:UPDxJwLRCfh/cv80UMSanzmZ0jIcfS1mcd0Y06HYuLw=
|
||||
fornaxian.tech/log v0.0.0-20211102185326-552e9b1f8640/go.mod h1:sN82qMToeHhP2u3ehvrcE8y1IudRZJAZO9yG5OBYblo=
|
||||
fornaxian.tech/pixeldrain_api_client v0.0.0-20211123215854-be6e25030ad8 h1:0ueAJb5nHOpFa5TXZgVVs4ORsf2zhpxLRUx3AuAx+wM=
|
||||
fornaxian.tech/pixeldrain_api_client v0.0.0-20211123215854-be6e25030ad8/go.mod h1:uajB2ofEsefUtxjvs4m7SDyPVRlfrI3qzCSWcud47hY=
|
||||
fornaxian.tech/pixeldrain_api_client v0.0.0-20211128192613-093515ebeaa7 h1:Gx7SRu906VslyPcIL9D3uw3B2uO6u7mIwsMD7SPr+9k=
|
||||
fornaxian.tech/pixeldrain_api_client v0.0.0-20211128192613-093515ebeaa7/go.mod h1:uajB2ofEsefUtxjvs4m7SDyPVRlfrI3qzCSWcud47hY=
|
||||
fornaxian.tech/util v0.0.0-20211102152345-9a486dee9787 h1:9ujI8Qi6+FTL/YW6xQAS9DmWDMerHBe8foQvVD/G/i0=
|
||||
fornaxian.tech/util v0.0.0-20211102152345-9a486dee9787/go.mod h1:FqVgfghmxTGR3l9Zx4MOMeZ9KHjiEFl3s3C0BSTvBwk=
|
||||
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
|
||||
|
@@ -1,7 +1,10 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
|
||||
import Euro from "../util/Euro.svelte";
|
||||
import Form from "./../util/Form.svelte";
|
||||
import Spinner from "../util/Spinner.svelte";
|
||||
|
||||
let loading = true
|
||||
|
||||
let credit_form = {
|
||||
name: "give_credit",
|
||||
@@ -54,10 +57,94 @@ let credit_form = {
|
||||
return {success: true, message: "Success: Granted user "+fields.credit+" credits"}
|
||||
},
|
||||
}
|
||||
|
||||
let coupon_form = {
|
||||
name: "make_coupon",
|
||||
fields: [
|
||||
{
|
||||
name: "id",
|
||||
label: "Code",
|
||||
type: "text",
|
||||
default_value: "",
|
||||
}, {
|
||||
name: "credit",
|
||||
label: "Credit",
|
||||
type: "decimal",
|
||||
default_value: 0,
|
||||
}, {
|
||||
name: "uses",
|
||||
label: "Uses",
|
||||
type: "number",
|
||||
default_value: "",
|
||||
},
|
||||
],
|
||||
submit_label: `<i class="icon">send</i> Create`,
|
||||
on_submit: async fields => {
|
||||
const form = new FormData()
|
||||
form.append("id", fields.id)
|
||||
form.append("credit", fields.credit*1e6)
|
||||
form.append("uses", fields.uses)
|
||||
|
||||
const resp = await fetch(
|
||||
window.api_endpoint+"/coupon",
|
||||
{ method: "POST", body: form }
|
||||
);
|
||||
if(resp.status >= 400) {
|
||||
return {error_json: await resp.json()}
|
||||
}
|
||||
|
||||
get_coupons()
|
||||
return {success: true, message: "Success: Coupon created"}
|
||||
},
|
||||
}
|
||||
|
||||
let coupons = []
|
||||
const get_coupons = async () => {
|
||||
loading = true;
|
||||
try {
|
||||
const resp = await fetch(window.api_endpoint+"/coupon");
|
||||
if(resp.status >= 400) {
|
||||
throw new Error(resp.text());
|
||||
}
|
||||
coupons = await resp.json()
|
||||
coupons.sort((a, b) => {
|
||||
return a.id.localeCompare(b.id)
|
||||
});
|
||||
} catch (err) {
|
||||
alert(err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
};
|
||||
const delete_coupon = async (id) => {
|
||||
if (!confirm("Delete this coupon code?\n\n"+id)) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await fetch(
|
||||
window.api_endpoint+"/coupon/"+encodeURI(id),
|
||||
{ method: "DELETE" }
|
||||
);
|
||||
if(resp.status >= 400) {
|
||||
throw new Error(await resp.text());
|
||||
}
|
||||
} catch (err) {
|
||||
alert("Failed to delete ban! "+err)
|
||||
}
|
||||
|
||||
get_coupons();
|
||||
}
|
||||
onMount(get_coupons)
|
||||
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="limit_width">
|
||||
{#if loading}
|
||||
<div class="spinner_container">
|
||||
<Spinner />
|
||||
</div>
|
||||
{/if}
|
||||
<h2>Give user credit</h2>
|
||||
<p>
|
||||
This adds credit to a user's account. You only need to enter one of
|
||||
@@ -66,5 +153,44 @@ let credit_form = {
|
||||
<div class="highlight_dark">
|
||||
<Form config={credit_form}></Form>
|
||||
</div>
|
||||
|
||||
<h2>Create coupon codes</h2>
|
||||
<div class="highlight_dark">
|
||||
<Form config={coupon_form}></Form>
|
||||
</div>
|
||||
|
||||
<h3>Active coupon codes</h3>
|
||||
<div class="table_scroll">
|
||||
<table style="text-align: left;">
|
||||
<tr>
|
||||
<td>ID</td>
|
||||
<td>Uses</td>
|
||||
<td>Credit</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
{#each coupons as row (row.id)}
|
||||
<tr>
|
||||
<td>{row.id}</td>
|
||||
<td>{row.uses}</td>
|
||||
<td><Euro amount={row.credit}></Euro></td>
|
||||
<td>
|
||||
<button on:click|preventDefault={() => {delete_coupon(row.id)}} class="button button_red round">
|
||||
<i class="icon">delete</i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.spinner_container {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
z-index: 1000;
|
||||
}
|
||||
</style>
|
||||
|
@@ -164,6 +164,14 @@ const example = () => {
|
||||
Deep sea
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<h3>Direct link</h3>
|
||||
<p>
|
||||
You can directly download the file from this link without using the
|
||||
file viewer:
|
||||
<br/>
|
||||
{domain_url()}{file.get_href}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<h3>Code</h3>
|
||||
@@ -171,7 +179,7 @@ const example = () => {
|
||||
Put this code in your website to embed the file.
|
||||
</p>
|
||||
<div class="center">
|
||||
<textarea bind:value={embed_html} style="width: 95%; height: 4em;"></textarea>
|
||||
<textarea bind:value={embed_html} style="width: 96%; height: 4em;"></textarea>
|
||||
<br/>
|
||||
<button on:click={copy} class:button_highlight={copy_status === "success"} class:button_red={copy_status === "error"}>
|
||||
<i class="icon">content_copy</i>
|
||||
|
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -175,3 +176,68 @@ func (wc *WebController) knoxfsLinkForm(td *TemplateData, r *http.Request) (f Fo
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (wc *WebController) couponForm(td *TemplateData, r *http.Request) (f Form) {
|
||||
f.Name = "redeem_coupon"
|
||||
f.Title = "Redeem coupon code"
|
||||
f.SubmitLabel = "Redeem"
|
||||
|
||||
if r.FormValue("code") == "" {
|
||||
f.Submitted = true
|
||||
f.SubmitMessages = []template.HTML{"Coupon code not found"}
|
||||
return f
|
||||
}
|
||||
|
||||
coupon, err := td.PixelAPI.GetCouponID(r.FormValue("code"))
|
||||
if err != nil && err.Error() == "not_found" {
|
||||
f.Submitted = true
|
||||
f.SubmitMessages = []template.HTML{"Coupon code not found"}
|
||||
return f
|
||||
} else if err != nil {
|
||||
f.Submitted = true
|
||||
formAPIError(err, &f)
|
||||
return f
|
||||
}
|
||||
|
||||
f.Fields = []Field{{
|
||||
Name: "1",
|
||||
Label: "",
|
||||
DefaultValue: "",
|
||||
Description: `When redeeming this coupon code the value will be ` +
|
||||
`added to your pixeldrain account. Go to the ` +
|
||||
`<a href="/user/subscription" target="_blank">subscription page</a> ` +
|
||||
`afterwards to activate your subscription.`,
|
||||
Type: FieldTypeDescription,
|
||||
}, {
|
||||
Name: "2",
|
||||
Label: "Coupon value:",
|
||||
DefaultValue: fmt.Sprintf("€ %.2f", float64(coupon.Credit)/1e6),
|
||||
Type: FieldTypeDescription,
|
||||
}, {
|
||||
Name: "2",
|
||||
Label: "Coupon uses left:",
|
||||
DefaultValue: strconv.Itoa(coupon.Uses),
|
||||
Type: FieldTypeDescription,
|
||||
}}
|
||||
|
||||
if coupon.Uses < 1 {
|
||||
f.Submitted = true
|
||||
f.SubmitMessages = []template.HTML{"Coupon has been used up"}
|
||||
return f
|
||||
}
|
||||
|
||||
if f.ReadInput(r) {
|
||||
if err := td.PixelAPI.PostCouponRedeem(r.FormValue("code")); err != nil {
|
||||
formAPIError(err, &f)
|
||||
} else {
|
||||
// Request was a success
|
||||
f.SubmitSuccess = true
|
||||
f.SubmitMessages = []template.HTML{template.HTML(
|
||||
`Success! Your account credit has been increased. Visit the ` +
|
||||
`<a href="/user/subscription">subscription page</a> to ` +
|
||||
`activate your subscription`,
|
||||
)}
|
||||
}
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
@@ -184,11 +184,14 @@ func New(
|
||||
{GET, "user/password_reset_confirm" /**/, wc.serveForm(wc.passwordResetConfirmForm, handlerOpts{NoEmbed: true})},
|
||||
{PST, "user/password_reset_confirm" /**/, wc.serveForm(wc.passwordResetConfirmForm, handlerOpts{NoEmbed: true})},
|
||||
|
||||
{GET, "patreon_activate" /* */, wc.serveForm(wc.patreonLinkForm, handlerOpts{Auth: true})},
|
||||
{PST, "patreon_activate" /* */, wc.serveForm(wc.patreonLinkForm, handlerOpts{Auth: true})},
|
||||
{GET, "patreon_activate", wc.serveForm(wc.patreonLinkForm, handlerOpts{Auth: true})},
|
||||
{PST, "patreon_activate", wc.serveForm(wc.patreonLinkForm, handlerOpts{Auth: true})},
|
||||
|
||||
{GET, "knoxfs_activate" /* */, wc.serveForm(wc.knoxfsLinkForm, handlerOpts{Auth: true})},
|
||||
{PST, "knoxfs_activate" /* */, wc.serveForm(wc.knoxfsLinkForm, handlerOpts{Auth: true})},
|
||||
{GET, "knoxfs_activate", wc.serveForm(wc.knoxfsLinkForm, handlerOpts{Auth: true})},
|
||||
{PST, "knoxfs_activate", wc.serveForm(wc.knoxfsLinkForm, handlerOpts{Auth: true})},
|
||||
|
||||
{GET, "coupon_redeem", wc.serveForm(wc.couponForm, handlerOpts{Auth: true})},
|
||||
{PST, "coupon_redeem", wc.serveForm(wc.couponForm, handlerOpts{Auth: true})},
|
||||
|
||||
// Admin settings
|
||||
{GET, "admin" /* */, wc.serveTemplate("admin", handlerOpts{Auth: true})},
|
||||
|
Reference in New Issue
Block a user