fix: harden badge links and polish small flows
This commit is contained in:
parent
9ce57bedf9
commit
2c8bd594a4
6 changed files with 24 additions and 14 deletions
|
|
@ -19,8 +19,7 @@ import { useTranslation } from '@/contexts/language';
|
|||
const DISCLAIMER_VERSION = 1;
|
||||
const STORAGE_KEY = `skatespots_disclaimer_accepted_v${DISCLAIMER_VERSION}`;
|
||||
|
||||
const REPO_URL =
|
||||
'https://github.com/search?q=SkateSpots';
|
||||
const REPO_URL = 'https://git.skate-spots.org/skatespots/SkateSpots';
|
||||
|
||||
export function BetaDisclaimerDialog() {
|
||||
const [accepted, setAccepted] = useLocalStorage<boolean>(STORAGE_KEY, false);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import { useTranslation } from '@/contexts/language';
|
|||
import { LANGUAGE_OPTIONS } from '@/lib/languageOptions';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const VISIBLE_LANGUAGE_OPTIONS = LANGUAGE_OPTIONS.filter((language) => !language.hidden);
|
||||
|
||||
interface LanguageToggleProps {
|
||||
/** Show only the flag icon (compact mode) */
|
||||
compact?: boolean;
|
||||
|
|
@ -65,13 +67,13 @@ export function LanguageToggle({ compact = false, inline = false, className, var
|
|||
|
||||
const safeQuery = query.trim().toLowerCase();
|
||||
const filteredLanguages = safeQuery
|
||||
? LANGUAGE_OPTIONS.filter(
|
||||
? VISIBLE_LANGUAGE_OPTIONS.filter(
|
||||
(l) =>
|
||||
l.name.toLowerCase().includes(safeQuery) ||
|
||||
l.nativeName.toLowerCase().includes(safeQuery) ||
|
||||
l.code.toLowerCase().includes(safeQuery)
|
||||
)
|
||||
: LANGUAGE_OPTIONS;
|
||||
: VISIBLE_LANGUAGE_OPTIONS;
|
||||
|
||||
const currentLanguage =
|
||||
LANGUAGE_OPTIONS.find((l) => l.code === language) || LANGUAGE_OPTIONS[0];
|
||||
|
|
@ -196,7 +198,7 @@ export function LanguageSelector() {
|
|||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">{t('settings.selectLanguage')}</label>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
{LANGUAGE_OPTIONS.map((lang) => (
|
||||
{VISIBLE_LANGUAGE_OPTIONS.map((lang) => (
|
||||
<button
|
||||
key={lang.code}
|
||||
onClick={() => setLanguage(lang.code)}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,10 @@ export function PaymentReadinessNote() {
|
|||
const metadata = author.data?.metadata;
|
||||
|
||||
const lud16 = metadata?.lud16;
|
||||
const hasLightning = isValidLightningAddress(lud16) || !!metadata?.lud06;
|
||||
// PayWithSats falls back to the profile's lud16. A lud06/LNURL alone is
|
||||
// not enough to settle a marketplace offer, so do not advertise readiness
|
||||
// for a payment path checkout cannot actually use.
|
||||
const hasLightning = isValidLightningAddress(lud16);
|
||||
// Silent payment lives in a custom kind-0 field the app reads elsewhere.
|
||||
const rawSp = (metadata as Record<string, unknown> | undefined)?.bitcoin_silent_payment;
|
||||
const hasSilentPayment = typeof rawSp === 'string' && isValidSilentPayment(rawSp);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { BadgeCheck, ExternalLink } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { SKATESPOTS_SITE, type ProfileBadge } from '@/lib/badges';
|
||||
import { sanitizeUrl } from '@/lib/sanitizeUrl';
|
||||
|
||||
/** The app logo is already served with the site. */
|
||||
const SKATESPOTS_LOGO = '/images/logo-icon.png';
|
||||
|
|
@ -26,6 +27,7 @@ export function ProfileBadges({
|
|||
<div className={cn('flex flex-wrap items-center gap-2', className)}>
|
||||
{badges.map((b, i) => {
|
||||
const app = isApp(b);
|
||||
const safeUrl = b.url ? sanitizeUrl(b.url) : null;
|
||||
const cls = cn(
|
||||
'inline-flex items-center gap-1.5 rounded-full border font-bold transition-colors',
|
||||
size === 'sm' ? 'px-2 py-0.5 text-[11px]' : 'px-3 py-1.5 text-sm',
|
||||
|
|
@ -41,17 +43,17 @@ export function ProfileBadges({
|
|||
<BadgeCheck className={size === 'sm' ? 'h-3 w-3' : 'h-3.5 w-3.5'} />
|
||||
)}
|
||||
<span className="max-w-[12rem] truncate">{b.label}</span>
|
||||
{b.url && !app && <ExternalLink className="h-3 w-3 opacity-60" />}
|
||||
{safeUrl && !app && <ExternalLink className="h-3 w-3 opacity-60" />}
|
||||
</>
|
||||
);
|
||||
|
||||
return b.url ? (
|
||||
return safeUrl ? (
|
||||
<a
|
||||
key={`${b.label}-${i}`}
|
||||
href={b.url}
|
||||
href={safeUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title={b.url}
|
||||
title={safeUrl}
|
||||
className={cls}
|
||||
>
|
||||
{inner}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ export interface LanguageOption {
|
|||
name: string;
|
||||
nativeName: string;
|
||||
flag: string;
|
||||
/** Kept for stored preferences, but omitted from the visible picker. */
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -26,7 +28,9 @@ export const LANGUAGE_OPTIONS: LanguageOption[] = [
|
|||
{ code: 'pl', name: 'Polish', nativeName: 'Polski', flag: '🇵🇱' },
|
||||
{ code: 'ru', name: 'Russian', nativeName: 'Русский', flag: '🇷🇺' },
|
||||
{ code: 'ko', name: 'Korean', nativeName: '한국어', flag: '🇰🇷' },
|
||||
{ code: 'ko-KP', name: 'Korean (North)', nativeName: '조선어', flag: '🇰🇵' },
|
||||
// North Korean reuses the Korean dictionary; hide the duplicate from the
|
||||
// picker while keeping the code valid for existing stored preferences.
|
||||
{ code: 'ko-KP', name: 'Korean (North)', nativeName: '조선어', flag: '🇰🇵', hidden: true },
|
||||
{ code: 'th', name: 'Thai', nativeName: 'ไทย', flag: '🇹🇭' },
|
||||
{ code: 'ja', name: 'Japanese', nativeName: '日本語', flag: '🇯🇵' },
|
||||
{ code: 'zh-CN', name: 'Chinese (Simplified)', nativeName: '简体中文', flag: '🇨🇳' },
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useSeoMeta } from "@unhead/react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from '@/contexts/language';
|
||||
|
||||
|
|
@ -24,9 +24,9 @@ const NotFound = () => {
|
|||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold mb-4 text-gray-900 dark:text-gray-100">404</h1>
|
||||
<p className="text-xl text-gray-600 dark:text-gray-400 mb-4">{t('settingsUi.notFound.message')}</p>
|
||||
<a href="/" className="text-blue-500 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 underline">
|
||||
<Link to="/" className="text-blue-500 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 underline">
|
||||
{t('settingsUi.notFound.returnHome')}
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue