├── .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 |
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 |
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 |
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 |
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 |
35 |
36 |
37 |
Already joined us? Log in
38 |
39 |
40 |
41 |
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 |
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 |
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 |
89 | {word}
90 |
91 | );
92 | }
93 |
94 | return (
95 |
96 | {word.split('').map((char, charIndex) => (
97 |
103 | {char}
104 |
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 |
51 |
52 |
53 |
54 |
61 |
62 |
63 |
64 | >
65 | );
66 | };
67 |
68 | export default PersonalInfo;
69 |
--------------------------------------------------------------------------------
/src/components/PhotoUpload.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import { storage } from "@/lib/Firebase";
3 | import { ref, uploadBytes, getDownloadURL } from "firebase/storage";
4 | import { Label } from "./ui/label";
5 | import { Input } from "./ui/input";
6 | import { useData } from "@/lib/Context";
7 | import { useState } from "react";
8 | import { Loader2 } from "lucide-react";
9 |
10 |
11 | const PhotoUpload = () =>{
12 | const { updateProfileInfo } = useData();
13 |
14 |
15 |
16 | const [loading ,setLoading] = useState(false)
17 |
18 | async function handleImageUpload (file:File) {
19 |
20 | const storageRef = ref(storage, `images/${file.name}`);
21 | await uploadBytes(storageRef, file);
22 | const res = await getDownloadURL(storageRef);
23 | return res
24 |
25 | };
26 | // FUNCTION IMAGE UPLOAD
27 | const onImageChange = async (e:React.ChangeEvent) => {
28 | setLoading(true)
29 | try {
30 | const [file]:any = e.target.files;
31 | if(!file) return
32 |
33 | const uploading = await handleImageUpload(file)
34 | updateProfileInfo('i', uploading);
35 |
36 |
37 | } catch (error) {
38 | console.log(error);
39 |
40 | } finally{
41 | setLoading(false)
42 | }
43 | };
44 |
45 |
46 |
47 | return(<>
48 |
49 | {loading ? :
50 |
56 | }
57 | >)
58 | }
59 |
60 | export default PhotoUpload
--------------------------------------------------------------------------------
/src/components/PreviewPage.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import React, { useEffect } from 'react'
3 | import DisplayScreen from './screen/DisplayScreen'
4 | import DataLoading from '@/app/1/loading'
5 | import confetti from 'canvas-confetti';
6 |
7 | function PreviewPage({data}:{data:any}) {
8 | const handleClick = () => {
9 | const duration = 5 * 1000;
10 | const animationEnd = Date.now() + duration;
11 | const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 100 };
12 |
13 | const randomInRange = (min: number, max: number) =>
14 | Math.random() * (max - min) + min;
15 |
16 | const interval = window.setInterval(() => {
17 | const timeLeft = animationEnd - Date.now();
18 |
19 | if (timeLeft <= 0) {
20 | return clearInterval(interval);
21 | }
22 |
23 | const particleCount = 50 * (timeLeft / duration);
24 | confetti({
25 | ...defaults,
26 | particleCount,
27 | origin: { x: randomInRange(0.1, 0.3), y: Math.random() - 0.2 },
28 | });
29 | confetti({
30 | ...defaults,
31 | particleCount,
32 | origin: { x: randomInRange(0.7, 0.9), y: Math.random() - 0.2 },
33 | });
34 | }, 250);
35 | };
36 | useEffect(() => {
37 | handleClick();
38 | }, [data]);
39 | return (
40 | <>
41 | {data ? : }
42 | >
43 | )
44 | }
45 |
46 | export default PreviewPage
--------------------------------------------------------------------------------
/src/components/Provider.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { DataProvider } from "@/lib/Context";
4 | import React from "react";
5 |
6 | // import { Toaster } from "@/components/ui/toaster";
7 |
8 | export function Providers({ children }: { children: React.ReactNode }) {
9 | return (
10 |
11 | {children}
12 | {/* */}
13 |
14 | );
15 | }
--------------------------------------------------------------------------------
/src/components/SocialLinkForm.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 { SocialInput } from './ui/SocialInput';
12 | import { useData } from '@/lib/Context';
13 |
14 | const socialLinksProvider: SocialLinkProviderProps[] = [
15 | { name: 'facebook', icon: "ph:facebook-logo-duotone", id: "fb" },
16 | { name: 'twitter', icon: "ph:twitter-logo-duotone", id: "tw" },
17 | { name: 'instagram', icon: "ph:instagram-logo-duotone", id: "ig" },
18 | { name: 'telegram', icon: "ph:telegram-logo-duotone", id: "tg" },
19 | { name: 'youtube', icon: "ph:youtube-logo-duotone", id: "yt" },
20 | { name: 'email', icon: "ph:envelope-duotone", id: "em" },
21 | { name: 'github', icon: "ph:github-logo-duotone", id: "gt" },
22 | { name: 'linkedin', icon: "ph:linkedin-logo-duotone", id: "lk" },
23 | { name: 'whatsapp', icon: "ph:whatsapp-logo-duotone", id: "wh" },
24 | ]
25 |
26 | type InputChangeEvent = React.ChangeEvent;
27 |
28 | interface SocialLinksFormProps { }
29 |
30 | const SocialLinksForm: FC = () => {
31 |
32 | const { MyLink, updateProfileInfo } = useData();
33 | const handleInfoChange = (event: InputChangeEvent) => {
34 | const { name, value } = event.target;
35 | updateProfileInfo(name, value);
36 | };
37 |
38 |
39 | return (
40 |
41 |
42 | Social Links
43 |
44 | Enter your social media links here.
45 |
46 |
47 |
48 | {socialLinksProvider.map((link) => {
49 | return (
50 |
59 | );
60 | })}
61 |
62 |
63 | )
64 | }
65 |
66 | export default SocialLinksForm
--------------------------------------------------------------------------------
/src/components/auth.ts:
--------------------------------------------------------------------------------
1 | import type { NextAuthConfig } from "next-auth";
2 | import "next-auth/jwt";
3 | import NextAuth from "next-auth";
4 | import GoogleProvider from "next-auth/providers/google";
5 | import { createUser, isUserExists } from "@/lib/supabase/actions";
6 | const config = {
7 | providers: [
8 | GoogleProvider({
9 | clientId: process.env.GOOGLE_CLIENT_ID as string,
10 | clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
11 | }),
12 | ],
13 | callbacks: {
14 | authorized({ request, auth }) {
15 | const { pathname } = request.nextUrl;
16 | if (pathname === "/middleware-example") return !!auth;
17 | return true;
18 | },
19 | jwt({ token, trigger, session, account }) {
20 | if (trigger === "update") token.name = session.user.name;
21 |
22 | return token;
23 | },
24 | async session({ session, token }) {
25 | if (token?.accessToken) {
26 | session.accessToken = token.accessToken;
27 | }
28 | return session;
29 | },
30 | async signIn({ account, profile }: any) {
31 | if (account?.provider === "google") {
32 | const isExists = await isUserExists(profile?.email);
33 |
34 | if (isExists.status === "success") {
35 | await createUser(profile);
36 | return true;
37 | }
38 | return true;
39 | }
40 | return true;
41 | },
42 | },
43 | } satisfies NextAuthConfig;
44 |
45 | export const { handlers, auth, signIn, signOut } = NextAuth(config);
46 |
47 | declare module "next-auth" {
48 | interface Session {
49 | accessToken?: string;
50 | }
51 | }
52 |
53 | declare module "next-auth/jwt" {
54 | interface JWT {
55 | accessToken?: string;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/components/forms/LoginGoogleBtn.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import React from 'react'
3 | import { Button } from '../ui/button'
4 | import { handleGoogleAuth } from './formAction'
5 |
6 | function LoginGoogleBtn({callbackUrl = '/'}:{callbackUrl:string}) {
7 | return (
8 | //
39 | )
40 | }
41 |
42 | export default LoginGoogleBtn
--------------------------------------------------------------------------------
/src/components/forms/SignupForm.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import React from 'react'
3 | import { Input } from "@/components/ui/input"
4 | import { Label } from "@/components/ui/label"
5 | import { Button } from "@/components/ui/button"
6 | function SignupForm() {
7 | return (
8 |
9 |
19 |
20 |
21 |
27 |
28 |
29 |
30 |
31 |
32 |
35 |
64 |
65 | )
66 | }
67 |
68 | export default SignupForm
--------------------------------------------------------------------------------
/src/components/forms/formAction.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 | import { signIn, signOut } from "../auth";
3 |
4 | export async function handleGoogleAuth(myCallbackUrl:string){
5 | await signIn('google', {redirectTo:myCallbackUrl})
6 | // await signOut({redirectTo:"/"})
7 | }
--------------------------------------------------------------------------------
/src/components/mockup/ComputerMockup.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | function ComputerMockup({
4 | children,
5 | }: {
6 | children: React.ReactNode;
7 | }) {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | https.itzmylink.vercel.app/...
20 |
21 |
22 |
23 |
24 | {children}
25 |
26 |
27 |
28 |
29 | )
30 | }
31 |
32 | export default ComputerMockup
--------------------------------------------------------------------------------
/src/components/mockup/MobileMockup.tsx:
--------------------------------------------------------------------------------
1 | import MobileScreen from "./MobileScreen";
2 |
3 | const MobileMockup = () => {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | );
13 | };
14 |
15 | export default MobileMockup;
16 |
--------------------------------------------------------------------------------
/src/components/mockup/MobileScreen.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import { cn } from "@/lib/utils"
3 | import { useData } from "@/lib/Context"
4 | import React from "react"
5 | import { BACKGROUND_OPTIONS } from "../Background/BgSnippets"
6 | import DisplayScreen from "../screen/DisplayScreen"
7 |
8 | const MobileScreen = () =>{
9 | const {MyLink} = useData()
10 |
11 | const [isEmpty, setIsEmpty] = React.useState(false)
12 |
13 | React.useEffect(() => {
14 | function isEmptyValues(obj: any) {
15 | for (let key in obj) {
16 | if (obj[key] !== "" && obj[key].length !== 0) {
17 | return false;
18 | }
19 | }
20 | return true;
21 | }
22 | setIsEmpty(isEmptyValues(MyLink))
23 | }, [MyLink])
24 |
25 |
26 |
27 | const selectedBgOption = MyLink
28 | ? BACKGROUND_OPTIONS.find((option) => option.code === MyLink.bg)
29 | : null;
30 |
31 | const selectedBgComponent = selectedBgOption ? selectedBgOption.component : null;
32 |
33 |
34 | return(
35 |
36 | {
37 | isEmpty
38 | ?
Nothing to show...
39 | : (
40 | <>
41 | {!isEmpty && selectedBgComponent}
42 |
43 |
44 |
45 |
46 | >
47 | )
48 | }
49 |
50 | )
51 | }
52 |
53 | export default MobileScreen
--------------------------------------------------------------------------------
/src/components/screen/DisplayScreen.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Icon } from "@iconify/react/dist/iconify.js";
3 | import React from "react";
4 | import AdditionalLinkCard from "../AdditionalLinkCards";
5 | import { motion, AnimatePresence } from "framer-motion";
6 | import SlightFlip from "../Animation/FlipText";
7 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
8 | import BlurIn from "../Animation/BlurText";
9 | import FramerWrapper from "../Animation/FramerWrapper";
10 | import { TextEffect } from "../Animation/TextEffect";
11 | import { ImageIcon } from "lucide-react";
12 |
13 | const DisplayScreen: React.FC = ({ myData }) => {
14 | const EmptySocialLiks =
15 | !myData.fb &&
16 | !myData.ig &&
17 | !myData.tg &&
18 | !myData.em &&
19 | !myData.tw &&
20 | !myData.lk &&
21 | !myData.yt &&
22 | !myData.gt &&
23 | !myData.wh;
24 |
25 | const iconMap: Record = {
26 | fb: "ph:facebook-logo-duotone",
27 | tw: "ph:twitter-logo-duotone",
28 | ig: "ph:instagram-logo-duotone",
29 | tg: "ph:telegram-logo-duotone",
30 | wh: "ph:whatsapp-logo-duotone",
31 | yt: "ph:youtube-logo-duotone",
32 | em: "ph:envelope-duotone",
33 | gt: "ph:github-logo-duotone",
34 | lk: "ph:linkedin-logo-duotone",
35 | };
36 |
37 |
38 | return (
39 |
40 |
41 |
42 | {/* Avatar------- */}
43 |
44 |
54 |
55 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | {/* Name & About------- */}
67 |
68 |
73 | {myData.n as string}
74 |
75 |
79 |
80 | {/* Social Icons------- */}
81 | {!EmptySocialLiks && (
82 |
83 | {Object.entries(myData).map(([key, value], indx) => {
84 | const timing = 0.55 + indx * 0.125;
85 | const excludedKeys = ["i", "n", "a", "bg"];
86 | if (key !== "ls" && value && !excludedKeys.includes(key)) {
87 | const propIcon = iconMap[key as keyof typeof iconMap];
88 | if (key === "em") {
89 | // Handle email link generation
90 | return (
91 |
98 |
99 |
100 | {/* */}
101 |
102 |
103 |
104 | );
105 | } else if (key === "wh") {
106 | // Handle WhatsApp link generation
107 | const whatsappValue = value.startsWith("https://wa.me/")
108 | ? value // If it already starts with the correct prefix
109 | : `https://wa.me/${value}`;
110 |
111 | return (
112 |
119 |
120 |
125 |
129 |
130 |
131 |
132 | );
133 | } else {
134 | return (
135 |
142 |
143 |
148 |
152 |
153 |
154 |
155 | );
156 | }
157 | }
158 | return null;
159 | })}
160 |
161 | )}
162 | {/* Links------- */}
163 |
164 |
165 | {myData.ls &&
166 | myData.ls.map((link, id) => {
167 | // Define the alternating values for x and y
168 | const positions = [
169 | { x: -200, y: 0 },
170 | { x: 200, y: 0 },
171 | ];
172 | const timing = 0.55 + id * 0.125;
173 | // Use modulus operator to cycle through the positions array
174 | const { x, y } = positions[id % positions.length];
175 |
176 | return (
177 |
178 |
184 |
185 | );
186 | })}
187 |
188 |
189 |
190 |
191 |
192 | );
193 | };
194 |
195 | export default DisplayScreen;
196 |
--------------------------------------------------------------------------------
/src/components/ui/Drrawer.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { forwardRef } from "react"
4 | import { Drawer as DrawerPrimitive } from "vaul"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const DrawerTrigger = DrawerPrimitive.Trigger
9 |
10 | const DrawerContent = forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, children, ...props }, ref) => (
14 |
15 |
16 |
24 |
25 | {children}
26 |
27 |
28 | ))
29 | DrawerContent.displayName = "DrawerContent"
30 |
31 | export { DrawerTrigger, DrawerContent }
--------------------------------------------------------------------------------
/src/components/ui/SocialInput.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 | import { Icon } from '@iconify/react';
5 |
6 | export interface InputProps
7 | extends React.InputHTMLAttributes {
8 | icon: string
9 | }
10 |
11 | const SocialInput = React.forwardRef(
12 | ({ className, icon: propIcon, ...props }, ref) => {
13 | return (
14 |
15 |
16 |
25 |
26 | )
27 | }
28 | )
29 | SocialInput.displayName = "SocialInput"
30 |
31 | export { SocialInput }
--------------------------------------------------------------------------------
/src/components/ui/SortableLink.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { FC } from "react";
3 | import { useSortable } from "@dnd-kit/sortable";
4 | import { CSS } from "@dnd-kit/utilities";
5 | import { Card } from "@/components/ui/card";
6 | import { Input } from "@/components/ui/input";
7 | import { Label } from "@/components/ui/label";
8 | import { useData } from "@/lib/Context";
9 | import {
10 | Select,
11 | SelectContent,
12 | SelectGroup,
13 | SelectItem,
14 | SelectLabel,
15 | SelectTrigger,
16 | SelectValue,
17 | } from "@/components/ui/select"
18 |
19 |
20 | interface SortableLinksProps {
21 | id: AdditionalLinkProps;
22 | index: number;
23 | }
24 |
25 | export const SortableLinks: FC = ({ id, index }) => {
26 | let uniqueID = id.id;
27 |
28 | const { attributes, listeners, setNodeRef, transform, transition } =
29 | useSortable({ id: uniqueID });
30 |
31 | const style = {
32 | transform: CSS.Transform.toString(transform),
33 | transition,
34 | };
35 |
36 | const { MyLink, updateAdditionalInfo } = useData();
37 |
38 | // ICONS LISTS
39 | const iconsLists = [
40 | {name:'My Portfolio',code:'ph:laptop-duotone'},
41 | {name:'Chatbot project',code:'ant-design:robot-outlined'},
42 | {name:'Ai project',code:'fluent:brain-circuit-20-regular'},
43 | {name:'Web Project',code:'icon-park-outline:blockchain'},
44 | {name:'My Blogs',code:'ph:pencil-duotone'},
45 | ]
46 |
47 |
48 | const showWhatvalue = (e:any) =>{
49 | const newLinks = [...MyLink.ls];
50 | newLinks[index].i = e
51 | updateAdditionalInfo(newLinks);
52 | console.log(e);
53 |
54 | }
55 |
56 | return (
57 |
58 |
59 |
60 |
61 |
62 |
63 |
77 |
78 |
79 |
80 |
81 | ) => {
88 | const newLinks = [...MyLink.ls];
89 | newLinks[index].l = e.target.value;
90 | updateAdditionalInfo(newLinks);
91 | }}
92 | />
93 |
94 |
95 |
96 |
97 | ) => {
104 | const newLinks = [...MyLink.ls];
105 | newLinks[index].u = e.target.value;
106 | updateAdditionalInfo(newLinks);
107 | }}
108 | />
109 |
110 |
111 |
124 |
125 |
126 | );
127 | };
128 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
5 |
6 | import { cn } from "@/lib/utils"
7 | import { buttonVariants } from "@/components/ui/button"
8 |
9 | const AlertDialog = AlertDialogPrimitive.Root
10 |
11 | const AlertDialogTrigger = AlertDialogPrimitive.Trigger
12 |
13 | const AlertDialogPortal = AlertDialogPrimitive.Portal
14 |
15 | const AlertDialogOverlay = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, ...props }, ref) => (
19 |
27 | ))
28 | AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
29 |
30 | const AlertDialogContent = React.forwardRef<
31 | React.ElementRef,
32 | React.ComponentPropsWithoutRef
33 | >(({ className, ...props }, ref) => (
34 |
35 |
36 |
44 |
45 | ))
46 | AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
47 |
48 | const AlertDialogHeader = ({
49 | className,
50 | ...props
51 | }: React.HTMLAttributes) => (
52 |
59 | )
60 | AlertDialogHeader.displayName = "AlertDialogHeader"
61 |
62 | const AlertDialogFooter = ({
63 | className,
64 | ...props
65 | }: React.HTMLAttributes) => (
66 |
73 | )
74 | AlertDialogFooter.displayName = "AlertDialogFooter"
75 |
76 | const AlertDialogTitle = React.forwardRef<
77 | React.ElementRef,
78 | React.ComponentPropsWithoutRef
79 | >(({ className, ...props }, ref) => (
80 |
85 | ))
86 | AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
87 |
88 | const AlertDialogDescription = React.forwardRef<
89 | React.ElementRef,
90 | React.ComponentPropsWithoutRef
91 | >(({ className, ...props }, ref) => (
92 |
97 | ))
98 | AlertDialogDescription.displayName =
99 | AlertDialogPrimitive.Description.displayName
100 |
101 | const AlertDialogAction = React.forwardRef<
102 | React.ElementRef,
103 | React.ComponentPropsWithoutRef
104 | >(({ className, ...props }, ref) => (
105 |
110 | ))
111 | AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
112 |
113 | const AlertDialogCancel = React.forwardRef<
114 | React.ElementRef,
115 | React.ComponentPropsWithoutRef
116 | >(({ className, ...props }, ref) => (
117 |
126 | ))
127 | AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
128 |
129 | export {
130 | AlertDialog,
131 | AlertDialogPortal,
132 | AlertDialogOverlay,
133 | AlertDialogTrigger,
134 | AlertDialogContent,
135 | AlertDialogHeader,
136 | AlertDialogFooter,
137 | AlertDialogTitle,
138 | AlertDialogDescription,
139 | AlertDialogAction,
140 | AlertDialogCancel,
141 | }
142 |
--------------------------------------------------------------------------------
/src/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AvatarPrimitive from "@radix-ui/react-avatar"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Avatar = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ))
21 | Avatar.displayName = AvatarPrimitive.Root.displayName
22 |
23 | const AvatarImage = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
32 | ))
33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName
34 |
35 | const AvatarFallback = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 | ))
48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
49 |
50 | export { Avatar, AvatarImage, AvatarFallback }
51 |
--------------------------------------------------------------------------------
/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/lib/utils'
2 | import { cva, VariantProps } from 'class-variance-authority'
3 | import { Loader2 } from 'lucide-react'
4 | import * as React from 'react'
5 |
6 | const buttonVariants = cva(
7 | 'active:scale-95 inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:opacity-50 dark:focus:ring-slate-400 disabled:pointer-events-none dark:focus:ring-offset-slate-900',
8 | {
9 | variants: {
10 | variant: {
11 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
12 | destructive:
13 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
14 | outline:
15 | 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
16 | custom:
17 | ' bg-[#343e44] text-white hover:bg-[#5c6165] hover:shadow-lg',
18 | subtle:
19 | 'border border-input bg-background hover:bg-[#6aa2e6] hover:text-white',
20 | ghost:
21 | 'bg-transparent hover:bg-zinc-100 text-zinc-800 data-[state=open]:bg-transparent data-[state=open]:bg-transparent',
22 | link: 'bg-transparent dark:bg-transparent underline-offset-4 hover:underline text-slate-900 dark:text-slate-100 hover:bg-transparent dark:hover:bg-transparent',
23 | },
24 | size: {
25 | default: 'h-10 py-2 px-4',
26 | sm: 'h-9 px-2 rounded-md',
27 | xs: 'h-8 px-1.5 rounded-sm',
28 | lg: 'h-11 px-8 rounded-md',
29 | icon: "h-10 w-10",
30 | },
31 | },
32 | defaultVariants: {
33 | variant: 'default',
34 | size: 'default',
35 | },
36 | }
37 | )
38 |
39 | export interface ButtonProps
40 | extends React.ButtonHTMLAttributes,
41 | VariantProps {
42 | isLoading?: boolean
43 | }
44 |
45 | const Button = React.forwardRef(
46 | ({ className, children, variant, isLoading, size, ...props }, ref) => {
47 | return (
48 |
56 | )
57 | }
58 | )
59 | Button.displayName = 'Button'
60 |
61 | export { Button, buttonVariants }
--------------------------------------------------------------------------------
/src/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
44 | ))
45 | CardTitle.displayName = "CardTitle"
46 |
47 | const CardDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | CardDescription.displayName = "CardDescription"
58 |
59 | const CardContent = React.forwardRef<
60 | HTMLDivElement,
61 | React.HTMLAttributes
62 | >(({ className, ...props }, ref) => (
63 |
64 | ))
65 | CardContent.displayName = "CardContent"
66 |
67 | const CardFooter = React.forwardRef<
68 | HTMLDivElement,
69 | React.HTMLAttributes
70 | >(({ className, ...props }, ref) => (
71 |
76 | ))
77 | CardFooter.displayName = "CardFooter"
78 |
79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
80 |
--------------------------------------------------------------------------------
/src/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DialogPrimitive from "@radix-ui/react-dialog"
5 | import { X } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Dialog = DialogPrimitive.Root
10 |
11 | const DialogTrigger = DialogPrimitive.Trigger
12 |
13 | const DialogPortal = DialogPrimitive.Portal
14 |
15 | const DialogOverlay = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, ...props }, ref) => (
19 |
27 | ))
28 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
29 |
30 | const DialogContent = React.forwardRef<
31 | React.ElementRef,
32 | React.ComponentPropsWithoutRef
33 | >(({ className, children, ...props }, ref) => (
34 |
35 |
36 |
44 | {children}
45 |
46 |
47 | Close
48 |
49 |
50 |
51 | ))
52 | DialogContent.displayName = DialogPrimitive.Content.displayName
53 |
54 | const DialogHeader = ({
55 | className,
56 | ...props
57 | }: React.HTMLAttributes) => (
58 |
65 | )
66 | DialogHeader.displayName = "DialogHeader"
67 |
68 | const DialogFooter = ({
69 | className,
70 | ...props
71 | }: React.HTMLAttributes) => (
72 |
79 | )
80 | DialogFooter.displayName = "DialogFooter"
81 |
82 | const DialogTitle = React.forwardRef<
83 | React.ElementRef,
84 | React.ComponentPropsWithoutRef
85 | >(({ className, ...props }, ref) => (
86 |
94 | ))
95 | DialogTitle.displayName = DialogPrimitive.Title.displayName
96 |
97 | const DialogDescription = React.forwardRef<
98 | React.ElementRef,
99 | React.ComponentPropsWithoutRef
100 | >(({ className, ...props }, ref) => (
101 |
106 | ))
107 | DialogDescription.displayName = DialogPrimitive.Description.displayName
108 |
109 | export {
110 | Dialog,
111 | DialogPortal,
112 | DialogOverlay,
113 | DialogTrigger,
114 | DialogContent,
115 | DialogHeader,
116 | DialogFooter,
117 | DialogTitle,
118 | DialogDescription,
119 | }
120 |
--------------------------------------------------------------------------------
/src/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5 | import { Check, ChevronRight, Circle } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const DropdownMenu = DropdownMenuPrimitive.Root
10 |
11 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
12 |
13 | const DropdownMenuGroup = DropdownMenuPrimitive.Group
14 |
15 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal
16 |
17 | const DropdownMenuSub = DropdownMenuPrimitive.Sub
18 |
19 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
20 |
21 | const DropdownMenuSubTrigger = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef & {
24 | inset?: boolean
25 | }
26 | >(({ className, inset, children, ...props }, ref) => (
27 |
36 | {children}
37 |
38 |
39 | ))
40 | DropdownMenuSubTrigger.displayName =
41 | DropdownMenuPrimitive.SubTrigger.displayName
42 |
43 | const DropdownMenuSubContent = React.forwardRef<
44 | React.ElementRef,
45 | React.ComponentPropsWithoutRef
46 | >(({ className, ...props }, ref) => (
47 |
55 | ))
56 | DropdownMenuSubContent.displayName =
57 | DropdownMenuPrimitive.SubContent.displayName
58 |
59 | const DropdownMenuContent = React.forwardRef<
60 | React.ElementRef,
61 | React.ComponentPropsWithoutRef
62 | >(({ className, sideOffset = 4, ...props }, ref) => (
63 |
64 |
73 |
74 | ))
75 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
76 |
77 | const DropdownMenuItem = React.forwardRef<
78 | React.ElementRef,
79 | React.ComponentPropsWithoutRef & {
80 | inset?: boolean
81 | }
82 | >(({ className, inset, ...props }, ref) => (
83 |
92 | ))
93 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
94 |
95 | const DropdownMenuCheckboxItem = React.forwardRef<
96 | React.ElementRef,
97 | React.ComponentPropsWithoutRef
98 | >(({ className, children, checked, ...props }, ref) => (
99 |
108 |
109 |
110 |
111 |
112 |
113 | {children}
114 |
115 | ))
116 | DropdownMenuCheckboxItem.displayName =
117 | DropdownMenuPrimitive.CheckboxItem.displayName
118 |
119 | const DropdownMenuRadioItem = React.forwardRef<
120 | React.ElementRef,
121 | React.ComponentPropsWithoutRef
122 | >(({ className, children, ...props }, ref) => (
123 |
131 |
132 |
133 |
134 |
135 |
136 | {children}
137 |
138 | ))
139 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
140 |
141 | const DropdownMenuLabel = React.forwardRef<
142 | React.ElementRef,
143 | React.ComponentPropsWithoutRef & {
144 | inset?: boolean
145 | }
146 | >(({ className, inset, ...props }, ref) => (
147 |
156 | ))
157 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
158 |
159 | const DropdownMenuSeparator = React.forwardRef<
160 | React.ElementRef,
161 | React.ComponentPropsWithoutRef
162 | >(({ className, ...props }, ref) => (
163 |
168 | ))
169 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
170 |
171 | const DropdownMenuShortcut = ({
172 | className,
173 | ...props
174 | }: React.HTMLAttributes) => {
175 | return (
176 |
180 | )
181 | }
182 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
183 |
184 | export {
185 | DropdownMenu,
186 | DropdownMenuTrigger,
187 | DropdownMenuContent,
188 | DropdownMenuItem,
189 | DropdownMenuCheckboxItem,
190 | DropdownMenuRadioItem,
191 | DropdownMenuLabel,
192 | DropdownMenuSeparator,
193 | DropdownMenuShortcut,
194 | DropdownMenuGroup,
195 | DropdownMenuPortal,
196 | DropdownMenuSub,
197 | DropdownMenuSubContent,
198 | DropdownMenuSubTrigger,
199 | DropdownMenuRadioGroup,
200 | }
201 |
--------------------------------------------------------------------------------
/src/components/ui/form.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { Slot } from "@radix-ui/react-slot"
6 | import {
7 | Controller,
8 | ControllerProps,
9 | FieldPath,
10 | FieldValues,
11 | FormProvider,
12 | useFormContext,
13 | } from "react-hook-form"
14 |
15 | import { cn } from "@/lib/utils"
16 | import { Label } from "@/components/ui/label"
17 |
18 | const Form = FormProvider
19 |
20 | type FormFieldContextValue<
21 | TFieldValues extends FieldValues = FieldValues,
22 | TName extends FieldPath = FieldPath
23 | > = {
24 | name: TName
25 | }
26 |
27 | const FormFieldContext = React.createContext(
28 | {} as FormFieldContextValue
29 | )
30 |
31 | const FormField = <
32 | TFieldValues extends FieldValues = FieldValues,
33 | TName extends FieldPath = FieldPath
34 | >({
35 | ...props
36 | }: ControllerProps) => {
37 | return (
38 |
39 |
40 |
41 | )
42 | }
43 |
44 | const useFormField = () => {
45 | const fieldContext = React.useContext(FormFieldContext)
46 | const itemContext = React.useContext(FormItemContext)
47 | const { getFieldState, formState } = useFormContext()
48 |
49 | const fieldState = getFieldState(fieldContext.name, formState)
50 |
51 | if (!fieldContext) {
52 | throw new Error("useFormField should be used within ")
53 | }
54 |
55 | const { id } = itemContext
56 |
57 | return {
58 | id,
59 | name: fieldContext.name,
60 | formItemId: `${id}-form-item`,
61 | formDescriptionId: `${id}-form-item-description`,
62 | formMessageId: `${id}-form-item-message`,
63 | ...fieldState,
64 | }
65 | }
66 |
67 | type FormItemContextValue = {
68 | id: string
69 | }
70 |
71 | const FormItemContext = React.createContext(
72 | {} as FormItemContextValue
73 | )
74 |
75 | const FormItem = React.forwardRef<
76 | HTMLDivElement,
77 | React.HTMLAttributes
78 | >(({ className, ...props }, ref) => {
79 | const id = React.useId()
80 |
81 | return (
82 |
83 |
84 |
85 | )
86 | })
87 | FormItem.displayName = "FormItem"
88 |
89 | const FormLabel = React.forwardRef<
90 | React.ElementRef,
91 | React.ComponentPropsWithoutRef
92 | >(({ className, ...props }, ref) => {
93 | const { error, formItemId } = useFormField()
94 |
95 | return (
96 |
102 | )
103 | })
104 | FormLabel.displayName = "FormLabel"
105 |
106 | const FormControl = React.forwardRef<
107 | React.ElementRef,
108 | React.ComponentPropsWithoutRef
109 | >(({ ...props }, ref) => {
110 | const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
111 |
112 | return (
113 |
124 | )
125 | })
126 | FormControl.displayName = "FormControl"
127 |
128 | const FormDescription = React.forwardRef<
129 | HTMLParagraphElement,
130 | React.HTMLAttributes
131 | >(({ className, ...props }, ref) => {
132 | const { formDescriptionId } = useFormField()
133 |
134 | return (
135 |
141 | )
142 | })
143 | FormDescription.displayName = "FormDescription"
144 |
145 | const FormMessage = React.forwardRef<
146 | HTMLParagraphElement,
147 | React.HTMLAttributes
148 | >(({ className, children, ...props }, ref) => {
149 | const { error, formMessageId } = useFormField()
150 | const body = error ? String(error?.message) : children
151 |
152 | if (!body) {
153 | return null
154 | }
155 |
156 | return (
157 |
163 | {body}
164 |
165 | )
166 | })
167 | FormMessage.displayName = "FormMessage"
168 |
169 | export {
170 | useFormField,
171 | Form,
172 | FormItem,
173 | FormLabel,
174 | FormControl,
175 | FormDescription,
176 | FormMessage,
177 | FormField,
178 | }
179 |
--------------------------------------------------------------------------------
/src/components/ui/input-otp.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { OTPInput, OTPInputContext } from "input-otp"
5 | import { Dot } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const InputOTP = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, containerClassName, ...props }, ref) => (
13 |
22 | ))
23 | InputOTP.displayName = "InputOTP"
24 |
25 | const InputOTPGroup = React.forwardRef<
26 | React.ElementRef<"div">,
27 | React.ComponentPropsWithoutRef<"div">
28 | >(({ className, ...props }, ref) => (
29 |
30 | ))
31 | InputOTPGroup.displayName = "InputOTPGroup"
32 |
33 | const InputOTPSlot = React.forwardRef<
34 | React.ElementRef<"div">,
35 | React.ComponentPropsWithoutRef<"div"> & { index: number }
36 | >(({ index, className, ...props }, ref) => {
37 | const inputOTPContext = React.useContext(OTPInputContext)
38 | const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
39 |
40 | return (
41 |
50 | {char}
51 | {hasFakeCaret && (
52 |
55 | )}
56 |
57 | )
58 | })
59 | InputOTPSlot.displayName = "InputOTPSlot"
60 |
61 | const InputOTPSeparator = React.forwardRef<
62 | React.ElementRef<"div">,
63 | React.ComponentPropsWithoutRef<"div">
64 | >(({ ...props }, ref) => (
65 |
66 |
67 |
68 | ))
69 | InputOTPSeparator.displayName = "InputOTPSeparator"
70 |
71 | export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
72 |
--------------------------------------------------------------------------------
/src/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 |
--------------------------------------------------------------------------------
/src/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 |
--------------------------------------------------------------------------------
/src/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SelectPrimitive from "@radix-ui/react-select"
5 | import { Check, ChevronDown } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Select = SelectPrimitive.Root
10 |
11 | const SelectGroup = SelectPrimitive.Group
12 |
13 | const SelectValue = SelectPrimitive.Value
14 |
15 | const SelectTrigger = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, children, ...props }, ref) => (
19 |
27 | {children}
28 |
29 |
30 |
31 |
32 | ))
33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
34 |
35 | const SelectContent = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, children, position = "popper", ...props }, ref) => (
39 |
40 |
51 |
58 | {children}
59 |
60 |
61 |
62 | ))
63 | SelectContent.displayName = SelectPrimitive.Content.displayName
64 |
65 | const SelectLabel = React.forwardRef<
66 | React.ElementRef,
67 | React.ComponentPropsWithoutRef
68 | >(({ className, ...props }, ref) => (
69 |
74 | ))
75 | SelectLabel.displayName = SelectPrimitive.Label.displayName
76 |
77 | const SelectItem = React.forwardRef<
78 | React.ElementRef,
79 | React.ComponentPropsWithoutRef
80 | >(({ className, children, ...props }, ref) => (
81 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | {children}
96 |
97 | ))
98 | SelectItem.displayName = SelectPrimitive.Item.displayName
99 |
100 | const SelectSeparator = React.forwardRef<
101 | React.ElementRef,
102 | React.ComponentPropsWithoutRef
103 | >(({ className, ...props }, ref) => (
104 |
109 | ))
110 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName
111 |
112 | export {
113 | Select,
114 | SelectGroup,
115 | SelectValue,
116 | SelectTrigger,
117 | SelectContent,
118 | SelectLabel,
119 | SelectItem,
120 | SelectSeparator,
121 | }
122 |
--------------------------------------------------------------------------------
/src/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 }
16 |
--------------------------------------------------------------------------------
/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/src/lib/Context.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useContext, useState } from "react";
2 |
3 | interface LinkProps {
4 | n: string; //Name
5 | i: string; // image
6 | a: string; //About
7 | bg: string; //Background
8 | fb: string; //facebook
9 | ig: string; //instagram
10 | tg: string; //telegram
11 | em: string; //email
12 | tw: string; //twitter
13 | lk: string; //linkedin
14 | yt: string; //youtube
15 | gt: string; //github
16 | wh: string; //whatsup
17 | ls: AdditionalLinkProps[]; // Additional Forms
18 | }
19 | const initialData: LinkProps = {
20 | n: "", //Name
21 | i: "", // image
22 | a: "", //About
23 | bg: "", //Background
24 | fb: "", //facebook
25 | ig: "", //instagram
26 | tg: "", //telegram
27 | em: "", //email
28 | tw: "", //twitter
29 | lk: "", //linkedin
30 | yt: "", //youtube
31 | gt: "", //github
32 | wh: "", //whatsup
33 | ls: [], //Additional Forms
34 | };
35 | interface DataContextType {
36 | // Todo: fix type props
37 | data: string;
38 | MyLink: LinkProps;
39 | addNewData: (linkData: AdditionalLinkProps) => void;
40 | setData: (val: string) => void;
41 | updateProfileInfo: (name: any, value: any) => void;
42 | selectBackground: (bgcode: string) => void;
43 | updateIndex: (updatedIndex: AdditionalLinkProps[]) => void;
44 | updateAdditionalInfo: (updatedIndex: any) => void;
45 | showDemo: () => void;
46 | }
47 |
48 | const demoData: LinkProps = {
49 | n: "Md Taqui Imam",
50 | i: "https://firebasestorage.googleapis.com/v0/b/projectfriendz-45b49.appspot.com/o/images%2FSocialLogo.png?alt=media&token=551c9a3c-07e4-486d-9d26-96c619d817a9&_gl=1*uluah4*_ga*NDUyNjQ5MjYxLjE2OTcyMjI3MDU.*_ga_CW55HF8NVT*MTY5NzIyMjcwNS4xLjEuMTY5NzIyMjg0Ni4xMy4wLjA.",
51 | a: "I'm a self-taught Web developer who is always learning and creating cool Project stuffs.",
52 | fb: "https://www.facebook.com/shahina.khatun.1044",
53 | tw: "https://twitter.com/Taquiimam14",
54 | ig: "https://www.instagram.com/taqui_imam_786/",
55 | tg: "https://t.me/@Taqui_devloper",
56 | gt: "https://github.com/taqui-786",
57 | lk: "https://linkedin.com/in/taqui-imam",
58 | em: "mdtaqui.jhar@gmail.com",
59 | wh: "+916666666666",
60 | yt: "https://youtube.com/@james_smith",
61 | bg: "#4F4F4F",
62 | ls: [
63 | {
64 | id: 1,
65 | i: "ph:laptop-duotone",
66 | l: "My Portfolio Website",
67 | u: "https://example.com",
68 | },
69 | {
70 | id: 2,
71 | i: "ant-design:robot-outlined",
72 | l: "My Chatbot Project",
73 | u: "https://example.com/chatbot",
74 | },
75 | {
76 | id: 3,
77 | i: "fluent:brain-circuit-20-regular",
78 | l: "My Machine Learning Project",
79 | u: "https://example.com/ml",
80 | },
81 | {
82 | id: 4,
83 | i: "icon-park-outline:blockchain",
84 | l: "My Blockchain Project",
85 | u: "https://example.com/blockchain",
86 | },
87 | {
88 | id: 5,
89 | i: "ph:pencil-duotone",
90 | l: "My Blog Posts",
91 | u: "https://taquideveloper.hashnode.dev/",
92 | },
93 | ],
94 | };
95 |
96 | const DataContext = createContext(undefined);
97 |
98 | export const DataProvider = ({ children }: { children: React.ReactNode }) => {
99 | const [data, setData] = useState("");
100 | const [MyLink, setMyLink] = useState(initialData);
101 |
102 | // UPDATE PERSONAL INFORMATION
103 | const updateProfileInfo = (name: any, value: any) => {
104 | setMyLink((prevState) => ({
105 | ...prevState,
106 | [name]: value,
107 | }));
108 | };
109 | // SELECT BACKGROUND FUNCTION
110 | const selectBackground = (bgcode: string) => {
111 | setMyLink((prevState) => ({
112 | ...prevState,
113 | bg: bgcode,
114 | }));
115 | };
116 | // ADDITIONAL INFO FUNCTIONS
117 | const updateIndex = (updatedIndex: AdditionalLinkProps[]) => {
118 | setMyLink((prevState) => ({
119 | ...prevState,
120 | ls: updatedIndex,
121 | }));
122 | };
123 | const updateAdditionalInfo = (updatedIndex: any) => {
124 | setMyLink((prevState) => ({
125 | ...prevState,
126 | ls: updatedIndex,
127 | }));
128 | };
129 | const addNewData = (linkData: AdditionalLinkProps) => {
130 | setMyLink((prevData) => ({
131 | ...prevData,
132 | ls: [...prevData.ls, linkData],
133 | }));
134 | };
135 | // SHOW DEMO FUNCTION
136 | const showDemo = () => {
137 |
138 | setMyLink(demoData);
139 | };
140 | return (
141 |
154 | {children}
155 |
156 | );
157 | };
158 |
159 | export const useData = () => {
160 | const context = useContext(DataContext);
161 | if (!context) {
162 | throw new Error("useData must be used within a DataProvider");
163 | }
164 | return context;
165 | };
166 |
--------------------------------------------------------------------------------
/src/lib/Firebase.ts:
--------------------------------------------------------------------------------
1 |
2 | import { initializeApp } from 'firebase/app';
3 | import { getStorage } from 'firebase/storage';
4 | const firebaseConfig = {
5 | apiKey: process.env.APIKEY,
6 | authDomain:"projectfriendz-45b49.firebaseapp.com",
7 | projectId: "projectfriendz-45b49",
8 | storageBucket: "projectfriendz-45b49.appspot.com",
9 | messagingSenderId: "186662584426",
10 | appId: "1:186662584426:web:b37a6002f57c2af7578a13",
11 | measurementId:"G-ZCMDL02FYD"
12 | };
13 | const app = initializeApp(firebaseConfig);
14 | const storage = getStorage(app);
15 | export{ storage };
--------------------------------------------------------------------------------
/src/lib/RateLimiter.tsx:
--------------------------------------------------------------------------------
1 | // Function written by ChatGPT
2 |
3 | class RateLimiter {
4 | private limit: number; // Maximum allowed requests
5 | private interval: number; // Time window (in milliseconds)
6 | private requests: Map; // In-memory storage for request timestamps
7 |
8 | constructor(limit: number, interval: number) {
9 | this.limit = limit;
10 | this.interval = interval;
11 | this.requests = new Map();
12 | }
13 |
14 | isAllowed(key: string): boolean {
15 | const now = Date.now();
16 | const requests = this.requests.get(key) || [];
17 |
18 | // Remove expired timestamps
19 | const validRequests = requests.filter((timestamp) => now - timestamp <= this.interval);
20 |
21 | if (validRequests.length < this.limit) {
22 | // Add the current timestamp to the list
23 | validRequests.push(now);
24 | this.requests.set(key, validRequests);
25 | return true;
26 | } else {
27 | return false; // Rate limit exceeded
28 | }
29 | }
30 | }
31 |
32 | export default RateLimiter;
--------------------------------------------------------------------------------
/src/lib/supabase/actions.ts:
--------------------------------------------------------------------------------
1 | "use server";
2 |
3 | import { auth } from "@/components/auth";
4 | import { supabaseServer } from "./supabaseServer";
5 |
6 |
7 |
8 | export const isUserExists = async (email: string) => {
9 | const supabase = await supabaseServer()
10 | .from("users")
11 | .select("*")
12 | .eq("email", email);
13 |
14 | if (supabase.data?.length) {
15 | return { status: "exists"};
16 | }
17 | return { status: "success" };
18 | };
19 |
20 | export const createUser = async (payload:any) => {
21 | const supabase = await supabaseServer().from('users').insert({fullname:payload?.name, email: payload?.email})
22 |
23 | if (supabase.statusText === 'Created') {
24 | return { status: "success"};
25 | }
26 | return { status: "error", message: supabase.error };
27 | };
28 |
29 | type CreateCustomPathResponse =
30 | | { status: "exists"; message: string }
31 | | { status: "notAuthenticated"; message: string }
32 | | { status: "created"; message: string };
33 |
34 | export const createCustomPath = async (path: string,localLink:string): Promise => {
35 | const session = await auth();
36 | if (session?.user) {
37 | const supabase = await supabaseServer();
38 | const isExists = await supabase.from('links').select('*').eq('path', path);
39 |
40 | if (isExists.data?.length) {
41 | return { status: "exists", message: `This '/${path}' already exists.` };
42 | }
43 |
44 | const create = await supabase.from('links').insert({ path, email: session.user?.email, link:localLink });
45 |
46 | if (create.statusText === 'Created') {
47 | return { status: "created", message: "Link created successfully" };
48 | }
49 | }
50 |
51 | return { status: "notAuthenticated", message: "Not authenticated" };
52 | };
53 |
--------------------------------------------------------------------------------
/src/lib/supabase/supabaseClient.ts:
--------------------------------------------------------------------------------
1 | import { createBrowserClient } from '@supabase/ssr'
2 |
3 | export function supabaseClient() {
4 | // Create a supabase client on the browser with project's credentials
5 | return createBrowserClient(
6 | process.env.NEXT_PUBLIC_SUPABASE_URL!,
7 | process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
8 | )
9 | }
--------------------------------------------------------------------------------
/src/lib/supabase/supabaseMiddleware.ts:
--------------------------------------------------------------------------------
1 | import { createServerClient, type CookieOptions } from '@supabase/ssr'
2 | import { NextResponse, type NextRequest } from 'next/server'
3 |
4 | export async function updateSession(request: NextRequest) {
5 | let supabaseResponse = NextResponse.next({
6 | request,
7 | })
8 |
9 | const supabase = createServerClient(
10 | process.env.NEXT_PUBLIC_SUPABASE_URL!,
11 | process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
12 | {
13 | cookies: {
14 | getAll() {
15 | return request.cookies.getAll()
16 | },
17 | setAll(cookiesToSet) {
18 | cookiesToSet.forEach(({ name, value, options }) => request.cookies.set(name, value))
19 | supabaseResponse = NextResponse.next({
20 | request,
21 | })
22 | cookiesToSet.forEach(({ name, value, options }) =>
23 | supabaseResponse.cookies.set(name, value, options)
24 | )
25 | },
26 | },
27 | }
28 | )
29 |
30 | // refreshing the auth token
31 | await supabase.auth.getUser()
32 |
33 | return supabaseResponse
34 | }
--------------------------------------------------------------------------------
/src/lib/supabase/supabaseServer.ts:
--------------------------------------------------------------------------------
1 | import { createServerClient, type CookieOptions } from '@supabase/ssr'
2 | import { cookies } from 'next/headers'
3 |
4 | export function supabaseServer() {
5 | const cookieStore = cookies()
6 |
7 | // Create a server's supabase client with newly configured cookie,
8 | // which could be used to maintain user's session
9 | return createServerClient(
10 | process.env.NEXT_PUBLIC_SUPABASE_URL!,
11 | process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
12 | {
13 | cookies: {
14 | getAll() {
15 | return cookieStore.getAll()
16 | },
17 | setAll(cookiesToSet) {
18 | try {
19 | cookiesToSet.forEach(({ name, value, options }) =>
20 | cookieStore.set(name, value, options)
21 | )
22 | } catch {
23 | // The `setAll` method was called from a Server Component.
24 | // This can be ignored if you have middleware refreshing
25 | // user sessions.
26 | }
27 | },
28 | },
29 | }
30 | )
31 | }
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 | import { encode, decode } from 'js-base64';
8 |
9 | export const encodeData = (obj: any): string => {
10 | return encode(JSON.stringify(obj));
11 | };
12 |
13 | export const decodeData = (base64: string): any => {
14 | return JSON.parse(decode(base64));
15 | };
--------------------------------------------------------------------------------
/src/middleware.ts:
--------------------------------------------------------------------------------
1 | import { type NextRequest } from 'next/server'
2 | import { updateSession } from './lib/supabase/supabaseMiddleware'
3 |
4 |
5 | export async function middleware(request: NextRequest) {
6 | // update user's auth session
7 | return await updateSession(request)
8 | }
9 |
10 | export const config = {
11 | matcher: [
12 | /*
13 | * Match all request paths except for the ones starting with:
14 | * - _next/static (static files)
15 | * - _next/image (image optimization files)
16 | * - favicon.ico (favicon file)
17 | * Feel free to modify this pattern to include more paths.
18 | */
19 | '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
20 | ],
21 | }
--------------------------------------------------------------------------------
/src/types/Types.ts:
--------------------------------------------------------------------------------
1 |
2 | interface DataProps {
3 | n?: string;
4 | a?: string;
5 | i?: string;
6 | bg?: string;
7 | fb?: string;
8 | tg?: string;
9 | ig?: string;
10 | gt?: string;
11 | tw?: string;
12 | lk?: string;
13 | em?: string;
14 | wh?: string;
15 | yt?: string;
16 | ls?: AdditionalLinkProps[];
17 | };
18 |
19 | const socialLinksData = {
20 | fb: 'facebook',
21 | tw: 'twitter',
22 | ig: 'instagram',
23 | gt: 'github',
24 | tg: 'telegram',
25 | lk: 'linkedin',
26 | em: 'email',
27 | wh: 'whatsapp',
28 | yt: 'youtube',
29 | };
30 |
31 | interface SocialLinkProviderProps {
32 | name: string;
33 | icon: string;
34 | id: keyof typeof socialLinksData;
35 | }
36 | interface DisplayDataProps {
37 | myData: DataProps
38 | }
39 | interface AdditionalLinkProps {
40 | id: number
41 | i: string;
42 | l: string;
43 | u: string;
44 | }
45 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | darkMode: ["class"],
4 | content: [
5 | './pages/**/*.{ts,tsx}',
6 | './components/**/*.{ts,tsx}',
7 | './app/**/*.{ts,tsx}',
8 | './src/**/*.{ts,tsx}',
9 | ],
10 | theme: {
11 | container: {
12 | center: true,
13 | padding: "2rem",
14 | screens: {
15 | "2xl": "1400px",
16 | },
17 | },
18 | extend: {
19 | colors: {
20 | border: "hsl(var(--border))",
21 | input: "hsl(var(--input))",
22 | ring: "hsl(var(--ring))",
23 | background: "hsl(var(--background))",
24 | foreground: "hsl(var(--foreground))",
25 | primary: {
26 | DEFAULT: "hsl(var(--primary))",
27 | foreground: "hsl(var(--primary-foreground))",
28 | },
29 | secondary: {
30 | DEFAULT: "hsl(var(--secondary))",
31 | foreground: "hsl(var(--secondary-foreground))",
32 | },
33 | destructive: {
34 | DEFAULT: "hsl(var(--destructive))",
35 | foreground: "hsl(var(--destructive-foreground))",
36 | },
37 | muted: {
38 | DEFAULT: "hsl(var(--muted))",
39 | foreground: "hsl(var(--muted-foreground))",
40 | },
41 | accent: {
42 | DEFAULT: "hsl(var(--accent))",
43 | foreground: "hsl(var(--accent-foreground))",
44 | },
45 | popover: {
46 | DEFAULT: "hsl(var(--popover))",
47 | foreground: "hsl(var(--popover-foreground))",
48 | },
49 | card: {
50 | DEFAULT: "hsl(var(--card))",
51 | foreground: "hsl(var(--card-foreground))",
52 | },
53 | },
54 | borderRadius: {
55 | lg: "var(--radius)",
56 | md: "calc(var(--radius) - 2px)",
57 | sm: "calc(var(--radius) - 4px)",
58 | },
59 | keyframes: {
60 | "accordion-down": {
61 | from: { height: 0 },
62 | to: { height: "var(--radix-accordion-content-height)" },
63 | },
64 | "accordion-up": {
65 | from: { height: "var(--radix-accordion-content-height)" },
66 | to: { height: 0 },
67 | },
68 | },
69 | animation: {
70 | "accordion-down": "accordion-down 0.2s ease-out",
71 | "accordion-up": "accordion-up 0.2s ease-out",
72 | },
73 | },
74 | },
75 | plugins: [require("tailwindcss-animate")],
76 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./src/*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------