├── public ├── profilee-b.png ├── drag_and_drop.webp ├── avatar.svg └── profilee.svg ├── .vscode └── settings.json ├── src ├── lib │ ├── constants.ts │ ├── utils.ts │ ├── posthog-provider.tsx │ └── routes.ts ├── app │ ├── auth │ │ ├── login │ │ │ └── page.tsx │ │ └── register │ │ │ └── page.tsx │ ├── (main) │ │ ├── builder │ │ │ ├── page.tsx │ │ │ ├── links │ │ │ │ └── page.tsx │ │ │ ├── _components │ │ │ │ ├── links │ │ │ │ │ ├── adhoc-links │ │ │ │ │ │ ├── toggle-group-items │ │ │ │ │ │ │ ├── edit-link.tsx │ │ │ │ │ │ │ └── edit-appearance.tsx │ │ │ │ │ │ └── adhoc-links.tsx │ │ │ │ │ └── social-links │ │ │ │ │ │ ├── social-icon.tsx │ │ │ │ │ │ ├── social-icons-section.tsx │ │ │ │ │ │ └── social-icon-drag.tsx │ │ │ │ ├── appearance │ │ │ │ │ ├── profile-section │ │ │ │ │ │ ├── dropzone.tsx │ │ │ │ │ │ ├── set-canvas-preview.tsx │ │ │ │ │ │ ├── edit-bio-title.tsx │ │ │ │ │ │ └── profile-section.tsx │ │ │ │ │ └── general-appearance-setting │ │ │ │ │ │ └── general-appearance-setting.tsx │ │ │ │ ├── popovers │ │ │ │ │ └── social-icons-popover.tsx │ │ │ │ ├── dialogs │ │ │ │ │ ├── social-icons-dialog.tsx │ │ │ │ │ ├── adhoc-links-dialog.tsx │ │ │ │ │ └── image-crop-dialog.tsx │ │ │ │ ├── navbar.tsx │ │ │ │ ├── page-elements.tsx │ │ │ │ ├── drag-overlay-wrapper.tsx │ │ │ │ ├── elements │ │ │ │ │ └── username.tsx │ │ │ │ └── preview │ │ │ │ │ ├── webpage.tsx │ │ │ │ │ └── preview.tsx │ │ │ ├── layout.tsx │ │ │ └── appearance │ │ │ │ └── page.tsx │ │ └── claim │ │ │ └── username │ │ │ └── page.tsx │ ├── api │ │ ├── auth │ │ │ └── [...nextauth] │ │ │ │ └── route.ts │ │ └── trpc │ │ │ └── [trpc] │ │ │ └── route.ts │ ├── (landing) │ │ ├── _components │ │ │ ├── landing-page.tsx │ │ │ ├── desktop-navbar.tsx │ │ │ ├── mobile-menu-navbar.tsx │ │ │ ├── navbar.tsx │ │ │ └── hero.tsx │ │ ├── layout.tsx │ │ └── page.tsx │ ├── [link] │ │ ├── page.tsx │ │ └── _components │ │ │ └── LinkClient.tsx │ └── layout.tsx ├── components │ ├── context │ │ ├── client-provider.tsx │ │ └── designer-context-action.tsx │ ├── ui │ │ ├── collapsible.tsx │ │ ├── loading-spinner.tsx │ │ ├── label.tsx │ │ ├── textarea.tsx │ │ ├── input.tsx │ │ ├── separator.tsx │ │ ├── progress.tsx │ │ ├── sonner.tsx │ │ ├── switch.tsx │ │ ├── tooltip.tsx │ │ ├── popover.tsx │ │ ├── toggle.tsx │ │ ├── alert.tsx │ │ ├── toggle-group.tsx │ │ ├── button.tsx │ │ ├── tabs.tsx │ │ ├── card.tsx │ │ ├── accordion.tsx │ │ ├── dialog.tsx │ │ └── form.tsx │ ├── tooltip-icon-text.tsx │ ├── logo.tsx │ ├── PostHogPageView.tsx │ ├── error.tsx │ ├── not-found.tsx │ ├── font-picker.tsx │ ├── footer.tsx │ └── auth │ │ ├── auth-pages-wrapper.tsx │ │ ├── login-account-card.tsx │ │ └── create-account-card.tsx ├── hooks │ ├── use-designer.tsx │ ├── use-scroll-top.tsx │ └── use-hex-to-rgba.tsx ├── server │ ├── db.ts │ ├── api │ │ ├── root.ts │ │ ├── routers │ │ │ ├── social-links.ts │ │ │ ├── images.ts │ │ │ ├── user-profile.ts │ │ │ ├── general-appearance.ts │ │ │ ├── user.ts │ │ │ └── adhoc-links.ts │ │ ├── utils │ │ │ └── user.ts │ │ ├── schemas │ │ │ └── index.ts │ │ └── trpc.ts │ └── auth.ts ├── trpc │ ├── server.ts │ ├── shared.ts │ └── react.tsx ├── middleware.ts ├── types │ └── types.ts ├── env.mjs └── styles │ └── globals.css ├── postcss.config.cjs ├── prisma └── migrations │ ├── 20240810085606_update_links │ └── migration.sql │ ├── 20240810134346_changed_default_count_to_1 │ └── migration.sql │ ├── migration_lock.toml │ ├── 20240810090250_added_count_in_the_link_analytics_model │ └── migration.sql │ ├── 20240814075617_updated_userprofile_schema │ └── migration.sql │ ├── 20240810133852_ │ └── migration.sql │ ├── 20240816091501_type │ └── migration.sql │ ├── 20240810090721_ │ └── migration.sql │ ├── 20240816091405_updated_typo │ └── migration.sql │ ├── 20240807090436_revert_the_link_theme_as_data_will_be_json │ └── migration.sql │ ├── 20240809091347_test │ └── migration.sql │ ├── 20240814172941_added_the_general_appearance_modal │ └── migration.sql │ ├── 20240818173248_changed_optional_to_required │ └── migration.sql │ └── 20240806065746_new_profille │ └── migration.sql ├── .prettierrc ├── prettier.config.mjs ├── components.json ├── next.config.mjs ├── .gitignore ├── tsconfig.json ├── .env.example ├── README.md ├── .eslintrc.cjs ├── tailwind.config.ts ├── package.json └── todo.js /public/profilee-b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mittalsam98/profilee/HEAD/public/profilee-b.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true, 3 | "cSpell.words": ["Profilee"] 4 | } 5 | -------------------------------------------------------------------------------- /public/drag_and_drop.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mittalsam98/profilee/HEAD/public/drag_and_drop.webp -------------------------------------------------------------------------------- /src/lib/constants.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_LOGIN_REDIRECT = '/'; 2 | export const OAUTH_REDIRECT = '/claim/username'; 3 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | 8 | module.exports = config; 9 | -------------------------------------------------------------------------------- /prisma/migrations/20240810085606_update_links/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE "LinkAnalytics" DROP CONSTRAINT "LinkAnalytics_adhocLinkId_fkey"; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20240810134346_changed_default_count_to_1/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "LinkAnalytics" ALTER COLUMN "count" SET DEFAULT 1; 3 | -------------------------------------------------------------------------------- /prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "jsxSingleQuote": true, 4 | "printWidth": 100, 5 | "trailingComma": "none", 6 | "tabWidth": 2, 7 | "semi": true 8 | } 9 | -------------------------------------------------------------------------------- /prisma/migrations/20240810090250_added_count_in_the_link_analytics_model/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "LinkAnalytics" ADD COLUMN "count" INTEGER NOT NULL DEFAULT 0; 3 | -------------------------------------------------------------------------------- /src/app/auth/login/page.tsx: -------------------------------------------------------------------------------- 1 | import LoginAccountCard from '@/components/auth/login-account-card'; 2 | 3 | export default function Login() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/auth/register/page.tsx: -------------------------------------------------------------------------------- 1 | import { CreateAccountCard } from '@/components/auth/create-account-card'; 2 | 3 | export default function Register() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/(main)/builder/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from 'next/navigation'; 2 | 3 | export default function Builder() { 4 | redirect(`/builder/appearance`); // Navigate to the new post page 5 | } 6 | -------------------------------------------------------------------------------- /prettier.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('prettier').Config & import('prettier-plugin-tailwindcss').options} */ 2 | const config = { 3 | plugins: ["prettier-plugin-tailwindcss"], 4 | }; 5 | 6 | export default config; 7 | -------------------------------------------------------------------------------- /src/app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import NextAuth from 'next-auth'; 2 | 3 | import { authOptions } from '@/server/auth'; 4 | 5 | const handler = NextAuth(authOptions); 6 | export { handler as GET, handler as POST }; 7 | -------------------------------------------------------------------------------- /prisma/migrations/20240814075617_updated_userprofile_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "UserProfile" ADD COLUMN "bioColor" TEXT, 3 | ADD COLUMN "bioFontSize" TEXT, 4 | ADD COLUMN "picBorder" TEXT, 5 | ADD COLUMN "titleColor" TEXT, 6 | ADD COLUMN "titleFontSize" TEXT; 7 | -------------------------------------------------------------------------------- /prisma/migrations/20240810133852_/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[adhocLinkId]` on the table `LinkAnalytics` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- CreateIndex 8 | CREATE UNIQUE INDEX "LinkAnalytics_adhocLinkId_key" ON "LinkAnalytics"("adhocLinkId"); 9 | -------------------------------------------------------------------------------- /prisma/migrations/20240816091501_type/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `linkCardSahdow` on the `GeneralAppearance` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "GeneralAppearance" DROP COLUMN "linkCardSahdow", 9 | ADD COLUMN "linkCardShadow" TEXT; 10 | -------------------------------------------------------------------------------- /src/components/context/client-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { SessionProvider } from 'next-auth/react'; 4 | 5 | export default function Provider({ 6 | children, 7 | session 8 | }: { 9 | children: React.ReactNode; 10 | session: any; 11 | }): React.ReactNode { 12 | return {children}; 13 | } 14 | -------------------------------------------------------------------------------- /prisma/migrations/20240810090721_/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[userId,adhocLinkId]` on the table `LinkAnalytics` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- CreateIndex 8 | CREATE UNIQUE INDEX "LinkAnalytics_userId_adhocLinkId_key" ON "LinkAnalytics"("userId", "adhocLinkId"); 9 | -------------------------------------------------------------------------------- /prisma/migrations/20240816091405_updated_typo/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `useSecondaryBackfround` on the `GeneralAppearance` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "GeneralAppearance" DROP COLUMN "useSecondaryBackfround", 9 | ADD COLUMN "useSecondaryBackground" BOOLEAN DEFAULT false; 10 | -------------------------------------------------------------------------------- /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.js", 8 | "css": "src/styles/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /src/components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as CollapsiblePrimitive from '@radix-ui/react-collapsible'; 4 | 5 | const Collapsible = CollapsiblePrimitive.Root; 6 | 7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger; 8 | 9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent; 10 | 11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }; 12 | -------------------------------------------------------------------------------- /src/hooks/use-designer.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { DesignerContext } from '@/components/context/designer-context'; 4 | import { useContext } from 'react'; 5 | 6 | function useDesigner() { 7 | const context = useContext(DesignerContext); 8 | 9 | if (!context) { 10 | throw new Error('useDesigner must be used within a DesignerContext'); 11 | } 12 | 13 | return context; 14 | } 15 | 16 | export default useDesigner; 17 | -------------------------------------------------------------------------------- /src/server/db.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client'; 2 | 3 | import { env } from '@/env.mjs'; 4 | 5 | const globalForPrisma = globalThis as unknown as { 6 | prisma: PrismaClient | undefined; 7 | }; 8 | 9 | export const db = 10 | globalForPrisma.prisma ?? 11 | new PrismaClient({ 12 | log: env.NODE_ENV === 'development' ? ['error', 'warn'] : ['error'] 13 | }); 14 | 15 | if (env.NODE_ENV !== 'production') globalForPrisma.prisma = db; 16 | -------------------------------------------------------------------------------- /prisma/migrations/20240807090436_revert_the_link_theme_as_data_will_be_json/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the `LinkTheme` table. If the table is not empty, all the data it contains will be lost. 5 | 6 | */ 7 | -- DropForeignKey 8 | ALTER TABLE "LinkTheme" DROP CONSTRAINT "LinkTheme_adhocLinkId_fkey"; 9 | 10 | -- DropTable 11 | DROP TABLE "LinkTheme"; 12 | 13 | -- DropEnum 14 | DROP TYPE "PropertSize"; 15 | 16 | -- DropEnum 17 | DROP TYPE "TextAlign"; 18 | -------------------------------------------------------------------------------- /src/app/(landing)/_components/landing-page.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@/components/ui/button'; 2 | import Image from 'next/image'; 3 | import Link from 'next/link'; 4 | import { RiGithubLine } from 'react-icons/ri'; 5 | import PricingSection from './pricing-section'; 6 | import Hero from './hero'; 7 | 8 | export default function LandingPage() { 9 | return ( 10 |
11 | 12 | 13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful 3 | * for Docker builds. 4 | */ 5 | await import('./src/env.mjs'); 6 | 7 | /** @type {import("next").NextConfig} */ 8 | const config = { 9 | images: { 10 | remotePatterns: [ 11 | { 12 | protocol: 'https', 13 | hostname: '**.amazonaws.com', 14 | port: '', 15 | pathname: '/**' 16 | } 17 | ] 18 | } 19 | }; 20 | 21 | export default config; 22 | -------------------------------------------------------------------------------- /src/app/(main)/builder/links/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { DndContext } from '@dnd-kit/core'; 3 | import DragOverlayWrapper from '../_components/drag-overlay-wrapper'; 4 | import SocialIconsSection from '../_components/links/social-links/social-icons-section'; 5 | import AdhocLinks from '../_components/links/adhoc-links/adhoc-links'; 6 | 7 | export default function Links() { 8 | return ( 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/app/(landing)/layout.tsx: -------------------------------------------------------------------------------- 1 | import Footer from '@/components/footer'; 2 | import Navbar from './_components/navbar'; 3 | 4 | const LandingPageLayout = ({ children }: { children: React.ReactNode }) => { 5 | return ( 6 |
7 |
8 | 9 |
10 |
{children}
11 |
12 |
13 | ); 14 | }; 15 | 16 | export default LandingPageLayout; 17 | -------------------------------------------------------------------------------- /src/hooks/use-scroll-top.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | 3 | export default function useScrollTop(threshold = 10) { 4 | const [scrolled, setScrolled] = useState(false); 5 | 6 | useEffect(() => { 7 | const handleScroll = () => { 8 | if (window.scrollY > threshold) { 9 | setScrolled(true); 10 | } else { 11 | setScrolled(false); 12 | } 13 | }; 14 | 15 | window.addEventListener('scroll', handleScroll); 16 | return () => window.removeEventListener('scroll', handleScroll); 17 | }, [threshold]); 18 | 19 | return scrolled; 20 | } 21 | -------------------------------------------------------------------------------- /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 | 8 | export const formatBytes = (bytes: number, decimals = 2) => { 9 | if (!+bytes) return '0 Bytes'; 10 | 11 | const k = 1024; 12 | const dm = decimals < 0 ? 0 : decimals; 13 | const sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; 14 | 15 | const i = Math.floor(Math.log(bytes) / Math.log(k)); 16 | 17 | return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`; 18 | }; 19 | -------------------------------------------------------------------------------- /src/components/tooltip-icon-text.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode, useState } from 'react'; 2 | import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; 3 | 4 | const ToolTipForTextAndIcon = ({ 5 | children, 6 | text, 7 | onClick 8 | }: { 9 | children: ReactNode; 10 | text: string; 11 | onClick?: () => void; 12 | }) => { 13 | return ( 14 | 15 | 16 | {children} 17 | {text} 18 | 19 | 20 | ); 21 | }; 22 | 23 | export default ToolTipForTextAndIcon; 24 | -------------------------------------------------------------------------------- /src/components/ui/loading-spinner.tsx: -------------------------------------------------------------------------------- 1 | export interface ISVGProps extends React.SVGProps { 2 | size?: number; 3 | className?: string; 4 | } 5 | 6 | export const LoadingSpinner = ({ size = 24, className, ...props }: ISVGProps) => { 7 | return ( 8 | 21 | 22 | 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /src/lib/posthog-provider.tsx: -------------------------------------------------------------------------------- 1 | // app/providers.tsx 2 | 'use client'; 3 | import posthog from 'posthog-js'; 4 | import { PostHogProvider } from 'posthog-js/react'; 5 | 6 | if (typeof window !== 'undefined') { 7 | posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, { 8 | api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST, 9 | person_profiles: 'identified_only', 10 | // loaded: (posthog) => { 11 | // if (process.env.NODE_ENV === 'development') posthog.debug(); 12 | // }, 13 | capture_pageview: false // Disable automatic pageview capture, as we capture manually 14 | }); 15 | } 16 | 17 | export function PHProvider({ children }: { children: React.ReactNode }) { 18 | return {children}; 19 | } 20 | -------------------------------------------------------------------------------- /src/lib/routes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An array of routes that are accessible to the public 3 | * These routes do not require authentication 4 | * @type {string[]} 5 | */ 6 | export const publicRoutes = ['/']; 7 | 8 | /** 9 | * An array of routes that are used for authentication 10 | * These routes will redirect logged in users to /settings 11 | * @type {string[]} 12 | */ 13 | export const authRoutes = ['/auth/login', '/auth/register', '/auth/error']; 14 | 15 | /** 16 | * The prefix for API authentication routes 17 | * Routes that start with this prefix are used for API authentication purposes 18 | * @type {string} 19 | */ 20 | export const apiAuthPrefix = '/api/auth'; 21 | 22 | /** 23 | * The default redirect path after logging in 24 | * @type {string} 25 | */ 26 | -------------------------------------------------------------------------------- /.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 | # database 12 | /prisma/db.sqlite 13 | /prisma/db.sqlite-journal 14 | 15 | # next.js 16 | /.next/ 17 | /out/ 18 | next-env.d.ts 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # local env files 34 | # do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables 35 | .env 36 | .env*.local 37 | 38 | # vercel 39 | .vercel 40 | 41 | # typescript 42 | *.tsbuildinfo 43 | -------------------------------------------------------------------------------- /src/app/api/trpc/[trpc]/route.ts: -------------------------------------------------------------------------------- 1 | import { fetchRequestHandler } from '@trpc/server/adapters/fetch'; 2 | import { type NextRequest } from 'next/server'; 3 | 4 | import { env } from '@/env.mjs'; 5 | import { appRouter } from '@/server/api/root'; 6 | import { createTRPCContext } from '@/server/api/trpc'; 7 | 8 | const handler = (req: NextRequest) => 9 | fetchRequestHandler({ 10 | endpoint: '/api/trpc', 11 | req, 12 | router: appRouter, 13 | createContext: () => createTRPCContext({ req }), 14 | onError: 15 | env.NODE_ENV === 'development' 16 | ? ({ path, error }) => { 17 | console.error(`❌ tRPC failed on ${path ?? ''}: ${error.message}`); 18 | } 19 | : undefined 20 | }); 21 | 22 | export { handler as GET, handler as POST }; 23 | -------------------------------------------------------------------------------- /src/components/logo.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils'; 2 | import { Poppins } from 'next/font/google'; 3 | import React from 'react'; 4 | import Link from 'next/link'; 5 | import Image from 'next/image'; 6 | 7 | const font = Poppins({ 8 | subsets: ['latin'], 9 | weight: ['400', '600'] 10 | }); 11 | 12 | type LogoProps = { 13 | height: number; 14 | width: number; 15 | className?: string; 16 | }; 17 | 18 | export default function Logo({ height, width, className }: LogoProps) { 19 | return ( 20 | 21 | Profilee Logo 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /src/app/(main)/builder/_components/links/adhoc-links/toggle-group-items/edit-link.tsx: -------------------------------------------------------------------------------- 1 | import { LoadingSpinner } from '@/components/ui/loading-spinner'; 2 | import { api } from '@/trpc/react'; 3 | 4 | interface PropsTypes { 5 | adhocLinkId: string; 6 | } 7 | 8 | export default function EditLink({ adhocLinkId }: PropsTypes) { 9 | const { data, isFetching, isSuccess, isError, error } = api.adHocLink.getLinkAnalytics.useQuery({ 10 | adhocLinkId: adhocLinkId 11 | }); 12 | return isFetching ? ( 13 | 14 | ) : ( 15 |
16 |
17 |

Total Views

18 |
19 |

{data?.count}

20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/components/PostHogPageView.tsx: -------------------------------------------------------------------------------- 1 | // app/PostHogPageView.tsx 2 | 'use client'; 3 | 4 | import { usePathname, useSearchParams } from 'next/navigation'; 5 | import { useEffect } from 'react'; 6 | import { usePostHog } from 'posthog-js/react'; 7 | 8 | export default function PostHogPageView(): null { 9 | const pathname = usePathname(); 10 | const searchParams = useSearchParams(); 11 | const posthog = usePostHog(); 12 | useEffect(() => { 13 | // Track pageviews 14 | if (pathname && posthog) { 15 | let url = window.origin + pathname; 16 | if (searchParams.toString()) { 17 | url = url + `?${searchParams.toString()}`; 18 | } 19 | posthog.capture('$pageview', { 20 | $current_url: url 21 | }); 22 | } 23 | }, [pathname, searchParams, posthog]); 24 | 25 | return null; 26 | } 27 | -------------------------------------------------------------------------------- /src/hooks/use-hex-to-rgba.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | 3 | // Function to convert hex to rgba with opacity 4 | const hexToRGBA = (hex: string, alpha: number) => { 5 | const bigint = parseInt(hex.replace(/^#/, ''), 16); 6 | const r = (bigint >> 16) & 255; 7 | const g = (bigint >> 8) & 255; 8 | const b = bigint & 255; 9 | return `rgba(${r}, ${g}, ${b}, ${alpha})`; 10 | }; 11 | 12 | const useHexToRGBA = (hexColor: string, opacity: number) => { 13 | const [rgbaColor, setRGBAColor] = useState(''); 14 | 15 | useEffect(() => { 16 | if (hexColor && opacity >= 0 && opacity <= 1) { 17 | const convertedColor = hexToRGBA(hexColor, opacity); 18 | setRGBAColor(convertedColor); 19 | } 20 | }, [hexColor, opacity]); 21 | 22 | return rgbaColor; 23 | }; 24 | 25 | export default useHexToRGBA; 26 | -------------------------------------------------------------------------------- /src/trpc/server.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createTRPCProxyClient, 3 | loggerLink, 4 | unstable_httpBatchStreamLink, 5 | } from "@trpc/client"; 6 | import { headers } from "next/headers"; 7 | 8 | import { type AppRouter } from "@/server/api/root"; 9 | import { getUrl, transformer } from "./shared"; 10 | 11 | export const api = createTRPCProxyClient({ 12 | transformer, 13 | links: [ 14 | loggerLink({ 15 | enabled: (op) => 16 | process.env.NODE_ENV === "development" || 17 | (op.direction === "down" && op.result instanceof Error), 18 | }), 19 | unstable_httpBatchStreamLink({ 20 | url: getUrl(), 21 | headers() { 22 | const heads = new Map(headers()); 23 | heads.set("x-trpc-source", "rsc"); 24 | return Object.fromEntries(heads); 25 | }, 26 | }), 27 | ], 28 | }); 29 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /prisma/migrations/20240809091347_test/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "EventType" AS ENUM ('CLICK', 'OPEN'); 3 | 4 | -- CreateTable 5 | CREATE TABLE "LinkAnalytics" ( 6 | "id" TEXT NOT NULL, 7 | "eventType" "EventType" NOT NULL, 8 | "timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 9 | "ipAddress" TEXT, 10 | "userAgent" TEXT, 11 | "adhocLinkId" TEXT NOT NULL, 12 | "userId" TEXT, 13 | 14 | CONSTRAINT "LinkAnalytics_pkey" PRIMARY KEY ("id") 15 | ); 16 | 17 | -- AddForeignKey 18 | ALTER TABLE "LinkAnalytics" ADD CONSTRAINT "LinkAnalytics_adhocLinkId_fkey" FOREIGN KEY ("adhocLinkId") REFERENCES "AdhocLink"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 19 | 20 | -- AddForeignKey 21 | ALTER TABLE "LinkAnalytics" ADD CONSTRAINT "LinkAnalytics_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; 22 | -------------------------------------------------------------------------------- /src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { cn } from '@/lib/utils'; 4 | 5 | export interface TextareaProps extends React.TextareaHTMLAttributes {} 6 | 7 | const Textarea = React.forwardRef( 8 | ({ className, ...props }, ref) => { 9 | return ( 10 |