├── .eslintrc.json ├── .gitignore ├── README.md ├── components.json ├── emails └── index.tsx ├── middleware.ts ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── landingPage.png ├── og-image.png └── og-image_2.png ├── src ├── app │ ├── 1 │ │ ├── [slug] │ │ │ └── page.tsx │ │ ├── error.tsx │ │ ├── loading.tsx │ │ └── page.tsx │ ├── Not-found.tsx │ ├── api │ │ ├── auth │ │ │ └── [...nextauth] │ │ │ │ └── route.ts │ │ └── og │ │ │ └── route.tsx │ ├── auth │ │ └── signin │ │ │ └── page.tsx │ ├── create │ │ ├── loading.tsx │ │ └── page.tsx │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ ├── links │ │ └── page.tsx │ ├── page.tsx │ └── preview │ │ ├── layout.tsx │ │ └── page.tsx ├── components │ ├── ActionButtons │ │ ├── CustomLinkDialog.tsx │ │ ├── DemoBtn.tsx │ │ ├── PreviewFooter.tsx │ │ ├── PublishBtn.tsx │ │ └── ResponsivePreviewBtn.tsx │ ├── AdditionalLinkCards.tsx │ ├── AdditionalLinkForm.tsx │ ├── Animation │ │ ├── BlurText.tsx │ │ ├── FlipText.tsx │ │ ├── FramerWrapper.tsx │ │ └── TextEffect.tsx │ ├── Background │ │ ├── BackgroundCards.tsx │ │ ├── BackgroundForm.tsx │ │ └── BgSnippets.tsx │ ├── DisplayData.tsx │ ├── HomeEditor.tsx │ ├── Navbar.tsx │ ├── PersonalInfo.tsx │ ├── PhotoUpload.tsx │ ├── PreviewPage.tsx │ ├── Provider.tsx │ ├── SocialLinkForm.tsx │ ├── auth.ts │ ├── forms │ │ ├── LoginGoogleBtn.tsx │ │ ├── SignupForm.tsx │ │ └── formAction.ts │ ├── mockup │ │ ├── ComputerMockup.tsx │ │ ├── MobileMockup.tsx │ │ └── MobileScreen.tsx │ ├── screen │ │ └── DisplayScreen.tsx │ └── ui │ │ ├── Drrawer.tsx │ │ ├── SocialInput.tsx │ │ ├── SortableLink.tsx │ │ ├── alert-dialog.tsx │ │ ├── avatar.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── dialog.tsx │ │ ├── dropdown-menu.tsx │ │ ├── form.tsx │ │ ├── input-otp.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── select.tsx │ │ ├── skeleton.tsx │ │ └── textarea.tsx ├── lib │ ├── Context.tsx │ ├── Firebase.ts │ ├── RateLimiter.tsx │ ├── supabase │ │ ├── actions.ts │ │ ├── supabaseClient.ts │ │ ├── supabaseMiddleware.ts │ │ └── supabaseServer.ts │ └── utils.ts ├── middleware.ts └── types │ └── Types.ts ├── tailwind.config.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | ``` 14 | 15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 16 | 17 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 18 | 19 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /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": "src/app/globals.css", 9 | "baseColor": "gray", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /emails/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Container, 4 | Head, 5 | Heading, 6 | Hr, 7 | Html, 8 | Img, 9 | Link, 10 | Preview, 11 | Section, 12 | Text, 13 | } from "@react-email/components"; 14 | import * as React from "react"; 15 | 16 | interface SupaAuthVerifyEmailProp { 17 | verificationCode?: string; 18 | } 19 | 20 | export default function SupaAuthVerifyEmail({ 21 | verificationCode = "596853", 22 | }: SupaAuthVerifyEmailProp) { 23 | return ( 24 | 25 | 26 | Supauth Email Verification 27 | 28 | 29 |
30 |
31 |
32 | 33 | SupaAuth Verify your email address 34 | 35 | 36 | { 37 | "Thanks for starting the new account creation process. We want to make sure it's really you. Please enter the following verification code when prompted. If you don't want to create an account, you can ignore this message." 38 | } 39 | 40 |
41 | 42 | Verification code 43 | 44 | 45 | {verificationCode} 46 | 47 | (This code is valid for 1 hour) 48 | 49 |
50 |
51 |
52 |
53 | 54 | 55 | ); 56 | } 57 | 58 | const main = { 59 | backgroundColor: "#fff", 60 | color: "#212121", 61 | }; 62 | 63 | const container = { 64 | padding: "20px", 65 | margin: "0 auto", 66 | backgroundColor: "#eee", 67 | }; 68 | 69 | const h1 = { 70 | color: "#333", 71 | fontFamily: 72 | "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif", 73 | fontSize: "20px", 74 | fontWeight: "bold", 75 | marginBottom: "15px", 76 | }; 77 | 78 | const link = { 79 | color: "#2754C5", 80 | fontFamily: 81 | "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif", 82 | fontSize: "14px", 83 | textDecoration: "underline", 84 | }; 85 | 86 | const text = { 87 | color: "#333", 88 | fontFamily: 89 | "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif", 90 | fontSize: "14px", 91 | margin: "24px 0", 92 | }; 93 | 94 | const imageSection = { 95 | backgroundColor: "#252f3d", 96 | display: "flex", 97 | padding: "20px 0", 98 | alignItems: "center", 99 | justifyContent: "center", 100 | }; 101 | 102 | const coverSection = { backgroundColor: "#fff" }; 103 | 104 | const upperSection = { padding: "25px 35px" }; 105 | 106 | const lowerSection = { padding: "25px 35px" }; 107 | 108 | const footerText = { 109 | ...text, 110 | fontSize: "12px", 111 | padding: "0 20px", 112 | }; 113 | 114 | const verifyText = { 115 | ...text, 116 | margin: 0, 117 | fontWeight: "bold", 118 | textAlign: "center" as const, 119 | }; 120 | 121 | const codeText = { 122 | ...text, 123 | fontWeight: "bold", 124 | fontSize: "36px", 125 | margin: "10px 0", 126 | textAlign: "center" as const, 127 | }; 128 | 129 | const validityText = { 130 | ...text, 131 | margin: "0px", 132 | textAlign: "center" as const, 133 | }; 134 | 135 | const verificationSection = { 136 | display: "flex", 137 | alignItems: "center", 138 | justifyContent: "center", 139 | }; 140 | 141 | const mainText = { ...text, marginBottom: "14px" }; 142 | -------------------------------------------------------------------------------- /middleware.ts: -------------------------------------------------------------------------------- 1 | import { updateSession } from "@/lib/supabase/supabaseMiddleware"; 2 | import { type NextRequest } from "next/server"; 3 | 4 | export async function middleware(request: NextRequest) { 5 | return await updateSession(request); 6 | } 7 | 8 | export const config = { 9 | matcher: [ 10 | /* 11 | * Match all request paths except for the ones starting with: 12 | * - _next/static (static files) 13 | * - _next/image (image optimization files) 14 | * - favicon.ico (favicon file) 15 | * Feel free to modify this pattern to include more paths. 16 | */ 17 | "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)", 18 | ], 19 | }; 20 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | 4 | }; 5 | 6 | export default nextConfig; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@dnd-kit/core": "^6.0.8", 13 | "@dnd-kit/modifiers": "^6.0.1", 14 | "@dnd-kit/sortable": "^7.0.2", 15 | "@dnd-kit/utilities": "^3.2.1", 16 | "@hookform/resolvers": "^3.9.0", 17 | "@iconify/react": "^4.1.1", 18 | "@radix-ui/react-alert-dialog": "^1.1.1", 19 | "@radix-ui/react-avatar": "^1.0.4", 20 | "@radix-ui/react-dialog": "^1.0.5", 21 | "@radix-ui/react-dropdown-menu": "^2.1.1", 22 | "@radix-ui/react-label": "^2.1.0", 23 | "@radix-ui/react-select": "^2.0.0", 24 | "@radix-ui/react-slot": "^1.1.0", 25 | "@react-email/components": "0.0.22", 26 | "@supabase/ssr": "^0.4.0", 27 | "@supabase/supabase-js": "^2.45.1", 28 | "@types/node": "20.6.0", 29 | "@types/react": "18.2.21", 30 | "@types/react-dom": "18.2.7", 31 | "@vercel/og": "^0.5.17", 32 | "autoprefixer": "10.4.15", 33 | "canvas-confetti": "^1.9.3", 34 | "class-variance-authority": "^0.7.0", 35 | "clsx": "^2.0.0", 36 | "encoding": "^0.1.13", 37 | "eslint": "8.49.0", 38 | "eslint-config-next": "13.4.19", 39 | "firebase": "^10.4.0", 40 | "framer-motion": "^11.2.13", 41 | "input-otp": "^1.2.4", 42 | "js-base64": "^3.7.5", 43 | "lucide-react": "^0.276.0", 44 | "next": "^14.1.4", 45 | "next-auth": "^5.0.0-beta.20", 46 | "postcss": "8.4.29", 47 | "react": "18.2.0", 48 | "react-dom": "18.2.0", 49 | "react-email": "2.1.6", 50 | "react-hook-form": "^7.52.2", 51 | "react-icons": "^4.12.0", 52 | "resend": "^3.5.0", 53 | "sonner": "^1.5.0", 54 | "tailwind-merge": "^1.14.0", 55 | "tailwindcss": "3.3.3", 56 | "tailwindcss-animate": "^1.0.7", 57 | "typescript": "5.2.2", 58 | "vaul": "^0.7.1", 59 | "zod": "^3.23.8" 60 | }, 61 | "devDependencies": { 62 | "@types/canvas-confetti": "^1.6.4" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/landingPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taqui-786/itZmyLink/a216201f8bec62db52463abda5cb5b3f7076c582/public/landingPage.png -------------------------------------------------------------------------------- /public/og-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taqui-786/itZmyLink/a216201f8bec62db52463abda5cb5b3f7076c582/public/og-image.png -------------------------------------------------------------------------------- /public/og-image_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taqui-786/itZmyLink/a216201f8bec62db52463abda5cb5b3f7076c582/public/og-image_2.png -------------------------------------------------------------------------------- /src/app/1/[slug]/page.tsx: -------------------------------------------------------------------------------- 1 | import { decodeData } from "@/lib/utils"; 2 | import { BACKGROUND_OPTIONS } from "@/components/Background/BgSnippets"; 3 | import DisplayScreen from "@/components/screen/DisplayScreen"; 4 | import { supabaseServer } from "@/lib/supabase/supabaseServer"; 5 | import NotFound from "@/app/Not-found"; 6 | import DataLoading from "../loading"; 7 | type Props = { 8 | params: { 9 | slug: string; 10 | }; 11 | }; 12 | export async function generateMetadata({ params }: Props) { 13 | const path = await supabaseServer() 14 | .from("links") 15 | .select("*") 16 | .eq("path", params.slug); 17 | if (path.data?.length === 0) return NotFound(); 18 | 19 | const data = decodeData(path?.data?.[0].link); 20 | if (!data) { 21 | return {}; 22 | } 23 | 24 | return { 25 | title: `${data.n}'s`, 26 | description: `Find all of ${data.n}'s links in one place.`, 27 | openGraph: { 28 | type: "website", 29 | locale: "en_US", 30 | url: "https://itZmyLink.vercel.app", 31 | title: `${data.n}'s - itZmyLink`, 32 | description: `Find all of ${data.n}'s links in one place.`, 33 | images: `https://itZmyLink.vercel.app/api/og?data=${encodeURI(data.n)}`, 34 | siteName: `${data.n}'s - itZmyLink`, 35 | }, 36 | twitter: { 37 | card: "summary_large_image", 38 | title: `${data.n} - itZmyLink`, 39 | description: `Find all of ${data.n}'s links in one place.`, 40 | images: `https://itZmyLink.vercel.app/api/og?data=${encodeURI(data.n)}`, 41 | creator: "@Taquiimam14", 42 | }, 43 | }; 44 | } 45 | 46 | const linkLandingPage: React.FC = async ({ params }) => { 47 | const path = await supabaseServer() 48 | .from("links") 49 | .select("*") 50 | .eq("path", params.slug); 51 | if (path.data?.length === 0) return NotFound(); 52 | 53 | const data = decodeData(path?.data?.[0].link); 54 | const selectedBgOption = data 55 | ? BACKGROUND_OPTIONS.find((option) => option.code === data.bg) 56 | : null; 57 | 58 | const selectedBgComponent = selectedBgOption 59 | ? selectedBgOption.component 60 | : null; 61 | 62 | return ( 63 | <> 64 |
65 | {selectedBgComponent} 66 |
67 |
68 | {data ? : } 69 |
70 | 71 | ); 72 | }; 73 | 74 | export default linkLandingPage; 75 | -------------------------------------------------------------------------------- /src/app/1/error.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { Button } from "@/components/ui/button" 4 | import Link from "next/link" 5 | 6 | 7 | export default function Error() { 8 | return ( 9 |
10 |
11 |

Sorry, there is mistake in url.

12 | 15 |
16 |
17 | ) 18 | } -------------------------------------------------------------------------------- /src/app/1/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from '@/components/ui/skeleton' 2 | import React from 'react' 3 | 4 | export default function DataLoading() { 5 | return ( 6 |
7 |
8 | 9 | 10 | 11 |
12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 26 |
27 | ) 28 | } -------------------------------------------------------------------------------- /src/app/1/page.tsx: -------------------------------------------------------------------------------- 1 | import { decodeData } from "@/lib/utils"; 2 | import NotFound from "../Not-found"; 3 | import { BACKGROUND_OPTIONS } from "@/components/Background/BgSnippets"; 4 | import DataLoading from "./loading"; 5 | import DisplayScreen from "@/components/screen/DisplayScreen"; 6 | 7 | export async function generateMetadata({ searchParams }: any) { 8 | const data = decodeData(searchParams.data); 9 | 10 | if (!data) { 11 | return {}; 12 | } 13 | 14 | return { 15 | title: `${data.n}'s`, 16 | description: `Find all of ${data.n}'s links in one place.`, 17 | openGraph: { 18 | type: "website", 19 | locale: "en_US", 20 | url: "https://itZmyLink.vercel.app", 21 | title: `${data.n}'s - itZmyLink`, 22 | description: `Find all of ${data.n}'s links in one place.`, 23 | images: `https://itZmyLink.vercel.app/api/og?data=${encodeURI(data.n)}`, 24 | siteName: `${data.n}'s - itZmyLink`, 25 | }, 26 | twitter: { 27 | card: "summary_large_image", 28 | title: `${data.n} - itZmyLink`, 29 | description: `Find all of ${data.n}'s links in one place.`, 30 | images: `https://itZmyLink.vercel.app/api/og?data=${encodeURI(data.n)}`, 31 | creator: "@Taquiimam14", 32 | }, 33 | }; 34 | } 35 | 36 | const linkLandingPage = ({ searchParams }: any) => { 37 | if (!searchParams.data) NotFound(); 38 | 39 | const data = decodeData(searchParams.data); 40 | 41 | const selectedBgOption = data 42 | ? BACKGROUND_OPTIONS.find((option) => option.code === data.bg) 43 | : null; 44 | 45 | const selectedBgComponent = selectedBgOption 46 | ? selectedBgOption.component 47 | : null; 48 | 49 | return ( 50 | <> 51 |
52 | {selectedBgComponent} 53 |
54 |
55 | {data ? : } 56 |
57 | 58 | ); 59 | }; 60 | 61 | export default linkLandingPage; 62 | -------------------------------------------------------------------------------- /src/app/Not-found.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@/components/ui/button' 2 | import Link from 'next/link' 3 | import React from 'react' 4 | 5 | export default function NotFound() { 6 | return ( 7 |
8 |
9 |
10 |

11 | 12 | 13 | 14 |

15 |

Opps! Page not found.

16 |

Organize your links with itZmyLink and make them easy to find and share.

17 |
18 | 23 |
24 |
25 |
26 |
27 | ) 28 | } -------------------------------------------------------------------------------- /src/app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import { handlers } from "@/components/auth"; 2 | 3 | export const {GET, POST} = handlers; -------------------------------------------------------------------------------- /src/app/api/og/route.tsx: -------------------------------------------------------------------------------- 1 | import { NextRequest } from "next/server"; 2 | import type { ServerRuntime} from "next" 3 | import { ImageResponse } from "@vercel/og" 4 | 5 | export const runtime: ServerRuntime = "edge" 6 | 7 | function truncateString({ str, maxLength }: { str: string, maxLength: number }) { 8 | if (str.length > maxLength) { 9 | return str.substring(0, maxLength); 10 | } 11 | return str; 12 | } 13 | 14 | export async function GET(req: NextRequest) { 15 | try { 16 | const url = new URL(req.url) 17 | const { data } = Object.fromEntries(url.searchParams) 18 | 19 | if (!data || !data.trim()) { 20 | return new Response("Data not in URL or empty.", { 21 | status: 400, 22 | }); 23 | } 24 | const maxNameLength = 25; 25 | let name = data.trim(); 26 | name = truncateString({ str: name, maxLength: maxNameLength }); 27 | return new ImageResponse( 28 | ( 29 |
30 |
31 | {name}'s - itZmyLink 32 |
33 |
34 | ), 35 | { 36 | width: 1200, 37 | height: 630, 38 | } 39 | ) 40 | } catch (error) { 41 | console.error(error); 42 | return new Response(`Failed to generate the image`, { 43 | status: 500, 44 | }) 45 | } 46 | } -------------------------------------------------------------------------------- /src/app/auth/signin/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from "next/link" 3 | 4 | import { 5 | Card, 6 | CardContent, 7 | CardDescription, 8 | CardHeader, 9 | CardTitle, 10 | } from "@/components/ui/card" 11 | import LoginGoogleBtn from '@/components/forms/LoginGoogleBtn'; 12 | type Props = { 13 | searchParams:{ 14 | callbackUrl?:string 15 | } 16 | }; 17 | const page: React.FC = async({searchParams}) => { 18 | return ( 19 | 20 |
21 | 22 | 23 | Welcome Back 24 | 25 | Select your registered email to continue. 26 | 27 | 28 | 29 |
30 | 31 | 32 |
33 |
34 | Don't have an account?{" "} 35 | 36 | Sign up 37 | 38 |
39 |
40 |
41 |
42 | ) 43 | } 44 | 45 | export default page -------------------------------------------------------------------------------- /src/app/create/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from '@/components/ui/skeleton' 2 | import React from 'react' 3 | 4 | export default function SiteLoading() { 5 | return ( 6 |
7 |
8 |
9 | 10 | 11 | 12 |
13 |
14 |
15 | 16 |
17 |
18 | ) 19 | } -------------------------------------------------------------------------------- /src/app/create/page.tsx: -------------------------------------------------------------------------------- 1 | import PreviewButton from "@/components/ActionButtons/ResponsivePreviewBtn"; 2 | import HomeEditor from "@/components/HomeEditor"; 3 | 4 | 5 | const CreateLink = async() => { 6 | 7 | 8 | 9 | 10 | return ( 11 |
12 | 13 | {/* EDITING PART */} 14 |
15 | 16 |
17 |
18 | 19 |
20 |
21 | ); 22 | }; 23 | 24 | export default CreateLink; 25 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taqui-786/itZmyLink/a216201f8bec62db52463abda5cb5b3f7076c582/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | 6 | 7 | 8 | 9 | @layer base { 10 | :root { 11 | --background: 0 0% 100%; 12 | --foreground: 20 14.3% 4.1%; 13 | --card: 0 0% 100%; 14 | --card-foreground: 20 14.3% 4.1%; 15 | --popover: 0 0% 100%; 16 | --popover-foreground: 20 14.3% 4.1%; 17 | --primary: 47.9 95.8% 53.1%; 18 | --primary-foreground: 26 83.3% 14.1%; 19 | --secondary: 60 4.8% 95.9%; 20 | --secondary-foreground: 24 9.8% 10%; 21 | --muted: 60 4.8% 95.9%; 22 | --muted-foreground: 25 5.3% 44.7%; 23 | --accent: 60 4.8% 95.9%; 24 | --accent-foreground: 24 9.8% 10%; 25 | --destructive: 0 84.2% 60.2%; 26 | --destructive-foreground: 60 9.1% 97.8%; 27 | --border: 20 5.9% 90%; 28 | --input: 20 5.9% 90%; 29 | --ring: 20 14.3% 4.1%; 30 | --radius: 0.5rem; 31 | } 32 | 33 | .dark { 34 | --background: 20 14.3% 4.1%; 35 | --foreground: 60 9.1% 97.8%; 36 | --card: 20 14.3% 4.1%; 37 | --card-foreground: 60 9.1% 97.8%; 38 | --popover: 20 14.3% 4.1%; 39 | --popover-foreground: 60 9.1% 97.8%; 40 | --primary: 47.9 95.8% 53.1%; 41 | --primary-foreground: 26 83.3% 14.1%; 42 | --secondary: 12 6.5% 15.1%; 43 | --secondary-foreground: 60 9.1% 97.8%; 44 | --muted: 12 6.5% 15.1%; 45 | --muted-foreground: 24 5.4% 63.9%; 46 | --accent: 12 6.5% 15.1%; 47 | --accent-foreground: 60 9.1% 97.8%; 48 | --destructive: 0 62.8% 30.6%; 49 | --destructive-foreground: 60 9.1% 97.8%; 50 | --border: 12 6.5% 15.1%; 51 | --input: 12 6.5% 15.1%; 52 | --ring: 35.5 91.7% 32.9%; 53 | } 54 | } 55 | 56 | 57 | 58 | 59 | @layer base { 60 | * { 61 | @apply border-border; 62 | } 63 | body { 64 | @apply bg-background text-foreground; 65 | } 66 | } 67 | html, body, :root{ 68 | height: 100%; 69 | } 70 | .hide_scrollbar::-webkit-scrollbar { 71 | display: none; 72 | -ms-overflow-style: none; /* IE and Edge */ 73 | scrollbar-width: none; /* Firefox */ 74 | } 75 | 76 | 77 | body::-webkit-scrollbar { 78 | display: none; 79 | -ms-overflow-style: none; /* IE and Edge */ 80 | scrollbar-width: none; /* Firefox */ 81 | } 82 | /* MOBILE TEMPLATE DESIGN */ 83 | .container{ 84 | max-width: 600px; 85 | margin: 4px auto; 86 | } 87 | 88 | .phone{ 89 | position: relative; 90 | background: #1e1e1e; 91 | height: 625px; 92 | width: 345px; 93 | border-radius: 25px; 94 | margin: 0 auto; 95 | } 96 | 97 | .camera{ 98 | position: absolute; 99 | background: #7A7A7A; 100 | height: 15px; 101 | width: 15px; 102 | border-radius: 15px; 103 | top: 7px; 104 | left: 166px; 105 | z-index: +9; 106 | } 107 | 108 | .speaker{ 109 | position: absolute; 110 | background: #000; 111 | height: 23px; 112 | width: 45px; 113 | border-radius: 0 0 27px 27px; 114 | top: 7px; 115 | left: 151px; 116 | z-index: +8; 117 | } 118 | 119 | .sleep-button{ 120 | position: absolute; 121 | background: #1e1e1e; 122 | height: 35px; 123 | width: 3px; 124 | border-top-right-radius: 3px 3px; 125 | border-bottom-right-radius: 3px 3px; 126 | top: 111px; 127 | left: 345px; 128 | } 129 | 130 | .silent-switch{ 131 | position: absolute; 132 | background: #1e1e1e; 133 | height: 25px; 134 | width: 3px; 135 | border-top-left-radius: 3px 3px; 136 | border-bottom-left-radius: 3px 3px; 137 | top: 60px; 138 | left: -3px; 139 | } 140 | 141 | .volume{ 142 | position: absolute; 143 | background: #1e1e1e; 144 | width: 3px; 145 | height: 35px; 146 | border-top-left-radius: 3px 3px; 147 | border-bottom-left-radius: 3px 3px; 148 | left: -3px; 149 | } 150 | 151 | .up{ 152 | top: 105px; 153 | } 154 | 155 | .down{ 156 | top: 145px; 157 | } 158 | 159 | .screen{ 160 | position: absolute; 161 | overflow: hidden; 162 | height: 593px; 163 | width: 325px; 164 | top: 10px; 165 | left: 10px; 166 | border-radius: 15px; 167 | } 168 | 169 | .home-button{ 170 | position: absolute; 171 | border: 1px solid #7A7A7A; 172 | height: 35px; 173 | width: 35px; 174 | border-radius: 25px; 175 | bottom: 12px; 176 | left: 50%; 177 | margin-left: -18px; 178 | display: none; 179 | } 180 | 181 | 182 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "./globals.css"; 2 | import type { Metadata } from "next"; 3 | import { siteConfig } from "./page"; 4 | import { Inter } from "next/font/google"; 5 | import { Providers } from "@/components/Provider"; 6 | 7 | const inter = Inter({ subsets: ["latin"] }); 8 | 9 | // Original source: https://github.com/sadmann7/skateshop/blob/main/src/app/layout.tsx 10 | export const metadata: Metadata = { 11 | metadataBase: new URL("https://itzmylink.vercel.app"), 12 | title: { 13 | default: siteConfig.name, 14 | template: `%s - itZmyLink`, 15 | }, 16 | description: siteConfig.description, 17 | 18 | // added new keywords for seo 19 | keywords: [ 20 | "bitly url shortener", 21 | "bitly link shortener", 22 | "link shortener", 23 | "url shortener", 24 | "bitly link", 25 | "tinyurls", 26 | "all in one link", 27 | "free url shortener", 28 | "linknode", 29 | "onelink", 30 | "social links", 31 | "free linktree", 32 | "link in bio", 33 | "short my url", 34 | "my links", 35 | "itzmylink", 36 | "itZmyLink", 37 | "mtLink", 38 | ], 39 | authors: [ 40 | { 41 | name: "Taqui Imam", 42 | url: "https://github.com/taqui-786", 43 | }, 44 | ], 45 | creator: "Taqui imam", 46 | 47 | openGraph: { 48 | type: "website", 49 | locale: "en_US", 50 | url: siteConfig.url, 51 | title: siteConfig.name, 52 | description: siteConfig.description, 53 | images: [`${siteConfig.url}/og-image.png`], 54 | siteName: siteConfig.name, 55 | }, 56 | twitter: { 57 | card: "summary_large_image", 58 | title: siteConfig.name, 59 | description: siteConfig.description, 60 | images: [`${siteConfig.url}/og-image.png`], 61 | creator: "@Taquiimam14", 62 | }, 63 | icons: { 64 | icon: "/favicon.ico", 65 | }, 66 | }; 67 | 68 | export default function RootLayout({ 69 | children, 70 | }: { 71 | children: React.ReactNode; 72 | }) { 73 | return ( 74 | 75 | 76 |
77 | 78 | {/* */} 79 |
{children}
80 | {/*
*/} 81 | 82 |
83 | 84 | 85 | ); 86 | } 87 | -------------------------------------------------------------------------------- /src/app/links/page.tsx: -------------------------------------------------------------------------------- 1 | import { Card, CardContent } from "@/components/ui/card" 2 | import { Button, buttonVariants } from "@/components/ui/button" 3 | import { Construction } from "lucide-react" 4 | import Link from "next/link" 5 | import { cn } from "@/lib/utils" 6 | 7 | export default function Component() { 8 | return ( 9 |
10 | 11 | 12 | 13 |

14 | Under Construction 15 |

16 |

17 | This page will be available soon. 18 |

19 | Go Home 20 |
21 |
22 |
23 | ) 24 | } -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import landingPageImg from '../../public/landingPage.png' 3 | import Image from "next/image"; 4 | import { auth } from "@/components/auth"; 5 | import { redirect } from "next/navigation"; 6 | 7 | 8 | export const siteConfig = { 9 | name: "itZmyLink - one page, many links.", 10 | description: "itZmyLink help you to Create a personalized page to showcase all your social media profiles in one place.", 11 | ogImage: "https://itzmylink.vercel.app/og-image.png", 12 | url: "https://itzmylink.vercel.app", 13 | } 14 | export default async function Home() { 15 | const session = await auth() 16 | if(session?.user) return redirect('/create') 17 | return ( 18 |
19 |
20 | 21 | 22 |
23 |
24 |
25 |
26 |

Simplify your online presence!

27 |

Take control of your links with itzmylink

28 |

Your all-in-one link management solution

29 | 30 | 31 | Get your Link 32 | 33 | 34 | 35 | 36 | 37 |

Already joined us? Log in

38 |
39 | 40 |
41 | heroImg 42 |
43 |
44 |
45 |
46 |
47 | 48 | 49 |
50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /src/app/preview/layout.tsx: -------------------------------------------------------------------------------- 1 | export default function PreviewLayout({ 2 | children, 3 | }: { 4 | children: React.ReactNode 5 | }) { 6 | return
7 | {children}
8 | } -------------------------------------------------------------------------------- /src/app/preview/page.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import NotFound from "../Not-found"; 3 | import { decodeData } from "@/lib/utils"; 4 | import ComputerMockup from "@/components/mockup/ComputerMockup"; 5 | import { BACKGROUND_OPTIONS } from "@/components/Background/BgSnippets"; 6 | import PreviewPage from "@/components/PreviewPage"; 7 | import PreviewFooter from "@/components/ActionButtons/PreviewFooter"; 8 | 9 | function page({ searchParams }: any) { 10 | if (!searchParams.data) NotFound(); 11 | 12 | const data = decodeData(searchParams.data); 13 | const selectedBgOption = data 14 | ? BACKGROUND_OPTIONS.find((option) => option.code === data.bg) 15 | : null; 16 | 17 | const selectedBgComponent = selectedBgOption 18 | ? selectedBgOption.component 19 | : null; 20 | return ( 21 | <> 22 | 23 |
24 | {selectedBgComponent} 25 |
26 | 27 | 28 |
29 | 30 | 31 | 32 | ); 33 | } 34 | 35 | export default page; 36 | -------------------------------------------------------------------------------- /src/components/ActionButtons/CustomLinkDialog.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState, useCallback } from "react"; 4 | import { 5 | Dialog, 6 | DialogContent, 7 | DialogDescription, 8 | DialogHeader, 9 | DialogTitle, 10 | DialogTrigger, 11 | } from "@/components/ui/dialog"; 12 | import { 13 | AlertDialog, 14 | AlertDialogAction, 15 | AlertDialogCancel, 16 | AlertDialogContent, 17 | AlertDialogDescription, 18 | AlertDialogFooter, 19 | AlertDialogHeader, 20 | AlertDialogTitle, 21 | } from "@/components/ui/alert-dialog"; 22 | import { Input } from "@/components/ui/input"; 23 | import { Button } from "@/components/ui/button"; 24 | import { ClipboardCopy, Check, PenSquare } from "lucide-react"; 25 | import { createCustomPath } from "@/lib/supabase/actions"; 26 | 27 | // Mock function to simulate checking if a path exists in the database 28 | const checkPathExists = async (path: string): Promise => { 29 | await new Promise((resolve) => setTimeout(resolve, 1000)); 30 | return path.toLowerCase().includes("taken"); 31 | }; 32 | 33 | export default function Component({localLink}:{localLink:string}) { 34 | const [customPath, setCustomPath] = useState(""); 35 | const [isLoading, setIsLoading] = useState(false); 36 | const [error, setError] = useState(null); 37 | const [success, setSuccess] = useState(null); 38 | const [isCopied, setIsCopied] = useState(false); 39 | const baseUrl = "itzmylink.vercel.app/1"; 40 | 41 | const fullUrl = `${baseUrl}/${customPath}`; 42 | 43 | const copyToClipboard = useCallback(() => { 44 | navigator.clipboard 45 | .writeText(fullUrl) 46 | .then(() => { 47 | setIsCopied(true); 48 | // toast({ 49 | // title: "Copied!", 50 | // description: "Link copied to clipboard", 51 | // }) 52 | setTimeout(() => setIsCopied(false), 2000); 53 | }) 54 | .catch((err) => { 55 | console.error("Failed to copy text: ", err); 56 | // toast({ 57 | // title: "Error", 58 | // description: "Failed to copy link", 59 | // variant: "destructive", 60 | // }) 61 | }); 62 | }, [fullUrl]); 63 | 64 | const handleCreate = useCallback(async () => { 65 | if (!customPath) { 66 | setError("Please enter a custom path"); 67 | return; 68 | } 69 | 70 | setIsLoading(true); 71 | setError(null); 72 | setSuccess(null); 73 | 74 | try { 75 | const exists = await checkPathExists(customPath); 76 | if (exists) { 77 | setError("This custom path is already taken. Please try another."); 78 | } else { 79 | const res = await createCustomPath(customPath,localLink); 80 | if (res.status !== "notAuthenticated") { 81 | res.status === 'created'? 82 | setSuccess(res.message): 83 | setError(res.message) 84 | } 85 | } 86 | } catch (err) { 87 | console.error("Error creating custom path:", err); 88 | setError("An error occurred while creating the custom path"); 89 | } finally { 90 | setIsLoading(false); 91 | } 92 | }, [customPath]); 93 | 94 | return ( 95 | <> 96 | 97 | 98 | 102 | 103 | 104 | 105 | Create Custom Link 106 | 107 | Enter your custom path to create a unique link. 108 | 109 | 110 |
111 |
112 | 113 | {baseUrl}/ 114 | 115 | setCustomPath(e.target.value)} 119 | className="col-span-3" 120 | placeholder="your-custom-path" 121 | /> 122 |
123 |
124 | 125 | 135 |
136 | 139 |
140 |
141 |
142 | 143 | 144 | 145 | 146 | {error ? "Already Exists 😅" : "Congratulation🎉"} 147 | 148 | {error || success} 149 | {success && ( 150 |
151 |

Your custom link:

152 |
153 | 154 | 168 |
169 |
170 | )} 171 |
172 |
173 | 174 | { 176 | setError(null); 177 | setSuccess(null); 178 | }} 179 | > 180 | Close 181 | 182 | {success && ( 183 | 184 | Copy URL 185 | 186 | )} 187 | 188 |
189 |
190 | 191 | ); 192 | } 193 | -------------------------------------------------------------------------------- /src/components/ActionButtons/DemoBtn.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | import React, { FC } from 'react' 3 | import { Button } from '@/components/ui/button' 4 | import { Play } from 'lucide-react' 5 | import { useData } from '@/lib/Context' 6 | 7 | interface DemoDataProps { } 8 | 9 | const DemoBtn: FC = ({ }) => { 10 | const { showDemo } = useData() 11 | return ( 12 | 16 | ) 17 | } 18 | 19 | export default DemoBtn -------------------------------------------------------------------------------- /src/components/ActionButtons/PreviewFooter.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | import { Button } from "../ui/button"; 4 | import { Check, Clipboard, Share } from "lucide-react"; 5 | 6 | import CustomLinkDialog from "./CustomLinkDialog"; 7 | 8 | function PreviewFooter({ 9 | MyLink, 10 | inputLink, 11 | }: { 12 | MyLink: any; 13 | inputLink: string; 14 | }) { 15 | const [hasCopied, setHasCopied] = React.useState(false); 16 | const copyToClipboard = React.useCallback(() => { 17 | const url = `${window.location.origin}/1?data=${inputLink}`; 18 | navigator.clipboard.writeText(url); 19 | return url; 20 | }, [MyLink]); 21 | return ( 22 |
23 |
24 |
25 | 39 | 58 | 59 |
60 |
61 |
62 | ); 63 | } 64 | 65 | export default PreviewFooter; 66 | -------------------------------------------------------------------------------- /src/components/ActionButtons/PublishBtn.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import React, { FC } from 'react' 4 | import { Button, buttonVariants } from '@/components/ui/button' 5 | import { cn, encodeData } from '@/lib/utils'; 6 | 7 | import { Check, Copy, Send, Share2 } from 'lucide-react'; 8 | import { 9 | Dialog, 10 | DialogContent, 11 | DialogDescription, 12 | DialogFooter, 13 | DialogHeader, 14 | DialogTitle, 15 | DialogTrigger 16 | } from '@/components/ui/dialog'; 17 | import { Input } from '@/components/ui/input'; 18 | import { DialogClose } from '@radix-ui/react-dialog'; 19 | import { useData } from '@/lib/Context'; 20 | import Link from 'next/link'; 21 | 22 | interface PublishProps { 23 | loggedIn:any 24 | } 25 | 26 | const Publish: FC = ({ loggedIn}) => { 27 | const { MyLink } = useData() 28 | 29 | const isEmpty = isEmptyValues(MyLink) 30 | const [inputLink, setInputLink] = React.useState("") 31 | const [hasCopied, setHasCopied] = React.useState(false) 32 | 33 | function isEmptyValues(obj: any) { 34 | for (let key in obj) { 35 | if (obj[key] !== "" && obj[key].length !== 0) { 36 | return false; 37 | } 38 | } 39 | return true; 40 | } 41 | 42 | const copyToClipboard = React.useCallback(() => { 43 | const url = `${window.location.origin}/1?data=${encodeData(MyLink)}`; 44 | navigator.clipboard.writeText(url) 45 | return url 46 | }, [MyLink]); 47 | 48 | React.useEffect(() => { 49 | setHasCopied(false); 50 | }, [MyLink]); 51 | 52 | function publish() { 53 | if (!isEmpty) { 54 | const getUrl = copyToClipboard() 55 | setInputLink(getUrl) 56 | } 57 | } 58 | 59 | return ( 60 | 61 | 62 | 66 | 67 | 68 | 69 | 70 | 71 | Share your page 72 | 73 | 74 | You can share your page with others and make it accessible from anywhere. 75 | 76 | 77 | 78 | {!isEmpty ? ( 79 | loggedIn ? 80 | Confirm to publish: 81 | Login To Continue 82 | // <> 83 | // 87 | // 88 | //
89 | // 102 | // 126 | //
127 | //
128 | // 129 | ) : ( 130 | 131 | 134 | 135 | )} 136 |
137 |
138 | ) 139 | } 140 | 141 | export default Publish -------------------------------------------------------------------------------- /src/components/ActionButtons/ResponsivePreviewBtn.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import React, { FC } from 'react' 4 | import { Button } from "@/components/ui/button" 5 | import { Drawer } from "vaul" 6 | import { useData } from '@/lib/Context' 7 | import { BACKGROUND_OPTIONS } from '../Background/BgSnippets' 8 | import { DrawerContent, DrawerTrigger } from '../ui/Drrawer' 9 | import DisplayData from '../DisplayData' 10 | 11 | interface PreviewButtonProps { } 12 | 13 | const PreviewButton: FC = () => { 14 | const { MyLink } = useData(); 15 | 16 | const [isEmpty, setIsEmpty] = React.useState(false) 17 | 18 | React.useEffect(() => { 19 | function isEmptyValues(obj: any) { 20 | for (let key in obj) { 21 | if (obj[key] !== "" && obj[key].length !== 0) { 22 | return false; 23 | } 24 | } 25 | return true; 26 | } 27 | setIsEmpty(isEmptyValues(MyLink)) 28 | }, [MyLink]) 29 | 30 | const selectedBgOption = MyLink 31 | ? BACKGROUND_OPTIONS.find((option) => option.code === MyLink.bg) 32 | : null; 33 | 34 | const selectedBgComponent = selectedBgOption ? selectedBgOption.component : null; 35 | 36 | return ( 37 |
38 | 39 | 40 | 43 | 44 | 45 | { 46 | isEmpty 47 | ?
Nothing to show...
48 | : ( 49 | <> 50 | {!isEmpty && selectedBgComponent} 51 |
52 | 53 |
54 | 55 | ) 56 | } 57 |
58 |
59 |
60 | ) 61 | } 62 | 63 | export default PreviewButton -------------------------------------------------------------------------------- /src/components/AdditionalLinkCards.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react' 2 | import { Icon } from '@iconify/react'; 3 | 4 | interface AdditionalLinkCardProps { 5 | label: string; 6 | url: string; 7 | icon?: string; 8 | } 9 | const AdditionalLinkCard: FC = ({ label, url, icon }) => { 10 | return ( 11 |
  • 12 | {label && url && ( 13 | 14 |
    15 |
    16 | {icon ? ( 17 | 18 | ) : ( 19 | 20 | )} 21 |
    22 |
    23 |

    24 | {label} 25 |

    26 |
    27 |
    28 |
    29 | )} 30 |
  • 31 | ) 32 | } 33 | 34 | export default AdditionalLinkCard -------------------------------------------------------------------------------- /src/components/AdditionalLinkForm.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import React, { FC } from 'react' 4 | import { 5 | Card, 6 | CardContent, 7 | CardDescription, 8 | CardHeader, 9 | CardTitle 10 | } from '@/components/ui/card' 11 | import { Button } from '@/components/ui/button' 12 | import { 13 | DndContext, 14 | closestCenter, 15 | KeyboardSensor, 16 | PointerSensor, 17 | useSensor, 18 | useSensors, 19 | } from '@dnd-kit/core'; 20 | import { 21 | SortableContext, 22 | sortableKeyboardCoordinates, 23 | verticalListSortingStrategy, 24 | } from '@dnd-kit/sortable'; 25 | import { 26 | restrictToVerticalAxis, 27 | restrictToParentElement, 28 | } from '@dnd-kit/modifiers'; 29 | import {SortableLinks} from '@/components/ui/SortableLink' 30 | import { useData } from '@/lib/Context'; 31 | 32 | 33 | 34 | const AdditionalLinkForm = () =>{ 35 | const sensors = useSensors( 36 | useSensor(PointerSensor), 37 | useSensor(KeyboardSensor, { 38 | coordinateGetter: sortableKeyboardCoordinates, 39 | }), 40 | ); 41 | 42 | const scrollDownRef = React.useRef(null) 43 | const [shouldScroll, setShouldScroll] = React.useState(false); 44 | const { MyLink, addNewData, updateIndex } = useData(); 45 | 46 | const addLinkDetailForm = () => { 47 | const newLink: AdditionalLinkProps = { id: Date.now(), i: '', l: '', u: '' }; 48 | addNewData(newLink); 49 | setShouldScroll(true); 50 | }; 51 | 52 | React.useEffect(() => { 53 | if (shouldScroll && scrollDownRef.current) { 54 | scrollDownRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' }); 55 | setShouldScroll(false); 56 | } 57 | }, [shouldScroll]); 58 | 59 | // "handleDragEnd" function written by chatGPT 60 | function handleDragEnd(event: any) { 61 | const { active, over } = event; 62 | 63 | if (active.id !== over.id) { 64 | const updatedItems = [...MyLink.ls]; // Accessing items from the context 65 | const draggedItem: any = updatedItems.find((item) => item.id === active.id); 66 | const targetItem: any = updatedItems.find((item) => item.id === over.id); 67 | 68 | const draggedIndex = updatedItems.indexOf(draggedItem); 69 | const targetIndex = updatedItems.indexOf(targetItem); 70 | 71 | if (draggedIndex !== -1 && targetIndex !== -1) { 72 | // Remove the dragged item from its original position 73 | updatedItems.splice(draggedIndex, 1); 74 | // Insert the dragged item at the target position 75 | updatedItems.splice(targetIndex, 0, draggedItem); 76 | 77 | updateIndex(updatedItems); 78 | } 79 | } 80 | } 81 | 82 | return( 83 | <> 84 | 85 | 86 | 87 | Extra Links 88 | {/* */} 89 | 90 | 91 | Enter your additional link details here. 92 | 93 | 94 | 95 | 101 | link.id)} 103 | strategy={verticalListSortingStrategy} 104 | > 105 | {MyLink.ls.map((link, index) => { 106 | return 107 | })} 108 | 109 | 110 | 116 | 117 | 118 |
    119 | 120 | ) 121 | } 122 | 123 | 124 | export default AdditionalLinkForm; -------------------------------------------------------------------------------- /src/components/Animation/BlurText.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { motion } from "framer-motion"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | 7 | interface BlurIntProps { 8 | word: string; 9 | className?: string; 10 | variant?: { 11 | hidden: { filter: string; opacity: number }; 12 | visible: { filter: string; opacity: number }; 13 | }; 14 | duration?: number; 15 | } 16 | const BlurIn = ({ word, className, variant, duration = 1 }: BlurIntProps) => { 17 | const defaultVariants = { 18 | hidden: { filter: "blur(10px)", opacity: 0 }, 19 | visible: { filter: "blur(0px)", opacity: 1 }, 20 | }; 21 | const combinedVariants = variant || defaultVariants; 22 | 23 | return ( 24 | 34 | {word} 35 | 36 | ); 37 | }; 38 | 39 | export default BlurIn; 40 | -------------------------------------------------------------------------------- /src/components/Animation/FlipText.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { AnimatePresence, motion, Variants } from "framer-motion"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | 7 | interface SlightFlipProps { 8 | word: string; 9 | duration?: number; 10 | delayMultiple?: number; 11 | framerProps?: Variants; 12 | className?: string; 13 | } 14 | 15 | export default function SlightFlip({ 16 | word, 17 | duration = 0.5, 18 | delayMultiple = 0.08, 19 | framerProps = { 20 | hidden: { rotateX: -90, opacity: 0 }, 21 | visible: { rotateX: 0, opacity: 1 }, 22 | }, 23 | className, 24 | }: SlightFlipProps) { 25 | return ( 26 |
    27 | 28 | {word.split("").map((char, i) => ( 29 | 38 | {char} 39 | 40 | ))} 41 | 42 |
    43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /src/components/Animation/FramerWrapper.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useState } from "react"; 3 | import { motion, AnimatePresence } from "framer-motion"; 4 | type FramerMotionProps = { 5 | children: React.ReactNode, 6 | className?:any, 7 | y?:number 8 | x?:number 9 | delay?:number 10 | duration?: number 11 | scale?:number 12 | } 13 | function FramerWrapper({children,delay = 0.25 ,y = 15, x = 0,duration = 0.20,scale = 0, className}:FramerMotionProps) { 14 | const [animateConfig, setAnimateConfig] = useState({ 15 | opacity:1, y:0, x:0 16 | }) 17 | return ( 18 | 19 | {children} 26 | 27 | ); 28 | } 29 | 30 | export default FramerWrapper; -------------------------------------------------------------------------------- /src/components/Animation/TextEffect.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { motion, Variants } from 'framer-motion'; 3 | import React from 'react'; 4 | 5 | type PresetType = 'blur' | 'shake' | 'scale' | 'fade' | 'slide'; 6 | 7 | type TextEffectProps = { 8 | children: string; 9 | per?: 'word' | 'char'; 10 | as?: keyof JSX.IntrinsicElements; 11 | variants?: { 12 | container?: Variants; 13 | item?: Variants; 14 | }; 15 | className?: string; 16 | preset?: PresetType; 17 | }; 18 | 19 | const defaultContainerVariants: Variants = { 20 | hidden: { opacity: 0 }, 21 | visible: { 22 | opacity: 1, 23 | transition: { 24 | staggerChildren: 0.05, 25 | }, 26 | }, 27 | }; 28 | 29 | const defaultItemVariants: Variants = { 30 | hidden: { opacity: 0 }, 31 | visible: { 32 | opacity: 1, 33 | }, 34 | }; 35 | 36 | const presetVariants: Record< 37 | PresetType, 38 | { container: Variants; item: Variants } 39 | > = { 40 | blur: { 41 | container: defaultContainerVariants, 42 | item: { 43 | hidden: { opacity: 0, filter: 'blur(12px)' }, 44 | visible: { opacity: 1, filter: 'blur(0px)' }, 45 | }, 46 | }, 47 | shake: { 48 | container: defaultContainerVariants, 49 | item: { 50 | hidden: { x: 0 }, 51 | visible: { x: [-5, 5, -5, 5, 0], transition: { duration: 0.5 } }, 52 | }, 53 | }, 54 | scale: { 55 | container: defaultContainerVariants, 56 | item: { 57 | hidden: { opacity: 0, scale: 0 }, 58 | visible: { opacity: 1, scale: 1 }, 59 | }, 60 | }, 61 | fade: { 62 | container: defaultContainerVariants, 63 | item: { 64 | hidden: { opacity: 0 }, 65 | visible: { opacity: 1 }, 66 | }, 67 | }, 68 | slide: { 69 | container: defaultContainerVariants, 70 | item: { 71 | hidden: { opacity: 0, y: 20 }, 72 | visible: { opacity: 1, y: 0 }, 73 | }, 74 | }, 75 | }; 76 | 77 | const AnimationComponent: React.FC<{ 78 | word: string; 79 | variants: Variants; 80 | per: 'word' | 'char'; 81 | }> = React.memo(({ word, variants, per }) => { 82 | if (per === 'word') { 83 | return ( 84 | 91 | ); 92 | } 93 | 94 | return ( 95 | 96 | {word.split('').map((char, charIndex) => ( 97 | 105 | ))} 106 | 107 | ); 108 | }); 109 | 110 | AnimationComponent.displayName = 'AnimationComponent'; 111 | 112 | export function TextEffect({ 113 | children, 114 | per = 'word', 115 | as = 'p', 116 | variants, 117 | className, 118 | preset, 119 | }: TextEffectProps) { 120 | const words = children.split(/(\S+)/); 121 | const MotionTag = motion[as as keyof typeof motion]; 122 | const selectedVariants = preset 123 | ? presetVariants[preset] 124 | : { container: defaultContainerVariants, item: defaultItemVariants }; 125 | const containerVariants = variants?.container || selectedVariants.container; 126 | const itemVariants = variants?.item || selectedVariants.item; 127 | 128 | return ( 129 | 136 | {words.map((word, wordIndex) => ( 137 | 143 | ))} 144 | 145 | ); 146 | } 147 | -------------------------------------------------------------------------------- /src/components/Background/BackgroundCards.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from '@/components/ui/button'; 3 | import { cn } from '@/lib/utils'; 4 | import { BACKGROUND_OPTIONS } from './BgSnippets'; 5 | import { useData } from '@/lib/Context'; 6 | 7 | type BackgroundCardProps = {}; 8 | 9 | const BackgroundCard: React.FC = () => { 10 | const { MyLink,selectBackground} = useData() 11 | return ( 12 | 13 |
    14 | {BACKGROUND_OPTIONS.map((background, index) => { 15 | return ( 16 | 28 | ); 29 | })} 30 |
    31 | 32 | ); 33 | }; 34 | 35 | export default BackgroundCard -------------------------------------------------------------------------------- /src/components/Background/BackgroundForm.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import React from 'react' 4 | import { 5 | Card, 6 | CardContent, 7 | CardDescription, 8 | CardHeader, 9 | CardTitle 10 | } from '@/components/ui/card' 11 | import BackgroundCard from './BackgroundCards' 12 | 13 | export default function Background() { 14 | return ( 15 | 16 | 17 | 18 | Background 19 | 20 | 21 | Customize your background theme from here. 22 | 23 | 24 | 25 | 26 | 27 | 28 | ) 29 | } -------------------------------------------------------------------------------- /src/components/Background/BgSnippets.tsx: -------------------------------------------------------------------------------- 1 | // original source: https://github.com/ibelick/background-snippets/blob/main/app/components/background.tsx 2 | 3 | const BgTheme1 = () => { 4 | return ( 5 |
    6 | ); 7 | }; 8 | 9 | const BgTheme8 = () => { 10 | return ( 11 |
    12 | ); 13 | }; 14 | 15 | const BgTheme2 = () => { 16 | return ( 17 |
    18 | ); 19 | }; 20 | 21 | const BgTheme9 = () => { 22 | return ( 23 |
    24 | ); 25 | }; 26 | 27 | const BgTheme7 = () => { 28 | return ( 29 |
    30 | ); 31 | }; 32 | 33 | const BgTheme10 = () => { 34 | return ( 35 |
    36 | ); 37 | }; 38 | const BgTheme11 = () => { 39 | return ( 40 |
    41 | ); 42 | }; 43 | 44 | const BgTheme12 = () => { 45 | return ( 46 |
    47 | ); 48 | }; 49 | 50 | 51 | const BgTheme4 = () => { 52 | return ( 53 |
    54 | ); 55 | }; 56 | 57 | const BgTheme5 = () => { 58 | return ( 59 |
    60 | ); 61 | }; 62 | 63 | const BgTheme6 = () => { 64 | return ( 65 |
    66 | ); 67 | }; 68 | 69 | const BgTheme3 = () => { 70 | return ( 71 |
    72 | ); 73 | }; 74 | 75 | 76 | 77 | export const BACKGROUND_OPTIONS = [ 78 | { 79 | code: '#FFFFFF', 80 | component: , 81 | name: 'WhiteCanvas' 82 | }, 83 | { 84 | code: '#4F4F4F', 85 | component: , 86 | name: 'ShadowyGray' 87 | }, 88 | { 89 | code: '#C9EBFF', 90 | component: , 91 | name: 'LinearSky', 92 | }, 93 | { 94 | code: '#E6E7EB', 95 | component: , 96 | name: 'SubtleGrayDots', 97 | }, 98 | { 99 | code: '#FF00FF', 100 | component: , 101 | name: 'BlurredFuchsia', 102 | }, 103 | { 104 | code: '#E5E7EB', 105 | component: , 106 | name: 'MaskedGray', 107 | }, 108 | { 109 | code: '#808080', 110 | component: , 111 | name: 'GradientGrid', 112 | }, 113 | { 114 | code: '#F0F0F0', 115 | component: , 116 | name: 'LightGrayLines', 117 | }, 118 | { 119 | code: '#00A3FF', 120 | component: , 121 | name: 'RadiantBlue', 122 | }, 123 | { 124 | code: '#AD6DF4', 125 | component: , 126 | name: 'GradientOrb', 127 | }, 128 | { 129 | code: '#63E', 130 | component: , 131 | name: 'RadialHalo', 132 | }, 133 | { 134 | code: '#D5C5FF', 135 | component: , 136 | name: 'VividCircles', 137 | } 138 | 139 | 140 | ] as const; -------------------------------------------------------------------------------- /src/components/DisplayData.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { ImageIcon } from "lucide-react"; 3 | import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar"; 4 | import { Icon } from "@iconify/react/dist/iconify.js"; 5 | import AdditionalLinkCard from "./AdditionalLinkCards"; 6 | 7 | const DisplayData:React.FC = ({ myData }) => { 8 | const EmptySocialLiks = 9 | !myData.fb && 10 | !myData.ig && 11 | !myData.tg && 12 | !myData.em && 13 | !myData.tw && 14 | !myData.lk && 15 | !myData.yt && 16 | !myData.gt && 17 | !myData.wh; 18 | 19 | const iconMap: Record = { 20 | fb: "ph:facebook-logo-duotone", 21 | tw: "ph:twitter-logo-duotone", 22 | ig: "ph:instagram-logo-duotone", 23 | tg: "ph:telegram-logo-duotone", 24 | wh: "ph:whatsapp-logo-duotone", 25 | yt: "ph:youtube-logo-duotone", 26 | em: "ph:envelope-duotone", 27 | gt: "ph:github-logo-duotone", 28 | lk: "ph:linkedin-logo-duotone", 29 | }; 30 | return ( 31 | <> 32 |
    33 |
    34 | {/* USER IMAGE */} 35 | 36 | 41 | 42 | 43 | 44 | 45 | {/* USER NAME AND BIO */} 46 |

    {myData.n}

    47 |

    {myData.a}

    48 |
    49 | {/* {!EmptySocialLiks && ( */} 50 |
    51 | { Object.entries(myData).map(([key, value]) => { 52 | 53 | 54 | const excludedKeys = ["i", "n", "a", "bg"]; 55 | if (key !== "ls" && value && !excludedKeys.includes(key)) { 56 | const propIcon = iconMap[key as keyof typeof iconMap]; 57 | if (key === "em") { 58 | // Handle email link generation 59 | return ( 60 | 61 | 62 | 63 | 64 | 65 | ); 66 | } else if (key === "wh") { 67 | // Handle WhatsApp link generation 68 | const whatsappValue = value.startsWith("https://wa.me/") 69 | ? value // If it already starts with the correct prefix 70 | : `https://wa.me/${value}`; 71 | 72 | return ( 73 | 74 | 75 | 76 | 77 | 78 | ); 79 | } else { 80 | return ( 81 | 82 | 83 | 84 | 85 | 86 | ); 87 | } 88 | } 89 | return null; 90 | })} 91 |
    92 | 93 | {/* )} */} 94 |
      95 | {myData.ls && myData.ls.map((link, id) => ( 96 | 102 | ))} 103 |
    104 |
    105 | 106 | ); 107 | }; 108 | 109 | export default DisplayData; 110 | -------------------------------------------------------------------------------- /src/components/HomeEditor.tsx: -------------------------------------------------------------------------------- 1 | import PersonalInfo from "./PersonalInfo"; 2 | import SocialLinksForm from "./SocialLinkForm"; 3 | import Background from "./Background/BackgroundForm"; 4 | import AdditionalLinkForm from "./AdditionalLinkForm"; 5 | import Publish from "./ActionButtons/PublishBtn"; 6 | import DemoBtn from "./ActionButtons/DemoBtn"; 7 | import Link from "next/link"; 8 | import { ShoppingCart ,Link2, Github, Coffee} from "lucide-react"; 9 | import { buttonVariants } from "./ui/button"; 10 | import Navbar from "./Navbar"; 11 | import { auth } from "./auth"; 12 | import MobileMockup from "./mockup/MobileMockup"; 13 | const HomeEditor = async() => { 14 | const session = await auth() 15 | return ( 16 | <> 17 | 18 |
    19 | 20 | 21 | 22 | 23 | 24 |
    25 | 26 | 27 | 31 | 32 | Github 33 | 34 | 38 | 39 | Buy Me a Coffee 40 | 41 |
    42 |
    43 |
    44 | {/* MOBILE MOCKUP */} 45 | 46 |
    47 | 48 | ); 49 | }; 50 | export default HomeEditor; 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | 2 | import Link from "next/link"; 3 | import React from "react"; 4 | import { auth, signOut } from "./auth"; 5 | import { 6 | DropdownMenu, 7 | DropdownMenuContent, 8 | DropdownMenuItem, 9 | DropdownMenuLabel, 10 | DropdownMenuSeparator, 11 | DropdownMenuTrigger, 12 | } from "@/components/ui/dropdown-menu"; 13 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; 14 | import { cn } from "@/lib/utils"; 15 | import { buttonVariants } from "./ui/button"; 16 | import { supabaseServer } from "@/lib/supabase/supabaseServer"; 17 | 18 | async function Navbar() { 19 | const session = await auth(); 20 | let linkCount = 0 21 | const supabase = await supabaseServer().from('links').select('*').eq('email',session?.user?.email) 22 | if(supabase.data?.length){ 23 | linkCount = supabase.data.length 24 | } 25 | return ( 26 | 65 | ); 66 | } 67 | 68 | export default Navbar; 69 | -------------------------------------------------------------------------------- /src/components/PersonalInfo.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { 3 | Card, 4 | CardContent, 5 | CardDescription, 6 | CardHeader, 7 | CardTitle, 8 | } from "@/components/ui/card"; 9 | import { Label } from "./ui/label"; 10 | import { Input } from "./ui/input"; 11 | import { Textarea } from "./ui/textarea"; 12 | import { useData } from "@/lib/Context"; 13 | import PhotoUpload from "./PhotoUpload"; 14 | 15 | type InputChangeEvent = React.ChangeEvent< 16 | HTMLInputElement | HTMLTextAreaElement 17 | >; 18 | 19 | const PersonalInfo = () => { 20 | const { MyLink, updateProfileInfo } = useData(); 21 | const handleInfoChange = (event: InputChangeEvent) => { 22 | const { name, value } = event.target; 23 | updateProfileInfo(name, value); 24 | }; 25 | 26 | return ( 27 | <> 28 | 29 | 30 | Profile Information 31 | 32 | Enter your profile or title information here. 33 | 34 | 35 | 36 |
    37 |
    38 | 39 | 47 |
    48 |
    49 | 50 |
    51 |
    52 |
    53 | 54 |