mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
Merge pull request #442 from JustFlavio/refactor/course-creation-tags
feat: replace textareas with FormTagInput for tags and learnings
This commit is contained in:
commit
6878fec2e8
7 changed files with 1482 additions and 49 deletions
|
|
@ -30,7 +30,7 @@ const CourseClient = (props: any) => {
|
|||
|
||||
function getLearningTags() {
|
||||
// create array of learnings from a string object (comma separated)
|
||||
let learnings = course?.learnings ? course?.learnings.split(',') : []
|
||||
let learnings = course?.learnings ? course?.learnings.split('|') : []
|
||||
setLearnings(learnings)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import * as Form from '@radix-ui/react-form';
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import ThumbnailUpdate from './ThumbnailUpdate';
|
||||
import { useCourse, useCourseDispatch } from '@components/Contexts/CourseContext';
|
||||
import FormTagInput from '@components/Objects/StyledElements/Form/TagInput';
|
||||
|
||||
type EditCourseStructureProps = {
|
||||
orgslug: string
|
||||
|
|
@ -138,24 +139,22 @@ function EditCourseGeneral(props: EditCourseStructureProps) {
|
|||
<FormField name="learnings">
|
||||
<FormLabelAndMessage label="Learnings" message={formik.errors.learnings} />
|
||||
<Form.Control asChild>
|
||||
<Textarea
|
||||
style={{ backgroundColor: 'white' }}
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.learnings}
|
||||
required
|
||||
/>
|
||||
<FormTagInput
|
||||
placeholder="Enter to add..."
|
||||
onChange={(value) => formik.setFieldValue('learnings', value)}
|
||||
value={formik.values.learnings}
|
||||
/>
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
|
||||
<FormField name="tags">
|
||||
<FormLabelAndMessage label="Tags" message={formik.errors.tags} />
|
||||
<Form.Control asChild>
|
||||
<Textarea
|
||||
style={{ backgroundColor: 'white' }}
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.tags}
|
||||
required
|
||||
/>
|
||||
<FormTagInput
|
||||
placeholder="Enter to add..."
|
||||
onChange={(value) => formik.setFieldValue('tags', value)}
|
||||
value={formik.values.tags}
|
||||
/>
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { useFormik } from 'formik'
|
|||
import * as Yup from 'yup'
|
||||
import { UploadCloud, Image as ImageIcon } from 'lucide-react'
|
||||
import UnsplashImagePicker from "@components/Dashboard/Pages/Course/EditCourseGeneral/UnsplashImagePicker"
|
||||
import FormTagInput from "@components/Objects/StyledElements/Form/TagInput"
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
name: Yup.string()
|
||||
|
|
@ -51,15 +52,16 @@ function CreateCourseModal({ closeModal, orgslug }: any) {
|
|||
validationSchema,
|
||||
onSubmit: async (values, { setSubmitting }) => {
|
||||
const toast_loading = toast.loading('Creating course...')
|
||||
|
||||
|
||||
try {
|
||||
const res = await createNewCourse(
|
||||
orgId,
|
||||
{
|
||||
name: values.name,
|
||||
description: values.description,
|
||||
tags: values.tags,
|
||||
visibility: values.visibility
|
||||
{
|
||||
name: values.name,
|
||||
description: values.description,
|
||||
learnings: values.learnings,
|
||||
tags: values.tags,
|
||||
visibility: values.visibility
|
||||
},
|
||||
values.thumbnail,
|
||||
session.data?.tokens?.access_token
|
||||
|
|
@ -123,8 +125,8 @@ function CreateCourseModal({ closeModal, orgslug }: any) {
|
|||
return (
|
||||
<FormLayout onSubmit={formik.handleSubmit} >
|
||||
<FormField name="name">
|
||||
<FormLabelAndMessage
|
||||
label="Course Name"
|
||||
<FormLabelAndMessage
|
||||
label="Course Name"
|
||||
message={formik.errors.name}
|
||||
/>
|
||||
<Form.Control asChild>
|
||||
|
|
@ -138,21 +140,21 @@ function CreateCourseModal({ closeModal, orgslug }: any) {
|
|||
</FormField>
|
||||
|
||||
<FormField name="description">
|
||||
<FormLabelAndMessage
|
||||
label="Description"
|
||||
<FormLabelAndMessage
|
||||
label="Description"
|
||||
message={formik.errors.description}
|
||||
/>
|
||||
<Form.Control asChild>
|
||||
<Textarea
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.description}
|
||||
|
||||
|
||||
/>
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
|
||||
<FormField name="thumbnail">
|
||||
<FormLabelAndMessage
|
||||
<FormLabelAndMessage
|
||||
label="Course Thumbnail"
|
||||
message={formik.errors.thumbnail}
|
||||
/>
|
||||
|
|
@ -200,22 +202,34 @@ function CreateCourseModal({ closeModal, orgslug }: any) {
|
|||
</div>
|
||||
</FormField>
|
||||
|
||||
<FormField name="tags">
|
||||
<FormLabelAndMessage
|
||||
label="Course Tags"
|
||||
message={formik.errors.tags}
|
||||
/>
|
||||
<Form.Control asChild>
|
||||
<Textarea
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.tags}
|
||||
placeholder="Enter tags separated by commas"
|
||||
/>
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
<FormField name="learnings">
|
||||
<FormLabelAndMessage
|
||||
label="Course Learnings (What will you teach?)"
|
||||
message={formik.errors.learnings}
|
||||
/>
|
||||
<FormTagInput
|
||||
placeholder="Enter to add..."
|
||||
value={formik.values.learnings}
|
||||
onChange={(value) => formik.setFieldValue('learnings', value)}
|
||||
error={formik.errors.learnings}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField name="tags">
|
||||
<FormLabelAndMessage
|
||||
label="Course Tags"
|
||||
message={formik.errors.tags}
|
||||
/>
|
||||
<FormTagInput
|
||||
placeholder="Enter to add..."
|
||||
value={formik.values.tags}
|
||||
onChange={(value) => formik.setFieldValue('tags', value)}
|
||||
error={formik.errors.tags}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField name="visibility">
|
||||
<FormLabelAndMessage
|
||||
<FormLabelAndMessage
|
||||
label="Course Visibility"
|
||||
message={formik.errors.visibility}
|
||||
/>
|
||||
|
|
|
|||
83
apps/web/components/Objects/StyledElements/Form/TagInput.tsx
Normal file
83
apps/web/components/Objects/StyledElements/Form/TagInput.tsx
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import React, { useState, Dispatch, SetStateAction, useEffect } from 'react'
|
||||
import { Tag, TagInput } from 'emblor'
|
||||
|
||||
interface FormTagInputProps {
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
separator?: string
|
||||
error?: string
|
||||
placeholder?: string
|
||||
}
|
||||
|
||||
const FormTagInput = ({
|
||||
value,
|
||||
onChange,
|
||||
separator = '|',
|
||||
error,
|
||||
placeholder,
|
||||
}: FormTagInputProps) => {
|
||||
const [tags, setTags] = useState<Tag[]>(() =>
|
||||
value && typeof value === 'string'
|
||||
? value.split(separator).filter(text => text.trim()).map((text, i) => ({
|
||||
id: i.toString(),
|
||||
text: text.trim(),
|
||||
}))
|
||||
: []
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (value && typeof value === 'string') {
|
||||
const newTags = value.split(separator)
|
||||
.filter(text => text.trim())
|
||||
.map((text, i) => ({
|
||||
id: i.toString(),
|
||||
text: text.trim(),
|
||||
}));
|
||||
setTags(newTags);
|
||||
} else {
|
||||
setTags([]);
|
||||
}
|
||||
}, [value, separator]);
|
||||
|
||||
const [activeTagIndex, setActiveTagIndex] = useState<number | null>(null)
|
||||
|
||||
const handleTagsChange: Dispatch<SetStateAction<Tag[]>> = (
|
||||
newTagsOrUpdater
|
||||
) => {
|
||||
const newTags =
|
||||
typeof newTagsOrUpdater === 'function'
|
||||
? newTagsOrUpdater(tags)
|
||||
: newTagsOrUpdater
|
||||
|
||||
setTags(newTags)
|
||||
onChange(newTags.map((tag) => tag.text).join(separator))
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="space-y-2">
|
||||
<TagInput
|
||||
tags={tags}
|
||||
setTags={handleTagsChange}
|
||||
placeholder={placeholder}
|
||||
styleClasses={{
|
||||
inlineTagsContainer:
|
||||
'border-input rounded-lg bg-background shadow-xs transition-shadow focus-within:border-ring/40 focus-within:outline-hidden focus-within:ring-[3px] ring-ring/8 dark:ring-ring/12 p-1 gap-1',
|
||||
input:
|
||||
'w-full min-w-[80px] focus-visible:outline-hidden shadow-none px-2 h-7',
|
||||
tag: {
|
||||
body: 'h-7 relative bg-background border border-input hover:bg-background rounded-md font-medium text-xs ps-2 pe-7',
|
||||
closeButton:
|
||||
'absolute -inset-y-px -end-px p-0 rounded-e-lg flex size-7 transition-colors outline-hidden focus-visible:ring-2 focus-visible:ring-ring/30 dark:focus-visible:ring-ring/40 text-muted-foreground/80 hover:text-foreground',
|
||||
},
|
||||
}}
|
||||
activeTagIndex={activeTagIndex}
|
||||
setActiveTagIndex={setActiveTagIndex}
|
||||
/>
|
||||
{error && <p className="text-sm font-medium text-destructive">{error}</p>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FormTagInput
|
||||
|
|
@ -50,6 +50,7 @@
|
|||
"currency-codes": "^2.2.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"dompurify": "^3.2.4",
|
||||
"emblor": "^1.4.7",
|
||||
"formik": "^2.4.6",
|
||||
"framer-motion": "^10.18.0",
|
||||
"get-youtube-id": "^1.0.1",
|
||||
|
|
|
|||
1354
apps/web/pnpm-lock.yaml
generated
1354
apps/web/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -102,7 +102,7 @@ export async function createNewCourse(
|
|||
formData.append('name', course_body.name)
|
||||
formData.append('description', course_body.description)
|
||||
formData.append('public', course_body.visibility)
|
||||
formData.append('learnings', course_body.tags)
|
||||
formData.append('learnings', course_body.learnings)
|
||||
formData.append('tags', course_body.tags)
|
||||
formData.append('about', course_body.description)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue