feat: notification settings
All checks were successful
continuous-integration/drone Build is passing
All checks were successful
continuous-integration/drone Build is passing
This commit is contained in:
22
src/api.ts
22
src/api.ts
@ -9,6 +9,12 @@ export type ApiResponse<T> = ApiResponseBase & {
|
||||
data: T;
|
||||
};
|
||||
|
||||
export interface AccountDetail {
|
||||
email?: string;
|
||||
contact_nip17: boolean;
|
||||
contact_email: boolean;
|
||||
}
|
||||
|
||||
export interface VmCostPlan {
|
||||
id: number;
|
||||
name: string;
|
||||
@ -108,7 +114,21 @@ export class LNVpsApi {
|
||||
constructor(
|
||||
readonly url: string,
|
||||
readonly publisher: EventPublisher | undefined,
|
||||
) {}
|
||||
) { }
|
||||
|
||||
async getAccount() {
|
||||
const { data } = await this.#handleResponse<ApiResponse<AccountDetail>>(
|
||||
await this.#req("/api/v1/account", "GET"),
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
async updateAccount(acc: AccountDetail) {
|
||||
const { data } = await this.#handleResponse<ApiResponse<void>>(
|
||||
await this.#req("/api/v1/account", "PATCH", acc),
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
async listVms() {
|
||||
const { data } = await this.#handleResponse<ApiResponse<Array<VmInstance>>>(
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useContext, useSyncExternalStore } from "react";
|
||||
import { useContext, useMemo, useSyncExternalStore } from "react";
|
||||
import { LoginState } from "../login";
|
||||
import { SnortContext } from "@snort/system-react";
|
||||
import { LNVpsApi } from "../api";
|
||||
@ -10,12 +10,12 @@ export default function useLogin() {
|
||||
() => LoginState.snapshot(),
|
||||
);
|
||||
const system = useContext(SnortContext);
|
||||
return session
|
||||
return useMemo(() => session
|
||||
? {
|
||||
type: session.type,
|
||||
publicKey: session.publicKey,
|
||||
system,
|
||||
api: new LNVpsApi(ApiUrl, LoginState.getSigner()),
|
||||
}
|
||||
: undefined;
|
||||
: undefined, [session, system]);
|
||||
}
|
||||
|
@ -41,3 +41,7 @@ textarea,
|
||||
select {
|
||||
@apply border-none rounded-xl bg-neutral-900 p-2;
|
||||
}
|
||||
|
||||
input:disabled {
|
||||
@apply text-neutral-200/50;
|
||||
}
|
@ -1,45 +1,91 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { VmInstance } from "../api";
|
||||
import { AccountDetail, LNVpsApi, VmInstance } from "../api";
|
||||
import useLogin from "../hooks/login";
|
||||
import VpsInstanceRow from "../components/vps-instance";
|
||||
import { hexToBech32 } from "@snort/shared";
|
||||
import { Icon } from "../components/icon";
|
||||
import { AsyncButton } from "../components/button";
|
||||
|
||||
export default function AccountPage() {
|
||||
const login = useLogin();
|
||||
const [acc, setAcc] = useState<AccountDetail>();
|
||||
const [editEmail, setEditEmail] = useState(false);
|
||||
const [vms, setVms] = useState<Array<VmInstance>>([]);
|
||||
|
||||
async function loadVms() {
|
||||
if (!login?.api) return;
|
||||
const vms = await login?.api.listVms();
|
||||
async function loadVms(api: LNVpsApi) {
|
||||
const vms = await api.listVms();
|
||||
setVms(vms);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadVms();
|
||||
const t = setInterval(() => loadVms(), 5_000);
|
||||
return () => clearInterval(t);
|
||||
if (login?.api) {
|
||||
loadVms(login.api);
|
||||
login.api.getAccount().then(setAcc);
|
||||
const t = setInterval(() => {
|
||||
loadVms(login.api);
|
||||
}, 5_000);
|
||||
return () => clearInterval(t);
|
||||
}
|
||||
}, [login]);
|
||||
|
||||
const npub = hexToBech32("npub", login?.publicKey);
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-2">
|
||||
Your Public Key:
|
||||
<pre className="bg-neutral-800 rounded-md px-3 py-2 select-all text-sm">{npub}</pre>
|
||||
function notifications() {
|
||||
return <>
|
||||
<h3>Notification Settings</h3>
|
||||
<div className="flex gap-2 items-center">
|
||||
<input type="checkbox" checked={acc?.contact_email ?? false} onChange={(e) => {
|
||||
setAcc((s) => (s ? { ...s, contact_email: e.target.checked } : undefined));
|
||||
}} />
|
||||
Email
|
||||
<input type="checkbox" checked={acc?.contact_nip17 ?? false} onChange={(e) => {
|
||||
setAcc((s) => (s ? { ...s, contact_nip17: e.target.checked } : undefined));
|
||||
}} />
|
||||
Nostr DM
|
||||
</div>
|
||||
<h3>My Resources</h3>
|
||||
<div className="rounded-xl bg-red-400 text-black p-3 font-bold">
|
||||
Something doesnt look right? <br />
|
||||
Please contact support on: {" "}
|
||||
<a href={`mailto:sales@lnvps.net?subject=[${npub}]%20Account%20Query`} className="underline">
|
||||
sales@lnvps.net
|
||||
</a>
|
||||
<div className="flex gap-2 items-center">
|
||||
<h4>Email</h4>
|
||||
<input type="text" disabled={!editEmail} value={acc?.email} onChange={e => setAcc(s => (s ? { ...s, email: e.target.value } : undefined))} />
|
||||
{!editEmail && <Icon name="pencil" onClick={() => setEditEmail(true)} />}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
{vms.map((a) => (
|
||||
<VpsInstanceRow key={a.id} vm={a} onReload={loadVms} />
|
||||
))}
|
||||
<div>
|
||||
<AsyncButton onClick={async () => {
|
||||
if (login?.api && acc) {
|
||||
await login.api.updateAccount(acc);
|
||||
const newAcc = await login.api.getAccount();
|
||||
setAcc(newAcc);
|
||||
setEditEmail(false);
|
||||
}
|
||||
}}>
|
||||
Save
|
||||
</AsyncButton>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
const npub = hexToBech32("npub", login?.publicKey);
|
||||
const subjectLine = `[${npub}] Account Query`;
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
Your Public Key:
|
||||
<pre className="bg-neutral-900 rounded-md px-3 py-2 select-all text-sm">{npub}</pre>
|
||||
{notifications()}
|
||||
<h3>My Resources</h3>
|
||||
<div className="rounded-xl bg-red-400 text-black p-3">
|
||||
Something doesnt look right? <br />
|
||||
Please contact support on: {" "}
|
||||
<a href={`mailto:sales@lnvps.net?subject=${encodeURIComponent(subjectLine)}`} className="underline">
|
||||
sales@lnvps.net
|
||||
</a>
|
||||
<br />
|
||||
<b>Please include your public key in all communications.</b>
|
||||
</div>
|
||||
{vms.map((a) => (
|
||||
<VpsInstanceRow key={a.id} vm={a} onReload={() => {
|
||||
if (login?.api) {
|
||||
loadVms(login.api);
|
||||
}
|
||||
}} />
|
||||
))}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user