+
diff --git a/lang/admin/en-us.json b/lang/admin/en-us.json
index fddf7e0..f38804b 100644
--- a/lang/admin/en-us.json
+++ b/lang/admin/en-us.json
@@ -17,6 +17,7 @@
"warning": "Warning",
"inviteInfiniteUsesWarning": "invites with infinite uses can be used abusively",
"inviteSendToEmail": "Send to",
+ "sentTo": "Sent to",
"create": "Create",
"apply": "Apply",
"select": "Select",
@@ -223,7 +224,7 @@
"restartRequired": "Restart required",
"required": "Required",
"syntax": "Syntax",
- "syntaxDescription": "Variables denoted as {varname}. If statements can evaluate truthfulness (e.g. {if messageAddress}Message sent to {messageAddress}{end}) or make basic comparisons (e.g. {if profile == \"Friends\"}Friend{else if profile != \"Admins\"}User{end})"
+ "syntaxDescription": "Variables denoted as {variable}. If statements can evaluate truthfulness (e.g. {ifTruth}) or make basic comparisons (e.g. {ifCompare})"
},
"notifications": {
"pathCopied": "Full path copied to clipboard.",
@@ -261,6 +262,7 @@
"errorLoadOmbiUsers": "Failed to load ombi users.",
"errorChangedEmailAddress": "Couldn't change email address of {n}.",
"errorFailureCheckLogs": "Failed (check console/logs)",
+ "errorCheckLogs": "Check console/logs",
"errorPartialFailureCheckLogs": "Partial failure (check console/logs)",
"errorUserCreated": "Failed to create user {n}.",
"errorSendWelcomeEmail": "Failed to send welcome message (check console/logs)",
@@ -272,7 +274,10 @@
"errorInvalidJSON": "Invalid JSON.",
"updateAvailable": "A new update is available, check settings.",
"noUpdatesAvailable": "No new updates available.",
- "runTask": "Triggered task."
+ "runTask": "Triggered task.",
+ "errorMultiUser": "Multiple matching users found",
+ "errorNoUser": "No matching user found",
+ "errorInvalidAddress": "Invalid address/name"
},
"quantityStrings": {
"modifySettingsFor": {
diff --git a/lang/common/en-us.json b/lang/common/en-us.json
index 19f285d..3a13d57 100644
--- a/lang/common/en-us.json
+++ b/lang/common/en-us.json
@@ -43,7 +43,8 @@
"referrals": "Referrals",
"inviteRemainingUses": "Remaining uses",
"internal": "Internal",
- "external": "External"
+ "external": "External",
+ "failed": "Failed"
},
"notifications": {
"errorLoginBlank": "The username and/or password were left blank.",
diff --git a/logmessages/logmessages.go b/logmessages/logmessages.go
index debd5d4..333ab5b 100644
--- a/logmessages/logmessages.go
+++ b/logmessages/logmessages.go
@@ -109,9 +109,11 @@ const (
GenerateInvite = "Generating new invite"
FailedGenerateInvite = "Failed to generate new invite: %v"
InvalidInviteCode = "Invalid invite code \"%s\""
+ FailedGetInvite = "Failed to get invite \"%s\": %v"
FailedSendToTooltipNoUser = "Failed: \"%s\" not found"
FailedSendToTooltipMultiUser = "Failed: \"%s\" linked to multiple users"
+ InvalidAddress = "invalid address \"%s\""
FailedParseTime = "Failed to parse time value: %v"
diff --git a/models.go b/models.go
index c536ae4..5f81f42 100644
--- a/models.go
+++ b/models.go
@@ -52,16 +52,16 @@ type enableDisableUserDTO struct {
}
type generateInviteDTO struct {
- Months int `json:"months" example:"0"` // Number of months
- Days int `json:"days" example:"1"` // Number of days
- Hours int `json:"hours" example:"2"` // Number of hours
- Minutes int `json:"minutes" example:"3"` // Number of minutes
- UserExpiry bool `json:"user-expiry"` // Whether or not user expiry is enabled
- UserMonths int `json:"user-months,omitempty" example:"1"` // Number of months till user expiry
- UserDays int `json:"user-days,omitempty" example:"1"` // Number of days till user expiry
- UserHours int `json:"user-hours,omitempty" example:"2"` // Number of hours till user expiry
- UserMinutes int `json:"user-minutes,omitempty" example:"3"` // Number of minutes till user expiry
- SendTo string `json:"send-to" example:"jeff@jellyf.in"` // Send invite to this address or discord name
+ Months int `json:"months" example:"0"` // Number of months
+ Days int `json:"days" example:"1"` // Number of days
+ Hours int `json:"hours" example:"2"` // Number of hours
+ Minutes int `json:"minutes" example:"3"` // Number of minutes
+ UserExpiry bool `json:"user-expiry"` // Whether or not user expiry is enabled
+ UserMonths int `json:"user-months,omitempty" example:"1"` // Number of months till user expiry
+ UserDays int `json:"user-days,omitempty" example:"1"` // Number of days till user expiry
+ UserHours int `json:"user-hours,omitempty" example:"2"` // Number of hours till user expiry
+ UserMinutes int `json:"user-minutes,omitempty" example:"3"` // Number of minutes till user expiry
+ sendInviteDTO
MultipleUses bool `json:"multiple-uses" example:"true"` // Allow multiple uses
NoLimit bool `json:"no-limit" example:"false"` // No invite use limit
RemainingUses int `json:"remaining-uses" example:"5"` // Remaining invite uses
@@ -70,6 +70,15 @@ type generateInviteDTO struct {
UserLabel string `json:"user_label,omitempty" example:"Friend"` // Label to apply to users created w/ this invite.
}
+type SendInviteDTO struct {
+ Invite string `json:"invite" example:"slakdaslkdl2342"` // Invite to apply to
+ sendInviteDTO
+}
+
+type sendInviteDTO struct {
+ SendTo string `json:"send-to" example:"jeff@jellyf.in"` // Send invite to this address or discord name
+}
+
type inviteProfileDTO struct {
Invite string `json:"invite" example:"slakdaslkdl2342"` // Invite to apply to
Profile string `json:"profile" example:"DefaultProfile"` // Profile to use
@@ -106,26 +115,28 @@ type newProfileDTO struct {
}
type inviteDTO struct {
- Code string `json:"code" example:"sajdlj23423j23"` // Invite code
- Months int `json:"months" example:"1"` // Number of months till expiry
- Days int `json:"days" example:"1"` // Number of days till expiry
- Hours int `json:"hours" example:"2"` // Number of hours till expiry
- Minutes int `json:"minutes" example:"3"` // Number of minutes till expiry
- UserExpiry bool `json:"user-expiry"` // Whether or not user expiry is enabled
- UserMonths int `json:"user-months,omitempty" example:"1"` // Number of months till user expiry
- UserDays int `json:"user-days,omitempty" example:"1"` // Number of days till user expiry
- UserHours int `json:"user-hours,omitempty" example:"2"` // Number of hours till user expiry
- UserMinutes int `json:"user-minutes,omitempty" example:"3"` // Number of minutes till user expiry
- Created int64 `json:"created" example:"1617737207510"` // Date of creation
- Profile string `json:"profile" example:"DefaultProfile"` // Profile used on this invite
- UsedBy map[string]int64 `json:"used-by,omitempty"` // Users who have used this invite mapped to their creation time in Epoch/Unix time
- NoLimit bool `json:"no-limit,omitempty"` // If true, invite can be used any number of times
- RemainingUses int `json:"remaining-uses,omitempty"` // Remaining number of uses (if applicable)
- SendTo string `json:"send_to,omitempty"` // Email/Discord username the invite was sent to (if applicable)
- NotifyExpiry bool `json:"notify-expiry,omitempty"` // Whether to notify the requesting user of expiry or not
- NotifyCreation bool `json:"notify-creation,omitempty"` // Whether to notify the requesting user of account creation or not
- Label string `json:"label,omitempty" example:"For Friends"` // Optional label for the invite
- UserLabel string `json:"user_label,omitempty" example:"Friend"` // Label to apply to users created w/ this invite.
+ Code string `json:"code" example:"sajdlj23423j23"` // Invite code
+ Months int `json:"months" example:"1"` // Number of months till expiry
+ Days int `json:"days" example:"1"` // Number of days till expiry
+ Hours int `json:"hours" example:"2"` // Number of hours till expiry
+ Minutes int `json:"minutes" example:"3"` // Number of minutes till expiry
+ UserExpiry bool `json:"user-expiry"` // Whether or not user expiry is enabled
+ UserMonths int `json:"user-months,omitempty" example:"1"` // Number of months till user expiry
+ UserDays int `json:"user-days,omitempty" example:"1"` // Number of days till user expiry
+ UserHours int `json:"user-hours,omitempty" example:"2"` // Number of hours till user expiry
+ UserMinutes int `json:"user-minutes,omitempty" example:"3"` // Number of minutes till user expiry
+ Created int64 `json:"created" example:"1617737207510"` // Date of creation
+ Profile string `json:"profile" example:"DefaultProfile"` // Profile used on this invite
+ UsedBy map[string]int64 `json:"used-by,omitempty"` // Users who have used this invite mapped to their creation time in Epoch/Unix time
+ NoLimit bool `json:"no-limit,omitempty"` // If true, invite can be used any number of times
+ RemainingUses int `json:"remaining-uses,omitempty"` // Remaining number of uses (if applicable)
+ SendTo string `json:"send_to,omitempty"` // DEPRECATED Email/Discord username the invite was sent to (if applicable)
+ SentTo SentToList `json:"sent_to,omitempty"` // Email/Discord usernames attempts were made to send this invite to, and a failure reason if failed.
+
+ NotifyExpiry bool `json:"notify-expiry,omitempty"` // Whether to notify the requesting user of expiry or not
+ NotifyCreation bool `json:"notify-creation,omitempty"` // Whether to notify the requesting user of account creation or not
+ Label string `json:"label,omitempty" example:"For Friends"` // Optional label for the invite
+ UserLabel string `json:"user_label,omitempty" example:"Friend"` // Label to apply to users created w/ this invite.
}
type getInvitesDTO struct {
diff --git a/package-lock.json b/package-lock.json
index 85f7035..0b261b7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"license": "ISC",
"dependencies": {
"@af-utils/scrollend-polyfill": "^0.0.14",
+ "@hrfee/simpletemplate": "^1.1.0",
"@ts-stack/markdown": "^1.4.0",
"@types/node": "^20.3.0",
"@webcoder49/code-input": "^2.7.2",
@@ -482,6 +483,12 @@
"node": ">=18"
}
},
+ "node_modules/@hrfee/simpletemplate": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@hrfee/simpletemplate/-/simpletemplate-1.1.0.tgz",
+ "integrity": "sha512-5H4/y7CegE4twstRPip/ms+OGUb+BPyVt+hriEpY88lTWW4I6jxzHFHmz6NDZmVyRa4RVIEVBxvtwk3RXKJVwA==",
+ "license": "MIT"
+ },
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
diff --git a/package.json b/package.json
index 2030c8c..3fbb61d 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"homepage": "https://github.com/hrfee/jfa-go#readme",
"dependencies": {
"@af-utils/scrollend-polyfill": "^0.0.14",
+ "@hrfee/simpletemplate": "^1.1.0",
"@ts-stack/markdown": "^1.4.0",
"@types/node": "^20.3.0",
"@webcoder49/code-input": "^2.7.2",
diff --git a/router.go b/router.go
index 8bde1c6..792f370 100644
--- a/router.go
+++ b/router.go
@@ -213,6 +213,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
api.GET(p+"/invites/count/used", app.GetInviteUsedCount)
api.DELETE(p+"/invites", app.DeleteInvite)
api.POST(p+"/invites/profile", app.SetProfile)
+ api.POST(p+"/invites/send", app.SendInvite)
api.GET(p+"/profiles", app.GetProfiles)
api.GET(p+"/profiles/names", app.GetProfileNames)
api.GET(p+"/profiles/raw/:name", app.GetRawProfile)
diff --git a/storage.go b/storage.go
index 9e4e9b1..3eb9783 100644
--- a/storage.go
+++ b/storage.go
@@ -769,20 +769,39 @@ type JellyseerrTemplate struct {
Notifications jellyseerr.NotificationsTemplate `json:"notifications,omitempty"`
}
+type SendFailureReason = string
+
+const (
+ CheckLogs SendFailureReason = "CheckLogs"
+ NoUser = "NoUser"
+ MultiUser = "MultiUser"
+ InvalidAddress = "InvalidAddress"
+)
+
+type SendFailure struct {
+ Address string `json:"address"`
+ Reason SendFailureReason `json:"reason"`
+}
+
+type SentToList struct {
+ Success []string `json:"success"`
+ Failed []SendFailure `json:"failed"`
+}
+
type Invite struct {
- Code string `badgerhold:"key"`
- Created time.Time `json:"created"`
- NoLimit bool `json:"no-limit"`
- RemainingUses int `json:"remaining-uses"`
- ValidTill time.Time `json:"valid_till"`
- UserExpiry bool `json:"user-duration"`
- UserMonths int `json:"user-months,omitempty"`
- UserDays int `json:"user-days,omitempty"`
- UserHours int `json:"user-hours,omitempty"`
- UserMinutes int `json:"user-minutes,omitempty"`
- SendTo string `json:"email"`
- // Used to be stored as formatted time, now as Unix.
- UsedBy [][]string `json:"used-by"`
+ Code string `badgerhold:"key"`
+ Created time.Time `json:"created"`
+ NoLimit bool `json:"no-limit"`
+ RemainingUses int `json:"remaining-uses"`
+ ValidTill time.Time `json:"valid_till"`
+ UserExpiry bool `json:"user-duration"`
+ UserMonths int `json:"user-months,omitempty"`
+ UserDays int `json:"user-days,omitempty"`
+ UserHours int `json:"user-hours,omitempty"`
+ UserMinutes int `json:"user-minutes,omitempty"`
+ SendTo string `json:"email"` // deprecated: use SentTo now.
+ SentTo SentToList `json:"sent-to,omitempty"`
+ UsedBy [][]string `json:"used-by"` // Used to be stored as formatted time, now as Unix.
Notify map[string]map[string]bool `json:"notify"`
Profile string `json:"profile"`
Label string `json:"label,omitempty"`
diff --git a/ts/admin.ts b/ts/admin.ts
index 6ce84d6..dbb8083 100644
--- a/ts/admin.ts
+++ b/ts/admin.ts
@@ -2,7 +2,7 @@ import { ThemeManager } from "./modules/theme.js";
import { lang, LangFile, loadLangSelector } from "./modules/lang.js";
import { Modal } from "./modules/modal.js";
import { Tabs, Tab } from "./modules/tabs.js";
-import { inviteList, createInvite } from "./modules/invites.js";
+import { DOMInviteList, createInvite } from "./modules/invites.js";
import { accountsList } from "./modules/accounts.js";
import { settingsList } from "./modules/settings.js";
import { activityList } from "./modules/activity.js";
@@ -105,7 +105,7 @@ var accounts = new accountsList();
var activity = new activityList();
-window.invites = new inviteList();
+window.invites = new DOMInviteList();
var settings = new settingsList();
diff --git a/ts/modules/accounts.ts b/ts/modules/accounts.ts
index 1057073..e3f03bd 100644
--- a/ts/modules/accounts.ts
+++ b/ts/modules/accounts.ts
@@ -1247,6 +1247,12 @@ export class accountsList extends PaginatedList {
this._search.showHideSearchOptionsHeader();
this.registerURLListener();
+
+ // Get rid of nasty CSS
+ window.modals.announce.onclose = () => {
+ const preview = document.getElementById("announce-preview") as HTMLDivElement;
+ preview.textContent = ``;
+ }
}
reload = (callback?: (resp: paginatedDTO) => void) => {
diff --git a/ts/modules/invites.ts b/ts/modules/invites.ts
index ba73e8d..c898cd8 100644
--- a/ts/modules/invites.ts
+++ b/ts/modules/invites.ts
@@ -123,20 +123,55 @@ class DOMInvite implements Invite {
} else {
chip.classList.add("button");
chip.parentElement.classList.add("h-full");
- if (address.includes("Failed")) {
+ if (address.includes(window.lang.strings("failed"))) {
icon.classList.remove("ri-mail-line");
icon.classList.add("ri-mail-close-line");
chip.classList.remove("~neutral");
chip.classList.add("~critical");
} else {
- address = "Sent to " + address;
icon.classList.remove("ri-mail-close-line");
icon.classList.add("ri-mail-line");
chip.classList.remove("~critical");
chip.classList.add("~neutral");
}
}
- tooltip.textContent = address;
+ // innerHTML as the newer sent_to re-uses this with HTML.
+ tooltip.innerHTML = address;
+ }
+
+ private _sent_to: SentToList;
+ get sent_to(): SentToList { return this._sent_to; }
+ set sent_to(v: SentToList) {
+ this._sent_to = v;
+ if (!v || !(v.success || v.failed)) return;
+ let text = "";
+ if (v.success && v.success.length > 0) {
+ text += window.lang.strings("sentTo") + ": " + v.success.join(", ") + "
"
+ }
+ if (v.failed && v.failed.length > 0) {
+ text += window.lang.strings("failed") + ": " + v.failed.map((el: SendFailure) => {
+ let err: string;
+ switch (el.reason) {
+ case "CheckLogs":
+ err = window.lang.notif("errorCheckLogs");
+ break;
+ case "NoUser":
+ err = window.lang.notif("errorNoUser");
+ break;
+ case "MultiUser":
+ err = window.lang.notif("errorMultiUser");
+ break;
+ case "InvalidAddress":
+ err = window.lang.notif("errorInvalidAddress");
+ break;
+ default:
+ err = el.reason;
+ break;
+ }
+ return el.address + " (" + err + ")";
+ }).join(", ");
+ }
+ if (text.length != 0) this.send_to = text;
}
private _usedBy: { [name: string]: number };
@@ -455,6 +490,7 @@ class DOMInvite implements Invite {
this.code = invite.code;
this.created = invite.created;
this.send_to = invite.send_to;
+ this.sent_to = invite.sent_to;
this.expiresIn = invite.expiresIn;
if (window.notificationsEnabled) {
this.notifyCreation = invite.notifyCreation;
@@ -477,7 +513,7 @@ class DOMInvite implements Invite {
remove = () => { this._container.remove(); }
}
-export class inviteList implements inviteList {
+export class DOMInviteList implements InviteList {
private _list: HTMLDivElement;
private _empty: boolean;
// since invite reload sends profiles, this event it broadcast so the createInvite object can load them.
@@ -493,7 +529,7 @@ export class inviteList implements inviteList {
};
public static readonly _inviteURLEvent = "invite-url";
- registerURLListener = () => document.addEventListener(inviteList._inviteURLEvent, (event: CustomEvent) => {
+ registerURLListener = () => document.addEventListener(DOMInviteList._inviteURLEvent, (event: CustomEvent) => {
this.focusInvite(event.detail);
})
@@ -587,12 +623,14 @@ export class inviteList implements inviteList {
}));
}
-export const inviteURLEvent = (id: string) => { return new CustomEvent(inviteList._inviteURLEvent, {"detail": id}) };
+export const inviteURLEvent = (id: string) => { return new CustomEvent(DOMInviteList._inviteURLEvent, {"detail": id}) };
-function parseInvite(invite: { [f: string]: string | number | { [name: string]: number } | boolean }): Invite {
+// FIXME: Please, i beg you, get rid of this horror!
+function parseInvite(invite: { [f: string]: string | number | { [name: string]: number } | boolean | SentToList }): Invite {
let parsed: Invite = {};
parsed.code = invite["code"] as string;
parsed.send_to = invite["send_to"] as string || "";
+ parsed.sent_to = invite["sent_to"] as SentToList || null;
parsed.label = invite["label"] as string || "";
parsed.user_label = invite["user_label"] as string || "";
let time = "";
diff --git a/ts/modules/lang.ts b/ts/modules/lang.ts
index df6a650..d437918 100644
--- a/ts/modules/lang.ts
+++ b/ts/modules/lang.ts
@@ -1,4 +1,5 @@
import { _get } from "../modules/common.js";
+import { Template } from "@hrfee/simpletemplate";
interface Meta {
name: string;
@@ -39,6 +40,15 @@ export class lang implements Lang {
return str;
}
+ template = (sect: string, key: string, subs: { [key: string]: any }): string => {
+ if (sect == "quantityStrings" || sect == "meta") { return ""; }
+ const map = new Map
();
+ for (let key of Object.keys(subs)) { map.set(key, subs[key]); }
+ const [out, err] = Template(this._lang[sect][key], map);
+ if (err != null) throw err;
+ return out;
+ }
+
quantity = (key: string, number: number): string => {
if (number == 1) {
return this._lang.quantityStrings[key].singular.replace("{n}", ""+number)
diff --git a/ts/modules/settings.ts b/ts/modules/settings.ts
index dfcd46b..126d27a 100644
--- a/ts/modules/settings.ts
+++ b/ts/modules/settings.ts
@@ -1626,7 +1626,8 @@ class MessageEditor {
} else {
for (let i = this._templ.conditionals.length-1; i >= 0; i--) {
let ci = i % colors.length;
- innerHTML += ''
+ // FIXME: Store full color strings (with ~) so tailwind sees them.
+ innerHTML += ''
}
this._conditionalsLabel.classList.remove("unfocused");
this._conditionals.innerHTML = innerHTML
@@ -1754,6 +1755,20 @@ class MessageEditor {
}
});
};
+
+ const descriptions = document.getElementsByClassName("editor-syntax-description") as HTMLCollectionOf;
+ for (let el of descriptions) {
+ el.innerHTML = window.lang.template("strings", "syntaxDescription", {
+ "variable": `{varname}`,
+ "ifTruth": `{if address}Message sent to {address}{end}`,
+ "ifCompare": `{if profile == "Friends"}Friend{else if profile != "Admins"}User{end}`
+ });
+ };
+
+ // Get rid of nasty CSS
+ window.modals.editor.onclose = () => {
+ this._preview.textContent = ``;
+ }
}
}
diff --git a/ts/typings/d.ts b/ts/typings/d.ts
index e29ca13..77327ce 100644
--- a/ts/typings/d.ts
+++ b/ts/typings/d.ts
@@ -48,7 +48,7 @@ declare interface GlobalWindow extends Window {
transitionEvent: string;
animationEvent: string;
tabs: Tabs;
- invites: inviteList;
+ invites: InviteList;
notifications: NotificationBox;
language: string;
lang: Lang;
@@ -61,6 +61,42 @@ declare interface GlobalWindow extends Window {
loginAppearance: string;
}
+declare interface InviteList {
+ empty: boolean;
+ invites: { [code: string]: Invite }
+ add: (invite: Invite) => void;
+ reload: (callback?: () => void) => void;
+ isInviteURL: () => boolean;
+ loadInviteURL: () => void;
+}
+
+declare interface Invite {
+ code?: string;
+ expiresIn?: string;
+ remainingUses?: string;
+ send_to?: string; // DEPRECATED: use sent_to instead.
+ sent_to?: SentToList;
+ usedBy?: { [name: string]: number };
+ created?: number;
+ notifyExpiry?: boolean;
+ notifyCreation?: boolean;
+ profile?: string;
+ label?: string;
+ user_label?: string;
+ userExpiry?: boolean;
+ userExpiryTime?: string;
+}
+
+declare interface SendFailure {
+ address: string;
+ reason: "CheckLogs" | "NoUser" | "MultiUser" | "InvalidAddress";
+}
+
+declare interface SentToList {
+ success: string[];
+ failed: SendFailure[];
+}
+
declare interface Update {
version: string;
commit: string;
@@ -83,6 +119,7 @@ declare interface Lang {
strings: (key: string) => string;
notif: (key: string) => string;
var: (sect: string, key: string, ...subs: string[]) => string;
+ template: (sect: string, key: string, subs: { [key: string]: string }) => string;
quantity: (key: string, number: number) => string;
}
@@ -131,31 +168,6 @@ declare interface Modals {
backups?: Modal;
}
-interface Invite {
- code?: string;
- expiresIn?: string;
- remainingUses?: string;
- send_to?: string;
- usedBy?: { [name: string]: number };
- created?: number;
- notifyExpiry?: boolean;
- notifyCreation?: boolean;
- profile?: string;
- label?: string;
- user_label?: string;
- userExpiry?: boolean;
- userExpiryTime?: string;
-}
-
-interface inviteList {
- empty: boolean;
- invites: { [code: string]: Invite }
- add: (invite: Invite) => void;
- reload: (callback?: () => void) => void;
- isInviteURL: () => boolean;
- loadInviteURL: () => void;
-}
-
interface paginatedDTO {
last_page: boolean;
}