implemented
This commit is contained in:
parent
a31bd8246c
commit
d3638e2e69
18 changed files with 1643 additions and 120 deletions
|
|
@ -6,15 +6,13 @@ import { api, ApiError } from "../api";
|
|||
import { sharedStyles } from "../styles/shared";
|
||||
import { Header } from "../components/Header";
|
||||
import { useRequireAuth } from "../hooks/useRequireAuth";
|
||||
import { components } from "../generated/api";
|
||||
import constants from "../../../shared/constants.json";
|
||||
|
||||
interface ProfileData {
|
||||
contact_email: string | null;
|
||||
telegram: string | null;
|
||||
signal: string | null;
|
||||
nostr_npub: string | null;
|
||||
godfather_email: string | null;
|
||||
}
|
||||
// Use generated type from OpenAPI schema
|
||||
type ProfileData = components["schemas"]["ProfileResponse"];
|
||||
|
||||
// UI-specific types (not from API)
|
||||
interface FormData {
|
||||
contact_email: string;
|
||||
telegram: string;
|
||||
|
|
@ -29,7 +27,9 @@ interface FieldErrors {
|
|||
nostr_npub?: string;
|
||||
}
|
||||
|
||||
// Client-side validation matching backend rules
|
||||
// Client-side validation using shared rules from constants
|
||||
const { telegram: telegramRules, signal: signalRules, nostrNpub: npubRules } = constants.validation;
|
||||
|
||||
function validateEmail(value: string): string | undefined {
|
||||
if (!value) return undefined;
|
||||
// More comprehensive email regex that matches email-validator behavior
|
||||
|
|
@ -43,15 +43,15 @@ function validateEmail(value: string): string | undefined {
|
|||
|
||||
function validateTelegram(value: string): string | undefined {
|
||||
if (!value) return undefined;
|
||||
if (!value.startsWith("@")) {
|
||||
return "Telegram handle must start with @";
|
||||
if (!value.startsWith(telegramRules.mustStartWith)) {
|
||||
return `Telegram handle must start with ${telegramRules.mustStartWith}`;
|
||||
}
|
||||
const handle = value.slice(1);
|
||||
if (handle.length < 1) {
|
||||
return "Telegram handle must have at least one character after @";
|
||||
return `Telegram handle must have at least one character after ${telegramRules.mustStartWith}`;
|
||||
}
|
||||
if (handle.length > 32) {
|
||||
return "Telegram handle must be at most 32 characters (after @)";
|
||||
if (handle.length > telegramRules.maxLengthAfterAt) {
|
||||
return `Telegram handle must be at most ${telegramRules.maxLengthAfterAt} characters (after ${telegramRules.mustStartWith})`;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
|
@ -61,16 +61,16 @@ function validateSignal(value: string): string | undefined {
|
|||
if (value.trim().length === 0) {
|
||||
return "Signal username cannot be empty";
|
||||
}
|
||||
if (value.length > 64) {
|
||||
return "Signal username must be at most 64 characters";
|
||||
if (value.length > signalRules.maxLength) {
|
||||
return `Signal username must be at most ${signalRules.maxLength} characters`;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function validateNostrNpub(value: string): string | undefined {
|
||||
if (!value) return undefined;
|
||||
if (!value.startsWith("npub1")) {
|
||||
return "Nostr npub must start with 'npub1'";
|
||||
if (!value.startsWith(npubRules.prefix)) {
|
||||
return `Nostr npub must start with '${npubRules.prefix}'`;
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -80,7 +80,7 @@ function validateNostrNpub(value: string): string | undefined {
|
|||
}
|
||||
// npub should decode to 32 bytes (256 bits) for a public key
|
||||
// In bech32, each character encodes 5 bits, so 32 bytes = 52 characters of data
|
||||
if (decoded.words.length !== 52) {
|
||||
if (decoded.words.length !== npubRules.bech32Words) {
|
||||
return "Invalid Nostr npub: incorrect length";
|
||||
}
|
||||
return undefined;
|
||||
|
|
@ -113,7 +113,7 @@ function toFormData(data: ProfileData): FormData {
|
|||
|
||||
export default function ProfilePage() {
|
||||
const { user, isLoading, isAuthorized } = useRequireAuth({
|
||||
requiredRole: "regular",
|
||||
requiredRole: constants.roles.REGULAR,
|
||||
fallbackRedirect: "/audit",
|
||||
});
|
||||
const [originalData, setOriginalData] = useState<FormData | null>(null);
|
||||
|
|
@ -152,7 +152,7 @@ export default function ProfilePage() {
|
|||
const formValues = toFormData(data);
|
||||
setFormData(formValues);
|
||||
setOriginalData(formValues);
|
||||
setGodfatherEmail(data.godfather_email);
|
||||
setGodfatherEmail(data.godfather_email ?? null);
|
||||
} catch (err) {
|
||||
console.error("Profile load error:", err);
|
||||
setToast({ message: "Failed to load profile", type: "error" });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue