diff --git a/src/pages/CreateResourcePage/ui/CreateResourcePage.module.css b/src/pages/CreateResourcePage/ui/CreateResourcePage.module.css index 220b188..8659069 100644 --- a/src/pages/CreateResourcePage/ui/CreateResourcePage.module.css +++ b/src/pages/CreateResourcePage/ui/CreateResourcePage.module.css @@ -1,114 +1,119 @@ -.container { - max-width: 500px; - margin: 30px auto; - } - - .title { - text-align: left; - margin-bottom: 20px; - color: #000000; - font-size: 20px; - font-weight: 400; - } - - .form { - display: flex; - flex-direction: column; - gap: 20px - } - - .label { - font-weight: bold; - } - - .input, - .textarea, - .fileButtons { - padding: 10px; - border: 1px solid #000000; - border-radius: 4px; - font-size: 20px; - } - - .input { - min-height: 60px; - } - - .input::placeholder, - .textarea::placeholder { - color: #000000; - font-size: 20px; - font-weight: 400; - line-height: 26px; - font-family: "Fira Mono", monospace; - } - - .textarea { - resize: vertical; - } - - .submitButton { - background-color: transparent; - color: #000000; - border: 1px solid #000000; - padding: 10px 16px; - border-radius: 4px; - cursor: pointer; - font-size: 12px; - width: 35%; - } - - .hiddenInput { - display: none; - } - - .fileButtons { - display: flex; - gap: 10px; - box-sizing: border-box; - } - - .iconButton { - background: none; - border: none; - cursor: pointer; - padding: 5px; - } - - .iconButton svg { - width: 30px; - height: 30px; - fill: #ffffff; - transition: fill 0.3s; - } - - .fileInfo { - font-size: 14px; - color: #000000; - } - - .tagSelector { - display: flex; - justify-content: space-between; - gap: 8px; - } - - .tagButton { - background-color: transparent; - border-radius: 5px; - padding: 4px 8px; - font-size: 12px; - color: #000000; - cursor: pointer; - border: 1px solid #000000; - min-height: 32px; - min-width: 102px; - box-sizing: border-box; - } - - .activeTag { - background-color: #000000; - color: #ffffff; - border-color: #000000; - } - \ No newline at end of file +.select { + width: 100%; +} + +.select :global(.select__control) { + background-color: transparent; + border: 1px solid rgb(100 116 139); + border-radius: 6px; + min-height: unset; + padding: 4px 0; + transition: border-color 150ms; + box-shadow: none; + cursor: pointer; +} + +.select :global(.select__control--is-focused) { + border-color: rgb(148 163 184); + box-shadow: none; +} + +.select :global(.select__control:hover) { + border-color: rgb(148, 163, 184); +} + +.select :global(.select__value-container) { + padding: 0 12px; +} + +.select :global(.select__placeholder) { + color: rgb(100 116 139); + font-size: 14px; +} + +.select :global(.select__single-value) { + font-size: 14px; + color: #111827; +} + +.select :global(.select__dropdown-indicator) { + padding-right: 12px; + cursor: pointer; +} + +.select :global(.select__clear-indicator) { + cursor: pointer; +} + +.select :global(.select__dropdown-indicator svg), +.select :global(.select__clear-indicator svg) { + fill: rgb(100 116 139); + transition: fill 150ms ease; +} + +.select :global(.select__dropdown-indicator:hover svg), +.select :global(.select__clear-indicator:hover svg) { + fill: rgb(148, 163, 184); +} + +.select :global(.select__indicator-separator) { + display: none; +} + +.select :global(.select__menu) { + border-radius: 6px; + overflow: hidden; +} + +.select :global(.select__option) { + cursor: pointer !important; +} + +.select :global(.select__option--is-focused) { + background-color: rgba(241, 233, 233, 0.9); +} + +.select :global(.select__option--is-selected) { + background-color: rgba(223, 205, 205, 0.9); + color: #fff; +} + +.select :global(.select__multi-value) { + background-color: #fef7f8; + border-radius: 100px; + padding: 5px 8px; +} + +.select :global(.select__multi-value__label) { + font-size: 12px; +} + +.select :global(.select__multi-value__remove:hover) { + background-color: transparent; +} + +.select :global(.select__multi-value__remove svg) { + fill: rgb(100 116 139); +} + +.select :global(.select__multi-value__remove:hover svg) { + fill: rgb(148, 163, 184); +} + +.audienceContainer { + position: relative; + background: rgba(250, 103, 115, 0.15); + backdrop-filter: blur(30); + box-shadow: 0px 6px 10px 0px #fe444440; + overflow: hidden; +} + +.audienceContainer::before { + content: ""; + position: absolute; + inset: 0; + background-color: rgba(250, 103, 115, 0.2); + filter: blur(30px); + z-index: -1; + box-shadow: 0px 6px 10px 0px #fe444440; +} diff --git a/src/pages/CreateResourcePage/ui/CreateResourcePage.tsx b/src/pages/CreateResourcePage/ui/CreateResourcePage.tsx index 80ce096..cd16b88 100644 --- a/src/pages/CreateResourcePage/ui/CreateResourcePage.tsx +++ b/src/pages/CreateResourcePage/ui/CreateResourcePage.tsx @@ -1,60 +1,137 @@ -import React, { useState } from "react"; +import { useEffect, useState } from "react"; import styles from "./CreateResourcePage.module.css"; -import VideoIcon from "../../../shared/ui/icons/VideoIcon"; -import ImageIcon from "../../../shared/ui/icons/ImageIcon"; -import FileIcon from "../../../shared/ui/icons/FileIcon"; -import CrossIcon from "../../../shared/ui/icons/CrossIcon"; import { useNavigate } from "react-router-dom"; import { Resource } from "../../CommunityResourcesPage/ui/constants"; +import Select from "react-select"; +import CreatableSelect from "react-select/creatable"; +import { MultiValue } from "react-select"; +import { useUser } from "../../../shared/lib/customHooks/useUser"; +import axios from "axios"; +import ConfirmModal from "../../../shared/ui/ConfirmModal/ConfirmModal"; +import Navbar from "../../../shared/ui/Navbar/Navbar"; +import makeAnimated from "react-select/animated"; +import Footer from "../../../shared/ui/Footer/Footer"; +import Button, { ButtonsEnum } from "../../../shared/ui/Button/Button"; interface CreateResourcePageProps { addResource?: (newResource: Resource) => void; } +type Type = "post" | "video"; +type Audience = "public" | "member" | "mentor"; + export const CreateResourcePage: React.FC = ({ addResource, }) => { - const [title, setTitle] = useState(""); - const [description, setDescription] = useState(""); - const [file, setFile] = useState(null); - const [fullInfo, setFullInfo] = useState(""); - const [selectedTag, setSelectedTag] = useState(null); - - const tags = ["Documents", "Video", "Links", "Posts"]; + const userIdFromLocalStorage = localStorage.getItem("userId") + ? Number(localStorage.getItem("userId")) + : null; + const { user } = useUser(userIdFromLocalStorage); + const [selectedType, setSelectedType] = useState("post"); + const [title, setTitle] = useState(""); + const [description, setDescription] = useState(""); + const [tags, setTags] = useState< + MultiValue<{ value: string; label: string }> + >([]); + const [selectedTags, setSelectedTags] = useState< + MultiValue<{ value: string; label: string }> + >([]); + const [audience, setAudience] = useState("public"); + const [isMentor, setIsMentor] = useState(false); + const [errors, setErrors] = useState>({}); + const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false); const navigate = useNavigate(); + const animatedComponent = makeAnimated(); + const MAX_TAG_LENGTH = 50; - const handleTagClick = (tag: string) => { - setSelectedTag(tag); - }; + const resourceTypeOptions = [ + { value: "post", label: "Post" }, + { value: "video", label: "YouTube video" }, + ]; + + const audienceOptions: { + value: Audience; + label: string; + isMentorOnly?: boolean; + }[] = [ + { value: "public", label: "Everyone" }, + { value: "member", label: "Members only" }, + { value: "mentor", label: "Mentors only", isMentorOnly: isMentor }, + ]; - const handleFileChange = (e: React.ChangeEvent) => { - if (e.target.files && e.target.files[0]) { - setFile(e.target.files[0]); + useEffect(() => { + if (user) { + setIsMentor( + user?.roles?.filter( + (role: { role_id: number; role_name: string }) => + role.role_name === "mentor", + ).length > 0, + ); } + }, [user]); + + useEffect(() => { + const fetchTags = async () => { + try { + const response = await axios.get(`${import.meta.env.VITE_DEVAPI}tags`, { + withCredentials: true, + }); + + const formattedTags = response?.data?.tags.map((tag: string) => ({ + value: tag, + label: tag, + })); + + setTags(formattedTags); + } catch (error) { + console.error("Error fetching tags:", error); + } + }; + + fetchTags(); + }, []); + + const handleTagChange = ( + selectedOptions: MultiValue<{ value: string; label: string }>, + ) => { + setSelectedTags(selectedOptions || []); + setErrors((prev) => ({ ...prev, selectedTags: "" })); }; - const handleFileClick = (accept: string) => { - const input = document.getElementById("file") as HTMLInputElement; - if (input) { - input.setAttribute("accept", accept); - input.click(); + const validate = () => { + const validateErrors: Record = {}; + + if (!title.trim()) { + validateErrors.title = "Title is required"; + } else if (title.trim().length < 1 || title.trim().length > 100) { + validateErrors.title = "Title must be between 1 and 100 characters"; + } + if (selectedType === "post" && !description.trim()) { + validateErrors.description = "Description is required for post"; } + if (selectedTags.length === 0 || selectedTags.length >= 10) + validateErrors.selectedTags = "Please add from 1 to 10 tags"; + + setErrors(validateErrors); + return Object.keys(validateErrors).length === 0; }; - const handleSubmit = (e: React.FormEvent) => { + const handlePublishClick = (e: React.FormEvent) => { e.preventDefault(); - if (!selectedTag) { - alert("Please select a tag for the resource."); - return; - } + if (!validate()) return; + + setIsConfirmModalOpen(true); + }; + + const submitResource = () => { const newResource = { id: Date.now(), + selectedType, title, description, - file, - fullInfo, - tags: [selectedTag], + tags: selectedTags.map((tag) => tag.value), + audience, }; if (addResource) { @@ -62,108 +139,164 @@ export const CreateResourcePage: React.FC = ({ } navigate("/community_resources"); - setTitle(""); + setSelectedType("post"); setDescription(""); - setFile(null); - setFullInfo(""); - setSelectedTag(null); + setSelectedTags([]); + setAudience("public"); + }; + + const handleConfirmation = (confirmed: boolean) => { + setIsConfirmModalOpen(false); + + if (confirmed) { + submitResource(); + } }; return ( -
-

Create a New Resource

-
- setTitle(e.target.value)} - className={styles.input} - required - /> - setDescription(e.target.value)} - className={styles.input} - required - /> - - - -
- - - -