mirror of
https://github.com/selfxyz/self.git
synced 2026-02-19 02:24:25 -05:00
ux: add country document json info as static asset (#1670)
* add country document json info as static asset * add staleness test * update test * formatting
This commit is contained in:
256
packages/mobile-sdk-alpha/src/data/country-document-types.json
Normal file
256
packages/mobile-sdk-alpha/src/data/country-document-types.json
Normal file
@@ -0,0 +1,256 @@
|
||||
{
|
||||
"ABW": ["p", "i"],
|
||||
"AFG": ["p"],
|
||||
"AGO": ["p", "i"],
|
||||
"AIA": ["p", "i"],
|
||||
"ALA": ["p", "i"],
|
||||
"ALB": ["p", "i"],
|
||||
"AND": ["p", "i"],
|
||||
"ARE": ["p", "i"],
|
||||
"ARG": ["p", "i"],
|
||||
"ARM": ["p", "i"],
|
||||
"ASM": ["p", "i"],
|
||||
"ATA": ["p", "i"],
|
||||
"ATF": ["p", "i"],
|
||||
"ATG": ["p", "i"],
|
||||
"AUS": ["p", "i"],
|
||||
"AUT": ["p", "i"],
|
||||
"AZE": ["p", "i"],
|
||||
"BDI": ["p", "i"],
|
||||
"BEL": ["p", "i"],
|
||||
"BEN": ["p", "i"],
|
||||
"BES": ["p", "i"],
|
||||
"BFA": ["p", "i"],
|
||||
"BGD": ["p", "i"],
|
||||
"BGR": ["p", "i"],
|
||||
"BHR": ["p", "i"],
|
||||
"BHS": ["p", "i"],
|
||||
"BIH": ["p", "i"],
|
||||
"BLM": ["p", "i"],
|
||||
"BLR": ["p", "i"],
|
||||
"BLZ": ["p", "i"],
|
||||
"BMU": ["p", "i"],
|
||||
"BOL": ["p", "i"],
|
||||
"BRA": ["p", "i"],
|
||||
"BRB": ["p", "i"],
|
||||
"BRN": ["p", "i"],
|
||||
"BTN": ["p", "i"],
|
||||
"BVT": ["p", "i"],
|
||||
"BWA": ["p", "i"],
|
||||
"CAF": ["p", "i"],
|
||||
"CAN": ["p", "i"],
|
||||
"CCK": ["p", "i"],
|
||||
"CHE": ["p", "i"],
|
||||
"CHL": ["p", "i"],
|
||||
"CHN": ["p", "i"],
|
||||
"CIV": ["p", "i"],
|
||||
"CMR": ["p", "i"],
|
||||
"COD": ["p", "i"],
|
||||
"COG": ["p", "i"],
|
||||
"COK": ["p", "i"],
|
||||
"COL": ["p", "i"],
|
||||
"COM": ["p", "i"],
|
||||
"CPV": ["p", "i"],
|
||||
"CRI": ["p", "i"],
|
||||
"CUB": ["p", "i"],
|
||||
"CUW": ["p", "i"],
|
||||
"CXR": ["p", "i"],
|
||||
"CYM": ["p", "i"],
|
||||
"CYP": ["p", "i"],
|
||||
"CZE": ["p", "i"],
|
||||
"D<<": ["p", "i"],
|
||||
"DJI": ["p", "i"],
|
||||
"DMA": ["p", "i"],
|
||||
"DNK": ["p", "i"],
|
||||
"DOM": ["p", "i"],
|
||||
"DZA": ["p", "i"],
|
||||
"ECU": ["p", "i"],
|
||||
"EGY": [],
|
||||
"ERI": ["p", "i"],
|
||||
"ESH": ["p", "i"],
|
||||
"ESP": ["p", "i"],
|
||||
"EST": ["p", "i"],
|
||||
"ETH": ["p", "i"],
|
||||
"EUE": ["p", "i"],
|
||||
"FIN": ["p", "i"],
|
||||
"FJI": ["p", "i"],
|
||||
"FLK": ["p", "i"],
|
||||
"FRA": ["p", "i"],
|
||||
"FRO": ["p", "i"],
|
||||
"FSM": ["p", "i"],
|
||||
"GAB": ["p", "i"],
|
||||
"GBR": ["p", "i"],
|
||||
"GEO": ["p", "i"],
|
||||
"GGY": ["p", "i"],
|
||||
"GHA": ["p", "i"],
|
||||
"GIB": ["p", "i"],
|
||||
"GIN": ["p", "i"],
|
||||
"GLP": ["p", "i"],
|
||||
"GMB": ["p", "i"],
|
||||
"GNB": ["p", "i"],
|
||||
"GNQ": ["p", "i"],
|
||||
"GRC": ["p", "i"],
|
||||
"GRD": ["p", "i"],
|
||||
"GRL": ["p", "i"],
|
||||
"GTM": ["p", "i"],
|
||||
"GUF": ["p", "i"],
|
||||
"GUM": ["p", "i"],
|
||||
"GUY": ["p", "i"],
|
||||
"HKG": ["p", "i"],
|
||||
"HMD": ["p", "i"],
|
||||
"HND": ["p", "i"],
|
||||
"HRV": ["p", "i"],
|
||||
"HTI": ["p", "i"],
|
||||
"HUN": ["p", "i"],
|
||||
"IDN": ["p", "i"],
|
||||
"IMN": ["p", "i"],
|
||||
"IND": ["p", "a"],
|
||||
"IOT": ["p", "i"],
|
||||
"IRL": ["p", "i"],
|
||||
"IRN": ["p", "i"],
|
||||
"IRQ": ["p", "i"],
|
||||
"ISL": ["p", "i"],
|
||||
"ISR": ["p", "i"],
|
||||
"ITA": ["p", "i"],
|
||||
"JAM": ["p", "i"],
|
||||
"JEY": ["p", "i"],
|
||||
"JOR": ["p", "i"],
|
||||
"JPN": ["p", "i"],
|
||||
"KAZ": ["p", "i"],
|
||||
"KEN": ["p", "i"],
|
||||
"KGZ": ["p", "i"],
|
||||
"KHM": ["p", "i"],
|
||||
"KIR": ["p", "i"],
|
||||
"KNA": ["p", "i"],
|
||||
"KOR": ["p", "i"],
|
||||
"KWT": ["p", "i"],
|
||||
"LAO": ["p", "i"],
|
||||
"LBN": ["p", "i"],
|
||||
"LBR": ["p", "i"],
|
||||
"LBY": ["p", "i"],
|
||||
"LCA": ["p", "i"],
|
||||
"LIE": ["p", "i"],
|
||||
"LKA": ["p", "i"],
|
||||
"LSO": ["p", "i"],
|
||||
"LTU": ["p", "i"],
|
||||
"LUX": ["p", "i"],
|
||||
"LVA": ["p", "i"],
|
||||
"MAC": ["p", "i"],
|
||||
"MAF": ["p", "i"],
|
||||
"MAR": ["p", "i"],
|
||||
"MCO": ["p", "i"],
|
||||
"MDA": ["p", "i"],
|
||||
"MDG": ["p", "i"],
|
||||
"MDV": ["p", "i"],
|
||||
"MEX": ["p", "i"],
|
||||
"MHL": ["p", "i"],
|
||||
"MKD": ["p", "i"],
|
||||
"MLI": ["p", "i"],
|
||||
"MLT": ["p", "i"],
|
||||
"MMR": ["p", "i"],
|
||||
"MNE": ["p", "i"],
|
||||
"MNG": ["p", "i"],
|
||||
"MNP": ["p", "i"],
|
||||
"MOZ": ["p", "i"],
|
||||
"MRT": ["p", "i"],
|
||||
"MSR": ["p", "i"],
|
||||
"MTQ": ["p", "i"],
|
||||
"MUS": ["p", "i"],
|
||||
"MWI": ["p", "i"],
|
||||
"MYS": ["p", "i"],
|
||||
"MYT": ["p", "i"],
|
||||
"NAM": ["p", "i"],
|
||||
"NCL": ["p", "i"],
|
||||
"NER": ["p", "i"],
|
||||
"NFK": ["p", "i"],
|
||||
"NGA": ["p", "i"],
|
||||
"NIC": ["p", "i"],
|
||||
"NIU": ["p", "i"],
|
||||
"NLD": ["p", "i"],
|
||||
"NOR": ["p", "i"],
|
||||
"NPL": ["p", "i"],
|
||||
"NRU": ["p", "i"],
|
||||
"NZL": ["p", "i"],
|
||||
"OMN": ["p", "i"],
|
||||
"PAK": ["p", "i"],
|
||||
"PAN": ["p", "i"],
|
||||
"PCN": ["p", "i"],
|
||||
"PER": ["p", "i"],
|
||||
"PHL": ["p", "i"],
|
||||
"PLW": ["p", "i"],
|
||||
"PNG": ["p", "i"],
|
||||
"POL": ["p", "i"],
|
||||
"PRI": ["p", "i"],
|
||||
"PRK": ["p", "i"],
|
||||
"PRT": ["p", "i"],
|
||||
"PRY": ["p", "i"],
|
||||
"PSE": ["p", "i"],
|
||||
"PYF": ["p", "i"],
|
||||
"QAT": ["p", "i"],
|
||||
"REU": ["p", "i"],
|
||||
"ROU": ["p", "i"],
|
||||
"RUS": ["p", "i"],
|
||||
"RWA": ["p", "i"],
|
||||
"SAU": ["p", "i"],
|
||||
"SDN": ["p", "i"],
|
||||
"SEN": ["p", "i"],
|
||||
"SGP": ["p", "i"],
|
||||
"SGS": ["p", "i"],
|
||||
"SHN": ["p", "i"],
|
||||
"SJM": ["p", "i"],
|
||||
"SLB": ["p", "i"],
|
||||
"SLE": ["p", "i"],
|
||||
"SLV": ["p", "i"],
|
||||
"SMR": ["p", "i"],
|
||||
"SOM": ["p", "i"],
|
||||
"SPM": ["p", "i"],
|
||||
"SRB": ["p", "i"],
|
||||
"SSD": ["p", "i"],
|
||||
"STP": ["p", "i"],
|
||||
"SUR": ["p", "i"],
|
||||
"SVK": ["p", "i"],
|
||||
"SVN": ["p", "i"],
|
||||
"SWE": ["p", "i"],
|
||||
"SWZ": ["p", "i"],
|
||||
"SXM": ["p", "i"],
|
||||
"SYC": ["p", "i"],
|
||||
"SYR": ["p", "i"],
|
||||
"TCA": ["p", "i"],
|
||||
"TCD": ["p", "i"],
|
||||
"TGO": ["p", "i"],
|
||||
"THA": ["p", "i"],
|
||||
"TJK": ["p", "i"],
|
||||
"TKL": ["p", "i"],
|
||||
"TKM": ["p", "i"],
|
||||
"TLS": ["p", "i"],
|
||||
"TON": ["p", "i"],
|
||||
"TTO": ["p", "i"],
|
||||
"TUN": ["p", "i"],
|
||||
"TUR": ["p", "i"],
|
||||
"TUV": ["p", "i"],
|
||||
"TWN": ["p", "i"],
|
||||
"TZA": ["p", "i"],
|
||||
"UGA": ["p", "i"],
|
||||
"UKR": ["p", "i"],
|
||||
"UMI": ["p", "i"],
|
||||
"UNO": ["p", "i"],
|
||||
"URY": ["p", "i"],
|
||||
"USA": ["p", "i"],
|
||||
"UZB": ["p", "i"],
|
||||
"VAT": ["p", "i"],
|
||||
"VCT": ["p", "i"],
|
||||
"VEN": ["p", "i"],
|
||||
"VGB": ["p", "i"],
|
||||
"VIR": ["p", "i"],
|
||||
"VNM": ["p", "i"],
|
||||
"VUT": ["p", "i"],
|
||||
"WLF": ["p", "i"],
|
||||
"WSM": ["p", "i"],
|
||||
"XCE": ["p", "i"],
|
||||
"XOM": ["p", "i"],
|
||||
"XPO": ["p", "i"],
|
||||
"YEM": ["p", "i"],
|
||||
"ZAF": ["p", "i"],
|
||||
"ZMB": ["p", "i"],
|
||||
"ZWE": ["p", "i"]
|
||||
}
|
||||
@@ -2,12 +2,14 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { getCountry } from 'react-native-localize';
|
||||
|
||||
import { commonNames } from '@selfxyz/common';
|
||||
import { alpha2ToAlpha3 } from '@selfxyz/common/constants/countries';
|
||||
|
||||
import countryDocumentTypesData from '../data/country-document-types.json';
|
||||
|
||||
export interface CountryData {
|
||||
[countryCode: string]: string[];
|
||||
}
|
||||
@@ -29,38 +31,11 @@ function getUserCountryCode(): string | null {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function useCountries() {
|
||||
const [countryData, setCountryData] = useState<CountryData>({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const countryData = countryDocumentTypesData as CountryData;
|
||||
const userCountryCode = useMemo(getUserCountryCode, []);
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
const fetchCountryData = async () => {
|
||||
try {
|
||||
const response = await fetch('https://api.staging.self.xyz/id-picker', {
|
||||
signal: controller.signal,
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'success') {
|
||||
setCountryData(result.data);
|
||||
// if (__DEV__) {
|
||||
// console.log('Set country data:', result.data);
|
||||
// }
|
||||
} else {
|
||||
console.error('API returned non-success status:', result.status);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching country data:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchCountryData();
|
||||
return () => controller.abort();
|
||||
}, []);
|
||||
|
||||
const countryList = useMemo(() => {
|
||||
const allCountries = Object.keys(countryData).map(countryCode => ({
|
||||
key: countryCode,
|
||||
@@ -77,5 +52,5 @@ export function useCountries() {
|
||||
|
||||
const showSuggestion = userCountryCode && countryData[userCountryCode];
|
||||
|
||||
return { countryData, countryList, loading, userCountryCode, showSuggestion };
|
||||
return { countryData, countryList, loading: false, userCountryCode, showSuggestion };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
/**
|
||||
* Integration test for country data synchronization.
|
||||
*
|
||||
* This test verifies that the bundled country-document-types.json matches
|
||||
* the staging API response. It gracefully skips when network is unavailable
|
||||
* to avoid CI flakiness from transient network issues.
|
||||
*
|
||||
* To run integration tests only: yarn test --grep="integration"
|
||||
* To skip integration tests: yarn test --grep="^(?!.*integration)"
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import countryDocumentTypesData from '../../src/data/country-document-types.json';
|
||||
|
||||
/**
|
||||
* Helper to check if an error is a network-related error that should cause
|
||||
* the test to skip rather than fail.
|
||||
*/
|
||||
function isNetworkError(error: unknown): boolean {
|
||||
if (!(error instanceof Error)) return false;
|
||||
|
||||
const networkErrorPatterns = [
|
||||
'ENOTFOUND', // DNS resolution failed
|
||||
'ECONNREFUSED', // Connection refused
|
||||
'ECONNRESET', // Connection reset
|
||||
'ETIMEDOUT', // Connection timed out
|
||||
'EAI_AGAIN', // DNS temporary failure
|
||||
'ENETUNREACH', // Network unreachable
|
||||
'EHOSTUNREACH', // Host unreachable
|
||||
'fetch failed', // Generic fetch failure
|
||||
'network', // Generic network error
|
||||
'AbortError', // Request aborted (timeout)
|
||||
];
|
||||
|
||||
const errorMessage = error.message.toLowerCase();
|
||||
const errorName = error.name;
|
||||
|
||||
return networkErrorPatterns.some(
|
||||
pattern =>
|
||||
errorMessage.includes(pattern.toLowerCase()) ||
|
||||
errorName === pattern ||
|
||||
('cause' in error &&
|
||||
error.cause instanceof Error &&
|
||||
error.cause.message.toLowerCase().includes(pattern.toLowerCase())),
|
||||
);
|
||||
}
|
||||
|
||||
describe('Country data synchronization [integration]', () => {
|
||||
it('bundled data should match API response', async ({ skip }) => {
|
||||
// Fetch current data from staging API with timeout
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
||||
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch('https://api.staging.self.xyz/id-picker', {
|
||||
signal: controller.signal,
|
||||
});
|
||||
} catch (error) {
|
||||
// Network errors should skip the test, not fail it
|
||||
if (isNetworkError(error)) {
|
||||
skip();
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
// Non-2xx responses that aren't network errors should also skip
|
||||
// (e.g., 503 Service Unavailable, 502 Bad Gateway)
|
||||
if (!response.ok) {
|
||||
if (response.status >= 500) {
|
||||
skip();
|
||||
return;
|
||||
}
|
||||
// 4xx errors are likely real issues, so we let them fail
|
||||
expect.fail(`API returned ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
expect(result.status).toBe('success');
|
||||
|
||||
const apiData = result.data;
|
||||
const bundledData = countryDocumentTypesData;
|
||||
|
||||
// Compare the data structures
|
||||
expect(bundledData).toEqual(apiData);
|
||||
|
||||
// If this test fails, it means the API has been updated with new countries
|
||||
// or document types that aren't in the bundled data yet.
|
||||
// To fix: Update src/data/country-document-types.json with the latest API data.
|
||||
}, 10000); // 10s Vitest timeout
|
||||
});
|
||||
Reference in New Issue
Block a user