Initial commit: code as received (Create/Anything export)
Insole-production time tracker exported from the Create/Anything AI platform. Baseline snapshot before any reverse-engineering or cleanup. - apps/mobile: Expo Router app (iOS/Android/web), the only workspace - publisher/: standalone OpenNext/AWS deploy tooling for the web side - Backend (/api/tasks, /api/logs + DB) lives remotely, not in this repo
This commit is contained in:
424
apps/mobile/polyfills/native/google-mobile-ads.native.tsx
Normal file
424
apps/mobile/polyfills/native/google-mobile-ads.native.tsx
Normal file
@@ -0,0 +1,424 @@
|
||||
import type React from "react";
|
||||
import { Text, View, type ViewStyle } from "react-native";
|
||||
|
||||
// Stub for react-native-google-mobile-ads on web.
|
||||
// Ads are native-only; these render visual placeholders so users can preview
|
||||
// their layouts in Expo Go without the native module.
|
||||
|
||||
export const BannerAdSize = {
|
||||
BANNER: "BANNER",
|
||||
FULL_BANNER: "FULL_BANNER",
|
||||
LARGE_BANNER: "LARGE_BANNER",
|
||||
LEADERBOARD: "LEADERBOARD",
|
||||
MEDIUM_RECTANGLE: "MEDIUM_RECTANGLE",
|
||||
ADAPTIVE_BANNER: "ADAPTIVE_BANNER",
|
||||
ANCHORED_ADAPTIVE_BANNER: "ANCHORED_ADAPTIVE_BANNER",
|
||||
INLINE_ADAPTIVE_BANNER: "INLINE_ADAPTIVE_BANNER",
|
||||
WIDE_SKYSCRAPER: "WIDE_SKYSCRAPER",
|
||||
};
|
||||
|
||||
export const AdEventType = {
|
||||
LOADED: "loaded",
|
||||
ERROR: "error",
|
||||
OPENED: "opened",
|
||||
CLICKED: "clicked",
|
||||
CLOSED: "closed",
|
||||
};
|
||||
|
||||
export const RewardedAdEventType = {
|
||||
LOADED: "loaded",
|
||||
EARNED_REWARD: "earned_reward",
|
||||
};
|
||||
|
||||
export const AdsConsentStatus = {
|
||||
UNKNOWN: 0,
|
||||
REQUIRED: 1,
|
||||
NOT_REQUIRED: 2,
|
||||
OBTAINED: 3,
|
||||
};
|
||||
|
||||
export const AdsConsentDebugGeography = {
|
||||
DISABLED: 0,
|
||||
EEA: 1,
|
||||
NOT_EEA: 2,
|
||||
};
|
||||
|
||||
export const TestIds = {
|
||||
BANNER: "ca-app-pub-3940256099942544/6300978111",
|
||||
GAM_BANNER: "ca-app-pub-3940256099942544/6300978111",
|
||||
INTERSTITIAL: "ca-app-pub-3940256099942544/1033173712",
|
||||
GAM_INTERSTITIAL: "ca-app-pub-3940256099942544/1033173712",
|
||||
REWARDED: "ca-app-pub-3940256099942544/5224354917",
|
||||
REWARDED_INTERSTITIAL: "ca-app-pub-3940256099942544/5354046379",
|
||||
APP_OPEN: "ca-app-pub-3940256099942544/3419835294",
|
||||
NATIVE: "ca-app-pub-3940256099942544/2247696110",
|
||||
NATIVE_VIDEO: "ca-app-pub-3940256099942544/1044960115",
|
||||
};
|
||||
|
||||
const PLACEHOLDER_BG = "#f5f5f5";
|
||||
const PLACEHOLDER_BORDER = "#e0e0e0";
|
||||
const PLACEHOLDER_TEXT = "#999999";
|
||||
const AD_LABEL_BG = "#fbbc04";
|
||||
const AD_LABEL_TEXT = "#1a1a1a";
|
||||
|
||||
const AdLabel = () => (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: AD_LABEL_BG,
|
||||
paddingHorizontal: 4,
|
||||
paddingVertical: 1,
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 9,
|
||||
fontWeight: "700",
|
||||
color: AD_LABEL_TEXT,
|
||||
lineHeight: 11,
|
||||
}}
|
||||
>
|
||||
Ad
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
const getBannerStyle = (size: string | undefined): ViewStyle => {
|
||||
switch (size) {
|
||||
case "FULL_BANNER":
|
||||
return { width: 468, height: 60 };
|
||||
case "LARGE_BANNER":
|
||||
return { width: 320, height: 100 };
|
||||
case "LEADERBOARD":
|
||||
return { width: 728, height: 90 };
|
||||
case "MEDIUM_RECTANGLE":
|
||||
return { width: 300, height: 250 };
|
||||
case "WIDE_SKYSCRAPER":
|
||||
return { width: 160, height: 600 };
|
||||
case "ADAPTIVE_BANNER":
|
||||
case "ANCHORED_ADAPTIVE_BANNER":
|
||||
return { width: "100%", height: 50 };
|
||||
case "INLINE_ADAPTIVE_BANNER":
|
||||
return { width: "100%", height: 100 };
|
||||
default:
|
||||
return { width: 320, height: 50 };
|
||||
}
|
||||
};
|
||||
|
||||
type BannerAdProps = {
|
||||
size?: string;
|
||||
unitId?: string;
|
||||
onAdLoaded?: () => void;
|
||||
onAdFailedToLoad?: (error: unknown) => void;
|
||||
onAdOpened?: () => void;
|
||||
onAdClosed?: () => void;
|
||||
};
|
||||
|
||||
const BannerPlaceholder = ({
|
||||
size,
|
||||
label,
|
||||
}: { size?: string; label: string }) => {
|
||||
const dims = getBannerStyle(size);
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
...dims,
|
||||
backgroundColor: PLACEHOLDER_BG,
|
||||
borderWidth: 1,
|
||||
borderColor: PLACEHOLDER_BORDER,
|
||||
borderRadius: 4,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
flexDirection: "row",
|
||||
gap: 6,
|
||||
}}
|
||||
>
|
||||
<AdLabel />
|
||||
<Text style={{ color: PLACEHOLDER_TEXT, fontSize: 12 }}>{label}</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export const BannerAd = ({ size }: BannerAdProps) => (
|
||||
<BannerPlaceholder size={size} label="Banner Ad" />
|
||||
);
|
||||
|
||||
export const GAMBannerAd = ({ size }: BannerAdProps) => (
|
||||
<BannerPlaceholder size={size} label="Ad Manager Banner" />
|
||||
);
|
||||
|
||||
type NativeAdViewProps = {
|
||||
children?: React.ReactNode;
|
||||
nativeAd?: unknown;
|
||||
style?: ViewStyle | ViewStyle[];
|
||||
};
|
||||
|
||||
const DefaultNativeAdContent = () => (
|
||||
<View>
|
||||
<View
|
||||
style={{ flexDirection: "row", alignItems: "center", marginBottom: 10 }}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 8,
|
||||
backgroundColor: PLACEHOLDER_BORDER,
|
||||
marginRight: 10,
|
||||
}}
|
||||
/>
|
||||
<View style={{ flex: 1 }}>
|
||||
<View
|
||||
style={{
|
||||
height: 12,
|
||||
backgroundColor: PLACEHOLDER_BORDER,
|
||||
borderRadius: 4,
|
||||
marginBottom: 6,
|
||||
width: "70%",
|
||||
}}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
height: 10,
|
||||
backgroundColor: "#ececec",
|
||||
borderRadius: 4,
|
||||
width: "40%",
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
height: 140,
|
||||
backgroundColor: PLACEHOLDER_BORDER,
|
||||
borderRadius: 4,
|
||||
marginBottom: 10,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: PLACEHOLDER_TEXT, fontSize: 12 }}>
|
||||
Native Ad Media
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
height: 10,
|
||||
backgroundColor: "#ececec",
|
||||
borderRadius: 4,
|
||||
marginBottom: 6,
|
||||
}}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
height: 10,
|
||||
backgroundColor: "#ececec",
|
||||
borderRadius: 4,
|
||||
width: "80%",
|
||||
marginBottom: 12,
|
||||
}}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
alignSelf: "flex-start",
|
||||
backgroundColor: "#1a73e8",
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 4,
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: "#fff", fontSize: 12, fontWeight: "600" }}>
|
||||
Install
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
export const NativeAdView = ({ children, style }: NativeAdViewProps) => (
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
backgroundColor: PLACEHOLDER_BG,
|
||||
borderWidth: 1,
|
||||
borderColor: PLACEHOLDER_BORDER,
|
||||
borderRadius: 8,
|
||||
padding: 12,
|
||||
position: "relative",
|
||||
},
|
||||
style as ViewStyle,
|
||||
]}
|
||||
>
|
||||
<View style={{ position: "absolute", top: 8, right: 8, zIndex: 1 }}>
|
||||
<AdLabel />
|
||||
</View>
|
||||
{children ?? <DefaultNativeAdContent />}
|
||||
</View>
|
||||
);
|
||||
|
||||
export const NativeAsset = ({
|
||||
children,
|
||||
style,
|
||||
}: {
|
||||
children?: React.ReactNode;
|
||||
assetType?: string;
|
||||
style?: ViewStyle | ViewStyle[];
|
||||
}) => <View style={style as ViewStyle}>{children}</View>;
|
||||
|
||||
export const NativeMediaView = ({
|
||||
style,
|
||||
}: { style?: ViewStyle | ViewStyle[] }) => (
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
height: 180,
|
||||
backgroundColor: PLACEHOLDER_BORDER,
|
||||
borderRadius: 4,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
style as ViewStyle,
|
||||
]}
|
||||
>
|
||||
<Text style={{ color: PLACEHOLDER_TEXT, fontSize: 12 }}>
|
||||
Ad Media (native only)
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
export const NativeAd = {
|
||||
createForAdRequest: async (_unitId?: string, _requestOptions?: unknown) => ({
|
||||
headline: "Sample Ad Headline",
|
||||
body: "Native ads only render on a real device.",
|
||||
advertiser: "Sample Advertiser",
|
||||
callToAction: "Install",
|
||||
icon: null,
|
||||
images: [],
|
||||
starRating: null,
|
||||
store: null,
|
||||
price: null,
|
||||
addAdEventListener: () => () => {},
|
||||
removeAllListeners: () => {},
|
||||
destroy: () => {},
|
||||
}),
|
||||
};
|
||||
|
||||
const createFullScreenAdStub = () => ({
|
||||
loaded: false,
|
||||
load: () => {},
|
||||
show: () => Promise.resolve(),
|
||||
addAdEventListener: () => () => {},
|
||||
addAdEventsListener: () => () => {},
|
||||
removeAllListeners: () => {},
|
||||
});
|
||||
|
||||
export const InterstitialAd = {
|
||||
createForAdRequest: () => createFullScreenAdStub(),
|
||||
};
|
||||
|
||||
export const RewardedAd = {
|
||||
createForAdRequest: () => createFullScreenAdStub(),
|
||||
};
|
||||
|
||||
export const RewardedInterstitialAd = {
|
||||
createForAdRequest: () => createFullScreenAdStub(),
|
||||
};
|
||||
|
||||
export const AppOpenAd = {
|
||||
createForAdRequest: () => createFullScreenAdStub(),
|
||||
};
|
||||
|
||||
export const GAMInterstitialAd = {
|
||||
createForAdRequest: () => createFullScreenAdStub(),
|
||||
};
|
||||
|
||||
export const GAMRewardedAd = {
|
||||
createForAdRequest: () => createFullScreenAdStub(),
|
||||
};
|
||||
|
||||
export const GAMRewardedInterstitialAd = {
|
||||
createForAdRequest: () => createFullScreenAdStub(),
|
||||
};
|
||||
|
||||
const baseHookResult = {
|
||||
isLoaded: false,
|
||||
isOpened: false,
|
||||
isClicked: false,
|
||||
isClosed: false,
|
||||
error: null as unknown,
|
||||
load: () => {},
|
||||
show: () => {},
|
||||
};
|
||||
|
||||
export const useInterstitialAd = () => ({ ...baseHookResult });
|
||||
export const useAppOpenAd = () => ({ ...baseHookResult });
|
||||
export const useRewardedAd = () => ({
|
||||
...baseHookResult,
|
||||
isEarnedReward: false,
|
||||
reward: null,
|
||||
});
|
||||
export const useRewardedInterstitialAd = () => ({
|
||||
...baseHookResult,
|
||||
isEarnedReward: false,
|
||||
reward: null,
|
||||
});
|
||||
|
||||
export const AdsConsent = {
|
||||
requestInfoUpdate: async () => ({
|
||||
status: AdsConsentStatus.NOT_REQUIRED,
|
||||
isConsentFormAvailable: false,
|
||||
}),
|
||||
showForm: async () => ({ status: AdsConsentStatus.OBTAINED }),
|
||||
loadAndShowConsentFormIfRequired: async () => ({
|
||||
status: AdsConsentStatus.NOT_REQUIRED,
|
||||
}),
|
||||
gatherConsent: async () => ({ status: AdsConsentStatus.NOT_REQUIRED }),
|
||||
reset: () => {},
|
||||
getConsentInfo: async () => ({
|
||||
status: AdsConsentStatus.NOT_REQUIRED,
|
||||
canRequestAds: true,
|
||||
isConsentFormAvailable: false,
|
||||
privacyOptionsRequirementStatus: "NOT_REQUIRED",
|
||||
}),
|
||||
getUserChoices: async () => ({}),
|
||||
getTCString: async () => "",
|
||||
getGdprApplies: async () => false,
|
||||
getPurposeConsents: async () => "",
|
||||
getPurposeLegitimateInterests: async () => "",
|
||||
};
|
||||
|
||||
const mobileAdsInstance = {
|
||||
initialize: async () => [],
|
||||
setRequestConfiguration: async () => {},
|
||||
openAdInspector: async () => {},
|
||||
openDebugMenu: () => {},
|
||||
setAppMuted: () => {},
|
||||
setAppVolume: () => {},
|
||||
};
|
||||
|
||||
const mobileAds = () => mobileAdsInstance;
|
||||
|
||||
const defaultExport = Object.assign(mobileAds, {
|
||||
BannerAd,
|
||||
GAMBannerAd,
|
||||
BannerAdSize,
|
||||
InterstitialAd,
|
||||
RewardedAd,
|
||||
RewardedInterstitialAd,
|
||||
AppOpenAd,
|
||||
GAMInterstitialAd,
|
||||
GAMRewardedAd,
|
||||
GAMRewardedInterstitialAd,
|
||||
NativeAd,
|
||||
NativeAdView,
|
||||
NativeAsset,
|
||||
NativeMediaView,
|
||||
AdEventType,
|
||||
RewardedAdEventType,
|
||||
AdsConsent,
|
||||
AdsConsentStatus,
|
||||
AdsConsentDebugGeography,
|
||||
TestIds,
|
||||
});
|
||||
|
||||
export { mobileAds };
|
||||
export default defaultExport;
|
||||
179
apps/mobile/polyfills/native/react-native-purchases.native.tsx
Normal file
179
apps/mobile/polyfills/native/react-native-purchases.native.tsx
Normal file
@@ -0,0 +1,179 @@
|
||||
// Expo Go-safe stub for react-native-purchases.
|
||||
//
|
||||
// The real package's index pulls in @revenuecat/purchases-js-hybrid-mappings
|
||||
// (a ~15k-line Svelte UMD bundle of browser-only code) which throws on
|
||||
// module evaluation under Hermes in Expo Go preview. Even importing it from
|
||||
// a hook that's never called crashes _layout.tsx, which makes expo-router
|
||||
// silently swallow the throw and warn "Route \"./_layout.tsx\" is missing
|
||||
// the required default export" — leaving the app stuck on a black/splash
|
||||
// screen forever.
|
||||
//
|
||||
// This polyfill is wired up in metro.config.js for native platforms only
|
||||
// when EXPO_PUBLIC_CREATE_ENV !== 'PRODUCTION'. Production EAS builds keep
|
||||
// the real SDK, so paid users hit RevenueCat as normal.
|
||||
|
||||
const noopAsync = async () => undefined;
|
||||
|
||||
const LOG_LEVEL = {
|
||||
VERBOSE: "VERBOSE",
|
||||
DEBUG: "DEBUG",
|
||||
INFO: "INFO",
|
||||
WARN: "WARN",
|
||||
ERROR: "ERROR",
|
||||
SILENT: "SILENT",
|
||||
};
|
||||
|
||||
const PRODUCT_CATEGORY = {
|
||||
SUBSCRIPTION: "SUBSCRIPTION",
|
||||
NON_SUBSCRIPTION: "NON_SUBSCRIPTION",
|
||||
UNKNOWN: "UNKNOWN",
|
||||
};
|
||||
|
||||
const PURCHASE_TYPE = {
|
||||
INAPP: "inapp",
|
||||
SUBS: "subs",
|
||||
};
|
||||
|
||||
const PURCHASES_ARE_COMPLETED_BY_TYPE = {
|
||||
REVENUECAT: "REVENUECAT",
|
||||
MY_APP: "MY_APP",
|
||||
};
|
||||
|
||||
const REFUND_REQUEST_STATUS = {
|
||||
SUCCESS: "SUCCESS",
|
||||
USER_CANCELLED: "USER_CANCELLED",
|
||||
ERROR: "ERROR",
|
||||
};
|
||||
|
||||
const BILLING_FEATURE = {
|
||||
SUBSCRIPTIONS: "SUBSCRIPTIONS",
|
||||
SUBSCRIPTIONS_UPDATE: "SUBSCRIPTIONS_UPDATE",
|
||||
IN_APP_MESSAGING: "IN_APP_MESSAGING",
|
||||
PRICE_CHANGE_CONFIRMATION: "PRICE_CHANGE_CONFIRMATION",
|
||||
};
|
||||
|
||||
const STOREKIT_VERSION = {
|
||||
DEFAULT: "DEFAULT",
|
||||
STOREKIT_1: "STOREKIT_1",
|
||||
STOREKIT_2: "STOREKIT_2",
|
||||
};
|
||||
|
||||
const Purchases = {
|
||||
configure: noopAsync,
|
||||
setLogLevel: () => {},
|
||||
setLogHandler: () => {},
|
||||
addCustomerInfoUpdateListener: () => () => {},
|
||||
removeCustomerInfoUpdateListener: () => {},
|
||||
getOfferings: async () => ({ current: null, all: {} }),
|
||||
getProducts: async () => [],
|
||||
getCustomerInfo: async () => ({
|
||||
entitlements: { active: {}, all: {} },
|
||||
activeSubscriptions: [],
|
||||
allPurchasedProductIdentifiers: [],
|
||||
latestExpirationDate: null,
|
||||
firstSeen: new Date().toISOString(),
|
||||
originalAppUserId: "expo-go-preview",
|
||||
requestDate: new Date().toISOString(),
|
||||
allExpirationDates: {},
|
||||
allPurchaseDates: {},
|
||||
originalApplicationVersion: null,
|
||||
originalPurchaseDate: null,
|
||||
managementURL: null,
|
||||
nonSubscriptionTransactions: [],
|
||||
}),
|
||||
purchasePackage: async () => {
|
||||
const error: Error & { userCancelled?: boolean } = new Error(
|
||||
"Purchases not available in Expo Go preview. Build a development build or run in TestFlight to test purchases.",
|
||||
);
|
||||
error.userCancelled = true;
|
||||
throw error;
|
||||
},
|
||||
purchaseProduct: async () => {
|
||||
const error: Error & { userCancelled?: boolean } = new Error(
|
||||
"Purchases not available in Expo Go preview.",
|
||||
);
|
||||
error.userCancelled = true;
|
||||
throw error;
|
||||
},
|
||||
restorePurchases: async () => ({
|
||||
entitlements: { active: {}, all: {} },
|
||||
activeSubscriptions: [],
|
||||
allPurchasedProductIdentifiers: [],
|
||||
latestExpirationDate: null,
|
||||
firstSeen: new Date().toISOString(),
|
||||
originalAppUserId: "expo-go-preview",
|
||||
requestDate: new Date().toISOString(),
|
||||
allExpirationDates: {},
|
||||
allPurchaseDates: {},
|
||||
originalApplicationVersion: null,
|
||||
originalPurchaseDate: null,
|
||||
managementURL: null,
|
||||
nonSubscriptionTransactions: [],
|
||||
}),
|
||||
logIn: async (appUserID: string) => ({
|
||||
customerInfo: {
|
||||
entitlements: { active: {}, all: {} },
|
||||
activeSubscriptions: [],
|
||||
originalAppUserId: appUserID,
|
||||
},
|
||||
created: false,
|
||||
}),
|
||||
logOut: async () => ({
|
||||
entitlements: { active: {}, all: {} },
|
||||
activeSubscriptions: [],
|
||||
originalAppUserId: "expo-go-preview",
|
||||
}),
|
||||
setAttributes: noopAsync,
|
||||
setEmail: noopAsync,
|
||||
setDisplayName: noopAsync,
|
||||
setPhoneNumber: noopAsync,
|
||||
setPushToken: noopAsync,
|
||||
setAdjustID: noopAsync,
|
||||
setAppsflyerID: noopAsync,
|
||||
setFBAnonymousID: noopAsync,
|
||||
setMparticleID: noopAsync,
|
||||
setOnesignalID: noopAsync,
|
||||
setAirshipChannelID: noopAsync,
|
||||
setMediaSource: noopAsync,
|
||||
setCampaign: noopAsync,
|
||||
setAdGroup: noopAsync,
|
||||
setAd: noopAsync,
|
||||
setKeyword: noopAsync,
|
||||
setCreative: noopAsync,
|
||||
collectDeviceIdentifiers: () => {},
|
||||
syncPurchases: noopAsync,
|
||||
syncAttributesAndOfferingsIfNeeded: async () => ({ current: null, all: {} }),
|
||||
enableAdServicesAttributionTokenCollection: () => {},
|
||||
isAnonymous: async () => true,
|
||||
checkTrialOrIntroductoryPriceEligibility: async () => ({}),
|
||||
invalidateCustomerInfoCache: () => {},
|
||||
presentCodeRedemptionSheet: () => {},
|
||||
beginRefundRequestForActiveEntitlement: async () => REFUND_REQUEST_STATUS.ERROR,
|
||||
beginRefundRequestForEntitlement: async () => REFUND_REQUEST_STATUS.ERROR,
|
||||
beginRefundRequestForProduct: async () => REFUND_REQUEST_STATUS.ERROR,
|
||||
showInAppMessages: noopAsync,
|
||||
getPromotionalOffer: async () => null,
|
||||
purchasePromotionalOffer: async () => {
|
||||
const error: Error & { userCancelled?: boolean } = new Error(
|
||||
"Purchases not available in Expo Go preview.",
|
||||
);
|
||||
error.userCancelled = true;
|
||||
throw error;
|
||||
},
|
||||
canMakePayments: async () => false,
|
||||
getAppUserID: async () => "expo-go-preview",
|
||||
close: () => {},
|
||||
configureInUITestMode: () => {},
|
||||
};
|
||||
|
||||
export {
|
||||
LOG_LEVEL,
|
||||
PRODUCT_CATEGORY,
|
||||
PURCHASE_TYPE,
|
||||
PURCHASES_ARE_COMPLETED_BY_TYPE,
|
||||
REFUND_REQUEST_STATUS,
|
||||
BILLING_FEATURE,
|
||||
STOREKIT_VERSION,
|
||||
};
|
||||
|
||||
export default Purchases;
|
||||
16
apps/mobile/polyfills/native/textinput.native.tsx
Normal file
16
apps/mobile/polyfills/native/textinput.native.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import { TextInput as RNTextInput, type TextInputProps } from 'react-native';
|
||||
|
||||
const TextInput = React.forwardRef<RNTextInput, TextInputProps>((props, ref) => {
|
||||
return (
|
||||
<RNTextInput
|
||||
ref={ref}
|
||||
placeholderTextColor={props.placeholderTextColor || 'black'}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
TextInput.displayName = 'TextInput';
|
||||
|
||||
export default TextInput;
|
||||
Reference in New Issue
Block a user