Skip to content

Commit 83f1756

Browse files
committed
seller-ui -> Dashboard/Create Product page WIP
1 parent f3bb264 commit 83f1756

7 files changed

Lines changed: 482 additions & 20 deletions

File tree

apps/seller-ui/src/app/(routes)/dashboard/create-product/page.tsx

Lines changed: 140 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import { ChevronRight } from "lucide-react";
33
import React, { useState } from "react";
44
import { set, useForm } from "react-hook-form";
55
import ImagePlaceHolder from "../../../shared/components/image-placeholder";
6+
import Input from "packages/components/input";
7+
import ColorSelector from "packages/components/color-selector";
8+
import CustomSpecifications from "packages/components/custom-specifications";
9+
import CustomProperties from "packages/components/custom-properties";
610

711
const CreateProduct = () => {
812
const {
@@ -74,27 +78,144 @@ const CreateProduct = () => {
7478
onRemove={handleRemoveImage}
7579
/>
7680
)}
81+
<div className="grid grid-cols-2 gap-3 mt-4 ">
82+
{images.slice(1).map((_, index) => (
83+
<ImagePlaceHolder
84+
key={index + 1}
85+
setOpenImageModal={setOpenImageModal}
86+
size="150 x 150"
87+
small={true}
88+
index={index + 1}
89+
onImageChange={handleImageChange}
90+
onRemove={handleRemoveImage}
91+
/>
92+
))}
93+
</div>
7794
</div>
7895

79-
<div className="grid grid-cols-2 gap-3 mt-4 ">
80-
{images.slice(1).map((_, index) => (
81-
<ImagePlaceHolder
82-
key={index + 1}
83-
setOpenImageModal={setOpenImageModal}
84-
size="150 x 150"
85-
small={true}
86-
index={index + 1}
87-
onImageChange={handleImageChange}
88-
onRemove={handleRemoveImage}
89-
/>
90-
))}
91-
</div>
92-
</div>
93-
{/* Right Side - Form Input */}
94-
<div className="md:w-[65%]">
95-
<div className="w-full flex gap-6">
96-
{/* Product Title Input */}
97-
<div className="w-2/4"></div>
96+
{/* Right Side - Form Input */}
97+
<div className="md:w-[65%]">
98+
<div className="w-full flex gap-6">
99+
{/* Product Title Input */}
100+
<div className="w-2/4">
101+
<Input
102+
label="Product Title *"
103+
placeholder="Enter product title"
104+
{...register("title", { required: "Title is required" })}
105+
/>
106+
{errors.title && (
107+
<p className="text-red-500 text-xs mt-1">
108+
{errors.title.message as string}
109+
</p>
110+
)}
111+
<div className="mt-2">
112+
<Input
113+
type="textarea"
114+
rows={7}
115+
cols={10}
116+
label="Product Description * (max 150 words)"
117+
placeholder="Enter product description"
118+
{...register("description", {
119+
required: "Description is required",
120+
validate: (value) => {
121+
const wordCount = value.trim().split(/\s+/).length;
122+
return (
123+
wordCount <= 150 ||
124+
`Description must be less than 150 words (Currently ${wordCount} words)`
125+
);
126+
},
127+
})}
128+
/>
129+
{errors.description && (
130+
<p className="text-red-500 text-xs mt-1">
131+
{errors.description.message as string}
132+
</p>
133+
)}
134+
</div>
135+
{/* Tags */}
136+
<div className="mt-2">
137+
<Input
138+
label="Tags *"
139+
placeholder="Apple, Flagship, iPhone"
140+
{...register("tags", {
141+
required: "separate related product tags with commas , ",
142+
})}
143+
/>
144+
{errors.tags && (
145+
<p className="text-red-500 text-xs mt-1">
146+
{errors.tags.message as string}
147+
</p>
148+
)}
149+
</div>
150+
{/* Warranty */}
151+
<div className="mt-2">
152+
<Input
153+
label="Warranty *"
154+
placeholder="1year / No Warranty"
155+
{...register("warranty", {
156+
required: "Warranty is required",
157+
})}
158+
/>
159+
{errors.warranty && (
160+
<p className="text-red-500 text-xs mt-1">
161+
{errors.warranty.message as string}
162+
</p>
163+
)}
164+
</div>
165+
{/* Slug */}
166+
<div className="mt-2">
167+
<Input
168+
label="Slug *"
169+
placeholder="product-slug-example"
170+
{...register("slug", {
171+
required: "Slug is required",
172+
pattern: {
173+
value: /^[a-z0-9]+(?:-[a-z0-9]+)*$/,
174+
message:
175+
"Slug can only contain lowercase letters, numbers, and hyphens",
176+
},
177+
minLength: {
178+
value: 3,
179+
message: "Slug must be at least 3 characters long",
180+
},
181+
maxLength: {
182+
value: 50,
183+
message: "Slug cannot exceed 50 characters",
184+
},
185+
})}
186+
/>
187+
{errors.slug && (
188+
<p className="text-red-500 text-xs mt-1">
189+
{errors.slug.message as string}
190+
</p>
191+
)}
192+
</div>
193+
{/* Brand */}
194+
<div className="mt-2">
195+
<Input
196+
label="Brand"
197+
placeholder="Apple, Samsung, Xiaomi"
198+
{...register("brand")}
199+
/>
200+
{errors.brand && (
201+
<p className="text-red-500 text-xs mt-1">
202+
{errors.brand.message as string}
203+
</p>
204+
)}
205+
</div>
206+
<div className="mt-2">
207+
<ColorSelector control={control} errors={errors} />
208+
</div>
209+
210+
<div className="mt-2">
211+
<CustomSpecifications control={control} errors={errors} />
212+
</div>
213+
214+
<div className="mt-2">
215+
<CustomProperties control={control} errors={errors} />
216+
</div>
217+
</div>
218+
</div>
98219
</div>
99220
</div>
100221
</form>

apps/seller-ui/tailwind.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module.exports = {
1414
content: [
1515
"./{src,pages,components,app}/**/*.{ts,tsx,js,jsx,html}",
1616
"../seller-ui/src/**/*.{js,ts,tsx,jsx}",
17+
"../../packages/components/**/*.{js,ts,tsx,jsx}",
1718
"!./{src,pages,components,app}/**/*.{stories,spec}.{ts,tsx,js,jsx,html}",
1819
// ...createGlobPatternsForDependencies(__dirname)
1920
],

apps/seller-ui/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@
2424
"@/*": ["./src/*"]
2525
},
2626
"outDir": "dist",
27-
"rootDir": "src",
27+
"rootDir": "../../",
2828
"tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
2929
},
3030
"include": [
3131
"../../apps/seller-ui/.next/types/**/*.ts",
32+
"../../packages/**/*.tsx",
3233
"../../dist/apps/seller-ui/.next/types/**/*.ts",
3334
"next-env.d.ts",
3435
"src/**/*.js",
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { Divide, Plus } from "lucide-react";
2+
import { useState } from "react";
3+
import { Controller } from "react-hook-form";
4+
5+
const defaultColors = [
6+
"#FFFFFF", //White
7+
"#000000", //Black
8+
"#FF0000", //Red
9+
"#00FF00", //Green
10+
"#0000FF", //Blue
11+
"#FFFF00", //Yellow
12+
"#FFA500", //Orange
13+
"#800080", //Purple
14+
"#00FFFF", //Cyan
15+
"#FFC0CB", //Pink
16+
];
17+
18+
const ColorSelector = ({ control, errors }: any) => {
19+
const [customColors, setCustomColors] = useState<string[]>([]);
20+
const [showColorPicker, setShowColorPicker] = useState(false);
21+
const [newColor, setNewColor] = useState("#ffffff");
22+
return (
23+
<div className="mt-2">
24+
<label className="block font-semibold text-gray-300 mb-1" htmlFor="">
25+
Colors
26+
</label>
27+
<Controller
28+
name="colors"
29+
control={control}
30+
render={({ field }) => (
31+
<div className="flex flex-wrap gap-3">
32+
{[...defaultColors, ...customColors].map((color) => {
33+
const isSelected = (field.value || []).includes(color);
34+
const isLightColor = [
35+
"#FFFFFF",
36+
"#FFFF00",
37+
"#FFC0CB",
38+
"#00FFFF",
39+
].includes(color);
40+
return (
41+
<button
42+
type="button"
43+
key={color}
44+
onClick={() =>
45+
field.onChange(
46+
isSelected
47+
? field.value.filter((c: string) => c !== color)
48+
: [...(field.value || []), color]
49+
)
50+
}
51+
className={`w-8 h-8 rounded-md my-1 flex justify-center transition border-2 ${
52+
isSelected ? "scale-110 border-white" : "border-transparent"
53+
} ${
54+
isLightColor ? "border-gray-600" : ""
55+
} focus:outline-none`}
56+
style={{ backgroundColor: color }}
57+
aria-label={color}
58+
/>
59+
);
60+
})}
61+
{/* Add New color */}
62+
<button
63+
type="button"
64+
onClick={() => setShowColorPicker(!showColorPicker)}
65+
className="w-8 h-8 flex items-center justify-center rounded-full border-2 border-gray-500 bg-gray-800 hover:bg-gray-700 transition"
66+
>
67+
<Plus size={16} color="white" />
68+
</button>
69+
{/* Color Picker */}
70+
{showColorPicker && (
71+
<div className="relative flex items-center gap-2">
72+
<input
73+
type="color"
74+
value={newColor}
75+
onChange={(e) => setNewColor(e.target.value)}
76+
className="w-10 h-10 p-0 border-none cursor-pointer"
77+
/>
78+
<button
79+
type="button"
80+
className="px-3 py-1 bg-gray-700 text-white rounded-md text-sm"
81+
onClick={() => {
82+
setCustomColors([...customColors, newColor]);
83+
setShowColorPicker(false);
84+
}}
85+
>
86+
Add
87+
</button>
88+
</div>
89+
)}
90+
</div>
91+
)}
92+
/>
93+
</div>
94+
);
95+
};
96+
97+
export default ColorSelector;

0 commit comments

Comments
 (0)