• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

atlp-rwanda / vikings-ec-fe / 9b795d4f-58b8-48a5-a705-a5f3603c9241

pending completion
9b795d4f-58b8-48a5-a705-a5f3603c9241

push

circleci

GitHub
feat: implement create product (#20)

134 of 236 branches covered (56.78%)

Branch coverage included in aggregate %.

99 of 99 new or added lines in 11 files covered. (100.0%)

627 of 767 relevant lines covered (81.75%)

7.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

52.73
/src/components/forms/CreateProductForm.js
1
import React, { useEffect, useState } from 'react';
2
import InputField from './InputField';
3
import Button from './Button';
4
import { useForm } from 'react-hook-form';
5
import { yupResolver } from '@hookform/resolvers/yup';
6
import { useDispatch, useSelector } from 'react-redux';
7
import { createProduct } from '../../features/createProductSlice';
8
import createProductSchema from '../../validations/createProduct.validation';
9
import { showErrorMessage, showSuccessMessage } from '../../utils/toast';
10
import { fetchCategories } from '../../features/categorySlice';
11
import getFormFromObject from '../../utils/getFormData';
12
import convertDate from '../../utils/formatDate';
13
import { formatDate } from '../../utils/formatDate';
14
import { useNavigate } from 'react-router-dom';
15

16
const CreateProductForm = () => {
24✔
17
const isAuthenticated =
18
    useSelector((state) => state.login.isAuthenticated) === true
5!
19
      ? useSelector((state) => state.login.isAuthenticated)
×
20
      : useSelector((state) => state.twoFactorAuth.isAuthenticated);
5✔
21
  const navigate = useNavigate();
3✔
22
  useEffect(() => {
3✔
23
    if (!isAuthenticated) {
2!
24
      showErrorMessage('You are not logged in');
2✔
25
      return navigate('/');
2✔
26
    }
27
  }, [isAuthenticated]);
28

29
  const { isLoading } = useSelector((state) => state.createProduct);
5✔
30
  const dispatch = useDispatch();
3✔
31

32
  const [selectedImages, setSelectedImages] = useState([]);
3✔
33

34
  const today = formatDate(new Date());
3✔
35
  
36
  const handleFileInputChange = (event) => {
3✔
37
    const files = Array.from(event.target.files);
×
38
    const images = files;
×
39
    setSelectedImages(images);
×
40
  };
41

42
  useEffect(() => {
3✔
43
    dispatch(fetchCategories());
2✔
44
  }, []);
45

46
  const categories = useSelector((state) => state.category.categories);
5✔
47

48
  const {
49
    register,
50
    handleSubmit,
51
    formState: { errors },
52
    setValue,
53
    reset,
54
  } = useForm({
3✔
55
    resolver: yupResolver(createProductSchema),
56
  });
57

58
  useEffect(() => {
3✔
59
    setValue('images', selectedImages);
2✔
60
  }, [selectedImages]);
61

62
  const onSubmit = async (productData) => {
3✔
63
    delete productData.images;
×
64
    let date = productData.expiryDate;
×
65
    delete productData.expiryDate;
×
66
    date = convertDate(date);
×
67
    const newData = getFormFromObject(productData);
×
68
    selectedImages.forEach((image) => {
×
69
      newData.append('images', image);
×
70
    });
71
    newData.append('expiryDate', date);
×
72
    try {
×
73
      const response = await dispatch(createProduct(newData)).unwrap();
×
74
      showSuccessMessage(response.message);
×
75
      setSelectedImages([]);
×
76
      reset();
×
77
      return (window.location.href = '/dashboard/products');
×
78
    } catch (error) {
79
      showErrorMessage(error.data.message);
×
80
    }
81
  };
82

83
  return (
3✔
84
    <div className="xs:px-6">
85
      <h1 className="sm:hidden text-center text-[25px] font-bold block py-10">
86
        Add new product
87
      </h1>
88
      <form
89
        onSubmit={(event) => {
90
          handleSubmit(onSubmit)(event);
1✔
91
        }}
92
        className="my-8 md:px-8"
93
      >
94
        <div className="grid md:grid-cols-2 gap-10 sm:grid-flow-row">
95
          <div className="">
96
            <div className="my-7">
97
              <InputField
98
                className="appearance-none border w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none h-8"
99
                label="Name"
100
                placeholder="Product Name"
101
                type="text"
102
                {...register('name')}
103
                error={errors?.name}
104
              />
105
            </div>
106

107
            <div className="my-7">
108
              <select
109
                className="appearance-none border w-full px-3 text-gray-700 leading-tight focus:outline-none h-8"
110
                {...register('categoryId')}
111
                defaultValue={''}
112
              >
113
                <option disabled value="">
114
                  Select category
115
                </option>
116
                {categories?.map((category) => (
117
                  <option
×
118
                    data-testid="select-option"
119
                    key={category.id}
120
                    value={category.id}
121
                  >
122
                    {category.name}
123
                  </option>
124
                ))}
125
              </select>
126
              {errors?.categoryId && (
3!
127
                <p className="text-red-500 text-xs ">
128
                  {errors?.categoryId?.message}
129
                </p>
130
              )}
131
            </div>
132

133
            <div className="my-7">
134
              <InputField
135
                className="appearance-none border w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none h-8"
136
                label="Price"
137
                placeholder="Product Price"
138
                type="number"
139
                {...register('price')}
140
                error={errors?.price}
141
              />
142
            </div>
143

144
            <div className="my-7">
145
              <InputField
146
                className="appearance-none border w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none h-8"
147
                label="Quantity"
148
                placeholder="Product Quantity"
149
                type="number"
150
                {...register('quantity')}
151
                error={errors?.quantity}
152
              />
153
            </div>
154

155
            <div className="my-7">
156
              <InputField
157
                className="appearance-none border w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none h-8"
158
                label="Bonus"
159
                placeholder="Product Bonus"
160
                type="number"
161
                {...register('bonus')}
162
                error={errors?.bonus}
163
              />
164
            </div>
165

166
            <div className="my-7">
167
              <InputField
168
                className="appearance-none border w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none h-8"
169
                label="Expiry Date"
170
                name="Expiry Date"
171
                type="date"
172
                min={today}
173
                {...register('expiryDate')}
174
                error={errors?.expiryDate}
175
              />
176
            </div>
177

178
            <div className="my-7 w-28 xs:w-full xs:sticky xs:top-[100vh]">
179
              {isLoading ? (
3!
180
                <>
181
                  <Button type="submit" label="" className="" disabled={true}>
182
                    <svg
183
                      role="status"
184
                      className="inline mr-3 w-4 h-4 text-white animate-spin"
185
                      viewBox="0 0 100 101"
186
                      fill="none"
187
                      xmlns="http://www.w3.org/2000/svg"
188
                    >
189
                      <path
190
                        d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
191
                        fill="#E5E7EB"
192
                      />
193
                      <path
194
                        d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
195
                        fill="currentColor"
196
                      />
197
                    </svg>
198
                  </Button>
199
                </>
200
              ) : (
201
                <Button
202
                  type="submit"
203
                  role="submit"
204
                  label="Save"
205
                  className="w-28 h-8 rounded-none"
206
                />
207
              )}
208
            </div>
209
          </div>
210

211
          <div className=" grid grid-cols-2 content-baseline justify-items-center gap-3 md:gap-10">
212
            {selectedImages &&
6✔
213
              selectedImages.map((image, index) => {
214
                return (
×
215
                  <img
216
                    key={index}
217
                    className="w-24 h-24 md:w-48 md:h-48 border object-cover py-1 px-1"
218
                    src={URL.createObjectURL(image)}
219
                    alt="thumbnail"
220
                  />
221
                );
222
              })}
223
            <label htmlFor="images" className="w-24 h-24 md:w-48 md:h-48">
224
              <div className="w-24 h-24 md:w-48 md:h-48 border py-6 md:py-16 text-[2rem] text-center bg-white">
225
                +
226
              </div>
227
              {errors?.images && (
3!
228
                <p className="text-red-500 text-xs ">
229
                  {errors?.images?.message}
230
                </p>
231
              )}
232
            </label>
233
            <input
234
              className="hidden"
235
              name="images"
236
              id="images"
237
              onChange={handleFileInputChange}
238
              type="file"
239
              accept="image/png, image/jpeg, image/jpg, image/webp, image/gif"
240
              multiple
241
            />
242
          </div>
243
        </div>
244
      </form>
245
    </div>
246
  );
247
};
248

249
export default CreateProductForm;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc