Quick save

This commit is contained in:
Andras Schmelczer 2026-02-07 22:19:44 +00:00
parent e5d5819098
commit 2906b01734
25 changed files with 1070 additions and 237 deletions

View file

@ -17,7 +17,9 @@ interface UseAreaSummaryResult {
retry: () => void;
}
const FORBIDDEN_FEATURES = ['% White', '% Black', '% Asian', '% Mixed', '% Other'];
const FORBIDDEN_FEATURES = ['% White', '% Black', '% Asian', '% Mixed', '% Other',
'Environmental risk', 'Collapsible deposits risk', 'Compressible ground risk', 'Landslide risk', 'Running sand risk', 'Shrink-swell risk', 'Soluble rocks risk'
];
export function useAreaSummary({
stats,

View file

@ -100,9 +100,23 @@ export function useAuth() {
setUser(null);
}, []);
const requestPasswordReset = useCallback(async (email: string) => {
setLoading(true);
setError(null);
try {
await pb.collection('users').requestPasswordReset(email);
} catch (err) {
const msg = err instanceof Error ? err.message : 'Password reset request failed';
setError(msg);
throw err;
} finally {
setLoading(false);
}
}, []);
const clearError = useCallback(() => {
setError(null);
}, []);
return { user, loading, error, login, register, loginWithOAuth, logout, clearError };
return { user, loading, error, login, register, loginWithOAuth, logout, requestPasswordReset, clearError };
}

View file

@ -0,0 +1,101 @@
import { useState, useCallback } from 'react';
import pb from '../lib/pocketbase';
import { apiUrl } from '../lib/api';
export interface SavedSearch {
id: string;
name: string;
params: string;
screenshotUrl: string;
created: string;
}
export function useSavedSearches(userId: string | null) {
const [searches, setSearches] = useState<SavedSearch[]>([]);
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchSearches = useCallback(async () => {
if (!userId) return;
setLoading(true);
setError(null);
try {
const records = await pb.collection('saved_searches').getFullList({
sort: '-created',
filter: `user = "${userId}"`,
});
setSearches(
records.map((r) => ({
id: r.id,
name: (r as Record<string, unknown>).name as string,
params: (r as Record<string, unknown>).params as string,
screenshotUrl: (r as Record<string, unknown>).screenshot
? pb.files.getURL(r, (r as Record<string, unknown>).screenshot as string)
: '',
created: r.created,
}))
);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load searches');
} finally {
setLoading(false);
}
}, [userId]);
const saveSearch = useCallback(
async (name: string) => {
if (!userId) return;
setSaving(true);
setError(null);
try {
const params = window.location.search.replace(/^\?/, '');
// Try to capture a screenshot via the OG image endpoint
let screenshotBlob: Blob | null = null;
try {
const ogUrl = apiUrl('og-image', new URLSearchParams(params));
const res = await fetch(ogUrl);
if (res.ok) {
screenshotBlob = await res.blob();
}
} catch {
// Screenshot is optional — save without it
}
const formData = new FormData();
formData.append('user', userId);
formData.append('name', name);
formData.append('params', params);
if (screenshotBlob) {
formData.append('screenshot', screenshotBlob, 'screenshot.png');
}
await pb.collection('saved_searches').create(formData);
await fetchSearches();
} catch (err) {
const msg = err instanceof Error ? err.message : 'Failed to save search';
setError(msg);
throw err;
} finally {
setSaving(false);
}
},
[userId, fetchSearches]
);
const deleteSearch = useCallback(
async (id: string) => {
setError(null);
try {
await pb.collection('saved_searches').delete(id);
setSearches((prev) => prev.filter((s) => s.id !== id));
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to delete search');
}
},
[]
);
return { searches, loading, saving, error, fetchSearches, saveSearch, deleteSearch };
}