feat: enhance booking flow with optional serviceId and improve dashboard settings
- Updated RootStackParamList to include optional serviceId in Booking route. - Modified Booking page to initialize step and serviceId based on route parameters. - Enhanced Dashboard page with new settings tab for shop details, including image upload and JSON schedule management. - Added functionality to upload barber images and cover images. - Improved Profile page with review submission for appointments and better notification handling. - Updated ShopDetails to pass serviceId when navigating to Booking.
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { Appointment, Barber, BarberShop, CartItem, Order, Product, Service, User, WaitlistEntry, AppNotification } from '../types';
|
||||
import { supabase } from '../lib/supabase';
|
||||
import { storage } from '../lib/storage';
|
||||
import * as Device from 'expo-device';
|
||||
import Constants from 'expo-constants';
|
||||
import { Platform } from 'react-native';
|
||||
@@ -45,6 +46,8 @@ type AppContextValue = State & {
|
||||
addBarber: (shopId: string, barber: Omit<Barber, 'id'>) => Promise<void>;
|
||||
updateBarber: (shopId: string, barber: Barber) => Promise<void>;
|
||||
deleteBarber: (shopId: string, barberId: string) => Promise<void>;
|
||||
updateShopDetails: (shopId: string, payload: Partial<BarberShop>) => Promise<void>;
|
||||
submitReview: (shopId: string, appointmentId: string, rating: number, comment: string) => Promise<void>;
|
||||
joinWaitlist: (shopId: string, serviceId: string, barberId: string, date: string) => Promise<boolean>;
|
||||
markNotificationRead: (id: string) => Promise<void>;
|
||||
refreshShops: () => Promise<void>;
|
||||
@@ -64,6 +67,22 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const [shopsReady, setShopsReady] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
storage.get<{ favorites?: string[]; cart?: CartItem[] }>('smart-agenda-mobile-state', {}).then((stored) => {
|
||||
if (!mounted) return;
|
||||
if (Array.isArray(stored.favorites)) setFavorites(stored.favorites);
|
||||
if (Array.isArray(stored.cart)) setCart(stored.cart);
|
||||
});
|
||||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
storage.set('smart-agenda-mobile-state', { favorites, cart });
|
||||
}, [favorites, cart]);
|
||||
|
||||
const applySupabaseUser = async (authUser: any): Promise<User | undefined> => {
|
||||
if (!authUser) return undefined;
|
||||
|
||||
@@ -113,39 +132,47 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const { data: ordersData } = await supabase.from('orders').select('*');
|
||||
const { data: waitlistsData } = await supabase.from('waitlist').select('*');
|
||||
const { data: notificationsData } = await supabase.from('notifications').select('*');
|
||||
const { data: reviewsData } = await supabase.from('reviews').select('shop_id, rating');
|
||||
|
||||
if (shopsData) {
|
||||
const merged: BarberShop[] = shopsData.map((shop: any) => ({
|
||||
id: shop.id,
|
||||
name: shop.name,
|
||||
address: shop.address || '',
|
||||
rating: shop.rating || 0,
|
||||
imageUrl: shop.image_url || shop.imageUrl || undefined,
|
||||
schedule: shop.schedule || undefined,
|
||||
paymentMethods: shop.payment_methods || undefined,
|
||||
socialNetworks: shop.social_networks || undefined,
|
||||
contacts: shop.contacts || undefined,
|
||||
services: (servicesData || []).filter((s: any) => s.shop_id === shop.id).map((s: any) => ({
|
||||
id: s.id,
|
||||
name: s.name,
|
||||
price: s.price || 0,
|
||||
duration: s.duration || 30,
|
||||
barberIds: s.barber_ids || [],
|
||||
})),
|
||||
products: (productsData || []).filter((p: any) => p.shop_id === shop.id).map((p: any) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
price: p.price || 0,
|
||||
stock: p.stock || 0,
|
||||
})),
|
||||
barbers: (barbersData || []).filter((b: any) => b.shop_id === shop.id).map((b: any) => ({
|
||||
id: b.id,
|
||||
name: b.name,
|
||||
imageUrl: b.image_url || undefined,
|
||||
specialties: b.specialties || [],
|
||||
schedule: b.schedule || [],
|
||||
})),
|
||||
}));
|
||||
const merged: BarberShop[] = shopsData.map((shop: any) => {
|
||||
const shopReviews = (reviewsData || []).filter((r: any) => r.shop_id === shop.id);
|
||||
const avgRating = shopReviews.length
|
||||
? shopReviews.reduce((sum: number, r: any) => sum + (r.rating || 0), 0) / shopReviews.length
|
||||
: shop.rating || 0;
|
||||
|
||||
return {
|
||||
id: shop.id,
|
||||
name: shop.name,
|
||||
address: shop.address || '',
|
||||
rating: avgRating,
|
||||
imageUrl: shop.image_url || shop.imageUrl || undefined,
|
||||
schedule: shop.schedule || undefined,
|
||||
paymentMethods: shop.payment_methods || undefined,
|
||||
socialNetworks: shop.social_networks || undefined,
|
||||
contacts: shop.contacts || undefined,
|
||||
services: (servicesData || []).filter((s: any) => s.shop_id === shop.id).map((s: any) => ({
|
||||
id: s.id,
|
||||
name: s.name,
|
||||
price: s.price || 0,
|
||||
duration: s.duration || 30,
|
||||
barberIds: s.barber_ids || [],
|
||||
})),
|
||||
products: (productsData || []).filter((p: any) => p.shop_id === shop.id).map((p: any) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
price: p.price || 0,
|
||||
stock: p.stock || 0,
|
||||
})),
|
||||
barbers: (barbersData || []).filter((b: any) => b.shop_id === shop.id).map((b: any) => ({
|
||||
id: b.id,
|
||||
name: b.name,
|
||||
imageUrl: b.image_url || undefined,
|
||||
specialties: b.specialties || [],
|
||||
schedule: b.schedule || [],
|
||||
})),
|
||||
};
|
||||
});
|
||||
setShops(merged);
|
||||
}
|
||||
|
||||
@@ -334,13 +361,23 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const clearCart = () => setCart([]);
|
||||
|
||||
const addService = async (shopId: string, service: Omit<Service, 'id'>) => {
|
||||
await supabase.from('services').insert([{ shop_id: shopId, ...service }]);
|
||||
await supabase.from('services').insert([{
|
||||
shop_id: shopId,
|
||||
name: service.name,
|
||||
price: service.price,
|
||||
duration: service.duration,
|
||||
barber_ids: service.barberIds || [],
|
||||
}]);
|
||||
await refreshShops();
|
||||
};
|
||||
|
||||
const updateService = async (shopId: string, service: Service) => {
|
||||
const { id, ...data } = service;
|
||||
await supabase.from('services').update(data).eq('id', id);
|
||||
await supabase.from('services').update({
|
||||
name: service.name,
|
||||
price: service.price,
|
||||
duration: service.duration,
|
||||
barber_ids: service.barberIds || [],
|
||||
}).eq('id', service.id);
|
||||
await refreshShops();
|
||||
};
|
||||
|
||||
@@ -391,6 +428,41 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
await refreshShops();
|
||||
};
|
||||
|
||||
const updateShopDetails = async (shopId: string, payload: Partial<BarberShop>) => {
|
||||
const { services, products, barbers, id, imageUrl, paymentMethods, socialNetworks, contacts, schedule, ...rest } = payload;
|
||||
const updateData: Record<string, any> = { ...rest };
|
||||
if (imageUrl !== undefined) updateData.image_url = imageUrl;
|
||||
if (paymentMethods !== undefined) updateData.payment_methods = paymentMethods;
|
||||
if (socialNetworks !== undefined) updateData.social_networks = socialNetworks;
|
||||
if (contacts !== undefined) updateData.contacts = contacts;
|
||||
if (schedule !== undefined) updateData.schedule = schedule;
|
||||
|
||||
const { error } = await supabase.from('shops').update(updateData).eq('id', shopId);
|
||||
if (error) throw error;
|
||||
|
||||
if (payload.name) {
|
||||
await supabase.from('profiles').update({ shop_name: payload.name }).eq('shop_id', shopId);
|
||||
}
|
||||
|
||||
setShops((prev) => prev.map((shop) => (shop.id === shopId ? { ...shop, ...payload } : shop)));
|
||||
};
|
||||
|
||||
const submitReview = async (shopId: string, appointmentId: string, rating: number, comment: string) => {
|
||||
const { data } = await supabase.auth.getUser();
|
||||
const customerId = data.user?.id;
|
||||
if (!customerId) throw new Error('Sessão expirada. Faz login novamente.');
|
||||
|
||||
const { error } = await supabase.from('reviews').insert([{
|
||||
shop_id: shopId,
|
||||
customer_id: customerId,
|
||||
appointment_id: appointmentId,
|
||||
rating,
|
||||
comment: comment.trim() || null,
|
||||
}]);
|
||||
if (error) throw error;
|
||||
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;
|
||||
@@ -493,6 +565,8 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
addBarber,
|
||||
updateBarber,
|
||||
deleteBarber,
|
||||
updateShopDetails,
|
||||
submitReview,
|
||||
joinWaitlist,
|
||||
markNotificationRead,
|
||||
refreshShops,
|
||||
|
||||
Reference in New Issue
Block a user