Add tag function in CreatePostPage
This commit is contained in:
74
client/src/components/NextUIFormikTagInput.tsx
Normal file
74
client/src/components/NextUIFormikTagInput.tsx
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { Input } from "@nextui-org/react";
|
||||||
|
import { useField } from "formik";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { EyeIcon, EyeSlashIcon } from "../icons";
|
||||||
|
|
||||||
|
interface NextUIFormikTagInputProps {
|
||||||
|
label: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
placeholder: string;
|
||||||
|
labelPlacement?: "inside" | "outside";
|
||||||
|
startContent?: JSX.Element;
|
||||||
|
readOnly?: boolean;
|
||||||
|
setFieldValue?: (field: string, value: any, shouldValidate?: boolean) => void;
|
||||||
|
value?: string;
|
||||||
|
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void; // Allow onChange prop
|
||||||
|
}
|
||||||
|
|
||||||
|
const NextUIFormikTagInput = ({
|
||||||
|
label,
|
||||||
|
startContent,
|
||||||
|
readOnly = false,
|
||||||
|
setFieldValue,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
...props
|
||||||
|
}: NextUIFormikTagInputProps) => {
|
||||||
|
// Use Formik's useField hook to get field props and meta information
|
||||||
|
const [field, meta] = useField(props.name);
|
||||||
|
const [inputType, setInputType] = useState(props.type);
|
||||||
|
|
||||||
|
// Handle changes in the input field
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { value } = e.target;
|
||||||
|
field.onChange(e);
|
||||||
|
if (setFieldValue) {
|
||||||
|
setFieldValue(props.name, value);
|
||||||
|
}
|
||||||
|
if (onChange) {
|
||||||
|
onChange(e); // Call the passed onChange handler
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
{...props}
|
||||||
|
label={label}
|
||||||
|
type={inputType}
|
||||||
|
isInvalid={meta.touched && !!meta.error}
|
||||||
|
errorMessage={meta.touched && meta.error ? meta.error : ""}
|
||||||
|
startContent={startContent}
|
||||||
|
endContent={
|
||||||
|
props.type === "password" ? (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setInputType(inputType === "text" ? "password" : "text");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{inputType === "password" ? <EyeSlashIcon /> : <EyeIcon />}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
readOnly={readOnly}
|
||||||
|
onChange={handleChange}
|
||||||
|
value={value} // Ensure the value from Formik is applied to the input
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NextUIFormikTagInput;
|
||||||
78
client/src/components/TagInput.tsx
Normal file
78
client/src/components/TagInput.tsx
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { Button, input } from "@nextui-org/react";
|
||||||
|
import NextUIFormikTagInput from "./NextUIFormikTagInput";
|
||||||
|
|
||||||
|
type TagInputProps = {
|
||||||
|
tags: string[];
|
||||||
|
setTags: React.Dispatch<React.SetStateAction<string[]>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TagInput: React.FC<TagInputProps> = ({ tags, setTags }) => {
|
||||||
|
const [inputTag, setInputTag] = useState("");
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Handle input change and dynamic duplicate check
|
||||||
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const newTag = e.target.value.trim();
|
||||||
|
setInputTag(newTag);
|
||||||
|
|
||||||
|
// Dynamic duplicate check
|
||||||
|
if (tags.some(tag => tag.toLowerCase() === newTag.toLowerCase())) {
|
||||||
|
setError("Tag already added.");
|
||||||
|
} else {
|
||||||
|
setError(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddTag = () => {
|
||||||
|
if (error) {
|
||||||
|
return; // Prevent adding if there's an error
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputTag.trim() !== "") {
|
||||||
|
setTags([...tags, inputTag.trim()]);
|
||||||
|
setInputTag(""); // Clear input field
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveTag = (index: number) => {
|
||||||
|
setTags(tags.filter((_, i) => i !== index));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<NextUIFormikTagInput
|
||||||
|
label="Tags (Optional)"
|
||||||
|
name="tagInput" // This name should be unique and not conflict with other form fields
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter tags"
|
||||||
|
labelPlacement="inside"
|
||||||
|
value={inputTag}
|
||||||
|
onChange={handleInputChange} // Use the dynamic check handler
|
||||||
|
/>
|
||||||
|
{error && <div className="text-red-500 mt-2">{error}</div>}
|
||||||
|
<Button onPress={handleAddTag} disabled={!!error}>Add</Button>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2 flex-wrap mt-4">
|
||||||
|
{tags.map((tag, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="flex items-center gap-1 bg-gray-200 p-2 rounded"
|
||||||
|
>
|
||||||
|
<span>{tag}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleRemoveTag(index)}
|
||||||
|
className="ml-2 text-red-500"
|
||||||
|
>
|
||||||
|
X
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TagInput;
|
||||||
@@ -10,6 +10,7 @@ import { ArrowUTurnLeftIcon } from "../icons";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { retrieveUserInformation } from "../security/users";
|
import { retrieveUserInformation } from "../security/users";
|
||||||
import InsertPostImage from "../components/InsertPostImage";
|
import InsertPostImage from "../components/InsertPostImage";
|
||||||
|
import TagInput from "../components/TagInput";
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
title: Yup.string()
|
title: Yup.string()
|
||||||
@@ -37,6 +38,7 @@ function CreatePostPage() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [userId, setUserId] = useState(null);
|
const [userId, setUserId] = useState(null);
|
||||||
// Add state for image selection
|
// Add state for image selection
|
||||||
|
const [tags, setTags] = useState<string[]>([]);
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
title: "",
|
title: "",
|
||||||
@@ -69,7 +71,8 @@ function CreatePostPage() {
|
|||||||
if (values.postImage) {
|
if (values.postImage) {
|
||||||
formData.append("postImage", values.postImage);
|
formData.append("postImage", values.postImage);
|
||||||
}
|
}
|
||||||
formData.append("tags", values.tags);
|
// formData.append("tags", values.tags);
|
||||||
|
formData.append("tags", tags.join(","));
|
||||||
formData.append("userId", userId || ""); // Ensure userId is appended to formData
|
formData.append("userId", userId || ""); // Ensure userId is appended to formData
|
||||||
|
|
||||||
console.log("Submitting formData:", formData);
|
console.log("Submitting formData:", formData);
|
||||||
@@ -83,6 +86,7 @@ function CreatePostPage() {
|
|||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
console.log("Post created successfully:", response.data);
|
console.log("Post created successfully:", response.data);
|
||||||
resetForm(); // Clear form after successful submit
|
resetForm(); // Clear form after successful submit
|
||||||
|
setTags([]);
|
||||||
setFieldValue("postImage", null);
|
setFieldValue("postImage", null);
|
||||||
navigate(-1);
|
navigate(-1);
|
||||||
} else {
|
} else {
|
||||||
@@ -136,13 +140,7 @@ function CreatePostPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<NextUIFormikInput
|
<TagInput tags={tags} setTags={setTags} />
|
||||||
label="Tags (Optional)"
|
|
||||||
name="tags"
|
|
||||||
type="text"
|
|
||||||
placeholder="Enter tags"
|
|
||||||
labelPlacement="inside"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
<div className="flex flex-row gap-10">
|
<div className="flex flex-row gap-10">
|
||||||
|
|||||||
Reference in New Issue
Block a user