Skip to content

Commit 8def827

Browse files
Fix 2fa and add product (#73)
* Fix 2FA and Product addition * Fix 2FA and Product addition
1 parent 68ec831 commit 8def827

File tree

9 files changed

+77
-59
lines changed

9 files changed

+77
-59
lines changed

Diff for: src/assets/styles/Header.scss

+2-2
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ a {
382382
left: -10%;
383383
padding-right: 10px;
384384
top: 150%;
385-
width: 140%;
385+
width: 135%;
386386
z-index: 500;
387387
box-shadow: 0 5rem 10rem 0 rgba(0, 0, 0, 0.08);
388388

@@ -409,7 +409,7 @@ a {
409409
&__2fa_btn{
410410
color: $white;
411411
background-color: $primary-color-dark ;
412-
margin-top: 5px;
412+
margin-top: 25px;
413413
border-radius: 4px;
414414
padding: 2px 4px;
415415
cursor: pointer;

Diff for: src/components/layout/Header.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,13 @@ const Header: React.FC = () => {
104104
}
105105

106106
const switch2FA = async () => {
107-
const successMessage = `2FA ${is2FAEnabled ? "Disabled" : "Enabled"}`;
107+
const successMessage = `2FA ${is2FAEnabled ? "Disabled" : "Enabled, Login now."}`;
108108
setIs2FALoading(true);
109109
const res = await dispatch(change2FAStatus({ newStatus: !is2FAEnabled }))
110110
setIs2FALoading(false);
111111
if (res.type === "auth/change-2fa-status/fulfilled") {
112-
toast.success(res.payload.message || successMessage)
112+
toast.success((!is2FAEnabled ? `${res.payload.message}, Login now.` : res.payload.message) || successMessage)
113+
if (!is2FAEnabled) { navigate('/logout') }
113114
setIs2FAEnabled(res.payload.data.user.is2FAEnabled || !is2FAEnabled)
114115
}
115116
else {

Diff for: src/components/product/SellerProduct.tsx

+34-20
Original file line numberDiff line numberDiff line change
@@ -35,24 +35,14 @@ const SellerProduct = ({ productId }: { productId: string }) => {
3535
const dispatch = useAppDispatch();
3636
const navigate = useNavigate();
3737
const isAdd = productId === "add";
38-
const { product, isError, isLoading, message, isUpdate, newAddedProduct, isUpdateSuccess, updateError }: ISingleProductInitialResponse = useAppSelector((state: any) => state.singleSellerProduct);
38+
const { product, isError, isLoading, message, newAddedProduct }: ISingleProductInitialResponse = useAppSelector((state: any) => state.singleSellerProduct);
3939

4040
const [updatedProduct, setUpdatedProduct] = useState<ISingleProduct>(initialProductState);
4141
const [updateImages, setUpdateImages] = useState<File[]>([]);
4242
const [isImagesUpdated, setIsImagesUpdated] = useState<boolean>(false);
4343
const [isThereAnyUpdate, setIsThereAnyUpdate] = useState<boolean>(false);
4444

45-
useEffect(() => {
46-
if (isUpdate && isUpdateSuccess && !updateError) {
47-
toast.success(`Product ${isAdd ? "added" : "updated"}`)
48-
isAdd && newAddedProduct && navigate(`/seller/product/${newAddedProduct.id}`)
49-
!isAdd && dispatch(fetchSingleSellerProduct(productId));
50-
}
51-
else if (updateError) {
52-
toast.error(updateError || `${isAdd ? "Adding" : "Updating"} a product failed.`)
53-
}
54-
dispatch(resetUpdateState())
55-
}, [isUpdate, isUpdateSuccess, updateError])
45+
const [updateLoading, setUpdateLoading] = useState(false);
5646

5747
useEffect(() => {
5848
if (!isAdd) {
@@ -85,11 +75,11 @@ const SellerProduct = ({ productId }: { productId: string }) => {
8575
}
8676
});
8777

88-
if(isAdd && (!updatedProduct.name || !updatedProduct.description || !updatedProduct.price || !updatedProduct.bonus || !updatedProduct.discount || !updatedProduct.category || !updatedProduct.expiryDate || !updatedProduct.quantity)){
78+
if (isAdd && (!updatedProduct.name || !updatedProduct.description || !updatedProduct.price || !updatedProduct.bonus || !updatedProduct.discount || !updatedProduct.category || !updatedProduct.expiryDate || !updatedProduct.quantity)) {
8979
toast.error('Fill all fields please')
9080
return;
9181
}
92-
if(isAdd && updateImages.length < 4){
82+
if (isAdd && updateImages.length < 4) {
9383
toast.error(updateImages.length)
9484
toast.error('Upload atleast 4 images please')
9585
return;
@@ -108,14 +98,29 @@ const SellerProduct = ({ productId }: { productId: string }) => {
10898
}
10999

110100
try {
101+
setUpdateLoading(true);
111102
if (isAdd) {
112-
dispatch(addSellerProduct(formData));
103+
const res = await dispatch(addSellerProduct(formData));
104+
console.dir(res)
105+
if (res.type === 'products/addSellerProduct/rejected') {
106+
toast.error(res.payload as string || "Failed to add product, try again")
107+
}
108+
else {
109+
navigate(`/seller/product/${(res.payload as any).data.product.id}`)
110+
}
113111
} else {
114-
dispatch(updateSellerProduct({ id: productId, newProductData: formData }));
112+
const res = await dispatch(updateSellerProduct({ id: productId, newProductData: formData }));
113+
if (res.type === 'products/updateSellerProduct/rejected') {
114+
toast.error(res.payload as string || "Failed to update product, try again")
115+
}
115116
}
117+
dispatch(resetUpdateState())
116118
} catch (error) {
117119
toast.error(`Error ${isAdd ? 'adding' : 'updating'} product: ${getErrorMessage(error)}`);
118120
}
121+
finally {
122+
setUpdateLoading(false);
123+
}
119124
};
120125

121126
if (isLoading) {
@@ -142,8 +147,8 @@ const SellerProduct = ({ productId }: { productId: string }) => {
142147
<div className="seller-product-header">
143148
<h1>{isAdd ? 'Add New Product' : 'Product View'}</h1>
144149
<div className="header-btns">
145-
<button disabled={!isThereAnyUpdate && !isImagesUpdated} className={`edit-btn ${!isThereAnyUpdate && !isImagesUpdated && 'disabled'}`} onClick={handleSaveOrAdd}>
146-
<FaSave /> {isAdd ? "ADD" : "UPDATE"}
150+
<button disabled={(!isThereAnyUpdate && !isImagesUpdated) || updateLoading} className={`edit-btn ${!isThereAnyUpdate && !isImagesUpdated && 'disabled'}`} onClick={handleSaveOrAdd}>
151+
<FaSave /> {isAdd ? "ADD" : "UPDATE"}{updateLoading && "ING..."}
147152
</button>
148153
{!isAdd && <button className='delete-btn'><FaTrash /> Delete</button>}
149154
</div>
@@ -273,16 +278,25 @@ const ProductImages = ({ initialImages, setUpdateImages, setIsImagesUpdated }: {
273278
setSelectedImage(0);
274279
}, [initialImages, setUpdateImages]);
275280

281+
const allowedExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.bmp', '.tiff'];
276282
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
277283
if (e.target.files && e.target.files.length > 0) {
284+
const file = e.target.files[0];
285+
const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase();
286+
287+
if (!allowedExtensions.includes(fileExtension)) {
288+
toast.error('Invalid file type. Allowed types: ' + allowedExtensions.join(', '));
289+
return;
290+
}
291+
278292
const reader = new FileReader();
279293
reader.onloadend = () => {
280294
const newImages = [...images, reader.result as string];
281295
setImages(newImages);
282296
setUpdateImages(newImages);
283297
setIsImagesUpdated(true)
284298
};
285-
reader.readAsDataURL(e.target.files[0]);
299+
reader.readAsDataURL(file);
286300
}
287301
};
288302

@@ -307,7 +321,7 @@ const ProductImages = ({ initialImages, setUpdateImages, setIsImagesUpdated }: {
307321
className={`thumbnail-image ${selectedImage === index ? 'active' : ''}`}
308322
onMouseEnter={() => setSelectedImage(index)}
309323
/>
310-
<button
324+
<button
311325
className="remove-image-button"
312326
onClick={() => handleRemoveImage(index)}
313327
>

Diff for: src/pages/UserLogin.tsx

+17-5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ function UserLogin() {
2929
const [isVisible, setIsVisible] = useState(false);
3030
const [openOTPDialog, setOpenOTPDialog] = useState(false);
3131
const [otp, setOtp] = useState(['', '', '', '', '', '']);
32+
const [otpError, setOtpError] = useState("");
33+
const [otpLoading, setOtpLoading] = useState(false);
3234
const navigate = useNavigate();
3335
const dispatch = useAppDispatch();
3436
const {
@@ -103,6 +105,7 @@ function UserLogin() {
103105
const newOtp = [...otp];
104106
newOtp[index] = value;
105107
setOtp(newOtp);
108+
setOtpError("");
106109
if (value && index < 5) {
107110
const nextInput = document.getElementById(`otp-${index + 1}`);
108111
if (nextInput) nextInput.focus();
@@ -112,16 +115,19 @@ function UserLogin() {
112115
const handleVerifyOTP = async () => {
113116
const otpString = otp.join('');
114117
if (otpString.length === 6) {
118+
setOtpLoading(true);
119+
setOtpError("");
115120
const res = await dispatch(verifyOTP({ userId, otp: otpString }));
121+
setOtpLoading(false);
116122
if (res.type = 'auth/verify-otp/rejected') {
117-
toast.error(res.payload)
123+
setOtpError(res.payload)
118124
}
119125
else {
120126
setOpenOTPDialog(false);
121127
}
122128
setOtp(['', '', '', '', '', ''])
123129
} else {
124-
toast.error("Please enter a valid 6-digit OTP");
130+
setOtpError("Please enter a valid 6-digit OTP");
125131
}
126132
};
127133

@@ -272,10 +278,16 @@ function UserLogin() {
272278
/>
273279
))}
274280
</Box>
281+
{
282+
otpError &&
283+
<DialogContentText id="alert-dialog-error" sx={{ fontSize: '1.2rem', marginBottom: '20px', color: 'red', display: 'flex', justifyContent: 'center' }}>
284+
{otpError}
285+
</DialogContentText>
286+
}
275287
</DialogContent>
276288
<DialogActions sx={{ justifyContent: 'center', gap: '16px' }}>
277289
<Button
278-
onClick={() => { setOpenOTPDialog(false); setOtp(['', '', '', '', '', '']); }}
290+
onClick={() => { setOpenOTPDialog(false); setOtp(['', '', '', '', '', '']); setOtpError("");}}
279291
sx={{
280292
backgroundColor: '#f0f0f0',
281293
color: '#333',
@@ -303,8 +315,8 @@ function UserLogin() {
303315
}}
304316
autoFocus
305317
>
306-
{isLoading ? "Verifying" : "Verify"}
307-
<PulseLoader size={6} color="#ffe2d1" loading={isLoading} />
318+
{otpLoading ? "Verifying" : "Verify"}
319+
<PulseLoader size={6} color="#ffe2d1" loading={otpLoading} />
308320
</Button>
309321
</DialogActions>
310322
</Dialog>

Diff for: src/pages/seller/SellerCollection.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export default function SellerCollection() {
6464

6565
<div className="action__icons">
6666
<Tooltip TransitionComponent={Zoom} title="Edit" arrow >
67-
<IconButton>
67+
<IconButton onClick={() => { navigate(`/seller/product/${product.id}`) }}>
6868
<EditIcon className='icon__edit' />
6969
</IconButton>
7070
</Tooltip>

Diff for: src/store/features/auth/authSlice.tsx

+5-13
Original file line numberDiff line numberDiff line change
@@ -319,11 +319,13 @@ const userSlice = createSlice({
319319
.addCase(loginUser.fulfilled, (state, action: PayloadAction<any>) => {
320320
state.isError = false;
321321
state.isLoading = false;
322-
state.isAuthenticated = true;
323322
state.isSuccess = true;
324323
state.message = action.payload.message;
325-
state.token = action.payload.data.token;
326324
state.userId = action.payload.data.userId || "";
325+
if(state.message !== "Check your Email for OTP Confirmation"){
326+
state.isAuthenticated = true;
327+
state.token = action.payload.data.token;
328+
}
327329
})
328330
.addCase(loginUser.rejected, (state, action: PayloadAction<any>) => {
329331
state.isError = true;
@@ -373,11 +375,6 @@ const userSlice = createSlice({
373375
state.error = action.payload.message;
374376
})
375377

376-
.addCase(verifyOTP.pending, (state) => {
377-
state.isError = false;
378-
state.isLoading = true;
379-
state.isSuccess = false;
380-
})
381378
.addCase(verifyOTP.fulfilled, (state, action: PayloadAction<any>) => {
382379
state.isError = false;
383380
state.isLoading = false;
@@ -386,12 +383,7 @@ const userSlice = createSlice({
386383
state.message = action.payload.message;
387384
state.token = action.payload.data.token;
388385
})
389-
.addCase(verifyOTP.rejected, (state, action: PayloadAction<any>) => {
390-
state.isError = true;
391-
state.isLoading = false;
392-
state.isSuccess = false;
393-
state.error = action.payload
394-
})
386+
395387
},
396388
});
397389

Diff for: src/store/features/product/productService.tsx

-10
Original file line numberDiff line numberDiff line change
@@ -62,17 +62,12 @@ const fetchSellerSingleProduct = async (id: string) => {
6262
};
6363

6464
const updateSellerProduct = async (id: string, newProductData: FormData) => {
65-
try {
6665
const response = await axiosInstance.put(`/api/shop/seller-update-product/${id}`, newProductData, {
6766
headers: {
6867
'Content-Type': 'multipart/form-data'
6968
}
7069
});
7170
return response.data;
72-
}
73-
catch (error) {
74-
throw new Error(getErrorMessage(error))
75-
}
7671
}
7772

7873
const updateSellerProductStatus = async (id: string, newStatus: string) => {
@@ -86,17 +81,12 @@ const updateSellerProductStatus = async (id: string, newStatus: string) => {
8681
}
8782

8883
const addSellerProduct = async (newProductData: FormData) => {
89-
try {
9084
const response = await axiosInstance.post(`/api/shop/seller-create-product`, newProductData, {
9185
headers: {
9286
'Content-Type': 'multipart/form-data'
9387
}
9488
});
9589
return response.data;
96-
}
97-
catch (error) {
98-
throw new Error(getErrorMessage(error))
99-
}
10090
}
10191
const sellerGetAllProducts = async () => {
10292
const response = await axiosInstance.get(`/api/shop/seller-get-products`);

Diff for: src/store/features/product/sellerProductSlice.tsx

+4-6
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const updateSellerProductStatus = createAsyncThunk<ISingleProductResponse
5353
const response = await productService.updateSellerProductStatus(id, newStatus);
5454
return response;
5555
} catch (error) {
56-
return rejectWithValue(error);
56+
return rejectWithValue(getErrorMessage(error));
5757
}
5858
}
5959
);
@@ -65,7 +65,7 @@ export const addSellerProduct = createAsyncThunk<ISingleProductResponse, FormDat
6565
const response = await productService.addSellerProduct(newProductData);
6666
return response;
6767
} catch (error) {
68-
return rejectWithValue(error);
68+
return rejectWithValue(getErrorMessage(error));
6969
}
7070
}
7171
);
@@ -75,7 +75,7 @@ const singleSellerProductSlice = createSlice({
7575
name: "singleProduct",
7676
initialState,
7777
reducers: {
78-
resetUpdateState : (state)=> {
78+
resetUpdateState: (state) => {
7979
state.updateError = null;
8080
state.isUpdate = false;
8181
state.isUpdateSuccess = false;
@@ -121,7 +121,6 @@ const singleSellerProductSlice = createSlice({
121121
.addCase(updateSellerProductStatus.pending, (state) => {
122122
state.isUpdate = true;
123123
state.isUpdateSuccess = false;
124-
state.isLoading = true;
125124
})
126125
.addCase(updateSellerProductStatus.fulfilled, (state, action: PayloadAction<any>) => {
127126
state.isUpdate = true;
@@ -139,7 +138,6 @@ const singleSellerProductSlice = createSlice({
139138
.addCase(addSellerProduct.pending, (state) => {
140139
state.isUpdate = true;
141140
state.isUpdateSuccess = false;
142-
state.isLoading = true;
143141
})
144142
.addCase(addSellerProduct.fulfilled, (state, action: PayloadAction<any>) => {
145143
state.isUpdate = true;
@@ -156,6 +154,6 @@ const singleSellerProductSlice = createSlice({
156154
}
157155
})
158156

159-
export const {resetUpdateState} = singleSellerProductSlice.actions;
157+
export const { resetUpdateState } = singleSellerProductSlice.actions;
160158

161159
export default singleSellerProductSlice.reducer;

Diff for: webpack.dev.config.ts

+11
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,17 @@ const config: Configuration = {
107107
optimization: {
108108
usedExports: true,
109109
},
110+
watchOptions: {
111+
ignored: [
112+
'**/node_modules',
113+
'**/.github',
114+
'**/.circleci',
115+
'**/.vscode',
116+
'**/.storybook'
117+
],
118+
aggregateTimeout: 300,
119+
poll: 1000,
120+
}
110121
};
111122

112123
export default config;

0 commit comments

Comments
 (0)