Improve MineGeo exploration and mapping features
This commit is contained in:
152
components/AccountSettings.tsx
Normal file
152
components/AccountSettings.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
"use client";
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { UserRound, LockKeyhole, Mail } from "lucide-react";
|
||||
import { api } from "./StartGame";
|
||||
import { PasswordForm } from "./PasswordForm";
|
||||
import { displayNameSchema } from "@/shared/account";
|
||||
export function AccountSettings({
|
||||
email,
|
||||
displayName,
|
||||
passwordUpdated = false,
|
||||
}: {
|
||||
email: string;
|
||||
displayName: string;
|
||||
passwordUpdated?: boolean;
|
||||
}) {
|
||||
const [name, setName] = useState(displayName),
|
||||
[message, setMessage] = useState(""),
|
||||
[error, setError] = useState(""),
|
||||
[busy, setBusy] = useState(false);
|
||||
const [recoveryMessage, setRecoveryMessage] = useState(""),
|
||||
[recoveryError, setRecoveryError] = useState(""),
|
||||
[sending, setSending] = useState(false);
|
||||
const router = useRouter();
|
||||
async function save(event: React.FormEvent) {
|
||||
event.preventDefault();
|
||||
setError("");
|
||||
setMessage("");
|
||||
const valid = displayNameSchema.safeParse(name);
|
||||
if (!valid.success) {
|
||||
setError(valid.error.issues[0].message);
|
||||
return;
|
||||
}
|
||||
setBusy(true);
|
||||
try {
|
||||
const data = await api<{ profile: { display_name: string } }>(
|
||||
"profile",
|
||||
{ displayName: valid.data },
|
||||
"PATCH",
|
||||
);
|
||||
setName(data.profile.display_name);
|
||||
setMessage("Display name saved.");
|
||||
router.refresh();
|
||||
} catch (e) {
|
||||
setError((e as Error).message);
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
async function recover() {
|
||||
setSending(true);
|
||||
setRecoveryError("");
|
||||
try {
|
||||
const data = await api<{ message: string }>("auth", {
|
||||
action: "recover",
|
||||
email,
|
||||
});
|
||||
setRecoveryMessage(data.message);
|
||||
} catch (e) {
|
||||
setRecoveryError((e as Error).message);
|
||||
} finally {
|
||||
setSending(false);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className="settings-grid">
|
||||
<section className="settings-card">
|
||||
<div className="settings-heading">
|
||||
<UserRound size={22} />
|
||||
<div>
|
||||
<h2>Your public profile.</h2>
|
||||
<p className="muted">A name for the maps and places you share.</p>
|
||||
</div>
|
||||
</div>
|
||||
<form onSubmit={save}>
|
||||
<label className="field">
|
||||
Display name
|
||||
<input
|
||||
name="displayName"
|
||||
autoComplete="nickname"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
minLength={2}
|
||||
maxLength={32}
|
||||
required
|
||||
disabled={busy}
|
||||
/>
|
||||
<small>
|
||||
2–32 characters. Display names do not have to be unique.
|
||||
</small>
|
||||
</label>
|
||||
<div className="account-email">
|
||||
<Mail size={17} />
|
||||
<div>
|
||||
<span>Email address</span>
|
||||
<strong>{email}</strong>
|
||||
<small>Used for sign-in and recovery. Only you can see it.</small>
|
||||
</div>
|
||||
</div>
|
||||
{error && (
|
||||
<p role="alert" className="error-message">
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
{message && (
|
||||
<p role="status" className="success-message">
|
||||
{message}
|
||||
</p>
|
||||
)}
|
||||
<button className="button primary" disabled={busy}>
|
||||
{busy ? "Saving…" : "Save display name"}
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
<section className="settings-card">
|
||||
<div className="settings-heading">
|
||||
<LockKeyhole size={22} />
|
||||
<div>
|
||||
<h2>Password & security.</h2>
|
||||
<p className="muted">Keep your account yours.</p>
|
||||
</div>
|
||||
</div>
|
||||
{passwordUpdated && (
|
||||
<p role="status" className="success-message">
|
||||
Your password has been updated.
|
||||
</p>
|
||||
)}
|
||||
<PasswordForm />
|
||||
<div className="settings-recovery">
|
||||
<h3>Signed up with an email link?</h3>
|
||||
<p className="muted">
|
||||
Set your first password, or recover a forgotten one, using a link
|
||||
sent to your account email.
|
||||
</p>
|
||||
<button className="button" disabled={sending} onClick={recover}>
|
||||
{sending ? "Sending…" : "Email me a password reset link"}
|
||||
</button>
|
||||
{recoveryMessage && (
|
||||
<p role="status" className="success-message">
|
||||
{recoveryMessage}
|
||||
</p>
|
||||
)}
|
||||
{recoveryError && (
|
||||
<p role="alert" className="error-message">
|
||||
{recoveryError}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user