diff --git a/payment_sdk/babel.config.js b/payment_sdk/babel.config.js index 39ed742..af947d9 100644 --- a/payment_sdk/babel.config.js +++ b/payment_sdk/babel.config.js @@ -16,7 +16,8 @@ module.exports = { "@assets": "./src/assets", "@components": "./src/components", "@util": "./src/util", - "@context": "./src/context" + "@context": "./src/context", + "@theme": "./src/theme", } } ] diff --git a/payment_sdk/eslint.config.mjs b/payment_sdk/eslint.config.mjs index b0669a5..4a508b7 100644 --- a/payment_sdk/eslint.config.mjs +++ b/payment_sdk/eslint.config.mjs @@ -78,6 +78,11 @@ export default [ pattern: "@assets/**", group: "internal", position: "after" + }, + { + pattern: "@theme/**", + group: "internal", + position: "after" } ], pathGroupsExcludedImportTypes: ["react", "react-native"], diff --git a/payment_sdk/jest.config.js b/payment_sdk/jest.config.js index eff5142..f38319e 100644 --- a/payment_sdk/jest.config.js +++ b/payment_sdk/jest.config.js @@ -12,4 +12,8 @@ module.exports = { setupFilesAfterEnv: ["@testing-library/jest-native/extend-expect"], transformIgnorePatterns: [], moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: "/src/" }), + testPathIgnorePatterns: [ + "/node_modules/", + "/dist/" + ] }; \ No newline at end of file diff --git a/payment_sdk/setupTests.js b/payment_sdk/setupTests.js index 6694fb1..047d75b 100644 --- a/payment_sdk/setupTests.js +++ b/payment_sdk/setupTests.js @@ -13,3 +13,26 @@ global.__DEV__ = process.env.NODE_ENV !== "production"; // jest.mock('react-native-worklets-core', () => ({ // Worklets: jest.fn(), // })); + +jest.mock('react-i18next', () => ({ + useTranslation: () => { + return { + t: (str) => str, + i18n: { + changeLanguage: () => new Promise(() => {}), + }, + }; + }, + initReactI18next: { + type: '3rdParty', + init: () => {}, + }, + })); + + jest.mock('./src/context/ThemeContext', () => ({ + ThemeProvider: ({ children }) => children, + useTheme: () => ({ + mode: 'light', + toggleMode: jest.fn(), + }), + })); \ No newline at end of file diff --git a/payment_sdk/src/__tests__/CardInputGroup.test.tsx b/payment_sdk/src/__tests__/CardInputGroup.test.tsx index f66f2c0..1b99fc5 100644 --- a/payment_sdk/src/__tests__/CardInputGroup.test.tsx +++ b/payment_sdk/src/__tests__/CardInputGroup.test.tsx @@ -6,6 +6,8 @@ import { Actions, DispatchContext, StateContext } from "../context/state"; import CardInputGroup from "../components/CardInputGroup"; import { isCardNumberValid, validateCardExpiry } from "../util/validator"; +import { ThemeProvider } from "../context/ThemeContext"; + // Mock helper functions jest.mock("../util/helpers", () => ({ formatCreditCardNumber: jest.fn(), @@ -38,11 +40,13 @@ const mockDispatch = jest.fn(); describe("CardInputGroup Component", () => { const renderWithContext = (component: ReactNode) => { return render( - - - {component} - - + + + + {component} + + + ); }; @@ -55,7 +59,7 @@ describe("CardInputGroup Component", () => { const { getByTestId } = renderWithContext( {}} + resetError={(data: string) => { }} /> ); @@ -77,7 +81,7 @@ describe("CardInputGroup Component", () => { const { getByTestId } = renderWithContext( {}} + resetError={(data: string) => { }} /> ); @@ -102,7 +106,7 @@ describe("CardInputGroup Component", () => { const { getByTestId } = renderWithContext( {}} + resetError={(data: string) => { }} /> ); @@ -125,7 +129,7 @@ describe("CardInputGroup Component", () => { const { getByTestId } = renderWithContext( {}} + resetError={(data: string) => { }} /> ); @@ -148,7 +152,7 @@ describe("CardInputGroup Component", () => { const { getByTestId } = renderWithContext( {}} + resetError={(data: string) => { }} /> ); diff --git a/payment_sdk/src/assets/images/close_dm.png b/payment_sdk/src/assets/images/close_dm.png new file mode 100644 index 0000000..c51eaa7 Binary files /dev/null and b/payment_sdk/src/assets/images/close_dm.png differ diff --git a/payment_sdk/src/assets/images/close_dm@2x.png b/payment_sdk/src/assets/images/close_dm@2x.png new file mode 100644 index 0000000..2577bcf Binary files /dev/null and b/payment_sdk/src/assets/images/close_dm@2x.png differ diff --git a/payment_sdk/src/assets/images/close_dm@3x.png b/payment_sdk/src/assets/images/close_dm@3x.png new file mode 100644 index 0000000..b419eda Binary files /dev/null and b/payment_sdk/src/assets/images/close_dm@3x.png differ diff --git a/payment_sdk/src/assets/images/footer_image2_dm.png b/payment_sdk/src/assets/images/footer_image2_dm.png new file mode 100644 index 0000000..b72b75c Binary files /dev/null and b/payment_sdk/src/assets/images/footer_image2_dm.png differ diff --git a/payment_sdk/src/assets/images/footer_image2_dm@2x.png b/payment_sdk/src/assets/images/footer_image2_dm@2x.png new file mode 100644 index 0000000..3ba46b2 Binary files /dev/null and b/payment_sdk/src/assets/images/footer_image2_dm@2x.png differ diff --git a/payment_sdk/src/assets/images/footer_image2_dm@3x.png b/payment_sdk/src/assets/images/footer_image2_dm@3x.png new file mode 100644 index 0000000..0394fb0 Binary files /dev/null and b/payment_sdk/src/assets/images/footer_image2_dm@3x.png differ diff --git a/payment_sdk/src/components/CardInputGroup.tsx b/payment_sdk/src/components/CardInputGroup.tsx index d2c12cb..6e19d2b 100644 --- a/payment_sdk/src/components/CardInputGroup.tsx +++ b/payment_sdk/src/components/CardInputGroup.tsx @@ -22,10 +22,13 @@ import { formatCreditCardNumber, formatExpiry, } from "@util/helpers"; -import { PaymentType, sessionShowPaymentMethodType } from "@util/types"; +import { PaymentType, sessionShowPaymentMethodType, ThemeSchemeType } from "@util/types"; import { isCardNumberValid, validateCardExpiry } from "@util/validator"; // import CardScanner from "./CardScanner"; +import { resizeFonts, responsiveScale } from "@theme/scalling"; +import { useCurrentTheme } from "@theme/useCurrentTheme"; + import Input from "./Input"; import KomojuText from "./KomojuText"; // import ScanCardButton from "./ScanCardButton"; @@ -39,13 +42,15 @@ type Props = { resetError: (type: string) => void; }; -const CARD_WIDTH = 26; -const CARD_HEIGHT = 30; +const CARD_WIDTH = responsiveScale(30); +const CARD_HEIGHT = responsiveScale(26); const CardInputGroup = ({ inputErrors, resetError }: Props) => { const dispatch = useContext(DispatchContext); const [cardType, setCardType] = useState(null); // const [toggleScanCard, setToggleScanCard] = useState(false); + const theme = useCurrentTheme(); + const styles = getStyles(theme); const { cardCVV, cardNumber, cardExpiredDate, paymentMethods } = useContext(StateContext); @@ -189,71 +194,75 @@ const CardInputGroup = ({ inputErrors, resetError }: Props) => { export default memo(CardInputGroup); -const styles = StyleSheet.create({ - parentContainer: { - margin: 16, - marginBottom: 24, - flexGrow: 0, - minHeight: 130, - }, - label: { - fontSize: 16, - marginBottom: 8, - color: "#172E44", - }, - container: { - flex: 1, - flexDirection: "column", - maxHeight: 120, - }, - cardNumberRow: { - flex: 1, - height: 60, - }, - splitRow: { - flex: 1, - flexDirection: "row", - height: 60, - }, - itemRow: { - flex: 1, - }, - numberInputStyle: { - borderBottomLeftRadius: 0, - borderBottomRightRadius: 0, - }, - expiryInputStyle: { - borderTopLeftRadius: 0, - borderTopRightRadius: 0, - borderBottomRightRadius: 0, - }, - cvvInputStyle: { - borderTopLeftRadius: 0, - borderTopRightRadius: 0, - borderBottomLeftRadius: 0, - }, - titleScanRow: { - flexDirection: "row", - justifyContent: "space-between", - alignItems: "center", - marginBottom: 8, - }, - cardContainer: { - flexDirection: "row", - justifyContent: "space-between", - alignItems: "center", - position: "absolute", - top: 9, - right: 0, - marginRight: 8, - }, - cardImage: { - width: 26, - marginRight: 8, - }, - scanContainer: { - width: "100%", - justifyContent: "center", - alignItems: "center", - }, -}); +const getStyles = (theme: ThemeSchemeType) => { + return StyleSheet.create({ + parentContainer: { + margin: responsiveScale(16), + marginBottom: responsiveScale(24), + flexGrow: 0, + minHeight: responsiveScale(130), + }, + label: { + fontSize: resizeFonts(16), + marginBottom: responsiveScale(8), + color: theme.TEXT_COLOR, + }, + container: { + flex: 1, + flexDirection: "column", + maxHeight: responsiveScale(120), + }, + cardNumberRow: { + flex: 1, + marginBottom: -responsiveScale(1), + height: responsiveScale(60), + }, + splitRow: { + flex: 1, + flexDirection: "row", + height: responsiveScale(60), + }, + itemRow: { + flex: 1, + }, + numberInputStyle: { + borderBottomLeftRadius: 0, + borderBottomRightRadius: 0, + }, + expiryInputStyle: { + borderTopLeftRadius: 0, + borderTopRightRadius: 0, + borderBottomRightRadius: 0, + }, + cvvInputStyle: { + borderTopLeftRadius: 0, + borderTopRightRadius: 0, + borderBottomLeftRadius: 0, + }, + titleScanRow: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + marginBottom: responsiveScale(8), + }, + cardContainer: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + position: "absolute", + top: responsiveScale(9), + right: 0, + marginRight: responsiveScale(8), + }, + cardImage: { + width: responsiveScale(26), + marginRight: responsiveScale(8), + }, + scanContainer: { + width: "100%", + justifyContent: "center", + alignItems: "center", + }, + }); + +} \ No newline at end of file diff --git a/payment_sdk/src/components/Input.tsx b/payment_sdk/src/components/Input.tsx index 66067b7..3e3f6b3 100644 --- a/payment_sdk/src/components/Input.tsx +++ b/payment_sdk/src/components/Input.tsx @@ -12,6 +12,11 @@ import { import { useTranslation } from "react-i18next"; +import { ThemeSchemeType } from "@util/types"; + +import { resizeFonts, responsiveScale } from "@theme/scalling"; +import { useCurrentTheme } from "@theme/useCurrentTheme"; + interface InputProps extends TextInputProps { value: string; onChangeText: (text: string) => void; @@ -35,6 +40,8 @@ const Input: React.FC = ({ ...rest }: InputProps) => { const { t } = useTranslation(); + const theme = useCurrentTheme(); + const styles = getStyles(theme); return ( @@ -44,7 +51,7 @@ const Input: React.FC = ({ keyboardType={keyboardType} onChangeText={onChangeText} placeholder={t(placeholder || "")} - placeholderTextColor="#7D8C9B" + placeholderTextColor={theme.INPUT_PLACEHOLDER} style={[ styles.input, styles.withBorder, @@ -58,25 +65,28 @@ const Input: React.FC = ({ ); }; -const styles = StyleSheet.create({ - label: { - fontSize: 16, - marginBottom: 8, - color: "#172E44", - }, - input: { - height: "100%", - paddingLeft: 16, - fontSize: 16, - borderColor: "#CAD6E1", - borderWidth: 1, - borderRadius: 8, - color: "#172E44", - }, - withErrorBorder: { - borderColor: "#F24D49", - }, - withBorder: {}, -}); +const getStyles = (theme: ThemeSchemeType) => { + return StyleSheet.create({ + label: { + fontSize: resizeFonts(16), + marginBottom: responsiveScale(8), + color: theme.TEXT_COLOR, + }, + input: { + height: "100%", + paddingLeft: responsiveScale(16), + fontSize: resizeFonts(16), + borderColor: theme.CARD_BORDER, + borderWidth: responsiveScale(1), + borderRadius: responsiveScale(8), + backgroundColor: theme.INPUT_BACKGROUND, + color: theme.INPUT_TEXT, + }, + withErrorBorder: { + borderColor: theme.ERROR, + }, + withBorder: {}, + }); +} export default Input; diff --git a/payment_sdk/src/components/LightBox.tsx b/payment_sdk/src/components/LightBox.tsx index 18940ea..f8f4030 100644 --- a/payment_sdk/src/components/LightBox.tsx +++ b/payment_sdk/src/components/LightBox.tsx @@ -4,6 +4,11 @@ import { StyleSheet, Text, View } from "react-native"; import { useTranslation } from "react-i18next"; +import { ThemeSchemeType } from "@util/types"; + +import { resizeFonts, responsiveScale } from "@theme/scalling"; +import { useCurrentTheme } from "@theme/useCurrentTheme"; + type Props = { content: string; icon?: string; @@ -11,6 +16,8 @@ type Props = { const LightBox = ({ content, icon }: Props) => { const { t } = useTranslation(); + const theme = useCurrentTheme(); + const styles = getStyles(theme); return ( @@ -22,25 +29,32 @@ const LightBox = ({ content, icon }: Props) => { export default LightBox; -const styles = StyleSheet.create({ - container: { - flex: 1, - padding: 16, - flexDirection: "row", - backgroundColor: "#F3F7F9", - borderRadius: 8, - justifyContent: "center", - alignItems: "center", - }, - content: { - fontSize: 16, - color: "#172E44", - flex: 0.9, - }, - iconWrapper: { - marginRight: 8, - borderRadius: 100, - backgroundColor: "white", - padding: 8, - }, -}); +const getStyles = (theme: ThemeSchemeType) => { + return StyleSheet.create({ + container: { + flex: 1, + padding: responsiveScale(16), + flexDirection: "row", + backgroundColor: theme.LIGHT_BOX, + borderRadius: responsiveScale(8), + justifyContent: "center", + alignItems: "center", + }, + content: { + fontSize: resizeFonts(16), + color: theme.TEXT_COLOR, + flex: 0.9, + }, + iconWrapper: { + marginRight: responsiveScale(8), + borderRadius: responsiveScale(100), + backgroundColor: theme.WHITE50, + width: responsiveScale(38), + height: responsiveScale(38), + display: 'flex', + justifyContent: 'center', + alignItems: 'center' + }, + }); + +} \ No newline at end of file diff --git a/payment_sdk/src/components/Pill.tsx b/payment_sdk/src/components/Pill.tsx index 1628b5d..37590ed 100644 --- a/payment_sdk/src/components/Pill.tsx +++ b/payment_sdk/src/components/Pill.tsx @@ -3,7 +3,6 @@ import React, { ReactNode } from "react"; import { Text, StyleSheet, - Dimensions, TouchableOpacity, Image, ImageSourcePropType, @@ -11,6 +10,11 @@ import { import { useTranslation } from "react-i18next"; +import { ThemeSchemeType } from "@util/types"; + +import { resizeFonts, responsiveScale, WINDOW_WIDTH } from "@theme/scalling"; +import { useCurrentTheme } from "@theme/useCurrentTheme"; + interface PillProps { label: string; icon?: ImageSourcePropType; @@ -27,6 +31,8 @@ const Pill: React.FC = ({ isSelected, }) => { const { t } = useTranslation(); + const theme = useCurrentTheme(); + const styles = getStyles(theme); return ( = ({ ); }; -const styles = StyleSheet.create({ - container: { - flexDirection: "row", - justifyContent: "center", - alignItems: "center", - padding: 16, - }, - pill: { - paddingHorizontal: 12, - paddingVertical: 8, - borderRadius: 8, - height: 82, - backgroundColor: "white", - borderColor: "#CAD6E1", - borderWidth: 1, - width: (Dimensions.get("window").width - 32) / 3, - marginRight: 8, - display: "flex", - flexDirection: "column", - justifyContent: "space-evenly", - }, - icon: { - marginRight: 8, - }, - label: { - fontSize: 16, - color: "#172E44", - fontWeight: "500", - }, - activeDeco: { - borderColor: "#172E44", - }, -}); +const getStyles = (theme: ThemeSchemeType) => { + return StyleSheet.create({ + container: { + flexDirection: "row", + justifyContent: "center", + alignItems: "center", + padding: responsiveScale(16), + }, + pill: { + paddingHorizontal: responsiveScale(12), + paddingVertical: responsiveScale(8), + borderRadius: responsiveScale(8), + height: responsiveScale(82), + backgroundColor: theme.CARD_BACKGROUND, + borderColor: theme.CARD_BORDER, + borderWidth: 1, + width: (WINDOW_WIDTH - responsiveScale(32)) / 3, + marginRight: responsiveScale(8), + display: "flex", + flexDirection: "column", + justifyContent: "space-evenly", + }, + icon: { + marginRight: responsiveScale(8), + }, + label: { + fontSize: resizeFonts(16), + color: theme.TEXT_COLOR, + fontWeight: "500", + }, + activeDeco: { + borderColor: theme.TEXT_COLOR, + }, + }); +} export default Pill; diff --git a/payment_sdk/src/components/PillContainer.tsx b/payment_sdk/src/components/PillContainer.tsx index 089b982..843139b 100644 --- a/payment_sdk/src/components/PillContainer.tsx +++ b/payment_sdk/src/components/PillContainer.tsx @@ -9,6 +9,8 @@ import { StateContext } from "@context/state"; import { BASE_URL } from "@util/constants"; import { PaymentType, sessionShowPaymentMethodType } from "@util/types"; +import { responsiveScale } from "@theme/scalling"; + import Pill from "./Pill"; type Props = { @@ -55,10 +57,10 @@ const PillContainer = ({ onSelect, selectedItem }: Props) => { const styles = StyleSheet.create({ container: { - marginBottom: 16, + marginBottom: responsiveScale(16), }, contentContainer: { - paddingLeft: 16, + paddingLeft: responsiveScale(16), }, }); diff --git a/payment_sdk/src/components/ResponseScreen.tsx b/payment_sdk/src/components/ResponseScreen.tsx index 8a4e4e1..23a3243 100644 --- a/payment_sdk/src/components/ResponseScreen.tsx +++ b/payment_sdk/src/components/ResponseScreen.tsx @@ -2,7 +2,10 @@ import React, { useCallback, useMemo } from "react"; import { Image, StyleSheet, Text, View } from "react-native"; -import { ResponseScreenStatuses } from "@util/types"; +import { ResponseScreenStatuses, ThemeSchemeType } from "@util/types"; + +import { resizeFonts, responsiveScale } from "@theme/scalling"; +import { useCurrentTheme } from "@theme/useCurrentTheme"; import SubmitButton from "./SubmitButton"; @@ -14,6 +17,10 @@ type Props = { }; const ResponseScreen = ({ status, message, onPress, onPressLabel }: Props) => { + + const theme = useCurrentTheme(); + const styles = getStyles(theme); + const renderMessageContent = useMemo(() => { const title = status === ResponseScreenStatuses.SUCCESS @@ -56,38 +63,41 @@ const ResponseScreen = ({ status, message, onPress, onPressLabel }: Props) => { export default ResponseScreen; -const styles = StyleSheet.create({ - parentContainer: { - flex: 1, - }, - container: { - padding: 16, - alignItems: "center", - }, - imageContainer: { - alignItems: "center", - marginBottom: 18, - }, - icon: { - width: 48, - height: 48, - }, - title: { - fontSize: 28, - fontWeight: "bold", - marginBottom: 16, - color: "#172E44", - }, - message: { - fontSize: 16, - marginBottom: 16, - textAlign: "center", - paddingHorizontal: 32, - }, - bottomButton: { - position: "absolute", - bottom: 42, - left: 0, - right: 0, - }, -}); +const getStyles = (theme: ThemeSchemeType) => { + return StyleSheet.create({ + parentContainer: { + flex: 1, + }, + container: { + padding: responsiveScale(16), + alignItems: "center", + }, + imageContainer: { + alignItems: "center", + marginBottom: responsiveScale(18), + }, + icon: { + width: responsiveScale(48), + height: responsiveScale(48), + }, + title: { + fontSize: resizeFonts(28), + fontWeight: "bold", + marginBottom: responsiveScale(16), + color: theme.TEXT_COLOR, + }, + message: { + fontSize: resizeFonts(16), + marginBottom: responsiveScale(16), + textAlign: "center", + paddingHorizontal: responsiveScale(32), + }, + bottomButton: { + position: "absolute", + bottom: responsiveScale(42), + left: 0, + right: 0, + }, + }); + +} \ No newline at end of file diff --git a/payment_sdk/src/components/Sheet.tsx b/payment_sdk/src/components/Sheet.tsx index c1ae75a..1a72507 100644 --- a/payment_sdk/src/components/Sheet.tsx +++ b/payment_sdk/src/components/Sheet.tsx @@ -18,18 +18,22 @@ import { } from "react-native"; import { Actions, DispatchContext, StateContext } from "@context/state"; +import { useTheme } from "@context/ThemeContext"; -import { paymentFailedCtaText, paymentSuccessCtaText } from "@util/constants"; -import { SCREEN_HEIGHT } from "@util/helpers"; -import { ResponseScreenStatuses } from "@util/types"; +import { paymentFailedCtaText, paymentSuccessCtaText, ThemeModes } from "@util/constants"; +import { ResponseScreenStatuses, ThemeSchemeType } from "@util/types"; import closeIcon from "@assets/images/close.png"; +import closeDMIcon from "@assets/images/close_dm.png"; + +import { resizeFonts, responsiveScale, WINDOW_HEIGHT } from "@theme/scalling"; +import { useCurrentTheme } from "@theme/useCurrentTheme"; import KomojuText from "./KomojuText"; import ResponseScreen from "./ResponseScreen"; import SheetContent from "./SheetContent"; -const MAX_TRANSLATE_Y = -SCREEN_HEIGHT + 50; +const MAX_TRANSLATE_Y = - WINDOW_HEIGHT + responsiveScale(50); type SheetProps = { children?: React.ReactNode; @@ -57,6 +61,9 @@ const Sheet: ForwardRefRenderFunction = ( const { paymentState } = useContext(StateContext); const dispatch = useContext(DispatchContext); + const theme = useCurrentTheme(); + const styles = getStyles(theme); + const {mode} = useTheme(); useEffect(() => { const yListener = translateY.addListener(({ value }) => { @@ -141,9 +148,9 @@ const Sheet: ForwardRefRenderFunction = ( translateY.setValue(Math.max(totalYMovement, MAX_TRANSLATE_Y + 50)); }, onPanResponderRelease: () => { - if (translateYState.current > -SCREEN_HEIGHT / 1.5) { + if (translateYState.current > -WINDOW_HEIGHT / 1.5) { closeSheet(false); - } else if (translateYState.current < -SCREEN_HEIGHT / 1.5) { + } else if (translateYState.current < -WINDOW_HEIGHT / 1.5) { scrollTo(MAX_TRANSLATE_Y + 50); } }, @@ -205,7 +212,7 @@ const Sheet: ForwardRefRenderFunction = ( ) } > - + { @@ -225,42 +232,44 @@ const Sheet: ForwardRefRenderFunction = ( ); }; -const styles = StyleSheet.create({ - backDrop: { - ...StyleSheet.absoluteFillObject, - backgroundColor: "rgba(0,0,0,0.6)", - }, - bottomSheetContainer: { - height: SCREEN_HEIGHT - 85, - width: "100%", - backgroundColor: "white", - position: "absolute", - top: SCREEN_HEIGHT, - borderRadius: 0, - }, - line: { - flexDirection: "row", - alignItems: "center", - marginVertical: 20, - marginHorizontal: 16, - }, - headerLabel: { - fontSize: 18, - fontWeight: "bold", - flex: 1, - alignItems: "center", - textAlign: "center", - color: "#172E44", - }, - crossBtn: { - position: "absolute", - right: 0, - padding: 10, - fontSize: 16, - }, - contentContainer: { - flex: 1, - }, -}); +const getStyles = (theme: ThemeSchemeType) => { + return StyleSheet.create({ + backDrop: { + ...StyleSheet.absoluteFillObject, + backgroundColor: theme.WHITE50, + }, + bottomSheetContainer: { + height: WINDOW_HEIGHT - 85, + width: "100%", + backgroundColor: theme.BACKGROUND_COLOR, + position: "absolute", + top: WINDOW_HEIGHT, + borderRadius: 0, + }, + line: { + flexDirection: "row", + alignItems: "center", + marginVertical: responsiveScale(20), + marginHorizontal: responsiveScale(16), + }, + headerLabel: { + fontSize: resizeFonts(18), + fontWeight: "bold", + flex: 1, + alignItems: "center", + textAlign: "center", + color: theme.TEXT_COLOR, + }, + crossBtn: { + position: "absolute", + right: 0, + padding: responsiveScale(10), + fontSize: resizeFonts(16), + }, + contentContainer: { + flex: 1, + }, + }); +} export default React.forwardRef(Sheet); diff --git a/payment_sdk/src/components/SheetContent.tsx b/payment_sdk/src/components/SheetContent.tsx index b89e1f5..b8dbfc8 100644 --- a/payment_sdk/src/components/SheetContent.tsx +++ b/payment_sdk/src/components/SheetContent.tsx @@ -1,12 +1,14 @@ import React, { useContext, useEffect, useState } from "react"; -import { Keyboard, ScrollView, StyleSheet, View } from "react-native"; +import { FlatList, Keyboard, Platform, StyleSheet, View } from "react-native"; import { Actions, DispatchContext, StateContext } from "@context/state"; -import { SCREEN_HEIGHT, isAndroid } from "@util/helpers"; +// import { isAndroid } from "@util/helpers"; import { PaymentType } from "@util/types"; +import { responsiveScale } from "@theme/scalling"; + import Loader from "./Loader"; import PillContainer from "./PillContainer"; import CardSection from "./sections/CardSection"; @@ -15,7 +17,7 @@ import PayPaySection from "./sections/PayPaySection"; import SheetFooter from "./sections/SheetFooter"; import WebView from "./WebView"; -const KEYBOARD_OFFSET = isAndroid() ? 120 : 80; +// const KEYBOARD_OFFSET = isAndroid() ? 120 : 80; const SheetContent = () => { const { paymentType, webViewData, loading } = useContext(StateContext); @@ -25,7 +27,7 @@ const SheetContent = () => { useEffect(() => { const keyboardDidShowListener = Keyboard.addListener( "keyboardDidShow", - (e) => setKeyboardHeight(e.endCoordinates.height + KEYBOARD_OFFSET) + (e) => setKeyboardHeight(e.endCoordinates.height) ); const keyboardDidHideListener = Keyboard.addListener( @@ -53,35 +55,56 @@ const SheetContent = () => { /> ); - return ( - - + const renderItem = () => { + return ( + {paymentType === PaymentType.CREDIT && } {paymentType === PaymentType.PAY_PAY && } {paymentType === PaymentType.KONBINI && } {renderLoading} - - - + ); + }; + + return ( + + item.key} + contentContainerStyle={[styles.flatListContent, { paddingBottom: keyboardHeight }]} + ListFooterComponent={} + ListFooterComponentStyle={styles.footerContent} + /> ); }; const styles = StyleSheet.create({ mainContent: { - height: "100%", + flex: 1, + }, + flatListContent: { + flexGrow: 1, }, - bottomContent: { - position: "absolute", - right: 0, - left: 0, - bottom: SCREEN_HEIGHT - (SCREEN_HEIGHT - 85 - 40), + item: { + height: "100%" }, + footerContent: { + ...Platform.select({ + ios: { + marginBottom: responsiveScale(200), + marginTop: -responsiveScale(60) + }, + android: { + marginBottom: responsiveScale(200), + marginTop: -responsiveScale(60) + }, + }), + } }); export default SheetContent; diff --git a/payment_sdk/src/components/SubmitButton.tsx b/payment_sdk/src/components/SubmitButton.tsx index 75e2107..7b73bb5 100644 --- a/payment_sdk/src/components/SubmitButton.tsx +++ b/payment_sdk/src/components/SubmitButton.tsx @@ -4,6 +4,11 @@ import { StyleSheet, Text, TouchableOpacity } from "react-native"; import { useTranslation } from "react-i18next"; +import { ThemeSchemeType } from "@util/types"; + +import { resizeFonts, responsiveScale } from "@theme/scalling"; +import { useCurrentTheme } from "@theme/useCurrentTheme"; + type Props = { onPress: () => void; label: string; @@ -13,6 +18,8 @@ type Props = { const SubmitButton = ({ label, labelSuffix, onPress, testID }: Props) => { const { t } = useTranslation(); + const theme = useCurrentTheme(); + const styles = getStyles(theme); return ( { export default SubmitButton; -const styles = StyleSheet.create({ - buttonWrapper: { - backgroundColor: "#0B82EE", - borderRadius: 8, - height: 50, - minHeight: 50, - marginHorizontal: 16, - flex: 1, - justifyContent: "center", - alignItems: "center", - }, - label: { - color: "white", - fontSize: 16, - fontWeight: "bold", - }, -}); +const getStyles = (theme: ThemeSchemeType) => { + return StyleSheet.create({ + buttonWrapper: { + backgroundColor: theme.PRIMARY_COLOR, + borderRadius: responsiveScale(8), + height: responsiveScale(50), + marginHorizontal: responsiveScale(16), + flex: 1, + justifyContent: "center", + alignItems: "center", + }, + label: { + color: "white", + fontSize: resizeFonts(16), + fontWeight: "bold", + }, + }); + +} \ No newline at end of file diff --git a/payment_sdk/src/components/WebView.tsx b/payment_sdk/src/components/WebView.tsx index e033418..9930f87 100644 --- a/payment_sdk/src/components/WebView.tsx +++ b/payment_sdk/src/components/WebView.tsx @@ -6,6 +6,8 @@ import { WebView } from "react-native-webview"; import { newNavStateProps } from "@util/types"; +import { responsiveScale } from "@theme/scalling"; + type webViewProps = { link: string; onNavigationStateChange?: (data: newNavStateProps) => void; @@ -29,6 +31,6 @@ export default WebViewComponent; const styles = StyleSheet.create({ container: { flex: 1, - marginBottom: 20, + marginBottom: responsiveScale(10), }, }); diff --git a/payment_sdk/src/components/sections/CardSection.tsx b/payment_sdk/src/components/sections/CardSection.tsx index f33398c..64a8d44 100644 --- a/payment_sdk/src/components/sections/CardSection.tsx +++ b/payment_sdk/src/components/sections/CardSection.tsx @@ -8,6 +8,8 @@ import { formatCurrency } from "@util/helpers"; import { PaymentType } from "@util/types"; import { validateCardFormFields } from "@util/validator"; +import { responsiveScale } from "@theme/scalling"; + import CardInputGroup from "../CardInputGroup"; import Input from "../Input"; import SubmitButton from "../SubmitButton"; @@ -86,12 +88,14 @@ const CardSection = (): JSX.Element => { /> - + + + ); }; @@ -101,14 +105,17 @@ export default CardSection; const styles = StyleSheet.create({ cardContainer: { position: "relative", - flex: 1, + flexGrow: 1, }, cardNameContainer: { - margin: 16, - marginBottom: 24, - height: 60, + margin: responsiveScale(16), + marginBottom: responsiveScale(24), + height: responsiveScale(60), + }, + btn: { + height: responsiveScale(60), }, inputStyle: { - height: 50, + height: responsiveScale(50), }, }); diff --git a/payment_sdk/src/components/sections/KonbiniSection.tsx b/payment_sdk/src/components/sections/KonbiniSection.tsx index 531ac18..7e77c37 100644 --- a/payment_sdk/src/components/sections/KonbiniSection.tsx +++ b/payment_sdk/src/components/sections/KonbiniSection.tsx @@ -14,6 +14,8 @@ import { } from "@util/types"; import { validateKonbiniFormFields } from "@util/validator"; +import { responsiveScale } from "@theme/scalling"; + import Input from "../Input"; import Pill from "../Pill"; import SubmitButton from "../SubmitButton"; @@ -72,7 +74,7 @@ const KonbiniSection = (): JSX.Element => { const shopImage = useCallback( (iconUrl: string) => { - return ; + return ; }, [konbiniBrands] ); @@ -130,11 +132,13 @@ const KonbiniSection = (): JSX.Element => { contentContainerStyle={styles.contentContainer} /> - + + + ); }; @@ -147,22 +151,25 @@ const styles = StyleSheet.create({ flex: 1, }, inputContainer: { - margin: 16, - marginBottom: 24, - height: 60, + margin: responsiveScale(16), + marginBottom: responsiveScale(24), + height: responsiveScale(60), }, inputStyle: { - height: 50, + height: responsiveScale(50), }, shopListContainer: { - marginBottom: 26, - marginTop: 16, + marginBottom: responsiveScale(26), + marginTop: responsiveScale(16), }, contentContainer: { - paddingLeft: 16, + paddingLeft: responsiveScale(16), }, iconStyle: { - width: 32, - height: 32, + width: responsiveScale(32), + height: responsiveScale(32), + }, + btn: { + height: responsiveScale(60), }, }); diff --git a/payment_sdk/src/components/sections/PayPaySection.tsx b/payment_sdk/src/components/sections/PayPaySection.tsx index 9b3345a..0c282be 100644 --- a/payment_sdk/src/components/sections/PayPaySection.tsx +++ b/payment_sdk/src/components/sections/PayPaySection.tsx @@ -4,7 +4,10 @@ import { StyleSheet, View } from "react-native"; import { StateContext } from "@context/state"; -import { PaymentType } from "@util/types"; +import { PaymentType, ThemeSchemeType } from "@util/types"; + +import { resizeFonts, responsiveScale } from "@theme/scalling"; +import { useCurrentTheme } from "@theme/useCurrentTheme"; import KomojuText from "../KomojuText"; import LightBox from "../LightBox"; @@ -12,7 +15,8 @@ import SubmitButton from "../SubmitButton"; const PayPaySection = () => { const { sessionPay } = useContext(StateContext); - + const theme = useCurrentTheme(); + const styles = getStyles(theme); const onPay = () => { sessionPay({ paymentType: PaymentType.PAY_PAY }); }; @@ -31,35 +35,42 @@ const PayPaySection = () => { icon="📱" /> - + + + ); }; export default PayPaySection; -const styles = StyleSheet.create({ - container: { - flexGrow: 1, - }, - textContent: { - marginBottom: 24, - marginTop: 16, - marginHorizontal: 16, - }, - title: { - fontSize: 20, - fontWeight: "bold", - color: "#172E44", - marginBottom: 8, - }, - description: { - fontSize: 16, - color: "#172E44", - }, - lbWrapper: { - minHeight: 80, - marginHorizontal: 16, - marginBottom: 24, - }, -}); +const getStyles = (theme: ThemeSchemeType) => { + return StyleSheet.create({ + container: { + flexGrow: 1, + }, + textContent: { + marginBottom: responsiveScale(24), + marginTop: responsiveScale(16), + marginHorizontal: responsiveScale(16), + }, + title: { + fontSize: resizeFonts(20), + fontWeight: "bold", + color: theme.TEXT_COLOR, + marginBottom: responsiveScale(8), + }, + description: { + fontSize: resizeFonts(16), + color: theme.TEXT_COLOR, + }, + lbWrapper: { + minHeight: responsiveScale(80), + marginHorizontal: responsiveScale(16), + marginBottom: responsiveScale(24), + }, + btn: { + height: responsiveScale(60), + }, + }); +} diff --git a/payment_sdk/src/components/sections/SheetFooter.tsx b/payment_sdk/src/components/sections/SheetFooter.tsx index b179196..7c4ad91 100644 --- a/payment_sdk/src/components/sections/SheetFooter.tsx +++ b/payment_sdk/src/components/sections/SheetFooter.tsx @@ -2,14 +2,22 @@ import React from "react"; import { Image, StyleSheet, View } from "react-native"; +import { useTheme } from "@context/ThemeContext"; + +import { ThemeModes } from "@util/constants"; + import FirstFooterImage from "@assets/images/footer_image1.png"; import SecondFooterImage from "@assets/images/footer_image2.png"; +import SecondFooterImageDM from "@assets/images/footer_image2_dm.png"; + +import { responsiveScale } from "@theme/scalling"; const SheetFooter = () => { + const { mode } = useTheme(); return ( - + ); }; @@ -21,6 +29,6 @@ const styles = StyleSheet.create({ flexDirection: "row", justifyContent: "space-between", alignItems: "center", - marginHorizontal: 16, + marginHorizontal: responsiveScale(16), }, }); diff --git a/payment_sdk/src/context/KomojuProvider.tsx b/payment_sdk/src/context/KomojuProvider.tsx index 2c03022..2d50d68 100644 --- a/payment_sdk/src/context/KomojuProvider.tsx +++ b/payment_sdk/src/context/KomojuProvider.tsx @@ -34,6 +34,7 @@ import { validateSessionResponse } from "@util/validator"; import "@assets/languages/i18n"; import { Actions, DispatchContext, KomojuContext } from "./state"; +import { ThemeProvider } from "./ThemeContext"; type KomojuProviderIprops = { children?: ReactNode | ReactNode[]; @@ -43,13 +44,15 @@ export const KomojuProvider = (props: KomojuProviderIprops) => { if (props?.language) i18next.changeLanguage(props?.language); return ( - - {props.children} - + + + {props.children} + + ); }; diff --git a/payment_sdk/src/context/ThemeContext.tsx b/payment_sdk/src/context/ThemeContext.tsx new file mode 100644 index 0000000..117f537 --- /dev/null +++ b/payment_sdk/src/context/ThemeContext.tsx @@ -0,0 +1,48 @@ +// ThemeContext.tsx +import React, { createContext, useContext, useState, useEffect } from 'react'; + +import { Appearance } from 'react-native'; + +import { ThemeModes } from '@util/constants'; + +interface ThemeContextType { + mode: ThemeModes; + toggleMode: () => void; +} + +const ThemeContext = createContext(undefined); + +export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [mode, setMode] = useState(() => { + const colorScheme = Appearance.getColorScheme(); + return colorScheme === 'dark' ? ThemeModes.dark : ThemeModes.light; + }); + + useEffect(() => { + const subscription = Appearance.addChangeListener(({ colorScheme }) => { + setMode(colorScheme === 'dark' ? ThemeModes.dark : ThemeModes.light); + }); + + return () => subscription.remove(); + }, []); + + const toggleMode = () => { + setMode((prevMode) => + prevMode === ThemeModes.light ? ThemeModes.dark : ThemeModes.light + ); + }; + + return ( + + {children} + + ); +}; + +export const useTheme = (): ThemeContextType => { + const context = useContext(ThemeContext); + if (context === undefined) { + throw new Error('useTheme must be used within a ThemeProvider'); + } + return context; +}; \ No newline at end of file diff --git a/payment_sdk/src/theme/defaultColorTheme.ts b/payment_sdk/src/theme/defaultColorTheme.ts new file mode 100644 index 0000000..36fbdef --- /dev/null +++ b/payment_sdk/src/theme/defaultColorTheme.ts @@ -0,0 +1,38 @@ +const darkTheme = { + PRIMARY_COLOR: "#0071D7", + BACKGROUND_COLOR: "#1E1E1E", + ERROR: "#fc4747", + TEXT_COLOR: "#fff", + INPUT_BACKGROUND: '#2C2C2C', + INPUT_TEXT: '#F7F8F8', + INPUT_PLACEHOLDER: '#958d8e', + INVERTED_CONTENT: '#1D1617', + WHITE50: "#00000080", + CARD_BACKGROUND: '#171717', + CARD_BORDER: '#33414c', + LIGHT_BOX: '#2C2C2C', + CARD_SHADOW_IOS_COLOR: '#000', + CARD_SHADOW_ANDROID_COLOR: '#000', +}; + +const lightTheme = { + PRIMARY_COLOR: "#0B82EE", + BACKGROUND_COLOR: "#FBFBFB", + ERROR: "#fc5d5d", + TEXT_COLOR: "#172E44", + INPUT_BACKGROUND: '#F7F8F8', + INPUT_TEXT: '#172E44', + INPUT_PLACEHOLDER: '#ADA4A5', + INVERTED_CONTENT: '#fff', + WHITE50: '#00000050', + CARD_BACKGROUND: '#ffffff', + CARD_BORDER: '#CAD6E1', + LIGHT_BOX: '#BCBCBC', + CARD_SHADOW_IOS_COLOR: '#D9D9D9', + CARD_SHADOW_ANDROID_COLOR: '#c4c2c2', +}; + +export const appTheme = { + dark: darkTheme, + light: lightTheme +} \ No newline at end of file diff --git a/payment_sdk/src/theme/scalling.ts b/payment_sdk/src/theme/scalling.ts new file mode 100644 index 0000000..016ea36 --- /dev/null +++ b/payment_sdk/src/theme/scalling.ts @@ -0,0 +1,28 @@ +import { Dimensions, PixelRatio, Platform } from 'react-native'; + +const figmaScreenWidth = 390; +const figmaScreenHeight = 844; + +const { width, height } = Dimensions.get('window'); +const screenWidth = Math.min(width, height); +const screenHeight = Math.max(width, height); + +const scaleWidth = screenWidth / figmaScreenWidth; +const scaleHeight = screenHeight / figmaScreenHeight; + +const scale = Math.min(scaleWidth, scaleHeight); + +const MIN_SCALE = 0.90; + +export const responsiveScale = (size: number): number => { + const scaledSize = size * scale; + return Math.max(scaledSize, size * MIN_SCALE); +}; + +export const resizeFonts = (size: number): number => { + const fontScale = Platform.OS === 'ios' ? 1 : PixelRatio.getFontScale(); + const spFontSize = size * fontScale; + return spFontSize; +}; +export const WINDOW_WIDTH = width; +export const WINDOW_HEIGHT = height; \ No newline at end of file diff --git a/payment_sdk/src/theme/useCurrentTheme.ts b/payment_sdk/src/theme/useCurrentTheme.ts new file mode 100644 index 0000000..20a3c38 --- /dev/null +++ b/payment_sdk/src/theme/useCurrentTheme.ts @@ -0,0 +1,12 @@ +import { useColorScheme } from 'react-native'; + +import { useTheme } from '@context/ThemeContext'; + +import { appTheme } from '@theme/defaultColorTheme'; + +export const useCurrentTheme = () => { + const { mode } = useTheme(); + const systemColorScheme = useColorScheme(); + const effectiveMode = systemColorScheme || mode; + return appTheme[effectiveMode]; +}; \ No newline at end of file diff --git a/payment_sdk/src/util/constants.ts b/payment_sdk/src/util/constants.ts index 585485d..7bf0b58 100644 --- a/payment_sdk/src/util/constants.ts +++ b/payment_sdk/src/util/constants.ts @@ -31,3 +31,9 @@ export const cardTypeRegex = { jcb: /^(?:35)\d{0,17}/, visa: /^4\d{0,18}/, }; + +export enum ThemeModes { + light = "light", + dark = "dark", + +} diff --git a/payment_sdk/src/util/display.ts b/payment_sdk/src/util/display.ts deleted file mode 100644 index 5b73f92..0000000 --- a/payment_sdk/src/util/display.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {Dimensions, PixelRatio, Platform} from 'react-native'; - -export type DimensionType = 'width' | 'height'; - -const {width: WINDOW_WIDTH, height: WINDOW_HEIGHT} = Dimensions.get('window'); - -// Design resolution -const designWidth = 375; -const designHeight = 812; - -// Convert dp to pixel -const dpToPixel = (dp: number, dimension: DimensionType): number => { - const designDimension = dimension === 'width' ? designWidth : designHeight; - return ( - (dp / designDimension) * - (dimension === 'width' ? WINDOW_WIDTH : WINDOW_HEIGHT) - ); -}; - -// Convert dp to sp for font size -const fontSizeDPToSP = (size: number): number => { - const scale = Platform.OS === 'ios' ? 1 : PixelRatio.getFontScale(); - const spFontSize = size * scale; - return spFontSize; -}; - -export {dpToPixel, fontSizeDPToSP, WINDOW_WIDTH, WINDOW_HEIGHT}; diff --git a/payment_sdk/src/util/types.ts b/payment_sdk/src/util/types.ts index dfb26e3..4cc675e 100644 --- a/payment_sdk/src/util/types.ts +++ b/payment_sdk/src/util/types.ts @@ -248,3 +248,20 @@ export const initialState: State = { // TODO: Fix this type error // eslint-disable-next-line @typescript-eslint/no-explicit-any export type ActionType = { type: string; payload: any }; + +export interface ThemeSchemeType { + PRIMARY_COLOR: string; + BACKGROUND_COLOR: string; + ERROR: string; + TEXT_COLOR: string; + INPUT_BACKGROUND: string; + INPUT_TEXT: string; + INPUT_PLACEHOLDER: string; + INVERTED_CONTENT: string; + WHITE50: string; + CARD_BACKGROUND: string; + CARD_BORDER: string; + LIGHT_BOX: string; + CARD_SHADOW_IOS_COLOR: string; + CARD_SHADOW_ANDROID_COLOR: string; +} diff --git a/payment_sdk/tsconfig.json b/payment_sdk/tsconfig.json index f97074d..60ef781 100644 --- a/payment_sdk/tsconfig.json +++ b/payment_sdk/tsconfig.json @@ -33,6 +33,9 @@ ], "@context/*": [ "context/*" + ], + "@theme/*": [ + "theme/*" ] } },