
이 저장소에는 YouTube 채널 JavaScript Mastery 에서 사용 가능한 심층 자습서에 해당하는 코드가 포함되어 있습니다.
시각적 학습을 선호한다면 이것은 완벽한 자원입니다. 튜토리얼을 따라 초보자 친화적 인 방식으로 이러한 단계별 프로젝트를 구축하는 방법을 배우십시오!

Next.js, GraphQL, Next Auth, TypeScript 및 TailWindcss를 사용하여 개발 된 풀 스택 드리블 클론에는 프로젝트 공유 및 선수로부터 드리블의 필요한 모든 기능이 특징입니다.
시작하고 도움이 필요하거나 버그에 직면 한 경우 27K+ 이상의 회원과 함께 활동적인 불화 커뮤니티에 가입하십시오. 사람들이 서로를 도울 수있는 곳입니다.

현대적인 디자인 홈페이지 : Dribbble과 유사한 깨끗하고 현대적인 디자인을 특징으로하며 시각적으로 매력적인 인터페이스가 프로젝트 미리보기 및 탐색을 보여줍니다.
탐색 및 페이지 매김 : 다양한 프로젝트를 탐색하고 카테고리별로 필터링하며 원활한 데이터 탐색을위한 원활한 페이지 매김을 경험하십시오.
인증 및 인증 시스템 : 완전히 기능적인 인증 및 인증 시스템을 사용하면 사용자가 JWT 및 Google 인증을 사용하여 안전하게 로그인 할 수 있습니다.
게시물 페이지 작성 : 사용자가 커뮤니티와 프로젝트를 공유 할 수있는 전용 공간을 제공합니다. 프로젝트 세부 사항, 이미지 및 기타 관련 정보를위한 필드가 포함되어 있습니다.
프로젝트 세부 사항 및 관련 프로젝트 : 관련 프로젝트 기능을 갖춘 자세한보기로 사용자가 동일한 범주 또는 테마 내에서 더 많은 프로젝트를 탐색 할 수 있습니다.
이미지 편집 및 재 설계 : 사용자는 업데이트를 위해 장치에서 클라우드로 이미지를 다시 업로드하는 기능을 포함하여 이전에 생성 된 프로젝트를 편집 할 수 있습니다.
프로젝트 삭제 : 삭제 기능 삭제는 1 클릭 프로세스로 프로젝트 제거를 단순화하여 사용자 경험을 간소화합니다.
포트폴리오 스타일의 사용자 프로필 페이지 : 사용자 프로필 페이지는 포트폴리오 스타일 레이아웃을 채택하여 다른 사용자의 프로젝트 프로필과 함께 사용자의 프로젝트를 표시하여 쉽게 탐색합니다.
백엔드 API 경로 : 보안 인증 및 이미지 업로드를위한 JWT 토큰 관리를위한 백엔드 API 경로, 프론트 엔드와의 원활한 통합을 지원합니다.
그리고 코드 아키텍처 및 재사용 성을 포함한 더 많은 것들
다음 단계에 따라 기계에서 로컬로 프로젝트를 설정하십시오.
전제 조건
컴퓨터에 다음과 같은 설치가 있는지 확인하십시오.
저장소 복제
git clone https://github.com/adrianhajdin/project_nextjs13_flexibble.git
cd project_nextjs13_flexibble설치
NPM을 사용하여 프로젝트 종속성을 설치하십시오.
npm install환경 변수를 설정합니다
프로젝트의 루트에서 .env 라는 새 파일을 만들고 다음 내용을 추가하십시오.
GOOGLE_CLIENT_ID =
GOOGLE_CLIENT_SECRET =
NEXTAUTH_URL =
NEXTAUTH_SECRET =
CLOUDINARY_NAME =
CLOUDINARY_KEY =
CLOUDINARY_SECRET =
GRAFBASE_API_URL =
GRAFBASE_API_KEY =자리 표시 자 값을 실제 자격 증명으로 바꾸십시오. Google Cloud, Cloudinary 및 Grafbase의 해당 웹 사이트에 가입하여 이러한 자격 증명을 얻을 수 있습니다.
다음 인증 비밀의 경우 Crytool을 사용하여 임의의 비밀을 생성 할 수 있습니다.
프로젝트 실행
npm run dev브라우저에서 http : // localhost : 3000을 열어 프로젝트를 볼 수 있습니다.
common.types.ts import { User , Session } from 'next-auth'
export type FormState = {
title : string ;
description : string ;
image : string ;
liveSiteUrl : string ;
githubUrl : string ;
category : string ;
} ;
export interface ProjectInterface {
title : string ;
description : string ;
image : string ;
liveSiteUrl : string ;
githubUrl : string ;
category : string ;
id : string ;
createdBy : {
name : string ;
email : string ;
avatarUrl : string ;
id : string ;
} ;
}
export interface UserProfile {
id : string ;
name : string ;
email : string ;
description : string | null ;
avatarUrl : string ;
githubUrl : string | null ;
linkedinUrl : string | null ;
projects : {
edges : { node : ProjectInterface } [ ] ;
pageInfo : {
hasPreviousPage : boolean ;
hasNextPage : boolean ;
startCursor : string ;
endCursor : string ;
} ;
} ;
}
export interface SessionInterface extends Session {
user : User & {
id : string ;
name : string ;
email : string ;
avatarUrl : string ;
} ;
}
export interface ProjectForm {
title : string ;
description : string ;
image : string ;
liveSiteUrl : string ;
githubUrl : string ;
category : string ;
}constants.ts export const NavLinks = [
{ href : '/' , key : 'Inspiration' , text : 'Inspiration' } ,
{ href : '/' , key : 'Find Projects' , text : 'Find Projects' } ,
{ href : '/' , key : 'Learn Development' , text : 'Learn Development' } ,
{ href : '/' , key : 'Career Advancement' , text : 'Career Advancement' } ,
{ href : '/' , key : 'Hire Developers' , text : 'Hire Developers' }
] ;
export const categoryFilters = [
"Frontend" ,
"Backend" ,
"Full-Stack" ,
"Mobile" ,
"UI/UX" ,
"Game Dev" ,
"DevOps" ,
"Data Science" ,
"Machine Learning" ,
"Cybersecurity" ,
"Blockchain" ,
"E-commerce" ,
"Chatbots"
]
export const footerLinks = [
{
title : 'For developers' ,
links : [
'Go Pro!' ,
'Explore development work' ,
'Development blog' ,
'Code podcast' ,
'Open-source projects' ,
'Refer a Friend' ,
'Code of conduct' ,
] ,
} ,
{
title : 'Hire developers' ,
links : [
'Post a job opening' ,
'Post a freelance project' ,
'Search for developers' ,
] ,
} ,
{
title : 'Brands' ,
links : [
'Advertise with us' ,
] ,
} ,
{
title : 'Company' ,
links : [
'About' ,
'Careers' ,
'Support' ,
'Media kit' ,
'Testimonials' ,
'API' ,
'Terms of service' ,
'Privacy policy' ,
'Cookie policy' ,
] ,
} ,
{
title : 'Directories' ,
links : [
'Development jobs' ,
'Developers for hire' ,
'Freelance developers for hire' ,
'Tags' ,
'Places' ,
] ,
} ,
{
title : 'Development assets' ,
links : [
'Code Marketplace' ,
'GitHub Marketplace' ,
'NPM Registry' ,
'Packagephobia' ,
] ,
} ,
{
title : 'Development Resources' ,
links : [
'Freelancing' ,
'Development Hiring' ,
'Development Portfolio' ,
'Development Education' ,
'Creative Process' ,
'Development Industry Trends' ,
] ,
} ,
] ;globals.css @import url ( "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" );
@tailwind base;
@tailwind components;
@tailwind utilities;
* {
margin : 0 ;
padding : 0 ;
box-sizing : border-box;
}
body {
font-family : Inter;
}
. flexCenter {
@apply flex justify-center items-center;
}
. flexBetween {
@apply flex justify-between items-center;
}
. flexStart {
@apply flex items-center justify-start;
}
. text-small {
@apply text-sm font-medium;
}
. paddings {
@apply lg:px-20 py-6 px-5;
}
:: -webkit-scrollbar {
width : 5 px ;
height : 4 px ;
}
:: -webkit-scrollbar-thumb {
background : # 888 ;
border-radius : 12 px ;
}
. modal-head-text {
@apply md:text-5xl text-3xl font-extrabold text-left max-w-5xl w-full;
}
. no-result-text {
@apply w-full text-center my-10 px-2;
}
/* Project Details */
. user-actions_section {
@apply fixed max-md:hidden flex gap-4 flex-col right-10 top-20;
}
. user-info {
@apply flex flex-wrap whitespace-nowrap text-sm font-normal gap-2 w-full;
}
/* Home */
. projects-grid {
@apply grid xl:grid-cols-4 md:grid-cols-3 sm:grid-cols-2 grid-cols-1 gap-10 mt-10 w-full;
}
/* Project Actions */
. edit-action_btn {
@apply p-3 text-gray-100 bg-light-white-400 rounded-lg text-sm font-medium;
}
. delete-action_btn {
@apply p-3 text-gray-100 hover:bg-red-600 rounded-lg text-sm font-medium;
}
/* Related Project Card */
. related_project-card {
@apply flex-col rounded-2xl min-w-[ 210 px ] min-h-[ 197 px ];
}
. related_project-card_title {
@apply justify-end items-end w-full h-1/3 bg-gradient-to-b from-transparent to-black/50 rounded-b-2xl gap-2 absolute bottom-0 right-0 font-semibold text-lg text-white p-4;
}
. related_projects-grid {
@apply grid xl:grid-cols-4 md:grid-cols-3 sm:grid-cols-2 grid-cols-1 gap-8 mt-5;
}
/* Custom Menu */
. custom_menu-btn {
@apply gap-4 w-full rounded-md bg-light-white-100 p-4 text-base outline-none capitalize;
}
. custom_menu-items {
@apply flex-col absolute left-0 mt-2 xs:min-w-[ 300 px ] w-fit max-h-64 origin-top-right rounded-xl bg-white border border-nav-border shadow-menu overflow-y-auto;
}
. custom_menu-item {
@apply text-left w-full px-5 py-2 text-sm hover:bg-light-white-100 self-start whitespace-nowrap capitalize;
}
/* Footer */
. footer {
@apply flex-col paddings w-full gap-20 bg-light-white;
}
. footer_copyright {
@apply max-sm:flex-col w-full text-sm font-normal;
}
. footer_column {
@apply flex-1 flex flex-col gap-3 text-sm min-w-max;
}
/* Form Field */
. form_field-input {
@apply w-full outline-0 bg-light-white-100 rounded-xl p-4;
}
/* Modal */
. modal {
@apply fixed z-10 left-0 right-0 top-0 bottom-0 mx-auto bg-black/80;
}
. modal_wrapper {
@apply flex justify-start items-center flex-col absolute h-[ 95 % ] w-full bottom-0 bg-white rounded-t-3xl lg:px-40 px-8 pt-14 pb-72 overflow-auto;
}
/* Navbar */
. navbar {
@apply py-5 px-8 border-b border-nav-border gap-4;
}
/* Profile Menu */
. profile_menu-items {
@apply flex-col absolute right-1/2 translate-x-1/2 mt-3 p-7 sm:min-w-[ 300 px ] min-w-max rounded-xl bg-white border border-nav-border shadow-menu;
}
/* Profile Card */
. profile_card-title {
@apply justify-end items-end w-full h-1/3 bg-gradient-to-b from-transparent to-black/50 rounded-b-2xl gap-2 absolute bottom-0 right-0 font-semibold text-lg text-white p-4;
}
/* Project Form */
. form {
@apply flex-col w-full lg:pt-24 pt-12 gap-10 text-lg max-w-5xl mx-auto;
}
. form_image-container {
@apply w-full lg:min-h-[ 400 px ] min-h-[ 200 px ] relative;
}
. form_image-label {
@apply z-10 text-center w-full h-full p-20 text-gray-100 border-2 border-gray-50 border-dashed;
}
. form_image-input {
@apply absolute z-30 w-full opacity-0 h-full cursor-pointer;
}
/* Profile Projects */
. profile_projects {
@apply grid xl:grid-cols-4 md:grid-cols-3 sm:grid-cols-2 grid-cols-1 gap-8 mt-5;
}graphqlQueriesAndMutations.ts export const createProjectMutation = `
mutation CreateProject($input: ProjectCreateInput!) {
projectCreate(input: $input) {
project {
id
title
description
createdBy {
email
name
}
}
}
}
` ;
export const updateProjectMutation = `
mutation UpdateProject($id: ID!, $input: ProjectUpdateInput!) {
projectUpdate(by: { id: $id }, input: $input) {
project {
id
title
description
createdBy {
email
name
}
}
}
}
` ;
export const deleteProjectMutation = `
mutation DeleteProject($id: ID!) {
projectDelete(by: { id: $id }) {
deletedId
}
}
` ;
export const createUserMutation = `
mutation CreateUser($input: UserCreateInput!) {
userCreate(input: $input) {
user {
name
email
avatarUrl
description
githubUrl
linkedinUrl
id
}
}
}
` ;
export const projectsQuery = `
query getProjects($category: String, $endCursor: String) {
projectSearch(first: 8, after: $endCursor, filter: {category: {eq: $category}}) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges {
node {
title
githubUrl
description
liveSiteUrl
id
image
category
createdBy {
id
email
name
avatarUrl
}
}
}
}
}
` ;
export const getProjectByIdQuery = `
query GetProjectById($id: ID!) {
project(by: { id: $id }) {
id
title
description
image
liveSiteUrl
githubUrl
category
createdBy {
id
name
email
avatarUrl
}
}
}
` ;
export const getUserQuery = `
query GetUser($email: String!) {
user(by: { email: $email }) {
id
name
email
avatarUrl
description
githubUrl
linkedinUrl
}
}
` ;
export const getProjectsOfUserQuery = `
query getUserProjects($id: ID!, $last: Int = 4) {
user(by: { id: $id }) {
id
name
email
description
avatarUrl
githubUrl
linkedinUrl
projects(last: $last) {
edges {
node {
id
title
image
}
}
}
}
}
` ;ProfileMenu.tsx "use client"
import Link from "next/link" ;
import Image from "next/image" ;
import { signOut } from "next-auth/react" ;
import { Fragment , useState } from "react" ;
import { Menu , Transition } from "@headlessui/react" ;
import { SessionInterface } from "@/common.types" ;
const ProfileMenu = ( { session } : { session : SessionInterface } ) => {
const [ openModal , setOpenModal ] = useState ( false ) ;
return (
< div className = "flexCenter z-10 flex-col relative" >
< Menu as = "div" >
< Menu . Button className = "flexCenter" onMouseEnter = { ( ) => setOpenModal ( true ) } >
{ session ?. user ?. image && (
< Image
src = { session . user . image }
width = { 40 }
height = { 40 }
className = "rounded-full"
alt = "user profile image"
/ >
) }
< / Menu . Button >
< Transition
show = { openModal }
as = { Fragment }
enter = "transition ease-out duration-200"
enterFrom = "transform opacity-0 scale-95"
enterTo = "transform opacity-100 scale-100"
leave = "transition ease-in duration-75"
leaveFrom = "transform opacity-100 scale-100"
leaveTo = "transform opacity-0 scale-95"
>
< Menu . Items
static
className = "flexStart profile_menu-items"
onMouseLeave = { ( ) => setOpenModal ( false ) }
>
< div className = "flex flex-col items-center gap-y-4" >
{ session ? . user ?. image && (
< Image
src = { session ? . user ? . image }
className = "rounded-full"
width = { 80 }
height = { 80 }
alt = "profile Image"
/ >
) }
< p className = "font-semibold" > { session ? . user ?. name } < / p>
< / div >
< div className = "flex flex-col gap-3 pt-10 items-start w-full" >
< Menu . Item >
< Link href = { `/profile/${ session ? . user ? . id } `} className="text-sm">Work Preferences</Link>
</Menu.Item>
<Menu.Item>
<Link href={` / profile / $ { session ?. user ?. id } `} className="text-sm">Settings</Link>
</Menu.Item>
<Menu.Item>
<Link href={` / profile / $ { session ?. user ?. id } ` } className = "text-sm" > Profile < / Link>
< / Menu.Item>
< / div >
< div className = "w-full flexStart border-t border-nav-border mt-5 pt-5" >
< Menu . Item >
< button type = "button" className = "text-sm" onClick = { ( ) => signOut ( ) } >
Sign out
< / button>
< / Menu.Item>
< / div >
< / Menu . Items >
< / Transition >
< / Menu >
< / div >
)
}
export default ProfileMenuProfilePage.tsx import { ProjectInterface , UserProfile } from '@/common.types'
import Image from 'next/image'
import Link from 'next/link'
import Button from "./Button" ;
import ProjectCard from './ProjectCard' ;
type Props = {
user : UserProfile ;
}
const ProfilePage = ( { user } : Props ) => (
< section className = 'flexCenter flex-col max-w-10xl w-full mx-auto paddings' >
< section className = "flexBetween max-lg:flex-col gap-10 w-full" >
< div className = 'flex items-start flex-col w-full' >
< Image src = { user ? . avatarUrl } width = { 100 } height = { 100 } className = "rounded-full" alt = "user image" / >
< p className = "text-4xl font-bold mt-10" > { user ? . name } < / p>
< p className = "md:text-5xl text-3xl font-extrabold md:mt-10 mt-5 max-w-lg" > I’m Software Engineer at JSM < / p>
< div className = "flex mt-8 gap-5 w-full flex-wrap" >
< Button
title = "Follow"
leftIcon = "/plus-round.svg"
bgColor = "bg-light-white-400 !w-max"
textColor = "text-black-100"
/ >
< Link href = { `mailto: ${ user ?. email } ` } >
< Button title = "Hire Me" leftIcon = "/email.svg" / >
< / Link >
< / div >
< / div >
{ user ? . projects ? . edges ? . length > 0 ? (
< Image
src = { user ? . projects ? . edges [ 0 ] ? . node ?. image }
alt = "project image"
width = { 739 }
height = { 554 }
className = 'rounded-xl object-contain'
/ >
) : (
< Image
src = "/profile-post.png"
width = { 739 }
height = { 554 }
alt = "project image"
className = 'rounded-xl'
/ >
) }
< / section >
< section className = "flexStart flex-col lg:mt-28 mt-16 w-full" >
< p className = "w-full text-left text-lg font-semibold" > Recent Work < / p>
< div className = "profile_projects" >
{ user ? . projects ?. edges ?. map (
( { node } : { node : ProjectInterface } ) => (
< ProjectCard
key = { `${ node ? . id } ` }
id = { node ? . id }
image = { node ?. image }
title = { node ?. title }
name = { user . name }
avatarUrl = { user . avatarUrl }
userId = { user . id }
/ >
)
) }
< / div >
< / section >
< / section >
)
export default ProfilePageprojectPage.tsx import Image from "next/image"
import Link from "next/link"
import { getCurrentUser } from "@/lib/session"
import { getProjectDetails } from "@/lib/actions"
import Modal from "@/components/Modal"
// import ProjectActions from "@/components/ProjectActions"
import RelatedProjects from "@/components/RelatedProjects"
import { ProjectInterface } from "@/common.types"
import ProjectActions from "@/components/ProjectActions"
const Project = async ( { params : { id } } : { params : { id : string } } ) => {
const session = await getCurrentUser ( )
const result = await getProjectDetails ( id ) as { project ?: ProjectInterface }
if ( ! result ?. project ) return (
< p className = "no-result-text" > Failed to fetch project info < / p>
)
const projectDetails = result ?. project
const renderLink = ( ) => `/profile/ ${ projectDetails ?. createdBy ?. id } `
return (
< Modal >
< section className = "flexBetween gap-y-8 max-w-4xl max-xs:flex-col w-full" >
< div className = "flex-1 flex items-start gap-5 w-full max-xs:flex-col" >
< Link href = { renderLink ( ) } >
< Image
src = { projectDetails ? . createdBy ? . avatarUrl }
width = { 50 }
height = { 50 }
alt = "profile"
className = "rounded-full"
/ >
< / Link >
< div className = "flex-1 flexStart flex-col gap-1" >
< p className = "self-start text-lg font-semibold" >
{ projectDetails ? . title }
< / p>
< div className = "user-info" >
< Link href = { renderLink ( ) } >
{ projectDetails ?. createdBy ?. name }
< / Link>
< Image src = "/dot.svg" width = { 4 } height = { 4 } alt = "dot" / >
< Link href = { `/?category=${ projectDetails . category } ` } className = "text-primary-purple font-semibold" >
{ projectDetails ?. category }
< / Link >
< / div >
< / div >
< / div >
{ session ? . user ?. email === projectDetails ?. createdBy ?. email && (
< div className = "flex justify-end items-center gap-2" >
< ProjectActions projectId = { projectDetails ? . id } / >
< / div >
) }
< / section >
< section className = "mt-14" >
< Image
src = { `${ projectDetails ? . image } ` }
className = "object-cover rounded-2xl"
width = { 1064 }
height = { 798 }
alt = "poster"
/ >
< / section >
< section className = "flexCenter flex-col mt-20" >
< p className = "max-w-5xl text-xl font-normal" >
{ projectDetails ? . description }
< / p>
< div className = "flex flex-wrap mt-5 gap-5" >
< Link href = { projectDetails ? . githubUrl } target = "_blank" rel = "noreferrer" className = "flexCenter gap-2 tex-sm font-medium text-primary-purple" >
? < span className = "underline" > Github < / span>
< / Link>
< Image src = "/dot.svg" width = { 4 } height = { 4 } alt = "dot" / >
< Link href = { projectDetails ? . liveSiteUrl } target = "_blank" rel = "noreferrer" className = "flexCenter gap-2 tex-sm font-medium text-primary-purple" >
< span className = "underline" > Live Site < / span>
< / Link>
< / div >
< / section >
< section className = "flexCenter w-full gap-8 mt-28" >
< span className = "w-full h-0.5 bg-light-white-200" / >
< Link href = { renderLink ( ) } className = "min-w-[82px] h-[82px]" >
< Image
src = { projectDetails ? . createdBy ? . avatarUrl }
className = "rounded-full"
width = { 82 }
height = { 82 }
alt = "profile image"
/ >
< / Link >
< span className = "w-full h-0.5 bg-light-white-200" / >
< / section >
< RelatedProjects userId = { projectDetails ? . createdBy ? . id } projectId = { projectDetails ?. id } / >
< / Modal >
)
}
export default Projecttailwind.config.ts tailwind . config . ts
/** @type {import('tailwindcss').Config} */
module . exports = {
content : [
'./pages/**/*.{js,ts,jsx,tsx,mdx}' ,
'./components/**/*.{js,ts,jsx,tsx,mdx}' ,
'./app/**/*.{js,ts,jsx,tsx,mdx}' ,
] ,
theme : {
extend : {
colors : {
'nav-border' : '#EBEAEA' ,
'light-white' : '#FAFAFB' ,
'light-white-100' : '#F1F4F5' ,
'light-white-200' : '#d7d7d7' ,
'light-white-300' : '#F3F3F4' ,
'light-white-400' : '#E2E5F1' ,
'light-white-500' : '#E4E4E4' ,
gray : '#4D4A4A' ,
'gray-100' : '#3d3d4e' ,
'black-100' : '#252525' ,
'primary-purple' : '#9747FF' ,
'gray-50' : '#D9D9D9' ,
} ,
boxShadow : {
menu : '0px 159px 95px rgba(13,12,34,0.01), 0px 71px 71px rgba(13,12,34,0.02), 0px 18px 39px rgba(13,12,34,0.02), 0px 0px 0px rgba(13,12,34,0.02)' ,
} ,
screens : {
'xs' : '400px' ,
} ,
maxWidth : {
'10xl' : '1680px'
}
} ,
} ,
plugins : [ ] ,
} ; 프로젝트에 사용 된 자산
Next.js 14 Pro Course로 기술을 발전 시키십시오
이 프로젝트를 만드는 것을 즐겼습니까? 더 풍부한 학습 모험을 위해 프로 코스에 더 깊이 빠져 나가십시오. 그들은 당신의 기술을 향상시키기 위해 자세한 설명, 멋진 기능 및 운동으로 가득합니다. 가십시오!

전문가 교육 프로그램으로 전문적인 여정을 가속화하십시오
그리고 당신이 단순한 코스 이상으로 배가 고프고 우리가 기술 문제를 배우고 어떻게 다루는 지 이해하고 싶다면 개인화 된 마스터 클래스로 뛰어들 것입니다. 우리는 모범 사례, 다양한 웹 기술을 다루고 자신감을 높이기 위해 멘토링을 제공합니다. 배우고 함께 성장합시다!