feat: atualizar tema para indigo, corrigir textos e implementar AppContext

This commit is contained in:
Rodrigo Lopes dos Santos
2026-03-15 10:49:05 +00:00
parent d63c27ec12
commit 8ece90a37e
14 changed files with 209 additions and 244 deletions

View File

@@ -3,7 +3,7 @@ import { View, Text, StyleSheet, ViewStyle } from 'react-native';
type Props = {
children: React.ReactNode;
color?: 'amber' | 'slate' | 'green' | 'red' | 'blue';
color?: 'amber' | 'slate' | 'green' | 'red' | 'blue' | 'indigo';
variant?: 'solid' | 'soft';
style?: ViewStyle;
};
@@ -15,6 +15,7 @@ const colorMap = {
green: { bg: '#10b981', text: '#fff' },
red: { bg: '#ef4444', text: '#fff' },
blue: { bg: '#3b82f6', text: '#fff' },
indigo: { bg: '#6366f1', text: '#fff' },
},
soft: {
amber: { bg: '#fef3c7', text: '#92400e' },
@@ -22,10 +23,11 @@ const colorMap = {
green: { bg: '#d1fae5', text: '#065f46' },
red: { bg: '#fee2e2', text: '#991b1b' },
blue: { bg: '#dbeafe', text: '#1e40af' },
indigo: { bg: '#e0e7ff', text: '#4338ca' },
},
};
export const Badge = ({ children, color = 'amber', variant = 'soft', style }: Props) => {
export const Badge = ({ children, color = 'indigo', variant = 'soft', style }: Props) => {
const colors = colorMap[variant][color];
return (

View File

@@ -36,7 +36,7 @@ export const Button = ({ children, onPress, variant = 'solid', size = 'md', disa
activeOpacity={0.7}
>
{loading ? (
<ActivityIndicator color={variant === 'solid' ? '#fff' : '#f59e0b'} />
<ActivityIndicator color={variant === 'solid' ? '#fff' : '#6366f1'} />
) : (
<Text style={textStyles}>{children}</Text>
)}
@@ -52,12 +52,12 @@ const styles = StyleSheet.create({
flexDirection: 'row',
},
solid: {
backgroundColor: '#f59e0b',
backgroundColor: '#6366f1',
},
outline: {
backgroundColor: 'transparent',
borderWidth: 2,
borderColor: '#f59e0b',
borderColor: '#6366f1',
},
ghost: {
backgroundColor: 'transparent',
@@ -84,10 +84,10 @@ const styles = StyleSheet.create({
color: '#fff',
},
text_outline: {
color: '#f59e0b',
color: '#6366f1',
},
text_ghost: {
color: '#f59e0b',
color: '#6366f1',
},
textSize_sm: {
fontSize: 12,

View File

@@ -7,23 +7,34 @@
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { Appointment, Barber, BarberShop, CartItem, Order, Product, Service, User } from '../types';
import { supabase } from '../lib/supabase';
import { nanoid } from 'nanoid';
// Tipo interno determinando as propriedades globais partilhadas (Estados e Funções)
type State = {
user?: User;
shops: BarberShop[];
cart: CartItem[];
appointments: Appointment[];
orders: Order[];
};
type AppContextValue = State & {
login: (email: string, password: string) => boolean;
logout: () => void;
register: (payload: any) => boolean;
addToCart: (item: CartItem) => void;
clearCart: () => void;
createAppointment: (input: Omit<Appointment, 'id' | 'status' | 'total'>) => Promise<Appointment | null>;
updateAppointmentStatus: (id: string, status: Appointment['status']) => Promise<void>;
updateOrderStatus: (id: string, status: Order['status']) => Promise<void>;
addService: (shopId: string, service: Omit<Service, 'id'>) => Promise<void>;
updateService: (shopId: string, service: Service) => Promise<void>;
deleteService: (shopId: string, serviceId: string) => Promise<void>;
addProduct: (shopId: string, product: Omit<Product, 'id'>) => Promise<void>;
updateProduct: (shopId: string, product: Product) => Promise<void>;
deleteProduct: (shopId: string, productId: string) => Promise<void>;
addBarber: (shopId: string, barber: Omit<Barber, 'id'>) => Promise<void>;
updateBarber: (shopId: string, barber: Barber) => Promise<void>;
deleteBarber: (shopId: string, barberId: string) => Promise<void>;
refreshShops: () => Promise<void>;
};
@@ -32,135 +43,96 @@ const AppContext = createContext<AppContextValue | undefined>(undefined);
export const AppProvider = ({ children }: { children: React.ReactNode }) => {
const [shops, setShops] = useState<BarberShop[]>([]);
const [appointments, setAppointments] = useState<Appointment[]>([]);
const [orders, setOrders] = useState<Order[]>([]);
const [cart, setCart] = useState<CartItem[]>([]);
const [user, setUser] = useState<User | undefined>(undefined);
const [loading, setLoading] = useState(true);
/**
* Hook executado no carregamento (mount) inicial.
* Valida através de `supabase.auth.getUser()` se existe um token de sessão válido
* (identificando o utilizador sem necessidade de o cliente refazer login ativamente).
*/
useEffect(() => {
const loadUser = async () => {
// Pedido restrito à API de autenticação do Supabase
const { data } = await supabase.auth.getUser();
if (data.user) {
let shopId: string | undefined = undefined;
// Vai buscar o shop_id mapeado na tabela profiles
const { data: prof } = await supabase
.from('profiles')
.select('shop_id')
.select('shop_id, role, name')
.eq('id', data.user.id)
.single();
shopId = prof?.shop_id || undefined;
setUser({
id: data.user.id,
name: prof?.name || data.user.email?.split('@')[0] || 'Utilizador',
email: data.user.email || '',
role: 'barbearia', // assumido estaticamente na V1, deve vir de profiles
shopId
role: (prof?.role as any) || 'cliente',
shopId: prof?.shop_id || undefined,
} as User);
}
};
loadUser();
}, []);
/**
* Consulta mestra (Query) - Refresca todo o ecossistema de dados das barbearias.
* Faz 2 queries (`supabase.from('shops').select('*')` e `services`) e depois
* executa um JOIN manual via Javascript para injetar os serviços dentro
* dos objetos da barbearia respetiva.
*/
const refreshShops = async () => {
console.log("A buscar shops...");
try {
const { data: shopsData } = await supabase.from('shops').select('*');
const { data: servicesData } = await supabase.from('services').select('*');
const { data: barbersData } = await supabase.from('barbers').select('*');
const { data: productsData } = await supabase.from('products').select('*');
const { data: appointmentsData } = await supabase.from('appointments').select('*');
const { data: ordersData } = await supabase.from('orders').select('*');
// Query 1: Obtém a listagem completa (tabela 'shops')
const { data: shopsData, error: shopsError } = await supabase
.from('shops')
.select('*');
if (shopsData) {
const merged: BarberShop[] = shopsData.map((shop: any) => ({
...shop,
services: (servicesData || []).filter((s: any) => s.shop_id === shop.id).map((s: any) => ({
id: s.id,
name: s.name,
price: s.price,
duration: s.duration,
barberIds: s.barber_ids || [],
})),
products: (productsData || []).filter((p: any) => p.shop_id === shop.id),
barbers: (barbersData || []).filter((b: any) => b.shop_id === shop.id).map((b: any) => ({
id: b.id,
name: b.name,
specialties: b.specialties || [],
schedule: b.schedule || [],
})),
}));
setShops(merged);
}
if (shopsError) {
console.error("Erro ao buscar shops:", shopsError);
return;
if (appointmentsData) {
setAppointments(
appointmentsData.map((a: any) => ({
id: a.id,
shopId: a.shop_id,
serviceId: a.service_id,
barberId: a.barber_id,
customerId: a.customer_id,
date: a.date,
status: a.status as Appointment['status'],
total: a.total,
}))
);
}
if (ordersData) {
setOrders(
ordersData.map((o: any) => ({
id: o.id,
shopId: o.shop_id,
customerId: o.customer_id,
items: o.items,
total: o.total,
status: o.status as Order['status'],
createdAt: o.created_at,
}))
);
}
} catch (err) {
console.error('Error refreshing shops:', err);
}
// Query 2: Obtém a listagem associada globalmente (tabela 'services')
const { data: servicesData, error: servicesError } = await supabase
.from('services')
.select('*');
if (servicesError) {
console.error("Erro ao buscar services:", servicesError);
return;
}
// Query 3: Obtém a listagem global de Barbeiros (tabela 'barbers')
const { data: barbersData, error: barbersError } = await supabase
.from('barbers')
.select('*');
if (barbersError) {
console.error("Erro ao buscar barbers:", barbersError);
return;
}
// Query 4: Obtém a listagem global de Produtos (tabela 'products')
const { data: productsData, error: productsError } = await supabase
.from('products')
.select('*');
if (productsError) {
console.error("Erro ao buscar products:", productsError);
return;
}
// Query 5: Obtém a listagem global de Marcações (tabela 'appointments')
const { data: appointmentsData, error: appointmentsError } = await supabase
.from('appointments')
.select('*');
if (appointmentsError) {
console.error("Erro ao buscar appointments:", appointmentsError);
return;
}
if (appointmentsData) {
setAppointments(
appointmentsData.map((a: any) => ({
id: a.id,
shopId: a.shop_id,
serviceId: a.service_id,
barberId: a.barber_id,
customerId: a.customer_id,
date: a.date,
status: a.status as Appointment['status'],
total: a.total,
}))
);
}
// Associar serviços, barbeiros e produtos às respetivas shops, simulando um INNER JOIN nativo do SQL
const shopsWithServices = shopsData.map((shop) => ({
...shop,
// Relaciona a 'foreign key' (shop_id) com o resgistro primário (shop.id)
services: servicesData.filter((s) => s.shop_id === shop.id),
products: productsData.filter((p) => p.shop_id === shop.id),
barbers: barbersData.filter((b) => b.shop_id === shop.id),
}));
console.log("Shops carregadas:", shopsWithServices);
setShops(shopsWithServices);
};
/**
* Hook de Inicialização Master.
* Aciona a função de preenchimento do Contexto assincronamente e liberta
* a interface UI da view de Loading (`setLoading(false)`).
*/
useEffect(() => {
const init = async () => {
await refreshShops();
@@ -169,150 +141,136 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
init();
}, []);
/**
* Encerra a sessão JWT ativa com o Supabase Auth.
* Limpa integralmente a interface do User local (estado React vazio).
*/
const login = (email: string, password: string) => {
return true;
};
const logout = async () => {
await supabase.auth.signOut();
setUser(undefined);
};
// Funções elementares do fluxo transacional não persistido (Estado do Carrinho transitório/local)
const register = (payload: any) => {
const id = nanoid();
const newUser: User = { ...payload, id };
setUser(newUser);
return true;
};
const addToCart = (item: CartItem) => {
setCart((prev) => [...prev, item]);
setCart((prev: CartItem[]) => [...prev, item]);
};
const clearCart = () => setCart([]);
// 🔹 CRUD SERVICES (SUPABASE REAL)
/**
* Executa um INSERT na BD (via API REST gerada) protegendo interações com a tabela estrita 'services'.
* @param {string} shopId - A foreign key relacionando o estabelecimento.
* @param {Omit<Service, 'id'>} service - O DTO (Data Transfer Object) sem a primary key autonumerável.
*/
const addService = async (shopId: string, service: Omit<Service, 'id'>) => {
// Insere os campos exatos formatados estritamente na query Supabase
const { error } = await supabase.from('services').insert([
{
shop_id: shopId,
name: service.name,
price: service.price,
duration: service.duration,
},
]);
if (error) {
console.error("Erro ao adicionar serviço:", error);
return;
}
// Para manter integridade reativa pura, força refetch dos dados pós-mutação
await supabase.from('services').insert([{ shop_id: shopId, ...service }]);
await refreshShops();
};
/**
* Executa um UPDATE num tuplo específico filtrando analiticamente pela primary key `(eq('id', service.id))`.
*/
const updateService = async (shopId: string, service: Service) => {
const { error } = await supabase
.from('services')
.update({
name: service.name,
price: service.price,
duration: service.duration,
})
.eq('id', service.id); // Identificador vital do update
if (error) {
console.error("Erro ao atualizar serviço:", error);
return;
}
const { id, ...data } = service;
await supabase.from('services').update(data).eq('id', id);
await refreshShops();
};
/**
* Executa uma instrução SQL DELETE remota rígida, baseada no ID unívoco do tuplo.
*/
const deleteService = async (shopId: string, serviceId: string) => {
const { error } = await supabase
.from('services')
.delete()
.eq('id', serviceId);
if (error) {
console.error("Erro ao apagar serviço:", error);
return;
}
await supabase.from('services').delete().eq('id', serviceId);
await refreshShops();
};
const createAppointment: AppContextValue['createAppointment'] = async (input) => {
const shop = shops.find((s) => s.id === input.shopId);
if (!shop) return null;
const svc = shop.services.find((s) => s.id === input.serviceId);
if (!svc) return null;
const { data: newRow, error } = await supabase.from('appointments').insert([
{
shop_id: input.shopId,
service_id: input.serviceId,
barber_id: input.barberId,
customer_id: input.customerId,
date: input.date,
status: 'pendente',
total: svc.price,
}
]).select().single();
if (error || !newRow) {
console.error("Erro ao criar marcação na BD:", error);
return null;
}
const addProduct = async (shopId: string, product: Omit<Product, 'id'>) => {
await supabase.from('products').insert([{ shop_id: shopId, ...product }]);
await refreshShops();
};
const updateProduct = async (shopId: string, product: Product) => {
const { id, ...data } = product;
await supabase.from('products').update(data).eq('id', id);
await refreshShops();
};
const deleteProduct = async (shopId: string, productId: string) => {
await supabase.from('products').delete().eq('id', productId);
await refreshShops();
};
const addBarber = async (shopId: string, barber: Omit<Barber, 'id'>) => {
await supabase.from('barbers').insert([{ shop_id: shopId, ...barber }]);
await refreshShops();
};
const updateBarber = async (shopId: string, barber: Barber) => {
const { id, ...data } = barber;
await supabase.from('barbers').update(data).eq('id', id);
await refreshShops();
};
const deleteBarber = async (shopId: string, barberId: string) => {
await supabase.from('barbers').delete().eq('id', barberId);
await refreshShops();
};
const createAppointment = async (input: Omit<Appointment, 'id' | 'status' | 'total'>) => {
const svc = shops.flatMap(s => s.services).find(s => s.id === input.serviceId);
const total = svc ? svc.price : 0;
const { data } = await supabase.from('appointments').insert([{
shop_id: input.shopId,
service_id: input.serviceId,
barber_id: input.barberId,
customer_id: input.customerId,
date: input.date,
status: 'pendente',
total
}]).select().single();
await refreshShops();
return data as any as Appointment;
};
const updateAppointmentStatus = async (id: string, status: Appointment['status']) => {
await supabase.from('appointments').update({ status }).eq('id', id);
await refreshShops();
};
const updateOrderStatus = async (id: string, status: Order['status']) => {
await supabase.from('orders').update({ status }).eq('id', id);
await refreshShops();
return {
id: newRow.id,
shopId: newRow.shop_id,
serviceId: newRow.service_id,
barberId: newRow.barber_id,
customerId: newRow.customer_id,
date: newRow.date,
status: newRow.status as Appointment['status'],
total: newRow.total,
};
};
// Empacotamento em objeto estabilizado memoizado face renderizações espúrias (React Context Pattern)
const value: AppContextValue = useMemo(
() => ({
user,
shops,
cart,
appointments,
orders,
login,
logout,
register,
addToCart,
clearCart,
createAppointment,
updateAppointmentStatus,
updateOrderStatus,
addService,
updateService,
deleteService,
addProduct,
updateProduct,
deleteProduct,
addBarber,
updateBarber,
deleteBarber,
refreshShops,
}),
[user, shops, cart, appointments]
[user, shops, cart, appointments, orders]
);
// Loading Shield evita quebra generalizada se o app renderizar sem BD disponível
if (loading) return null;
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
};
// Hook prático de acesso central sem import múltiplo do 'useContext' em toda aplicação
export const useApp = () => {
const ctx = useContext(AppContext);
if (!ctx) throw new Error('useApp deve ser usado dentro de AppProvider');

7
src/lib/supabase.ts Normal file
View File

@@ -0,0 +1,7 @@
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = 'https://jqklhhpyykzrktikjnmb.supabase.co'
const supabaseAnonKey =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Impxa2xoaHB5eWt6cmt0aWtqbm1iIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjgzODQ0MDgsImV4cCI6MjA4Mzk2MDQwOH0.QsPuBnyUtRPSavlqKj3IGR9c8juT02LY_hSi-j3c6M0'
export const supabase = createClient(supabaseUrl, supabaseAnonKey)

View File

@@ -22,7 +22,7 @@ export default function AppNavigator() {
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerStyle: { backgroundColor: '#f59e0b' },
headerStyle: { backgroundColor: '#6366f1' },
headerTintColor: '#fff',
headerTitleStyle: { fontWeight: 'bold' },
}}

View File

@@ -161,7 +161,7 @@ const styles = StyleSheet.create({
},
footerLink: {
fontSize: 14,
color: '#f59e0b',
color: '#6366f1',
fontWeight: '600',
},
});

View File

@@ -108,7 +108,7 @@ export default function AuthRegister() {
label="Nome da barbearia"
value={shopName}
onChangeText={setShopName}
placeholder="Barbearia XPTO"
placeholder="Ex: Minha Barbearia"
/>
)}
@@ -170,8 +170,8 @@ const styles = StyleSheet.create({
alignItems: 'center',
},
roleButtonActive: {
borderColor: '#f59e0b',
backgroundColor: '#fef3c7',
borderColor: '#6366f1',
backgroundColor: '#c7d2fe',
},
roleText: {
fontSize: 14,
@@ -179,7 +179,7 @@ const styles = StyleSheet.create({
color: '#64748b',
},
roleTextActive: {
color: '#f59e0b',
color: '#6366f1',
},
submitButton: {
width: '100%',
@@ -199,7 +199,7 @@ const styles = StyleSheet.create({
},
footerLink: {
fontSize: 14,
color: '#f59e0b',
color: '#6366f1',
fontWeight: '600',
},
});

View File

@@ -248,8 +248,8 @@ const styles = StyleSheet.create({
marginBottom: 8,
},
serviceButtonActive: {
borderColor: '#f59e0b',
backgroundColor: '#fef3c7',
borderColor: '#6366f1',
backgroundColor: '#e0e7ff',
},
serviceText: {
fontSize: 14,
@@ -258,7 +258,7 @@ const styles = StyleSheet.create({
marginBottom: 4,
},
serviceTextActive: {
color: '#f59e0b',
color: '#6366f1',
},
servicePrice: {
fontSize: 12,
@@ -278,8 +278,8 @@ const styles = StyleSheet.create({
borderColor: '#e2e8f0',
},
barberButtonActive: {
borderColor: '#f59e0b',
backgroundColor: '#f59e0b',
borderColor: '#6366f1',
backgroundColor: '#6366f1',
},
barberText: {
fontSize: 14,
@@ -303,8 +303,8 @@ const styles = StyleSheet.create({
borderColor: '#e2e8f0',
},
slotButtonActive: {
borderColor: '#f59e0b',
backgroundColor: '#f59e0b',
borderColor: '#6366f1',
backgroundColor: '#6366f1',
},
slotText: {
fontSize: 14,
@@ -339,7 +339,7 @@ const styles = StyleSheet.create({
summaryTotal: {
fontSize: 18,
fontWeight: 'bold',
color: '#f59e0b',
color: '#6366f1',
marginTop: 8,
},
submitButton: {

View File

@@ -181,7 +181,7 @@ const styles = StyleSheet.create({
total: {
fontSize: 18,
fontWeight: 'bold',
color: '#f59e0b',
color: '#6366f1',
},
item: {
flexDirection: 'row',

View File

@@ -126,7 +126,7 @@ export default function Dashboard() {
* Atualiza a quantidade de inventário de um produto iterando a variação (+1/-1).
* Desencadeia um update para a tabela products (ex: `supabase.from('products').update(...)`) evitando stocks negativos.
* @param {string} productId - O identificador único do produto afetado.
* @param {number} delta - A quantidade exata matemática a variar da realidade material.
* @param {number} delta - A quantidade exata matemática a variar do inventário.
*/
const updateProductStock = (productId: string, delta: number) => {
const product = shop.products.find((p) => p.id === productId);
@@ -206,7 +206,7 @@ export default function Dashboard() {
<Text style={styles.itemName}>{svc?.name ?? 'Serviço'}</Text>
<Text style={styles.itemDesc}>{barber?.name} · {a.date}</Text>
</View>
<Badge color={a.status === 'pendente' ? 'amber' : a.status === 'confirmado' ? 'green' : 'red'}>
<Badge color={a.status === 'pendente' ? 'indigo' : a.status === 'confirmado' ? 'green' : 'red'}>
{a.status}
</Badge>
</View>
@@ -247,7 +247,7 @@ export default function Dashboard() {
<Text style={styles.itemName}>{currency(o.total)}</Text>
<Text style={styles.itemDesc}>{new Date(o.createdAt).toLocaleString('pt-BR')}</Text>
</View>
<Badge color={o.status === 'pendente' ? 'amber' : o.status === 'confirmado' ? 'green' : 'red'}>
<Badge color={o.status === 'pendente' ? 'indigo' : o.status === 'confirmado' ? 'green' : 'red'}>
{o.status}
</Badge>
</View>
@@ -438,7 +438,7 @@ const styles = StyleSheet.create({
borderBottomColor: 'transparent',
},
tabActive: {
borderBottomColor: '#f59e0b',
borderBottomColor: '#6366f1',
},
tabText: {
fontSize: 14,
@@ -446,7 +446,7 @@ const styles = StyleSheet.create({
color: '#64748b',
},
tabTextActive: {
color: '#f59e0b',
color: '#6366f1',
},
content: {
flex: 1,
@@ -476,15 +476,15 @@ const styles = StyleSheet.create({
color: '#0f172a',
},
statValueWarning: {
color: '#f59e0b',
color: '#6366f1',
},
itemCard: {
marginBottom: 12,
padding: 16,
},
itemCardWarning: {
borderColor: '#fbbf24',
backgroundColor: '#fef3c7',
borderColor: '#c7d2fe',
backgroundColor: '#e0e7ff',
},
itemHeader: {
flexDirection: 'row',
@@ -506,7 +506,7 @@ const styles = StyleSheet.create({
itemPrice: {
fontSize: 18,
fontWeight: 'bold',
color: '#f59e0b',
color: '#6366f1',
},
statusSelector: {
marginTop: 8,
@@ -534,15 +534,15 @@ const styles = StyleSheet.create({
color: '#64748b',
},
alertCard: {
backgroundColor: '#fef3c7',
borderColor: '#fbbf24',
backgroundColor: '#e0e7ff',
borderColor: '#c7d2fe',
marginBottom: 16,
padding: 16,
},
alertText: {
fontSize: 14,
fontWeight: '600',
color: '#92400e',
color: '#4338ca',
},
formCard: {
marginTop: 16,

View File

@@ -35,7 +35,7 @@ export default function Explore() {
<Card style={styles.shopCard}>
<View style={styles.shopHeader}>
<Text style={styles.shopName}>{shop.name}</Text>
<Badge color="amber">{shop.rating.toFixed(1)} </Badge>
<Badge color="indigo">{shop.rating.toFixed(1)} </Badge>
</View>
<Text style={styles.shopAddress}>{shop.address}</Text>

View File

@@ -34,7 +34,7 @@ export default function Landing() {
Explorar barbearias
</Button>
{/* Botão nativo focado à inserção de utilizadores - Cria sessão no ecositema de Auth/BD */}
{/* Botão focado no registo de novos utilizadores */}
<Button
onPress={() => navigation.navigate('Register' as never)}
variant="outline"
@@ -80,7 +80,7 @@ const styles = StyleSheet.create({
padding: 16,
},
hero: {
backgroundColor: '#f59e0b',
backgroundColor: '#6366f1',
borderRadius: 16,
padding: 24,
marginBottom: 24,
@@ -99,7 +99,7 @@ const styles = StyleSheet.create({
},
heroDesc: {
fontSize: 16,
color: '#fef3c7',
color: '#e0e7ff',
marginBottom: 20,
},
buttons: {
@@ -126,5 +126,3 @@ const styles = StyleSheet.create({
lineHeight: 20,
},
});

View File

@@ -172,7 +172,7 @@ const styles = StyleSheet.create({
itemTotal: {
fontSize: 16,
fontWeight: 'bold',
color: '#f59e0b',
color: '#6366f1',
},
emptyCard: {
padding: 32,

View File

@@ -164,7 +164,7 @@ const styles = StyleSheet.create({
alignItems: 'center',
},
tabActive: {
borderColor: '#f59e0b',
borderColor: '#6366f1',
backgroundColor: '#fef3c7',
},
tabText: {
@@ -173,7 +173,7 @@ const styles = StyleSheet.create({
color: '#64748b',
},
tabTextActive: {
color: '#f59e0b',
color: '#6366f1',
},
list: {
gap: 12,
@@ -195,7 +195,7 @@ const styles = StyleSheet.create({
itemPrice: {
fontSize: 16,
fontWeight: 'bold',
color: '#f59e0b',
color: '#6366f1',
},
itemDesc: {
fontSize: 14,