Skip to content

Commit

Permalink
feat: add user profile
Browse files Browse the repository at this point in the history
  • Loading branch information
brunosllz committed Nov 17, 2023
1 parent a7770ff commit d9b1bae
Show file tree
Hide file tree
Showing 52 changed files with 2,171 additions and 2,497 deletions.
1,612 changes: 617 additions & 995 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,14 @@
"react-markdown": "^8.0.7",
"remark-breaks": "^4.0.0",
"remark-gfm": "^4.0.0",
"remark-html": "^16.0.1",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.0.0",
"sharp": "^0.32.6",
"socket.io-client": "^4.7.2",
"tailwind-merge": "^1.14.0",
"tailwind-scrollbar": "^3.0.5",
"tailwind-variants": "^0.1.13",
"tailwindcss-animate": "^1.0.7",
"tiptap-markdown": "^0.8.2",
"unified": "^11.0.4",
"utf-8-validate": "^5.0.10",
"zod": "^3.22.2",
"zustand": "^4.4.1"
Expand Down
12 changes: 12 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ model projects {
interested_in_projects interested_in_projects[]
project_roles project_roles[]
users users @relation(fields: [author_id], references: [id])
projects_realized projects_realized[]
team_members team_members[]
skills skills[] @relation("ProjectToSkill")
Expand Down Expand Up @@ -172,12 +173,23 @@ model users {
notifications_notifications_author_idTousers notifications[] @relation("notifications_author_idTousers")
notifications_notifications_recipient_idTousers notifications[] @relation("notifications_recipient_idTousers")
projects projects[]
projects_realized projects_realized[]
sessions sessions[]
team_members team_members[]
projectRole project_roles[] @relation("ProjectRoleToUser")
skills skills[] @relation("SkillToUser")
}

model projects_realized {
id String @id @default(uuid())
user_id String
project_id String
short_description String
occurred_at DateTime @default(now())
project projects @relation(fields: [project_id], references: [id])
user users @relation(fields: [user_id], references: [id])
}

enum MEMBER_PERMISSION_TYPE {
member
owner
Expand Down
2 changes: 1 addition & 1 deletion src/actions/get-current-user.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { authOptions } from '@/app/api/auth/[...nextauth]/route'
import { authOptions } from '@/app/api/auth/[...nextauth]/auth-options'
import { prisma } from '@/libs/prisma'
import { getServerSession } from 'next-auth'

Expand Down
53 changes: 53 additions & 0 deletions src/actions/get-user-profile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { api } from '@/libs/fetch-api'
import { cookies as nextCookies } from 'next/headers'

export type UserProfile = {
id: string
name: string
aboutMe: string | null
seniority: string
role: string
avatar_url: string
state: string
city: string
country: string
overallRate: string
slugProfile: string
linkedinLink: string
githubLink: string
title: string
skills: Array<string>
updatedAt: string
involvedProjects: Array<{
id: string
image_url: string
name: string
status: 'inProgress' | 'recruiting' | 'closed'
}>
projectRealized: Array<{ id: string }>
}

type GetUserResponse = {
user: UserProfile | null
}

export async function getUserProfile({
slug,
}: {
slug: string
}): Promise<GetUserResponse> {
const cookies = nextCookies()

const response = await api(`/account/user/me/${slug}`, {
headers: {
Cookie: cookies.toString(),
},
next: {
tags: [`profile:${slug}`],
},
})

const user = await response.json()

return user
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Editor } from '@/components/editor'
import { InputMessageError } from '@/components/ui/input'
import { useController, useFormContext } from 'react-hook-form'

interface RequirementsTextAreaProps {
editable?: boolean
content?: string
}

export function AboutMeTextArea({
editable,
content,
}: RequirementsTextAreaProps) {
const { control } = useFormContext()

const {
formState: { errors },
field: { onChange, value },
} = useController({
name: 'aboutMe',
control,
})

const MAX_LENGTH = 1200

return (
<div className="space-y-1">
<div className="relative max-w-[704px]">
<Editor
id="aboutMe"
onUpdateMarkdown={(value) => {
onChange(value)
}}
config={{
editable,
maxLength: MAX_LENGTH,
className: '',
}}
placeholderValue="Conte-nos sobre você"
content={content}
/>

{value && (
<span
data-in-limit={value.length > MAX_LENGTH}
className="absolute bottom-3 right-3 text-xs font-light data-[in-limit=true]:text-red-500"
>
{value.length}/{MAX_LENGTH}
</span>
)}
</div>

{errors.aboutMe && (
<InputMessageError>
{errors.aboutMe.message?.toString()}
</InputMessageError>
)}
</div>
)
}
147 changes: 147 additions & 0 deletions src/app/(app)/me/[slug]/components/edit-about-section/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
'use client'

import { Button } from '@/components/ui/button'
// import { Combobox } from '@/components/ui/combobox'
import {
Dialog,
DialogContent,
DialogFooter,
DialogTitle,
// DialogTrigger,
} from '@/components/ui/dialog'
// import {
// InputControl,
// InputMessageError,
// InputPrefix,
// InputRoot,
// } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { ReactNode, useState } from 'react'
import { FormProvider, useForm } from 'react-hook-form'
import { AboutMeTextArea } from './about-me-text-area'
import { UserProfile } from '@/actions/get-user-profile'
import { z } from 'zod'
import { zodResolver } from '@hookform/resolvers/zod'
import { SkillsInput } from './skills-input'
import { useMutation } from '@tanstack/react-query'
import { api } from '@/libs/fetch-api'
import { useRouter } from 'next/navigation'
import { toast } from '@/components/ui/use-toast'
import { Loader2 } from 'lucide-react'
import dayjs from 'dayjs'

const editAboutInput = z.object({
aboutMe: z
.string()
.max(1200, { message: 'Limite máximo de 1200 caracteres.' })
.optional(),
skills: z.array(z.string(), { required_error: 'Select a technology' }),
})

export type EditAboutInput = z.infer<typeof editAboutInput>

type EditaPersonalSectionProps = {
children: ReactNode
user: Pick<UserProfile, 'aboutMe' | 'skills' | 'slugProfile' | 'updatedAt'>
}

export function EditAboutSection({
children,
user,
}: EditaPersonalSectionProps) {
const router = useRouter()
const [open, setOpen] = useState(false)

const form = useForm<EditAboutInput>({
resolver: zodResolver(editAboutInput),
defaultValues: {
aboutMe: user.aboutMe ?? '',
skills: user.skills ?? [],
},
})

const {
handleSubmit,

formState: { isSubmitting },
} = form

const { mutateAsync, isPending } = useMutation({
mutationFn: async ({ skills, aboutMe }: EditAboutInput) => {
await api(`/account/user/me/${user.slugProfile}/about/save`, {
method: 'POST',
body: JSON.stringify({
skills,
aboutMe,
}),
})
},
})

async function handleEditAbout(data: EditAboutInput) {
try {
await mutateAsync(data)
setOpen(false)

router.refresh()
} catch (error) {
console.error(error)

return toast({
title: 'Ocorreu um error ao salvar suas informações.',
description: `Tente novamente mais tarde.`,
variant: 'destructive',
})
}
}

return (
<Dialog open={open} onOpenChange={setOpen}>
{children}
<DialogContent>
<DialogTitle>Editar perfil</DialogTitle>

<FormProvider {...form}>
<form
id="edit-about"
className="space-y-6"
onSubmit={handleSubmit(handleEditAbout)}
>
<div className="space-y-3.5">
<Label htmlFor="aboutMe">Sobre</Label>

<AboutMeTextArea
content={user.aboutMe ?? ''}
editable={!isSubmitting || !isPending}
/>
</div>

<div className="space-y-3.5">
<Label htmlFor="technologies">Habilidades</Label>

<SkillsInput disabled={isSubmitting || isPending} />
</div>
</form>
</FormProvider>

<DialogFooter>
<Button
form="edit-about"
type="submit"
className="w-full max-w-[10.8125rem]"
>
{isPending || isSubmitting ? (
<Loader2 className="animate-spin" size={16} />
) : (
'Salvar alterações'
)}
</Button>
<span className="block text-xs text-muted-foreground">
Última alteração realizada em{' '}
{dayjs(user.updatedAt).format('DD/MM/YYYY')}
</span>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
Loading

0 comments on commit d9b1bae

Please sign in to comment.