Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,7 @@ NEXT_PUBLIC_POSTHOG_HOST=replace_me

# used to show breakpoint overlay in development
NEXT_PUBLIC_IS_LOCAL=true

# Tell if it's development or not
## .env.development, add: NODE_ENV=development
## .env.production, add: NODE_ENV=production
29 changes: 15 additions & 14 deletions src/app-config.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
export const appConfig: {
mode: "comingSoon" | "maintenance" | "live";
mode: "comingSoon" | "maintenance" | "live"
} = {
mode: "live",
};
mode: "live",
}

export const protectedRoutes = ["/purchases", "/dashboard"];
export const applicationName = "Group Finder";
export const companyName = "Groupie, LLC";
export const protectedRoutes = ["/purchases", "/dashboard"]
export const applicationName = "Group Finder"
export const companyName = "Groupie, LLC"
export const siteName = "GroupFinder.com"

export const MAX_UPLOAD_IMAGE_SIZE_IN_MB = 5;
export const MAX_UPLOAD_IMAGE_SIZE = 1024 * 1024 * MAX_UPLOAD_IMAGE_SIZE_IN_MB;
export const MAX_UPLOAD_IMAGE_SIZE_IN_MB = 5
export const MAX_UPLOAD_IMAGE_SIZE = 1024 * 1024 * MAX_UPLOAD_IMAGE_SIZE_IN_MB

export const TOKEN_LENGTH = 32;
export const TOKEN_TTL = 1000 * 60 * 5; // 5 min
export const VERIFY_EMAIL_TTL = 1000 * 60 * 60 * 24 * 7; // 7 days
export const TOKEN_LENGTH = 32
export const TOKEN_TTL = 1000 * 60 * 5 // 5 min
export const VERIFY_EMAIL_TTL = 1000 * 60 * 60 * 24 * 7 // 7 days

export const MAX_GROUP_LIMIT = 10;
export const MAX_GROUP_PREMIUM_LIMIT = 50;
export const MAX_GROUP_LIMIT = 10
export const MAX_GROUP_PREMIUM_LIMIT = 50

export const afterLoginUrl = "/dashboard";
export const afterLoginUrl = "/dashboard"
171 changes: 87 additions & 84 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,96 +1,99 @@
import "@/app/globals.css";
import "fumadocs-ui/style.css";
import type { Metadata } from "next";
import NextTopLoader from "nextjs-toploader";
import { Toaster } from "@/components/ui/toaster";
import { cn } from "@/lib/utils";
import { ReactNode, Suspense } from "react";
import { Providers } from "@/providers/providers";
import { Footer } from "@/components/footer";
import { applicationName, appConfig } from "@/app-config";
import PostHogPageView from "@/components/posthog-page-view";
import { ComingSoonFooter } from "@/app/(coming-soon)/footer";
import { Header } from "@/app/_header/header";
import "@/app/globals.css"
import "fumadocs-ui/style.css"
import type { Metadata } from "next"
import NextTopLoader from "nextjs-toploader"
import { Toaster } from "@/components/ui/toaster"
import { cn } from "@/lib/utils"
import { ReactNode, Suspense } from "react"
import { Providers } from "@/providers/providers"
import { Footer } from "@/components/footer"
import { applicationName, appConfig } from "@/app-config"
import PostHogPageView from "@/components/posthog-page-view"
import { ComingSoonFooter } from "@/app/(coming-soon)/footer"
import { Header } from "@/app/_header/header"

import { Archivo } from "next/font/google";
import { Libre_Franklin } from "next/font/google";
import { BreakpointOverlay } from "@/components/breakpoint-overlay";
import { Archivo } from "next/font/google"
import { Libre_Franklin } from "next/font/google"
import { BreakpointOverlay } from "@/components/breakpoint-overlay"

const archivo = Archivo({
subsets: ["latin"],
display: "swap",
variable: "--font-archivo",
});
subsets: ["latin"],
display: "swap",
variable: "--font-archivo",
})
const libre_franklin = Libre_Franklin({
subsets: ["latin"],
display: "swap",
variable: "--font-libre_franklin",
});
subsets: ["latin"],
display: "swap",
variable: "--font-libre_franklin",
})

const { mode } = appConfig;
const { mode } = appConfig

export const metadata: Metadata = {
title: applicationName,
icons: [
{ rel: "icon", type: "image/png", sizes: "48x48", url: "/favicon.ico" },
],
keywords:
"next.js, starter kit, saas, ecommerce, digital products, saas code kit, indie hacking, indie hacker kit, micro saas, entrepreneurship, Code Starter Kit, SaaS Product Launch, Code Documentation Tutorial, Beginner Coding Kit, Start-up SaaS Kit, Coding Guides and Resources, Video Tutorials for Coding, Beginner SaaS Guide, Launch your First SaaS, Step-by-step Coding Kit, SaaS Launch Kit, Software as a Service Starter, Easy Code Launch Kit, Coding Skills for SaaS, Starter Kit for SaaS, Code, Document, Launch, Comprehensive Coding Starter Kit, Master SaaS Product Launch, SaaS Documentation Tutorial, First-Time Coders Kit, SaaS coding course, Initiate SaaS Journey, Seamless SaaS Launch Guide, First SaaS Product Guidance, Bootstrap SaaS Tutorial, Ultimate SaaS Starter Pack, Learning Guide for SaaS, DIY SaaS Kit, Code your SaaS Product, All-in-one Coding Starter Kit",
description:
"The code kit to help you quickly setup an online store and sell your digital assets without a middleman skipping off the top of your profits.",
openGraph:
mode === "comingSoon"
? {
title: "WDCStarterKit.com",
description:
"I'm building the ultimate next.js starter kit to help you hit the ground runnning on your next saas product.",
url: "https://wdcstarterkit.com",
siteName: "WDC StarterKit",
type: "website",
images: [
{
url: "https://wdcstarterkit.com/starterkitcard.png",
secureUrl: "https://wdcstarterkit.com/starterkitcard.png",
width: 800,
height: 418,
alt: "The WDC StarterKit social media card image",
},
],
}
: undefined,
};
title:
process.env.NODE_ENV === "development"
? `DEV - ${applicationName} `
: applicationName,
icons: [
{ rel: "icon", type: "image/png", sizes: "48x48", url: "/favicon.ico" },
],
keywords:
"next.js, starter kit, saas, ecommerce, digital products, saas code kit, indie hacking, indie hacker kit, micro saas, entrepreneurship, Code Starter Kit, SaaS Product Launch, Code Documentation Tutorial, Beginner Coding Kit, Start-up SaaS Kit, Coding Guides and Resources, Video Tutorials for Coding, Beginner SaaS Guide, Launch your First SaaS, Step-by-step Coding Kit, SaaS Launch Kit, Software as a Service Starter, Easy Code Launch Kit, Coding Skills for SaaS, Starter Kit for SaaS, Code, Document, Launch, Comprehensive Coding Starter Kit, Master SaaS Product Launch, SaaS Documentation Tutorial, First-Time Coders Kit, SaaS coding course, Initiate SaaS Journey, Seamless SaaS Launch Guide, First SaaS Product Guidance, Bootstrap SaaS Tutorial, Ultimate SaaS Starter Pack, Learning Guide for SaaS, DIY SaaS Kit, Code your SaaS Product, All-in-one Coding Starter Kit",
description:
"The code kit to help you quickly setup an online store and sell your digital assets without a middleman skipping off the top of your profits.",
openGraph:
mode === "comingSoon"
? {
title: "WDCStarterKit.com",
description:
"I'm building the ultimate next.js starter kit to help you hit the ground runnning on your next saas product.",
url: "https://wdcstarterkit.com",
siteName: "WDC StarterKit",
type: "website",
images: [
{
url: "https://wdcstarterkit.com/starterkitcard.png",
secureUrl:
"https://wdcstarterkit.com/starterkitcard.png",
width: 800,
height: 418,
alt: "The WDC StarterKit social media card image",
},
],
}
: undefined,
}

export default async function RootLayout({
children,
children,
}: Readonly<{
children: ReactNode;
children: ReactNode
}>) {
return (
<html lang="en" suppressHydrationWarning>
<body
className={cn(
"min-h-screen bg-background antialiased",
archivo.variable + " " + libre_franklin.variable
)}
>
<Providers>
<Suspense>
<PostHogPageView />
</Suspense>
<NextTopLoader />
<div className="flex flex-col w-full">
{appConfig.mode === "live" && <Header />}
<div>{children}</div>
{appConfig.mode === "comingSoon" ? (
<ComingSoonFooter />
) : (
<Footer />
)}
</div>
</Providers>
<Toaster />
<BreakpointOverlay />
</body>
</html>
);
return (
<html lang="en" suppressHydrationWarning>
<body
className={cn(
"min-h-screen bg-background antialiased",
archivo.variable + " " + libre_franklin.variable
)}>
<Providers>
<Suspense>
<PostHogPageView />
</Suspense>
<NextTopLoader />
<div className="flex flex-col w-full">
{appConfig.mode === "live" && <Header />}
<div>{children}</div>
{appConfig.mode === "comingSoon" ? (
<ComingSoonFooter />
) : (
<Footer />
)}
</div>
</Providers>
<Toaster />
<BreakpointOverlay />
</body>
</html>
)
}
112 changes: 60 additions & 52 deletions src/app/users/[userId]/profile-header.tsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,68 @@
import { Button } from "@/components/ui/button";
import { getCurrentUser } from "@/lib/session";
import { headerStyles, pageTitleStyles } from "@/styles/common";
import { btnIconStyles, btnStyles } from "@/styles/icons";
import { getUserProfileUseCase } from "@/use-cases/users";
import { SquareUser } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { FollowButton } from "./follow-button";
import { isFollowingUserUseCase } from "@/use-cases/following";
import { UnfollowButton } from "./unfollow-button";
import { cn } from "@/lib/utils";
import { UserId } from "@/use-cases/types";
import { Button } from "@/components/ui/button"
import { getCurrentUser } from "@/lib/session"
import { headerStyles, pageTitleStyles } from "@/styles/common"
import { btnIconStyles, btnStyles } from "@/styles/icons"
import { getUserProfileUseCase } from "@/use-cases/users"
import { SquareUser } from "lucide-react"
import Image from "next/image"
import Link from "next/link"
import { FollowButton } from "./follow-button"
import { isFollowingUserUseCase } from "@/use-cases/following"
import { UnfollowButton } from "./unfollow-button"
import { cn } from "@/lib/utils"
import { UserId } from "@/use-cases/types"
import { siteName } from "@/app-config"

export async function ProfileHeader({ userId }: { userId: UserId }) {
const user = await getCurrentUser();
const profile = await getUserProfileUseCase(userId);
const isOwnProfile = user?.id === userId;
const user = await getCurrentUser()
const profile = await getUserProfileUseCase(userId)
const isUnderDevelopment = process.env.NODE_ENV === "development"
const url = isUnderDevelopment
? "http://localhost:3000"
: `https://${siteName}`
const profileImage = `${url}/api/users/2/images/${profile.imageId}`
const isOwnProfile = user?.id === userId

const isFollowingUser = user
? await isFollowingUserUseCase(user, userId)
: false;
const isFollowingUser = user
? await isFollowingUserUseCase(user, userId)
: false

const shouldShowFollowButtons = user && !isOwnProfile;
const shouldShowFollowButtons = user && !isOwnProfile

return (
<div className={cn(headerStyles, "py-8")}>
<div className="container mx-auto">
<div className="flex justify-between items-center">
<div className="flex flex-col items-center md:flex-row gap-8">
<Image
src={profile.image ?? "/group.jpeg"}
width={60}
height={60}
alt="image of the group"
className="rounded-full object-cover h-[60px]"
/>
return (
<div className={cn(headerStyles, "py-8")}>
<div className="container mx-auto">
<div className="flex justify-between items-center">
<div className="flex flex-col items-center md:flex-row gap-8">
<Image
src={profile.image ?? profileImage ?? "/group.jpeg"}
width={60}
height={60}
alt="image of the group"
className="rounded-full object-cover h-[60px]"
/>
<h1 className={pageTitleStyles}>
{profile.displayName}{" "}
</h1>
</div>

<h1 className={pageTitleStyles}>{profile.displayName} </h1>
</div>
{shouldShowFollowButtons &&
(isFollowingUser ? (
<UnfollowButton foreignUserId={userId} />
) : (
<FollowButton foreignUserId={userId} />
))}

{shouldShowFollowButtons &&
(isFollowingUser ? (
<UnfollowButton foreignUserId={userId} />
) : (
<FollowButton foreignUserId={userId} />
))}

{isOwnProfile && (
<Button asChild className={btnStyles}>
<Link href={`/dashboard/settings/profile`}>
<SquareUser className={btnIconStyles} /> Edit your Profile
</Link>
</Button>
)}
</div>
</div>
</div>
);
{isOwnProfile && (
<Button asChild className={btnStyles}>
<Link href={`/dashboard/settings/profile`}>
<SquareUser className={btnIconStyles} /> Edit
your Profile
</Link>
</Button>
)}
</div>
</div>
</div>
)
}
28 changes: 16 additions & 12 deletions src/components/breakpoint-overlay.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
"use client";
"use client"

export function BreakpointOverlay() {
if (process.env.NEXT_PUBLIC_IS_LOCAL !== "true") return null;
if (
process.env.NEXT_PUBLIC_IS_LOCAL !== "true" ||
process.env.NODE_ENV === "production"
)
return null

return (
<div className="fixed bottom-2 right-2 bg-yellow-300/50 bg-opacity-75 text-white px-2 py-1 rounded-md text-sm z-50">
<span className="sm:hidden">xs</span>
<span className="hidden sm:inline md:hidden">sm</span>
<span className="hidden md:inline lg:hidden">md</span>
<span className="hidden lg:inline xl:hidden">lg</span>
<span className="hidden xl:inline 2xl:hidden">xl</span>
<span className="hidden 2xl:inline">2xl</span>
</div>
);
return (
<div className="fixed bottom-2 right-2 bg-yellow-300/50 bg-opacity-75 text-white px-2 py-1 rounded-md text-sm z-50">
<span className="sm:hidden">xs</span>
<span className="hidden sm:inline md:hidden">sm</span>
<span className="hidden md:inline lg:hidden">md</span>
<span className="hidden lg:inline xl:hidden">lg</span>
<span className="hidden xl:inline 2xl:hidden">xl</span>
<span className="hidden 2xl:inline">2xl</span>
</div>
)
}