├── .dockerignore ├── .eslintrc.json ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug.yml │ ├── chore.yml │ ├── docs.yml │ └── features.yml └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .husky └── pre-commit ├── .lintstagedrc.json ├── .nvmrc ├── .prettierignore ├── .prettierrc.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── components.json ├── components ├── Common │ ├── BackToTop │ │ └── index.tsx │ ├── Constants │ │ ├── messages.ts │ │ └── textLabels.ts │ ├── Error │ │ └── index.tsx │ ├── Logo │ │ └── index.tsx │ ├── ProgressBar │ │ └── index.tsx │ └── Typography │ │ ├── Typography.interface.ts │ │ ├── Typography.tsx │ │ └── index.ts ├── Layouts │ ├── BottomBar │ │ ├── BottomBar.tsx │ │ └── index.ts │ ├── RightBar │ │ └── index.tsx │ ├── SharedLayout │ │ ├── SharedLayout.interface.ts │ │ ├── SharedLayout.tsx │ │ └── index.ts │ ├── Sidebar │ │ ├── SessionCard.tsx │ │ ├── SessionLessCard.tsx │ │ ├── Sidebar.tsx │ │ ├── data.tsx │ │ └── index.ts │ ├── TopNavbar │ │ ├── TopNavbar.tsx │ │ └── index.ts │ └── index.ts ├── Modals │ ├── AddProjectModal │ │ ├── AddProjectModal.tsx │ │ ├── index.ts │ │ └── schema.ts │ ├── AuthModal │ │ ├── AuthModal.tsx │ │ └── index.ts │ └── ShareModal │ │ ├── ShareModal.tsx │ │ └── index.ts ├── ThemeProvider │ └── ThemeProvider.tsx ├── ui │ ├── accordion.tsx │ ├── avatar.tsx │ ├── badge.tsx │ ├── button.tsx │ ├── card.tsx │ ├── dialog.tsx │ ├── dropdown-menu.tsx │ ├── form.tsx │ ├── header.tsx │ ├── hover-card.tsx │ ├── input.tsx │ ├── label.tsx │ ├── popover.tsx │ ├── separator.tsx │ ├── sheet.tsx │ ├── textarea.tsx │ ├── toast.tsx │ ├── toaster.tsx │ ├── tooltip.tsx │ └── use-toast.ts └── views │ ├── LandingPage │ ├── ActionComponent │ │ ├── ActionComponent.tsx │ │ └── index.ts │ ├── FAQComponent │ │ └── index.tsx │ ├── FeaturesComponent │ │ └── index.tsx │ ├── Footer │ │ ├── Footer.interface.ts │ │ ├── Footer.tsx │ │ ├── data.tsx │ │ └── index.ts │ ├── Header │ │ ├── Header.tsx │ │ └── index.ts │ ├── HeroComponent │ │ ├── HeroComponent.tsx │ │ └── index.ts │ ├── VideoDemo │ │ └── index.tsx │ └── index.ts │ ├── MatesList │ ├── Mate.interface.ts │ ├── Mate.tsx │ ├── MateSkeleton.tsx │ ├── MatesList.tsx │ └── index.ts │ ├── ProfilePage │ ├── ProfilePage.interface.ts │ ├── ProfilePage.tsx │ ├── ProfilePageProject.tsx │ ├── ProfileProjectSkeleton.tsx │ ├── index.ts │ └── schema.ts │ └── ProjectsPage │ ├── Filters │ ├── Filters.tsx │ └── index.ts │ ├── Project │ ├── Project.interface.ts │ └── Project.tsx │ ├── ProjectSkeleton │ ├── ProjectSkeleton.tsx │ └── index.ts │ └── ProjectsList │ ├── ProjectsList.tsx │ └── index.ts ├── context ├── AddProjectModal │ ├── AddProjectModalContext.ts │ ├── AddProjectModalProvider.tsx │ ├── reducers.ts │ └── types.ts ├── AppContextProvider.tsx ├── AuthModal │ ├── AuthModalContext.ts │ ├── AuthModalProvider.tsx │ ├── reducers.ts │ └── types.ts ├── Common │ └── CommonContext.ts └── ShareModal │ ├── ShareModalContext.ts │ ├── ShareModalProvider.tsx │ ├── reducers.ts │ └── types.ts ├── data └── index.ts ├── docker-compose.yml ├── docker ├── mongo_rs │ └── Dockerfile └── web │ └── Dockerfile ├── hooks ├── index.ts ├── useAddProjectModal.ts ├── useAuthModal.ts ├── useGetAllTags.ts ├── useGetTProjects.ts └── useShareModal.ts ├── interfaces └── next.d.ts ├── lib ├── bodyValidator.ts ├── combineProviders.tsx ├── fetcher.ts ├── generateSitemap.ts ├── getServerAuthSession.ts ├── httpResponse.ts ├── prisma.ts ├── user │ └── index.ts └── utils.ts ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── 404.tsx ├── _app.tsx ├── _document.tsx ├── api │ ├── auth │ │ └── [...nextauth].ts │ ├── project │ │ ├── [postId].ts │ │ └── index.ts │ ├── tags │ │ └── index.ts │ ├── tprojectsapi │ │ └── [tag].ts │ └── user │ │ ├── [username].ts │ │ ├── all.ts │ │ ├── details.ts │ │ └── project │ │ ├── [username].tsx │ │ └── index.ts ├── auth │ ├── github.tsx │ └── google.tsx ├── blogs.tsx ├── index.tsx ├── mates.tsx ├── profile │ └── [username].tsx ├── projects.tsx └── tprojects │ └── [tag].tsx ├── postcss.config.js ├── prisma ├── schema.prisma └── seed.ts ├── public ├── addproject.gif ├── checks.png ├── favicon_io │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ └── favicon.ico ├── landingpage-screenshot.png ├── logo.png ├── og.png ├── profile.gif ├── projects.gif ├── robots.txt ├── share.gif ├── sitemap.xml └── stats.gif ├── schema ├── index.ts ├── postSchema.ts └── userDetailsSchema.ts ├── styles └── globals.css ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.sitemap.json └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | .github 4 | .husky -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "project": "./tsconfig.json" 5 | }, 6 | "plugins": ["@typescript-eslint"], 7 | "extends": [ 8 | "next/core-web-vitals", 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:prettier/recommended" 11 | ], 12 | "rules": { 13 | "consistent-return": "warn", 14 | "no-console": "warn", 15 | "react/react-in-jsx-scope": "off", 16 | "prettier/prettier": [ 17 | "error", 18 | { 19 | "endOfLine": "auto", 20 | "singleQuote": true 21 | } 22 | ] 23 | }, 24 | "ignorePatterns": ["tailwind.config.js"] 25 | } 26 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [rohitdasu] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: ['https://www.buymeacoffee.com/rohit.dasu'] 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug 2 | description: Report an issue to help improve the project. 3 | title: '[BUG] ' 4 | labels: ['bug'] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: A brief description of the question or issue, also include what you tried and what didn't work 11 | validations: 12 | required: true 13 | - type: textarea 14 | id: screenshots 15 | attributes: 16 | label: Screenshots 17 | description: Please add screenshots if applicable 18 | validations: 19 | required: false 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/chore.yml: -------------------------------------------------------------------------------- 1 | name: chore 2 | description: Report an issue that doesn't effect on production code. 3 | title: '[CHORE] ' 4 | labels: ['chore'] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: A brief description of the improvement / feature suggestion 11 | validations: 12 | required: true 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/docs.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | description: Found an issue in the documentation? You can use this one! 3 | title: '[DOCS] ' 4 | labels: ['documentation'] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: Description of the question or issue, also include what you tried and what didn't work 11 | validations: 12 | required: true 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/features.yml: -------------------------------------------------------------------------------- 1 | name: General Feature Request 2 | description: Have a new idea/feature for this project? Please suggest! 3 | title: '[FEATURE] ' 4 | labels: ['feature'] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: Description of the enhancement you propose, also include what you tried and what worked. 11 | validations: 12 | required: true 13 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Fixes Issue 4 | 5 | 6 | 7 | 8 | 9 | ### Changes proposed 10 | 11 | 12 | 13 | ### Note to reviewers 14 | 15 | 16 | 17 | ### Screenshots 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no-install lint-staged 5 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,jsx,ts,tsx}": "eslint --fix", 3 | "*": "prettier --write --ignore-unknown" 4 | } 5 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18.12.0 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "tabWidth": 2, 4 | "semi": true 5 | } 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We love pull requests from everyone. By participating in this project, you 4 | agree to abide by the Code Of Conduct 5 | 6 | # Contributor Code of Conduct 7 | 8 | ## Our Pledge 9 | 10 | In the interest of fostering an open and welcoming environment, we as 11 | contributors and maintainers pledge to making participation in our project and 12 | our community a harassment-free experience for everyone, regardless of age, body 13 | size, disability, ethnicity, gender identity and expression, level of experience, 14 | education, socio-economic status, nationality, personal appearance, race, 15 | religion, or sexual identity and orientation. 16 | 17 | ## Our Standards 18 | 19 | Examples of behavior that contributes to creating a positive environment 20 | include: 21 | 22 | - Using welcoming and inclusive language 23 | - Being respectful of differing viewpoints and experiences 24 | - Gracefully accepting constructive criticism 25 | - Focusing on what is best for the community 26 | - Showing empathy towards other community members 27 | 28 | Examples of unacceptable behavior by participants include: 29 | 30 | - The use of sexualized language or imagery and unwelcome sexual attention or 31 | advances 32 | - Trolling, insulting/derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or electronic 35 | address, without explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Our Responsibilities 40 | 41 | Project maintainers are responsible for clarifying the standards of acceptable 42 | behavior and are expected to take appropriate and fair corrective action in 43 | response to any instances of unacceptable behavior. 44 | 45 | Project maintainers have the right and responsibility to remove, edit, or 46 | reject comments, commits, code, wiki edits, issues, and other contributions 47 | that are not aligned to this Code of Conduct, or to ban temporarily or 48 | permanently any contributor for other behaviors that they deem inappropriate, 49 | threatening, offensive, or harmful. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies both within project spaces and in public spaces 54 | when an individual is representing the project or its community. Examples of 55 | representing a project or community include using an official project e-mail 56 | address, posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. Representation of a project may be 58 | further defined and clarified by project maintainers. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported by contacting the project team at {{ email }}. All 64 | complaints will be reviewed and investigated and will result in a response that 65 | is deemed necessary and appropriate to the circumstances. The project team is 66 | obligated to maintain confidentiality with regard to the reporter of an incident. 67 | Further details of specific enforcement policies may be posted separately. 68 | 69 | Project maintainers who do not follow or enforce the Code of Conduct in good 70 | faith may face temporary or permanent repercussions as determined by other 71 | members of the project's leadership. 72 | 73 | ## Steps 74 | 75 | 1. Fork this project 76 | 2. Make changes 77 | 3. Create a branch 78 | 4. commit 79 | 5. push your changes to your forked version 80 | 6. Go to the original project on GitHub & Create a Pull Request 81 | 82 | ## Congratulations! You are now a contributor 83 | 84 | ## Attribution 85 | 86 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 87 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 88 | 89 | [homepage]: https://www.contributor-covenant.org 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Dasu Rohit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "styles/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /components/Common/BackToTop/index.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { IoArrowUpSharp } from 'react-icons/io5'; 3 | 4 | const BackToTopButton = () => { 5 | const [isVisible, setIsVisible] = useState(false); 6 | 7 | const handleScroll = () => { 8 | const scrolled = document.documentElement.scrollTop; 9 | setIsVisible(scrolled > 300); 10 | }; 11 | 12 | const scrollToTop = () => { 13 | window.scrollTo({ 14 | top: 0, 15 | behavior: 'smooth', 16 | }); 17 | }; 18 | 19 | useEffect(() => { 20 | window.addEventListener('scroll', handleScroll); 21 | return () => { 22 | window.removeEventListener('scroll', handleScroll); 23 | }; 24 | }, []); 25 | 26 | return ( 27 | <> 28 | {isVisible && ( 29 | 35 | )} 36 | 37 | ); 38 | }; 39 | 40 | export default BackToTopButton; 41 | -------------------------------------------------------------------------------- /components/Common/Constants/messages.ts: -------------------------------------------------------------------------------- 1 | // This will hold all the messages 2 | export const MESSAGES = Object.freeze({ 3 | comingSoon: 'coming soon', 4 | }); 5 | -------------------------------------------------------------------------------- /components/Common/Constants/textLabels.ts: -------------------------------------------------------------------------------- 1 | // This will hold all the Button texts 2 | export const BUTTON_TEXT = Object.freeze({ 3 | retry: 'Retry', 4 | home: 'Home', 5 | addProject: 'Add project', 6 | toggleTheme: 'Toggle theme', 7 | submit: 'Submit', 8 | continueWithGitHub: 'Continue with Github', 9 | continueWithGoogle: 'Continue with Google', 10 | copy: 'Copy', 11 | singUp: 'Sign Up', 12 | exploreProjects: 'Explore projects', 13 | editProfile: 'Edit profile', 14 | gitHub: 'GitHub', 15 | linkedIn: 'LinkedIn', 16 | twitter: 'Twitter', 17 | website: 'Website', 18 | saveChanges: 'Save changes', 19 | live: 'Live', 20 | edit: 'Edit', 21 | stats: 'Stats', 22 | contribute: 'Contribute', 23 | share: 'Share', 24 | goHome: 'Go Home', 25 | }); 26 | -------------------------------------------------------------------------------- /components/Common/Error/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@/components/ui/button'; 2 | import Link from 'next/link'; 3 | import React from 'react'; 4 | import { BUTTON_TEXT } from '../Constants/textLabels'; 5 | 6 | export const ErrorPage = () => { 7 | return ( 8 |
9 |

Something went wrong

10 |
11 | 20 | 23 |
24 |
25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /components/Common/Logo/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import Link from 'next/link'; 3 | 4 | interface LogoProps { 5 | route: string; 6 | } 7 | 8 | export const Logo: FC = ({ route }) => { 9 | return ( 10 | 11 |
12 |
13 |

project

14 |

mate

15 |
16 | 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /components/Common/ProgressBar/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import Router from 'next/router'; 3 | import NProgress from 'nprogress'; 4 | 5 | import 'nprogress/nprogress.css'; 6 | 7 | // NProgress configuration 8 | NProgress.configure({ showSpinner: false }); 9 | 10 | export const ProgressBar = () => { 11 | useEffect(() => { 12 | const handleStart = () => { 13 | NProgress.start(); 14 | }; 15 | 16 | const handleComplete = () => { 17 | NProgress.done(); 18 | }; 19 | 20 | Router.events.on('routeChangeStart', handleStart); 21 | Router.events.on('routeChangeComplete', handleComplete); 22 | Router.events.on('routeChangeError', handleComplete); 23 | 24 | return () => { 25 | Router.events.off('routeChangeStart', handleStart); 26 | Router.events.off('routeChangeComplete', handleComplete); 27 | Router.events.off('routeChangeError', handleComplete); 28 | }; 29 | }, []); 30 | 31 | return null; 32 | }; 33 | -------------------------------------------------------------------------------- /components/Common/Typography/Typography.interface.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode, ElementType, ComponentPropsWithoutRef } from 'react'; 2 | 3 | export type TextElementsUnion = 4 | | 'h1' 5 | | 'h2' 6 | | 'h3' 7 | | 'h4' 8 | | 'h5' 9 | | 'h6' 10 | | 'span' 11 | | 'p'; 12 | 13 | export type TextElementType = Extract; 14 | 15 | export type Alignment = 'center' | 'inherit' | 'justify' | 'left' | 'right'; 16 | 17 | export enum FontSizes { 18 | 'xs' = 'text-xs', 19 | 'sm' = 'text-sm', 20 | 'base' = 'text-base', 21 | 'lg' = 'text-lg', 22 | 'xl' = 'text-xl', 23 | '2xl' = 'text-2xl', 24 | '3xl' = 'text-3xl', 25 | '4xl' = 'text-4xl', 26 | '5xl' = 'text-5xl', 27 | '6xl' = 'text-6xl', 28 | '7xl' = 'text-7xl', 29 | } 30 | 31 | export type FontSizesUnion = keyof typeof FontSizes; 32 | 33 | export enum FontWeights { 34 | 'light' = 'font-light', 35 | 'normal' = 'font-normal', 36 | 'medium' = 'font-medium', 37 | 'semibold' = 'font-semibold', 38 | 'bold' = 'font-bold', 39 | } 40 | 41 | export type FontWeightsUnion = keyof typeof FontWeights; 42 | 43 | export type TypographyProps = { 44 | as?: C; 45 | align?: Alignment; 46 | fontSize?: FontSizesUnion; 47 | fontWeight?: FontWeightsUnion; 48 | children: ReactNode; 49 | className?: string; 50 | } & ComponentPropsWithoutRef; 51 | -------------------------------------------------------------------------------- /components/Common/Typography/Typography.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | TypographyProps, 3 | TextElementType, 4 | FontWeights, 5 | FontSizes, 6 | } from './Typography.interface'; 7 | 8 | export const Typography = ({ 9 | as, 10 | align = 'left', 11 | fontSize = 'base', 12 | fontWeight = 'normal', 13 | children, 14 | className, 15 | ...restProps 16 | }: TypographyProps) => { 17 | const Component = as || 'span'; 18 | const style = align ? { style: { textAlign: align } } : {}; 19 | return ( 20 | 25 | {children} 26 | 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /components/Common/Typography/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Typography'; 2 | export * from './Typography.interface'; 3 | -------------------------------------------------------------------------------- /components/Layouts/BottomBar/BottomBar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { PlusCircle } from 'lucide-react'; 3 | import { useRouter } from 'next/router'; 4 | import { Button } from '@/components/ui/button'; 5 | import { useSession } from 'next-auth/react'; 6 | import { useAuthModal } from '@/hooks/useAuthModal'; 7 | import { NavRoutes } from '../Sidebar/data'; 8 | import { useAddProjectModal } from '@/hooks/useAddProjectModal'; 9 | import { AddProjectModal } from '@/components/Modals/AddProjectModal'; 10 | import Link from 'next/link'; 11 | import { useAppData } from '@/context/Common/CommonContext'; 12 | 13 | export const BottomBar = () => { 14 | const { openModal } = useAuthModal(); 15 | const profile = useAppData(); 16 | const { openModal: openAddProjectModal } = useAddProjectModal(); 17 | const { status, data } = useSession(); 18 | const handleAddProject = () => { 19 | if (status === 'authenticated') { 20 | openAddProjectModal(); 21 | } else { 22 | openModal(); 23 | } 24 | }; 25 | const router = useRouter(); 26 | 27 | const handleNavLink = (nav: { link: string; addUsername?: boolean }) => { 28 | if (nav.addUsername && profile.profileDetails?.results?.username) { 29 | return `${nav.link}/${profile.profileDetails.results.username}`; 30 | } else { 31 | return nav.link; 32 | } 33 | }; 34 | 35 | return ( 36 |
37 | 38 |
    39 | {NavRoutes.map((route) => { 40 | const isActive = 41 | router.pathname === route.link || 42 | router.pathname === route.link + '/[username]'; 43 | if (route.authGuard && status === 'unauthenticated') { 44 | return; 45 | } 46 | // eslint-disable-next-line consistent-return 47 | return ( 48 | 49 |
  • 50 | {React.cloneElement(route.icon, { 51 | strokeWidth: isActive ? 2 : 1, 52 | })} 53 |
  • 54 | 55 | ); 56 | })} 57 |
58 |
59 | 67 |
68 |
69 | ); 70 | }; 71 | -------------------------------------------------------------------------------- /components/Layouts/BottomBar/index.ts: -------------------------------------------------------------------------------- 1 | export { BottomBar } from './BottomBar'; 2 | -------------------------------------------------------------------------------- /components/Layouts/RightBar/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@/components/ui/button'; 2 | import Header from '@/components/ui/header'; 3 | import useGetAllTags from '@/hooks/useGetAllTags'; 4 | import { useRouter } from 'next/router'; 5 | import React, { useCallback } from 'react'; 6 | 7 | const RightBar = () => { 8 | const { data: tags } = useGetAllTags(); 9 | // console.log("LINE AT 9" , tags); 10 | // const allUniqueTag = tags?.results; 11 | const router = useRouter(); 12 | const { tag } = router.query; 13 | // console.log(tag); 14 | // const [flag, setFlag] = useState(true); 15 | // console.log("LINE AT 16" , flag); 16 | const handleClick = useCallback( 17 | (e: any, tag: string) => { 18 | e.stopPropagation(); 19 | if (tag === router.query.tag) { 20 | router.push('/projects'); 21 | return; // Exit the function to avoid unnecessary state update 22 | } 23 | // Otherwise, navigate to the tag's page 24 | const url = `/tprojects/${tag}`; 25 | router.push(url); 26 | }, 27 | [router] 28 | ); 29 | return ( 30 |
31 |
32 | {tags?.results.map((item: string, index: number) => { 33 | return ( 34 | 42 | ); 43 | })} 44 |
45 | ); 46 | }; 47 | 48 | export default RightBar; 49 | -------------------------------------------------------------------------------- /components/Layouts/SharedLayout/SharedLayout.interface.ts: -------------------------------------------------------------------------------- 1 | export interface SharedLayoutProps { 2 | children: React.ReactElement; 3 | title: string; 4 | leftSidebar?: boolean; 5 | rightSidebar?: boolean; 6 | topBar?: boolean; 7 | bottomBar?: boolean; 8 | } 9 | -------------------------------------------------------------------------------- /components/Layouts/SharedLayout/SharedLayout.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useState, useEffect } from 'react'; 2 | import { SharedLayoutProps } from './SharedLayout.interface'; 3 | import Head from 'next/head'; 4 | import { Sidebar } from '../Sidebar'; 5 | import { BottomBar } from '../BottomBar'; 6 | import { TopNavbar } from '../TopNavbar'; 7 | import { favicons } from '@/data'; 8 | import BackToTopButton from '@/components/Common/BackToTop'; 9 | // import RightBar from '../RightBar'; 10 | 11 | export const SharedLayout: FC = ({ 12 | title = '', 13 | children, 14 | leftSidebar = true, 15 | rightSidebar = true, 16 | topBar = true, 17 | bottomBar = true, 18 | }) => { 19 | const [isNavbarVisible, setIsNavbarVisible] = useState(true); 20 | 21 | useEffect(() => { 22 | let prevScrollPos = window.pageYOffset; 23 | 24 | const handleScroll = () => { 25 | const currentScrollPos = window.pageYOffset; 26 | const isScrollingDown = prevScrollPos < currentScrollPos; 27 | 28 | setIsNavbarVisible(!isScrollingDown); 29 | prevScrollPos = currentScrollPos; 30 | }; 31 | 32 | window.addEventListener('scroll', handleScroll); 33 | 34 | return () => { 35 | window.removeEventListener('scroll', handleScroll); 36 | }; 37 | }, []); 38 | 39 | return ( 40 |
41 | 42 | {`${title} | Projectmate`} 43 | {favicons.map((favicon, index) => ( 44 | 45 | ))} 46 | 47 | {topBar && ( 48 | 55 | )} 56 |
57 | {leftSidebar && ( 58 |
59 | 60 |
61 | )} 62 |
67 | {children} 68 |
69 | {rightSidebar && ( 70 |
{/* */}
71 | )} 72 |
73 | {bottomBar && ( 74 |
75 | 76 |
77 | )} 78 | 79 |
80 | ); 81 | }; 82 | -------------------------------------------------------------------------------- /components/Layouts/SharedLayout/index.ts: -------------------------------------------------------------------------------- 1 | export { SharedLayout } from './SharedLayout'; 2 | -------------------------------------------------------------------------------- /components/Layouts/Sidebar/SessionCard.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { User } from '@prisma/client'; 3 | import { 4 | DropdownMenu, 5 | DropdownMenuContent, 6 | DropdownMenuItem, 7 | DropdownMenuLabel, 8 | DropdownMenuSeparator, 9 | DropdownMenuTrigger, 10 | } from '@/components/ui/dropdown-menu'; 11 | import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; 12 | import { signOut } from 'next-auth/react'; 13 | 14 | export const SessionCard: FC> = ({ 15 | name, 16 | image, 17 | email, 18 | }) => { 19 | return ( 20 |
  • 21 | 22 | 23 |
    24 | 25 | 26 | {name && name[0]} 27 | 28 | {name} 29 |
    30 |
    31 | 32 | 33 |

    My Account

    34 | 35 | {email} 36 | 37 |
    38 | 39 | signOut({ redirect: false })}> 40 | Log out 41 | 42 |
    43 |
    44 |
  • 45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /components/Layouts/Sidebar/SessionLessCard.tsx: -------------------------------------------------------------------------------- 1 | import { useAuthModal } from '@/hooks/useAuthModal'; 2 | import { LogIn } from 'lucide-react'; 3 | 4 | export const SessionLessCard = () => { 5 | const { openModal } = useAuthModal(); 6 | return ( 7 | <> 8 |
  • { 10 | openModal(); 11 | }} 12 | className="h-9 cursor-pointer" 13 | > 14 |
    15 | 16 | Login 17 |
    18 |
  • 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /components/Layouts/Sidebar/data.tsx: -------------------------------------------------------------------------------- 1 | import { Home, Newspaper, User, Users } from 'lucide-react'; 2 | 3 | export const NavRoutes = [ 4 | { 5 | id: '1', 6 | title: 'Projects', 7 | icon: , 8 | link: '/projects', 9 | authGuard: false, 10 | }, 11 | { 12 | id: '2', 13 | title: 'Mates', 14 | icon: , 15 | link: '/mates', 16 | authGuard: false, 17 | }, 18 | { 19 | id: '3', 20 | title: 'Profile', 21 | icon: , 22 | link: '/profile', 23 | authGuard: true, 24 | addUsername: true, 25 | }, 26 | { 27 | id: '4', 28 | title: 'Blogs', 29 | icon: , 30 | link: '/blogs', 31 | authGuard: false, 32 | }, 33 | ]; 34 | -------------------------------------------------------------------------------- /components/Layouts/Sidebar/index.ts: -------------------------------------------------------------------------------- 1 | export { Sidebar } from './Sidebar'; 2 | -------------------------------------------------------------------------------- /components/Layouts/TopNavbar/TopNavbar.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@/components/ui/button'; 2 | import { 3 | DropdownMenu, 4 | DropdownMenuContent, 5 | DropdownMenuItem, 6 | DropdownMenuTrigger, 7 | DropdownMenuLabel, 8 | DropdownMenuSeparator, 9 | } from '@/components/ui/dropdown-menu'; 10 | import { Loader, LogIn, MoonIcon, SunIcon } from 'lucide-react'; 11 | import { useTheme } from 'next-themes'; 12 | import React from 'react'; 13 | import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; 14 | import { signOut, useSession } from 'next-auth/react'; 15 | import { useAuthModal } from '@/hooks/useAuthModal'; 16 | import { Logo } from '@/components/Common/Logo'; 17 | import { BUTTON_TEXT } from '@/components/Common/Constants/textLabels'; 18 | 19 | export const TopNavbar = () => { 20 | const { setTheme } = useTheme(); 21 | const { data, status } = useSession(); 22 | const { openModal } = useAuthModal(); 23 | return ( 24 |
    25 | 26 |
    27 | 28 | 29 | 34 | 35 | 36 | setTheme('light')}> 37 | Light 38 | 39 | setTheme('dark')}> 40 | Dark 41 | 42 | setTheme('system')}> 43 | System 44 | 45 | 46 | 47 | {status === 'authenticated' && ( 48 | 49 | 50 | 51 | 52 | 53 | {data?.user?.name && data.user.name[0]} 54 | 55 | 56 | 57 | 58 | 59 |

    My Account

    60 | 61 | {data?.user?.email} 62 | 63 |
    64 | 65 | signOut({ redirect: false })}> 66 | Log out 67 | 68 |
    69 |
    70 | )} 71 | {status === 'unauthenticated' && ( 72 | 75 | )} 76 | {status === 'loading' && ( 77 | 80 | )} 81 |
    82 |
    83 | ); 84 | }; 85 | -------------------------------------------------------------------------------- /components/Layouts/TopNavbar/index.ts: -------------------------------------------------------------------------------- 1 | export { TopNavbar } from './TopNavbar'; 2 | -------------------------------------------------------------------------------- /components/Layouts/index.ts: -------------------------------------------------------------------------------- 1 | export { SharedLayout } from './SharedLayout'; 2 | export { Sidebar } from './Sidebar'; 3 | -------------------------------------------------------------------------------- /components/Modals/AddProjectModal/index.ts: -------------------------------------------------------------------------------- 1 | export { AddProjectModal } from './AddProjectModal'; 2 | -------------------------------------------------------------------------------- /components/Modals/AddProjectModal/schema.ts: -------------------------------------------------------------------------------- 1 | import * as z from 'zod'; 2 | 3 | export const formSchema = z.object({ 4 | projectname: z.string().min(1).max(100), 5 | repositoryURL: z 6 | .string() 7 | .regex( 8 | new RegExp( 9 | '^(https://)?(www\\.)?github.com/[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+$' 10 | ), 11 | { 12 | message: 'Invalid URL (We support only GitHub repositories now)', 13 | } 14 | ), 15 | tags: z.string().min(1).max(160), 16 | description: z.string().min(160).max(500), 17 | liveUrl: z 18 | .string() 19 | .refine( 20 | (value) => 21 | !value || 22 | new RegExp( 23 | '^(https://)?(www\\.)?[a-zA-Z0-9_-]+(\\.[a-zA-Z]{2,})+(/[a-zA-Z0-9_-]*)*$' 24 | ).test(value), 25 | { 26 | message: 'Invalid URL (We support only live websites now)', 27 | } 28 | ), 29 | }); 30 | -------------------------------------------------------------------------------- /components/Modals/AuthModal/AuthModal.tsx: -------------------------------------------------------------------------------- 1 | import { useAuthModal } from '@/hooks/useAuthModal'; 2 | import { FcGoogle } from 'react-icons/fc'; 3 | import { FaGithub } from 'react-icons/fa'; 4 | import { 5 | Dialog, 6 | DialogContent, 7 | DialogDescription, 8 | DialogHeader, 9 | DialogTitle, 10 | } from '@/components/ui/dialog'; 11 | import { Button } from '../../ui/button'; 12 | import { BUTTON_TEXT } from '@/components/Common/Constants/textLabels'; 13 | 14 | export const AuthModal = () => { 15 | const { 16 | state: { isOpen }, 17 | closeModal, 18 | } = useAuthModal(); 19 | 20 | return ( 21 | <> 22 | 23 | 24 | 25 | 26 | projectmate 27 | 28 | 29 | Continue with your social accounts 30 | 31 | 32 |
    33 | 44 | 55 |
    56 |
    57 |
    58 | 59 | ); 60 | }; 61 | 62 | const popupCenter = (url: string, title: string) => { 63 | const dualScreenLeft = window.screenLeft ?? window.screenX; 64 | const dualScreenTop = window.screenTop ?? window.screenY; 65 | 66 | const width = 67 | window.innerWidth ?? document.documentElement.clientWidth ?? screen.width; 68 | 69 | const height = 70 | window.innerHeight ?? 71 | document.documentElement.clientHeight ?? 72 | screen.height; 73 | 74 | const systemZoom = width / window.screen.availWidth; 75 | 76 | const left = (width - 500) / 2 / systemZoom + dualScreenLeft; 77 | const top = (height - 550) / 2 / systemZoom + dualScreenTop; 78 | 79 | const newWindow = window.open( 80 | url, 81 | title, 82 | `width=${500 / systemZoom},height=${ 83 | 550 / systemZoom 84 | },top=${top},left=${left}` 85 | ); 86 | 87 | newWindow?.focus(); 88 | }; 89 | -------------------------------------------------------------------------------- /components/Modals/AuthModal/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AuthModal'; 2 | -------------------------------------------------------------------------------- /components/Modals/ShareModal/ShareModal.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { Typography } from '@/components/Common/Typography'; 3 | import { 4 | EmailShareButton, 5 | EmailIcon, 6 | FacebookShareButton, 7 | FacebookIcon, 8 | TwitterShareButton, 9 | TwitterIcon, 10 | WhatsappShareButton, 11 | WhatsappIcon, 12 | LinkedinShareButton, 13 | LinkedinIcon, 14 | } from 'react-share'; 15 | import { 16 | Dialog, 17 | DialogContent, 18 | DialogDescription, 19 | DialogFooter, 20 | DialogHeader, 21 | DialogTitle, 22 | } from '@/components/ui/dialog'; 23 | import { useCopyToClipboard } from 'usehooks-ts'; 24 | import { useShareModal } from '@/hooks/useShareModal'; 25 | import { Button } from '../../ui/button'; 26 | import { useToast } from '../../ui/use-toast'; 27 | import { BUTTON_TEXT } from '@/components/Common/Constants/textLabels'; 28 | 29 | export const ShareModal: FC = () => { 30 | const { 31 | state: { isOpen, data }, 32 | closeModal, 33 | } = useShareModal(); 34 | const { toast } = useToast(); 35 | const { url, title } = data; 36 | const [, copy] = useCopyToClipboard(); 37 | 38 | const copyToClipboard = (url: string) => { 39 | const isCopied = copy(url); 40 | if (!isCopied) { 41 | toast({ 42 | title: 'Copying error', 43 | variant: 'destructive', 44 | }); 45 | return; 46 | } 47 | toast({ 48 | title: 'Copied to clipboard', 49 | }); 50 | }; 51 | 52 | return ( 53 | 54 | 55 | 56 | Share project 57 | 58 | Share this project through social network or email 59 | 60 | 61 |
    62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 |
    78 | 79 |
    80 |
    81 | 86 | {url} 87 | 88 |
    89 | 92 |
    93 |
    94 |
    95 |
    96 | ); 97 | }; 98 | -------------------------------------------------------------------------------- /components/Modals/ShareModal/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ShareModal'; 2 | -------------------------------------------------------------------------------- /components/ThemeProvider/ThemeProvider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ThemeProvider as NextThemesProvider } from 'next-themes'; 3 | import { type ThemeProviderProps } from 'next-themes/dist/types'; 4 | 5 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 6 | return {children}; 7 | } 8 | -------------------------------------------------------------------------------- /components/ui/accordion.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as AccordionPrimitive from '@radix-ui/react-accordion'; 3 | import { ChevronDown } from 'lucide-react'; 4 | 5 | import { cn } from '@/lib/utils'; 6 | 7 | const Accordion = AccordionPrimitive.Root; 8 | 9 | const AccordionItem = React.forwardRef< 10 | React.ElementRef, 11 | React.ComponentPropsWithoutRef 12 | >(({ className, ...props }, ref) => ( 13 | 18 | )); 19 | AccordionItem.displayName = 'AccordionItem'; 20 | 21 | const AccordionTrigger = React.forwardRef< 22 | React.ElementRef, 23 | React.ComponentPropsWithoutRef 24 | >(({ className, children, ...props }, ref) => ( 25 | 26 | svg]:rotate-180', 30 | className 31 | )} 32 | {...props} 33 | > 34 | {children} 35 | 36 | 37 | 38 | )); 39 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; 40 | 41 | const AccordionContent = React.forwardRef< 42 | React.ElementRef, 43 | React.ComponentPropsWithoutRef 44 | >(({ className, children, ...props }, ref) => ( 45 | 50 |
    {children}
    51 |
    52 | )); 53 | 54 | AccordionContent.displayName = AccordionPrimitive.Content.displayName; 55 | 56 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; 57 | -------------------------------------------------------------------------------- /components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as AvatarPrimitive from '@radix-ui/react-avatar'; 3 | 4 | import { cn } from '@/lib/utils'; 5 | 6 | const Avatar = React.forwardRef< 7 | React.ElementRef, 8 | React.ComponentPropsWithoutRef 9 | >(({ className, ...props }, ref) => ( 10 | 18 | )); 19 | Avatar.displayName = AvatarPrimitive.Root.displayName; 20 | 21 | const AvatarImage = React.forwardRef< 22 | React.ElementRef, 23 | React.ComponentPropsWithoutRef 24 | >(({ className, ...props }, ref) => ( 25 | 30 | )); 31 | AvatarImage.displayName = AvatarPrimitive.Image.displayName; 32 | 33 | const AvatarFallback = React.forwardRef< 34 | React.ElementRef, 35 | React.ComponentPropsWithoutRef 36 | >(({ className, ...props }, ref) => ( 37 | 45 | )); 46 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; 47 | 48 | export { Avatar, AvatarImage, AvatarFallback }; 49 | -------------------------------------------------------------------------------- /components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { cva, type VariantProps } from 'class-variance-authority'; 3 | 4 | import { cn } from '@/lib/utils'; 5 | 6 | const badgeVariants = cva( 7 | 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80', 13 | secondary: 14 | 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80', 15 | destructive: 16 | 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80', 17 | outline: 'text-foreground', 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: 'default', 22 | }, 23 | } 24 | ); 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
    33 | ); 34 | } 35 | 36 | export { Badge, badgeVariants }; 37 | -------------------------------------------------------------------------------- /components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Slot } from '@radix-ui/react-slot'; 3 | import { cva, type VariantProps } from 'class-variance-authority'; 4 | 5 | import { cn } from '@/lib/utils'; 6 | 7 | const buttonVariants = cva( 8 | 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', 9 | { 10 | variants: { 11 | variant: { 12 | default: 'bg-primary text-primary-foreground hover:bg-primary/90', 13 | destructive: 14 | 'bg-destructive text-destructive-foreground hover:bg-destructive/90', 15 | destructive_outline: 'border border-red-500 text-red-500', 16 | outline: 17 | 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', 18 | secondary: 19 | 'bg-secondary text-secondary-foreground hover:bg-secondary/80', 20 | ghost: 'hover:bg-accent hover:text-accent-foreground', 21 | link: 'text-primary underline-offset-4 hover:underline', 22 | }, 23 | size: { 24 | default: 'h-10 px-4 py-2', 25 | sm: 'h-9 rounded-md px-3', 26 | lg: 'h-11 rounded-md px-8', 27 | icon: 'h-10 w-10', 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: 'default', 32 | size: 'default', 33 | }, 34 | } 35 | ); 36 | 37 | export interface ButtonProps 38 | extends React.ButtonHTMLAttributes, 39 | VariantProps { 40 | asChild?: boolean; 41 | } 42 | 43 | const Button = React.forwardRef( 44 | ({ className, variant, size, asChild = false, ...props }, ref) => { 45 | const Comp = asChild ? Slot : 'button'; 46 | return ( 47 | 52 | ); 53 | } 54 | ); 55 | Button.displayName = 'Button'; 56 | 57 | export { Button, buttonVariants }; 58 | -------------------------------------------------------------------------------- /components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { cn } from '@/lib/utils'; 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
    17 | )); 18 | Card.displayName = 'Card'; 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
    29 | )); 30 | CardHeader.displayName = 'CardHeader'; 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLParagraphElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |

    44 | )); 45 | CardTitle.displayName = 'CardTitle'; 46 | 47 | const CardDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |

    56 | )); 57 | CardDescription.displayName = 'CardDescription'; 58 | 59 | const CardContent = React.forwardRef< 60 | HTMLDivElement, 61 | React.HTMLAttributes 62 | >(({ className, ...props }, ref) => ( 63 |

    64 | )); 65 | CardContent.displayName = 'CardContent'; 66 | 67 | const CardFooter = React.forwardRef< 68 | HTMLDivElement, 69 | React.HTMLAttributes 70 | >(({ className, ...props }, ref) => ( 71 |
    76 | )); 77 | CardFooter.displayName = 'CardFooter'; 78 | 79 | export { 80 | Card, 81 | CardHeader, 82 | CardFooter, 83 | CardTitle, 84 | CardDescription, 85 | CardContent, 86 | }; 87 | -------------------------------------------------------------------------------- /components/ui/dialog.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as DialogPrimitive from '@radix-ui/react-dialog'; 3 | import { X } from 'lucide-react'; 4 | 5 | import { cn } from '@/lib/utils'; 6 | 7 | const Dialog = DialogPrimitive.Root; 8 | 9 | const DialogTrigger = DialogPrimitive.Trigger; 10 | 11 | const DialogPortal = DialogPrimitive.Portal; 12 | 13 | const DialogClose = DialogPrimitive.Close; 14 | 15 | const DialogOverlay = React.forwardRef< 16 | React.ElementRef, 17 | React.ComponentPropsWithoutRef 18 | >(({ className, ...props }, ref) => ( 19 | 27 | )); 28 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; 29 | 30 | const DialogContent = React.forwardRef< 31 | React.ElementRef, 32 | React.ComponentPropsWithoutRef 33 | >(({ className, children, ...props }, ref) => ( 34 | 35 | 36 | 44 | {children} 45 | 46 | 47 | Close 48 | 49 | 50 | 51 | )); 52 | DialogContent.displayName = DialogPrimitive.Content.displayName; 53 | 54 | const DialogHeader = ({ 55 | className, 56 | ...props 57 | }: React.HTMLAttributes) => ( 58 |
    65 | ); 66 | DialogHeader.displayName = 'DialogHeader'; 67 | 68 | const DialogFooter = ({ 69 | className, 70 | ...props 71 | }: React.HTMLAttributes) => ( 72 |
    79 | ); 80 | DialogFooter.displayName = 'DialogFooter'; 81 | 82 | const DialogTitle = React.forwardRef< 83 | React.ElementRef, 84 | React.ComponentPropsWithoutRef 85 | >(({ className, ...props }, ref) => ( 86 | 94 | )); 95 | DialogTitle.displayName = DialogPrimitive.Title.displayName; 96 | 97 | const DialogDescription = React.forwardRef< 98 | React.ElementRef, 99 | React.ComponentPropsWithoutRef 100 | >(({ className, ...props }, ref) => ( 101 | 106 | )); 107 | DialogDescription.displayName = DialogPrimitive.Description.displayName; 108 | 109 | export { 110 | Dialog, 111 | DialogPortal, 112 | DialogOverlay, 113 | DialogClose, 114 | DialogTrigger, 115 | DialogContent, 116 | DialogHeader, 117 | DialogFooter, 118 | DialogTitle, 119 | DialogDescription, 120 | }; 121 | -------------------------------------------------------------------------------- /components/ui/form.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as LabelPrimitive from '@radix-ui/react-label'; 3 | import { Slot } from '@radix-ui/react-slot'; 4 | import { 5 | Controller, 6 | ControllerProps, 7 | FieldPath, 8 | FieldValues, 9 | FormProvider, 10 | useFormContext, 11 | } from 'react-hook-form'; 12 | 13 | import { cn } from '@/lib/utils'; 14 | import { Label } from '@/components/ui/label'; 15 | 16 | const Form = FormProvider; 17 | 18 | type FormFieldContextValue< 19 | TFieldValues extends FieldValues = FieldValues, 20 | TName extends FieldPath = FieldPath 21 | > = { 22 | name: TName; 23 | }; 24 | 25 | const FormFieldContext = React.createContext( 26 | {} as FormFieldContextValue 27 | ); 28 | 29 | const FormField = < 30 | TFieldValues extends FieldValues = FieldValues, 31 | TName extends FieldPath = FieldPath 32 | >({ 33 | ...props 34 | }: ControllerProps) => { 35 | return ( 36 | 37 | 38 | 39 | ); 40 | }; 41 | 42 | const useFormField = () => { 43 | const fieldContext = React.useContext(FormFieldContext); 44 | const itemContext = React.useContext(FormItemContext); 45 | const { getFieldState, formState } = useFormContext(); 46 | 47 | const fieldState = getFieldState(fieldContext.name, formState); 48 | 49 | if (!fieldContext) { 50 | throw new Error('useFormField should be used within '); 51 | } 52 | 53 | const { id } = itemContext; 54 | 55 | return { 56 | id, 57 | name: fieldContext.name, 58 | formItemId: `${id}-form-item`, 59 | formDescriptionId: `${id}-form-item-description`, 60 | formMessageId: `${id}-form-item-message`, 61 | ...fieldState, 62 | }; 63 | }; 64 | 65 | type FormItemContextValue = { 66 | id: string; 67 | }; 68 | 69 | const FormItemContext = React.createContext( 70 | {} as FormItemContextValue 71 | ); 72 | 73 | const FormItem = React.forwardRef< 74 | HTMLDivElement, 75 | React.HTMLAttributes 76 | >(({ className, ...props }, ref) => { 77 | const id = React.useId(); 78 | 79 | return ( 80 | 81 |
    82 | 83 | ); 84 | }); 85 | FormItem.displayName = 'FormItem'; 86 | 87 | const FormLabel = React.forwardRef< 88 | React.ElementRef, 89 | React.ComponentPropsWithoutRef 90 | >(({ className, ...props }, ref) => { 91 | const { error, formItemId } = useFormField(); 92 | 93 | return ( 94 |