### Environment Variables Example Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/implementation/sprints/sprint-0/task-4-project-configuration-files.task.md List all required environment variables in this example file. Add .env.local to .gitignore. ```dotenv EXPO_PUBLIC_FIREBASE_API_KEY= EXPO_PUBLIC_FIREBASE_PROJECT_ID= EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN= EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET= EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID= EXPO_PUBLIC_FIREBASE_APP_ID= ``` -------------------------------- ### Install Project Dependencies Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/README.md Standard npm command to install all project dependencies after cloning the repository. Ensure Node.js LTS is installed. ```bash npm install ``` -------------------------------- ### Start Development Server Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/README.md Command to launch the Expo development server, enabling hot-reloading and debugging for the AutoShop POS application. ```bash npx expo start ``` -------------------------------- ### Install Core Dependencies for AutoShop POS Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/implementation/plan.md Install essential libraries for the AutoShop POS application, including UI components, navigation, state management, Firebase, and device-specific modules. ```bash npm install \ react-native-paper \ @react-navigation/native, @react-navigation/bottom-tabs, @react-navigation/native-stack \ zustand \ firebase \ expo-camera \ expo-crypto \ @react-native-async-storage/async-storage \ react-native-device-info \ expo-bluetooth ``` -------------------------------- ### Initial Setup Screen UI Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/implementation/plan.md Component displayed on the first app launch for setting the main administrator PIN, including validation for 6-digit numeric input and confirmation matching. ```typescript const InitSetupScreen = () => { const [pin, setPin] = useState(''); const [confirmPin, setConfirmPin] = useState(''); const [error, setError] = useState(''); const handleSetup = async () => { if (pin !== confirmPin) { setError('PINs do not match'); return; } if (!/^\[0-9]{6}$/.test(pin)) { setError('PIN must be 6 digits'); return; } // ... hash PIN and create user ... }; return ( // ... JSX for PIN entry, confirmation, and setup button ... ); }; ``` -------------------------------- ### Create Inventory List Screen Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/implementation/sprints/sprint-2/us-2-2-product-inventory-screen-and-list-management.task.md Implementation details for the InventoryListScreen component, including FlatList setup, search functionality, and low-stock indicators. ```typescript src/screens/inventory/InventoryListScreen.tsx ``` -------------------------------- ### Subscribe to Customers in Firestore Source: https://context7.com/jun-alcantara/automachenicshop-poc/llms.txt Sets up a real-time listener for active customers, ordered by name. Requires Firebase Firestore setup. ```typescript // src/services/customerService.ts import { collection, doc, query, where, orderBy, onSnapshot, writeBatch, serverTimestamp, Unsubscribe } from 'firebase/firestore'; import { db } from './firebase'; import { Customer, User } from '../types'; export const subscribeToCustomers = ( onChange: (customers: Customer[]) => void, onError: (error: Error) => void ): Unsubscribe => { const q = query( collection(db, 'customers'), where('isActive', '==', true), orderBy('name', 'asc') ); return onSnapshot(q, (snap) => { onChange(snap.docs.map(doc => ({ id: doc.id, ...doc.data() } as Customer))); }, onError); }; ``` -------------------------------- ### Inventory Store Setup (TypeScript) Source: https://context7.com/jun-alcantara/automachenicshop-poc/llms.txt Initializes a Zustand store for inventory management, subscribing to product and service item changes via their respective services. Manages loading states and errors. ```typescript // src/stores/inventoryStore.ts import { create } from 'zustand'; import { Product, ServiceItem } from '../types'; import { subscribeToProducts } from '../services/productService'; import { subscribeToServices } from '../services/serviceItemService'; import { Unsubscribe, FirestoreError } from 'firebase/firestore'; interface InventoryStore { products: Product[]; serviceItems: ServiceItem[]; loading: boolean; error: FirestoreError | null; _unsubscribeProducts: Unsubscribe | null; _unsubscribeServices: Unsubscribe | null; subscribe: () => void; unsubscribe: () => void; } export const useInventoryStore = create((set, get) => ({ products: [], serviceItems: [], loading: true, error: null, _unsubscribeProducts: null, _unsubscribeServices: null, subscribe: () => { const unsubProducts = subscribeToProducts( (products) => set({ products, loading: false }), (error) => set({ error, loading: false }) ); const unsubServices = subscribeToServices( (serviceItems) => set({ serviceItems }), (error) => set({ error }) ); set({ _unsubscribeProducts: unsubProducts, _unsubscribeServices: unsubServices }); }, unsubscribe: () => { const { _unsubscribeProducts, _unsubscribeServices } = get(); _unsubscribeProducts?.(); _unsubscribeServices?.(); set({ _unsubscribeProducts: null, _unsubscribeServices: null, products: [], serviceItems: [], }); }, })); ``` -------------------------------- ### Get Product by Barcode (TypeScript) Source: https://context7.com/jun-alcantara/automachenicshop-poc/llms.txt Finds a product within a given list by its barcode. Returns undefined if no product matches. ```typescript export const getProductByBarcode = ( products: Product[], barcode: string ): Product | undefined => { return products.find(p => p.barcode === barcode); }; ``` -------------------------------- ### Initialize Firebase and Firestore with Offline Persistence Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/implementation/sprints/sprint-0/task-2-firebase-project-setup.task.md Initializes the Firebase app and Firestore, enabling offline persistence. Handles potential 'failed-precondition' errors during persistence setup. ```typescript import { initializeApp } from 'firebase/app'; import { getFirestore, enableIndexedDbPersistence } from 'firebase/firestore'; const app = initializeApp({ apiKey: process.env.EXPO_PUBLIC_FIREBASE_API_KEY, projectId: process.env.EXPO_PUBLIC_FIREBASE_PROJECT_ID, // ... other config }); export const db = getFirestore(app); enableIndexedDbPersistence(db).catch((err) => { if (err.code !== 'failed-precondition') console.error(err); }); ``` -------------------------------- ### Initialize Firebase with Offline Persistence Source: https://context7.com/jun-alcantara/automachenicshop-poc/llms.txt Initializes Firebase with Firestore and enables IndexedDB persistence for offline-first functionality. Handles potential 'failed-precondition' errors during persistence setup. ```typescript // src/services/firebase.ts import { initializeApp } from 'firebase/app'; import { getFirestore, enableIndexedDbPersistence } from 'firebase/firestore'; const app = initializeApp({ apiKey: process.env.EXPO_PUBLIC_FIREBASE_API_KEY, projectId: process.env.EXPO_PUBLIC_FIREBASE_PROJECT_ID, // ... other config }); export const db = getFirestore(app); enableIndexedDbPersistence(db).catch((err) => { if (err.code !== 'failed-precondition') console.error(err); }); ``` -------------------------------- ### Configure GitHub Actions Lint Workflow Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/implementation/sprints/sprint-0/task-7-cicd-pipeline.task.md Defines a workflow that runs ESLint on every push and pull request. Requires Node.js 20 and dependencies installed via npm ci. ```yaml name: Lint on: [push, pull_request] jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: '20' } - run: npm ci - run: npx eslint src/ --max-warnings 0 ``` -------------------------------- ### Configure App Entry Point Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/implementation/sprints/sprint-0/task-5-navigation-and-app-entry-point.task.md Sets up the main App component with PaperProvider and conditional navigation based on session status. ```tsx import { PaperProvider } from 'react-native-paper'; import RootNavigator from './src/navigation/RootNavigator'; import { useSessionStore } from './src/stores/sessionStore'; export default function App() { const status = useSessionStore(s => s.status); return ( ); } ``` -------------------------------- ### Initialize and Configure Firebase Project Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/implementation/plan.md This task involves setting up the Firebase project and ensuring the application can connect to it. It's a foundational step for features relying on cloud services like Firestore. ```bash npm install && npm start ``` -------------------------------- ### Initialize Expo App with TypeScript Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/implementation/sprints/sprint-0/task-1-project-initialization-and-folder-structure.task.md Use this command to bootstrap a new Expo project with TypeScript support. This command sets up the basic project structure and configuration. ```bash npx create-expo-app autoshop-pos --template ``` -------------------------------- ### Subscribe to Products (TypeScript) Source: https://context7.com/jun-alcantara/automachenicshop-poc/llms.txt Sets up a real-time listener for active products, ordered by name. Handles data changes and errors. ```typescript // src/services/productService.ts import { collection, doc, query, where, orderBy, onSnapshot, writeBatch, serverTimestamp, Unsubscribe } from 'firebase/firestore'; import { db } from './firebase'; import { Product, User, AuditLog } from '../types'; export const subscribeToProducts = ( onChange: (products: Product[]) => void, onError: (error: Error) => void ): Unsubscribe => { const q = query( collection(db, 'products'), where('isActive', '==', true), orderBy('name', 'asc') ); return onSnapshot(q, (snap) => { onChange(snap.docs.map(doc => ({ id: doc.id, ...doc.data() } as Product))); }, onError); }; ``` -------------------------------- ### PIN Hashing Implementation Strategy Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/documents/tech_stack.md Conceptual implementation for secure PIN hashing using PBKDF2 with a per-user salt and 10,000 iterations. ```javascript PBKDF2(pin, userId + randomSalt, { iterations: 10000, keySize: 256/32 }) ``` -------------------------------- ### Implement Virtualized Lists with FlatList Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/implementation/sprints/sprint-6/us-5-6-performance-optimization-and-app-stability.task.md Utilize FlatList for all scrollable lists and ensure a `keyExtractor` prop is provided. This enables list virtualization, rendering only the items currently visible on screen, which significantly improves performance for long lists. ```javascript item.id} // ... other props /> ``` -------------------------------- ### Implement Session Store Login Functionality Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/implementation/plan.md Manages user session state, including logging a user in by fetching their document and populating the session store. ```typescript function login(userId: string) { // Fetch user doc from Firestore const userDoc = await firestore.collection('users').doc(userId).get(); // Populate session store sessionStore.set({ userId, isLoggedIn: true, ...userDoc.data() }); } ``` -------------------------------- ### Project Structure Overview Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/README.md This text-based representation illustrates the directory structure of the AutoShop POS project, categorizing files by their function within the application. ```text autoshop-pos/ ├── src/ │ ├── navigation/ # React Navigation setup and stack definitions │ ├── screens/ # Feature-grouped UI screens │ ├── components/ # Reusable UI components │ ├── stores/ # Zustand state management │ ├── services/ # Firestore and external API integrations │ ├── hooks/ # Custom React hooks (e.g., useInactivityTimer) │ ├── constants/ # App-wide enums, theme, and literals │ ├── types/ # TypeScript interfaces and types │ └── utils/ # Helper functions (math, formatting, hashing) ├── assets/ # Static images and fonts ├── documents/ # Technical specifications and architecture docs └── design/ # UI/UX design assets ``` -------------------------------- ### Build Development Client Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/README.md EAS Build command to create a development client for Android, useful for testing hardware integrations and custom native modules. ```bash eas build --profile development --platform android ``` -------------------------------- ### Project Root Directory Structure Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/documents/architecture.md Top-level directory layout for the AutoShop POS project. ```text autoshop-pos/ ├── src/ │ ├── navigation/ # React Navigation setup │ ├── screens/ # One file per screen, grouped by feature │ ├── components/ # Reusable UI components │ ├── stores/ # Zustand store slices │ ├── services/ # All Firestore reads/writes │ ├── hooks/ # Custom React hooks │ ├── constants/ # Enums, theme, app-wide literals │ ├── types/ # TypeScript interfaces & types │ └── utils/ # Pure functions (math, formatting, hashing) ├── assets/ # Images, fonts ├── app.json ├── package.json └── tsconfig.json ``` -------------------------------- ### Components Module Structure Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/documents/architecture.md Categorization of UI components by scope and reusability. ```text components/ ├── common/ # Generic, context-free UI atoms │ ├── AppButton.tsx │ ├── AppInput.tsx │ ├── AppBadge.tsx │ ├── Divider.tsx │ ├── EmptyState.tsx │ └── LoadingOverlay.tsx ├── layout/ # Screen chrome and structural wrappers │ ├── ScreenWrapper.tsx │ └── SectionHeader.tsx ├── transaction/ # Components used only in transaction screens │ ├── TransactionCard.tsx │ ├── LineItemRow.tsx │ ├── PaymentMethodInput.tsx │ └── RefundBreakdown.tsx └── inventory/ # Components used only in inventory screens ├── ProductCard.tsx └── StockBadge.tsx ``` -------------------------------- ### Create Product Form Screen Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/implementation/sprints/sprint-2/us-2-2-product-inventory-screen-and-list-management.task.md Implementation details for the ProductFormScreen component, covering form fields, validation, and product creation/update logic. ```typescript src/screens/inventory/ProductFormScreen.tsx ``` -------------------------------- ### Hash PIN with PBKDF2 and Generate Salt Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/implementation/plan.md Implements PIN hashing using PBKDF2 with a unique salt generated per instance. This is crucial for securely storing user PINs without storing the plain text. ```typescript async function hashPin(pin: string): Promise<{ hash: string, salt: string }> { const salt = crypto.randomBytes(16).toString('hex'); const hash = crypto.pbkdf2Sync(pin, salt, 10000, 64, 'sha512').toString('hex'); return { hash, salt }; } ``` -------------------------------- ### Create Product Card Component Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/implementation/sprints/sprint-2/us-2-2-product-inventory-screen-and-list-management.task.md Component for displaying individual product information in a list format. ```typescript src/components/inventory/ProductCard.tsx ``` -------------------------------- ### Create Product (TypeScript) Source: https://context7.com/jun-alcantara/automachenicshop-poc/llms.txt Creates a new product in Firestore using a batched write operation. Includes audit logging and sets initial timestamps and created/updated by fields. ```typescript export const createProduct = async ( product: Omit, actingUser: User ): Promise => { const batch = writeBatch(db); const productRef = doc(collection(db, 'products')); batch.set(productRef, { ...product, stockReserved: 0, isActive: true, createdAt: serverTimestamp(), createdBy: actingUser.id, updatedAt: serverTimestamp(), updatedBy: actingUser.id, }); // Audit entry const auditRef = doc(collection(db, 'auditLogs')); batch.set(auditRef, { timestamp: serverTimestamp(), userId: actingUser.id, userName: actingUser.displayName, actionType: 'CREATE_PRODUCT', entityType: 'PRODUCT', entityId: productRef.id, after: product, }); await batch.commit(); return productRef.id; }; ``` -------------------------------- ### Implement Stock Reservation Pattern Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/documents/architecture.md Uses runTransaction to atomically update product stock and transaction line items. ```typescript // services/transactionService.ts export const addLineItemToTransaction = async ( transactionId: string, product: Product, actingUser: User ): Promise => { await runTransaction(db, async (tx) => { const productRef = doc(db, 'products', product.id); const productSnap = await tx.get(productRef); const current = productSnap.data() as Product; // Reserve stock tx.update(productRef, { stockAvailable: current.stockAvailable - 1, stockReserved: current.stockReserved + 1, }); // Append line item const transactionRef = doc(db, 'transactions', transactionId); const newLineItem: LineItem = buildLineItem(product, actingUser); tx.update(transactionRef, { lineItems: arrayUnion(newLineItem), updatedAt: serverTimestamp(), }); // Audit const auditRef = doc(collection(db, 'auditLogs')); tx.set(auditRef, buildAuditEntry('ADD_ITEM', 'TRANSACTION', transactionId, actingUser, { lineItem: newLineItem })); }); }; ``` -------------------------------- ### Implement Permission System Source: https://context7.com/jun-alcantara/automachenicshop-poc/llms.txt Provides role-based access control via constants, utility functions, and React hooks for screen guarding and conditional UI rendering. ```typescript // src/constants/permissions.ts export enum Permission { MANAGE_USERS = 'MANAGE_USERS', MANAGE_INVENTORY = 'MANAGE_INVENTORY', CREATE_TRANSACTIONS = 'CREATE_TRANSACTIONS', APPLY_DISCOUNTS = 'APPLY_DISCOUNTS', VOID_TRANSACTIONS = 'VOID_TRANSACTIONS', CANCEL_TRANSACTIONS = 'CANCEL_TRANSACTIONS', VIEW_REPORTS = 'VIEW_REPORTS', MANAGE_SETTINGS = 'MANAGE_SETTINGS', } // src/utils/permissions.ts import { User } from '../types'; import { Permission } from '../constants/permissions'; export const hasPermission = (user: User | null, permission: Permission): boolean => { if (!user) return false; if (user.isMainAdmin) return true; return user.permissions.includes(permission); }; // src/hooks/useHasPermission.ts import { useSessionStore } from '../stores/sessionStore'; import { hasPermission } from '../utils/permissions'; import { Permission } from '../constants/permissions'; export const useHasPermission = (permission: Permission): boolean => { const user = useSessionStore(s => s.user); return hasPermission(user, permission); }; // src/hooks/usePermissionGuard.ts import { useEffect } from 'react'; import { useNavigation } from '@react-navigation/native'; import { useSessionStore } from '../stores/sessionStore'; import { hasPermission } from '../utils/permissions'; import { Permission } from '../constants/permissions'; export const usePermissionGuard = (permission: Permission): boolean => { const user = useSessionStore(s => s.user); const navigation = useNavigation(); const isAuthorized = hasPermission(user, permission); useEffect(() => { if (!isAuthorized) { navigation.goBack(); // Show toast: "You don't have permission to access this screen." } }, [isAuthorized, navigation]); return isAuthorized; }; // Usage in screen component export const ProductFormScreen = () => { const isAuthorized = usePermissionGuard(Permission.MANAGE_INVENTORY); if (!isAuthorized) return null; // Screen content... }; // Usage for conditional UI const TransactionDetail = () => { const canApplyDiscount = useHasPermission(Permission.APPLY_DISCOUNTS); return ( {canApplyDiscount && ( )} ); }; ``` -------------------------------- ### Stores Module Structure Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/documents/architecture.md Zustand store slices for state management. ```text stores/ ├── sessionStore.ts # Current user, session status, activity timer ├── transactionDraftStore.ts # Transaction being actively built/edited on screen ├── openTransactionsStore.ts # Live IN_PROGRESS transaction list ├── inventoryStore.ts # Products + service items (real-time listener) ├── catalogStore.ts # Categories, suppliers, add-ons (real-time listener) ├── userStore.ts # User accounts (real-time listener) └── settingsStore.ts # App settings (real-time listener) ``` -------------------------------- ### Create Customer and Audit Log in Firestore Source: https://context7.com/jun-alcantara/automachenicshop-poc/llms.txt Creates a new customer record and an audit log entry using a Firestore batch write. Includes timestamps and user information. ```typescript export const createCustomer = async ( customer: { name: string; type: 'NAMED' | 'WALKIN'; phone?: string; email?: string }, actingUser: User ): Promise => { const batch = writeBatch(db); const customerRef = doc(collection(db, 'customers')); batch.set(customerRef, { ...customer, isActive: true, createdAt: serverTimestamp(), createdBy: actingUser.id, }); const auditRef = doc(collection(db, 'auditLogs')); batch.set(auditRef, { timestamp: serverTimestamp(), userId: actingUser.id, userName: actingUser.displayName, actionType: 'CREATE_CUSTOMER', entityType: 'CUSTOMER', entityId: customerRef.id, after: customer, }); await batch.commit(); return customerRef.id; }; ``` -------------------------------- ### Calculate Line Item with VAT and Discounts Source: https://context7.com/jun-alcantara/automachenicshop-poc/llms.txt Calculates the subtotal, VAT amount, discount amount, and total for a single line item. Supports different VAT types and discount options. Ensure correct VAT rate is configured. ```typescript // src/utils/calculateLineItem.ts import { LineItem } from '../types'; export type VatType = 'NO_VAT' | 'VAT_EXCLUSIVE' | 'VAT_INCLUSIVE'; const VAT_RATE = 0.12; export const calculateLineItem = ( unitPrice: number, quantity: number, vatType: VatType, discountType?: 'FIXED' | 'PERCENTAGE', discountValue?: number, addOnTotal: number = 0 ): { subtotal: number; vatAmount: number; discountAmount: number; total: number } => { const subtotal = unitPrice * quantity; // Calculate discount let discountAmount = 0; if (discountType && discountValue) { discountAmount = discountType === 'FIXED' ? discountValue : subtotal * (discountValue / 100); } const afterDiscount = subtotal - discountAmount + addOnTotal; // Calculate VAT based on type let vatAmount = 0; let total = afterDiscount; switch (vatType) { case 'NO_VAT': vatAmount = 0; total = afterDiscount; break; case 'VAT_EXCLUSIVE': vatAmount = afterDiscount * VAT_RATE; total = afterDiscount + vatAmount; break; case 'VAT_INCLUSIVE': vatAmount = afterDiscount * VAT_RATE / (1 + VAT_RATE); total = afterDiscount; // Price already includes VAT break; } return { subtotal, vatAmount, discountAmount, total }; }; ``` ```typescript // Usage const result = calculateLineItem(1000, 2, 'VAT_INCLUSIVE', 'PERCENTAGE', 10); // { subtotal: 2000, vatAmount: 192.86, discountAmount: 200, total: 1800 } ``` -------------------------------- ### Zustand Session Store with Persistence Source: https://context7.com/jun-alcantara/automachenicshop-poc/llms.txt Manages user session state (user, status, activity) using Zustand with persistence via AsyncStorage. Includes login, logout, lock, unlock, and activity refresh functionalities. ```typescript // src/stores/sessionStore.ts import { create } from 'zustand'; import { persist, createJSONStorage } from 'zustand/middleware'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { User } from '../types'; import { getUserById } from '../services/userService'; import { verifyPin } from '../utils/pinHash'; interface SessionStore { user: User | null; status: 'NONE' | 'ACTIVE' | 'LOCKED'; lastActivityAt: number; login: (userId: string) => Promise; logout: () => void; lock: () => void; unlock: (pin: string) => Promise; refreshActivity: () => void; } export const useSessionStore = create()( persist( (set, get) => ({ user: null, status: 'NONE', lastActivityAt: Date.now(), login: async (userId: string) => { const user = await getUserById(userId); set({ user, status: 'ACTIVE', lastActivityAt: Date.now() }); }, logout: () => { set({ user: null, status: 'NONE', lastActivityAt: 0 }); }, lock: () => { set({ status: 'LOCKED' }); }, unlock: async (pin: string) => { const { user } = get(); if (!user) return false; const isValid = await verifyPin(pin, user.salt, user.pinHash); if (isValid) { set({ status: 'ACTIVE', lastActivityAt: Date.now() }); return true; } return false; }, refreshActivity: () => { set({ lastActivityAt: Date.now() }); }, }), { name: 'session-storage', storage: createJSONStorage(() => AsyncStorage), partialize: (state) => ({ user: state.user, status: state.status }), } ) ); ``` -------------------------------- ### Services Module Structure Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/documents/architecture.md Data access layer mapping to Firestore collections. ```text services/ ├── firebase.ts # Firebase app init; exports `db` ├── authService.ts # PIN hashing and verification ├── userService.ts # users collection CRUD + listener ├── productService.ts # products collection CRUD + listener ├── serviceItemService.ts # services collection CRUD + listener ├── transactionService.ts # transactions collection CRUD + listener + atomic ops ├── customerService.ts # customers + vehicles CRUD + listener ├── catalogService.ts # categories, suppliers, addOns CRUD + listeners ├── auditService.ts # auditLogs write-only (append) └── settingsService.ts # app settings read/write ``` -------------------------------- ### Define SessionStore Interface Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/documents/architecture.md Defines the state and actions for managing authenticated user sessions, including login, logout, and lock/unlock functionality. ```typescript interface SessionStore { // State user: User | null; status: 'NONE' | 'ACTIVE' | 'LOCKED'; lastActivityAt: number; // Actions login: (userId: string) => Promise; // fetches user doc, sets status ACTIVE logout: () => void; // clears user, status → NONE lock: () => void; // status → LOCKED (preserves user ref) unlock: (pin: string) => Promise; // verify PIN, status → ACTIVE if correct refreshActivity: () => void; // resets lastActivityAt to Date.now() } ``` -------------------------------- ### Implement Transaction Draft Store with Zustand Source: https://context7.com/jun-alcantara/automachenicshop-poc/llms.txt Uses Zustand to manage transaction state, including optimistic updates for line items and backend synchronization. ```typescript // src/stores/transactionDraftStore.ts import { create } from 'zustand'; import { Transaction, Product, ServiceItem, AddOn } from '../types'; import * as transactionService from '../services/transactionService'; import { calculateLineItem } from '../utils/calculateLineItem'; interface TransactionDraftStore { draft: Transaction | null; openDraft: (transactionId: string) => Promise; addLineItem: (item: Product | ServiceItem) => Promise; removeLineItem: (lineItemId: string) => Promise; updateLineItemQty: (lineItemId: string, qty: number) => Promise; applyDiscount: ( lineItemId: string, type: 'FIXED' | 'PERCENTAGE', value: number ) => Promise; applyAddOn: (lineItemId: string, addOn: AddOn) => Promise; clearDraft: () => void; } export const useTransactionDraftStore = create((set, get) => ({ draft: null, openDraft: async (transactionId: string) => { const transaction = await transactionService.getTransaction(transactionId); set({ draft: transaction }); }, addLineItem: async (item: Product | ServiceItem) => { const { draft } = get(); if (!draft) return; // Optimistic update const tempLineItem = { lineItemId: `temp-${Date.now()}`, type: 'stockAvailable' in item ? 'PRODUCT' : 'SERVICE', refId: item.id, name: item.name, unitPrice: 'sellingPrice' in item ? item.sellingPrice : item.basePrice, quantity: 1, vatType: 'VAT_INCLUSIVE' as const, addOns: [], subtotal: 0, vatAmount: 0, discountAmount: 0, total: 0, }; set({ draft: { ...draft, lineItems: [...draft.lineItems, tempLineItem] } }); try { // Backend write await transactionService.addLineItemToTransaction( draft.id, item as Product, 1, useSessionStore.getState().user! ); } catch (error) { // Rollback on failure set({ draft }); throw error; } }, applyDiscount: async (lineItemId: string, type: 'FIXED' | 'PERCENTAGE', value: number) => { const { draft } = get(); if (!draft) return; const updatedItems = draft.lineItems.map(item => { if (item.lineItemId !== lineItemId) return item; const calc = calculateLineItem( item.unitPrice, item.quantity, item.vatType, type, value, item.addOns.reduce((sum, a) => sum + a.amount, 0) ); return { ...item, discountType: type, discountValue: value, ...calc, }; }); set({ draft: { ...draft, lineItems: updatedItems } }); await transactionService.updateLineItemDiscount(draft.id, lineItemId, type, value); }, clearDraft: () => set({ draft: null }), })); ``` -------------------------------- ### Build Production App Source: https://github.com/jun-alcantara/automachenicshop-poc/blob/main/README.md EAS Build command to generate a production-ready Android App Bundle (AAB) for distribution on the Google Play Store. ```bash eas build --profile production --platform android ```