├── app ├── favicon.ico ├── opengraph-image.png ├── login │ ├── page.tsx │ └── client.tsx ├── (dashboard) │ ├── dashboard │ │ ├── advertise │ │ │ ├── new │ │ │ │ └── page.tsx │ │ │ └── page.tsx │ │ ├── page.tsx │ │ ├── account │ │ │ └── page.tsx │ │ └── projects │ │ │ ├── page.tsx │ │ │ ├── new │ │ │ └── page.tsx │ │ │ └── [projectuuid] │ │ │ └── edit │ │ │ └── page.tsx │ └── layout.tsx ├── (regular) │ ├── advertise │ │ ├── page.tsx │ │ └── client.tsx │ ├── layout.tsx │ ├── projects │ │ ├── groups │ │ │ ├── ai │ │ │ │ └── page.tsx │ │ │ ├── finance │ │ │ │ └── page.tsx │ │ │ ├── dev-tools │ │ │ │ └── page.tsx │ │ │ ├── ecommerce │ │ │ │ └── page.tsx │ │ │ ├── education │ │ │ │ └── page.tsx │ │ │ ├── marketing │ │ │ │ └── page.tsx │ │ │ ├── non-profit │ │ │ │ └── page.tsx │ │ │ ├── productivity │ │ │ │ └── page.tsx │ │ │ └── cybersecurity │ │ │ │ └── page.tsx │ │ ├── contributions │ │ │ ├── reviews │ │ │ │ └── page.tsx │ │ │ ├── testing │ │ │ │ └── page.tsx │ │ │ ├── localization │ │ │ │ └── page.tsx │ │ │ ├── ui_design │ │ │ │ └── page.tsx │ │ │ ├── optimization │ │ │ │ └── page.tsx │ │ │ ├── docs │ │ │ │ └── page.tsx │ │ │ ├── community-management │ │ │ │ └── page.tsx │ │ │ └── automation │ │ │ │ └── page.tsx │ │ └── [projectuuid] │ │ │ ├── page.tsx │ │ │ └── client.tsx │ ├── page.tsx │ ├── privacy │ │ └── page.tsx │ ├── about │ │ └── page.tsx │ └── terms │ │ └── page.tsx ├── layout.tsx ├── api │ └── payments │ │ └── route.ts ├── globals.css └── sitemap.ts ├── .github └── og-ch.png ├── public ├── ch_icon.png ├── contribhub.png ├── vercel-logotype-dark.png └── hacktoberfest.svg ├── postcss.config.mjs ├── lib └── utils.ts ├── config ├── .env.example └── supabase │ └── seed.sql ├── services ├── utils │ ├── supabase │ │ ├── client.ts │ │ ├── service_role.ts │ │ ├── server.ts │ │ └── middleware.ts │ ├── index.ts │ └── github.ts ├── users.ts ├── stripe.ts ├── projects │ └── utils.ts └── ads.ts ├── next.config.mjs ├── eslint.config.mjs ├── components.json ├── .gitignore ├── components ├── ui │ ├── badge.tsx │ ├── label.tsx │ ├── input.tsx │ ├── button.tsx │ ├── tabs.tsx │ ├── calendar.tsx │ └── dropdown-menu.tsx ├── Ads │ ├── DynamicHomeAd.tsx │ ├── HomeAd.tsx │ └── AdsScheduler.tsx ├── Security │ ├── AuthenticatedPage.tsx │ └── SigninButtons │ │ └── SignWithGitHubButton.tsx ├── ScrollToTop.tsx ├── SweaveBadge.tsx ├── Footer.tsx ├── Projects │ ├── Tabs │ │ ├── ContributorTab.tsx │ │ └── IssuesTab.tsx │ └── ContribHubProject.tsx ├── Contexts │ └── SessionContext.tsx ├── ShareButton.tsx └── Menus │ ├── PublicMenu.tsx │ └── DashMenu.tsx ├── proxy.ts ├── tsconfig.json ├── tailwind.config.ts ├── package.json ├── README.md └── CONTRIBUTING.md /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphicmade/contribhub/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /.github/og-ch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphicmade/contribhub/HEAD/.github/og-ch.png -------------------------------------------------------------------------------- /public/ch_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphicmade/contribhub/HEAD/public/ch_icon.png -------------------------------------------------------------------------------- /public/contribhub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphicmade/contribhub/HEAD/public/contribhub.png -------------------------------------------------------------------------------- /app/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphicmade/contribhub/HEAD/app/opengraph-image.png -------------------------------------------------------------------------------- /public/vercel-logotype-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphicmade/contribhub/HEAD/public/vercel-logotype-dark.png -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /config/.env.example: -------------------------------------------------------------------------------- 1 | GITHUB_APP_TOKEN="" 2 | NEXT_PUBLIC_SUPABASE_ANON_KEY="" 3 | SUPABASE_SERVICE_KEY="" 4 | NEXT_PUBLIC_SUPABASE_URL="" 5 | NEXT_PUBLIC_CONTRIBHUB_ORIGIN_DOMAIN="" 6 | NODE_ENV=development -------------------------------------------------------------------------------- /app/login/page.tsx: -------------------------------------------------------------------------------- 1 | import LoginClient from "./client" 2 | 3 | 4 | export default function LoginPage() { 5 | 6 | return ( 7 | <> 8 | 9 | 10 | ) 11 | } -------------------------------------------------------------------------------- /services/utils/supabase/client.ts: -------------------------------------------------------------------------------- 1 | import { createBrowserClient } from '@supabase/ssr' 2 | 3 | export function createClient() { 4 | return createBrowserClient( 5 | process.env.NEXT_PUBLIC_SUPABASE_URL!, 6 | process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! 7 | ) 8 | } -------------------------------------------------------------------------------- /app/(dashboard)/dashboard/advertise/new/page.tsx: -------------------------------------------------------------------------------- 1 | import AdsScheduler from '@/components/Ads/AdsScheduler' 2 | import React from 'react' 3 | 4 | function AdvertisePage() { 5 | return ( 6 |
7 | 8 |
9 | ) 10 | } 11 | 12 | export default AdvertisePage -------------------------------------------------------------------------------- /app/(dashboard)/dashboard/page.tsx: -------------------------------------------------------------------------------- 1 | import Projects from '@/components/Projects/Projects' 2 | import React from 'react' 3 | 4 | function DashboardHomePage() { 5 | return ( 6 |
7 | 8 |
9 | ) 10 | } 11 | 12 | export default DashboardHomePage -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | async rewrites() { 4 | return [ 5 | { 6 | source: "/umami/script.js", 7 | destination: `https://eu.umami.is/script.js`, 8 | }, 9 | { 10 | source: "/umami/api/send", 11 | destination: `https://eu.umami.is/api/send`, 12 | }, 13 | ]; 14 | }, 15 | }; 16 | 17 | export default nextConfig; 18 | -------------------------------------------------------------------------------- /services/utils/supabase/service_role.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from '@supabase/supabase-js' 2 | 3 | export function createServiceRoleClient() { 4 | const supabase = createClient( 5 | process.env.NEXT_PUBLIC_SUPABASE_URL!, 6 | process.env.SUPABASE_SERVICE_KEY!, 7 | { 8 | auth: { 9 | autoRefreshToken: false, 10 | persistSession: false 11 | } 12 | } 13 | ) 14 | 15 | return supabase 16 | } -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import nextCoreWebVitals from "eslint-config-next/core-web-vitals"; 2 | import nextTypescript from "eslint-config-next/typescript"; 3 | 4 | const eslintConfig = [ 5 | ...nextCoreWebVitals, 6 | ...nextTypescript, 7 | { 8 | ignores: [ 9 | "node_modules/**", 10 | ".next/**", 11 | "out/**", 12 | "build/**", 13 | "next-env.d.ts", 14 | ], 15 | }, 16 | ]; 17 | 18 | export default eslintConfig; 19 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } -------------------------------------------------------------------------------- /app/(regular)/advertise/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Metadata } from 'next'; 3 | import AdvertiseClient from './client'; 4 | 5 | export const metadata: Metadata = { 6 | title: 'Advertise on ContribHub | ContribHub', 7 | description: 'Learn how to advertise on ContribHub and reach a community of developers and open-source enthusiasts.', 8 | }; 9 | 10 | const AdvertisePage: React.FC = () => { 11 | return ( 12 | 13 | ); 14 | }; 15 | 16 | 17 | export default AdvertisePage; 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 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | supabase 31 | .vscode 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | next-env.d.ts -------------------------------------------------------------------------------- /components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | interface BadgeProps { 6 | children: React.ReactNode; 7 | content: string | number; 8 | className?: string; 9 | } 10 | 11 | const Badge: React.FC = ({ children, content, className }) => { 12 | return ( 13 |
14 | {children} 15 | {content && ( 16 | 17 | {content} 18 | 19 | )} 20 |
21 | ); 22 | }; 23 | 24 | export { Badge }; 25 | -------------------------------------------------------------------------------- /services/utils/index.ts: -------------------------------------------------------------------------------- 1 | const CONTRIBHUB_ORIGIN_DOMAIN = `${process.env.NEXT_PUBLIC_CONTRIBHUB_ORIGIN_DOMAIN}`; 2 | const CONTRIBHUB_STORAGE_URL = `${process.env.NEXT_PUBLIC_STORAGE_URL}`; 3 | export const STRIPE_SECRET_KEY = `${process.env.STRIPE_SECRET_KEY}`; 4 | export const STRIPE_WEBHOOK_SECRET = `${process.env.STRIPE_WEBHOOK_SECRET}`; 5 | export const AD_PRICE_ID = `${process.env.AD_PRICE_ID}`; 6 | 7 | export const getUri = (path: string) => { 8 | return `${CONTRIBHUB_ORIGIN_DOMAIN}${path}`; 9 | }; 10 | 11 | export const getStorageUrl = (path: string) => { 12 | return `${CONTRIBHUB_STORAGE_URL}${path}`; 13 | }; 14 | 15 | export const isDevEnvironment = (): boolean => { 16 | return process.env.NODE_ENV === 'development'; 17 | }; -------------------------------------------------------------------------------- /proxy.ts: -------------------------------------------------------------------------------- 1 | 2 | import { NextResponse, type NextRequest } from "next/server"; 3 | import { updateSession } from "./services/utils/supabase/middleware"; 4 | 5 | export const config = { 6 | matcher: [ 7 | /* 8 | * Match all request paths except for the ones starting with: 9 | * - _next/static (static files) 10 | * - _next/image (image optimization files) 11 | * - favicon.ico (favicon file) 12 | * Feel free to modify this pattern to include more paths. 13 | */ 14 | "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)", 15 | ], 16 | }; 17 | 18 | export async function proxy(request: NextRequest) { 19 | 20 | // Update session 21 | await updateSession(request); 22 | 23 | // Continue to the next middleware 24 | return NextResponse.next(); 25 | } -------------------------------------------------------------------------------- /components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "dom", 5 | "dom.iterable", 6 | "esnext" 7 | ], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "strict": true, 11 | "noEmit": true, 12 | "esModuleInterop": true, 13 | "module": "esnext", 14 | "moduleResolution": "bundler", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "jsx": "react-jsx", 18 | "incremental": true, 19 | "plugins": [ 20 | { 21 | "name": "next" 22 | } 23 | ], 24 | "paths": { 25 | "@/*": [ 26 | "./*" 27 | ] 28 | }, 29 | "target": "ES2017" 30 | }, 31 | "include": [ 32 | "next-env.d.ts", 33 | "**/*.ts", 34 | "**/*.tsx", 35 | ".next/types/**/*.ts", 36 | ".next/dev/types/**/*.ts" 37 | ], 38 | "exclude": [ 39 | "node_modules" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /components/Ads/DynamicHomeAd.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import React, { useEffect, useState } from 'react' 3 | import HomeAd from './HomeAd' 4 | import { getActiveAds } from '@/services/ads' 5 | 6 | function DynamicHomeAd() { 7 | const [ad, setAd] = useState(null) 8 | 9 | useEffect(() => { 10 | async function fetchAd() { 11 | const activeAds = await getActiveAds() 12 | console.log(activeAds) 13 | if (activeAds.length > 0) { 14 | setAd(activeAds[0]) 15 | } 16 | } 17 | fetchAd() 18 | }, []) 19 | 20 | if (!ad) { 21 | return null 22 | } 23 | 24 | return ( 25 | } 30 | /> 31 | ) 32 | } 33 | 34 | export default DynamicHomeAd -------------------------------------------------------------------------------- /services/utils/supabase/server.ts: -------------------------------------------------------------------------------- 1 | import { createServerClient } from '@supabase/ssr' 2 | import { cookies } from 'next/headers'; 3 | 4 | export async function createClient() { 5 | const cookieStore = await cookies() 6 | 7 | return createServerClient( 8 | process.env.NEXT_PUBLIC_SUPABASE_URL!, 9 | process.env.SUPABASE_SERVICE_KEY!, 10 | { 11 | cookies: { 12 | getAll() { 13 | return cookieStore.getAll() 14 | }, 15 | setAll(cookiesToSet) { 16 | try { 17 | cookiesToSet.forEach(({ name, value, options }) => 18 | cookieStore.set(name, value, options) 19 | ) 20 | } catch { 21 | // The `setAll` method was called from a Server Component. 22 | // This can be ignored if you have middleware refreshing 23 | // user sessions. 24 | } 25 | }, 26 | }, 27 | } 28 | ) 29 | } -------------------------------------------------------------------------------- /components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ) 21 | } 22 | ) 23 | Input.displayName = "Input" 24 | 25 | export { Input } 26 | -------------------------------------------------------------------------------- /components/Security/AuthenticatedPage.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import React, { useEffect } from 'react'; 3 | import { useRouter, usePathname } from 'next/navigation'; 4 | import { useSession } from '../Contexts/SessionContext'; 5 | 6 | interface AuthenticatedPageProps { 7 | redirectPath: string; 8 | children: React.ReactNode; 9 | } 10 | 11 | const AuthenticatedPage: React.FC = ({ redirectPath, children }) => { 12 | const session = useSession() as any; 13 | const router = useRouter(); 14 | const pathname = usePathname(); 15 | 16 | useEffect(() => { 17 | if (!session.authenticated && !session.loading) { 18 | router.push(redirectPath + '?redirect=' + pathname); 19 | } 20 | }, [session.authenticated, session.loading, pathname, router, redirectPath]); 21 | 22 | if (session.loading || !session.authenticated) { 23 | return null; 24 | } 25 | 26 | return <>{children}; 27 | }; 28 | 29 | export default AuthenticatedPage; 30 | -------------------------------------------------------------------------------- /app/(regular)/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PublicMenu from '@/components/Menus/PublicMenu' 3 | import SessionProvider from '@/components/Contexts/SessionContext' 4 | import { Toaster } from 'react-hot-toast' 5 | import NextTopLoader from 'nextjs-toploader' 6 | import Footer from '@/components/Footer' 7 | import SweaveBadge from '@/components/SweaveBadge' 8 | import ScrollToTop from '@/components/ScrollToTop' 9 | function RegularLayout({ children }: { children: React.ReactNode }) { 10 | return ( 11 | <> 12 | 13 | 14 | 15 | 16 | 17 | {children} 18 |