Merge pull request 'Refactor frontend-mobile' (#18) from feat/mobile-pwa-design-alignment into main
Build & Push Docker Images / test-backend (push) Has been cancelled
Build & Push Docker Images / build-backend (push) Has been cancelled
Build & Push Docker Images / build-frontend (push) Has been cancelled
Build & Push Docker Images / deploy (push) Has been cancelled
Build & Push Docker Images / test-frontend (push) Has been cancelled
Build & Push Docker Images / test-backend (push) Has been cancelled
Build & Push Docker Images / build-backend (push) Has been cancelled
Build & Push Docker Images / build-frontend (push) Has been cancelled
Build & Push Docker Images / deploy (push) Has been cancelled
Build & Push Docker Images / test-frontend (push) Has been cancelled
Reviewed-on: #18
This commit was merged in pull request #18.
This commit is contained in:
@@ -1,14 +1,40 @@
|
|||||||
import { Tabs } from 'expo-router';
|
import { Tabs } from 'expo-router';
|
||||||
import { Ionicons } from '@expo/vector-icons';
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import { View, StyleSheet, Platform } from 'react-native';
|
||||||
|
import { colors, shadows } from '../../constants/theme';
|
||||||
|
|
||||||
|
function ScanIcon({ color, size }: { color: string; size: number }) {
|
||||||
|
return (
|
||||||
|
<View style={styles.fabContainer}>
|
||||||
|
<View style={[styles.fab, shadows.scanButton]}>
|
||||||
|
<Ionicons name="scan" size={size} color="#ffffff" />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function TabLayout() {
|
export default function TabLayout() {
|
||||||
return (
|
return (
|
||||||
<Tabs
|
<Tabs
|
||||||
screenOptions={{
|
screenOptions={{
|
||||||
tabBarActiveTintColor: '#007AFF',
|
tabBarActiveTintColor: colors.primary,
|
||||||
tabBarInactiveTintColor: '#8E8E93',
|
tabBarInactiveTintColor: colors.textSecondary,
|
||||||
|
tabBarStyle: styles.tabBar,
|
||||||
|
tabBarLabelStyle: styles.tabBarLabel,
|
||||||
|
headerStyle: styles.header,
|
||||||
|
headerTintColor: colors.primary,
|
||||||
|
headerTitleStyle: styles.headerTitle,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Tabs.Screen
|
||||||
|
name="home"
|
||||||
|
options={{
|
||||||
|
title: 'Inicio',
|
||||||
|
tabBarIcon: ({ color, size }) => (
|
||||||
|
<Ionicons name="home" size={size} color={color} />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="index"
|
name="index"
|
||||||
options={{
|
options={{
|
||||||
@@ -18,13 +44,27 @@ export default function TabLayout() {
|
|||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Tabs.Screen
|
||||||
|
name="scan"
|
||||||
|
options={{
|
||||||
|
title: 'Escanear',
|
||||||
|
tabBarIcon: ({ color, size }) => <ScanIcon color={color} size={size} />,
|
||||||
|
tabBarLabel: () => null,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Tabs.Screen
|
||||||
|
name="alerts"
|
||||||
|
options={{
|
||||||
|
title: 'Avisos',
|
||||||
|
tabBarIcon: ({ color, size }) => (
|
||||||
|
<Ionicons name="notifications" size={size} color={color} />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="map"
|
name="map"
|
||||||
options={{
|
options={{
|
||||||
title: 'Mapa',
|
href: null,
|
||||||
tabBarIcon: ({ color, size }) => (
|
|
||||||
<Ionicons name="map" size={size} color={color} />
|
|
||||||
),
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
@@ -39,3 +79,43 @@ export default function TabLayout() {
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
tabBar: {
|
||||||
|
backgroundColor: colors.card,
|
||||||
|
borderTopColor: colors.border,
|
||||||
|
height: Platform.OS === 'ios' ? 88 : 64,
|
||||||
|
paddingBottom: Platform.OS === 'ios' ? 24 : 8,
|
||||||
|
paddingTop: 8,
|
||||||
|
},
|
||||||
|
tabBarLabel: {
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: '500',
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
backgroundColor: colors.card,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 1 },
|
||||||
|
shadowOpacity: 0.05,
|
||||||
|
shadowRadius: 4,
|
||||||
|
elevation: 1,
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
color: colors.text,
|
||||||
|
fontWeight: '600',
|
||||||
|
fontSize: 17,
|
||||||
|
},
|
||||||
|
fabContainer: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: -16,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
fab: {
|
||||||
|
width: 52,
|
||||||
|
height: 52,
|
||||||
|
borderRadius: 26,
|
||||||
|
backgroundColor: colors.scanButton,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,284 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { View, Text, StyleSheet, FlatList, TouchableOpacity, Alert } from 'react-native';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import { colors, spacing, borderRadius, shadows } from '../../constants/theme';
|
||||||
|
import { LoadingSpinner } from '../../components/LoadingSpinner';
|
||||||
|
|
||||||
|
interface NotificationItem {
|
||||||
|
scope: string;
|
||||||
|
id: number;
|
||||||
|
medicine_name?: string;
|
||||||
|
medicine_nregistro?: string;
|
||||||
|
pharmacy_name?: string;
|
||||||
|
pharmacy_id?: number;
|
||||||
|
pharmacy_address?: string;
|
||||||
|
created_at?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AlertsScreen() {
|
||||||
|
const [items, setItems] = useState<NotificationItem[]>([]);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [deletingId, setDeletingId] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadNotifications();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function loadNotifications() {
|
||||||
|
setIsLoading(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/notifications/mine', { credentials: 'include' });
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
|
const data = await res.json();
|
||||||
|
const merged = [
|
||||||
|
...(data.pharmacy || []),
|
||||||
|
...(data.global || []),
|
||||||
|
].sort((a: NotificationItem, b: NotificationItem) =>
|
||||||
|
(b.created_at || '').localeCompare(a.created_at || '')
|
||||||
|
);
|
||||||
|
setItems(merged);
|
||||||
|
} catch (err: any) {
|
||||||
|
setError(err.message || 'No se pudieron cargar las notificaciones');
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDelete(item: NotificationItem) {
|
||||||
|
const key = `${item.scope}:${item.id}`;
|
||||||
|
Alert.alert(
|
||||||
|
'Eliminar notificación',
|
||||||
|
`¿Eliminar la notificación de ${item.medicine_name || item.medicine_nregistro}?`,
|
||||||
|
[
|
||||||
|
{ text: 'Cancelar', style: 'cancel' },
|
||||||
|
{
|
||||||
|
text: 'Eliminar',
|
||||||
|
style: 'destructive',
|
||||||
|
onPress: async () => {
|
||||||
|
setDeletingId(key);
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/notifications/mine', {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify({ scope: item.scope, id: item.id }),
|
||||||
|
});
|
||||||
|
if (!res.ok && res.status !== 204) throw new Error(`HTTP ${res.status}`);
|
||||||
|
setItems(prev => prev.filter(i => !(i.scope === item.scope && i.id === item.id)));
|
||||||
|
} catch (err: any) {
|
||||||
|
Alert.alert('Error', err.message || 'No se pudo eliminar');
|
||||||
|
} finally {
|
||||||
|
setDeletingId(null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderItem({ item }: { item: NotificationItem }) {
|
||||||
|
const key = `${item.scope}:${item.id}`;
|
||||||
|
return (
|
||||||
|
<View style={styles.item}>
|
||||||
|
<View style={styles.itemContent}>
|
||||||
|
<Text style={styles.itemName} numberOfLines={2}>
|
||||||
|
{item.medicine_name || item.medicine_nregistro}
|
||||||
|
</Text>
|
||||||
|
<View style={styles.itemMeta}>
|
||||||
|
<View style={styles.chip}>
|
||||||
|
<Ionicons
|
||||||
|
name={item.scope === 'pharmacy' ? 'medical' : 'globe'}
|
||||||
|
size={12}
|
||||||
|
color={colors.primary}
|
||||||
|
/>
|
||||||
|
<Text style={styles.chipText}>
|
||||||
|
{item.scope === 'pharmacy'
|
||||||
|
? item.pharmacy_name || `Farmacia #${item.pharmacy_id}`
|
||||||
|
: 'Cualquier farmacia'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
{item.pharmacy_address && (
|
||||||
|
<Text style={styles.itemAddress} numberOfLines={1}>
|
||||||
|
{item.pharmacy_address}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.deleteButton}
|
||||||
|
onPress={() => handleDelete(item)}
|
||||||
|
disabled={deletingId === key}
|
||||||
|
>
|
||||||
|
{deletingId === key ? (
|
||||||
|
<Ionicons name="hourglass" size={18} color={colors.danger} />
|
||||||
|
) : (
|
||||||
|
<Ionicons name="trash-outline" size={18} color={colors.danger} />
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <LoadingSpinner message="Cargando notificaciones..." />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={styles.header}>
|
||||||
|
<Text style={styles.title}>Notificaciones Guardadas</Text>
|
||||||
|
<Text style={styles.subtitle}>
|
||||||
|
Recibe avisos cuando medicamentos sin stock se repongan
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<View style={styles.errorContainer}>
|
||||||
|
<Text style={styles.errorText}>{error}</Text>
|
||||||
|
<TouchableOpacity onPress={loadNotifications} style={styles.retryButton}>
|
||||||
|
<Text style={styles.retryText}>Reintentar</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!error && items.length === 0 && (
|
||||||
|
<View style={styles.emptyContainer}>
|
||||||
|
<Ionicons name="notifications-off-outline" size={64} color={colors.border} />
|
||||||
|
<Text style={styles.emptyTitle}>Sin notificaciones</Text>
|
||||||
|
<Text style={styles.emptyText}>
|
||||||
|
Toca la campana en una farmacia sin stock para recibir notificaciones cuando se reponga.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!error && items.length > 0 && (
|
||||||
|
<FlatList
|
||||||
|
data={items}
|
||||||
|
keyExtractor={(item) => `${item.scope}:${item.id}`}
|
||||||
|
renderItem={renderItem}
|
||||||
|
contentContainerStyle={styles.list}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: colors.background,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
paddingHorizontal: spacing.lg,
|
||||||
|
paddingTop: spacing.lg,
|
||||||
|
paddingBottom: spacing.md,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: colors.text,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: colors.textSecondary,
|
||||||
|
marginTop: spacing.xs,
|
||||||
|
lineHeight: 20,
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
paddingHorizontal: spacing.lg,
|
||||||
|
paddingBottom: spacing.xl,
|
||||||
|
gap: spacing.sm,
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: colors.card,
|
||||||
|
borderRadius: borderRadius.lg,
|
||||||
|
padding: spacing.md,
|
||||||
|
...shadows.card,
|
||||||
|
},
|
||||||
|
itemContent: {
|
||||||
|
flex: 1,
|
||||||
|
gap: spacing.xs,
|
||||||
|
},
|
||||||
|
itemName: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: colors.text,
|
||||||
|
lineHeight: 22,
|
||||||
|
},
|
||||||
|
itemMeta: {
|
||||||
|
gap: spacing.xs,
|
||||||
|
},
|
||||||
|
chip: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: spacing.xs,
|
||||||
|
backgroundColor: colors.primaryContainer,
|
||||||
|
borderRadius: borderRadius.full,
|
||||||
|
paddingHorizontal: spacing.sm,
|
||||||
|
paddingVertical: 3,
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
},
|
||||||
|
chipText: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: colors.onPrimaryContainer,
|
||||||
|
},
|
||||||
|
itemAddress: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: colors.textSecondary,
|
||||||
|
},
|
||||||
|
deleteButton: {
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
borderRadius: 18,
|
||||||
|
backgroundColor: colors.dangerContainer,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginLeft: spacing.sm,
|
||||||
|
},
|
||||||
|
errorContainer: {
|
||||||
|
margin: spacing.lg,
|
||||||
|
padding: spacing.md,
|
||||||
|
backgroundColor: colors.dangerContainer,
|
||||||
|
borderRadius: borderRadius.lg,
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: spacing.sm,
|
||||||
|
},
|
||||||
|
errorText: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: colors.danger,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
retryButton: {
|
||||||
|
paddingHorizontal: spacing.md,
|
||||||
|
paddingVertical: spacing.xs,
|
||||||
|
},
|
||||||
|
retryText: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: colors.primary,
|
||||||
|
},
|
||||||
|
emptyContainer: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingHorizontal: spacing.xl,
|
||||||
|
gap: spacing.md,
|
||||||
|
},
|
||||||
|
emptyTitle: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: colors.text,
|
||||||
|
},
|
||||||
|
emptyText: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: colors.textSecondary,
|
||||||
|
textAlign: 'center',
|
||||||
|
lineHeight: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { View, Text, StyleSheet, TouchableOpacity, Image } from 'react-native';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import { useRouter } from 'expo-router';
|
||||||
|
import { colors, spacing, borderRadius, shadows } from '../../constants/theme';
|
||||||
|
|
||||||
|
export default function HomeScreen() {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={styles.hero}>
|
||||||
|
<Image
|
||||||
|
source={require('../../assets/farmaclic_logo.png')}
|
||||||
|
style={styles.logo}
|
||||||
|
resizeMode="contain"
|
||||||
|
/>
|
||||||
|
<Text style={styles.brandName}>FarmaClic</Text>
|
||||||
|
<Text style={styles.description}>
|
||||||
|
Encuentra tus medicamentos en farmacias cercanas
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.cards}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.card}
|
||||||
|
activeOpacity={0.85}
|
||||||
|
onPress={() => router.push('/(tabs)')}
|
||||||
|
>
|
||||||
|
<View style={[styles.cardIcon, styles.cardIconSearch]}>
|
||||||
|
<Ionicons name="search" size={24} color={colors.onPrimaryContainer} />
|
||||||
|
</View>
|
||||||
|
<View style={styles.cardContent}>
|
||||||
|
<Text style={styles.cardLabel}>Buscar Medicamento</Text>
|
||||||
|
<Ionicons name="chevron-forward" size={20} color={colors.onPrimaryContainer} style={{ opacity: 0.7 }} />
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.card, styles.cardScan]}
|
||||||
|
activeOpacity={0.85}
|
||||||
|
onPress={() => router.push('/scanner')}
|
||||||
|
>
|
||||||
|
<View style={[styles.cardIcon, styles.cardIconScan]}>
|
||||||
|
<Ionicons name="scan" size={24} color="#ffffff" />
|
||||||
|
</View>
|
||||||
|
<View style={styles.cardContent}>
|
||||||
|
<Text style={[styles.cardLabel, styles.cardLabelScan]}>Escanear TSI</Text>
|
||||||
|
<Ionicons name="chevron-forward" size={20} color="#ffffff" style={{ opacity: 0.7 }} />
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: colors.background,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingHorizontal: spacing.lg,
|
||||||
|
gap: spacing.lg,
|
||||||
|
},
|
||||||
|
hero: {
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: spacing.sm,
|
||||||
|
marginBottom: spacing.md,
|
||||||
|
},
|
||||||
|
logo: {
|
||||||
|
width: 120,
|
||||||
|
height: 120,
|
||||||
|
marginBottom: spacing.xs,
|
||||||
|
},
|
||||||
|
brandName: {
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: colors.text,
|
||||||
|
letterSpacing: -0.5,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: colors.textSecondary,
|
||||||
|
textAlign: 'center',
|
||||||
|
maxWidth: 260,
|
||||||
|
lineHeight: 24,
|
||||||
|
},
|
||||||
|
cards: {
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: 320,
|
||||||
|
gap: spacing.md,
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: spacing.md,
|
||||||
|
backgroundColor: colors.primaryContainer,
|
||||||
|
borderRadius: borderRadius.lg,
|
||||||
|
padding: spacing.md,
|
||||||
|
minHeight: 64,
|
||||||
|
...shadows.card,
|
||||||
|
},
|
||||||
|
cardScan: {
|
||||||
|
backgroundColor: colors.scanButton,
|
||||||
|
},
|
||||||
|
cardIcon: {
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
borderRadius: borderRadius.md,
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
flexShrink: 0,
|
||||||
|
},
|
||||||
|
cardIconSearch: {
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.3)',
|
||||||
|
},
|
||||||
|
cardIconScan: {
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
},
|
||||||
|
cardContent: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
cardLabel: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: colors.onPrimaryContainer,
|
||||||
|
lineHeight: 24,
|
||||||
|
},
|
||||||
|
cardLabelScan: {
|
||||||
|
color: '#ffffff',
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -55,11 +55,11 @@ export default function HomeScreen() {
|
|||||||
value={query}
|
value={query}
|
||||||
onChangeText={setQuery}
|
onChangeText={setQuery}
|
||||||
/>
|
/>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.scannerButton}
|
style={styles.scannerButton}
|
||||||
onPress={() => router.push('/scanner')}
|
onPress={() => router.push('/scanner')}
|
||||||
>
|
>
|
||||||
<Ionicons name="scan" size={24} color={colors.primary} />
|
<Ionicons name="scan" size={22} color="#ffffff" />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -96,25 +96,34 @@ const styles = StyleSheet.create({
|
|||||||
searchContainer: {
|
searchContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
paddingRight: spacing.lg,
|
||||||
},
|
},
|
||||||
scannerButton: {
|
scannerButton: {
|
||||||
marginRight: spacing.md,
|
width: 44,
|
||||||
padding: spacing.sm,
|
height: 44,
|
||||||
backgroundColor: colors.card,
|
borderRadius: 22,
|
||||||
borderRadius: borderRadius.md,
|
backgroundColor: colors.scanButton,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
shadowColor: '#2b5bb5',
|
||||||
|
shadowOffset: { width: 0, height: 4 },
|
||||||
|
shadowOpacity: 0.3,
|
||||||
|
shadowRadius: 12,
|
||||||
|
elevation: 6,
|
||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
paddingBottom: spacing.xl,
|
paddingBottom: spacing.xl,
|
||||||
},
|
},
|
||||||
errorContainer: {
|
errorContainer: {
|
||||||
padding: spacing.md,
|
padding: spacing.md,
|
||||||
marginHorizontal: spacing.md,
|
marginHorizontal: spacing.lg,
|
||||||
backgroundColor: '#F8D7DA',
|
backgroundColor: colors.dangerContainer,
|
||||||
borderRadius: 8,
|
borderRadius: borderRadius.lg,
|
||||||
},
|
},
|
||||||
errorText: {
|
errorText: {
|
||||||
color: '#721C24',
|
color: colors.danger,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
|
fontSize: 14,
|
||||||
},
|
},
|
||||||
emptyContainer: {
|
emptyContainer: {
|
||||||
padding: spacing.xl,
|
padding: spacing.xl,
|
||||||
|
|||||||
@@ -92,14 +92,14 @@ const styles = StyleSheet.create({
|
|||||||
left: spacing.md,
|
left: spacing.md,
|
||||||
right: spacing.md,
|
right: spacing.md,
|
||||||
backgroundColor: colors.card,
|
backgroundColor: colors.card,
|
||||||
borderRadius: 8,
|
borderRadius: borderRadius.lg,
|
||||||
padding: spacing.sm,
|
padding: spacing.sm + 4,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
shadowColor: '#000',
|
shadowColor: '#000',
|
||||||
shadowOffset: { width: 0, height: 2 },
|
shadowOffset: { width: 0, height: 4 },
|
||||||
shadowOpacity: 0.25,
|
shadowOpacity: 0.08,
|
||||||
shadowRadius: 4,
|
shadowRadius: 20,
|
||||||
elevation: 5,
|
elevation: 4,
|
||||||
},
|
},
|
||||||
legendText: {
|
legendText: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
|||||||
@@ -830,7 +830,7 @@ const styles = StyleSheet.create({
|
|||||||
width: 100,
|
width: 100,
|
||||||
height: 100,
|
height: 100,
|
||||||
borderRadius: 50,
|
borderRadius: 50,
|
||||||
backgroundColor: colors.background,
|
backgroundColor: colors.primaryContainer,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
@@ -846,7 +846,7 @@ const styles = StyleSheet.create({
|
|||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
opacity: 0.8,
|
opacity: 0.8,
|
||||||
@@ -857,17 +857,17 @@ const styles = StyleSheet.create({
|
|||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
},
|
},
|
||||||
infoSection: {
|
infoSection: {
|
||||||
padding: spacing.xl,
|
padding: spacing.lg,
|
||||||
backgroundColor: colors.card,
|
backgroundColor: colors.card,
|
||||||
marginTop: spacing.md,
|
marginTop: spacing.sm,
|
||||||
gap: spacing.md,
|
gap: spacing.md,
|
||||||
},
|
},
|
||||||
infoCard: {
|
infoCard: {
|
||||||
backgroundColor: colors.background,
|
backgroundColor: colors.surfaceLow,
|
||||||
padding: spacing.md,
|
padding: spacing.md,
|
||||||
borderRadius: borderRadius.md,
|
borderRadius: borderRadius.lg,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: colors.border || '#E0E0E0',
|
borderColor: colors.border,
|
||||||
},
|
},
|
||||||
infoLabel: {
|
infoLabel: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
@@ -883,7 +883,7 @@ const styles = StyleSheet.create({
|
|||||||
color: colors.text,
|
color: colors.text,
|
||||||
},
|
},
|
||||||
menuSection: {
|
menuSection: {
|
||||||
marginTop: spacing.md,
|
marginTop: spacing.sm,
|
||||||
backgroundColor: colors.card,
|
backgroundColor: colors.card,
|
||||||
},
|
},
|
||||||
menuItem: {
|
menuItem: {
|
||||||
@@ -891,7 +891,7 @@ const styles = StyleSheet.create({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
padding: spacing.md,
|
padding: spacing.md,
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderBottomColor: colors.separator,
|
borderBottomColor: colors.border,
|
||||||
},
|
},
|
||||||
menuText: {
|
menuText: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -901,9 +901,9 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
searchHistorySection: {
|
searchHistorySection: {
|
||||||
padding: spacing.md,
|
padding: spacing.md,
|
||||||
backgroundColor: colors.background,
|
backgroundColor: colors.surfaceLow,
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderBottomColor: colors.separator,
|
borderBottomColor: colors.border,
|
||||||
},
|
},
|
||||||
searchHistoryTitle: {
|
searchHistoryTitle: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
@@ -916,7 +916,7 @@ const styles = StyleSheet.create({
|
|||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
paddingVertical: spacing.sm,
|
paddingVertical: spacing.sm,
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderBottomColor: colors.separator,
|
borderBottomColor: colors.border,
|
||||||
},
|
},
|
||||||
searchAddress: {
|
searchAddress: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -928,7 +928,7 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
backgroundColor: colors.primary,
|
backgroundColor: colors.primary,
|
||||||
borderRadius: borderRadius.md,
|
borderRadius: borderRadius.lg,
|
||||||
paddingVertical: spacing.md,
|
paddingVertical: spacing.md,
|
||||||
paddingHorizontal: spacing.xl,
|
paddingHorizontal: spacing.xl,
|
||||||
},
|
},
|
||||||
@@ -951,7 +951,7 @@ const styles = StyleSheet.create({
|
|||||||
margin: spacing.xl,
|
margin: spacing.xl,
|
||||||
padding: spacing.md,
|
padding: spacing.md,
|
||||||
backgroundColor: colors.card,
|
backgroundColor: colors.card,
|
||||||
borderRadius: borderRadius.md,
|
borderRadius: borderRadius.lg,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: colors.danger,
|
borderColor: colors.danger,
|
||||||
},
|
},
|
||||||
@@ -979,7 +979,7 @@ const styles = StyleSheet.create({
|
|||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
padding: spacing.lg,
|
padding: spacing.lg,
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderBottomColor: colors.separator,
|
borderBottomColor: colors.border,
|
||||||
},
|
},
|
||||||
modalTitle: {
|
modalTitle: {
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
@@ -1000,26 +1000,26 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
modalInput: {
|
modalInput: {
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: colors.border || '#E0E0E0',
|
borderColor: colors.border,
|
||||||
borderRadius: borderRadius.md,
|
borderRadius: borderRadius.lg,
|
||||||
padding: spacing.md,
|
padding: spacing.md,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: colors.text,
|
color: colors.text,
|
||||||
},
|
},
|
||||||
modalFeedback: {
|
modalFeedback: {
|
||||||
padding: spacing.md,
|
padding: spacing.md,
|
||||||
borderRadius: borderRadius.md,
|
borderRadius: borderRadius.lg,
|
||||||
marginBottom: spacing.md,
|
marginBottom: spacing.md,
|
||||||
},
|
},
|
||||||
modalFeedbackOk: {
|
modalFeedbackOk: {
|
||||||
backgroundColor: 'rgba(0, 69, 13, 0.1)',
|
backgroundColor: '#eaf7ec',
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: 'rgba(0, 69, 13, 0.2)',
|
borderColor: '#cfead0',
|
||||||
},
|
},
|
||||||
modalFeedbackErr: {
|
modalFeedbackErr: {
|
||||||
backgroundColor: 'rgba(186, 26, 26, 0.1)',
|
backgroundColor: colors.dangerContainer,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: 'rgba(186, 26, 26, 0.2)',
|
borderColor: '#fecaca',
|
||||||
},
|
},
|
||||||
modalFeedbackText: {
|
modalFeedbackText: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
@@ -1034,9 +1034,9 @@ const styles = StyleSheet.create({
|
|||||||
modalCancelBtn: {
|
modalCancelBtn: {
|
||||||
paddingVertical: spacing.md,
|
paddingVertical: spacing.md,
|
||||||
paddingHorizontal: spacing.lg,
|
paddingHorizontal: spacing.lg,
|
||||||
borderRadius: borderRadius.md,
|
borderRadius: borderRadius.lg,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: colors.border || '#E0E0E0',
|
borderColor: colors.border,
|
||||||
},
|
},
|
||||||
modalCancelText: {
|
modalCancelText: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
@@ -1046,7 +1046,7 @@ const styles = StyleSheet.create({
|
|||||||
backgroundColor: colors.primary,
|
backgroundColor: colors.primary,
|
||||||
paddingVertical: spacing.md,
|
paddingVertical: spacing.md,
|
||||||
paddingHorizontal: spacing.xl,
|
paddingHorizontal: spacing.xl,
|
||||||
borderRadius: borderRadius.md,
|
borderRadius: borderRadius.lg,
|
||||||
minWidth: 100,
|
minWidth: 100,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
@@ -1068,7 +1068,7 @@ const styles = StyleSheet.create({
|
|||||||
tabContainer: {
|
tabContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderBottomColor: colors.separator,
|
borderBottomColor: colors.border,
|
||||||
},
|
},
|
||||||
tabButton: {
|
tabButton: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -1115,10 +1115,10 @@ const styles = StyleSheet.create({
|
|||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
padding: spacing.md,
|
padding: spacing.md,
|
||||||
backgroundColor: colors.background,
|
backgroundColor: colors.surfaceLow,
|
||||||
borderRadius: borderRadius.md,
|
borderRadius: borderRadius.lg,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: colors.border || '#E5E5EA',
|
borderColor: colors.border,
|
||||||
},
|
},
|
||||||
uploadIconContainer: {
|
uploadIconContainer: {
|
||||||
width: 56,
|
width: 56,
|
||||||
@@ -1155,14 +1155,14 @@ const styles = StyleSheet.create({
|
|||||||
alignItems: 'flex-start',
|
alignItems: 'flex-start',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
padding: spacing.md,
|
padding: spacing.md,
|
||||||
backgroundColor: colors.background,
|
backgroundColor: colors.surfaceLow,
|
||||||
borderRadius: borderRadius.md,
|
borderRadius: borderRadius.lg,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: colors.border || '#E0E0E0',
|
borderColor: colors.border,
|
||||||
},
|
},
|
||||||
addressItemDefault: {
|
addressItemDefault: {
|
||||||
borderColor: colors.primary,
|
borderColor: colors.primary,
|
||||||
backgroundColor: 'rgba(0, 69, 13, 0.03)',
|
backgroundColor: '#eaf7ec',
|
||||||
},
|
},
|
||||||
addressItemInfo: {
|
addressItemInfo: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -1181,7 +1181,7 @@ const styles = StyleSheet.create({
|
|||||||
addressBadge: {
|
addressBadge: {
|
||||||
alignSelf: 'flex-start',
|
alignSelf: 'flex-start',
|
||||||
backgroundColor: colors.primary,
|
backgroundColor: colors.primary,
|
||||||
borderRadius: 12,
|
borderRadius: borderRadius.full,
|
||||||
paddingHorizontal: spacing.sm,
|
paddingHorizontal: spacing.sm,
|
||||||
paddingVertical: 2,
|
paddingVertical: 2,
|
||||||
marginTop: 4,
|
marginTop: 4,
|
||||||
@@ -1219,9 +1219,9 @@ const styles = StyleSheet.create({
|
|||||||
padding: spacing.md,
|
padding: spacing.md,
|
||||||
marginTop: spacing.md,
|
marginTop: spacing.md,
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
borderColor: colors.border || '#E0E0E0',
|
borderColor: colors.border,
|
||||||
borderStyle: 'dashed',
|
borderStyle: 'dashed',
|
||||||
borderRadius: borderRadius.md,
|
borderRadius: borderRadius.lg,
|
||||||
},
|
},
|
||||||
addressAddBtnText: {
|
addressAddBtnText: {
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import { useRouter } from 'expo-router';
|
||||||
|
import { colors, spacing, borderRadius, shadows } from '../../constants/theme';
|
||||||
|
|
||||||
|
export default function ScanTabScreen() {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={styles.content}>
|
||||||
|
<View style={styles.iconContainer}>
|
||||||
|
<Ionicons name="scan" size={64} color={colors.scanButton} />
|
||||||
|
</View>
|
||||||
|
<Text style={styles.title}>Escanear TSI</Text>
|
||||||
|
<Text style={styles.description}>
|
||||||
|
Escanea el código de barras de tu tarjeta sanitaria para encontrar tus medicamentos
|
||||||
|
</Text>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.button, shadows.scanButton]}
|
||||||
|
activeOpacity={0.85}
|
||||||
|
onPress={() => router.push('/scanner')}
|
||||||
|
>
|
||||||
|
<Ionicons name="scan" size={24} color="#ffffff" />
|
||||||
|
<Text style={styles.buttonText}>Iniciar escaneo</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: colors.background,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingHorizontal: spacing.xl,
|
||||||
|
gap: spacing.md,
|
||||||
|
},
|
||||||
|
iconContainer: {
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
borderRadius: 50,
|
||||||
|
backgroundColor: colors.primaryContainer,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginBottom: spacing.sm,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: colors.text,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
fontSize: 15,
|
||||||
|
color: colors.textSecondary,
|
||||||
|
textAlign: 'center',
|
||||||
|
lineHeight: 22,
|
||||||
|
maxWidth: 280,
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: spacing.sm,
|
||||||
|
backgroundColor: colors.scanButton,
|
||||||
|
borderRadius: borderRadius.lg,
|
||||||
|
paddingVertical: spacing.md,
|
||||||
|
paddingHorizontal: spacing.xl,
|
||||||
|
marginTop: spacing.md,
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
|
fontSize: 17,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#ffffff',
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -5,6 +5,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|||||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '../store/authStore';
|
||||||
import { registerForPushNotifications, addNotificationListener, addNotificationResponseListener } from '../services/notifications';
|
import { registerForPushNotifications, addNotificationListener, addNotificationResponseListener } from '../services/notifications';
|
||||||
|
import { colors } from '../constants/theme';
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
@@ -35,20 +36,27 @@ export default function RootLayout() {
|
|||||||
return (
|
return (
|
||||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<Stack>
|
<Stack
|
||||||
|
screenOptions={{
|
||||||
|
headerStyle: { backgroundColor: colors.card },
|
||||||
|
headerTintColor: colors.primary,
|
||||||
|
headerTitleStyle: { color: colors.text, fontWeight: '600' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="medicine/[id]"
|
name="medicine/[id]"
|
||||||
options={{
|
options={{ title: 'Medicamento' }}
|
||||||
title: 'Medicamento',
|
|
||||||
headerTintColor: '#007AFF',
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="pharmacy/[id]"
|
name="pharmacy/[id]"
|
||||||
|
options={{ title: 'Farmacia' }}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name="scanner"
|
||||||
options={{
|
options={{
|
||||||
title: 'Farmacia',
|
title: 'Escanear',
|
||||||
headerTintColor: '#007AFF',
|
headerShown: false,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ const styles = StyleSheet.create({
|
|||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: 'flex-start',
|
alignItems: 'flex-start',
|
||||||
padding: spacing.md,
|
padding: spacing.lg,
|
||||||
backgroundColor: colors.card,
|
backgroundColor: colors.card,
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
@@ -142,14 +142,14 @@ const styles = StyleSheet.create({
|
|||||||
infoSection: {
|
infoSection: {
|
||||||
backgroundColor: colors.card,
|
backgroundColor: colors.card,
|
||||||
marginTop: spacing.sm,
|
marginTop: spacing.sm,
|
||||||
padding: spacing.md,
|
padding: spacing.lg,
|
||||||
},
|
},
|
||||||
infoRow: {
|
infoRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
paddingVertical: spacing.sm,
|
paddingVertical: spacing.sm,
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderBottomColor: colors.separator,
|
borderBottomColor: colors.border,
|
||||||
},
|
},
|
||||||
infoLabel: {
|
infoLabel: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
@@ -162,7 +162,7 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
pharmaciesSection: {
|
pharmaciesSection: {
|
||||||
marginTop: spacing.sm,
|
marginTop: spacing.sm,
|
||||||
padding: spacing.md,
|
padding: spacing.lg,
|
||||||
},
|
},
|
||||||
sectionTitle: {
|
sectionTitle: {
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
@@ -177,9 +177,14 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
pharmacyCard: {
|
pharmacyCard: {
|
||||||
backgroundColor: colors.card,
|
backgroundColor: colors.card,
|
||||||
borderRadius: borderRadius.md,
|
borderRadius: borderRadius.lg,
|
||||||
marginBottom: spacing.sm,
|
marginBottom: spacing.sm,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.06,
|
||||||
|
shadowRadius: 8,
|
||||||
|
elevation: 2,
|
||||||
},
|
},
|
||||||
pharmacyCardContent: {
|
pharmacyCardContent: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
@@ -219,7 +224,7 @@ const styles = StyleSheet.create({
|
|||||||
gap: spacing.xs,
|
gap: spacing.xs,
|
||||||
paddingVertical: spacing.sm,
|
paddingVertical: spacing.sm,
|
||||||
borderTopWidth: 1,
|
borderTopWidth: 1,
|
||||||
borderTopColor: colors.separator,
|
borderTopColor: colors.border,
|
||||||
},
|
},
|
||||||
directionsText: {
|
directionsText: {
|
||||||
color: colors.primary,
|
color: colors.primary,
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
@@ -52,14 +52,14 @@ export function MedicineCard({ medicine }: MedicineCardProps) {
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
card: {
|
card: {
|
||||||
backgroundColor: colors.card,
|
backgroundColor: colors.card,
|
||||||
borderRadius: borderRadius.md,
|
borderRadius: borderRadius.lg,
|
||||||
padding: spacing.md,
|
padding: spacing.md,
|
||||||
marginHorizontal: spacing.md,
|
marginHorizontal: spacing.lg,
|
||||||
marginVertical: spacing.xs,
|
marginVertical: spacing.xs,
|
||||||
shadowColor: '#000',
|
shadowColor: '#000',
|
||||||
shadowOffset: { width: 0, height: 2 },
|
shadowOffset: { width: 0, height: 2 },
|
||||||
shadowOpacity: 0.1,
|
shadowOpacity: 0.06,
|
||||||
shadowRadius: 4,
|
shadowRadius: 8,
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
@@ -92,7 +92,7 @@ const styles = StyleSheet.create({
|
|||||||
price: {
|
price: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
color: colors.text,
|
color: colors.primary,
|
||||||
marginLeft: spacing.xs,
|
marginLeft: spacing.xs,
|
||||||
},
|
},
|
||||||
labContainer: {
|
labContainer: {
|
||||||
|
|||||||
@@ -60,11 +60,16 @@ const styles = StyleSheet.create({
|
|||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: colors.card,
|
backgroundColor: colors.card,
|
||||||
borderRadius: borderRadius.md,
|
borderRadius: borderRadius.lg,
|
||||||
paddingHorizontal: spacing.md,
|
paddingHorizontal: spacing.md,
|
||||||
paddingVertical: spacing.sm,
|
paddingVertical: spacing.sm + 2,
|
||||||
marginHorizontal: spacing.md,
|
marginHorizontal: spacing.lg,
|
||||||
marginVertical: spacing.sm,
|
marginVertical: spacing.sm,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.06,
|
||||||
|
shadowRadius: 8,
|
||||||
|
elevation: 2,
|
||||||
},
|
},
|
||||||
icon: {
|
icon: {
|
||||||
marginRight: spacing.sm,
|
marginRight: spacing.sm,
|
||||||
|
|||||||
@@ -33,13 +33,13 @@ const styles = StyleSheet.create({
|
|||||||
borderRadius: borderRadius.sm,
|
borderRadius: borderRadius.sm,
|
||||||
},
|
},
|
||||||
success: {
|
success: {
|
||||||
backgroundColor: '#D4EDDA',
|
backgroundColor: '#eaf7ec',
|
||||||
},
|
},
|
||||||
warning: {
|
warning: {
|
||||||
backgroundColor: '#FFF3CD',
|
backgroundColor: '#fff3cd',
|
||||||
},
|
},
|
||||||
danger: {
|
danger: {
|
||||||
backgroundColor: '#F8D7DA',
|
backgroundColor: '#feecec',
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
|||||||
@@ -1,25 +1,47 @@
|
|||||||
export const colors = {
|
export const colors = {
|
||||||
// Primary
|
// Primary - pastel green pharmacy palette (matching PWA)
|
||||||
primary: '#007AFF',
|
primary: '#7fbf8f',
|
||||||
primaryLight: '#4DA3FF',
|
primaryLight: '#cfead0',
|
||||||
primaryDark: '#0056CC',
|
primaryDark: '#09310a',
|
||||||
|
onPrimary: '#09310a',
|
||||||
|
primaryContainer: '#cfead0',
|
||||||
|
onPrimaryContainer: '#0d2b12',
|
||||||
|
|
||||||
|
// Secondary - soft blue
|
||||||
|
secondary: '#a3b8ff',
|
||||||
|
secondaryContainer: '#dbe7ff',
|
||||||
|
|
||||||
|
// Tertiary - soft lavender
|
||||||
|
tertiary: '#c9b3ff',
|
||||||
|
tertiaryContainer: '#efe7ff',
|
||||||
|
|
||||||
|
// Scan button - vivid blue (intentionally prominent)
|
||||||
|
scanButton: '#2b5bb5',
|
||||||
|
scanButtonShadow: 'rgba(43, 91, 181, 0.32)',
|
||||||
|
|
||||||
// Semantic
|
// Semantic
|
||||||
success: '#34C759',
|
success: '#34C759',
|
||||||
danger: '#FF3B30',
|
danger: '#b91c1c',
|
||||||
|
dangerContainer: '#feecec',
|
||||||
warning: '#FF9500',
|
warning: '#FF9500',
|
||||||
|
|
||||||
// Neutral
|
// Surface
|
||||||
background: '#F2F2F7',
|
background: '#fbfbfb',
|
||||||
card: '#FFFFFF',
|
card: '#ffffff',
|
||||||
border: '#E5E5EA',
|
surfaceLow: '#f2f4f5',
|
||||||
separator: '#C6C6C8',
|
surface: '#eceeef',
|
||||||
|
surfaceHigh: '#e6e8e9',
|
||||||
|
border: '#c0c9bb',
|
||||||
|
separator: '#c0c9bb',
|
||||||
|
|
||||||
// Text
|
// Text
|
||||||
text: '#1C1C1E',
|
text: '#111417',
|
||||||
textSecondary: '#8E8E93',
|
textSecondary: '#41493e',
|
||||||
textInverse: '#FFFFFF',
|
textInverse: '#ffffff',
|
||||||
textLink: '#007AFF',
|
textLink: '#7fbf8f',
|
||||||
|
|
||||||
|
// Accent
|
||||||
|
accentWarm: '#f5a97a',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const spacing = {
|
export const spacing = {
|
||||||
@@ -32,27 +54,67 @@ export const spacing = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const borderRadius = {
|
export const borderRadius = {
|
||||||
sm: 8,
|
sm: 4,
|
||||||
md: 12,
|
md: 8,
|
||||||
lg: 16,
|
lg: 12,
|
||||||
xl: 24,
|
xl: 16,
|
||||||
|
full: 9999,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const typography = {
|
export const typography = {
|
||||||
title: {
|
title: {
|
||||||
fontSize: 28,
|
fontSize: 28,
|
||||||
fontWeight: 'bold' as const,
|
fontWeight: 'bold' as const,
|
||||||
|
color: '#111417',
|
||||||
},
|
},
|
||||||
heading: {
|
heading: {
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: '600' as const,
|
fontWeight: '600' as const,
|
||||||
|
color: '#111417',
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: 'normal' as const,
|
fontWeight: 'normal' as const,
|
||||||
|
color: '#111417',
|
||||||
|
},
|
||||||
|
bodyLarge: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '400' as const,
|
||||||
|
color: '#41493e',
|
||||||
|
lineHeight: 27,
|
||||||
},
|
},
|
||||||
caption: {
|
caption: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: 'normal' as const,
|
fontWeight: 'normal' as const,
|
||||||
|
color: '#41493e',
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '500' as const,
|
||||||
|
color: '#41493e',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const shadows = {
|
||||||
|
soft: {
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 4 },
|
||||||
|
shadowOpacity: 0.08,
|
||||||
|
shadowRadius: 20,
|
||||||
|
elevation: 3,
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.06,
|
||||||
|
shadowRadius: 8,
|
||||||
|
elevation: 2,
|
||||||
|
},
|
||||||
|
scanButton: {
|
||||||
|
shadowColor: '#2b5bb5',
|
||||||
|
shadowOffset: { width: 0, height: 8 },
|
||||||
|
shadowOpacity: 0.32,
|
||||||
|
shadowRadius: 26,
|
||||||
|
elevation: 8,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user