mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-05 08:06:19 +00:00
fix minor ux changes
This commit is contained in:
parent
582009aa38
commit
49ea8f9136
2 changed files with 243 additions and 219 deletions
|
|
@ -31,6 +31,9 @@ const CREDIT_FEATURE_ID = "usd_credits"
|
|||
const TOP_UP_PLAN_ID = "credits_topup"
|
||||
const TOP_UP_AMOUNTS = [10, 25, 50, 100] as const
|
||||
|
||||
const SURFACE_SHADOW =
|
||||
"0 2.842px 14.211px 0 rgba(0,0,0,0.25), 0.711px 0.711px 0.711px 0 rgba(255,255,255,0.10) inset"
|
||||
|
||||
type BillingInvoice = {
|
||||
planIds?: string[]
|
||||
stripeId: string
|
||||
|
|
@ -694,227 +697,247 @@ export default function Billing() {
|
|||
</SettingsCard>
|
||||
</section>
|
||||
|
||||
<Dialog open={isCreditsDialogOpen} onOpenChange={setIsCreditsDialogOpen}>
|
||||
<DialogContent
|
||||
showCloseButton={false}
|
||||
className="w-[min(560px,calc(100vw-32px))] rounded-[18px] border border-[#1C2B3E] bg-[#0B0D12] p-6 shadow-[0px_18px_70px_rgba(0,0,0,0.72)]"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<p
|
||||
className={cn(
|
||||
dmSans125ClassName(),
|
||||
"text-[22px] font-semibold tracking-[-0.22px] text-[#FAFAFA]",
|
||||
)}
|
||||
>
|
||||
Buy Credits
|
||||
</p>
|
||||
<p
|
||||
className={cn(
|
||||
dmSans125ClassName(),
|
||||
"mt-2 text-[15px] text-[#A3A3A3]",
|
||||
)}
|
||||
>
|
||||
Add USD to your balance for metered usage.
|
||||
</p>
|
||||
</div>
|
||||
<DialogClose asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="flex size-9 shrink-0 items-center justify-center rounded-full border border-white/10 bg-[#0D121A] text-[#737373] transition-colors hover:text-[#FAFAFA]"
|
||||
>
|
||||
<X className="size-5" />
|
||||
</button>
|
||||
</DialogClose>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 flex flex-col gap-5">
|
||||
<div className="flex flex-col gap-3">
|
||||
<p
|
||||
className={cn(
|
||||
dmSans125ClassName(),
|
||||
"text-[16px] font-semibold text-[#FAFAFA]",
|
||||
)}
|
||||
>
|
||||
Choose an amount
|
||||
</p>
|
||||
<FieldSelect
|
||||
value={topUpAmount}
|
||||
values={TOP_UP_AMOUNTS}
|
||||
prefix="$"
|
||||
onChange={(value) => {
|
||||
setTopUpAmount(value)
|
||||
setCustomTopUpAmount("")
|
||||
}}
|
||||
disabled={topUpPendingAmount !== null}
|
||||
/>
|
||||
<div className="flex flex-col gap-2">
|
||||
<label
|
||||
htmlFor="custom-topup-amount"
|
||||
className="text-[11px] font-bold uppercase tracking-[0.5px] text-[#737373]"
|
||||
>
|
||||
Custom amount (USD)
|
||||
</label>
|
||||
<input
|
||||
id="custom-topup-amount"
|
||||
inputMode="decimal"
|
||||
min={1}
|
||||
onChange={(event) => setCustomTopUpAmount(event.target.value)}
|
||||
placeholder="e.g. 75"
|
||||
type="number"
|
||||
value={customTopUpAmount}
|
||||
className="h-11 rounded-[10px] border border-white/10 bg-[#080B10] px-3 text-[14px] text-[#FAFAFA] outline-none placeholder:text-[#737373] focus:border-[#0054AD]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-px bg-white/[0.06]" />
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-[11px] font-bold uppercase tracking-[0.5px] text-[#737373]">
|
||||
Auto reload
|
||||
</p>
|
||||
<span className="text-[12px] text-[#737373]">
|
||||
{autoTopUpEnabled ? "on" : "off"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<Dialog
|
||||
open={isCreditsDialogOpen}
|
||||
onOpenChange={setIsCreditsDialogOpen}
|
||||
>
|
||||
<DialogContent
|
||||
showCloseButton={false}
|
||||
style={{ boxShadow: SURFACE_SHADOW }}
|
||||
className="w-[min(560px,calc(100vw-32px))] rounded-[22px] border border-white/[0.12] bg-[#1B1F24] p-6"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<p
|
||||
className={cn(
|
||||
dmSans125ClassName(),
|
||||
"text-[16px] text-[#FAFAFA]",
|
||||
"text-[22px] font-semibold tracking-[-0.22px] text-[#FAFAFA]",
|
||||
)}
|
||||
>
|
||||
Auto reload is {autoTopUpEnabled ? "enabled" : "disabled"}
|
||||
Buy Credits
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
disabled={
|
||||
isSavingAutoTopUp ||
|
||||
!isAdmin ||
|
||||
(!hasPaymentMethod && !activeAutoTopUp?.enabled)
|
||||
}
|
||||
onClick={() => handleAutoReloadToggle(!autoTopUpEnabled)}
|
||||
<p
|
||||
className={cn(
|
||||
dmSans125ClassName(),
|
||||
"inline-flex h-9 min-w-[96px] items-center justify-center rounded-[9px] border border-white/10 bg-[#0D121A] px-3 text-[13px] font-medium text-[#FAFAFA] transition-colors hover:bg-[#121A24] disabled:cursor-not-allowed disabled:opacity-45",
|
||||
"mt-2 text-[15px] text-[#A3A3A3]",
|
||||
)}
|
||||
>
|
||||
{autoTopUpEnabled ? "Disable" : "Enable"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{!hasPaymentMethod && !activeAutoTopUp?.enabled ? (
|
||||
<p className="text-[13px] text-[#737373]">
|
||||
Save a card in Manage Billing to enable automatic reloads.
|
||||
Add USD to your balance for metered usage.
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"grid gap-3 rounded-[10px] border border-white/[0.06] bg-[#0D121A] p-3 transition-[filter,opacity] sm:grid-cols-2",
|
||||
!autoTopUpEnabled &&
|
||||
"pointer-events-none select-none opacity-45 blur-[3px]",
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<label
|
||||
htmlFor="auto-topup-threshold"
|
||||
className="text-[11px] font-bold uppercase tracking-[0.5px] text-[#737373]"
|
||||
>
|
||||
Threshold (USD)
|
||||
</label>
|
||||
<input
|
||||
id="auto-topup-threshold"
|
||||
disabled={!autoTopUpEnabled || isSavingAutoTopUp}
|
||||
inputMode="decimal"
|
||||
min={0}
|
||||
onChange={(event) => {
|
||||
const value = Number.parseFloat(event.target.value)
|
||||
setAutoTopUpThreshold(Number.isFinite(value) ? value : 0)
|
||||
}}
|
||||
type="number"
|
||||
value={
|
||||
Number.isFinite(autoTopUpThreshold)
|
||||
? autoTopUpThreshold
|
||||
: ""
|
||||
}
|
||||
className="h-10 rounded-[8px] border border-white/10 bg-[#080B10] px-3 text-[13px] text-[#FAFAFA] outline-none focus:border-[#0054AD] disabled:opacity-60"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<label
|
||||
htmlFor="auto-topup-amount"
|
||||
className="text-[11px] font-bold uppercase tracking-[0.5px] text-[#737373]"
|
||||
>
|
||||
Reload amount (USD)
|
||||
</label>
|
||||
<input
|
||||
id="auto-topup-amount"
|
||||
disabled={!autoTopUpEnabled || isSavingAutoTopUp}
|
||||
inputMode="decimal"
|
||||
min={0.01}
|
||||
onChange={(event) => {
|
||||
const value = Number.parseFloat(event.target.value)
|
||||
setAutoTopUpAmount(Number.isFinite(value) ? value : 0)
|
||||
}}
|
||||
type="number"
|
||||
value={
|
||||
Number.isFinite(autoTopUpAmount) ? autoTopUpAmount : ""
|
||||
}
|
||||
className="h-10 rounded-[8px] border border-white/10 bg-[#080B10] px-3 text-[13px] text-[#FAFAFA] outline-none focus:border-[#0054AD] disabled:opacity-60"
|
||||
/>
|
||||
</div>
|
||||
<div className="sm:col-span-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void handleSaveAutoTopUp()}
|
||||
disabled={isSavingAutoTopUp || !isAdmin}
|
||||
className={cn(
|
||||
dmSans125ClassName(),
|
||||
"inline-flex h-9 w-full items-center justify-center gap-2 rounded-[8px] border border-white/10 bg-[#080B10] text-[13px] font-medium text-[#FAFAFA] transition-colors hover:bg-[#121A24] disabled:cursor-not-allowed disabled:opacity-60",
|
||||
)}
|
||||
>
|
||||
{isSavingAutoTopUp ? (
|
||||
<LoaderIcon className="size-3.5 animate-spin" />
|
||||
) : null}
|
||||
Save threshold & reload amount
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<DialogClose asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="flex size-9 shrink-0 items-center justify-center rounded-full border border-white/10 bg-[#0D121A] text-[#737373] transition-colors hover:text-[#FAFAFA]"
|
||||
>
|
||||
<X className="size-5" />
|
||||
</button>
|
||||
</DialogClose>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void handleTopUp(selectedTopUpAmount)}
|
||||
disabled={
|
||||
topUpPendingAmount !== null ||
|
||||
!isAdmin ||
|
||||
selectedTopUpAmount <= 0
|
||||
}
|
||||
className={cn(
|
||||
dmSans125ClassName(),
|
||||
"inline-flex h-11 w-full items-center justify-center gap-2 rounded-[10px] bg-[#0054AD] text-[14px] font-bold text-[#FAFAFA] transition-colors hover:bg-[#0B65C9] disabled:cursor-not-allowed disabled:opacity-60",
|
||||
)}
|
||||
>
|
||||
{topUpPendingAmount !== null ? (
|
||||
<LoaderIcon className="size-4 animate-spin" />
|
||||
<div className="mt-8 flex flex-col gap-5">
|
||||
<div className="flex flex-col gap-3">
|
||||
<p
|
||||
className={cn(
|
||||
dmSans125ClassName(),
|
||||
"text-[16px] font-semibold text-[#FAFAFA]",
|
||||
)}
|
||||
>
|
||||
Choose an amount
|
||||
</p>
|
||||
<FieldSelect
|
||||
value={topUpAmount}
|
||||
values={TOP_UP_AMOUNTS}
|
||||
prefix="$"
|
||||
onChange={(value) => {
|
||||
setTopUpAmount(value)
|
||||
setCustomTopUpAmount("")
|
||||
}}
|
||||
disabled={topUpPendingAmount !== null}
|
||||
/>
|
||||
<div className="flex flex-col gap-2">
|
||||
<label
|
||||
htmlFor="custom-topup-amount"
|
||||
className="text-[11px] font-bold uppercase tracking-[0.5px] text-[#737373]"
|
||||
>
|
||||
Custom amount (USD)
|
||||
</label>
|
||||
<input
|
||||
id="custom-topup-amount"
|
||||
inputMode="decimal"
|
||||
min={1}
|
||||
onChange={(event) =>
|
||||
setCustomTopUpAmount(event.target.value)
|
||||
}
|
||||
placeholder="e.g. 75"
|
||||
type="number"
|
||||
value={customTopUpAmount}
|
||||
className="h-11 rounded-[10px] border border-white/10 bg-[#080B10] px-3 text-[14px] text-[#FAFAFA] outline-none placeholder:text-[#737373] focus:border-[#0054AD]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-px bg-white/[0.06]" />
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-[11px] font-bold uppercase tracking-[0.5px] text-[#737373]">
|
||||
Auto reload
|
||||
</p>
|
||||
<span className="text-[12px] text-[#737373]">
|
||||
{autoTopUpEnabled ? "on" : "off"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<p
|
||||
className={cn(
|
||||
dmSans125ClassName(),
|
||||
"text-[16px] text-[#FAFAFA]",
|
||||
)}
|
||||
>
|
||||
Auto reload is{" "}
|
||||
{autoTopUpEnabled ? "enabled" : "disabled"}
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
disabled={
|
||||
isSavingAutoTopUp ||
|
||||
!isAdmin ||
|
||||
(!hasPaymentMethod && !activeAutoTopUp?.enabled)
|
||||
}
|
||||
onClick={() =>
|
||||
handleAutoReloadToggle(!autoTopUpEnabled)
|
||||
}
|
||||
className={cn(
|
||||
dmSans125ClassName(),
|
||||
"inline-flex h-9 min-w-[96px] items-center justify-center rounded-[9px] border border-white/10 bg-[#0D121A] px-3 text-[13px] font-medium text-[#FAFAFA] transition-colors hover:bg-[#121A24] disabled:cursor-not-allowed disabled:opacity-45",
|
||||
)}
|
||||
>
|
||||
{autoTopUpEnabled ? "Disable" : "Enable"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{!hasPaymentMethod && !activeAutoTopUp?.enabled ? (
|
||||
<p className="text-[13px] text-[#737373]">
|
||||
Save a card in Manage Billing to enable automatic
|
||||
reloads.
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"grid gap-3 rounded-[10px] border border-white/[0.06] bg-[#0D121A] p-3 transition-[filter,opacity] sm:grid-cols-2",
|
||||
!autoTopUpEnabled &&
|
||||
"pointer-events-none select-none opacity-45 blur-[3px]",
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<label
|
||||
htmlFor="auto-topup-threshold"
|
||||
className="text-[11px] font-bold uppercase tracking-[0.5px] text-[#737373]"
|
||||
>
|
||||
Threshold (USD)
|
||||
</label>
|
||||
<input
|
||||
id="auto-topup-threshold"
|
||||
disabled={!autoTopUpEnabled || isSavingAutoTopUp}
|
||||
inputMode="decimal"
|
||||
min={0}
|
||||
onChange={(event) => {
|
||||
const value = Number.parseFloat(
|
||||
event.target.value,
|
||||
)
|
||||
setAutoTopUpThreshold(
|
||||
Number.isFinite(value) ? value : 0,
|
||||
)
|
||||
}}
|
||||
type="number"
|
||||
value={
|
||||
Number.isFinite(autoTopUpThreshold)
|
||||
? autoTopUpThreshold
|
||||
: ""
|
||||
}
|
||||
className="h-10 rounded-[8px] border border-white/10 bg-[#080B10] px-3 text-[13px] text-[#FAFAFA] outline-none focus:border-[#0054AD] disabled:opacity-60"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<label
|
||||
htmlFor="auto-topup-amount"
|
||||
className="text-[11px] font-bold uppercase tracking-[0.5px] text-[#737373]"
|
||||
>
|
||||
Reload amount (USD)
|
||||
</label>
|
||||
<input
|
||||
id="auto-topup-amount"
|
||||
disabled={!autoTopUpEnabled || isSavingAutoTopUp}
|
||||
inputMode="decimal"
|
||||
min={0.01}
|
||||
onChange={(event) => {
|
||||
const value = Number.parseFloat(
|
||||
event.target.value,
|
||||
)
|
||||
setAutoTopUpAmount(
|
||||
Number.isFinite(value) ? value : 0,
|
||||
)
|
||||
}}
|
||||
type="number"
|
||||
value={
|
||||
Number.isFinite(autoTopUpAmount)
|
||||
? autoTopUpAmount
|
||||
: ""
|
||||
}
|
||||
className="h-10 rounded-[8px] border border-white/10 bg-[#080B10] px-3 text-[13px] text-[#FAFAFA] outline-none focus:border-[#0054AD] disabled:opacity-60"
|
||||
/>
|
||||
</div>
|
||||
<div className="sm:col-span-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void handleSaveAutoTopUp()}
|
||||
disabled={isSavingAutoTopUp || !isAdmin}
|
||||
className={cn(
|
||||
dmSans125ClassName(),
|
||||
"inline-flex h-9 w-full items-center justify-center gap-2 rounded-[8px] border border-white/10 bg-[#080B10] text-[13px] font-medium text-[#FAFAFA] transition-colors hover:bg-[#121A24] disabled:cursor-not-allowed disabled:opacity-60",
|
||||
)}
|
||||
>
|
||||
{isSavingAutoTopUp ? (
|
||||
<LoaderIcon className="size-3.5 animate-spin" />
|
||||
) : null}
|
||||
Save threshold & reload amount
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void handleTopUp(selectedTopUpAmount)}
|
||||
disabled={
|
||||
topUpPendingAmount !== null ||
|
||||
!isAdmin ||
|
||||
selectedTopUpAmount <= 0
|
||||
}
|
||||
className={cn(
|
||||
dmSans125ClassName(),
|
||||
"inline-flex h-11 w-full items-center justify-center gap-2 rounded-[10px] bg-[#0054AD] text-[14px] font-bold text-[#FAFAFA] transition-colors hover:bg-[#0B65C9] disabled:cursor-not-allowed disabled:opacity-60",
|
||||
)}
|
||||
>
|
||||
{topUpPendingAmount !== null ? (
|
||||
<LoaderIcon className="size-4 animate-spin" />
|
||||
) : null}
|
||||
Buy {formatUsd(selectedTopUpAmount)} in credits
|
||||
</button>
|
||||
|
||||
{!isAdmin ? (
|
||||
<p className="text-center text-[11px] text-[#737373]">
|
||||
Only owners/admins can purchase credits.
|
||||
</p>
|
||||
) : null}
|
||||
Buy {formatUsd(selectedTopUpAmount)} in credits
|
||||
</button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{!isAdmin ? (
|
||||
<p className="text-center text-[11px] text-[#737373]">
|
||||
Only owners/admins can purchase credits.
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{hasPaidPlan ? (
|
||||
{true || hasPaidPlan ? (
|
||||
<section className="flex flex-col gap-4">
|
||||
<SectionTitle>Credits</SectionTitle>
|
||||
<SettingsCard className="border border-dashed border-white/10 bg-[#14161A]/70">
|
||||
|
|
@ -953,7 +976,7 @@ export default function Billing() {
|
|||
)}
|
||||
>
|
||||
<Plus className="size-3.5" />
|
||||
{creditRemaining > 0 ? "Add more" : "Add credits"}
|
||||
{creditRemaining > 0 ? "Add more" : "Buy credits"}
|
||||
</button>
|
||||
</div>
|
||||
</SettingsCard>
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ function PillButton({
|
|||
children: React.ReactNode
|
||||
onClick: () => void
|
||||
disabled?: boolean
|
||||
variant?: "default" | "danger" | "primary"
|
||||
variant?: "default" | "ghost" | "primary"
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
|
|
@ -103,14 +103,10 @@ function PillButton({
|
|||
disabled={disabled}
|
||||
className={cn(
|
||||
dmSansClassName(),
|
||||
"inline-flex h-9 items-center justify-center gap-2 rounded-full border px-4 text-[13px] font-semibold transition-opacity cursor-pointer disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"bg-[#0D121A] text-[#FAFAFA] hover:opacity-80",
|
||||
"shadow-[inset_1.5px_1.5px_4.5px_rgba(0,0,0,0.7)]",
|
||||
variant === "primary"
|
||||
? "border-transparent"
|
||||
: variant === "danger"
|
||||
? "border-transparent"
|
||||
: "border-transparent",
|
||||
"inline-flex h-9 items-center justify-center gap-2 rounded-full px-4 text-[13px] font-semibold transition-[color,opacity] cursor-pointer disabled:cursor-not-allowed disabled:opacity-50",
|
||||
variant === "ghost"
|
||||
? "px-3 font-medium text-[#737373] hover:bg-white/[0.04] hover:text-[#A3A3A3]"
|
||||
: "border border-transparent bg-[#0D121A] text-[#FAFAFA] shadow-[inset_1.5px_1.5px_4.5px_rgba(0,0,0,0.7)] hover:opacity-80",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
|
@ -212,7 +208,7 @@ export function OrgContext() {
|
|||
<PillButton
|
||||
onClick={() => setConfirmDialog(enabled ? "disable" : "enable")}
|
||||
disabled={!settingsReady || updateSettings.isPending}
|
||||
variant={enabled ? "danger" : "primary"}
|
||||
variant={enabled ? "ghost" : "primary"}
|
||||
>
|
||||
{enabled ? "DISABLE" : "ENABLE"}
|
||||
</PillButton>
|
||||
|
|
@ -300,7 +296,9 @@ export function OrgContext() {
|
|||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2 border-t border-white/[0.08] bg-[#171B22] px-4 py-3">
|
||||
<PillButton onClick={handleCancel}>CANCEL</PillButton>
|
||||
<PillButton onClick={handleCancel} variant="ghost">
|
||||
CANCEL
|
||||
</PillButton>
|
||||
<PillButton
|
||||
onClick={handleSave}
|
||||
disabled={!dirty || updateSettings.isPending}
|
||||
|
|
@ -363,13 +361,16 @@ export function OrgContext() {
|
|||
</DialogPrimitive.Close>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
<PillButton onClick={() => setConfirmDialog(null)}>
|
||||
<PillButton
|
||||
onClick={() => setConfirmDialog(null)}
|
||||
variant="ghost"
|
||||
>
|
||||
CANCEL
|
||||
</PillButton>
|
||||
<PillButton
|
||||
onClick={handleConfirmToggle}
|
||||
disabled={updateSettings.isPending}
|
||||
variant={confirmDialog === "disable" ? "danger" : "primary"}
|
||||
variant="primary"
|
||||
>
|
||||
{updateSettings.isPending && (
|
||||
<LoaderIcon className="size-3.5 animate-spin" />
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue