feat(crew): validate & normalise website, band and social links
Website/band/social inputs stored whatever was typed, so a bare "crew.com" wasn't clickable. Now they run through normalizeUrlInput + sanitizeUrl (bare domains become https://), with an inline "enter a valid link" hint on website and band URL. A non-URL social (e.g. a @handle) is kept as typed so nothing is dropped. Matches the profile/craft forms.
This commit is contained in:
parent
0548a7a7c6
commit
4d30240df9
3 changed files with 23 additions and 6 deletions
|
|
@ -26,6 +26,7 @@ import { SKATE_CREW_KIND, SKATE_SPOT_KIND, SKATE_SHOP_KIND, parseAddrCoord, ZAP_
|
|||
import { CREW_VIBE_KEYS, CREW_FOCUS_KEYS, CREW_FOCUS_DESC_KEYS, CREW_LOOKING_FOR_KEYS, CREW_ROLE_KEYS } from '@/lib/labelKeys';
|
||||
import { useCrewGoal } from '@/hooks/useCrewGoal';
|
||||
import { useCoordinator, useCoordinatorHealth } from '@/hooks/useCoordinator';
|
||||
import { normalizeUrlInput, sanitizeUrl } from '@/lib/sanitizeUrl';
|
||||
import { Nip05HandlesEditor } from '@/components/Nip05HandlesEditor';
|
||||
import { coordinatorAddUrl, crewCoordinatorInputValue, parseCrewCoordinatorInput, shortNpub } from '@/lib/coordinator';
|
||||
import { useRelays } from '@/lib/relays';
|
||||
|
|
@ -394,10 +395,14 @@ export function CrewForm({ existing }: CrewFormProps) {
|
|||
};
|
||||
|
||||
const addSocial = () => {
|
||||
const val = socialInput.trim();
|
||||
const raw = socialInput.trim();
|
||||
setSocialInput('');
|
||||
if (!val || socials.includes(val)) return;
|
||||
setSocials((prev) => [...prev, val]);
|
||||
if (!raw) return;
|
||||
// Coerce a bare domain to https:// so it renders as a link; keep a non-URL
|
||||
// (e.g. a @handle) as typed so nothing is silently dropped.
|
||||
const safe = sanitizeUrl(normalizeUrlInput(raw)) ?? raw;
|
||||
if (socials.includes(safe)) return;
|
||||
setSocials((prev) => [...prev, safe]);
|
||||
};
|
||||
|
||||
// Crew NIP-05 handles — up to 5, each verified against the founder's key.
|
||||
|
|
@ -550,11 +555,15 @@ export function CrewForm({ existing }: CrewFormProps) {
|
|||
}
|
||||
}
|
||||
if (image) tags.push(['image', image]);
|
||||
if (website.trim()) tags.push(['website', website.trim()]);
|
||||
{
|
||||
const safe = website.trim() ? sanitizeUrl(normalizeUrlInput(website)) : null;
|
||||
if (website.trim()) tags.push(['website', safe ?? website.trim()]);
|
||||
}
|
||||
if (instagram.trim()) tags.push(['instagram', instagram.trim()]);
|
||||
if (bandName.trim()) tags.push(['band', bandName.trim()]);
|
||||
if (bandUrl.trim() && /^https?:\/\//i.test(bandUrl.trim())) {
|
||||
tags.push(['band-url', bandUrl.trim()]);
|
||||
{
|
||||
const safe = bandUrl.trim() ? sanitizeUrl(normalizeUrlInput(bandUrl)) : null;
|
||||
if (bandUrl.trim()) tags.push(['band-url', safe ?? bandUrl.trim()]);
|
||||
}
|
||||
for (const s of socials) {
|
||||
if (s.trim()) tags.push(['social', s.trim()]);
|
||||
|
|
@ -1275,6 +1284,9 @@ export function CrewForm({ existing }: CrewFormProps) {
|
|||
<div className="space-y-2">
|
||||
<Label htmlFor="crew-website">{t('socialUi.form.website')}</Label>
|
||||
<Input id="crew-website" type="url" placeholder="https://crew.com" value={website} onChange={(e) => setWebsite(e.target.value)} className="bg-gray-800 border-gray-700" />
|
||||
{website.trim() && !sanitizeUrl(normalizeUrlInput(website)) && (
|
||||
<p className="text-[11px] text-amber-400">{t('socialUi.form.invalidUrl')}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-2 sm:col-span-2">
|
||||
<Label htmlFor="crew-social">{t('socialUi.form.otherSocials')}</Label>
|
||||
|
|
@ -1349,6 +1361,9 @@ export function CrewForm({ existing }: CrewFormProps) {
|
|||
autoCapitalize="none"
|
||||
className="bg-gray-800 border-gray-700"
|
||||
/>
|
||||
{bandUrl.trim() && !sanitizeUrl(normalizeUrlInput(bandUrl)) && (
|
||||
<p className="text-[11px] text-amber-400">{t('socialUi.form.invalidUrl')}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -3922,6 +3922,7 @@ socialUi: {
|
|||
website: 'Website',
|
||||
yourRoles: 'Your roles (Team Leader)',
|
||||
youtubeDetected: 'YouTube video detected',
|
||||
invalidUrl: 'Enter a valid link (e.g. https://example.com).',
|
||||
},
|
||||
groupConversation: {
|
||||
seoTitle: 'Group chat - SkateSpots',
|
||||
|
|
|
|||
|
|
@ -3922,6 +3922,7 @@ socialUi: {
|
|||
website: 'Sitio web',
|
||||
yourRoles: 'Tus roles (líder del equipo)',
|
||||
youtubeDetected: 'Vídeo de YouTube detectado',
|
||||
invalidUrl: 'Introduce un enlace válido (p. ej. https://ejemplo.com).',
|
||||
},
|
||||
groupConversation: {
|
||||
seoTitle: 'Chat de grupo - SkateSpots',
|
||||
|
|
|
|||
Loading…
Reference in a new issue