react-native-seel-widget 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/package.json +1 -1
  2. package/src/assets/images/accredited.png +0 -0
  3. package/src/assets/images/background_image.jpg +0 -0
  4. package/src/assets/images/button_close.png +0 -0
  5. package/src/assets/images/button_close_background.png +0 -0
  6. package/src/assets/images/checkbox_normal.png +0 -0
  7. package/src/assets/images/checkbox_selected.png +0 -0
  8. package/src/assets/images/close_white.png +0 -0
  9. package/src/assets/images/ic_launcher.png +0 -0
  10. package/src/assets/images/icon_arrow_left.png +0 -0
  11. package/src/assets/images/icon_bg_select.png +0 -0
  12. package/src/assets/images/icon_check_selected_black.png +0 -0
  13. package/src/assets/images/icon_select.png +0 -0
  14. package/src/assets/images/info_black.png +0 -0
  15. package/src/assets/images/seel_icon.png +0 -0
  16. package/src/assets/images/seel_logo.png +0 -0
  17. package/src/assets/images/seel_word.png +0 -0
  18. package/src/assets/images/tick_small_minor.png +0 -0
  19. package/src/constants/key_value.ts +48 -0
  20. package/src/constants/network_request_statue_enum.ts +13 -0
  21. package/src/core/SeelWidgetSDK.ts +103 -0
  22. package/src/dto/EventsRequest.ts +71 -0
  23. package/src/dto/EventsResponse.ts +13 -0
  24. package/src/dto/IEvents.ts +51 -0
  25. package/src/dto/IQuotes.ts +36 -0
  26. package/src/dto/IQuotesRequest.ts +220 -0
  27. package/src/dto/IQuotesResponse.ts +111 -0
  28. package/src/network/request.ts +214 -0
  29. package/src/ui/coverage-info-footer.tsx +186 -0
  30. package/src/ui/gradient-animation-text.tsx +185 -0
  31. package/src/ui/gradient-animation-view.tsx +150 -0
  32. package/src/ui/index.ts +4 -0
  33. package/src/ui/seel-wfp-info-view.tsx +351 -0
  34. package/src/ui/seel-wfp-title-view.tsx +388 -0
  35. package/src/ui/seel-wfp-widget.tsx +270 -0
  36. package/src/utils/format_util.ts +117 -0
  37. package/src/utils/http_util.ts +30 -0
  38. package/src/utils/storage_util.ts +42 -0
  39. package/src/utils/uuid.ts +18 -0
@@ -0,0 +1,111 @@
1
+ import type { IShippingOrigin, IShippingAddress } from './IQuotes';
2
+
3
+ /**
4
+ * Quote status type
5
+ */
6
+ export type QuoteStatus = 'accepted' | 'rejected';
7
+
8
+ /**
9
+ * Customer information for quotes response
10
+ */
11
+ export interface IQuotesResponseCustomer {
12
+ customer_id: string;
13
+ email: string;
14
+ first_name: string;
15
+ last_name: string;
16
+ }
17
+
18
+ export interface II18N {
19
+ lang: string;
20
+ texts: Map<string, string>[];
21
+ }
22
+
23
+ /**
24
+ * Extra information for quotes response
25
+ */
26
+ export interface IQuotesResponseExtraInfo {
27
+ coverage_details_text?: string[];
28
+ display_widget_text?: string[];
29
+ opt_out_warning_text?: string;
30
+ privacy_policy_url?: string;
31
+ shipping_fee?: number;
32
+ terms_url?: string;
33
+ widget_title?: string;
34
+ i18n?: II18N;
35
+ }
36
+
37
+ /**
38
+ * Line item extra info (for nested structure)
39
+ */
40
+ export interface IQuotesResponseLineItemExtraInfo {
41
+ [key: string]: unknown;
42
+ }
43
+
44
+ /**
45
+ * Line item information for quotes response
46
+ */
47
+ export interface IQuotesResponseLineItem {
48
+ allocated_discounts?: number;
49
+ brand_name?: string;
50
+ category_1?: string;
51
+ category_2?: string;
52
+ category_3?: string;
53
+ category_4?: string;
54
+ condition?: string;
55
+ currency?: string;
56
+ extra_info?: IQuotesResponseLineItemExtraInfo;
57
+ final_price?: string;
58
+ image_url?: string;
59
+ is_final_sale?: boolean;
60
+ line_item_id?: string;
61
+ price?: number;
62
+ product_attributes?: string;
63
+ product_description?: string;
64
+ product_id?: string;
65
+ product_title?: string;
66
+ product_url?: string;
67
+ quantity?: number;
68
+ requires_shipping?: boolean;
69
+ retail_price?: number;
70
+ sales_tax?: number;
71
+ seller_id?: string;
72
+ seller_name?: string;
73
+ shipping_fee?: number;
74
+ shipping_origin?: IShippingOrigin;
75
+ sku?: string;
76
+ variant_id?: string;
77
+ variant_title?: string;
78
+ }
79
+
80
+ /**
81
+ * Quotes Response interface
82
+ * Fields are ordered logically: identifiers, metadata, configuration, data, status
83
+ */
84
+ export default interface IQuotesResponse {
85
+ // Identifiers
86
+ quote_id: string;
87
+ cart_id: string;
88
+ merchant_id: string;
89
+ device_id: string;
90
+ session_id: string;
91
+
92
+ // Metadata
93
+ created_ts: string;
94
+ status: string;
95
+
96
+ // Configuration
97
+ type: string;
98
+ device_category: string;
99
+ device_platform: string;
100
+ is_default_on: boolean;
101
+
102
+ // Financial data
103
+ currency: string;
104
+ price: number;
105
+
106
+ // Data
107
+ line_items?: IQuotesResponseLineItem[];
108
+ customer?: IQuotesResponseCustomer;
109
+ shipping_address?: IShippingAddress;
110
+ extra_info?: IQuotesResponseExtraInfo;
111
+ }
@@ -0,0 +1,214 @@
1
+ import { SeelWidgetSDK } from '../core/SeelWidgetSDK';
2
+
3
+ export interface RequestHeaders {
4
+ 'X-Seel-API-Key': string;
5
+ 'X-Seel-API-Version': string;
6
+ [key: string]: string;
7
+ }
8
+
9
+ export interface RequestOptions {
10
+ headers?: RequestHeaders;
11
+ timeout?: number;
12
+ }
13
+
14
+ export interface GetRequestParams {
15
+ [key: string]: any;
16
+ }
17
+
18
+ const defaultXSeelApiKey: string = SeelWidgetSDK.shared.apiKey || '';
19
+ const defaultXSeelApiVersion: string = SeelWidgetSDK.shared.apiVersion || '';
20
+ const defaultRequestTimeout: number = SeelWidgetSDK.shared.requestTimeout;
21
+
22
+ class Request {
23
+ private defaultHeaders: RequestHeaders = {
24
+ 'Accept': 'application/json',
25
+ 'Content-Type': 'application/json',
26
+ 'X-Seel-API-Key': defaultXSeelApiKey,
27
+ 'X-Seel-API-Version': defaultXSeelApiVersion,
28
+ };
29
+
30
+ private defaultTimeout: number = defaultRequestTimeout;
31
+
32
+ /**
33
+ * Set default request headers
34
+ */
35
+ setDefaultHeaders(headers: RequestHeaders): void {
36
+ this.defaultHeaders = { ...this.defaultHeaders, ...headers };
37
+ }
38
+
39
+ /**
40
+ * Set default timeout
41
+ */
42
+ setDefaultTimeout(timeout: number): void {
43
+ this.defaultTimeout = timeout;
44
+ }
45
+
46
+ /**
47
+ * Build query string
48
+ */
49
+ private buildQueryString(params: GetRequestParams): string {
50
+ const queryParams = new URLSearchParams();
51
+ Object.keys(params).forEach((key) => {
52
+ const value = params[key];
53
+ if (value !== undefined && value !== null && value !== '') {
54
+ queryParams.append(key, String(value));
55
+ }
56
+ });
57
+
58
+ return queryParams.toString();
59
+ }
60
+
61
+ /**
62
+ * Handle request timeout
63
+ */
64
+ private createTimeoutPromise(timeout: number): Promise<never> {
65
+ return new Promise((_, reject) => {
66
+ setTimeout(() => {
67
+ reject(new Error(`Request timeout after ${timeout}ms`));
68
+ }, timeout);
69
+ });
70
+ }
71
+
72
+ /**
73
+ * GET request
74
+ * @param url Request URL
75
+ * @param params Request parameters
76
+ * @param options Request options (headers, timeout, etc.)
77
+ * @returns Promise<any>
78
+ */
79
+ async get<T = any>(
80
+ url: string,
81
+ params?: GetRequestParams,
82
+ options?: RequestOptions
83
+ ): Promise<T> {
84
+ try {
85
+ // Build full URL (including query parameters)
86
+ let fullUrl = url;
87
+ if (params && Object.keys(params).length > 0) {
88
+ const queryString = this.buildQueryString(params);
89
+ fullUrl = `${url}${url.includes('?') ? '&' : '?'}${queryString}`;
90
+ }
91
+
92
+ // Merge request headers
93
+ const headers = {
94
+ ...this.defaultHeaders,
95
+ ...options?.headers,
96
+ };
97
+
98
+ // Create request configuration
99
+ const requestOptions: RequestInit = {
100
+ method: 'GET',
101
+ headers,
102
+ };
103
+
104
+ // Handle timeout
105
+ const timeout = options?.timeout || this.defaultTimeout;
106
+ const timeoutPromise = this.createTimeoutPromise(timeout);
107
+
108
+ // Send request
109
+ const fetchPromise = fetch(fullUrl, requestOptions);
110
+
111
+ // Use Promise.race to handle timeout
112
+ const response = await Promise.race([fetchPromise, timeoutPromise]);
113
+
114
+ // Check response status
115
+ if (!response.ok) {
116
+ throw new Error(`HTTP error! status: ${response.status}`);
117
+ }
118
+
119
+ // Parse response data
120
+ const data = await response.json();
121
+ return data as T;
122
+ } catch (error) {
123
+ // Unified error handling
124
+ if (error instanceof Error) {
125
+ throw new Error(`GET request failed: ${error.message}`);
126
+ }
127
+ throw new Error('GET request failed: Unknown error');
128
+ }
129
+ }
130
+
131
+ /**
132
+ * POST request
133
+ * @param url Request URL
134
+ * @param data Request body data
135
+ * @param options Request options (headers, timeout, etc.)
136
+ * @returns Promise<any>
137
+ */
138
+ async post<T = any>(
139
+ url: string,
140
+ data?: any,
141
+ options?: RequestOptions
142
+ ): Promise<T> {
143
+ try {
144
+ // Merge request headers
145
+ const headers = {
146
+ ...this.defaultHeaders,
147
+ ...options?.headers,
148
+ };
149
+
150
+ // Build request body
151
+ let body: string | undefined;
152
+ if (data !== undefined && data !== null) {
153
+ // If data is already a string, use it directly
154
+ if (typeof data === 'string') {
155
+ body = data;
156
+ } else {
157
+ // Otherwise, serialize to JSON
158
+ body = JSON.stringify(data);
159
+ }
160
+ }
161
+
162
+ // Create request configuration
163
+ const requestOptions: RequestInit = {
164
+ method: 'POST',
165
+ headers,
166
+ ...(body && { body }),
167
+ };
168
+
169
+ // Handle timeout
170
+ const timeout = options?.timeout || this.defaultTimeout;
171
+ const timeoutPromise = this.createTimeoutPromise(timeout);
172
+
173
+ // Send request
174
+ const fetchPromise = fetch(url, requestOptions);
175
+
176
+ // Use Promise.race to handle timeout
177
+ const response = await Promise.race([fetchPromise, timeoutPromise]);
178
+ // Check response status
179
+ if (!response.ok) {
180
+ throw new Error(`HTTP error! status: ${response.status}`);
181
+ }
182
+
183
+ // Parse response data
184
+ const responseData = await response.json();
185
+ return responseData as T;
186
+ } catch (error) {
187
+ // Unified error handling
188
+ if (error instanceof Error) {
189
+ throw new Error(`POST request failed: ${error.message}`);
190
+ }
191
+ throw new Error('POST request failed: Unknown error');
192
+ }
193
+ }
194
+ }
195
+
196
+ // Export singleton instance
197
+ export const instance = new Request();
198
+
199
+ // Export convenience methods
200
+ export const get = <T = any>(
201
+ url: string,
202
+ params?: GetRequestParams,
203
+ options?: RequestOptions
204
+ ): Promise<T> => {
205
+ return instance.get<T>(url, params, options);
206
+ };
207
+
208
+ export const post = <T = any>(
209
+ url: string,
210
+ data?: any,
211
+ options?: RequestOptions
212
+ ): Promise<T> => {
213
+ return instance.post<T>(url, data, options);
214
+ };
@@ -0,0 +1,186 @@
1
+ import {
2
+ Image,
3
+ Linking,
4
+ Platform,
5
+ StyleSheet,
6
+ Text,
7
+ TouchableOpacity,
8
+ useColorScheme,
9
+ View,
10
+ } from 'react-native';
11
+ import KeyValue from '../constants/key_value';
12
+
13
+ interface CoverageInfoFooterProps {
14
+ termsUrl: string;
15
+ privacyPolicyUrl: string;
16
+ dictionary: any;
17
+ onChangeOptedInValue: (value: boolean) => void;
18
+ }
19
+
20
+ export default function CoverageInfoFooter({
21
+ termsUrl = '',
22
+ privacyPolicyUrl = '',
23
+ dictionary = {},
24
+ onChangeOptedInValue = (_: boolean) => {},
25
+ }: CoverageInfoFooterProps) {
26
+ const colorScheme = useColorScheme();
27
+ const isDark = false && colorScheme === 'dark';
28
+
29
+ const _poweredByText = [
30
+ defaultStyles.poweredByText,
31
+ isDark ? defaultStyles.dartPoweredByText : defaultStyles.lightPoweredByText,
32
+ ];
33
+ return (
34
+ <View style={[defaultStyles.container]}>
35
+ <TouchableOpacity
36
+ style={defaultStyles.optedInButton}
37
+ onPress={() => {
38
+ onChangeOptedInValue(true);
39
+ }}
40
+ >
41
+ <Text style={defaultStyles.optedInButtonTitle}>
42
+ {dictionary[KeyValue.cta_secure_purchase] ??
43
+ 'Secure Your Purchase Now'}
44
+ </Text>
45
+ </TouchableOpacity>
46
+ <TouchableOpacity
47
+ style={defaultStyles.noNeedButton}
48
+ hitSlop={{ top: 8, bottom: 8 }}
49
+ onPress={() => {
50
+ onChangeOptedInValue(false);
51
+ }}
52
+ >
53
+ <Text style={defaultStyles.noNeedButtonTitle}>
54
+ {dictionary[KeyValue.cta_continue_without] ??
55
+ 'Continue Without Protection'}
56
+ </Text>
57
+ </TouchableOpacity>
58
+ <View style={defaultStyles.horizontalStackView}>
59
+ <TouchableOpacity
60
+ style={defaultStyles.privacyPolicyButton}
61
+ hitSlop={{ top: 14 }}
62
+ onPress={() => {
63
+ Linking.openURL(privacyPolicyUrl);
64
+ }}
65
+ >
66
+ <Text style={defaultStyles.privacyPolicyButtonTitle}>
67
+ {dictionary[KeyValue.privacy_policy] ?? 'Privacy Policy'}
68
+ </Text>
69
+ </TouchableOpacity>
70
+ <TouchableOpacity
71
+ style={defaultStyles.termsButton}
72
+ hitSlop={{ top: 14 }}
73
+ onPress={() => {
74
+ Linking.openURL(termsUrl);
75
+ }}
76
+ >
77
+ <Text style={defaultStyles.termsButtonTitle}>
78
+ {dictionary[KeyValue.terms_of_service] ?? 'Terms of Service'}
79
+ </Text>
80
+ </TouchableOpacity>
81
+ </View>
82
+ <View style={defaultStyles.poweredByContainer}>
83
+ <Text style={_poweredByText}>
84
+ {dictionary[KeyValue.powered_by] ?? 'Powered by'}
85
+ </Text>
86
+ <Image
87
+ style={defaultStyles.seelWordIcon}
88
+ source={require('../assets/images/seel_word.png')}
89
+ />
90
+ </View>
91
+ </View>
92
+ );
93
+ }
94
+
95
+ const defaultStyles = StyleSheet.create({
96
+ container: {
97
+ paddingTop: 12,
98
+ flexDirection: 'column',
99
+ justifyContent: 'center',
100
+ alignItems: 'center',
101
+ },
102
+ optedInButton: {
103
+ paddingLeft: 16,
104
+ paddingRight: 16,
105
+ width: '100%',
106
+ height: 52,
107
+ borderRadius: 10,
108
+ justifyContent: 'center',
109
+ alignItems: 'center',
110
+ backgroundColor: '#333333',
111
+ },
112
+ optedInButtonTitle: {
113
+ color: '#ffffff',
114
+ fontSize: 14,
115
+ fontFamily: Platform.select({
116
+ ios: 'Inter',
117
+ }),
118
+ textAlign: 'center',
119
+ fontWeight: 600,
120
+ },
121
+ noNeedButton: {
122
+ marginTop: 12,
123
+ justifyContent: 'center',
124
+ alignItems: 'center',
125
+ },
126
+ noNeedButtonTitle: {
127
+ color: '#808692',
128
+ fontSize: 14,
129
+ fontFamily: Platform.select({
130
+ ios: 'Inter',
131
+ }),
132
+ textAlign: 'center',
133
+ fontWeight: 600,
134
+ },
135
+ horizontalStackView: {
136
+ width: '100%',
137
+ marginTop: 12,
138
+ padding: 14,
139
+ flexDirection: 'row',
140
+ justifyContent: 'space-around',
141
+ alignItems: 'center',
142
+ },
143
+ privacyPolicyButton: {
144
+ justifyContent: 'center',
145
+ alignItems: 'center',
146
+ },
147
+ privacyPolicyButtonTitle: {
148
+ color: '#5C5F62',
149
+ fontSize: 10,
150
+ fontWeight: 400,
151
+ lineHeight: 16,
152
+ textDecorationLine: 'underline',
153
+ },
154
+ termsButton: {
155
+ justifyContent: 'center',
156
+ alignItems: 'center',
157
+ },
158
+ termsButtonTitle: {
159
+ color: '#5C5F62',
160
+ fontSize: 10,
161
+ fontWeight: 400,
162
+ lineHeight: 16,
163
+ textDecorationLine: 'underline',
164
+ },
165
+ poweredByContainer: {
166
+ marginTop: 'auto',
167
+ flexDirection: 'row',
168
+ alignItems: 'center',
169
+ },
170
+ poweredByText: {
171
+ color: '#000000',
172
+ fontSize: 10,
173
+ fontWeight: 400,
174
+ lineHeight: 16,
175
+ },
176
+ lightPoweredByText: {
177
+ color: '#565656',
178
+ },
179
+ dartPoweredByText: {
180
+ color: 'white',
181
+ },
182
+ seelWordIcon: {
183
+ width: 32,
184
+ height: 16,
185
+ },
186
+ });
@@ -0,0 +1,185 @@
1
+ import {
2
+ forwardRef,
3
+ useCallback,
4
+ useEffect,
5
+ useImperativeHandle,
6
+ useRef,
7
+ } from 'react';
8
+ import { Animated, StyleSheet, Text, View } from 'react-native';
9
+ import type { TextStyle, ViewStyle } from 'react-native';
10
+
11
+ export interface GradientAnimationTextProps {
12
+ /**
13
+ * Text content to display
14
+ */
15
+ text?: string;
16
+ /**
17
+ * Custom text style
18
+ */
19
+ textStyle?: TextStyle;
20
+ /**
21
+ * Custom container style
22
+ */
23
+ containerStyle?: ViewStyle;
24
+ /**
25
+ * Whether to start animation automatically (default: true)
26
+ */
27
+ autoStart?: boolean;
28
+ /**
29
+ * Animation duration in milliseconds for one cycle (default: 1500)
30
+ */
31
+ animationDuration?: number;
32
+ /**
33
+ * Gradient colors for loading effect (default: ['rgba(255,255,255,0)', 'rgba(255,255,255,0.3)', 'rgba(255,255,255,0)'])
34
+ */
35
+ gradientColors?: string[];
36
+ }
37
+
38
+ export interface GradientAnimationTextRef {
39
+ /**
40
+ * Start the loading animation
41
+ */
42
+ start: () => void;
43
+ /**
44
+ * Stop the loading animation
45
+ */
46
+ stop: () => void;
47
+ }
48
+
49
+ const GradientAnimationText = forwardRef<
50
+ GradientAnimationTextRef,
51
+ GradientAnimationTextProps
52
+ >(
53
+ (
54
+ {
55
+ text = '',
56
+ textStyle,
57
+ containerStyle,
58
+ autoStart = true,
59
+ animationDuration = 1500,
60
+ gradientColors = [
61
+ 'rgba(255, 255, 255, 0.3)',
62
+ 'rgba(255, 255, 255, 0.6)',
63
+ 'rgba(255, 255, 255, 0.3)',
64
+ ],
65
+ },
66
+ ref
67
+ ) => {
68
+ // Use a larger range for smoother animation across the container
69
+ const translateX = useRef(new Animated.Value(-300)).current;
70
+ const animationRef = useRef<any>(null);
71
+
72
+ const startAnimation = useCallback(() => {
73
+ // Stop existing animation if any
74
+ if (animationRef.current) {
75
+ animationRef.current.stop();
76
+ }
77
+ // Reset position to start (off-screen left)
78
+ translateX.setValue(-300);
79
+ // Create loop animation (move from left to right)
80
+ animationRef.current = Animated.loop(
81
+ Animated.timing(translateX, {
82
+ toValue: 300,
83
+ duration: animationDuration,
84
+ useNativeDriver: true,
85
+ })
86
+ );
87
+ animationRef.current.start();
88
+ }, [animationDuration, translateX]);
89
+
90
+ const stopAnimation = useCallback(() => {
91
+ if (animationRef.current) {
92
+ animationRef.current.stop();
93
+ animationRef.current = null;
94
+ }
95
+ }, []);
96
+
97
+ useImperativeHandle(ref, () => ({
98
+ start: startAnimation,
99
+ stop: stopAnimation,
100
+ }));
101
+
102
+ useEffect(() => {
103
+ if (autoStart) {
104
+ startAnimation();
105
+ }
106
+ return () => {
107
+ stopAnimation();
108
+ };
109
+ }, [autoStart, startAnimation, stopAnimation]);
110
+
111
+ return (
112
+ <View style={[styles.container, containerStyle]}>
113
+ <View style={styles.textContainer}>
114
+ <Text style={[styles.text, textStyle]}>{text}</Text>
115
+ {/* Gradient loading effect - moving from left to right */}
116
+ <Animated.View
117
+ style={[
118
+ styles.gradientMask,
119
+ {
120
+ transform: [{ translateX }],
121
+ },
122
+ ]}
123
+ pointerEvents="none"
124
+ >
125
+ {/* Create gradient effect with three colors */}
126
+ <View style={styles.gradientOverlay}>
127
+ <View
128
+ style={[
129
+ styles.gradientSection,
130
+ { backgroundColor: gradientColors[0] },
131
+ ]}
132
+ />
133
+ <View
134
+ style={[
135
+ styles.gradientSection,
136
+ { backgroundColor: gradientColors[1] },
137
+ ]}
138
+ />
139
+ <View
140
+ style={[
141
+ styles.gradientSection,
142
+ { backgroundColor: gradientColors[2] },
143
+ ]}
144
+ />
145
+ </View>
146
+ </Animated.View>
147
+ </View>
148
+ </View>
149
+ );
150
+ }
151
+ );
152
+
153
+ const styles = StyleSheet.create({
154
+ container: {
155
+ overflow: 'hidden',
156
+ },
157
+ textContainer: {
158
+ position: 'relative',
159
+ overflow: 'hidden',
160
+ },
161
+ text: {
162
+ fontSize: 16,
163
+ color: '#333333',
164
+ },
165
+ gradientMask: {
166
+ position: 'absolute',
167
+ top: 0,
168
+ left: 0,
169
+ bottom: 0,
170
+ width: 300,
171
+ height: '100%',
172
+ overflow: 'hidden',
173
+ },
174
+ gradientOverlay: {
175
+ flex: 1,
176
+ flexDirection: 'row',
177
+ width: 300,
178
+ height: '100%',
179
+ },
180
+ gradientSection: {
181
+ flex: 1,
182
+ },
183
+ });
184
+
185
+ export default GradientAnimationText;