DEAD_CODE_CLEAN #7

Merged
shafi merged 30 commits from DEAD_CODE_CLEAN into master 2026-09-14 04:32:52 +00:00
43 changed files with 319 additions and 266 deletions
Showing only changes of commit 1ff771355e - Show all commits

View file

@ -5,11 +5,10 @@ import * as Yup from 'yup'
import { MyTextInput, BottomDropdown, MyText, tw, ImageUploaderNeo, ImageUploaderNeoItem, ImageUploaderNeoPayload, Checkbox, InfoDialog } from 'common-ui'
import MaterialIcons from '@expo/vector-icons/MaterialIcons'
import { trpc } from '../trpc-client'
import type { SkuFeatureLike } from '@packages/shared'
interface Attribute {
featureName: string | null
featureValue: string
}
// Feature input shape — single source: shared SkuFeatureLike
type Attribute = SkuFeatureLike
interface Variant {
id?: number

View file

@ -1,3 +1,5 @@
import type { ConstValueType } from '@packages/shared'
export const CONST_KEYS = {
minRegularOrderValue: 'minRegularOrderValue',
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 type ConstValueType = 'string' | 'boolean' | 'number'
export const CONST_TYPES: Record<ConstKey, ConstValueType> = {
minRegularOrderValue: 'number',
freeDeliveryThreshold: 'number',

View file

@ -1,22 +1,12 @@
// import redisClient from '@/src/lib/redis-client'
import type { BannerData } from '@packages/shared'
import {
getAllBannersForCache,
getBanners,
getBannerById as getBannerByIdFromDb,
type BannerData,
} from '@/src/dbService'
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> {
try {
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 {
// const key = `banner:${id}`
// 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 {
// Get all keys matching the pattern "banner:*"
// const keys = await redisClient.KEYS('banner:*')

View file

@ -24,6 +24,7 @@ import {
} from "@/src/dbService";
import { scaffoldAssetUrl } from "@/src/lib/s3-client";
import { ApiError } from "@/src/lib/api-error";
import type { DeliveryStatus, OrderStatus } from "@packages/shared";
import {
sendOrderPlacedNotification,
sendOrderCancelledNotification,
@ -334,9 +335,6 @@ export const orderRouter = router({
const status = order.orderStatus[0];
const refund = order.refunds[0];
type DeliveryStatus = "cancelled" | "success" | "pending" | "packaged";
type OrderStatus = "cancelled" | "success";
let deliveryStatus: DeliveryStatus;
let orderStatus: OrderStatus;
@ -474,9 +472,6 @@ export const orderRouter = router({
const status = order.orderStatus[0];
const refund = order.refunds[0];
type DeliveryStatus = "cancelled" | "success" | "pending" | "packaged";
type OrderStatus = "cancelled" | "success";
let deliveryStatus: DeliveryStatus;
let orderStatusResult: OrderStatus;

View file

@ -1,6 +1,7 @@
import { useRef, useEffect, useMemo } from 'react';
import { useFrame } from '@react-three/fiber';
import * as THREE from 'three';
import type { CelebrationProps } from '@packages/shared';
interface CharacterProps {
color: string;
@ -758,11 +759,7 @@ function Character({ color, isCelebrating, delay = 0, type, hairColor = '#2D1B0E
);
}
interface CharactersProps {
isCelebrating: boolean;
}
export default function Characters({ isCelebrating }: CharactersProps) {
export default function Characters({ isCelebrating }: CelebrationProps) {
return (
<group>
{/* Character 1 - Cheerer - Left side */}

View file

@ -2,6 +2,7 @@ import { useRef, useEffect } from 'react';
import { Canvas, useFrame, useThree } from '@react-three/fiber';
import { OrbitControls, Stars, Float } from '@react-three/drei';
import * as THREE from 'three';
import type { CelebrationProps } from '@packages/shared';
import Characters from './Characters';
// Warm gradient background
@ -136,11 +137,7 @@ function CameraController({ isCelebrating }: { isCelebrating: boolean }) {
return null;
}
interface SceneProps {
isCelebrating: boolean;
}
export default function Scene({ isCelebrating }: SceneProps) {
export default function Scene({ isCelebrating }: CelebrationProps) {
return (
<div className="w-full h-screen">
<Canvas

View file

@ -20,6 +20,7 @@ import AddToCartDialog from "@/src/components/AddToCartDialog";
import MyFlatList from "common-ui/src/components/flat-list";
import { trpc } from "@/src/trpc-client";
import type { ListItemProps } from '@packages/shared';
import { useAllProducts, useStores, useSlots, useGetEssentialConsts } from "@/src/hooks/prominent-api-hooks";
import { useProductSlotIdentifier } from "@/hooks/useProductSlotIdentifier";
import { useCentralSlotStore } from "@/src/store/centralSlotStore";
@ -68,11 +69,7 @@ const TAG_COLORS = [
const getTagColor = (id: number) => TAG_COLORS[id % TAG_COLORS.length];
interface RenderStoreProps {
item: any;
}
const RenderStore = memo(({ item }: RenderStoreProps) => {
const RenderStore = memo(({ item }: ListItemProps) => {
const router = useRouter();
const { setNavigatedFromHome, setSelectedStoreId } = useNavigationStore();
@ -343,11 +340,7 @@ const ExploreProductItem = memo(({ item, onPress }: ProductItemProps) => {
);
});
interface SlotItemProps {
item: any;
}
const SlotItem = memo(({ item }: SlotItemProps) => <SlotCard slot={item} />);
const SlotItem = memo(({ item }: ListItemProps) => <SlotCard slot={item} />);
interface ProductItemProps {
item: any;

View file

@ -17,23 +17,12 @@ import ProductCard from "@/components/ProductCard";
import FloatingCartBar from "@/components/floating-cart-bar";
import { useStoreHeaderStore } from "@/src/store/storeHeaderStore";
import { useAllProducts, useStoreWithProducts } from "@/src/hooks/prominent-api-hooks";
import type { StoreTag, TagChipProps } from "@packages/shared";
const { width: screenWidth } = Dimensions.get("window");
const itemWidth = (screenWidth - 48) / 2;
interface Tag {
id: number;
tagName: string;
productIds?: number[];
}
interface ChipProps {
tag: Tag;
isSelected: boolean;
onPress: () => void;
}
const Chip: React.FC<ChipProps> = ({ tag, isSelected, onPress }) => {
const Chip: React.FC<TagChipProps> = ({ tag, isSelected, onPress }) => {
const productCount = tag.productIds?.length || 0;
return (

View file

@ -28,12 +28,9 @@ import { useCentralProductStore } from '@/src/store/centralProductStore';
import { useCentralSlotStore } from '@/src/store/centralSlotStore';
import { useGetCart, useUpdateCartItem, useRemoveFromCart } from '@/hooks/cart-query-hooks';
import { useGetEssentialConsts } from '@/src/hooks/prominent-api-hooks';
import type { FlashDeliveryProps } from '@packages/shared';
interface CartPageProps {
isFlashDelivery?: boolean;
}
export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
export default function CartPage({ isFlashDelivery = false }: FlashDeliveryProps) {
// Hide tabs when cart page is active
useHideTabNav();

View file

@ -16,12 +16,9 @@ import { useGetEssentialConsts } from '@/src/hooks/prominent-api-hooks';
import PaymentAndOrderComponent from '@/components/PaymentAndOrderComponent';
import CheckoutAddressSelector from '@/components/CheckoutAddressSelector';
import { useAddressStore } from '@/src/store/addressStore';
import type { FlashDeliveryProps } from '@packages/shared';
interface CheckoutPageProps {
isFlashDelivery?: boolean;
}
const CheckoutPage: React.FC<CheckoutPageProps> = ({ isFlashDelivery = false }) => {
const CheckoutPage: React.FC<FlashDeliveryProps> = ({ isFlashDelivery = false }) => {
const params = useLocalSearchParams();
const queryClient = useQueryClient();
const router = useRouter();

View file

@ -2,6 +2,7 @@ import { useCentralProductStore } from '@/src/store/centralProductStore';
import { useCentralSlotStore } from '@/src/store/centralSlotStore';
import { Alert } from 'react-native';
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';
// Cart type definition
@ -42,11 +43,7 @@ export interface CartItem {
slotId: number;
}
interface CartData {
items: CartItem[];
totalItems: number;
totalAmount: number;
}
type CartData = SharedCartData<CartItem>
interface UseGetCartOptions {
refetchOnWindowFocus?: boolean;

View file

@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react'
import type { FlashDeliveryProps } from '@packages/shared'
import { useNavigate } from '@tanstack/react-router'
import { useGetCart, useUpdateCartItem, useRemoveFromCart } from '../hooks/cart-query-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 dayjs from 'dayjs'
interface FloatingCartBarProps {
isFlashDelivery?: boolean
}
// Smart time window formatting function
const formatTimeRange = (deliveryTime: string | Date) => {
const time = dayjs(deliveryTime)
@ -28,7 +25,7 @@ const formatTimeRange = (deliveryTime: string | Date) => {
return `${time.format('ddd, DD MMM ')}${timeRange}`
}
export function FloatingCartBar({ isFlashDelivery = false }: FloatingCartBarProps) {
export function FloatingCartBar({ isFlashDelivery = false }: FlashDeliveryProps) {
const navigate = useNavigate()
const [isExpanded, setIsExpanded] = useState(false)
const [quantities, setQuantities] = useState<Record<number, number>>({})

View file

@ -1,4 +1,5 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import type { CartData as SharedCartData } from '@packages/shared'
type CartType = 'regular' | 'flash'
@ -12,11 +13,7 @@ interface CartItem {
deliveryDate?: string | null
}
interface CartData {
items: CartItem[]
totalItems: number
totalAmount: number
}
type CartData = SharedCartData<CartItem>
function getCartKey(cartType: CartType): string {
return `local-cart-${cartType}`

View file

@ -1,6 +1,7 @@
import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router'
import { useStoreWithProducts, useAllProducts } from '../hooks/prominent-api-hooks'
import { useState, useMemo } from 'react'
import type { StoreTag, TagChipProps } from '@packages/shared'
import { AppLayout } from '../components/AppLayout'
import AddToCartDialog from '../components/AddToCartDialog'
import { ProductCard } from '../components/ProductCard'
@ -11,12 +12,6 @@ export const Route = createFileRoute('/stores/$storeId')({
component: StoreDetailPage,
})
interface Tag {
id: number
tagName: string
productIds?: number[]
}
function StoreDetailPage() {
const { storeId } = useParams({ from: '/stores/$storeId' })
const navigate = useNavigate()
@ -47,7 +42,7 @@ function StoreDetailPage() {
// Filter products based on selected tag
const filteredProducts = selectedTagId
? 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
})
: storeProducts
@ -131,7 +126,7 @@ function StoreDetailPage() {
isSelected={selectedTagId === null}
onPress={() => setSelectedTagId(null)}
/>
{storeData.tags.map((tag: Tag) => (
{storeData.tags.map((tag: StoreTag) => (
<TagChip
key={tag.id}
tag={tag}
@ -148,7 +143,7 @@ function StoreDetailPage() {
<Grid3X3 className="mr-2 h-5 w-5 text-gray-700" />
<p className="text-lg font-bold text-gray-900">
{selectedTagId
? `${storeData?.tags.find((t: Tag) => t.id === selectedTagId)?.tagName} items`
? `${storeData?.tags.find((t: StoreTag) => t.id === selectedTagId)?.tagName} items`
: `${filteredProducts.length} products`}
</p>
</div>
@ -197,12 +192,6 @@ function StoreDetailPage() {
)
}
interface TagChipProps {
tag: Tag
isSelected: boolean
onPress: () => void
}
function TagChip({ tag, isSelected, onPress }: TagChipProps) {
const productCount = tag.productIds?.length || 0

View file

@ -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.
[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.

View file

@ -58,6 +58,7 @@ import type {
AdminSpecialDeal,
AdminUnit,
AdminUpdateSlotProductsResult,
SkuFeatureLike,
Store,
} from '@packages/shared'
@ -66,11 +67,6 @@ type SkuRow = InferSelectModel<typeof productSkus>
type MarketStatsRow = InferSelectModel<typeof productMarketStats>
type SkuFeatureRow = InferSelectModel<typeof skuFeatures>
interface CreateSkuFeatureInput {
featureName?: string | null
featureValue: string
}
interface CreateComboItemInput {
skuId: number
}
@ -87,7 +83,7 @@ interface CreateSkuInput {
isOffer?: boolean
isComboOnly?: boolean
isDeleted?: boolean
features: CreateSkuFeatureInput[]
features: SkuFeatureLike[]
comboItems?: CreateComboItemInput[]
}

View file

@ -10,9 +10,10 @@ import type {
AdminVendorUpdatePackagingResult,
} from '@packages/shared'
import { coerceDate } from '../lib/date'
import type { SlotRow } from '../user-apis/slots'
type VendorSnippetRow = InferSelectModel<typeof vendorSnippets>
type DeliverySlotRow = InferSelectModel<typeof deliverySlotInfo>
type DeliverySlotRow = SlotRow
const mapVendorSnippet = (snippet: VendorSnippetRow): AdminVendorSnippet => ({
id: snippet.id,

View file

@ -1,3 +1,5 @@
import type { ConstValueType } from '@packages/shared'
export const CONST_KEYS = {
minRegularOrderValue: 'minRegularOrderValue',
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 type ConstValueType = 'string' | 'boolean' | 'number'
export const CONST_TYPES: Record<ConstKey, ConstValueType> = {
minRegularOrderValue: 'number',
freeDeliveryThreshold: 'number',

View file

@ -1,7 +1,6 @@
export interface SkuFeatureLike {
featureName?: string | null
featureValue: string
}
import type { SkuFeatureLike } from '@packages/shared'
export type { SkuFeatureLike } from '@packages/shared'
export const cleanFeatureValue = (value: string): string =>
value.replace(/\.0(?=\D|$)/g, '')

View file

@ -17,11 +17,11 @@ import {
} from '../db/schema'
import { eq, and, gt, sql, isNotNull, asc } from 'drizzle-orm'
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'`)
// 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.
export type StoreBasicData = StoreSummary
@ -30,15 +30,6 @@ export type StoreBasicData = StoreSummary
// 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[]> {
return db.query.homeBanners.findMany({
where: isNotNull(homeBanners.serialNum),

View file

@ -4,7 +4,7 @@ import { asc, eq } from 'drizzle-orm'
import type { InferSelectModel } from 'drizzle-orm'
import type { UserDeliverySlot, UserSlotAvailability } from '@packages/shared'
type SlotRow = InferSelectModel<typeof deliverySlotInfo>
export type SlotRow = InferSelectModel<typeof deliverySlotInfo>
const mapSlot = (slot: SlotRow): UserDeliverySlot => ({
id: slot.id,

View file

@ -5,7 +5,7 @@
import type { MessageResponse, BasicSuccessResponse, IdName } from './primitives.types'
export type { Banner } from './banner.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 { StaffRole } from './staff-user.types'
export type { MessageResponse, IdName } from './primitives.types'
@ -317,6 +317,13 @@ export interface AdminSkuFeature {
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 {
skuId: number;
skuName: string | null;

View file

@ -37,11 +37,24 @@ export interface AddressFormProps {
isEdit?: boolean
}
// Complaint item — identical in user-ui + web-ui complaints lists
export interface ComplaintItemProps {
// Generic list-item renderer props — user-ui home RenderStoreProps/SlotItemProps
export interface ListItemProps {
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
export interface CompactProductCardProps {
item: any

View file

@ -1,10 +1,8 @@
/**
* Cache / Store-Helper Types
* Identical in db_helper_postgres and db_helper_sqlite `src/stores/store-helpers.ts`.
* Centralized here so both helpers + backend stores import one source.
* NOTE: BannerData/ProductBasicData are intentionally NOT included here they
* 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.
* Identical in db_helper_sqlite `src/stores/store-helpers.ts` (pg keeps its own
* divergent copies by exclusion). Centralized here so helpers + backend stores
* import one source.
*/
export interface ProductTagData {
@ -21,3 +19,13 @@ export interface UserNegativityData {
userId: 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
}

View file

@ -13,3 +13,6 @@ export interface ConstantUpdateResult {
updatedCount: number;
keys: string[];
}
// Value union for CONST_TYPES maps — was duplicated in backend + sqlite const-keys.ts
export type ConstValueType = 'string' | 'boolean' | 'number'

View file

@ -9,10 +9,11 @@ export type * from './user';
export type * from './store.types';
// 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 './app-common.types'; // LoginFormInputs, DropdownOption, CartIconProps, AddressFormProps, CartData
export type * from './order.types'; // PlacedOrder, CouponUsageWithCoupon, GetAllOrdersInput
export type * from './app-common.types'; // LoginFormInputs, ComplaintItemProps/ListItemProps, FlashDeliveryProps, CelebrationProps
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 './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

View file

@ -47,3 +47,17 @@ export type GetAllOrdersInput = {
cancellationFilter?: 'all' | 'cancelled' | 'not_cancelled'
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
}

View file

@ -20,3 +20,22 @@ export interface StoreSummary {
export interface StoresSummaryResponse {
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;
}

View file

@ -1,20 +1,12 @@
import React from "react";
import { View, TouchableOpacity } from "react-native";
import { Image } from 'expo-image';
import type { ImageUploaderProps } from '@packages/shared';
import MyText from "./text";
import tw from '../lib/tailwind';
import Ionicons from "@expo/vector-icons/Ionicons";
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> = ({
images,
existingImageUrls = [],

View file

@ -3,27 +3,13 @@ import { View } from 'react-native'
import { Image } from 'expo-image'
import Ionicons from '@expo/vector-icons/Ionicons'
import { MaterialIcons } from '@expo/vector-icons'
import type { ImageUploaderNeoItem, ImageUploaderNeoPayload, ImageUploaderNeoProps } from '@packages/shared'
import tw from '../lib/tailwind'
import MyText from './text'
import MyTouchableOpacity from './touchable-opacity'
import usePickImage from './use-pick-image'
export interface ImageUploaderNeoItem {
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
}
export type { ImageUploaderNeoItem, ImageUploaderNeoPayload } from '@packages/shared'
const ImageUploaderNeo: React.FC<ImageUploaderNeoProps> = ({
images,

View file

@ -1,6 +1,7 @@
import React from "react";
import { Button as PaperButton, ButtonProps } from "react-native-paper";
// import { useTheme } from "../hooks/theme-context";
import type { TextButtonProps } from '@packages/shared';
import { theme, useTheme } from "common-ui";
import { TouchableOpacity } from "react-native";
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
interface MyTextButtonProps extends Omit<Props, "children"> {
text: string;
}
interface MyTextButtonProps extends Omit<Props, "children">, TextButtonProps {}
export function MyTextButton({
variant = "blue",

View file

@ -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 type { ConfirmationDialogProps, DialogProps } from '@packages/shared';
import MyText from './text';
import MyButton from './button';
import tw from "../lib/tailwind";
interface DialogProps {
open: boolean;
onClose: () => void;
children: ReactNode;
enableDismiss?: boolean;
}
const SCREEN_HEIGHT = Dimensions.get('window').height;
export const BottomDialog: React.FC<DialogProps> = ({ open, onClose, children, enableDismiss = true }) => {
@ -191,18 +185,6 @@ const commentStyles = StyleSheet.create({
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) => {
const {
open,

View file

@ -2,11 +2,9 @@ import tw from "../lib/tailwind";
import React from "react";
import { Text, View } from "react-native";
import { Dropdown } from "react-native-element-dropdown";
import type { DropdownOption } from '@packages/shared';
export interface DropdownOption {
label: string;
value: string | number;
}
export type { DropdownOption } from '@packages/shared';
interface Props {
label: string;

View file

@ -1,13 +1,9 @@
import React from 'react';
import { View, ActivityIndicator, Modal, TouchableOpacity } from 'react-native';
import type { LoadingDialogProps } from '@packages/shared';
import tw from '../lib/tailwind';
import MyText from './text';
interface LoadingDialogProps {
open: boolean;
message?: string;
}
export const LoadingDialog: React.FC<LoadingDialogProps> = ({
open,
message = 'Loading...'

View file

@ -1,18 +1,10 @@
import React from 'react';
import { View, TouchableOpacity, Text } from 'react-native';
import { MaterialIcons } from '@expo/vector-icons';
import type { QuantifierProps } from '@packages/shared';
import tw from '../lib/tailwind';
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> = ({
value,
setValue,

View file

@ -1,21 +1,15 @@
import React, { useEffect, useState } from 'react'
import type { ConfirmationDialogProps, DialogProps } from '@packages/shared'
import { p } from './my-text'
import { MyButton } from './my-button'
import { cn } from '../lib/utils'
interface BottomDialogProps {
open: boolean
onClose: () => void
children: React.ReactNode
enableDismiss?: boolean
}
export function BottomDialog({
open,
onClose,
children,
enableDismiss = true,
}: BottomDialogProps) {
}: DialogProps) {
const [visible, setVisible] = useState(false)
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({
open,
positiveAction,

View file

@ -1,11 +1,9 @@
import React from 'react'
import { cn } from '../lib/utils'
import { ChevronDown } from 'lucide-react'
import type { DropdownOption } from '@packages/shared'
export interface DropdownOption {
label: string
value: string | number
}
export type { DropdownOption } from '@packages/shared'
interface DropdownProps {
label: string

View file

@ -1,24 +1,10 @@
import React, { useRef } from 'react'
import { cn } from '../lib/utils'
import { Plus, X } from 'lucide-react'
import type { ImageUploaderNeoItem, ImageUploaderNeoPayload, ImageUploaderNeoProps } from '@packages/shared'
import { div } from './my-touchable-opacity'
export interface ImageUploaderNeoItem {
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
}
export type { ImageUploaderNeoItem, ImageUploaderNeoPayload } from '@packages/shared'
function usePickImage({
multiple,

View file

@ -1,15 +1,8 @@
import React from 'react'
import { cn } from '../lib/utils'
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({
images,

View file

@ -1,9 +1,5 @@
import React from 'react'
interface LoadingDialogProps {
open: boolean
message?: string
}
import type { LoadingDialogProps } from '@packages/shared'
export function LoadingDialog({ open, message = 'Loading...' }: LoadingDialogProps) {
if (!open) return null

View file

@ -1,4 +1,5 @@
import React from 'react'
import type { TextButtonProps } from '@packages/shared'
import { cn } from '../lib/utils'
interface MyButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
@ -43,9 +44,7 @@ export function MyButton({
)
}
interface pButtonProps extends Omit<MyButtonProps, 'children'> {
text: string
}
interface pButtonProps extends Omit<MyButtonProps, 'children'>, TextButtonProps {}
export function pButton({ text, ...props }: pButtonProps) {
return <MyButton {...props}>{text}</MyButton>

View file

@ -1,16 +1,9 @@
import React from 'react'
import { cn } from '../lib/utils'
import { p } from './my-text'
import type { QuantifierProps } from '@packages/shared'
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({
value,

View file

@ -14,7 +14,9 @@
"outDir": "./dist",
"rootDir": "./src",
"paths": {
"@/*": ["./src/*"]
"@/*": ["./src/*"],
"@packages/shared": ["../shared"],
"@packages/shared/*": ["../shared/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.tsx"],