├── drizzle ├── 0001_stiff_cardiac.sql ├── 0004_secret_wolfpack.sql ├── 0005_illegal_arclight.sql ├── 0003_puzzling_firedrake.sql ├── 0006_keen_maddog.sql ├── 0010_amused_betty_brant.sql ├── 0011_nervous_mantis.sql ├── 0014_far_dust.sql ├── 0008_giant_spiral.sql ├── 0009_friendly_logan.sql ├── 0016_many_red_wolf.sql ├── 0013_bent_madame_masque.sql ├── 0015_tricky_silver_sable.sql ├── 0019_groovy_kid_colt.sql ├── 0018_oval_supreme_intelligence.sql ├── 0017_gorgeous_firestar.sql ├── 0000_famous_maggott.sql ├── 0007_elite_norman_osborn.sql ├── relations.ts ├── 0012_harsh_blue_blade.sql ├── 0002_foamy_iron_monger.sql ├── schema.ts └── meta │ ├── 0000_snapshot.json │ ├── 0001_snapshot.json │ ├── _journal.json │ ├── 0002_snapshot.json │ └── 0003_snapshot.json ├── public ├── og.png ├── og1.png ├── hero1.png ├── hero2.png ├── hero3.png ├── feature1.png ├── templates │ ├── elegant.png │ ├── minimal.png │ ├── pristine.png │ └── vibrant.png ├── vercel.svg ├── logo.svg ├── window.svg ├── file.svg ├── producthunt.svg ├── medium.svg ├── devto.svg ├── globe.svg └── next.svg ├── app ├── favicon.ico ├── (auth) │ ├── sign-in │ │ └── [[...sign-in]] │ │ │ └── page.tsx │ └── sign-up │ │ └── [[...sign-up]] │ │ └── page.tsx ├── (root) │ ├── [username] │ │ ├── theme-provider.tsx │ │ ├── layout.tsx │ │ └── provider.tsx │ ├── create │ │ └── layout.tsx │ └── admin │ │ ├── page.tsx │ │ ├── layout.tsx │ │ ├── analytics │ │ └── page.tsx │ │ └── styles │ │ └── page.tsx ├── api │ ├── user │ │ ├── update │ │ │ └── route.ts │ │ ├── medium │ │ │ ├── settings │ │ │ │ └── [userId] │ │ │ │ │ └── route.ts │ │ │ └── [username] │ │ │ │ └── route.ts │ │ ├── devto │ │ │ ├── settings │ │ │ │ └── [userId] │ │ │ │ │ └── route.ts │ │ │ └── [username] │ │ │ │ └── route.ts │ │ ├── github │ │ │ ├── settings │ │ │ │ └── [userId] │ │ │ │ │ └── route.ts │ │ │ └── [username] │ │ │ │ └── route.ts │ │ ├── producthunt │ │ │ ├── settings │ │ │ │ └── [userId] │ │ │ │ │ └── route.ts │ │ │ └── [username] │ │ │ │ └── route.ts │ │ └── [userId] │ │ │ └── route.ts │ ├── projects │ │ ├── order │ │ │ └── route.ts │ │ ├── click │ │ │ └── route.ts │ │ └── route.ts │ ├── profile │ │ └── visit │ │ │ └── route.ts │ ├── upload │ │ └── route.ts │ ├── theme │ │ └── route.ts │ ├── template │ │ └── route.ts │ ├── font │ │ └── route.ts │ └── analytics │ │ └── route.ts └── layout.tsx ├── postcss.config.mjs ├── lib ├── utils.ts ├── cloudinary.ts ├── hero-images.ts ├── templates.ts ├── features.ts ├── fonts.ts └── faqs.ts ├── db ├── drizzle.ts └── schema.ts ├── drizzle.config.ts ├── components ├── ui │ ├── skeleton.tsx │ ├── textarea.tsx │ ├── label.tsx │ ├── input.tsx │ ├── separator.tsx │ ├── toaster.tsx │ ├── switch.tsx │ ├── badge.tsx │ ├── tooltip.tsx │ ├── avatar.tsx │ ├── radio-group.tsx │ ├── text-rotate.tsx │ ├── alert.tsx │ ├── blur-fade.tsx │ ├── button.tsx │ ├── card.tsx │ ├── float.tsx │ ├── accordion.tsx │ ├── expandable-tabs.tsx │ ├── scramble-in.tsx │ ├── dialog.tsx │ ├── sheet.tsx │ └── platform-toggle.tsx ├── theme-provider.tsx ├── navbar-container.tsx ├── feature-card.tsx ├── projects.tsx ├── footer-container.tsx ├── portfolio │ └── top-bar.tsx ├── mode-toggler.tsx ├── projects │ ├── project-clicks.tsx │ └── sortable-project-card.tsx ├── call-to-action.tsx ├── editor.tsx ├── admin-sidebar.tsx ├── font-options.tsx ├── preview.tsx ├── navbar.tsx ├── feature-section.tsx ├── footer.tsx ├── template-options.tsx └── theme-options.tsx ├── middleware.ts ├── eslint.config.mjs ├── components.json ├── actions ├── user.ts ├── user-data.ts └── username.ts ├── hooks ├── use-mobile.tsx ├── use-click-outside.ts └── use-toast.ts ├── .gitignore ├── next.config.ts ├── tsconfig.json ├── types └── index.ts ├── package.json └── tailwind.config.ts /drizzle/0001_stiff_cardiac.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "user" ADD COLUMN "profile_picture" text; -------------------------------------------------------------------------------- /drizzle/0004_secret_wolfpack.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "projects" ADD COLUMN "github" text; -------------------------------------------------------------------------------- /drizzle/0005_illegal_arclight.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "user" ADD COLUMN "skills" text[]; -------------------------------------------------------------------------------- /public/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArjunCodess/portify/HEAD/public/og.png -------------------------------------------------------------------------------- /public/og1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArjunCodess/portify/HEAD/public/og1.png -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArjunCodess/portify/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /public/hero1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArjunCodess/portify/HEAD/public/hero1.png -------------------------------------------------------------------------------- /public/hero2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArjunCodess/portify/HEAD/public/hero2.png -------------------------------------------------------------------------------- /public/hero3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArjunCodess/portify/HEAD/public/hero3.png -------------------------------------------------------------------------------- /public/feature1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArjunCodess/portify/HEAD/public/feature1.png -------------------------------------------------------------------------------- /drizzle/0003_puzzling_firedrake.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "projects" ADD COLUMN "order" integer DEFAULT 0; -------------------------------------------------------------------------------- /drizzle/0006_keen_maddog.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "user" ADD COLUMN "theme" varchar DEFAULT 'neutral' NOT NULL; -------------------------------------------------------------------------------- /drizzle/0010_amused_betty_brant.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "projects" ADD COLUMN "click_count" integer DEFAULT 0; -------------------------------------------------------------------------------- /drizzle/0011_nervous_mantis.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "user" ADD COLUMN "profile_visit_count" integer DEFAULT 0; -------------------------------------------------------------------------------- /drizzle/0014_far_dust.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "user" ADD CONSTRAINT "user_username_unique" UNIQUE("username"); -------------------------------------------------------------------------------- /drizzle/0008_giant_spiral.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "projects_clicks" ALTER COLUMN "timestamp" SET DATA TYPE timestamp; -------------------------------------------------------------------------------- /drizzle/0009_friendly_logan.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "projects_clicks" ALTER COLUMN "timestamp" SET DATA TYPE integer; -------------------------------------------------------------------------------- /drizzle/0016_many_red_wolf.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "user" ADD COLUMN "show_github" boolean DEFAULT false NOT NULL; -------------------------------------------------------------------------------- /drizzle/0013_bent_madame_masque.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "user" ADD COLUMN "template" varchar DEFAULT 'minimal' NOT NULL; -------------------------------------------------------------------------------- /public/templates/elegant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArjunCodess/portify/HEAD/public/templates/elegant.png -------------------------------------------------------------------------------- /public/templates/minimal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArjunCodess/portify/HEAD/public/templates/minimal.png -------------------------------------------------------------------------------- /public/templates/pristine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArjunCodess/portify/HEAD/public/templates/pristine.png -------------------------------------------------------------------------------- /public/templates/vibrant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArjunCodess/portify/HEAD/public/templates/vibrant.png -------------------------------------------------------------------------------- /drizzle/0015_tricky_silver_sable.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "user" ADD COLUMN "font" jsonb DEFAULT '{"heading":"geist","content":"geist"}'::jsonb NOT NULL; -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /drizzle/0019_groovy_kid_colt.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "user" ADD COLUMN "medium" varchar;--> statement-breakpoint 2 | ALTER TABLE "user" ADD COLUMN "show_medium" boolean DEFAULT false NOT NULL; -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /drizzle/0018_oval_supreme_intelligence.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "user" ADD COLUMN "devto" varchar;--> statement-breakpoint 2 | ALTER TABLE "user" ADD COLUMN "show_devto" boolean DEFAULT false NOT NULL; -------------------------------------------------------------------------------- /drizzle/0017_gorgeous_firestar.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "user" ADD COLUMN "product_hunt" varchar;--> statement-breakpoint 2 | ALTER TABLE "user" ADD COLUMN "show_product_hunt" boolean DEFAULT false NOT NULL; -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/drizzle.ts: -------------------------------------------------------------------------------- 1 | import { neon } from '@neondatabase/serverless'; 2 | import { drizzle } from 'drizzle-orm/neon-http'; 3 | import * as schema from './schema'; 4 | 5 | const sql = neon(process.env.DATABASE_URL!); 6 | export const db = drizzle({ client: sql, schema }); -------------------------------------------------------------------------------- /drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import { defineConfig } from 'drizzle-kit'; 3 | 4 | export default defineConfig({ 5 | out: './drizzle', 6 | schema: './db/schema.ts', 7 | dialect: 'postgresql', 8 | dbCredentials: { 9 | url: process.env.DATABASE_URL!, 10 | }, 11 | }); -------------------------------------------------------------------------------- /lib/cloudinary.ts: -------------------------------------------------------------------------------- 1 | import { v2 as cloudinary } from 'cloudinary'; 2 | 3 | cloudinary.config({ 4 | cloud_name: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME, 5 | api_key: process.env.CLOUDINARY_API_KEY, 6 | api_secret: process.env.CLOUDINARY_API_SECRET, 7 | }); 8 | 9 | export { cloudinary }; 10 | -------------------------------------------------------------------------------- /drizzle/0000_famous_maggott.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "user" ( 2 | "id" varchar PRIMARY KEY NOT NULL, 3 | "name" varchar NOT NULL, 4 | "email" varchar NOT NULL, 5 | "username" varchar NOT NULL, 6 | "tagline" text, 7 | "bio" text, 8 | "twitter" varchar, 9 | "github" varchar, 10 | "link" text, 11 | "location" varchar 12 | ); 13 | -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } -------------------------------------------------------------------------------- /components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { ThemeProvider as NextThemesProvider } from "next-themes"; 5 | 6 | export function ThemeProvider({ 7 | children, 8 | ...props 9 | }: React.ComponentProps) { 10 | return {children}; 11 | } 12 | -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /drizzle/0007_elite_norman_osborn.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "projects_clicks" ( 2 | "id" varchar PRIMARY KEY NOT NULL, 3 | "project_id" varchar NOT NULL, 4 | "timestamp" integer NOT NULL 5 | ); 6 | --> statement-breakpoint 7 | ALTER TABLE "projects_clicks" ADD CONSTRAINT "projects_clicks_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE no action ON UPDATE no action; -------------------------------------------------------------------------------- /drizzle/relations.ts: -------------------------------------------------------------------------------- 1 | import { relations } from "drizzle-orm/relations"; 2 | import { user, projects } from "./schema"; 3 | 4 | export const projectsRelations = relations(projects, ({one}) => ({ 5 | user: one(user, { 6 | fields: [projects.userId], 7 | references: [user.id] 8 | }), 9 | })); 10 | 11 | export const userRelations = relations(user, ({many}) => ({ 12 | projects: many(projects), 13 | })); -------------------------------------------------------------------------------- /middleware.ts: -------------------------------------------------------------------------------- 1 | import { clerkMiddleware } from "@clerk/nextjs/server"; 2 | 3 | export default clerkMiddleware(); 4 | 5 | export const config = { 6 | matcher: [ 7 | // Skip Next.js internals and all static files, unless found in search params 8 | '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)', 9 | // Always run for API routes 10 | '/(api|trpc)(.*)', 11 | ], 12 | }; -------------------------------------------------------------------------------- /drizzle/0012_harsh_blue_blade.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "profile_visits" ( 2 | "id" varchar PRIMARY KEY NOT NULL, 3 | "user_id" varchar NOT NULL, 4 | "timestamp" integer NOT NULL 5 | ); 6 | --> statement-breakpoint 7 | ALTER TABLE "profile_visits" ADD CONSTRAINT "profile_visits_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint 8 | ALTER TABLE "user" DROP COLUMN "profile_visit_count"; -------------------------------------------------------------------------------- /lib/hero-images.ts: -------------------------------------------------------------------------------- 1 | export const heroImages = [ 2 | { 3 | src: '/og.png', 4 | alt: 'OG', 5 | rotation: -6 6 | }, 7 | { 8 | src: '/hero2.png', 9 | alt: 'HERO2', 10 | rotation: -12 11 | }, 12 | { 13 | src: '/hero3.png', 14 | alt: 'HERO3', 15 | rotation: 6 16 | }, 17 | { 18 | src: '/hero1.png', 19 | alt: 'HERO1', 20 | rotation: 12 21 | } 22 | ] -------------------------------------------------------------------------------- /drizzle/0002_foamy_iron_monger.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "projects" ( 2 | "id" varchar PRIMARY KEY NOT NULL, 3 | "name" varchar NOT NULL, 4 | "description" text, 5 | "link" text, 6 | "user_id" varchar NOT NULL, 7 | "logo" text, 8 | "banner" text, 9 | "category" varchar 10 | ); 11 | --> statement-breakpoint 12 | ALTER TABLE "projects" ADD CONSTRAINT "projects_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action; -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 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 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /components/navbar-container.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { usePathname } from "next/navigation"; 4 | import Navbar from "./navbar"; 5 | 6 | export default function NavbarContainer() { 7 | const pathname = usePathname(); 8 | const isUsernameRoute = 9 | pathname?.split("/").length === 2 && 10 | pathname !== "/" && 11 | pathname !== "/admin" && 12 | pathname !== "/create" && 13 | pathname !== "/sign-in" && 14 | pathname !== "/sign-up"; 15 | 16 | if (isUsernameRoute) { 17 | return null; 18 | } 19 | 20 | return ; 21 | } -------------------------------------------------------------------------------- /lib/templates.ts: -------------------------------------------------------------------------------- 1 | export const templates = [ 2 | { 3 | value: "minimal", 4 | label: "Minimal", 5 | image: "/templates/minimal.png", 6 | }, 7 | { 8 | value: "pristine", 9 | label: "Pristine", 10 | image: "/templates/pristine.png", 11 | }, 12 | { 13 | value: "vibrant", 14 | label: "Vibrant", 15 | image: "/templates/vibrant.png", 16 | }, 17 | { 18 | value: "elegant", 19 | label: "Elegant", 20 | image: "/templates/elegant.png", 21 | }, 22 | ] as const; 23 | 24 | export type TemplateType = typeof templates[number]["value"]; -------------------------------------------------------------------------------- /components/feature-card.tsx: -------------------------------------------------------------------------------- 1 | import { Card, CardContent } from "@/components/ui/card" 2 | 3 | interface FeatureCardProps { 4 | icon: React.ReactNode 5 | text: string 6 | } 7 | 8 | export function FeatureCard({ icon, text }: FeatureCardProps) { 9 | return ( 10 | 11 | 12 |
{icon}
13 |

{text}

14 |
15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /public/producthunt.svg: -------------------------------------------------------------------------------- 1 | Product Hunt icon 2 | 10 | -------------------------------------------------------------------------------- /actions/user.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | 3 | import { db } from "@/db/drizzle"; 4 | import { user as userSchema } from "@/db/schema"; 5 | import { eq } from "drizzle-orm"; 6 | 7 | export async function checkUserExists(email: string) { 8 | const result = await db 9 | .select() 10 | .from(userSchema) 11 | .where(eq(userSchema.email, email)); 12 | 13 | return result.length > 0; 14 | } 15 | 16 | export async function getUserByEmail(email: string) { 17 | const result = await db 18 | .select() 19 | .from(userSchema) 20 | .where(eq(userSchema.email, email)); 21 | 22 | return result[0]; 23 | } -------------------------------------------------------------------------------- /app/(auth)/sign-in/[[...sign-in]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignIn } from "@clerk/nextjs"; 2 | import { Metadata } from 'next'; 3 | 4 | export const metadata: Metadata = { 5 | title: 'Sign In | Portify', 6 | description: 'Sign in to your Portify account to manage your portfolio', 7 | robots: { 8 | index: false, 9 | follow: false, 10 | }, 11 | alternates: { 12 | canonical: '/sign-in', 13 | }, 14 | }; 15 | 16 | export default function Page() { 17 | return ( 18 |
19 | 20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /app/(auth)/sign-up/[[...sign-up]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignUp } from "@clerk/nextjs"; 2 | import { Metadata } from 'next'; 3 | 4 | export const metadata: Metadata = { 5 | title: 'Create Account | Portify', 6 | description: 'Join Portify to create and showcase your professional portfolio', 7 | robots: { 8 | index: false, 9 | follow: false, 10 | }, 11 | alternates: { 12 | canonical: '/sign-up', 13 | }, 14 | }; 15 | 16 | export default function Page() { 17 | return ( 18 |
19 | 20 |
21 | ); 22 | } -------------------------------------------------------------------------------- /lib/features.ts: -------------------------------------------------------------------------------- 1 | export const features = [ 2 | { 3 | step: '1', 4 | title: 'Create a Profile', 5 | content: 'Login/Sign up, go to /create and create your profile.', 6 | image: '/feature1.png' 7 | }, 8 | { 9 | step: '2', 10 | title: 'Add Your Details', 11 | content: 'Provide the necessary information to complete your profile.', 12 | image: '/hero3.png' 13 | }, 14 | { 15 | step: '3', 16 | title: 'Share with Anyone', 17 | content: 'Share your profile with anyone to showcase your work.', 18 | image: '/hero2.png' 19 | }, 20 | ] -------------------------------------------------------------------------------- /components/projects.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import AddProject from "./projects/add-project"; 3 | import ProjectList from "./projects/project-list"; 4 | import { Card, CardContent, CardHeader, CardTitle } from "./ui/card"; 5 | 6 | export default function Projects() { 7 | return ( 8 | 9 | 10 | Projects 11 | 12 | 13 | 14 | 15 | 16 | 17 | ); 18 | } -------------------------------------------------------------------------------- /hooks/use-mobile.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | const MOBILE_BREAKPOINT = 768 4 | 5 | export function useIsMobile() { 6 | const [isMobile, setIsMobile] = React.useState(undefined) 7 | 8 | React.useEffect(() => { 9 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) 10 | const onChange = () => { 11 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 12 | } 13 | mql.addEventListener("change", onChange) 14 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 15 | return () => mql.removeEventListener("change", onChange) 16 | }, []) 17 | 18 | return !!isMobile 19 | } 20 | -------------------------------------------------------------------------------- /components/footer-container.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { usePathname } from "next/navigation"; 4 | import Footer from "./footer"; 5 | 6 | export default function FooterContainer() { 7 | const pathname = usePathname(); 8 | const isUsernameRoute = 9 | pathname?.split("/").length === 2 && 10 | pathname !== "/" && 11 | pathname !== "/create" && 12 | pathname !== "/sign-in" && 13 | pathname !== "/sign-up"; 14 | 15 | if ( 16 | isUsernameRoute || 17 | pathname === "/admin/" || 18 | pathname === "/admin/styles" || 19 | pathname === "/admin/analytics" 20 | ) { 21 | return null; 22 | } 23 | 24 | return