fix(client): contact-request endpoint path + search screen polish

1. Contact requests silently 404'd
   fetchContactRequests hit /api/relay/contacts, but the server mounts
   the whole /relay/* group at root (no /api prefix). Result: every
   poll returned 404, the catch swallowed it, and the notifications
   tab stayed empty even after the user sent themselves a CONTACT_
   REQUEST on-chain. Fixed the client path to /relay/contacts — same
   pattern as sendEnvelope / fetchInbox in the v1.0.x relay cleanup.

2. Search screen was half-finished
   SearchBar used a dual-state hack (idle-centered Text overlaid with
   an invisible TextInput) that broke focus + alignment on Android and
   sometimes ate taps. Rewrote as a plain single-row pill: icon +
   TextInput + optional clear button. Fewer moving parts, predictable
   focus, proper placeholder styling.

   new-contact.tsx cleaned up:
   - Title "New chat" → "Поиск" (matches the NavBar tab label and the
     rest of the Russian UI).
   - All labels localised: "Accept/Decline", "Intro", "Anti-spam fee",
     fee-tier names, error messages, final CTA.
   - Proper empty-state hint when query is empty (icon + headline +
     explanation of @username / hex / DC prefix) instead of just a
     floating helper text.
   - Search button hidden until user types something — the empty-
     state stands alone, no dead grey button under it.
   - onClear handler on SearchBar resets the resolved profile too.

   requests.tsx localised: title, empty-state, Accept/Decline button
   copy, confirmation Alert text.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
vsecoder
2026-04-18 23:19:03 +03:00
parent 3e9ddc1a43
commit 516940fa8e
4 changed files with 115 additions and 111 deletions

View File

@@ -1,12 +1,11 @@
/**
* SearchBar — серый блок, в состоянии idle текст с иконкой 🔍 отцентрированы.
*
* Когда пользователь тапает/фокусирует — поле становится input-friendly, но
* визуально рестайл не нужен: при наличии текста placeholder скрыт и
* пользовательский ввод выравнивается влево автоматически (multiline off).
* SearchBar — single-TextInput pill. Icon + input в одном ряду, без
* idle/focused двойного состояния (раньше был хак с невидимым
* TextInput поверх отцентрированного Text — ломал focus и выравнивание
* на Android).
*/
import React, { useState } from 'react';
import { View, TextInput, Text } from 'react-native';
import React from 'react';
import { View, TextInput, Pressable } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
export interface SearchBarProps {
@@ -15,73 +14,55 @@ export interface SearchBarProps {
placeholder?: string;
autoFocus?: boolean;
onSubmitEditing?: () => void;
onClear?: () => void;
}
export function SearchBar({
value, onChangeText, placeholder = 'Search', autoFocus, onSubmitEditing,
value, onChangeText, placeholder = 'Поиск', autoFocus, onSubmitEditing, onClear,
}: SearchBarProps) {
const [focused, setFocused] = useState(false);
// Placeholder центрируется пока нет фокуса И нет значения.
// Как только юзер фокусируется или начинает печатать — иконка+текст
// прыгают к левому краю, чтобы не мешать вводу.
const centered = !focused && !value;
return (
<View
style={{
backgroundColor: '#1a1a1a',
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#111111',
borderWidth: 1,
borderColor: '#1f1f1f',
borderRadius: 999,
paddingHorizontal: 14,
paddingVertical: 9,
minHeight: 36,
justifyContent: 'center',
gap: 8,
}}
>
{centered ? (
// ── Idle state — только текст+icon, отцентрированы.
// Невидимый TextInput поверх ловит tap, чтобы не дергать focus вручную.
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'center' }}>
<Ionicons name="search" size={14} color="#8b8b8b" style={{ marginRight: 6 }} />
<Text style={{ color: '#8b8b8b', fontSize: 14 }}>{placeholder}</Text>
<TextInput
value={value}
onChangeText={onChangeText}
autoFocus={autoFocus}
onFocus={() => setFocused(true)}
onSubmitEditing={onSubmitEditing}
returnKeyType="search"
style={{
position: 'absolute', left: 0, right: 0, top: 0, bottom: 0,
color: 'transparent',
// Скрываем cursor в idle-режиме; при focus компонент перерисуется.
}}
/>
</View>
) : (
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Ionicons name="search" size={14} color="#8b8b8b" style={{ marginRight: 8 }} />
<TextInput
value={value}
onChangeText={onChangeText}
placeholder={placeholder}
placeholderTextColor="#8b8b8b"
autoCapitalize="none"
autoCorrect={false}
autoFocus
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
onSubmitEditing={onSubmitEditing}
returnKeyType="search"
style={{
flex: 1,
color: '#ffffff',
fontSize: 14,
padding: 0,
includeFontPadding: false,
}}
/>
</View>
<Ionicons name="search" size={16} color="#6a6a6a" />
<TextInput
value={value}
onChangeText={onChangeText}
placeholder={placeholder}
placeholderTextColor="#5a5a5a"
autoCapitalize="none"
autoCorrect={false}
autoFocus={autoFocus}
onSubmitEditing={onSubmitEditing}
returnKeyType="search"
style={{
flex: 1,
color: '#ffffff',
fontSize: 14,
paddingVertical: 10,
padding: 0,
includeFontPadding: false,
}}
/>
{value.length > 0 && (
<Pressable
onPress={() => {
onChangeText('');
onClear?.();
}}
hitSlop={8}
>
<Ionicons name="close-circle" size={16} color="#6a6a6a" />
</Pressable>
)}
</View>
);