1+ 'use client'
2+
3+ import React , { useState , useEffect } from 'react'
4+ import { User , Skill } from '@prisma/client'
5+ import { Input } from "@/components/ui/input"
6+ import { Button } from "@/components/ui/button"
7+ import { X } from 'lucide-react'
8+ import { addUserSkill , removeUserSkill , searchSkills } from './skillActions'
9+ import { useDebounce } from 'use-debounce'
10+
11+ type UserSkillWithSkill = {
12+ id : string ;
13+ skillId : string ;
14+ skill : Skill ;
15+ }
16+
17+ type UserSkillsProps = {
18+ user : User & { userSkills : UserSkillWithSkill [ ] } ;
19+ }
20+
21+ export default function UserSkills ( { user } : UserSkillsProps ) {
22+ const [ searchTerm , setSearchTerm ] = useState ( '' )
23+ const [ debouncedSearchTerm ] = useDebounce ( searchTerm , 300 )
24+ const [ filteredSkills , setFilteredSkills ] = useState < Skill [ ] > ( [ ] )
25+
26+ useEffect ( ( ) => {
27+ const fetchSkills = async ( ) => {
28+ if ( debouncedSearchTerm ) {
29+ const skills = await searchSkills ( debouncedSearchTerm )
30+ setFilteredSkills ( skills . filter ( skill =>
31+ ! user . userSkills . some ( userSkill => userSkill . skillId === skill . id )
32+ ) )
33+ } else {
34+ setFilteredSkills ( [ ] )
35+ }
36+ }
37+ fetchSkills ( )
38+ } , [ debouncedSearchTerm , user . userSkills ] )
39+
40+ const handleAddSkill = async ( skillName : string ) => {
41+ if ( skillName . trim ( ) ) {
42+ await addUserSkill ( user . id , skillName . trim ( ) )
43+ setSearchTerm ( '' )
44+ }
45+ }
46+
47+ const handleRemoveSkill = async ( skillId : string ) => {
48+ await removeUserSkill ( user . id , skillId )
49+ }
50+
51+ const handleKeyPress = ( e : React . KeyboardEvent < HTMLInputElement > ) => {
52+ if ( e . key === 'Enter' ) {
53+ e . preventDefault ( ) // Prevent form submission
54+ handleAddSkill ( searchTerm )
55+ }
56+ }
57+
58+ return (
59+ < div className = "space-y-4" >
60+ < div className = "relative" >
61+ < Input
62+ type = "text"
63+ placeholder = "Search for a skill..."
64+ value = { searchTerm }
65+ onChange = { ( e ) => setSearchTerm ( e . target . value ) }
66+ onKeyPress = { handleKeyPress }
67+ />
68+ { searchTerm && filteredSkills . length > 0 && (
69+ < div className = "absolute z-10 w-full border mt-1 rounded-md shadow-lg bg-background" >
70+ { filteredSkills . map ( ( skill ) => (
71+ < div
72+ key = { skill . id }
73+ className = "p-2 hover:bg-accent hover:text-accent-foreground cursor-pointer"
74+ onClick = { ( ) => handleAddSkill ( skill . name ) }
75+ >
76+ { skill . name }
77+ </ div >
78+ ) ) }
79+ </ div >
80+ ) }
81+ </ div >
82+ < div className = "flex flex-wrap gap-2" >
83+ { user . userSkills . map ( ( userSkill ) => (
84+ < div key = { userSkill . id } className = "flex items-center bg-opacity-10 rounded-full px-3 py-1" >
85+ < span > { userSkill . skill . name } </ span >
86+ < Button
87+ variant = "ghost"
88+ size = "sm"
89+ className = "ml-2 p-0"
90+ onClick = { ( ) => handleRemoveSkill ( userSkill . skillId ) }
91+ >
92+ < X size = { 16 } />
93+ </ Button >
94+ </ div >
95+ ) ) }
96+ </ div >
97+ </ div >
98+ )
99+ }
0 commit comments