first round of review
This commit is contained in:
parent
5908660e56
commit
7140cf6f27
9 changed files with 61 additions and 63 deletions
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { bech32 } from "bech32";
|
||||
import { useAuth } from "../auth-context";
|
||||
import { API_URL } from "../config";
|
||||
import { sharedStyles } from "../styles/shared";
|
||||
|
|
@ -30,7 +31,9 @@ interface FieldErrors {
|
|||
// Client-side validation matching backend rules
|
||||
function validateEmail(value: string): string | undefined {
|
||||
if (!value) return undefined;
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
// More comprehensive email regex that matches email-validator behavior
|
||||
// Checks for: local part, @, domain with at least one dot, valid TLD
|
||||
const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/;
|
||||
if (!emailRegex.test(value)) {
|
||||
return "Please enter a valid email address";
|
||||
}
|
||||
|
|
@ -43,15 +46,12 @@ function validateTelegram(value: string): string | undefined {
|
|||
return "Telegram handle must start with @";
|
||||
}
|
||||
const handle = value.slice(1);
|
||||
if (handle.length < 5) {
|
||||
return "Telegram handle must be at least 5 characters (after @)";
|
||||
if (handle.length < 1) {
|
||||
return "Telegram handle must have at least one character after @";
|
||||
}
|
||||
if (handle.length > 32) {
|
||||
return "Telegram handle must be at most 32 characters (after @)";
|
||||
}
|
||||
if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(handle)) {
|
||||
return "Telegram handle must start with a letter and contain only letters, numbers, and underscores";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
|
@ -71,15 +71,21 @@ function validateNostrNpub(value: string): string | undefined {
|
|||
if (!value.startsWith("npub1")) {
|
||||
return "Nostr npub must start with 'npub1'";
|
||||
}
|
||||
// Basic length check (valid npubs are 63 characters)
|
||||
if (value.length !== 63) {
|
||||
return "Invalid Nostr npub format";
|
||||
|
||||
try {
|
||||
const decoded = bech32.decode(value);
|
||||
if (decoded.prefix !== "npub") {
|
||||
return "Nostr npub must have 'npub' prefix";
|
||||
}
|
||||
// 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) {
|
||||
return "Invalid Nostr npub: incorrect length";
|
||||
}
|
||||
return undefined;
|
||||
} catch {
|
||||
return "Invalid Nostr npub: bech32 checksum failed";
|
||||
}
|
||||
// Check for valid bech32 characters
|
||||
if (!/^npub1[023456789acdefghjklmnpqrstuvwxyz]+$/.test(value)) {
|
||||
return "Invalid Nostr npub: contains invalid characters";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function validateForm(data: FormData): FieldErrors {
|
||||
|
|
@ -138,21 +144,7 @@ export default function ProfilePage() {
|
|||
}
|
||||
}, [isLoading, user, router, isRegularUser]);
|
||||
|
||||
useEffect(() => {
|
||||
if (user && isRegularUser) {
|
||||
fetchProfile();
|
||||
}
|
||||
}, [user, isRegularUser]);
|
||||
|
||||
// Auto-dismiss toast after 3 seconds
|
||||
useEffect(() => {
|
||||
if (toast) {
|
||||
const timer = setTimeout(() => setToast(null), 3000);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [toast]);
|
||||
|
||||
const fetchProfile = async () => {
|
||||
const fetchProfile = useCallback(async () => {
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/profile`, {
|
||||
credentials: "include",
|
||||
|
|
@ -167,13 +159,29 @@ export default function ProfilePage() {
|
|||
};
|
||||
setFormData(formValues);
|
||||
setOriginalData(formValues);
|
||||
} else {
|
||||
setToast({ message: "Failed to load profile", type: "error" });
|
||||
}
|
||||
} catch {
|
||||
// Handle error silently for now
|
||||
setToast({ message: "Network error. Please try again.", type: "error" });
|
||||
} finally {
|
||||
setIsLoadingProfile(false);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (user && isRegularUser) {
|
||||
fetchProfile();
|
||||
}
|
||||
}, [user, isRegularUser, fetchProfile]);
|
||||
|
||||
// Auto-dismiss toast after 3 seconds
|
||||
useEffect(() => {
|
||||
if (toast) {
|
||||
const timer = setTimeout(() => setToast(null), 3000);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [toast]);
|
||||
|
||||
const handleInputChange = (field: keyof FormData) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue