referrals: add "use expiry" option

adds an option when enabling referrals to use the duration of the source
invited (i.e., months, days, hours) for the referral invite. If enabled,
the user won't be able to make a new referral link after it expires. For
referrals enabled for new users via a profile, the clock starts ticking
as soon as the account is created.
This commit is contained in:
Harvey Tindall 2023-11-10 15:07:29 +00:00
parent d0de1142ae
commit a66c522b73
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
14 changed files with 118 additions and 23 deletions

View File

@ -45,14 +45,24 @@ func (app *appContext) checkInvites() {
app.storage.SetInvitesKey(data.Code, data) app.storage.SetInvitesKey(data.Code, data)
} }
if data.IsReferral { if data.IsReferral && (!data.UseReferralExpiry || data.ReferrerJellyfinID == "") {
continue continue
} }
expiry := data.ValidTill expiry := data.ValidTill
if !currentTime.After(expiry) { if !currentTime.After(expiry) {
continue continue
} }
app.debug.Printf("Housekeeping: Deleting old invite %s", data.Code) app.debug.Printf("Housekeeping: Deleting old invite %s", data.Code)
// Disable referrals for the user if UseReferralExpiry is enabled, so no new ones are made.
if data.IsReferral && data.UseReferralExpiry && data.ReferrerJellyfinID != "" {
user, ok := app.storage.GetEmailsKey(data.ReferrerJellyfinID)
if ok {
user.ReferralTemplateKey = ""
app.storage.SetEmailsKey(data.ReferrerJellyfinID, user)
}
}
notify := data.Notify notify := data.Notify
if emailEnabled && app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 { if emailEnabled && app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 {
app.debug.Printf("%s: Expiry notification", data.Code) app.debug.Printf("%s: Expiry notification", data.Code)
@ -136,6 +146,13 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool
} }
wait.Wait() wait.Wait()
} }
if inv.IsReferral && inv.ReferrerJellyfinID != "" && inv.UseReferralExpiry {
user, ok := app.storage.GetEmailsKey(inv.ReferrerJellyfinID)
if ok {
user.ReferralTemplateKey = ""
app.storage.SetEmailsKey(inv.ReferrerJellyfinID, user)
}
}
match = false match = false
app.storage.DeleteInvitesKey(code) app.storage.DeleteInvitesKey(code)
app.storage.SetActivityKey(shortuuid.New(), Activity{ app.storage.SetActivityKey(shortuuid.New(), Activity{

View File

@ -130,15 +130,17 @@ func (app *appContext) DeleteProfile(gc *gin.Context) {
// @Produce json // @Produce json
// @Param profile path string true "name of profile to enable referrals for." // @Param profile path string true "name of profile to enable referrals for."
// @Param invite path string true "invite code to create referral template from." // @Param invite path string true "invite code to create referral template from."
// @Param useExpiry path string true "with-expiry or none."
// @Success 200 {object} boolResponse // @Success 200 {object} boolResponse
// @Failure 400 {object} stringResponse // @Failure 400 {object} stringResponse
// @Failure 500 {object} stringResponse // @Failure 500 {object} stringResponse
// @Router /profiles/referral/{profile}/{invite} [post] // @Router /profiles/referral/{profile}/{invite}/{useExpiry} [post]
// @Security Bearer // @Security Bearer
// @tags Profiles & Settings // @tags Profiles & Settings
func (app *appContext) EnableReferralForProfile(gc *gin.Context) { func (app *appContext) EnableReferralForProfile(gc *gin.Context) {
profileName := gc.Param("profile") profileName := gc.Param("profile")
invCode := gc.Param("invite") invCode := gc.Param("invite")
useExpiry := gc.Param("useExpiry") == "with-expiry"
inv, ok := app.storage.GetInvitesKey(invCode) inv, ok := app.storage.GetInvitesKey(invCode)
if !ok { if !ok {
respond(400, "Invalid invite code", gc) respond(400, "Invalid invite code", gc)
@ -154,9 +156,15 @@ func (app *appContext) EnableReferralForProfile(gc *gin.Context) {
// Generate new code for referral template // Generate new code for referral template
inv.Code = GenerateInviteCode() inv.Code = GenerateInviteCode()
expiryDelta := inv.ValidTill.Sub(inv.Created)
inv.Created = time.Now() inv.Created = time.Now()
inv.ValidTill = inv.Created.Add(REFERRAL_EXPIRY_DAYS * 24 * time.Hour) if useExpiry {
inv.ValidTill = inv.Created.Add(expiryDelta)
} else {
inv.ValidTill = inv.Created.Add(REFERRAL_EXPIRY_DAYS * 24 * time.Hour)
}
inv.IsReferral = true inv.IsReferral = true
inv.UseReferralExpiry = useExpiry
// Since this is a template for multiple users, ReferrerJellyfinID is not set. // Since this is a template for multiple users, ReferrerJellyfinID is not set.
// inv.ReferrerJellyfinID = ... // inv.ReferrerJellyfinID = ...

View File

@ -746,21 +746,37 @@ func (app *appContext) GetMyReferral(gc *gin.Context) {
// Since this key is shared between users in a profile, we make a copy. // Since this key is shared between users in a profile, we make a copy.
user, ok := app.storage.GetEmailsKey(gc.GetString("jfId")) user, ok := app.storage.GetEmailsKey(gc.GetString("jfId"))
err = app.storage.db.Get(user.ReferralTemplateKey, &inv) err = app.storage.db.Get(user.ReferralTemplateKey, &inv)
if !ok || err != nil { if !ok || err != nil || user.ReferralTemplateKey == "" {
app.debug.Printf("Ignoring referral request, couldn't find template.") app.debug.Printf("Ignoring referral request, couldn't find template.")
respondBool(400, false, gc) respondBool(400, false, gc)
return return
} }
inv.Code = GenerateInviteCode() inv.Code = GenerateInviteCode()
expiryDelta := inv.ValidTill.Sub(inv.Created)
inv.Created = time.Now() inv.Created = time.Now()
inv.ValidTill = inv.Created.Add(REFERRAL_EXPIRY_DAYS * 24 * time.Hour) if inv.UseReferralExpiry {
inv.ValidTill = inv.Created.Add(expiryDelta)
} else {
inv.ValidTill = inv.Created.Add(REFERRAL_EXPIRY_DAYS * 24 * time.Hour)
}
inv.IsReferral = true inv.IsReferral = true
inv.ReferrerJellyfinID = gc.GetString("jfId") inv.ReferrerJellyfinID = gc.GetString("jfId")
app.storage.SetInvitesKey(inv.Code, inv) app.storage.SetInvitesKey(inv.Code, inv)
} else if time.Now().After(inv.ValidTill) { } else if time.Now().After(inv.ValidTill) {
// 3. We found an invite for us, but it's expired. // 3. We found an invite for us, but it's expired.
// We delete it from storage, and put it back with a fresh code and expiry. // We delete it from storage, and put it back with a fresh code and expiry.
// If UseReferralExpiry is enabled, we delete it and return nothing.
app.storage.DeleteInvitesKey(inv.Code) app.storage.DeleteInvitesKey(inv.Code)
if inv.UseReferralExpiry {
user, ok := app.storage.GetEmailsKey(gc.GetString("jfId"))
if ok {
user.ReferralTemplateKey = ""
app.storage.SetEmailsKey(gc.GetString("jfId"), user)
}
app.debug.Printf("Ignoring referral request, expired.")
respondBool(400, false, gc)
return
}
inv.Code = GenerateInviteCode() inv.Code = GenerateInviteCode()
inv.Created = time.Now() inv.Created = time.Now()
inv.ValidTill = inv.Created.Add(REFERRAL_EXPIRY_DAYS * 24 * time.Hour) inv.ValidTill = inv.Created.Add(REFERRAL_EXPIRY_DAYS * 24 * time.Hour)
@ -771,5 +787,6 @@ func (app *appContext) GetMyReferral(gc *gin.Context) {
RemainingUses: inv.RemainingUses, RemainingUses: inv.RemainingUses,
NoLimit: inv.NoLimit, NoLimit: inv.NoLimit,
Expiry: inv.ValidTill.Unix(), Expiry: inv.ValidTill.Unix(),
UseExpiry: inv.UseReferralExpiry,
}) })
} }

View File

@ -367,6 +367,19 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
emailStore.ReferralTemplateKey = profile.ReferralTemplateKey emailStore.ReferralTemplateKey = profile.ReferralTemplateKey
// Store here, just incase email are disabled (whether this is even possible, i don't know) // Store here, just incase email are disabled (whether this is even possible, i don't know)
app.storage.SetEmailsKey(id, emailStore) app.storage.SetEmailsKey(id, emailStore)
// If UseReferralExpiry is enabled, create the ref now so the clock starts ticking
refInv := Invite{}
err = app.storage.db.Get(profile.ReferralTemplateKey, &refInv)
if refInv.UseReferralExpiry {
refInv.Code = GenerateInviteCode()
expiryDelta := refInv.ValidTill.Sub(refInv.Created)
refInv.Created = time.Now()
refInv.ValidTill = refInv.Created.Add(expiryDelta)
refInv.IsReferral = true
refInv.ReferrerJellyfinID = id
app.storage.SetInvitesKey(refInv.Code, refInv)
}
} }
} }
// if app.config.Section("password_resets").Key("enabled").MustBool(false) { // if app.config.Section("password_resets").Key("enabled").MustBool(false) {
@ -729,10 +742,11 @@ func (app *appContext) ExtendExpiry(gc *gin.Context) {
// @Param EnableDisableReferralDTO body EnableDisableReferralDTO true "List of users" // @Param EnableDisableReferralDTO body EnableDisableReferralDTO true "List of users"
// @Param mode path string true "mode of template sourcing from 'invite' or 'profile'." // @Param mode path string true "mode of template sourcing from 'invite' or 'profile'."
// @Param source path string true "invite code or profile name, depending on what mode is." // @Param source path string true "invite code or profile name, depending on what mode is."
// @Param useExpiry path string true "with-expiry or none."
// @Success 200 {object} boolResponse // @Success 200 {object} boolResponse
// @Failure 400 {object} boolResponse // @Failure 400 {object} boolResponse
// @Failure 500 {object} boolResponse // @Failure 500 {object} boolResponse
// @Router /users/referral/{mode}/{source} [post] // @Router /users/referral/{mode}/{source}/{useExpiry} [post]
// @Security Bearer // @Security Bearer
// @tags Users // @tags Users
func (app *appContext) EnableReferralForUsers(gc *gin.Context) { func (app *appContext) EnableReferralForUsers(gc *gin.Context) {
@ -740,7 +754,7 @@ func (app *appContext) EnableReferralForUsers(gc *gin.Context) {
gc.BindJSON(&req) gc.BindJSON(&req)
mode := gc.Param("mode") mode := gc.Param("mode")
source := gc.Param("source") source := gc.Param("source")
useExpiry := gc.Param("useExpiry") == "with-expiry"
baseInv := Invite{} baseInv := Invite{}
if mode == "profile" { if mode == "profile" {
profile, ok := app.storage.GetProfileKey(source) profile, ok := app.storage.GetProfileKey(source)
@ -768,10 +782,16 @@ func (app *appContext) EnableReferralForUsers(gc *gin.Context) {
// 2. Generate referral invite. // 2. Generate referral invite.
inv := baseInv inv := baseInv
inv.Code = GenerateInviteCode() inv.Code = GenerateInviteCode()
expiryDelta := inv.ValidTill.Sub(inv.Created)
inv.Created = time.Now() inv.Created = time.Now()
inv.ValidTill = inv.Created.Add(REFERRAL_EXPIRY_DAYS * 24 * time.Hour) if useExpiry {
inv.ValidTill = inv.Created.Add(expiryDelta)
} else {
inv.ValidTill = inv.Created.Add(REFERRAL_EXPIRY_DAYS * 24 * time.Hour)
}
inv.IsReferral = true inv.IsReferral = true
inv.ReferrerJellyfinID = u inv.ReferrerJellyfinID = u
inv.UseReferralExpiry = useExpiry
app.storage.SetInvitesKey(inv.Code, inv) app.storage.SetInvitesKey(inv.Code, inv)
} }
} }

View File

@ -130,6 +130,11 @@
<div class="select ~neutral @low mb-4 unfocused"> <div class="select ~neutral @low mb-4 unfocused">
<select id="enable-referrals-user-invites"></select> <select id="enable-referrals-user-invites"></select>
</div> </div>
<label class="switch mb-4">
<input type="checkbox" id="enable-referrals-user-expiry">
<span>{{ .strings.useInviteExpiry }}</span>
<span class="flex flex-row support mt-2">{{ .strings.useInviteExpiryNote }}</span>
</label>
<label> <label>
<input type="submit" class="unfocused"> <input type="submit" class="unfocused">
<span class="button ~urge @low full-width center supra submit">{{ .strings.apply }}</span> <span class="button ~urge @low full-width center supra submit">{{ .strings.apply }}</span>
@ -144,6 +149,11 @@
<div class="select ~neutral @low mb-4 mt-2"> <div class="select ~neutral @low mb-4 mt-2">
<select id="enable-referrals-profile-invites"></select> <select id="enable-referrals-profile-invites"></select>
</div> </div>
<label class="switch mb-4">
<input type="checkbox" id="enable-referrals-profile-expiry">
<span>{{ .strings.useInviteExpiry }}</span>
<span class="flex flex-row support mt-2">{{ .strings.useInviteExpiryNote }}</span>
</label>
<label> <label>
<input type="submit" class="unfocused"> <input type="submit" class="unfocused">
<span class="button ~urge @low full-width center supra submit">{{ .strings.apply }}</span> <span class="button ~urge @low full-width center supra submit">{{ .strings.apply }}</span>

View File

@ -155,7 +155,7 @@
<div> <div>
<div class="card @low dark:~d_neutral unfocused" id="card-referrals"> <div class="card @low dark:~d_neutral unfocused" id="card-referrals">
<span class="heading mb-2">{{ .strings.referrals }}</span> <span class="heading mb-2">{{ .strings.referrals }}</span>
<aside class="aside ~neutral my-4 col">{{ .strings.referralsDescription }}</aside> <aside class="aside ~neutral my-4 col user-referrals-description"></aside>
<div class="row flex-expand"> <div class="row flex-expand">
<div class="user-referrals-info"></div> <div class="user-referrals-info"></div>
<div class="grid my-2"> <div class="grid my-2">

View File

@ -76,6 +76,8 @@
"disableReferrals": "Disable Referrals", "disableReferrals": "Disable Referrals",
"enableReferralsDescription": "Give users a personal referral link similiar to an invite, to send to friends/family. Can be sourced from a referral template in a profile, or from an existing invite.", "enableReferralsDescription": "Give users a personal referral link similiar to an invite, to send to friends/family. Can be sourced from a referral template in a profile, or from an existing invite.",
"enableReferralsProfileDescription": "Give users created with this profile a personal referral link similiar to an invite, to send to friends/family. Create an invite with the desired settings, then select it here. Each referral will then be based on this invite. You can delete the invite once complete.", "enableReferralsProfileDescription": "Give users created with this profile a personal referral link similiar to an invite, to send to friends/family. Create an invite with the desired settings, then select it here. Each referral will then be based on this invite. You can delete the invite once complete.",
"useInviteExpiry": "Set expiry from profile/invite",
"useInviteExpiryNote": "By default, invites expire after 90 days but can be renewed by the user. Enable for the referral to be disabled after the time set.",
"applyHomescreenLayout": "Apply homescreen layout", "applyHomescreenLayout": "Apply homescreen layout",
"sendDeleteNotificationEmail": "Send notification message", "sendDeleteNotificationEmail": "Send notification message",
"sendDeleteNotifiationExample": "Your account has been deleted.", "sendDeleteNotifiationExample": "Your account has been deleted.",

View File

@ -36,6 +36,7 @@
"resetSentDescription": "If an account with the given username/contact method exists, a password reset link has been sent via all contact methods available. The code will expire in 30 minutes.", "resetSentDescription": "If an account with the given username/contact method exists, a password reset link has been sent via all contact methods available. The code will expire in 30 minutes.",
"changePassword": "Change Password", "changePassword": "Change Password",
"referralsDescription": "Invite friends & family to Jellyfin with this link. Come back here for a new one if it expires.", "referralsDescription": "Invite friends & family to Jellyfin with this link. Come back here for a new one if it expires.",
"referralsWithExpiryDescription": "Invite friends & family to Jellyfin with this link. The link will be disabled once it expires.",
"copyReferral": "Copy Link", "copyReferral": "Copy Link",
"invitedBy": "You were invited by user {user}." "invitedBy": "You were invited by user {user}."
}, },

View File

@ -424,7 +424,8 @@ type GetMyReferralRespDTO struct {
Code string `json:"code"` Code string `json:"code"`
RemainingUses int `json:"remaining_uses"` RemainingUses int `json:"remaining_uses"`
NoLimit bool `json:"no_limit"` NoLimit bool `json:"no_limit"`
Expiry int64 `json:"expiry"` // Come back after this time to get a new referral Expiry int64 `json:"expiry"` // Come back after this time to get a new referral (if UseExpiry, a new one can't be made).
UseExpiry bool `json:"use_expiry"`
} }
type EnableDisableReferralDTO struct { type EnableDisableReferralDTO struct {

View File

@ -229,9 +229,9 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
} }
api.POST(p+"/matrix/login", app.MatrixLogin) api.POST(p+"/matrix/login", app.MatrixLogin)
if app.config.Section("user_page").Key("referrals").MustBool(false) { if app.config.Section("user_page").Key("referrals").MustBool(false) {
api.POST(p+"/users/referral/:mode/:source", app.EnableReferralForUsers) api.POST(p+"/users/referral/:mode/:source/:useExpiry", app.EnableReferralForUsers)
api.DELETE(p+"/users/referral", app.DisableReferralForUsers) api.DELETE(p+"/users/referral", app.DisableReferralForUsers)
api.POST(p+"/profiles/referral/:profile/:invite", app.EnableReferralForProfile) api.POST(p+"/profiles/referral/:profile/:invite/:useExpiry", app.EnableReferralForProfile)
api.DELETE(p+"/profiles/referral/:profile", app.DisableReferralForProfile) api.DELETE(p+"/profiles/referral/:profile", app.DisableReferralForProfile)
} }

View File

@ -659,15 +659,15 @@ type Invite struct {
UserMinutes int `json:"user-minutes,omitempty"` UserMinutes int `json:"user-minutes,omitempty"`
SendTo string `json:"email"` SendTo string `json:"email"`
// Used to be stored as formatted time, now as Unix. // Used to be stored as formatted time, now as Unix.
UsedBy [][]string `json:"used-by"` UsedBy [][]string `json:"used-by"`
Notify map[string]map[string]bool `json:"notify"` Notify map[string]map[string]bool `json:"notify"`
Profile string `json:"profile"` Profile string `json:"profile"`
Label string `json:"label,omitempty"` Label string `json:"label,omitempty"`
UserLabel string `json:"user_label,omitempty" example:"Friend"` // Label to apply to users created w/ this invite. UserLabel string `json:"user_label,omitempty" example:"Friend"` // Label to apply to users created w/ this invite.
Captchas map[string]Captcha // Map of Captcha IDs to images & answers Captchas map[string]Captcha // Map of Captcha IDs to images & answers
IsReferral bool `json:"is_referral" badgerhold:"index"` IsReferral bool `json:"is_referral" badgerhold:"index"`
ReferrerJellyfinID string `json:"referrer_id"` ReferrerJellyfinID string `json:"referrer_id"`
ReferrerTemplateForProfile string UseReferralExpiry bool `json:"use_referral_expiry"`
} }
type Captcha struct { type Captcha struct {

View File

@ -783,6 +783,7 @@ export class accountsList {
private _userSelect = document.getElementById("modify-user-users") as HTMLSelectElement; private _userSelect = document.getElementById("modify-user-users") as HTMLSelectElement;
private _referralsProfileSelect = document.getElementById("enable-referrals-user-profiles") as HTMLSelectElement; private _referralsProfileSelect = document.getElementById("enable-referrals-user-profiles") as HTMLSelectElement;
private _referralsInviteSelect = document.getElementById("enable-referrals-user-invites") as HTMLSelectElement; private _referralsInviteSelect = document.getElementById("enable-referrals-user-invites") as HTMLSelectElement;
private _referralsExpiry = document.getElementById("enable-referrals-user-expiry") as HTMLInputElement;
private _searchBox = document.getElementById("accounts-search") as HTMLInputElement; private _searchBox = document.getElementById("accounts-search") as HTMLInputElement;
private _search: Search; private _search: Search;
@ -1578,7 +1579,7 @@ export class accountsList {
send["from"] = "invite"; send["from"] = "invite";
send["id"] = this._referralsInviteSelect.value; send["id"] = this._referralsInviteSelect.value;
} }
_post("/users/referral/" + send["from"] + "/" + (send["id"] ? send["id"] : send["profile"]), send, (req: XMLHttpRequest) => { _post("/users/referral/" + send["from"] + "/" + (send["id"] ? send["id"] : send["profile"]) + "/" + (this._referralsExpiry.checked ? "with-expiry" : "none"), send, (req: XMLHttpRequest) => {
if (req.readyState == 4) { if (req.readyState == 4) {
toggleLoader(button); toggleLoader(button);
if (req.status == 400) { if (req.status == 400) {
@ -1593,6 +1594,7 @@ export class accountsList {
}; };
this._enableReferralsProfile.checked = true; this._enableReferralsProfile.checked = true;
this._enableReferralsInvite.checked = false; this._enableReferralsInvite.checked = false;
this._referralsExpiry.checked = false;
window.modals.enableReferralsUser.show(); window.modals.enableReferralsUser.show();
} }

View File

@ -225,6 +225,7 @@ export class ProfileEditor {
enableReferrals = (name: string) => { enableReferrals = (name: string) => {
const referralsInviteSelect = document.getElementById("enable-referrals-profile-invites") as HTMLSelectElement; const referralsInviteSelect = document.getElementById("enable-referrals-profile-invites") as HTMLSelectElement;
const referralsExpiry = document.getElementById("enable-referrals-profile-expiry") as HTMLInputElement;
_get("/invites", null, (req: XMLHttpRequest) => { _get("/invites", null, (req: XMLHttpRequest) => {
if (req.readyState != 4 || req.status != 200) return; if (req.readyState != 4 || req.status != 200) return;
@ -257,7 +258,7 @@ export class ProfileEditor {
"invite": referralsInviteSelect.value "invite": referralsInviteSelect.value
}; };
_post("/profiles/referral/" + send["profile"] + "/" + send["invite"], send, (req: XMLHttpRequest) => { _post("/profiles/referral/" + send["profile"] + "/" + send["invite"] + "/" + (referralsExpiry.checked ? "with-expiry" : "none"), send, (req: XMLHttpRequest) => {
if (req.readyState == 4) { if (req.readyState == 4) {
toggleLoader(button); toggleLoader(button);
if (req.status == 400) { if (req.status == 400) {
@ -270,6 +271,7 @@ export class ProfileEditor {
} }
}); });
}; };
referralsExpiry.checked = false;
window.modals.profiles.close(); window.modals.profiles.close();
window.modals.enableReferralsProfile.show(); window.modals.enableReferralsProfile.show();
}; };

View File

@ -116,6 +116,7 @@ interface MyReferral {
remaining_uses: number; remaining_uses: number;
no_limit: boolean; no_limit: boolean;
expiry: number; expiry: number;
use_expiry: boolean;
} }
interface ContactDTO { interface ContactDTO {
@ -252,6 +253,7 @@ class ReferralCard {
private _url: string; private _url: string;
private _expiry: Date; private _expiry: Date;
private _expiryUnix: number; private _expiryUnix: number;
private _useExpiry: boolean;
private _remainingUses: number; private _remainingUses: number;
private _noLimit: boolean; private _noLimit: boolean;
@ -259,6 +261,7 @@ class ReferralCard {
private _infoArea: HTMLDivElement; private _infoArea: HTMLDivElement;
private _remainingUsesEl: HTMLSpanElement; private _remainingUsesEl: HTMLSpanElement;
private _expiryEl: HTMLSpanElement; private _expiryEl: HTMLSpanElement;
private _descriptionEl: HTMLSpanElement;
get code(): string { return this._code; } get code(): string { return this._code; }
set code(c: string) { set code(c: string) {
@ -294,11 +297,22 @@ class ReferralCard {
this._expiry = new Date(expiryUnix * 1000); this._expiry = new Date(expiryUnix * 1000);
this._expiryEl.textContent = toDateString(this._expiry); this._expiryEl.textContent = toDateString(this._expiry);
} }
get use_expiry(): boolean { return this._useExpiry; }
set use_expiry(v: boolean) {
this._useExpiry = v;
if (v) {
this._descriptionEl.textContent = window.lang.strings("referralsWithExpiryDescription");
} else {
this._descriptionEl.textContent = window.lang.strings("referralsDescription");
}
}
constructor(card: HTMLElement) { constructor(card: HTMLElement) {
this._card = card; this._card = card;
this._button = this._card.querySelector(".user-referrals-button") as HTMLButtonElement; this._button = this._card.querySelector(".user-referrals-button") as HTMLButtonElement;
this._infoArea = this._card.querySelector(".user-referrals-info") as HTMLDivElement; this._infoArea = this._card.querySelector(".user-referrals-info") as HTMLDivElement;
this._descriptionEl = this._card.querySelector(".user-referrals-description") as HTMLSpanElement;
this._infoArea.innerHTML = ` this._infoArea.innerHTML = `
<div class="row my-3"> <div class="row my-3">
@ -344,6 +358,7 @@ class ReferralCard {
this.no_limit = referral.no_limit; this.no_limit = referral.no_limit;
this.expiry = referral.expiry; this.expiry = referral.expiry;
this._card.classList.remove("unfocused"); this._card.classList.remove("unfocused");
this.use_expiry = referral.use_expiry;
}; };
} }