adding aws db
This commit is contained in:
parent
05ffef32ed
commit
2f6381870d
6 changed files with 297 additions and 36 deletions
BIN
HLD_optimized.docx
Normal file
BIN
HLD_optimized.docx
Normal file
Binary file not shown.
BIN
HLD_optimized.pptx
Normal file
BIN
HLD_optimized.pptx
Normal file
Binary file not shown.
BIN
business_overview.pptx
Normal file
BIN
business_overview.pptx
Normal file
Binary file not shown.
|
|
@ -8,7 +8,29 @@ beforeAll(() => {
|
|||
globalThis.fetch = vi.fn((url) => {
|
||||
let responseData: any = {};
|
||||
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')) {
|
||||
responseData = [];
|
||||
} else if (url.includes('/api/orders')) {
|
||||
|
|
@ -91,6 +113,7 @@ describe('Supplier Portal Onboarding & Active Dashboard Tests', () => {
|
|||
fireEvent.change(whatsappCodeInput, { target: { value: '123456' } })
|
||||
const verifyWhatsappBtn = screen.getByRole('button', { name: /Verify Code/i })
|
||||
fireEvent.click(verifyWhatsappBtn)
|
||||
expect(await screen.findByText(/✓ Mobile & WhatsApp Verified/i)).toBeInTheDocument()
|
||||
|
||||
const sendEmailBtn = screen.getByText(/Send Email OTP/i)
|
||||
fireEvent.click(sendEmailBtn)
|
||||
|
|
@ -100,17 +123,19 @@ describe('Supplier Portal Onboarding & Active Dashboard Tests', () => {
|
|||
fireEvent.change(codeInputs[codeInputs.length - 1], { target: { value: '123456' } })
|
||||
const verifyButtons = screen.getAllByRole('button', { name: /Verify Code/i })
|
||||
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 })
|
||||
fireEvent.click(nextStep1Btn)
|
||||
|
||||
// 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)
|
||||
fireEvent.change(taxInput, { target: { value: '29AAAAA1111A1Z1' } })
|
||||
const verifyGstinBtn = screen.getByRole('button', { name: /Submit GSTIN/i })
|
||||
fireEvent.click(verifyGstinBtn)
|
||||
expect(await screen.findByText(/verification will be completed within next 24 hrs/i)).toBeInTheDocument()
|
||||
|
||||
// Upload files
|
||||
const aadharInput = screen.getByLabelText(/Aadhaar Card Upload \*/i)
|
||||
|
|
@ -118,13 +143,16 @@ describe('Supplier Portal Onboarding & Active Dashboard Tests', () => {
|
|||
|
||||
// Trigger file changes
|
||||
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' }] } })
|
||||
expect(await screen.findByText(/Selected: pan_card.pdf/i)).toBeInTheDocument()
|
||||
|
||||
const nextStep2Btn = screen.getByRole('button', { name: /Next Step: Store & Location/i })
|
||||
fireEvent.click(nextStep2Btn)
|
||||
|
||||
// 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)
|
||||
fireEvent.change(storeNameInput, { target: { value: 'Crafty Store' } })
|
||||
|
|
@ -144,7 +172,7 @@ describe('Supplier Portal Onboarding & Active Dashboard Tests', () => {
|
|||
fireEvent.click(submitBtn)
|
||||
|
||||
// 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
|
||||
const startTourBtn = screen.getByRole('button', { name: /Start Guided Tour/i })
|
||||
|
|
|
|||
291
src/App.tsx
291
src/App.tsx
|
|
@ -65,10 +65,13 @@ export default function App() {
|
|||
const [isGstinVerified, setIsGstinVerified] = useState(false)
|
||||
const [aadharFile, setAadharFile] = 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
|
||||
const [storeName, setStoreName] = useState('My Artisan Handloom')
|
||||
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 [address, setAddress] = useState({
|
||||
street: '123 Handloom Lane',
|
||||
|
|
@ -140,6 +143,59 @@ export default function App() {
|
|||
const [withdrawAmount, setWithdrawAmount] = useState('')
|
||||
|
||||
// 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(() => {
|
||||
if (isProfileComplete || currentPage === 'dashboard') {
|
||||
// Fetch Products
|
||||
|
|
@ -176,6 +232,30 @@ export default function App() {
|
|||
}
|
||||
}, [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) => {
|
||||
apiFetch(`${CONFIG.apiBaseUrl}/api/orders/${id}/accept/`, {
|
||||
method: 'POST'
|
||||
|
|
@ -206,12 +286,31 @@ export default function App() {
|
|||
|
||||
// GSTIN verification simulator
|
||||
const handleVerifyGstin = () => {
|
||||
if (!gstin.trim()) return
|
||||
setIsGstinVerified(true)
|
||||
if (!gstin.trim() || gstin.length !== 15) {
|
||||
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
|
||||
const handleLogoChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const handleLogoChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0]
|
||||
if (file) {
|
||||
const reader = new FileReader()
|
||||
|
|
@ -219,6 +318,9 @@ export default function App() {
|
|||
setStoreLogo(reader.result as string)
|
||||
}
|
||||
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))
|
||||
.finally(() => {
|
||||
localStorage.removeItem('access_token')
|
||||
// Reset all onboarding & profile states to their defaults
|
||||
setIsProfileComplete(false)
|
||||
setProfileStep(1)
|
||||
|
|
@ -263,9 +366,12 @@ export default function App() {
|
|||
setIsGstinVerified(false)
|
||||
setAadharFile(null)
|
||||
setPanFile(null)
|
||||
setAadharS3Key(null)
|
||||
setPanS3Key(null)
|
||||
|
||||
setStoreName('My Artisan Handloom')
|
||||
setStoreLogo(null)
|
||||
setLogoS3Key(null)
|
||||
setBusinessBio('Traditional weaving and local sustainable designs.')
|
||||
setAddress({
|
||||
street: '123 Handloom Lane',
|
||||
|
|
@ -320,7 +426,9 @@ export default function App() {
|
|||
username: email,
|
||||
email: email,
|
||||
phone: phone,
|
||||
password: password
|
||||
password: password,
|
||||
confirm_password: confirmPassword,
|
||||
policy_accepted: policyAccepted
|
||||
})
|
||||
})
|
||||
.then(res => {
|
||||
|
|
@ -334,7 +442,12 @@ export default function App() {
|
|||
headers: { 'Content-Type': 'application/json' },
|
||||
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')
|
||||
})
|
||||
})
|
||||
|
|
@ -362,26 +475,104 @@ export default function App() {
|
|||
if (!res.ok) throw new Error('Invalid credentials.');
|
||||
return res.json();
|
||||
})
|
||||
.then(() => {
|
||||
setIsProfileComplete(true)
|
||||
navigateTo('dashboard', true)
|
||||
.then(data => {
|
||||
localStorage.setItem('access_token', data.access_token);
|
||||
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 => {
|
||||
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) => {
|
||||
e.preventDefault()
|
||||
if (profileStep === 2) {
|
||||
setIsGstinVerified(true)
|
||||
}
|
||||
if (profileStep < 3) {
|
||||
setProfileStep(prev => prev + 1)
|
||||
} else {
|
||||
setIsProfileComplete(true)
|
||||
navigateTo('welcome-tour', true)
|
||||
}
|
||||
const isLastStep = profileStep === 3;
|
||||
saveProfileBackend(isLastStep)
|
||||
.then(() => {
|
||||
if (!isLastStep) {
|
||||
setProfileStep(prev => prev + 1)
|
||||
} else {
|
||||
setIsProfileComplete(true)
|
||||
navigateTo('welcome-tour', true)
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
alert(err.message);
|
||||
});
|
||||
}
|
||||
|
||||
// --- Dashboard Logic Actions ---
|
||||
|
|
@ -1107,12 +1298,26 @@ export default function App() {
|
|||
type="button"
|
||||
className="btn btn-dark"
|
||||
onClick={() => {
|
||||
if (enteredPhoneOtp === '123456') {
|
||||
setPhoneVerified(true);
|
||||
alert('Phone verified successfully!');
|
||||
} else {
|
||||
alert('Incorrect code. Enter 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);
|
||||
alert('Phone verified successfully!');
|
||||
} else {
|
||||
alert('Incorrect code. Enter 123456');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
alert(err.message);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Verify Code
|
||||
|
|
@ -1165,12 +1370,26 @@ export default function App() {
|
|||
type="button"
|
||||
className="btn btn-dark"
|
||||
onClick={() => {
|
||||
if (enteredEmailOtp === '123456') {
|
||||
setEmailVerified(true);
|
||||
alert('Email verified successfully!');
|
||||
} else {
|
||||
alert('Incorrect code. Enter 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);
|
||||
alert('Email verified successfully!');
|
||||
} else {
|
||||
alert('Incorrect code. Enter 123456');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
alert(err.message);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Verify Code
|
||||
|
|
@ -1238,10 +1457,14 @@ export default function App() {
|
|||
id="aadhar-upload"
|
||||
type="file"
|
||||
accept=".pdf,image/*"
|
||||
onChange={(e) => {
|
||||
onChange={async (e) => {
|
||||
if (e.target.files?.[0]) {
|
||||
setAadharFile(e.target.files[0].name);
|
||||
alert(`Aadhaar card uploaded: ${e.target.files[0].name}`);
|
||||
const file = e.target.files[0];
|
||||
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' }}
|
||||
|
|
@ -1267,10 +1490,14 @@ export default function App() {
|
|||
id="pan-upload"
|
||||
type="file"
|
||||
accept=".pdf,image/*"
|
||||
onChange={(e) => {
|
||||
onChange={async (e) => {
|
||||
if (e.target.files?.[0]) {
|
||||
setPanFile(e.target.files[0].name);
|
||||
alert(`PAN card uploaded: ${e.target.files[0].name}`);
|
||||
const file = e.target.files[0];
|
||||
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' }}
|
||||
|
|
|
|||
|
|
@ -121,8 +121,14 @@ export const CONFIG = {
|
|||
}
|
||||
|
||||
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, {
|
||||
...options,
|
||||
headers,
|
||||
credentials: 'include'
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue