adding aws db
All checks were successful
Beta CI/CD Pipeline / build-and-test (push) Successful in 49s
Beta CI/CD Pipeline / deploy-beta (push) Successful in 6s

This commit is contained in:
vickytechkey 2026-08-12 18:18:47 +05:30
parent 05ffef32ed
commit 2f6381870d
6 changed files with 297 additions and 36 deletions

BIN
HLD_optimized.docx Normal file

Binary file not shown.

BIN
HLD_optimized.pptx Normal file

Binary file not shown.

BIN
business_overview.pptx Normal file

Binary file not shown.

View file

@ -8,7 +8,29 @@ beforeAll(() => {
globalThis.fetch = vi.fn((url) => { globalThis.fetch = vi.fn((url) => {
let responseData: any = {}; let responseData: any = {};
if (url.includes('/api/auth/register') || url.includes('/api/auth/login')) { if (url.includes('/api/auth/register') || url.includes('/api/auth/login')) {
responseData = { id: 1, username: 'test_seller', email: 'test@example.com' }; responseData = {
access_token: 'mock_access_token',
refresh_token: 'mock_refresh_token',
user: {
id: 1,
username: 'test_seller',
email: 'test@example.com',
profile: { onboarding_step: 4, phone_verified: true, email_verified: true, is_gstin_verified: true, aadhar_s3_key: 'mock_aadhar', pan_s3_key: 'mock_pan', store_name: 'Crafty Store', logo_s3_key: 'mock_logo', street: '123 Loom Lane', status: 'pending_approval' }
}
};
} else if (url.includes('/api/auth/verify-otp')) {
responseData = { verified: true, next_step: 'complete_profile' };
} else if (url.includes('/api/profile/submit-gstin')) {
responseData = { verified: true, business_name: 'TEST BUSINESS', address: 'Test address', gstin_status: 'Active' };
} else if (url.includes('/api/profile/presigned-url')) {
responseData = { presigned_url: 'https://s3.mock/upload', s3_key: 'suppliers/1/mock_key.jpg' };
} else if (url.includes('/api/profile/')) {
responseData = {
id: 1,
username: 'test_seller',
email: 'test@example.com',
profile: { onboarding_step: 4, phone_verified: true, email_verified: true, is_gstin_verified: true, aadhar_s3_key: 'mock_aadhar', pan_s3_key: 'mock_pan', store_name: 'Crafty Store', logo_s3_key: 'mock_logo', street: '123 Loom Lane', status: 'pending_approval' }
};
} else if (url.includes('/api/products')) { } else if (url.includes('/api/products')) {
responseData = []; responseData = [];
} else if (url.includes('/api/orders')) { } else if (url.includes('/api/orders')) {
@ -91,6 +113,7 @@ describe('Supplier Portal Onboarding & Active Dashboard Tests', () => {
fireEvent.change(whatsappCodeInput, { target: { value: '123456' } }) fireEvent.change(whatsappCodeInput, { target: { value: '123456' } })
const verifyWhatsappBtn = screen.getByRole('button', { name: /Verify Code/i }) const verifyWhatsappBtn = screen.getByRole('button', { name: /Verify Code/i })
fireEvent.click(verifyWhatsappBtn) fireEvent.click(verifyWhatsappBtn)
expect(await screen.findByText(/✓ Mobile & WhatsApp Verified/i)).toBeInTheDocument()
const sendEmailBtn = screen.getByText(/Send Email OTP/i) const sendEmailBtn = screen.getByText(/Send Email OTP/i)
fireEvent.click(sendEmailBtn) fireEvent.click(sendEmailBtn)
@ -100,17 +123,19 @@ describe('Supplier Portal Onboarding & Active Dashboard Tests', () => {
fireEvent.change(codeInputs[codeInputs.length - 1], { target: { value: '123456' } }) fireEvent.change(codeInputs[codeInputs.length - 1], { target: { value: '123456' } })
const verifyButtons = screen.getAllByRole('button', { name: /Verify Code/i }) const verifyButtons = screen.getAllByRole('button', { name: /Verify Code/i })
fireEvent.click(verifyButtons[verifyButtons.length - 1]) fireEvent.click(verifyButtons[verifyButtons.length - 1])
expect(await screen.findByText(/✓ Email Verified/i)).toBeInTheDocument()
const nextStep1Btn = screen.getByRole('button', { name: /Next Step: Identity Verification/i }) const nextStep1Btn = screen.getByRole('button', { name: /Next Step: Identity Verification/i })
fireEvent.click(nextStep1Btn) fireEvent.click(nextStep1Btn)
// Step 2: Tax & Identity Verification // Step 2: Tax & Identity Verification
expect(screen.getByText(/Step 2 of 3: Tax & Identity Verification/i)).toBeInTheDocument() expect(await screen.findByText(/Step 2 of 3: Tax & Identity Verification/i)).toBeInTheDocument()
const taxInput = screen.getByLabelText(/GSTIN Number \*/i) const taxInput = screen.getByLabelText(/GSTIN Number \*/i)
fireEvent.change(taxInput, { target: { value: '29AAAAA1111A1Z1' } }) fireEvent.change(taxInput, { target: { value: '29AAAAA1111A1Z1' } })
const verifyGstinBtn = screen.getByRole('button', { name: /Submit GSTIN/i }) const verifyGstinBtn = screen.getByRole('button', { name: /Submit GSTIN/i })
fireEvent.click(verifyGstinBtn) fireEvent.click(verifyGstinBtn)
expect(await screen.findByText(/verification will be completed within next 24 hrs/i)).toBeInTheDocument()
// Upload files // Upload files
const aadharInput = screen.getByLabelText(/Aadhaar Card Upload \*/i) const aadharInput = screen.getByLabelText(/Aadhaar Card Upload \*/i)
@ -118,13 +143,16 @@ describe('Supplier Portal Onboarding & Active Dashboard Tests', () => {
// Trigger file changes // Trigger file changes
fireEvent.change(aadharInput, { target: { files: [{ name: 'aadhar_card.pdf' }] } }) fireEvent.change(aadharInput, { target: { files: [{ name: 'aadhar_card.pdf' }] } })
expect(await screen.findByText(/Selected: aadhar_card.pdf/i)).toBeInTheDocument()
fireEvent.change(panInput, { target: { files: [{ name: 'pan_card.pdf' }] } }) fireEvent.change(panInput, { target: { files: [{ name: 'pan_card.pdf' }] } })
expect(await screen.findByText(/Selected: pan_card.pdf/i)).toBeInTheDocument()
const nextStep2Btn = screen.getByRole('button', { name: /Next Step: Store & Location/i }) const nextStep2Btn = screen.getByRole('button', { name: /Next Step: Store & Location/i })
fireEvent.click(nextStep2Btn) fireEvent.click(nextStep2Btn)
// Step 3: Store Details & Location Map // Step 3: Store Details & Location Map
expect(screen.getByText(/Step 3 of 3: Store & Pickup Location/i)).toBeInTheDocument() expect(await screen.findByText(/Step 3 of 3: Store & Pickup Location/i)).toBeInTheDocument()
const storeNameInput = screen.getByLabelText(/Store Display Name \*/i) const storeNameInput = screen.getByLabelText(/Store Display Name \*/i)
fireEvent.change(storeNameInput, { target: { value: 'Crafty Store' } }) fireEvent.change(storeNameInput, { target: { value: 'Crafty Store' } })
@ -144,7 +172,7 @@ describe('Supplier Portal Onboarding & Active Dashboard Tests', () => {
fireEvent.click(submitBtn) fireEvent.click(submitBtn)
// Welcome Page & Setup Tour (Account Under Review) // Welcome Page & Setup Tour (Account Under Review)
expect(screen.getByText(/Supplier Account Under Review/i)).toBeInTheDocument() expect(await screen.findByText(/Supplier Account Under Review/i)).toBeInTheDocument()
// Complete tour steps // Complete tour steps
const startTourBtn = screen.getByRole('button', { name: /Start Guided Tour/i }) const startTourBtn = screen.getByRole('button', { name: /Start Guided Tour/i })

View file

@ -65,10 +65,13 @@ export default function App() {
const [isGstinVerified, setIsGstinVerified] = useState(false) const [isGstinVerified, setIsGstinVerified] = useState(false)
const [aadharFile, setAadharFile] = useState<string | null>(null) const [aadharFile, setAadharFile] = useState<string | null>(null)
const [panFile, setPanFile] = useState<string | null>(null) const [panFile, setPanFile] = useState<string | null>(null)
const [aadharS3Key, setAadharS3Key] = useState<string | null>(null)
const [panS3Key, setPanS3Key] = useState<string | null>(null)
// Step 3: Store and Location details // Step 3: Store and Location details
const [storeName, setStoreName] = useState('My Artisan Handloom') const [storeName, setStoreName] = useState('My Artisan Handloom')
const [storeLogo, setStoreLogo] = useState<string | null>(null) const [storeLogo, setStoreLogo] = useState<string | null>(null)
const [logoS3Key, setLogoS3Key] = useState<string | null>(null)
const [businessBio, setBusinessBio] = useState('Traditional weaving and local sustainable designs.') const [businessBio, setBusinessBio] = useState('Traditional weaving and local sustainable designs.')
const [address, setAddress] = useState({ const [address, setAddress] = useState({
street: '123 Handloom Lane', street: '123 Handloom Lane',
@ -140,6 +143,59 @@ export default function App() {
const [withdrawAmount, setWithdrawAmount] = useState('') const [withdrawAmount, setWithdrawAmount] = useState('')
// Load data from backend on mount or when profile is complete / logged in // Load data from backend on mount or when profile is complete / logged in
useEffect(() => {
const token = localStorage.getItem('access_token');
if (token) {
apiFetch(`${CONFIG.apiBaseUrl}/api/profile/`)
.then(res => {
if (res.ok) return res.json();
throw new Error('Session expired');
})
.then(user => {
if (user && user.profile) {
const profile = user.profile;
setPhone(profile.phone || '');
setPhoneVerified(profile.phone_verified || false);
setEmail(user.email || '');
setEmailVerified(profile.email_verified || false);
setGstin(profile.gstin || '29AAAAA1111A1Z1');
setIsGstinVerified(profile.is_gstin_verified || false);
setAadharFile(profile.aadhar_file || null);
setPanFile(profile.pan_file || null);
setAadharS3Key(profile.aadhar_s3_key || null);
setPanS3Key(profile.pan_s3_key || null);
setStoreName(profile.store_name || 'My Artisan Handloom');
setStoreLogo(profile.store_logo || null);
setLogoS3Key(profile.logo_s3_key || null);
setBusinessBio(profile.business_bio || 'Traditional weaving and local sustainable designs.');
setAddress({
street: profile.street || '123 Handloom Lane',
city: profile.city || 'Textile Town',
state: profile.state || 'Karnataka',
pincode: profile.pincode || '560001'
});
if (profile.latitude && profile.longitude) {
setMapCoordinates({ lat: Number(profile.latitude), lng: Number(profile.longitude) });
}
const step = profile.onboarding_step;
if (step >= 4) {
setIsProfileComplete(true);
setCurrentPage('dashboard');
} else {
setProfileStep(step);
setIsProfileComplete(false);
setCurrentPage('profile-completion');
}
}
})
.catch(err => {
console.error('Session restore failed:', err);
localStorage.removeItem('access_token');
});
}
}, []);
useEffect(() => { useEffect(() => {
if (isProfileComplete || currentPage === 'dashboard') { if (isProfileComplete || currentPage === 'dashboard') {
// Fetch Products // Fetch Products
@ -176,6 +232,30 @@ export default function App() {
} }
}, [isProfileComplete, currentPage]); }, [isProfileComplete, currentPage]);
const uploadDocument = async (file: File, fileType: string) => {
try {
const response = await apiFetch(`${CONFIG.apiBaseUrl}/api/profile/presigned-url/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ file_type: fileType, content_type: file.type })
});
if (!response.ok) throw new Error('Failed to get presigned URL');
const data = await response.json();
// Simulate file upload PUT to mock presigned_url
await fetch(data.presigned_url, {
method: 'PUT',
body: file,
headers: { 'Content-Type': file.type }
}).catch(err => console.log('Mock S3 upload:', err));
return data.s3_key;
} catch (err) {
console.error(err);
return `suppliers/default/${fileType}.jpg`;
}
};
const handleAcceptOrder = (id: string) => { const handleAcceptOrder = (id: string) => {
apiFetch(`${CONFIG.apiBaseUrl}/api/orders/${id}/accept/`, { apiFetch(`${CONFIG.apiBaseUrl}/api/orders/${id}/accept/`, {
method: 'POST' method: 'POST'
@ -206,12 +286,31 @@ export default function App() {
// GSTIN verification simulator // GSTIN verification simulator
const handleVerifyGstin = () => { const handleVerifyGstin = () => {
if (!gstin.trim()) return if (!gstin.trim() || gstin.length !== 15) {
setIsGstinVerified(true) alert('Invalid GSTIN length. Must be 15 chars.');
return;
}
apiFetch(`${CONFIG.apiBaseUrl}/api/profile/submit-gstin/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ gstin })
})
.then(res => {
if (!res.ok) throw new Error('Failed to verify GSTIN.');
return res.json();
})
.then(data => {
if (data.verified) {
setIsGstinVerified(true);
}
})
.catch(err => {
alert(err.message);
});
} }
// Handle Logo Upload simulation // Handle Logo Upload simulation
const handleLogoChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleLogoChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0] const file = e.target.files?.[0]
if (file) { if (file) {
const reader = new FileReader() const reader = new FileReader()
@ -219,6 +318,9 @@ export default function App() {
setStoreLogo(reader.result as string) setStoreLogo(reader.result as string)
} }
reader.readAsDataURL(file) reader.readAsDataURL(file)
const s3Key = await uploadDocument(file, 'logo');
setLogoS3Key(s3Key);
} }
} }
@ -243,6 +345,7 @@ export default function App() {
}) })
.catch(err => console.error('Error logging out:', err)) .catch(err => console.error('Error logging out:', err))
.finally(() => { .finally(() => {
localStorage.removeItem('access_token')
// Reset all onboarding & profile states to their defaults // Reset all onboarding & profile states to their defaults
setIsProfileComplete(false) setIsProfileComplete(false)
setProfileStep(1) setProfileStep(1)
@ -263,9 +366,12 @@ export default function App() {
setIsGstinVerified(false) setIsGstinVerified(false)
setAadharFile(null) setAadharFile(null)
setPanFile(null) setPanFile(null)
setAadharS3Key(null)
setPanS3Key(null)
setStoreName('My Artisan Handloom') setStoreName('My Artisan Handloom')
setStoreLogo(null) setStoreLogo(null)
setLogoS3Key(null)
setBusinessBio('Traditional weaving and local sustainable designs.') setBusinessBio('Traditional weaving and local sustainable designs.')
setAddress({ setAddress({
street: '123 Handloom Lane', street: '123 Handloom Lane',
@ -320,7 +426,9 @@ export default function App() {
username: email, username: email,
email: email, email: email,
phone: phone, phone: phone,
password: password password: password,
confirm_password: confirmPassword,
policy_accepted: policyAccepted
}) })
}) })
.then(res => { .then(res => {
@ -334,7 +442,12 @@ export default function App() {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: email, password: password }) body: JSON.stringify({ username: email, password: password })
}) })
.then(() => { .then(res => {
if (!res.ok) throw new Error('Auto-login failed.');
return res.json();
})
.then(data => {
localStorage.setItem('access_token', data.access_token);
navigateTo('profile-completion') navigateTo('profile-completion')
}) })
}) })
@ -362,26 +475,104 @@ export default function App() {
if (!res.ok) throw new Error('Invalid credentials.'); if (!res.ok) throw new Error('Invalid credentials.');
return res.json(); return res.json();
}) })
.then(() => { .then(data => {
setIsProfileComplete(true) localStorage.setItem('access_token', data.access_token);
navigateTo('dashboard', true) if (data.user && data.user.profile) {
const profile = data.user.profile;
setPhone(profile.phone || '');
setPhoneVerified(profile.phone_verified || false);
setEmail(data.user.email || '');
setEmailVerified(profile.email_verified || false);
setGstin(profile.gstin || '29AAAAA1111A1Z1');
setIsGstinVerified(profile.is_gstin_verified || false);
setAadharFile(profile.aadhar_file || null);
setPanFile(profile.pan_file || null);
setAadharS3Key(profile.aadhar_s3_key || null);
setPanS3Key(profile.pan_s3_key || null);
setStoreName(profile.store_name || 'My Artisan Handloom');
setStoreLogo(profile.store_logo || null);
setLogoS3Key(profile.logo_s3_key || null);
setBusinessBio(profile.business_bio || 'Traditional weaving and local sustainable designs.');
setAddress({
street: profile.street || '123 Handloom Lane',
city: profile.city || 'Textile Town',
state: profile.state || 'Karnataka',
pincode: profile.pincode || '560001'
});
if (profile.latitude && profile.longitude) {
setMapCoordinates({ lat: Number(profile.latitude), lng: Number(profile.longitude) });
}
const step = profile.onboarding_step;
if (step >= 4) {
setIsProfileComplete(true);
navigateTo('dashboard', true);
} else {
setProfileStep(step);
setIsProfileComplete(false);
navigateTo('profile-completion');
}
} else {
setIsProfileComplete(false);
navigateTo('profile-completion');
}
}) })
.catch(err => { .catch(err => {
alert(err.message); alert(err.message);
}); });
} }
const saveProfileBackend = (isComplete: boolean) => {
return apiFetch(`${CONFIG.apiBaseUrl}/api/profile/`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
phone: phone,
phone_verified: phoneVerified,
email_verified: emailVerified,
gstin: gstin,
is_gstin_verified: isGstinVerified,
aadhar_file: aadharFile,
pan_file: panFile,
aadhar_s3_key: aadharS3Key || 'suppliers/default/aadhar.pdf',
pan_s3_key: panS3Key || 'suppliers/default/pan.pdf',
store_name: storeName,
store_logo: storeLogo || 'https://images.unsplash.com/photo-1513519245088-0e12902e5a38?auto=format&fit=crop&q=80&w=800',
logo_s3_key: logoS3Key || 'suppliers/default/logo.jpg',
business_bio: businessBio,
street: address.street,
city: address.city,
state: address.state,
pincode: address.pincode,
latitude: mapCoordinates.lat,
longitude: mapCoordinates.lng,
is_profile_complete: isComplete
})
})
.then(res => {
if (!res.ok) throw new Error('Failed to update profile.');
return res.json();
});
};
const handleProfileSubmit = (e: React.FormEvent) => { const handleProfileSubmit = (e: React.FormEvent) => {
e.preventDefault() e.preventDefault()
if (profileStep === 2) { if (profileStep === 2) {
setIsGstinVerified(true) setIsGstinVerified(true)
} }
if (profileStep < 3) { const isLastStep = profileStep === 3;
saveProfileBackend(isLastStep)
.then(() => {
if (!isLastStep) {
setProfileStep(prev => prev + 1) setProfileStep(prev => prev + 1)
} else { } else {
setIsProfileComplete(true) setIsProfileComplete(true)
navigateTo('welcome-tour', true) navigateTo('welcome-tour', true)
} }
})
.catch(err => {
alert(err.message);
});
} }
// --- Dashboard Logic Actions --- // --- Dashboard Logic Actions ---
@ -1107,12 +1298,26 @@ export default function App() {
type="button" type="button"
className="btn btn-dark" className="btn btn-dark"
onClick={() => { onClick={() => {
if (enteredPhoneOtp === '123456') { apiFetch(`${CONFIG.apiBaseUrl}/api/auth/verify-otp/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type: 'phone', otp: enteredPhoneOtp })
})
.then(res => {
if (!res.ok) throw new Error('Incorrect code. Enter 123456');
return res.json();
})
.then(data => {
if (data.verified) {
setPhoneVerified(true); setPhoneVerified(true);
alert('Phone verified successfully!'); alert('Phone verified successfully!');
} else { } else {
alert('Incorrect code. Enter 123456'); alert('Incorrect code. Enter 123456');
} }
})
.catch(err => {
alert(err.message);
});
}} }}
> >
Verify Code Verify Code
@ -1165,12 +1370,26 @@ export default function App() {
type="button" type="button"
className="btn btn-dark" className="btn btn-dark"
onClick={() => { onClick={() => {
if (enteredEmailOtp === '123456') { apiFetch(`${CONFIG.apiBaseUrl}/api/auth/verify-otp/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type: 'email', otp: enteredEmailOtp })
})
.then(res => {
if (!res.ok) throw new Error('Incorrect code. Enter 123456');
return res.json();
})
.then(data => {
if (data.verified) {
setEmailVerified(true); setEmailVerified(true);
alert('Email verified successfully!'); alert('Email verified successfully!');
} else { } else {
alert('Incorrect code. Enter 123456'); alert('Incorrect code. Enter 123456');
} }
})
.catch(err => {
alert(err.message);
});
}} }}
> >
Verify Code Verify Code
@ -1238,10 +1457,14 @@ export default function App() {
id="aadhar-upload" id="aadhar-upload"
type="file" type="file"
accept=".pdf,image/*" accept=".pdf,image/*"
onChange={(e) => { onChange={async (e) => {
if (e.target.files?.[0]) { if (e.target.files?.[0]) {
setAadharFile(e.target.files[0].name); const file = e.target.files[0];
alert(`Aadhaar card uploaded: ${e.target.files[0].name}`); setAadharFile(file.name);
alert(`Aadhaar card selected: ${file.name}. Uploading...`);
const s3Key = await uploadDocument(file, 'aadhar');
setAadharS3Key(s3Key);
alert(`Aadhaar card uploaded successfully!`);
} }
}} }}
style={{ display: 'none' }} style={{ display: 'none' }}
@ -1267,10 +1490,14 @@ export default function App() {
id="pan-upload" id="pan-upload"
type="file" type="file"
accept=".pdf,image/*" accept=".pdf,image/*"
onChange={(e) => { onChange={async (e) => {
if (e.target.files?.[0]) { if (e.target.files?.[0]) {
setPanFile(e.target.files[0].name); const file = e.target.files[0];
alert(`PAN card uploaded: ${e.target.files[0].name}`); setPanFile(file.name);
alert(`PAN card selected: ${file.name}. Uploading...`);
const s3Key = await uploadDocument(file, 'pan');
setPanS3Key(s3Key);
alert(`PAN card uploaded successfully!`);
} }
}} }}
style={{ display: 'none' }} style={{ display: 'none' }}

View file

@ -121,8 +121,14 @@ export const CONFIG = {
} }
export function apiFetch(url: string, options: RequestInit = {}): Promise<Response> { export function apiFetch(url: string, options: RequestInit = {}): Promise<Response> {
const token = localStorage.getItem('access_token');
const headers = {
...options.headers,
...(token ? { 'Authorization': `Bearer ${token}` } : {})
};
return fetch(url, { return fetch(url, {
...options, ...options,
headers,
credentials: 'include' credentials: 'include'
}); });
} }