card balance on homepage
This commit is contained in:
@@ -724,6 +724,209 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/card-balance-quick-check": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "cardBalance.quickCheck",
|
||||||
|
"summary": "Quick card balance check",
|
||||||
|
"tags": [
|
||||||
|
"Sargytlar - Kart - Kart galyndylary"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "passport_serie",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"I-AS",
|
||||||
|
"I-MR",
|
||||||
|
"II-MR",
|
||||||
|
"I-AH",
|
||||||
|
"II-AH",
|
||||||
|
"I-LB",
|
||||||
|
"II-LB",
|
||||||
|
"I-BN",
|
||||||
|
"II-BN",
|
||||||
|
"I-DZ",
|
||||||
|
"II-DZ"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"example": "I-AS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "passport_id",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"example": 379514
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "card_number",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"example": "9934612100000243"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "card_month",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"01",
|
||||||
|
"02",
|
||||||
|
"03",
|
||||||
|
"04",
|
||||||
|
"05",
|
||||||
|
"06",
|
||||||
|
"07",
|
||||||
|
"08",
|
||||||
|
"09",
|
||||||
|
"10",
|
||||||
|
"11",
|
||||||
|
"12"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"example": "12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "card_year",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"2024",
|
||||||
|
"2025",
|
||||||
|
"2026",
|
||||||
|
"2027",
|
||||||
|
"2028",
|
||||||
|
"2029",
|
||||||
|
"2030",
|
||||||
|
"2031",
|
||||||
|
"2032",
|
||||||
|
"2033",
|
||||||
|
"2034",
|
||||||
|
"2035",
|
||||||
|
"2036",
|
||||||
|
"2037",
|
||||||
|
"2038",
|
||||||
|
"2039",
|
||||||
|
"2040",
|
||||||
|
"2041",
|
||||||
|
"2042",
|
||||||
|
"2043",
|
||||||
|
"2044",
|
||||||
|
"2045",
|
||||||
|
"2046",
|
||||||
|
"2047",
|
||||||
|
"2048",
|
||||||
|
"2049",
|
||||||
|
"2050",
|
||||||
|
"2051",
|
||||||
|
"2052",
|
||||||
|
"2053",
|
||||||
|
"2054",
|
||||||
|
"2055",
|
||||||
|
"2056",
|
||||||
|
"2057",
|
||||||
|
"2058",
|
||||||
|
"2059",
|
||||||
|
"2060",
|
||||||
|
"2061",
|
||||||
|
"2062",
|
||||||
|
"2063",
|
||||||
|
"2064",
|
||||||
|
"2065",
|
||||||
|
"2066",
|
||||||
|
"2067",
|
||||||
|
"2068",
|
||||||
|
"2069",
|
||||||
|
"2070",
|
||||||
|
"2071",
|
||||||
|
"2072",
|
||||||
|
"2073",
|
||||||
|
"2074",
|
||||||
|
"2075",
|
||||||
|
"2076",
|
||||||
|
"2077",
|
||||||
|
"2078",
|
||||||
|
"2079",
|
||||||
|
"2080",
|
||||||
|
"2081",
|
||||||
|
"2082",
|
||||||
|
"2083",
|
||||||
|
"2084",
|
||||||
|
"2085",
|
||||||
|
"2086",
|
||||||
|
"2087",
|
||||||
|
"2088",
|
||||||
|
"2089",
|
||||||
|
"2090"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"example": "2049"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"status",
|
||||||
|
"data"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"status",
|
||||||
|
"message",
|
||||||
|
"url"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"$ref": "#/components/responses/AuthenticationException"
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"$ref": "#/components/responses/ValidationException"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/card-balances": {
|
"/card-balances": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "cardBalance.index",
|
"operationId": "cardBalance.index",
|
||||||
@@ -6795,6 +6998,9 @@
|
|||||||
},
|
},
|
||||||
"card_year": {
|
"card_year": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"card_name": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@@ -6804,7 +7010,8 @@
|
|||||||
"passport_id",
|
"passport_id",
|
||||||
"card_number",
|
"card_number",
|
||||||
"card_month",
|
"card_month",
|
||||||
"card_year"
|
"card_year",
|
||||||
|
"card_name"
|
||||||
],
|
],
|
||||||
"title": "ProfileResponse"
|
"title": "ProfileResponse"
|
||||||
},
|
},
|
||||||
@@ -6947,6 +7154,11 @@
|
|||||||
"2090"
|
"2090"
|
||||||
],
|
],
|
||||||
"example": "2049"
|
"example": "2049"
|
||||||
|
},
|
||||||
|
"card_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Nurmuhammet Allanov",
|
||||||
|
"maxLength": 255
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
|||||||
@@ -19,6 +19,14 @@ const HomeScreen = () => {
|
|||||||
const { user, logout } = useAuth();
|
const { user, logout } = useAuth();
|
||||||
const [metrics, setMetrics] = useState(null);
|
const [metrics, setMetrics] = useState(null);
|
||||||
const [loadingMetrics, setLoadingMetrics] = useState(true);
|
const [loadingMetrics, setLoadingMetrics] = useState(true);
|
||||||
|
const [cardBalance, setCardBalance] = useState(null);
|
||||||
|
const [loadingCardBalance, setLoadingCardBalance] = useState(true);
|
||||||
|
const [cardBalanceError, setCardBalanceError] = useState(null);
|
||||||
|
const [isBalanceVisible, setIsBalanceVisible] = useState(true);
|
||||||
|
|
||||||
|
const showBalanceCard = !loadingCardBalance && cardBalanceError === null && cardBalance !== null;
|
||||||
|
|
||||||
|
// (Optional) Add helpers here if needed in the future
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchMetrics = async () => {
|
const fetchMetrics = async () => {
|
||||||
@@ -39,6 +47,38 @@ const HomeScreen = () => {
|
|||||||
fetchMetrics();
|
fetchMetrics();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchCardBalance = async () => {
|
||||||
|
// Ensure user has filled all required card & passport fields
|
||||||
|
if (!user?.passport_serie || !user?.passport_id || !user?.card_number || !user?.card_month || !user?.card_year) {
|
||||||
|
setLoadingCardBalance(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await apiService.getCardBalanceQuickCheck();
|
||||||
|
if (res.success) {
|
||||||
|
// Try common balance keys else fallback to raw
|
||||||
|
const raw = res.data;
|
||||||
|
let balanceValue = null;
|
||||||
|
if (raw && typeof raw === 'object') {
|
||||||
|
balanceValue = raw.balance ?? raw.card_balance ?? raw.amount ?? null;
|
||||||
|
}
|
||||||
|
setCardBalance(balanceValue);
|
||||||
|
setCardBalanceError(null);
|
||||||
|
} else {
|
||||||
|
console.warn('Failed to fetch card balance:', res.error);
|
||||||
|
setCardBalanceError(res.error || 'Error');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Error fetching card balance:', e);
|
||||||
|
} finally {
|
||||||
|
setLoadingCardBalance(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchCardBalance();
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container}>
|
<SafeAreaView style={styles.container}>
|
||||||
<StatusBar style="dark" />
|
<StatusBar style="dark" />
|
||||||
@@ -67,19 +107,25 @@ const HomeScreen = () => {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Balance Card */}
|
{/* Balance Card */}
|
||||||
|
{showBalanceCard && (
|
||||||
<View style={styles.balanceCard}>
|
<View style={styles.balanceCard}>
|
||||||
<View style={styles.balanceHeader}>
|
<View style={styles.balanceHeader}>
|
||||||
<Text style={styles.balanceLabel}>Jemi balans</Text>
|
<Text style={styles.balanceLabel}>Jemi balans</Text>
|
||||||
<TouchableOpacity>
|
<TouchableOpacity onPress={() => setIsBalanceVisible((prev) => !prev)}>
|
||||||
<Ionicons name="eye" size={20} color={COLORS.white} />
|
<Ionicons name={isBalanceVisible ? 'eye' : 'eye-off'} size={20} color={COLORS.white} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.balanceAmount}>1,250.00 TMT</Text>
|
<Text style={styles.balanceAmount}>
|
||||||
|
{isBalanceVisible ? `${cardBalance} TMT` : '•••••'}
|
||||||
|
</Text>
|
||||||
<View style={styles.balanceFooter}>
|
<View style={styles.balanceFooter}>
|
||||||
<Text style={styles.accountNumber}>Hasap: ****1234</Text>
|
<Text style={styles.accountNumber}>
|
||||||
|
{user?.card_number ? `Hasap: ****${String(user.card_number).replace(/[^0-9]/g, '').slice(-4)}` : 'Hasap: ----'}
|
||||||
|
</Text>
|
||||||
<View style={styles.cardChip} />
|
<View style={styles.cardChip} />
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
)}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -165,6 +165,29 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Quick card balance check
|
||||||
|
async getCardBalanceQuickCheck(cardNumber = null, cardMonth = null, cardYear = null, passportSerie = null, passportId = null) {
|
||||||
|
try {
|
||||||
|
let raw = await authService.getCardBalanceQuickCheck(cardNumber, cardMonth, cardYear, passportSerie, passportId);
|
||||||
|
|
||||||
|
// Handle text response that is JSON-stringified
|
||||||
|
if (typeof raw === 'string') {
|
||||||
|
try { raw = JSON.parse(raw); } catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When API returns { status: false, message: '...' }
|
||||||
|
if (raw && raw.status === false) {
|
||||||
|
return { success: false, error: raw.message || 'Failed', data: raw };
|
||||||
|
}
|
||||||
|
|
||||||
|
// On success, data may be inside raw.data or raw itself
|
||||||
|
const data = raw?.data ?? raw;
|
||||||
|
return { success: true, data };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ================================
|
// ================================
|
||||||
// Loan Paid-Off Letter Orders
|
// Loan Paid-Off Letter Orders
|
||||||
// ================================
|
// ================================
|
||||||
|
|||||||
@@ -370,6 +370,47 @@ class AuthService {
|
|||||||
const query = `start_date=${encodeURIComponent(startDate)}&end_date=${encodeURIComponent(endDate)}`;
|
const query = `start_date=${encodeURIComponent(startDate)}&end_date=${encodeURIComponent(endDate)}`;
|
||||||
return this.makeRequest(`/card-transactions-download/${orderId}?${query}`, null, true, 'GET');
|
return this.makeRequest(`/card-transactions-download/${orderId}?${query}`, null, true, 'GET');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ================================
|
||||||
|
// Card balance quick check (Kart galyndysy)
|
||||||
|
// ================================
|
||||||
|
|
||||||
|
async getCardBalanceQuickCheck(cardNumber = null, cardMonth = null, cardYear = null, passportSerie = null, passportId = null) {
|
||||||
|
// Fallback to stored user profile for missing fields
|
||||||
|
let serie = passportSerie;
|
||||||
|
let pid = passportId;
|
||||||
|
let cNumber = cardNumber;
|
||||||
|
let cMonth = cardMonth;
|
||||||
|
let cYear = cardYear;
|
||||||
|
|
||||||
|
if (!serie || !pid || !cNumber || !cMonth || !cYear) {
|
||||||
|
const user = await this.getStoredUser();
|
||||||
|
serie = serie || user?.passport_serie;
|
||||||
|
pid = pid || user?.passport_id;
|
||||||
|
cNumber = cNumber || user?.card_number;
|
||||||
|
cMonth = cMonth || user?.card_month;
|
||||||
|
cYear = cYear || user?.card_year;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!serie || !pid || !cNumber || !cMonth || !cYear) {
|
||||||
|
throw new Error('Card or passport details are missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure values are properly formatted
|
||||||
|
const plainCardNumber = String(cNumber).replace(/[^0-9]/g, '').slice(0, 16);
|
||||||
|
const monthStr = String(cMonth).padStart(2, '0');
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
passport_serie: serie,
|
||||||
|
passport_id: pid,
|
||||||
|
card_number: plainCardNumber,
|
||||||
|
card_month: monthStr,
|
||||||
|
card_year: String(cYear),
|
||||||
|
};
|
||||||
|
|
||||||
|
// POST request – authenticated (token header will be included if available)
|
||||||
|
return this.makeRequest('/card-balance-quick-check', payload, true, 'POST');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new AuthService();
|
export default new AuthService();
|
||||||
Reference in New Issue
Block a user