From 1c7f6957cad448f2a41869ae096a37f06ebec6f9 Mon Sep 17 00:00:00 2001 From: MOHAN Date: Sun, 22 Mar 2026 00:32:56 +0530 Subject: [PATCH] fixed known bugs --- src/components/CodeExamples.jsx | 7 +- src/components/FAQ.jsx | 3 +- src/components/Hero.jsx | 3 +- src/index.css | 47 ++++++++++++ src/pages/APIKeys.jsx | 48 +++++++----- src/pages/Analytics.jsx | 28 ++++++- src/pages/Billing.jsx | 120 ++++++++++++++++++++++-------- src/pages/Docs.jsx | 15 ++-- src/pages/Integrate.jsx | 58 ++++++++------- src/pages/MessageConfig.jsx | 53 +++++++++---- src/pages/Overview.jsx | 11 ++- src/pages/Settings.jsx | 31 ++++++-- src/pages/auth/ForgotPassword.jsx | 3 +- src/pages/auth/Signup.jsx | 3 +- src/utils/pageCache.js | 33 ++++++++ 15 files changed, 346 insertions(+), 117 deletions(-) create mode 100644 src/utils/pageCache.js diff --git a/src/components/CodeExamples.jsx b/src/components/CodeExamples.jsx index 879943b..fad88a2 100644 --- a/src/components/CodeExamples.jsx +++ b/src/components/CodeExamples.jsx @@ -10,7 +10,7 @@ const codes = { const client = Veriflo.init('your-api-key'); // Send OTP to any WhatsApp number -const { requestId } = await client.sendOTP('+919999999999', { +const { requestId } = await client.sendOTP('+12025550123', { length: 6, // OTP digit length expiry: 60, // seconds }); @@ -29,7 +29,7 @@ await client.resendOTP(requestId);`, client = Veriflo(api_key="your-api-key") # Send OTP to any WhatsApp number -result = client.send_otp("+919999999999", length=6, expiry=60) +result = client.send_otp("+12025550123", length=6, expiry=60) # Verify OTP entered by user is_valid = client.verify_otp(result.request_id, "482916") @@ -44,7 +44,7 @@ client.resend_otp(result.request_id)`, curl -X POST https://api.veriflo.app/v1/otp/send \\ -H "Authorization: Bearer YOUR_API_KEY" \\ -H "Content-Type: application/json" \\ - -d '{"phone": "+919999999999", "otpLength": 6}' + -d '{"phone": "+12025550123", "otpLength": 6}' # Response: { "success": true, "requestId": "req_abc123" } @@ -108,3 +108,4 @@ export default function CodeExamples() { ) } + diff --git a/src/components/FAQ.jsx b/src/components/FAQ.jsx index af45167..0fc3d2f 100644 --- a/src/components/FAQ.jsx +++ b/src/components/FAQ.jsx @@ -29,7 +29,7 @@ const faqs = [ }, { q: 'What phone number formats are accepted?', - a: 'We accept E.164 format (+91XXXXXXXXXX). The number must have WhatsApp installed. We support all countries where WhatsApp operates.', + a: 'We accept E.164 format (+). The number must have WhatsApp installed. We support all countries where WhatsApp operates.', }, { q: 'Can users request OTP resend?', @@ -112,3 +112,4 @@ export default function FAQ() { ) } + diff --git a/src/components/Hero.jsx b/src/components/Hero.jsx index e0ec994..dd70c76 100644 --- a/src/components/Hero.jsx +++ b/src/components/Hero.jsx @@ -7,7 +7,7 @@ import { marketingConfig } from '../config/marketingConfig' const heroCode = `import Veriflo from 'veriflo'; const client = Veriflo.init('YOUR_API_KEY'); -await client.sendOTP('+919999999999');` +await client.sendOTP('+12025550123');` const OTP_SEQUENCES = ['4821', '738291', '92847165', '5173', '294817', '83726105', '6291', '582047', '17495820'] const HERO_HEADLINES = [ @@ -236,3 +236,4 @@ export default function Hero() { ) } + diff --git a/src/index.css b/src/index.css index 3544343..81ffd6c 100644 --- a/src/index.css +++ b/src/index.css @@ -355,6 +355,53 @@ background-color: rgba(15, 23, 42, 0.04) !important; } +[data-theme='light'] .bg-white\/12 { + background-color: rgba(15, 23, 42, 0.07) !important; +} + +[data-theme='light'] .hover\:bg-white\/5:hover, +[data-theme='light'] .hover\:bg-white\/8:hover, +[data-theme='light'] .hover\:bg-white\/10:hover, +[data-theme='light'] .hover\:bg-white\/12:hover { + background-color: rgba(15, 23, 42, 0.08) !important; +} + +[data-theme='light'] .hover\:text-white:hover, +[data-theme='light'] .focus\:text-white:focus { + color: var(--color-text-primary) !important; +} + +[data-theme='light'] .group:hover .group-hover\:text-white { + color: var(--color-text-primary) !important; +} + +[data-theme='light'] .group:hover .group-hover\:text-white\/10, +[data-theme='light'] .group:hover .group-hover\:text-white\/5 { + color: rgba(15, 23, 42, 0.16) !important; +} + +[data-theme='light'] .glass-panel { + background: rgba(255,255,255,0.7); + border-color: rgba(15,23,42,0.12); +} + +[data-theme='light'] .glass-panel:hover { + background: rgba(255,255,255,0.88); + border-color: rgba(15,23,42,0.18); +} + +[data-theme='light'] .solid-panel { + background: #f8fbff; + border-color: rgba(15,23,42,0.1); + box-shadow: 0 14px 40px rgba(15,23,42,0.12); +} + +[data-theme='light'] .btn-icon:hover { + color: var(--color-text-primary); + background: rgba(15,23,42,0.08); + border-color: rgba(15,23,42,0.18); +} + /* Animations */ @keyframes float { 0%, 100% { transform: translateY(0px); } diff --git a/src/pages/APIKeys.jsx b/src/pages/APIKeys.jsx index 793ede8..b6e87ac 100644 --- a/src/pages/APIKeys.jsx +++ b/src/pages/APIKeys.jsx @@ -3,6 +3,7 @@ import { Plus, Copy, Check, Trash2, Loader2, AlertCircle, Download, KeyRound, Ci import api from '../services/api' import { useNavigate } from 'react-router-dom' import { useToast } from '../components/ToastProvider' +import { readPageCache, writePageCache } from '../utils/pageCache' function formatDateTime(value) { if (!value) return 'Never' @@ -62,7 +63,7 @@ const HELP_TOPICS = [ content: `curl -X POST http://localhost:3000/v1/otp/send \\ -H "Authorization: Bearer YOUR_API_KEY" \\ -H "Content-Type: application/json" \\ - -d '{"phone":"+919999999999"}'`, + -d '{"phone":"+12025550123"}'`, }, { id: 'best-practices', @@ -111,7 +112,7 @@ const HELP_TOPICS = [ code: `const Veriflo = require('veriflo'); const client = Veriflo.init('YOUR_API_KEY'); -const result = await client.sendOTP('+919999999999', { length: 6 }); +const result = await client.sendOTP('+12025550123', { length: 6 }); console.log(result.request_id);`, }, { @@ -119,7 +120,7 @@ console.log(result.request_id);`, code: `from veriflo import Veriflo client = Veriflo(api_key="YOUR_API_KEY") -result = client.send_otp("+919999999999", length=6) +result = client.send_otp("+12025550123", length=6) print(result.request_id)`, }, { @@ -135,7 +136,7 @@ Content-Type: application/json`, Authorization: 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' }, - body: JSON.stringify({ phone: '+919999999999' }) + body: JSON.stringify({ phone: '+12025550123' }) });`, }, ], @@ -143,10 +144,11 @@ Content-Type: application/json`, ] export default function APIKeys() { + const cached = readPageCache('api_keys_page', 90 * 1000) const navigate = useNavigate() const toast = useToast() - const [keys, setKeys] = useState([]) - const [loading, setLoading] = useState(true) + const [keys, setKeys] = useState(cached?.keys || []) + const [loading, setLoading] = useState(!cached) const [generating, setGenerating] = useState(false) const [deletingId, setDeletingId] = useState(null) const [copied, setCopied] = useState('') @@ -154,9 +156,9 @@ export default function APIKeys() { const [error, setError] = useState('') const [keyName, setKeyName] = useState('') const [keyMode, setKeyMode] = useState('sandbox') - const [keyLimitPerMode, setKeyLimitPerMode] = useState(2) - const [keyLimitTotal, setKeyLimitTotal] = useState(4) - const [trialStatus, setTrialStatus] = useState('not_applied') + const [keyLimitPerMode, setKeyLimitPerMode] = useState(cached?.keyLimitPerMode || 2) + const [keyLimitTotal, setKeyLimitTotal] = useState(cached?.keyLimitTotal || 4) + const [trialStatus, setTrialStatus] = useState(cached?.trialStatus || 'not_applied') const [activeHelpId, setActiveHelpId] = useState('') const [showCreateModal, setShowCreateModal] = useState(false) @@ -171,25 +173,36 @@ export default function APIKeys() { const liveKeys = keys.filter((key) => key.mode === 'live') useEffect(() => { - fetchKeys() + fetchKeys({ silent: Boolean(cached) }) }, []) - const fetchKeys = async () => { - setLoading(true) + const fetchKeys = async ({ silent = false } = {}) => { + if (!silent) setLoading(true) try { const profileRes = await api.get('/api/user/profile') - setTrialStatus(profileRes.data.plan?.trial_status || 'not_applied') + const nextTrialStatus = profileRes.data.plan?.trial_status || 'not_applied' + setTrialStatus(nextTrialStatus) const res = await api.get('/api/user/api-keys') - setKeys(res.data.keys || []) - setKeyLimitPerMode(res.data.limit_per_mode || 2) - setKeyLimitTotal(res.data.limit_total || 4) + const nextKeys = res.data.keys || [] + const nextLimitPerMode = res.data.limit_per_mode || 2 + const nextLimitTotal = res.data.limit_total || 4 + + setKeys(nextKeys) + setKeyLimitPerMode(nextLimitPerMode) + setKeyLimitTotal(nextLimitTotal) + writePageCache('api_keys_page', { + keys: nextKeys, + keyLimitPerMode: nextLimitPerMode, + keyLimitTotal: nextLimitTotal, + trialStatus: nextTrialStatus, + }) } catch (err) { const message = err.response?.data?.error || 'Failed to load keys' setError(message) toast.error(message) } finally { - setLoading(false) + if (!silent) setLoading(false) } } @@ -567,3 +580,4 @@ export default function APIKeys() { ) } + diff --git a/src/pages/Analytics.jsx b/src/pages/Analytics.jsx index 6d3a08e..7335bcf 100644 --- a/src/pages/Analytics.jsx +++ b/src/pages/Analytics.jsx @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react' import { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts' import { Download, RefreshCw, AlertCircle, Loader2 } from 'lucide-react' import api from '../services/api' +import { readPageCache, writePageCache } from '../utils/pageCache' export default function Analytics() { const [data, setData] = useState(null) @@ -16,15 +17,34 @@ export default function Analytics() { }, [page, pageSize]) const fetchData = async () => { - setLoading(true) + const cacheKey = `analytics_page_${page}_${pageSize}` + const cached = readPageCache(cacheKey, 75 * 1000) + if (cached) { + setData(cached.data || null) + setLogs(cached.logs || []) + setPagination(cached.pagination || { total: 0, page: 1, page_size: pageSize, total_pages: 1, has_prev: false, has_next: false }) + setLoading(false) + } else { + setLoading(true) + } + try { const [summaryRes, logsRes] = await Promise.all([ api.get('/api/user/analytics/summary'), api.get(`/api/user/analytics/logs?page=${page}&page_size=${pageSize}`) ]) - setData(summaryRes.data) - setLogs(logsRes.data.logs) - setPagination(logsRes.data.pagination || { total: 0, page: 1, page_size: pageSize, total_pages: 1, has_prev: false, has_next: false }) + const nextData = summaryRes.data + const nextLogs = logsRes.data.logs + const nextPagination = logsRes.data.pagination || { total: 0, page: 1, page_size: pageSize, total_pages: 1, has_prev: false, has_next: false } + + setData(nextData) + setLogs(nextLogs) + setPagination(nextPagination) + writePageCache(cacheKey, { + data: nextData, + logs: nextLogs, + pagination: nextPagination, + }) } catch (err) { console.error("Failed to fetch analytics", err) } finally { diff --git a/src/pages/Billing.jsx b/src/pages/Billing.jsx index 1857462..a5e9496 100644 --- a/src/pages/Billing.jsx +++ b/src/pages/Billing.jsx @@ -4,19 +4,22 @@ import api from '../services/api' import { useToast } from '../components/ToastProvider' import { getAuthUser } from '../utils/authSession' import { useNavigate } from 'react-router-dom' +import { readPageCache, writePageCache } from '../utils/pageCache' export default function Billing() { + const cached = readPageCache('billing_page', 90 * 1000) const navigate = useNavigate() const toast = useToast() - const [stats, setStats] = useState(null) - const [plan, setPlan] = useState(null) - const [limits, setLimits] = useState(null) - const [billingConfig, setBillingConfig] = useState(null) - const [payments, setPayments] = useState([]) - const [loading, setLoading] = useState(true) + const [stats, setStats] = useState(cached?.stats || null) + const [plan, setPlan] = useState(cached?.plan || null) + const [limits, setLimits] = useState(cached?.limits || null) + const [billingConfig, setBillingConfig] = useState(cached?.billingConfig || null) + const [payments, setPayments] = useState(cached?.payments || []) + const [loading, setLoading] = useState(!cached) const [actionLoading, setActionLoading] = useState(false) const [payingCode, setPayingCode] = useState('') const [selectedPackageCode, setSelectedPackageCode] = useState('growth') + const [showTrialApplyModal, setShowTrialApplyModal] = useState(false) const [showQuotaHelp, setShowQuotaHelp] = useState(false) const [showPurchaseHelp, setShowPurchaseHelp] = useState(false) const [showHistoryHelp, setShowHistoryHelp] = useState(false) @@ -26,48 +29,46 @@ export default function Billing() { }, []) const fetchBillingInfo = async () => { + const silent = Boolean(stats && plan && limits) + if (!silent) setLoading(true) try { const [profileRes, paymentsRes, billingConfigRes] = await Promise.all([ api.get('/api/user/profile'), api.get('/api/user/billing/payments'), api.get('/api/user/billing/config'), ]) - setStats(profileRes.data.stats) - setPlan(profileRes.data.plan) - setLimits(profileRes.data.limits) - setBillingConfig(billingConfigRes.data.config || null) - setPayments(paymentsRes.data.payments || []) + const nextStats = profileRes.data.stats + const nextPlan = profileRes.data.plan + const nextLimits = profileRes.data.limits + const nextBillingConfig = billingConfigRes.data.config || null + const nextPayments = paymentsRes.data.payments || [] + + setStats(nextStats) + setPlan(nextPlan) + setLimits(nextLimits) + setBillingConfig(nextBillingConfig) + setPayments(nextPayments) + writePageCache('billing_page', { + stats: nextStats, + plan: nextPlan, + limits: nextLimits, + billingConfig: nextBillingConfig, + payments: nextPayments, + }) } catch (err) { console.error("Failed to fetch billing info", err) } finally { - setLoading(false) + if (!silent) setLoading(false) } } const handleApply = async () => { - const confirmed = window.confirm( - [ - 'Activate Free Trial?', - '', - 'By continuing, you agree to the free trial rules:', - `1. Sandbox trial quota: ${stats?.trial_total_credits || 500} OTPs/month (self WhatsApp number only).`, - `2. Live starter quota: ${stats?.live_total_credits || 100} OTPs/month (can send to other valid numbers).`, - '3. Sandbox and Live use separate API key sets and separate usage counters.', - '4. Misuse, spam, or policy violations may suspend trial access.', - '', - 'Click OK to agree and activate now.' - ].join('\n') - ) - - if (!confirmed) { - return - } - setActionLoading(true) try { await api.post('/api/user/trial/apply') const res = await api.post('/api/user/trial/activate') toast.success(res.data.message || 'Free trial activated successfully') + setShowTrialApplyModal(false) await fetchBillingInfo() } catch (err) { toast.error(err.response?.data?.error || 'Failed to activate free trial') @@ -209,8 +210,8 @@ export default function Billing() {
{plan?.trial_status === 'not_applied' && ( - )} {plan?.trial_status === 'applied' && ( @@ -358,6 +359,61 @@ export default function Billing() {
) : null} + {showTrialApplyModal ? ( +
+ +
+
+
+ 1. Sandbox trial quota: {stats?.trial_total_credits || 500} OTPs/month (self WhatsApp number only). +
+
+ 2. Live starter quota: {stats?.live_total_credits || 100} OTPs/month (can send to valid external numbers). +
+
+ 3. Sandbox and Live use separate API key sets and separate usage counters. +
+
+ 4. Misuse, spam, or policy violations may suspend trial access. +
+
+
+ + +
+ + + ) : null} + {showPurchaseHelp ? (
@@ -317,7 +317,7 @@ function SendOTP() { + -d '{"phone": "+12025550123", "otpLength": 6}'`} language="bash" />

Response

@@ -581,3 +581,4 @@ function RateLimits() { ) } + diff --git a/src/pages/Integrate.jsx b/src/pages/Integrate.jsx index b7e75df..47a4075 100644 --- a/src/pages/Integrate.jsx +++ b/src/pages/Integrate.jsx @@ -34,14 +34,29 @@ export default function Integrate() { return () => { mounted = false } }, []) + const normalizeE164 = (value) => { + const trimmed = String(value || '').trim() + if (!trimmed) return '' + const digits = trimmed.replace(/\D/g, '').slice(0, 15) + if (!digits) return '' + return `+${digits}` + } + + const phoneForApi = normalizeE164(testPhone).replace(/\D/g, '') + const displayPhone = normalizeE164(testPhone) || '+12025550123' + const handleTestSend = async (e) => { e.preventDefault() + if (phoneForApi.length < 8) { + setError('Enter a valid phone number with country code.') + return + } setSending(true) setError('') setSent(false) try { - await api.post('/api/user/test-otp', { phone: '91' + testPhone }) + await api.post('/api/user/test-otp', { phone: phoneForApi }) setSent(true) window.dispatchEvent(new CustomEvent('veriflo-usage-updated')) toast.success('Test OTP sent successfully') @@ -63,7 +78,7 @@ const Veriflo = require('veriflo'); const client = Veriflo.init('${key}'); // Send OTP -const { requestId } = await client.sendOTP('+91${testPhone || '9999999999'}', { length: 6 }); +const { requestId } = await client.sendOTP('${displayPhone}', { length: 6 }); // Verify OTP later const verified = await client.verifyOTP(requestId, '123456'); @@ -78,7 +93,7 @@ from veriflo import Veriflo client = Veriflo(api_key="${key}") # Send OTP -result = client.send_otp("+91${testPhone || '9999999999'}", length=6) +result = client.send_otp("${displayPhone}", length=6) is_valid = client.verify_otp(result.request_id, "123456") @@ -88,7 +103,7 @@ print('Verified?', is_valid)` if(tab === 'HTTP') return `curl -X POST http://localhost:3000/v1/otp/send \\ -H "Authorization: Bearer ${key}" \\ -H "Content-Type: application/json" \\ - -d '{"phone": "+91${testPhone || '9999999999'}"}' + -d '{"phone": "${displayPhone}"}' curl -X POST http://localhost:3000/v1/otp/verify \\ -H "Authorization: Bearer ${key}" \\ @@ -189,30 +204,23 @@ curl -X POST http://localhost:3000/v1/otp/verify \\
-
- - setTestPhone(e.target.value)} - className="neu-input min-w-0 tracking-[0.12em] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/55" - aria-label="Recipient phone number" - inputMode="numeric" - autoComplete="tel-national" - /> -
+ setTestPhone(e.target.value)} + className="neu-input min-w-0 tracking-[0.08em] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/55" + aria-label="Recipient phone number" + inputMode="tel" + autoComplete="tel" + /> +
Use international format with country code, like +1, +44, +81.