types enh
This commit is contained in:
parent
d58530075d
commit
1ff771355e
43 changed files with 319 additions and 266 deletions
|
|
@ -5,11 +5,10 @@ import * as Yup from 'yup'
|
||||||
import { MyTextInput, BottomDropdown, MyText, tw, ImageUploaderNeo, ImageUploaderNeoItem, ImageUploaderNeoPayload, Checkbox, InfoDialog } from 'common-ui'
|
import { MyTextInput, BottomDropdown, MyText, tw, ImageUploaderNeo, ImageUploaderNeoItem, ImageUploaderNeoPayload, Checkbox, InfoDialog } from 'common-ui'
|
||||||
import MaterialIcons from '@expo/vector-icons/MaterialIcons'
|
import MaterialIcons from '@expo/vector-icons/MaterialIcons'
|
||||||
import { trpc } from '../trpc-client'
|
import { trpc } from '../trpc-client'
|
||||||
|
import type { SkuFeatureLike } from '@packages/shared'
|
||||||
|
|
||||||
interface Attribute {
|
// Feature input shape — single source: shared SkuFeatureLike
|
||||||
featureName: string | null
|
type Attribute = SkuFeatureLike
|
||||||
featureValue: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Variant {
|
interface Variant {
|
||||||
id?: number
|
id?: number
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import type { ConstValueType } from '@packages/shared'
|
||||||
|
|
||||||
export const CONST_KEYS = {
|
export const CONST_KEYS = {
|
||||||
minRegularOrderValue: 'minRegularOrderValue',
|
minRegularOrderValue: 'minRegularOrderValue',
|
||||||
freeDeliveryThreshold: 'freeDeliveryThreshold',
|
freeDeliveryThreshold: 'freeDeliveryThreshold',
|
||||||
|
|
@ -52,8 +54,6 @@ export type ConstKey = (typeof CONST_KEYS)[keyof typeof CONST_KEYS];
|
||||||
|
|
||||||
export const CONST_KEYS_ARRAY = Object.values(CONST_KEYS) as ConstKey[];
|
export const CONST_KEYS_ARRAY = Object.values(CONST_KEYS) as ConstKey[];
|
||||||
|
|
||||||
export type ConstValueType = 'string' | 'boolean' | 'number'
|
|
||||||
|
|
||||||
export const CONST_TYPES: Record<ConstKey, ConstValueType> = {
|
export const CONST_TYPES: Record<ConstKey, ConstValueType> = {
|
||||||
minRegularOrderValue: 'number',
|
minRegularOrderValue: 'number',
|
||||||
freeDeliveryThreshold: 'number',
|
freeDeliveryThreshold: 'number',
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,12 @@
|
||||||
// import redisClient from '@/src/lib/redis-client'
|
// import redisClient from '@/src/lib/redis-client'
|
||||||
|
import type { BannerData } from '@packages/shared'
|
||||||
import {
|
import {
|
||||||
getAllBannersForCache,
|
getAllBannersForCache,
|
||||||
getBanners,
|
getBanners,
|
||||||
getBannerById as getBannerByIdFromDb,
|
getBannerById as getBannerByIdFromDb,
|
||||||
type BannerData,
|
|
||||||
} from '@/src/dbService'
|
} from '@/src/dbService'
|
||||||
import { scaffoldAssetUrl } from '@/src/lib/s3-client'
|
import { scaffoldAssetUrl } from '@/src/lib/s3-client'
|
||||||
|
|
||||||
// Banner Type (matches getBanners return)
|
|
||||||
interface Banner {
|
|
||||||
id: number
|
|
||||||
name: string
|
|
||||||
imageUrl: string | null
|
|
||||||
serialNum: number | null
|
|
||||||
skuIds: number[] | null
|
|
||||||
createdAt: Date
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function initializeBannerStore(): Promise<void> {
|
export async function initializeBannerStore(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
console.log('Initializing banner store in Redis...')
|
console.log('Initializing banner store in Redis...')
|
||||||
|
|
@ -59,7 +49,7 @@ export async function initializeBannerStore(): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getBannerById(id: number): Promise<Banner | null> {
|
export async function getBannerById(id: number): Promise<BannerData | null> {
|
||||||
try {
|
try {
|
||||||
// const key = `banner:${id}`
|
// const key = `banner:${id}`
|
||||||
// const data = await redisClient.get(key)
|
// const data = await redisClient.get(key)
|
||||||
|
|
@ -87,7 +77,7 @@ export async function getBannerById(id: number): Promise<Banner | null> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllBanners(): Promise<Banner[]> {
|
export async function getAllBanners(): Promise<BannerData[]> {
|
||||||
try {
|
try {
|
||||||
// Get all keys matching the pattern "banner:*"
|
// Get all keys matching the pattern "banner:*"
|
||||||
// const keys = await redisClient.KEYS('banner:*')
|
// const keys = await redisClient.KEYS('banner:*')
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import {
|
||||||
} from "@/src/dbService";
|
} from "@/src/dbService";
|
||||||
import { scaffoldAssetUrl } from "@/src/lib/s3-client";
|
import { scaffoldAssetUrl } from "@/src/lib/s3-client";
|
||||||
import { ApiError } from "@/src/lib/api-error";
|
import { ApiError } from "@/src/lib/api-error";
|
||||||
|
import type { DeliveryStatus, OrderStatus } from "@packages/shared";
|
||||||
import {
|
import {
|
||||||
sendOrderPlacedNotification,
|
sendOrderPlacedNotification,
|
||||||
sendOrderCancelledNotification,
|
sendOrderCancelledNotification,
|
||||||
|
|
@ -334,9 +335,6 @@ export const orderRouter = router({
|
||||||
const status = order.orderStatus[0];
|
const status = order.orderStatus[0];
|
||||||
const refund = order.refunds[0];
|
const refund = order.refunds[0];
|
||||||
|
|
||||||
type DeliveryStatus = "cancelled" | "success" | "pending" | "packaged";
|
|
||||||
type OrderStatus = "cancelled" | "success";
|
|
||||||
|
|
||||||
let deliveryStatus: DeliveryStatus;
|
let deliveryStatus: DeliveryStatus;
|
||||||
let orderStatus: OrderStatus;
|
let orderStatus: OrderStatus;
|
||||||
|
|
||||||
|
|
@ -474,9 +472,6 @@ export const orderRouter = router({
|
||||||
const status = order.orderStatus[0];
|
const status = order.orderStatus[0];
|
||||||
const refund = order.refunds[0];
|
const refund = order.refunds[0];
|
||||||
|
|
||||||
type DeliveryStatus = "cancelled" | "success" | "pending" | "packaged";
|
|
||||||
type OrderStatus = "cancelled" | "success";
|
|
||||||
|
|
||||||
let deliveryStatus: DeliveryStatus;
|
let deliveryStatus: DeliveryStatus;
|
||||||
let orderStatusResult: OrderStatus;
|
let orderStatusResult: OrderStatus;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useRef, useEffect, useMemo } from 'react';
|
import { useRef, useEffect, useMemo } from 'react';
|
||||||
import { useFrame } from '@react-three/fiber';
|
import { useFrame } from '@react-three/fiber';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
import type { CelebrationProps } from '@packages/shared';
|
||||||
|
|
||||||
interface CharacterProps {
|
interface CharacterProps {
|
||||||
color: string;
|
color: string;
|
||||||
|
|
@ -758,11 +759,7 @@ function Character({ color, isCelebrating, delay = 0, type, hairColor = '#2D1B0E
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CharactersProps {
|
export default function Characters({ isCelebrating }: CelebrationProps) {
|
||||||
isCelebrating: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Characters({ isCelebrating }: CharactersProps) {
|
|
||||||
return (
|
return (
|
||||||
<group>
|
<group>
|
||||||
{/* Character 1 - Cheerer - Left side */}
|
{/* Character 1 - Cheerer - Left side */}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { useRef, useEffect } from 'react';
|
||||||
import { Canvas, useFrame, useThree } from '@react-three/fiber';
|
import { Canvas, useFrame, useThree } from '@react-three/fiber';
|
||||||
import { OrbitControls, Stars, Float } from '@react-three/drei';
|
import { OrbitControls, Stars, Float } from '@react-three/drei';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
import type { CelebrationProps } from '@packages/shared';
|
||||||
import Characters from './Characters';
|
import Characters from './Characters';
|
||||||
|
|
||||||
// Warm gradient background
|
// Warm gradient background
|
||||||
|
|
@ -136,11 +137,7 @@ function CameraController({ isCelebrating }: { isCelebrating: boolean }) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SceneProps {
|
export default function Scene({ isCelebrating }: CelebrationProps) {
|
||||||
isCelebrating: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Scene({ isCelebrating }: SceneProps) {
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-screen">
|
<div className="w-full h-screen">
|
||||||
<Canvas
|
<Canvas
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import AddToCartDialog from "@/src/components/AddToCartDialog";
|
||||||
import MyFlatList from "common-ui/src/components/flat-list";
|
import MyFlatList from "common-ui/src/components/flat-list";
|
||||||
|
|
||||||
import { trpc } from "@/src/trpc-client";
|
import { trpc } from "@/src/trpc-client";
|
||||||
|
import type { ListItemProps } from '@packages/shared';
|
||||||
import { useAllProducts, useStores, useSlots, useGetEssentialConsts } from "@/src/hooks/prominent-api-hooks";
|
import { useAllProducts, useStores, useSlots, useGetEssentialConsts } from "@/src/hooks/prominent-api-hooks";
|
||||||
import { useProductSlotIdentifier } from "@/hooks/useProductSlotIdentifier";
|
import { useProductSlotIdentifier } from "@/hooks/useProductSlotIdentifier";
|
||||||
import { useCentralSlotStore } from "@/src/store/centralSlotStore";
|
import { useCentralSlotStore } from "@/src/store/centralSlotStore";
|
||||||
|
|
@ -68,11 +69,7 @@ const TAG_COLORS = [
|
||||||
|
|
||||||
const getTagColor = (id: number) => TAG_COLORS[id % TAG_COLORS.length];
|
const getTagColor = (id: number) => TAG_COLORS[id % TAG_COLORS.length];
|
||||||
|
|
||||||
interface RenderStoreProps {
|
const RenderStore = memo(({ item }: ListItemProps) => {
|
||||||
item: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
const RenderStore = memo(({ item }: RenderStoreProps) => {
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { setNavigatedFromHome, setSelectedStoreId } = useNavigationStore();
|
const { setNavigatedFromHome, setSelectedStoreId } = useNavigationStore();
|
||||||
|
|
||||||
|
|
@ -343,11 +340,7 @@ const ExploreProductItem = memo(({ item, onPress }: ProductItemProps) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
interface SlotItemProps {
|
const SlotItem = memo(({ item }: ListItemProps) => <SlotCard slot={item} />);
|
||||||
item: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SlotItem = memo(({ item }: SlotItemProps) => <SlotCard slot={item} />);
|
|
||||||
|
|
||||||
interface ProductItemProps {
|
interface ProductItemProps {
|
||||||
item: any;
|
item: any;
|
||||||
|
|
|
||||||
|
|
@ -17,23 +17,12 @@ import ProductCard from "@/components/ProductCard";
|
||||||
import FloatingCartBar from "@/components/floating-cart-bar";
|
import FloatingCartBar from "@/components/floating-cart-bar";
|
||||||
import { useStoreHeaderStore } from "@/src/store/storeHeaderStore";
|
import { useStoreHeaderStore } from "@/src/store/storeHeaderStore";
|
||||||
import { useAllProducts, useStoreWithProducts } from "@/src/hooks/prominent-api-hooks";
|
import { useAllProducts, useStoreWithProducts } from "@/src/hooks/prominent-api-hooks";
|
||||||
|
import type { StoreTag, TagChipProps } from "@packages/shared";
|
||||||
|
|
||||||
const { width: screenWidth } = Dimensions.get("window");
|
const { width: screenWidth } = Dimensions.get("window");
|
||||||
const itemWidth = (screenWidth - 48) / 2;
|
const itemWidth = (screenWidth - 48) / 2;
|
||||||
|
|
||||||
interface Tag {
|
const Chip: React.FC<TagChipProps> = ({ tag, isSelected, onPress }) => {
|
||||||
id: number;
|
|
||||||
tagName: string;
|
|
||||||
productIds?: number[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ChipProps {
|
|
||||||
tag: Tag;
|
|
||||||
isSelected: boolean;
|
|
||||||
onPress: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Chip: React.FC<ChipProps> = ({ tag, isSelected, onPress }) => {
|
|
||||||
const productCount = tag.productIds?.length || 0;
|
const productCount = tag.productIds?.length || 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,9 @@ import { useCentralProductStore } from '@/src/store/centralProductStore';
|
||||||
import { useCentralSlotStore } from '@/src/store/centralSlotStore';
|
import { useCentralSlotStore } from '@/src/store/centralSlotStore';
|
||||||
import { useGetCart, useUpdateCartItem, useRemoveFromCart } from '@/hooks/cart-query-hooks';
|
import { useGetCart, useUpdateCartItem, useRemoveFromCart } from '@/hooks/cart-query-hooks';
|
||||||
import { useGetEssentialConsts } from '@/src/hooks/prominent-api-hooks';
|
import { useGetEssentialConsts } from '@/src/hooks/prominent-api-hooks';
|
||||||
|
import type { FlashDeliveryProps } from '@packages/shared';
|
||||||
|
|
||||||
interface CartPageProps {
|
export default function CartPage({ isFlashDelivery = false }: FlashDeliveryProps) {
|
||||||
isFlashDelivery?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
|
|
||||||
// Hide tabs when cart page is active
|
// Hide tabs when cart page is active
|
||||||
useHideTabNav();
|
useHideTabNav();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,9 @@ import { useGetEssentialConsts } from '@/src/hooks/prominent-api-hooks';
|
||||||
import PaymentAndOrderComponent from '@/components/PaymentAndOrderComponent';
|
import PaymentAndOrderComponent from '@/components/PaymentAndOrderComponent';
|
||||||
import CheckoutAddressSelector from '@/components/CheckoutAddressSelector';
|
import CheckoutAddressSelector from '@/components/CheckoutAddressSelector';
|
||||||
import { useAddressStore } from '@/src/store/addressStore';
|
import { useAddressStore } from '@/src/store/addressStore';
|
||||||
|
import type { FlashDeliveryProps } from '@packages/shared';
|
||||||
|
|
||||||
interface CheckoutPageProps {
|
const CheckoutPage: React.FC<FlashDeliveryProps> = ({ isFlashDelivery = false }) => {
|
||||||
isFlashDelivery?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CheckoutPage: React.FC<CheckoutPageProps> = ({ isFlashDelivery = false }) => {
|
|
||||||
const params = useLocalSearchParams();
|
const params = useLocalSearchParams();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { useCentralProductStore } from '@/src/store/centralProductStore';
|
||||||
import { useCentralSlotStore } from '@/src/store/centralSlotStore';
|
import { useCentralSlotStore } from '@/src/store/centralSlotStore';
|
||||||
import { Alert } from 'react-native';
|
import { Alert } from 'react-native';
|
||||||
import { useQuery, useMutation, useQueryClient, UseQueryResult, UseMutationResult } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient, UseQueryResult, UseMutationResult } from '@tanstack/react-query';
|
||||||
|
import type { CartData as SharedCartData } from '@packages/shared';
|
||||||
import { StorageServiceCasual } from 'common-ui/src/services/StorageServiceCasual';
|
import { StorageServiceCasual } from 'common-ui/src/services/StorageServiceCasual';
|
||||||
|
|
||||||
// Cart type definition
|
// Cart type definition
|
||||||
|
|
@ -42,11 +43,7 @@ export interface CartItem {
|
||||||
slotId: number;
|
slotId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CartData {
|
type CartData = SharedCartData<CartItem>
|
||||||
items: CartItem[];
|
|
||||||
totalItems: number;
|
|
||||||
totalAmount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UseGetCartOptions {
|
interface UseGetCartOptions {
|
||||||
refetchOnWindowFocus?: boolean;
|
refetchOnWindowFocus?: boolean;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
|
import type { FlashDeliveryProps } from '@packages/shared'
|
||||||
import { useNavigate } from '@tanstack/react-router'
|
import { useNavigate } from '@tanstack/react-router'
|
||||||
import { useGetCart, useUpdateCartItem, useRemoveFromCart } from '../hooks/cart-query-hooks'
|
import { useGetCart, useUpdateCartItem, useRemoveFromCart } from '../hooks/cart-query-hooks'
|
||||||
import { useAllProducts } from '../hooks/prominent-api-hooks'
|
import { useAllProducts } from '../hooks/prominent-api-hooks'
|
||||||
|
|
@ -7,10 +8,6 @@ import { MiniQuantifier } from 'web-components'
|
||||||
import { ShoppingCart, ChevronRight, Clock, X, Package } from 'lucide-react'
|
import { ShoppingCart, ChevronRight, Clock, X, Package } from 'lucide-react'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
interface FloatingCartBarProps {
|
|
||||||
isFlashDelivery?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
// Smart time window formatting function
|
// Smart time window formatting function
|
||||||
const formatTimeRange = (deliveryTime: string | Date) => {
|
const formatTimeRange = (deliveryTime: string | Date) => {
|
||||||
const time = dayjs(deliveryTime)
|
const time = dayjs(deliveryTime)
|
||||||
|
|
@ -28,7 +25,7 @@ const formatTimeRange = (deliveryTime: string | Date) => {
|
||||||
return `${time.format('ddd, DD MMM ')}${timeRange}`
|
return `${time.format('ddd, DD MMM ')}${timeRange}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FloatingCartBar({ isFlashDelivery = false }: FloatingCartBarProps) {
|
export function FloatingCartBar({ isFlashDelivery = false }: FlashDeliveryProps) {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [isExpanded, setIsExpanded] = useState(false)
|
const [isExpanded, setIsExpanded] = useState(false)
|
||||||
const [quantities, setQuantities] = useState<Record<number, number>>({})
|
const [quantities, setQuantities] = useState<Record<number, number>>({})
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||||
|
import type { CartData as SharedCartData } from '@packages/shared'
|
||||||
|
|
||||||
type CartType = 'regular' | 'flash'
|
type CartType = 'regular' | 'flash'
|
||||||
|
|
||||||
|
|
@ -12,11 +13,7 @@ interface CartItem {
|
||||||
deliveryDate?: string | null
|
deliveryDate?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CartData {
|
type CartData = SharedCartData<CartItem>
|
||||||
items: CartItem[]
|
|
||||||
totalItems: number
|
|
||||||
totalAmount: number
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCartKey(cartType: CartType): string {
|
function getCartKey(cartType: CartType): string {
|
||||||
return `local-cart-${cartType}`
|
return `local-cart-${cartType}`
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router'
|
import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router'
|
||||||
import { useStoreWithProducts, useAllProducts } from '../hooks/prominent-api-hooks'
|
import { useStoreWithProducts, useAllProducts } from '../hooks/prominent-api-hooks'
|
||||||
import { useState, useMemo } from 'react'
|
import { useState, useMemo } from 'react'
|
||||||
|
import type { StoreTag, TagChipProps } from '@packages/shared'
|
||||||
import { AppLayout } from '../components/AppLayout'
|
import { AppLayout } from '../components/AppLayout'
|
||||||
import AddToCartDialog from '../components/AddToCartDialog'
|
import AddToCartDialog from '../components/AddToCartDialog'
|
||||||
import { ProductCard } from '../components/ProductCard'
|
import { ProductCard } from '../components/ProductCard'
|
||||||
|
|
@ -11,12 +12,6 @@ export const Route = createFileRoute('/stores/$storeId')({
|
||||||
component: StoreDetailPage,
|
component: StoreDetailPage,
|
||||||
})
|
})
|
||||||
|
|
||||||
interface Tag {
|
|
||||||
id: number
|
|
||||||
tagName: string
|
|
||||||
productIds?: number[]
|
|
||||||
}
|
|
||||||
|
|
||||||
function StoreDetailPage() {
|
function StoreDetailPage() {
|
||||||
const { storeId } = useParams({ from: '/stores/$storeId' })
|
const { storeId } = useParams({ from: '/stores/$storeId' })
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
@ -47,7 +42,7 @@ function StoreDetailPage() {
|
||||||
// Filter products based on selected tag
|
// Filter products based on selected tag
|
||||||
const filteredProducts = selectedTagId
|
const filteredProducts = selectedTagId
|
||||||
? storeProducts.filter((product: any) => {
|
? storeProducts.filter((product: any) => {
|
||||||
const selectedTag = storeData?.tags.find((t: Tag) => t.id === selectedTagId)
|
const selectedTag = storeData?.tags.find((t: StoreTag) => t.id === selectedTagId)
|
||||||
return selectedTag?.productIds?.includes(product.id) ?? false
|
return selectedTag?.productIds?.includes(product.id) ?? false
|
||||||
})
|
})
|
||||||
: storeProducts
|
: storeProducts
|
||||||
|
|
@ -131,7 +126,7 @@ function StoreDetailPage() {
|
||||||
isSelected={selectedTagId === null}
|
isSelected={selectedTagId === null}
|
||||||
onPress={() => setSelectedTagId(null)}
|
onPress={() => setSelectedTagId(null)}
|
||||||
/>
|
/>
|
||||||
{storeData.tags.map((tag: Tag) => (
|
{storeData.tags.map((tag: StoreTag) => (
|
||||||
<TagChip
|
<TagChip
|
||||||
key={tag.id}
|
key={tag.id}
|
||||||
tag={tag}
|
tag={tag}
|
||||||
|
|
@ -148,7 +143,7 @@ function StoreDetailPage() {
|
||||||
<Grid3X3 className="mr-2 h-5 w-5 text-gray-700" />
|
<Grid3X3 className="mr-2 h-5 w-5 text-gray-700" />
|
||||||
<p className="text-lg font-bold text-gray-900">
|
<p className="text-lg font-bold text-gray-900">
|
||||||
{selectedTagId
|
{selectedTagId
|
||||||
? `${storeData?.tags.find((t: Tag) => t.id === selectedTagId)?.tagName} items`
|
? `${storeData?.tags.find((t: StoreTag) => t.id === selectedTagId)?.tagName} items`
|
||||||
: `${filteredProducts.length} products`}
|
: `${filteredProducts.length} products`}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -197,12 +192,6 @@ function StoreDetailPage() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TagChipProps {
|
|
||||||
tag: Tag
|
|
||||||
isSelected: boolean
|
|
||||||
onPress: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
function TagChip({ tag, isSelected, onPress }: TagChipProps) {
|
function TagChip({ tag, isSelected, onPress }: TagChipProps) {
|
||||||
const productCount = tag.productIds?.length || 0
|
const productCount = tag.productIds?.length || 0
|
||||||
|
|
||||||
|
|
|
||||||
173
change-log.txt
173
change-log.txt
|
|
@ -1039,3 +1039,176 @@ FINAL POSITION on "unify the rest" (06-56): no cluster carries a unify verdict.
|
||||||
- apps/fallback-ui/src/stores/userStore.ts: delete local `interface User/Role/Permission` (each exactly {id:number;name:string}); add import type { IdName } from '@packages/shared' + type User = IdName / type Role = IdName / type Permission = IdName. Names kept (UserWithRole extends User + store shape unchanged); only the hook is exported so no external imports affected.
|
- apps/fallback-ui/src/stores/userStore.ts: delete local `interface User/Role/Permission` (each exactly {id:number;name:string}); add import type { IdName } from '@packages/shared' + type User = IdName / type Role = IdName / type Permission = IdName. Names kept (UserWithRole extends User + store shape unchanged); only the hook is exported so no external imports affected.
|
||||||
|
|
||||||
[2026-09-05 11:47:00] COMPLETED Cluster-06 IdName adoption. Verification: fallback-ui tsc 107 = baseline, zero errors in userStore.ts. Docs: cluster-06 file updated.
|
[2026-09-05 11:47:00] COMPLETED Cluster-06 IdName adoption. Verification: fallback-ui tsc 107 = baseline, zero errors in userStore.ts. Docs: cluster-06 file updated.
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
2026-09-05 12:20:00 — TYPE-CLUSTER UNIFICATION (all remaining duplicated types → @packages/shared; db_helper_postgres excluded per user)
|
||||||
|
Scope decisions: schema-bound sqlite mirrors stay local (no remaining dup once pg excluded); backend-derived BaseProduct/AvailabilityEntry/MergedProduct (c23-25) stay local; ConstKey (c22) stays local; c44/49 Tag gets qualified shared name `StoreTag`.
|
||||||
|
|
||||||
|
[NEW FILE] packages/shared/types/ui-common.types.ts — single source for common-ui ↔ web-components duplicated component prop types (clusters 11,12,13,14,47,51,52,53,54,56):
|
||||||
|
+ import type { ReactNode } from 'react'
|
||||||
|
+ export interface DropdownOption { label: string; value: string | number }
|
||||||
|
+ export interface DialogProps { open: boolean; onClose: () => void; children: ReactNode; enableDismiss?: boolean }
|
||||||
|
+ export interface ConfirmationDialogProps { open: boolean; positiveAction: (comment?: string) => void; commentNeeded?: boolean; negativeAction?: () => void; title?: string; message?: string; confirmText?: string; cancelText?: string; isLoading?: boolean }
|
||||||
|
+ export interface LoadingDialogProps { open: boolean; message?: string }
|
||||||
|
+ export interface QuantifierProps { value: number; setValue: (value: number) => void; step?: number; unit?: string | { shortNotation: string }; min?: number; max?: number }
|
||||||
|
+ export interface ImageUploaderProps { images: { uri?: string }[]; onAddImage: () => void; onRemoveImage: (uri: string) => void; existingImageUrls?: string[]; onRemoveExistingImage?: (url: string) => void; allowMultiple?: boolean }
|
||||||
|
+ export interface ImageUploaderNeoItem { imgUrl: string; mimeType: string | null }
|
||||||
|
+ export interface ImageUploaderNeoPayload { url: string; mimeType: string | null }
|
||||||
|
+ export interface ImageUploaderNeoProps { images: ImageUploaderNeoItem[]; onImageAdd: (images: ImageUploaderNeoPayload[]) => void; onImageRemove: (image: ImageUploaderNeoPayload) => void; allowMultiple?: boolean }
|
||||||
|
+ export interface TextButtonProps { text: string }
|
||||||
|
|
||||||
|
[EDIT] packages/shared/types/app-common.types.ts:
|
||||||
|
+ export interface FlashDeliveryProps { isFlashDelivery?: boolean } (c8: web-ui FloatingCartBarProps, user-ui CheckoutPageProps/CartPageProps)
|
||||||
|
+ export interface ListItemProps { item: any } (c9: user-ui RenderStoreProps/SlotItemProps)
|
||||||
|
- export interface ComplaintItemProps { item: any }
|
||||||
|
+ export type ComplaintItemProps = ListItemProps (canonical shape kept under old name)
|
||||||
|
+ export interface CelebrationProps { isCelebrating: boolean } (c48: fallback-ui CharactersProps/SceneProps)
|
||||||
|
|
||||||
|
[EDIT] packages/shared/types/order.types.ts:
|
||||||
|
+ export type OrderStatus = 'cancelled' | 'success' (c20: backend order.ts 2 function-scoped copies)
|
||||||
|
+ export type DeliveryStatus = 'cancelled' | 'success' | 'pending' | 'packaged' (c21: same file)
|
||||||
|
+ export interface CartData<T = unknown> { items: T[]; totalItems: number; totalAmount: number } (c50: element CartItem differs per app → generic)
|
||||||
|
|
||||||
|
[EDIT] packages/shared/types/const.types.ts:
|
||||||
|
+ export type ConstValueType = 'string' | 'boolean' | 'number' (c19: backend + sqlite const-keys.ts)
|
||||||
|
|
||||||
|
[EDIT] packages/shared/types/admin.ts:
|
||||||
|
+ export type { ConstValueType } from './const.types' (added to existing const re-export line — barrel surface unchanged)
|
||||||
|
|
||||||
|
[EDIT] packages/shared/types/cache.types.ts (c17):
|
||||||
|
- stale note "BannerData ... intentionally NOT included" replaced: pg copy remains local by exclusion, sqlite + backend now share.
|
||||||
|
+ export interface BannerData { id: number; name: string; imageUrl: string | null; serialNum: number | null; skuIds: number[] | null; createdAt: Date }
|
||||||
|
|
||||||
|
[EDIT] packages/shared/types/store.types.ts (c44/c49):
|
||||||
|
+ export interface StoreTag { id: number; tagName: string; productIds?: number[] } (qualified name — avoids clash with richer backend Tag)
|
||||||
|
+ export interface TagChipProps { tag: StoreTag; isSelected: boolean; onPress: () => void }
|
||||||
|
|
||||||
|
[EDIT] packages/shared/types/admin.ts (c7):
|
||||||
|
+ export interface SkuFeatureLike { featureName?: string | null; featureValue: string } (was sqlite CreateSkuFeatureInput + SkuFeatureLike)
|
||||||
|
|
||||||
|
[EDIT] packages/shared/types/index.ts: + export type * from './ui-common.types'; stale comments corrected (DropdownOption/CartData now real; CartIconProps comment fixed).
|
||||||
|
|
||||||
|
2026-09-05 12:26:00 — Adopt shared ui-common types in packages/ui (common-ui) + packages/web-components (clusters 11,12,13,14,47,51,52,53,54,56). All replacements are `import type` (runtime-erased). Local prop interfaces deleted; public re-exports preserved.
|
||||||
|
|
||||||
|
packages/ui/src/components/dialog.tsx:
|
||||||
|
- lines 7-12 interface DialogProps {...} → import type { ConfirmationDialogProps, DialogProps } from '@packages/shared'
|
||||||
|
- lines 194-204 interface ConfirmationDialogProps {...} (deleted; shared import above)
|
||||||
|
- line 1: ReactNode named import dropped (only DialogProps used it): import React, { useState } from 'react'
|
||||||
|
|
||||||
|
packages/ui/src/components/loading-dialog.tsx:
|
||||||
|
- lines 3-5 interface LoadingDialogProps → import type { LoadingDialogProps } from '@packages/shared'
|
||||||
|
|
||||||
|
packages/ui/src/components/quantifier.tsx:
|
||||||
|
- lines 7-13 interface QuantifierProps → import type { QuantifierProps } from '@packages/shared' (Quantifier + MiniQuantifier both use it)
|
||||||
|
|
||||||
|
packages/ui/src/components/ImageUploader.tsx:
|
||||||
|
- lines 9-16 interface ImageUploaderProps (+ trailing comments) → import type { ImageUploaderProps } from '@packages/shared'
|
||||||
|
|
||||||
|
packages/ui/src/components/ImageUploaderNeo.tsx:
|
||||||
|
- lines 11-27: local ImageUploaderNeoItem / ImageUploaderNeoPayload / ImageUploaderNeoProps deleted
|
||||||
|
+ import type { ImageUploaderNeoItem, ImageUploaderNeoPayload, ImageUploaderNeoProps } from '@packages/shared'
|
||||||
|
+ export type { ImageUploaderNeoItem, ImageUploaderNeoPayload } from '@packages/shared' (keeps packages/ui/index.ts:22,91-92 re-export surface intact)
|
||||||
|
|
||||||
|
packages/ui/src/components/dropdown.tsx:
|
||||||
|
- lines 6-9 export interface DropdownOption → import type { DropdownOption } from '@packages/shared' + export type { DropdownOption } from '@packages/shared' (public surface kept)
|
||||||
|
NOTE: bottom-dropdown.tsx (+disabled) and multi-select.tsx (value: string) are DIFFERENT shapes — intentionally untouched.
|
||||||
|
|
||||||
|
packages/ui/src/components/button.tsx:
|
||||||
|
- lines 74-76 interface MyTextButtonProps extends Omit<Props, "children"> { text: string }
|
||||||
|
+ import type { TextButtonProps } from '@packages/shared' (top)
|
||||||
|
+ interface MyTextButtonProps extends Omit<Props, "children">, TextButtonProps {}
|
||||||
|
|
||||||
|
packages/web-components/tsconfig.json:
|
||||||
|
+ paths: "@packages/shared": ["../shared"], "@packages/shared/*": ["../shared/*"] (type-only imports resolve; no runtime dep)
|
||||||
|
|
||||||
|
packages/web-components/src/components/dialog.tsx:
|
||||||
|
- lines 6-12 interface BottomDialogProps {...} → import type { ConfirmationDialogProps, DialogProps } from '@packages/shared'; BottomDialog annotated : DialogProps
|
||||||
|
- lines 55-65 interface ConfirmationDialogProps {...} (deleted)
|
||||||
|
|
||||||
|
packages/web-components/src/components/loading-dialog.tsx:
|
||||||
|
- lines 3-5 interface LoadingDialogProps → import type { LoadingDialogProps } from '@packages/shared'
|
||||||
|
|
||||||
|
packages/web-components/src/components/quantifier.tsx:
|
||||||
|
- lines 6-12 interface QuantifierProps → import type { QuantifierProps } from '@packages/shared' (Quantifier + MiniQuantifier)
|
||||||
|
|
||||||
|
packages/web-components/src/components/image-uploader.tsx:
|
||||||
|
- lines 5-12 interface ImageUploaderProps → import type { ImageUploaderProps } from '@packages/shared'
|
||||||
|
|
||||||
|
packages/web-components/src/components/image-uploader-neo.tsx:
|
||||||
|
- lines 6-19: local ImageUploaderNeoItem / ImageUploaderNeoPayload / ImageUploaderNeoProps deleted
|
||||||
|
+ import type { ImageUploaderNeoItem, ImageUploaderNeoPayload, ImageUploaderNeoProps } from '@packages/shared'
|
||||||
|
+ export type { ImageUploaderNeoItem, ImageUploaderNeoPayload } from '@packages/shared' (keeps src/index.ts:19 re-export intact)
|
||||||
|
|
||||||
|
packages/web-components/src/components/dropdown.tsx:
|
||||||
|
- lines 5-8 export interface DropdownOption → import type { DropdownOption } from '@packages/shared' + export type { DropdownOption } from '@packages/shared' (keeps src/index.ts:24 intact)
|
||||||
|
|
||||||
|
packages/web-components/src/components/my-button.tsx:
|
||||||
|
- lines 46-48 interface pButtonProps extends Omit<MyButtonProps, 'children'> { text: string }
|
||||||
|
+ import type { TextButtonProps } from '@packages/shared' (top)
|
||||||
|
+ interface pButtonProps extends Omit<MyButtonProps, 'children'>, TextButtonProps {}
|
||||||
|
|
||||||
|
2026-09-05 12:34:00 — Adopt shared types across apps + db_helper_sqlite (clusters 3,7,8,9,17,19,20,21,44,48,49,50). db_helper_postgres untouched throughout.
|
||||||
|
|
||||||
|
apps/user-ui/components/checkout-page.tsx:
|
||||||
|
- lines 20-22 interface CheckoutPageProps { isFlashDelivery?: boolean }
|
||||||
|
+ import type { FlashDeliveryProps } from '@packages/shared' (top); FC generic uses FlashDeliveryProps
|
||||||
|
|
||||||
|
apps/user-ui/components/cart-page.tsx:
|
||||||
|
- lines 32-34 interface CartPageProps { isFlashDelivery?: boolean } → same shared FlashDeliveryProps import
|
||||||
|
|
||||||
|
apps/user-ui/app/(drawer)/(tabs)/home/index.tsx:
|
||||||
|
- line 71-73 interface RenderStoreProps { item: any } → import type { ListItemProps } from '@packages/shared'; RenderStore uses ListItemProps
|
||||||
|
- line 346-348 interface SlotItemProps { item: any } → SlotItem uses ListItemProps
|
||||||
|
|
||||||
|
apps/user-ui/app/(drawer)/(tabs)/stores/store-detail/[id].tsx:
|
||||||
|
- lines 24-28 interface Tag { id; tagName; productIds? } → import type { StoreTag, TagChipProps } from '@packages/shared'; local `Tag` refs replaced with StoreTag
|
||||||
|
- lines 30-34 interface ChipProps { tag; isSelected; onPress } → Chip uses shared TagChipProps
|
||||||
|
|
||||||
|
apps/user-ui/hooks/cart-query-hooks.tsx:
|
||||||
|
- lines 45-49 interface CartData { items: CartItem[]; totalItems; totalAmount }
|
||||||
|
+ import type { CartData as SharedCartData } from '@packages/shared'; type CartData = SharedCartData<CartItem> (element differs per app — generic instantiation)
|
||||||
|
|
||||||
|
apps/web-ui/src/components/FloatingCartBar.tsx:
|
||||||
|
- lines 10-12 interface FloatingCartBarProps { isFlashDelivery?: boolean } → import type { FlashDeliveryProps } from '@packages/shared'
|
||||||
|
|
||||||
|
apps/web-ui/src/routes/stores.$storeId.tsx:
|
||||||
|
- lines 14-18 interface Tag → shared StoreTag (local refs at lines 50,134 updated)
|
||||||
|
- lines 200-204 interface TagChipProps → shared TagChipProps (tag: StoreTag)
|
||||||
|
|
||||||
|
apps/web-ui/src/hooks/cart-query-hooks.ts:
|
||||||
|
- lines 15-19 interface CartData → type CartData = SharedCartData<CartItem> (same generic pattern as user-ui)
|
||||||
|
|
||||||
|
apps/backend/src/trpc/apis/user-apis/apis/order.ts:
|
||||||
|
- lines 337-338 + 477-478: function-scoped `type DeliveryStatus` / `type OrderStatus` duplicates deleted (2 pairs)
|
||||||
|
+ import type { DeliveryStatus, OrderStatus } from '@packages/shared'
|
||||||
|
|
||||||
|
apps/backend/src/lib/const-keys.ts (c19):
|
||||||
|
- line 55 export type ConstValueType = 'string' | 'boolean' | 'number' → import type { ConstValueType } from '@packages/shared' (no external importers — verified grep)
|
||||||
|
|
||||||
|
apps/backend/src/stores/banner-store.ts (c17):
|
||||||
|
- lines 11-18 interface Banner {...} → import type { BannerData } from '@packages/shared'; Promise<Banner|null>/Promise<Banner[]> signatures now use BannerData
|
||||||
|
- line 6: `type BannerData` removed from '@/src/dbService' import list (now from shared; shape identical)
|
||||||
|
|
||||||
|
packages/db_helper_sqlite/src/lib/const-keys.ts (c19):
|
||||||
|
- line 57 export type ConstValueType → import type { ConstValueType } from '@packages/shared'
|
||||||
|
|
||||||
|
packages/db_helper_sqlite/src/user-apis/slots.ts + src/admin-apis/vendor-snippets.ts (c3, schema-bound → single-sourced INSIDE sqlite, not shared):
|
||||||
|
- vendor-snippets.ts line 15 type DeliverySlotRow = InferSelectModel<typeof deliverySlotInfo>
|
||||||
|
+ import type { SlotRow } from '../user-apis/slots' ; type DeliverySlotRow = SlotRow
|
||||||
|
- slots.ts line 7: type SlotRow = ... → export type SlotRow = ... (single source of the alias)
|
||||||
|
|
||||||
|
packages/db_helper_sqlite/src/lib/sku-features.ts + src/admin-apis/product.ts (c7):
|
||||||
|
- sku-features.ts lines 1-4 export interface SkuFeatureLike → import type { SkuFeatureLike } from '@packages/shared' + export type { SkuFeatureLike } from '@packages/shared'
|
||||||
|
- product.ts lines 69-72 interface CreateSkuFeatureInput → import adds SkuFeatureLike; line 90 features: CreateSkuFeatureInput[] → features: SkuFeatureLike[]
|
||||||
|
|
||||||
|
packages/db_helper_sqlite/src/stores/store-helpers.ts (c17):
|
||||||
|
- lines 33-40 export interface BannerData → added to existing shared re-export line: export type { ProductTagData, TagProductMapping, UserNegativityData, BannerData } from '@packages/shared'; BannerData added to type import (used by getAllBannersForCache)
|
||||||
|
|
||||||
|
apps/admin-ui/src/components/ProductForm.tsx (c7):
|
||||||
|
- lines 9-12 interface Attribute { featureName: string | null; featureValue: string } → import type { SkuFeatureLike } from '@packages/shared'; type Attribute = SkuFeatureLike (name kept for ~10 in-file refs; optional featureName is a safe widening — verified all uses)
|
||||||
|
|
||||||
|
apps/fallback-ui/src/components/3d/Characters.tsx + 3d/Scene.tsx (c48):
|
||||||
|
- local CharactersProps / SceneProps { isCelebrating: boolean } → import type { CelebrationProps } from '@packages/shared'
|
||||||
|
|
||||||
|
[2026-09-05 12:55:00] COMPLETED type-cluster unification pass (clusters 3,7,8,9,11,12,13,14,17,19,20,21,44,47,48,49,50,51,52,53,54,56 → @packages/shared).
|
||||||
|
Verification (tsc --noEmit error counts vs pre-change baselines): backend 11=11, web-ui 150=150, user-ui 106=106, admin-ui 122=122, fallback-ui 107=107, db_helper_sqlite 4=4. Diffs are line-number shifts only (deleted lines) — zero new errors. `apps/web-ui` vite build exit 0. db_helper_postgres: untouched. Remaining same-name locals verified genuinely different shapes (bottom-dropdown/multi-select DropdownOption variants, web-ui Dialog.tsx DialogProps, user-ui floating-cart-bar props) — intentionally left. type-clusters/README.md verdicts updated to ✅ for the resolved clusters with a pass note at top.
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ import type {
|
||||||
AdminSpecialDeal,
|
AdminSpecialDeal,
|
||||||
AdminUnit,
|
AdminUnit,
|
||||||
AdminUpdateSlotProductsResult,
|
AdminUpdateSlotProductsResult,
|
||||||
|
SkuFeatureLike,
|
||||||
Store,
|
Store,
|
||||||
} from '@packages/shared'
|
} from '@packages/shared'
|
||||||
|
|
||||||
|
|
@ -66,11 +67,6 @@ type SkuRow = InferSelectModel<typeof productSkus>
|
||||||
type MarketStatsRow = InferSelectModel<typeof productMarketStats>
|
type MarketStatsRow = InferSelectModel<typeof productMarketStats>
|
||||||
type SkuFeatureRow = InferSelectModel<typeof skuFeatures>
|
type SkuFeatureRow = InferSelectModel<typeof skuFeatures>
|
||||||
|
|
||||||
interface CreateSkuFeatureInput {
|
|
||||||
featureName?: string | null
|
|
||||||
featureValue: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CreateComboItemInput {
|
interface CreateComboItemInput {
|
||||||
skuId: number
|
skuId: number
|
||||||
}
|
}
|
||||||
|
|
@ -87,7 +83,7 @@ interface CreateSkuInput {
|
||||||
isOffer?: boolean
|
isOffer?: boolean
|
||||||
isComboOnly?: boolean
|
isComboOnly?: boolean
|
||||||
isDeleted?: boolean
|
isDeleted?: boolean
|
||||||
features: CreateSkuFeatureInput[]
|
features: SkuFeatureLike[]
|
||||||
comboItems?: CreateComboItemInput[]
|
comboItems?: CreateComboItemInput[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,10 @@ import type {
|
||||||
AdminVendorUpdatePackagingResult,
|
AdminVendorUpdatePackagingResult,
|
||||||
} from '@packages/shared'
|
} from '@packages/shared'
|
||||||
import { coerceDate } from '../lib/date'
|
import { coerceDate } from '../lib/date'
|
||||||
|
import type { SlotRow } from '../user-apis/slots'
|
||||||
|
|
||||||
type VendorSnippetRow = InferSelectModel<typeof vendorSnippets>
|
type VendorSnippetRow = InferSelectModel<typeof vendorSnippets>
|
||||||
type DeliverySlotRow = InferSelectModel<typeof deliverySlotInfo>
|
type DeliverySlotRow = SlotRow
|
||||||
|
|
||||||
const mapVendorSnippet = (snippet: VendorSnippetRow): AdminVendorSnippet => ({
|
const mapVendorSnippet = (snippet: VendorSnippetRow): AdminVendorSnippet => ({
|
||||||
id: snippet.id,
|
id: snippet.id,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import type { ConstValueType } from '@packages/shared'
|
||||||
|
|
||||||
export const CONST_KEYS = {
|
export const CONST_KEYS = {
|
||||||
minRegularOrderValue: 'minRegularOrderValue',
|
minRegularOrderValue: 'minRegularOrderValue',
|
||||||
freeDeliveryThreshold: 'freeDeliveryThreshold',
|
freeDeliveryThreshold: 'freeDeliveryThreshold',
|
||||||
|
|
@ -54,8 +56,6 @@ export type ConstKey = (typeof CONST_KEYS)[keyof typeof CONST_KEYS]
|
||||||
|
|
||||||
export const CONST_KEYS_ARRAY = Object.values(CONST_KEYS) as ConstKey[]
|
export const CONST_KEYS_ARRAY = Object.values(CONST_KEYS) as ConstKey[]
|
||||||
|
|
||||||
export type ConstValueType = 'string' | 'boolean' | 'number'
|
|
||||||
|
|
||||||
export const CONST_TYPES: Record<ConstKey, ConstValueType> = {
|
export const CONST_TYPES: Record<ConstKey, ConstValueType> = {
|
||||||
minRegularOrderValue: 'number',
|
minRegularOrderValue: 'number',
|
||||||
freeDeliveryThreshold: 'number',
|
freeDeliveryThreshold: 'number',
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
export interface SkuFeatureLike {
|
import type { SkuFeatureLike } from '@packages/shared'
|
||||||
featureName?: string | null
|
|
||||||
featureValue: string
|
export type { SkuFeatureLike } from '@packages/shared'
|
||||||
}
|
|
||||||
|
|
||||||
export const cleanFeatureValue = (value: string): string =>
|
export const cleanFeatureValue = (value: string): string =>
|
||||||
value.replace(/\.0(?=\D|$)/g, '')
|
value.replace(/\.0(?=\D|$)/g, '')
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,11 @@ import {
|
||||||
} from '../db/schema'
|
} from '../db/schema'
|
||||||
import { eq, and, gt, sql, isNotNull, asc } from 'drizzle-orm'
|
import { eq, and, gt, sql, isNotNull, asc } from 'drizzle-orm'
|
||||||
import { composeSkuName, composeUnitNotation } from '../lib/sku-features'
|
import { composeSkuName, composeUnitNotation } from '../lib/sku-features'
|
||||||
import type { ProductTagData, TagProductMapping, UserNegativityData, StoreSummary } from '@packages/shared'
|
import type { ProductTagData, TagProductMapping, UserNegativityData, StoreSummary, BannerData } from '@packages/shared'
|
||||||
|
|
||||||
// Re-exported so the package index (`export { ... } from './src/stores/store-helpers'`)
|
// Re-exported so the package index (`export { ... } from './src/stores/store-helpers'`)
|
||||||
// keeps resolving to the single shared source.
|
// keeps resolving to the single shared source.
|
||||||
export type { ProductTagData, TagProductMapping, UserNegativityData } from '@packages/shared'
|
export type { ProductTagData, TagProductMapping, UserNegativityData, BannerData } from '@packages/shared'
|
||||||
|
|
||||||
// StoreBasicData is exactly the shared StoreSummary shape — single source, same export name.
|
// StoreBasicData is exactly the shared StoreSummary shape — single source, same export name.
|
||||||
export type StoreBasicData = StoreSummary
|
export type StoreBasicData = StoreSummary
|
||||||
|
|
@ -30,15 +30,6 @@ export type StoreBasicData = StoreSummary
|
||||||
// BANNER STORE HELPERS
|
// BANNER STORE HELPERS
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
export interface BannerData {
|
|
||||||
id: number
|
|
||||||
name: string
|
|
||||||
imageUrl: string | null
|
|
||||||
serialNum: number | null
|
|
||||||
skuIds: number[] | null
|
|
||||||
createdAt: Date
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAllBannersForCache(): Promise<BannerData[]> {
|
export async function getAllBannersForCache(): Promise<BannerData[]> {
|
||||||
return db.query.homeBanners.findMany({
|
return db.query.homeBanners.findMany({
|
||||||
where: isNotNull(homeBanners.serialNum),
|
where: isNotNull(homeBanners.serialNum),
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { asc, eq } from 'drizzle-orm'
|
||||||
import type { InferSelectModel } from 'drizzle-orm'
|
import type { InferSelectModel } from 'drizzle-orm'
|
||||||
import type { UserDeliverySlot, UserSlotAvailability } from '@packages/shared'
|
import type { UserDeliverySlot, UserSlotAvailability } from '@packages/shared'
|
||||||
|
|
||||||
type SlotRow = InferSelectModel<typeof deliverySlotInfo>
|
export type SlotRow = InferSelectModel<typeof deliverySlotInfo>
|
||||||
|
|
||||||
const mapSlot = (slot: SlotRow): UserDeliverySlot => ({
|
const mapSlot = (slot: SlotRow): UserDeliverySlot => ({
|
||||||
id: slot.id,
|
id: slot.id,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
import type { MessageResponse, BasicSuccessResponse, IdName } from './primitives.types'
|
import type { MessageResponse, BasicSuccessResponse, IdName } from './primitives.types'
|
||||||
export type { Banner } from './banner.types'
|
export type { Banner } from './banner.types'
|
||||||
export type { Complaint, ComplaintWithUser } from './complaint.types'
|
export type { Complaint, ComplaintWithUser } from './complaint.types'
|
||||||
export type { Constant, ConstantUpdateResult } from './const.types'
|
export type { Constant, ConstantUpdateResult, ConstValueType } from './const.types'
|
||||||
export type { CouponValidationResult, UserMiniInfo } from './coupon.types'
|
export type { CouponValidationResult, UserMiniInfo } from './coupon.types'
|
||||||
export type { StaffRole } from './staff-user.types'
|
export type { StaffRole } from './staff-user.types'
|
||||||
export type { MessageResponse, IdName } from './primitives.types'
|
export type { MessageResponse, IdName } from './primitives.types'
|
||||||
|
|
@ -317,6 +317,13 @@ export interface AdminSkuFeature {
|
||||||
featureValue: string;
|
featureValue: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Feature input shape — was duplicated as CreateSkuFeatureInput (db_helper_sqlite
|
||||||
|
// admin-apis/product.ts) and SkuFeatureLike (db_helper_sqlite lib/sku-features.ts).
|
||||||
|
export interface SkuFeatureLike {
|
||||||
|
featureName?: string | null;
|
||||||
|
featureValue: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AdminProductComboItem {
|
export interface AdminProductComboItem {
|
||||||
skuId: number;
|
skuId: number;
|
||||||
skuName: string | null;
|
skuName: string | null;
|
||||||
|
|
|
||||||
|
|
@ -37,11 +37,24 @@ export interface AddressFormProps {
|
||||||
isEdit?: boolean
|
isEdit?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complaint item — identical in user-ui + web-ui complaints lists
|
// Generic list-item renderer props — user-ui home RenderStoreProps/SlotItemProps
|
||||||
export interface ComplaintItemProps {
|
export interface ListItemProps {
|
||||||
item: any
|
item: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Complaint item — identical in user-ui + web-ui complaints lists
|
||||||
|
export type ComplaintItemProps = ListItemProps
|
||||||
|
|
||||||
|
// Page-level flash-delivery flag — web-ui FloatingCartBar + user-ui checkout/cart pages
|
||||||
|
export interface FlashDeliveryProps {
|
||||||
|
isFlashDelivery?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3D celebration flag — fallback-ui CharactersProps/SceneProps
|
||||||
|
export interface CelebrationProps {
|
||||||
|
isCelebrating: boolean
|
||||||
|
}
|
||||||
|
|
||||||
// Compact product card — identical in web-ui flash.tsx + slot-view.tsx
|
// Compact product card — identical in web-ui flash.tsx + slot-view.tsx
|
||||||
export interface CompactProductCardProps {
|
export interface CompactProductCardProps {
|
||||||
item: any
|
item: any
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
/**
|
/**
|
||||||
* Cache / Store-Helper Types
|
* Cache / Store-Helper Types
|
||||||
* Identical in db_helper_postgres and db_helper_sqlite `src/stores/store-helpers.ts`.
|
* Identical in db_helper_sqlite `src/stores/store-helpers.ts` (pg keeps its own
|
||||||
* Centralized here so both helpers + backend stores import one source.
|
* divergent copies by exclusion). Centralized here so helpers + backend stores
|
||||||
* NOTE: BannerData/ProductBasicData are intentionally NOT included here — they
|
* import one source.
|
||||||
* diverge between pg (productIds/flat product) and sqlite (skuIds/SKU-composed shape).
|
|
||||||
* Only the truly identical subset is shared; divergent shapes keep their local defs.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface ProductTagData {
|
export interface ProductTagData {
|
||||||
|
|
@ -21,3 +19,13 @@ export interface UserNegativityData {
|
||||||
userId: number
|
userId: number
|
||||||
totalNegativityScore: number
|
totalNegativityScore: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Banner cache row — sqlite store-helpers + backend banner-store (was local `Banner`)
|
||||||
|
export interface BannerData {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
imageUrl: string | null
|
||||||
|
serialNum: number | null
|
||||||
|
skuIds: number[] | null
|
||||||
|
createdAt: Date
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,3 +13,6 @@ export interface ConstantUpdateResult {
|
||||||
updatedCount: number;
|
updatedCount: number;
|
||||||
keys: string[];
|
keys: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Value union for CONST_TYPES maps — was duplicated in backend + sqlite const-keys.ts
|
||||||
|
export type ConstValueType = 'string' | 'boolean' | 'number'
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,11 @@ export type * from './user';
|
||||||
export type * from './store.types';
|
export type * from './store.types';
|
||||||
|
|
||||||
// Shared cross-cutting types — import from '@packages/shared' anywhere:
|
// Shared cross-cutting types — import from '@packages/shared' anywhere:
|
||||||
export type * from './primitives.types'; // MessageResponse, BasicSuccessResponse, IdName, ReactParentComponent
|
export type * from './primitives.types'; // MessageResponse, BasicSuccessResponse, IdName, ReactParentComponent, TabIconProps
|
||||||
export type * from './upload.types'; // ContextString, UploadInput, UploadBatchInput, UploadResult
|
export type * from './upload.types'; // ContextString, UploadInput, UploadBatchInput, UploadResult
|
||||||
export type * from './app-common.types'; // LoginFormInputs, DropdownOption, CartIconProps, AddressFormProps, CartData
|
export type * from './app-common.types'; // LoginFormInputs, ComplaintItemProps/ListItemProps, FlashDeliveryProps, CelebrationProps
|
||||||
export type * from './order.types'; // PlacedOrder, CouponUsageWithCoupon, GetAllOrdersInput
|
export type * from './ui-common.types'; // DropdownOption, DialogProps, ConfirmationDialogProps, LoadingDialogProps, QuantifierProps, ImageUploader*, TextButtonProps
|
||||||
|
export type * from './order.types'; // PlacedOrder, CouponUsageWithCoupon, GetAllOrdersInput, OrderStatus, DeliveryStatus, CartData
|
||||||
export type * from './seed.types'; // UnitSeedData, StaffRoleName, StaffPermissionName, RolePermissionAssignment, KeyValSeedData
|
export type * from './seed.types'; // UnitSeedData, StaffRoleName, StaffPermissionName, RolePermissionAssignment, KeyValSeedData
|
||||||
export type * from './inputs.types'; // CreateBannerInput, UpdateBannerInput, CreateStoreInput, Login/Register requests
|
export type * from './inputs.types'; // CreateBannerInput, UpdateBannerInput, CreateStoreInput, Login/Register requests
|
||||||
export type * from './cache.types'; // ProductTagData, TagProductMapping, UserNegativityData (truly identical subset)
|
export type * from './cache.types'; // ProductTagData, TagProductMapping, UserNegativityData, BannerData
|
||||||
|
|
|
||||||
|
|
@ -47,3 +47,17 @@ export type GetAllOrdersInput = {
|
||||||
cancellationFilter?: 'all' | 'cancelled' | 'not_cancelled'
|
cancellationFilter?: 'all' | 'cancelled' | 'not_cancelled'
|
||||||
flashDeliveryFilter?: 'all' | 'flash' | 'regular'
|
flashDeliveryFilter?: 'all' | 'flash' | 'regular'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cancellation-flow status unions — were duplicated as function-scoped locals
|
||||||
|
// in backend user-apis order.ts (two closures). Single source here.
|
||||||
|
export type OrderStatus = 'cancelled' | 'success'
|
||||||
|
|
||||||
|
export type DeliveryStatus = 'cancelled' | 'success' | 'pending' | 'packaged'
|
||||||
|
|
||||||
|
// Cart summary shape — identical in user-ui + web-ui cart hooks, but the
|
||||||
|
// element CartItem differs per app, so the element type is generic.
|
||||||
|
export interface CartData<T = unknown> {
|
||||||
|
items: T[]
|
||||||
|
totalItems: number
|
||||||
|
totalAmount: number
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,22 @@ export interface StoreSummary {
|
||||||
export interface StoresSummaryResponse {
|
export interface StoresSummaryResponse {
|
||||||
stores: StoreSummary[];
|
stores: StoreSummary[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lightweight tag row used on store-detail pages (user-ui + web-ui).
|
||||||
|
* Named StoreTag to avoid clashing with the richer backend Tag shape.
|
||||||
|
*/
|
||||||
|
export interface StoreTag {
|
||||||
|
id: number;
|
||||||
|
tagName: string;
|
||||||
|
productIds?: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chip props for tag selection on store-detail pages.
|
||||||
|
*/
|
||||||
|
export interface TagChipProps {
|
||||||
|
tag: StoreTag;
|
||||||
|
isSelected: boolean;
|
||||||
|
onPress: () => void;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,12 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { View, TouchableOpacity } from "react-native";
|
import { View, TouchableOpacity } from "react-native";
|
||||||
import { Image } from 'expo-image';
|
import { Image } from 'expo-image';
|
||||||
|
import type { ImageUploaderProps } from '@packages/shared';
|
||||||
import MyText from "./text";
|
import MyText from "./text";
|
||||||
import tw from '../lib/tailwind';
|
import tw from '../lib/tailwind';
|
||||||
import Ionicons from "@expo/vector-icons/Ionicons";
|
import Ionicons from "@expo/vector-icons/Ionicons";
|
||||||
import { MaterialIcons } from "@expo/vector-icons";
|
import { MaterialIcons } from "@expo/vector-icons";
|
||||||
|
|
||||||
interface ImageUploaderProps {
|
|
||||||
images: { uri?: string }[];
|
|
||||||
onAddImage: () => void;
|
|
||||||
onRemoveImage: (uri: string) => void;
|
|
||||||
existingImageUrls?: string[]; // URLs of existing images that should be displayed
|
|
||||||
onRemoveExistingImage?: (url: string) => void; // Callback to track which existing images are removed
|
|
||||||
allowMultiple?: boolean; // Whether to allow multiple image uploads
|
|
||||||
}
|
|
||||||
|
|
||||||
const ImageUploader: React.FC<ImageUploaderProps> = ({
|
const ImageUploader: React.FC<ImageUploaderProps> = ({
|
||||||
images,
|
images,
|
||||||
existingImageUrls = [],
|
existingImageUrls = [],
|
||||||
|
|
|
||||||
|
|
@ -3,27 +3,13 @@ import { View } from 'react-native'
|
||||||
import { Image } from 'expo-image'
|
import { Image } from 'expo-image'
|
||||||
import Ionicons from '@expo/vector-icons/Ionicons'
|
import Ionicons from '@expo/vector-icons/Ionicons'
|
||||||
import { MaterialIcons } from '@expo/vector-icons'
|
import { MaterialIcons } from '@expo/vector-icons'
|
||||||
|
import type { ImageUploaderNeoItem, ImageUploaderNeoPayload, ImageUploaderNeoProps } from '@packages/shared'
|
||||||
import tw from '../lib/tailwind'
|
import tw from '../lib/tailwind'
|
||||||
import MyText from './text'
|
import MyText from './text'
|
||||||
import MyTouchableOpacity from './touchable-opacity'
|
import MyTouchableOpacity from './touchable-opacity'
|
||||||
import usePickImage from './use-pick-image'
|
import usePickImage from './use-pick-image'
|
||||||
|
|
||||||
export interface ImageUploaderNeoItem {
|
export type { ImageUploaderNeoItem, ImageUploaderNeoPayload } from '@packages/shared'
|
||||||
imgUrl: string
|
|
||||||
mimeType: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ImageUploaderNeoPayload {
|
|
||||||
url: string
|
|
||||||
mimeType: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ImageUploaderNeoProps {
|
|
||||||
images: ImageUploaderNeoItem[]
|
|
||||||
onImageAdd: (images: ImageUploaderNeoPayload[]) => void
|
|
||||||
onImageRemove: (image: ImageUploaderNeoPayload) => void
|
|
||||||
allowMultiple?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const ImageUploaderNeo: React.FC<ImageUploaderNeoProps> = ({
|
const ImageUploaderNeo: React.FC<ImageUploaderNeoProps> = ({
|
||||||
images,
|
images,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Button as PaperButton, ButtonProps } from "react-native-paper";
|
import { Button as PaperButton, ButtonProps } from "react-native-paper";
|
||||||
// import { useTheme } from "../hooks/theme-context";
|
// import { useTheme } from "../hooks/theme-context";
|
||||||
|
import type { TextButtonProps } from '@packages/shared';
|
||||||
import { theme, useTheme } from "common-ui";
|
import { theme, useTheme } from "common-ui";
|
||||||
import { TouchableOpacity } from "react-native";
|
import { TouchableOpacity } from "react-native";
|
||||||
import MyText from "./text"; // Updated import path
|
import MyText from "./text"; // Updated import path
|
||||||
|
|
@ -70,9 +71,7 @@ function MyButton({
|
||||||
}
|
}
|
||||||
|
|
||||||
// MyTextButton props: same as Props, but no children and with an extra 'text' property
|
// MyTextButton props: same as Props, but no children and with an extra 'text' property
|
||||||
interface MyTextButtonProps extends Omit<Props, "children"> {
|
interface MyTextButtonProps extends Omit<Props, "children">, TextButtonProps {}
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function MyTextButton({
|
export function MyTextButton({
|
||||||
variant = "blue",
|
variant = "blue",
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,10 @@
|
||||||
import React, { ReactNode, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Modal, View, TouchableOpacity, StyleSheet, Animated, Easing, Dimensions, TextInput, KeyboardAvoidingView, Platform, ScrollView } from 'react-native';
|
import { Modal, View, TouchableOpacity, StyleSheet, Animated, Easing, Dimensions, TextInput, KeyboardAvoidingView, Platform, ScrollView } from 'react-native';
|
||||||
|
import type { ConfirmationDialogProps, DialogProps } from '@packages/shared';
|
||||||
import MyText from './text';
|
import MyText from './text';
|
||||||
import MyButton from './button';
|
import MyButton from './button';
|
||||||
import tw from "../lib/tailwind";
|
import tw from "../lib/tailwind";
|
||||||
|
|
||||||
interface DialogProps {
|
|
||||||
open: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
children: ReactNode;
|
|
||||||
enableDismiss?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SCREEN_HEIGHT = Dimensions.get('window').height;
|
const SCREEN_HEIGHT = Dimensions.get('window').height;
|
||||||
|
|
||||||
export const BottomDialog: React.FC<DialogProps> = ({ open, onClose, children, enableDismiss = true }) => {
|
export const BottomDialog: React.FC<DialogProps> = ({ open, onClose, children, enableDismiss = true }) => {
|
||||||
|
|
@ -191,18 +185,6 @@ const commentStyles = StyleSheet.create({
|
||||||
|
|
||||||
export default BottomDialog;
|
export default BottomDialog;
|
||||||
|
|
||||||
interface ConfirmationDialogProps {
|
|
||||||
open: boolean;
|
|
||||||
positiveAction: (comment?: string) => void;
|
|
||||||
commentNeeded?: boolean;
|
|
||||||
negativeAction?: () => void;
|
|
||||||
title?: string;
|
|
||||||
message?: string;
|
|
||||||
confirmText?: string;
|
|
||||||
cancelText?: string;
|
|
||||||
isLoading?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ConfirmationDialog: React.FC<ConfirmationDialogProps> = (props) => {
|
export const ConfirmationDialog: React.FC<ConfirmationDialogProps> = (props) => {
|
||||||
const {
|
const {
|
||||||
open,
|
open,
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,9 @@ import tw from "../lib/tailwind";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Text, View } from "react-native";
|
import { Text, View } from "react-native";
|
||||||
import { Dropdown } from "react-native-element-dropdown";
|
import { Dropdown } from "react-native-element-dropdown";
|
||||||
|
import type { DropdownOption } from '@packages/shared';
|
||||||
|
|
||||||
export interface DropdownOption {
|
export type { DropdownOption } from '@packages/shared';
|
||||||
label: string;
|
|
||||||
value: string | number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
label: string;
|
label: string;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, ActivityIndicator, Modal, TouchableOpacity } from 'react-native';
|
import { View, ActivityIndicator, Modal, TouchableOpacity } from 'react-native';
|
||||||
|
import type { LoadingDialogProps } from '@packages/shared';
|
||||||
import tw from '../lib/tailwind';
|
import tw from '../lib/tailwind';
|
||||||
import MyText from './text';
|
import MyText from './text';
|
||||||
|
|
||||||
interface LoadingDialogProps {
|
|
||||||
open: boolean;
|
|
||||||
message?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LoadingDialog: React.FC<LoadingDialogProps> = ({
|
export const LoadingDialog: React.FC<LoadingDialogProps> = ({
|
||||||
open,
|
open,
|
||||||
message = 'Loading...'
|
message = 'Loading...'
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, TouchableOpacity, Text } from 'react-native';
|
import { View, TouchableOpacity, Text } from 'react-native';
|
||||||
import { MaterialIcons } from '@expo/vector-icons';
|
import { MaterialIcons } from '@expo/vector-icons';
|
||||||
|
import type { QuantifierProps } from '@packages/shared';
|
||||||
import tw from '../lib/tailwind';
|
import tw from '../lib/tailwind';
|
||||||
import { colors } from '../lib/theme-colors';
|
import { colors } from '../lib/theme-colors';
|
||||||
|
|
||||||
interface QuantifierProps {
|
|
||||||
value: number;
|
|
||||||
setValue: (value: number) => void;
|
|
||||||
step?: number;
|
|
||||||
unit?: string | { shortNotation: string };
|
|
||||||
min?: number;
|
|
||||||
max?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Quantifier: React.FC<QuantifierProps> = ({
|
const Quantifier: React.FC<QuantifierProps> = ({
|
||||||
value,
|
value,
|
||||||
setValue,
|
setValue,
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,15 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import type { ConfirmationDialogProps, DialogProps } from '@packages/shared'
|
||||||
import { p } from './my-text'
|
import { p } from './my-text'
|
||||||
import { MyButton } from './my-button'
|
import { MyButton } from './my-button'
|
||||||
import { cn } from '../lib/utils'
|
import { cn } from '../lib/utils'
|
||||||
|
|
||||||
interface BottomDialogProps {
|
|
||||||
open: boolean
|
|
||||||
onClose: () => void
|
|
||||||
children: React.ReactNode
|
|
||||||
enableDismiss?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export function BottomDialog({
|
export function BottomDialog({
|
||||||
open,
|
open,
|
||||||
onClose,
|
onClose,
|
||||||
children,
|
children,
|
||||||
enableDismiss = true,
|
enableDismiss = true,
|
||||||
}: BottomDialogProps) {
|
}: DialogProps) {
|
||||||
const [visible, setVisible] = useState(false)
|
const [visible, setVisible] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -52,18 +46,6 @@ export function BottomDialog({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ConfirmationDialogProps {
|
|
||||||
open: boolean
|
|
||||||
positiveAction: (comment?: string) => void
|
|
||||||
commentNeeded?: boolean
|
|
||||||
negativeAction?: () => void
|
|
||||||
title?: string
|
|
||||||
message?: string
|
|
||||||
confirmText?: string
|
|
||||||
cancelText?: string
|
|
||||||
isLoading?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ConfirmationDialog({
|
export function ConfirmationDialog({
|
||||||
open,
|
open,
|
||||||
positiveAction,
|
positiveAction,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { cn } from '../lib/utils'
|
import { cn } from '../lib/utils'
|
||||||
import { ChevronDown } from 'lucide-react'
|
import { ChevronDown } from 'lucide-react'
|
||||||
|
import type { DropdownOption } from '@packages/shared'
|
||||||
|
|
||||||
export interface DropdownOption {
|
export type { DropdownOption } from '@packages/shared'
|
||||||
label: string
|
|
||||||
value: string | number
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DropdownProps {
|
interface DropdownProps {
|
||||||
label: string
|
label: string
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,10 @@
|
||||||
import React, { useRef } from 'react'
|
import React, { useRef } from 'react'
|
||||||
import { cn } from '../lib/utils'
|
import { cn } from '../lib/utils'
|
||||||
import { Plus, X } from 'lucide-react'
|
import { Plus, X } from 'lucide-react'
|
||||||
|
import type { ImageUploaderNeoItem, ImageUploaderNeoPayload, ImageUploaderNeoProps } from '@packages/shared'
|
||||||
import { div } from './my-touchable-opacity'
|
import { div } from './my-touchable-opacity'
|
||||||
|
|
||||||
export interface ImageUploaderNeoItem {
|
export type { ImageUploaderNeoItem, ImageUploaderNeoPayload } from '@packages/shared'
|
||||||
imgUrl: string
|
|
||||||
mimeType: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ImageUploaderNeoPayload {
|
|
||||||
url: string
|
|
||||||
mimeType: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ImageUploaderNeoProps {
|
|
||||||
images: ImageUploaderNeoItem[]
|
|
||||||
onImageAdd: (images: ImageUploaderNeoPayload[]) => void
|
|
||||||
onImageRemove: (image: ImageUploaderNeoPayload) => void
|
|
||||||
allowMultiple?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
function usePickImage({
|
function usePickImage({
|
||||||
multiple,
|
multiple,
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,8 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { cn } from '../lib/utils'
|
import { cn } from '../lib/utils'
|
||||||
import { Plus, X } from 'lucide-react'
|
import { Plus, X } from 'lucide-react'
|
||||||
|
import type { ImageUploaderProps } from '@packages/shared'
|
||||||
|
|
||||||
interface ImageUploaderProps {
|
|
||||||
images: { uri?: string }[]
|
|
||||||
onAddImage: () => void
|
|
||||||
onRemoveImage: (uri: string) => void
|
|
||||||
existingImageUrls?: string[]
|
|
||||||
onRemoveExistingImage?: (url: string) => void
|
|
||||||
allowMultiple?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ImageUploader({
|
export function ImageUploader({
|
||||||
images,
|
images,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import type { LoadingDialogProps } from '@packages/shared'
|
||||||
interface LoadingDialogProps {
|
|
||||||
open: boolean
|
|
||||||
message?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function LoadingDialog({ open, message = 'Loading...' }: LoadingDialogProps) {
|
export function LoadingDialog({ open, message = 'Loading...' }: LoadingDialogProps) {
|
||||||
if (!open) return null
|
if (!open) return null
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import type { TextButtonProps } from '@packages/shared'
|
||||||
import { cn } from '../lib/utils'
|
import { cn } from '../lib/utils'
|
||||||
|
|
||||||
interface MyButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
interface MyButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
|
|
@ -43,9 +44,7 @@ export function MyButton({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface pButtonProps extends Omit<MyButtonProps, 'children'> {
|
interface pButtonProps extends Omit<MyButtonProps, 'children'>, TextButtonProps {}
|
||||||
text: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function pButton({ text, ...props }: pButtonProps) {
|
export function pButton({ text, ...props }: pButtonProps) {
|
||||||
return <MyButton {...props}>{text}</MyButton>
|
return <MyButton {...props}>{text}</MyButton>
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,9 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { cn } from '../lib/utils'
|
import { cn } from '../lib/utils'
|
||||||
import { p } from './my-text'
|
import { p } from './my-text'
|
||||||
|
import type { QuantifierProps } from '@packages/shared'
|
||||||
import { Minus, Plus } from 'lucide-react'
|
import { Minus, Plus } from 'lucide-react'
|
||||||
|
|
||||||
interface QuantifierProps {
|
|
||||||
value: number
|
|
||||||
setValue: (value: number) => void
|
|
||||||
step?: number
|
|
||||||
unit?: string | { shortNotation: string }
|
|
||||||
min?: number
|
|
||||||
max?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Quantifier({
|
export function Quantifier({
|
||||||
value,
|
value,
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,9 @@
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"rootDir": "./src",
|
"rootDir": "./src",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"],
|
||||||
|
"@packages/shared": ["../shared"],
|
||||||
|
"@packages/shared/*": ["../shared/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue