├── bun.lockb ├── public └── favicon.ico ├── .prettierrc.json ├── postcss.config.js ├── next.config.js ├── .vscode └── settings.json ├── app ├── api │ └── comments │ │ └── route.tsx ├── signup │ └── page.tsx ├── forgot-password │ └── page.tsx ├── verify │ └── page.tsx ├── dashboard │ └── page.tsx ├── login │ └── page.tsx ├── contact │ └── page.tsx ├── reset-password │ └── page.tsx ├── events │ ├── [slug] │ │ └── page.tsx │ └── page.tsx ├── blog │ ├── [slug] │ │ └── page.tsx │ └── page.tsx ├── services │ ├── [slug] │ │ └── page.tsx │ └── page.tsx ├── actions │ └── checkout.ts ├── page.tsx ├── about │ └── page.tsx ├── work │ ├── page.tsx │ └── [slug] │ │ └── page.tsx ├── globals.css └── layout.tsx ├── components ├── theme-provider.tsx ├── tailwind-indicator.tsx ├── theme-toggle.tsx ├── Header.tsx ├── project-card.tsx ├── Footer.tsx ├── Banner.tsx └── ui │ └── dropdown-menu.tsx ├── components.json ├── cosmic ├── client.ts ├── blocks │ ├── image-gallery │ │ ├── ImageGallery.tsx │ │ └── ImageGalleryClient.tsx │ ├── events │ │ ├── EventsList.tsx │ │ ├── EventCard.tsx │ │ └── SingleEvent.tsx │ ├── ecommerce │ │ ├── ProductList.tsx │ │ ├── CartProvider.tsx │ │ ├── ProductCard.tsx │ │ ├── AddToCart.tsx │ │ ├── SingleProduct.tsx │ │ └── CheckOut.tsx │ ├── blog │ │ ├── BlogList.tsx │ │ ├── BlogCard.tsx │ │ └── SingleBlog.tsx │ ├── team │ │ ├── TeamList.tsx │ │ └── TeamCard.tsx │ ├── testimonials │ │ ├── Testimonials.tsx │ │ └── Testimonial.tsx │ ├── faqs │ │ ├── FAQs.tsx │ │ └── Accordion.tsx │ ├── user-management │ │ ├── AuthButtons.tsx │ │ ├── AuthContext.tsx │ │ ├── VerifyClient.tsx │ │ ├── LoginClient.tsx │ │ ├── SignUpClient.tsx │ │ ├── ForgotPasswordForm.tsx │ │ ├── DashboardClient.tsx │ │ ├── ResetPasswordForm.tsx │ │ ├── AuthForm.tsx │ │ ├── UserProfileForm.tsx │ │ └── actions.ts │ ├── navigation-menu │ │ ├── NavMenu.tsx │ │ └── MobileNav.tsx │ ├── contact-form │ │ ├── actions.tsx │ │ └── ContactForm.tsx │ ├── comments │ │ ├── Comments.tsx │ │ └── CommentForm.tsx │ └── pages │ │ ├── Page.tsx │ │ └── PageSection.tsx ├── elements │ ├── Label.tsx │ ├── TextArea.tsx │ ├── Input.tsx │ └── Button.tsx └── utils.ts ├── .env.example ├── .gitignore ├── tsconfig.json ├── package.json ├── tailwind.config.js └── README.md /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmicjs/agency-template/main/bun.lockb -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmicjs/agency-template/main/public/favicon.ico -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": false 6 | } -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | domains: ["imgix.cosmicjs.com"], // Add your Cosmic CDN domain 5 | }, 6 | }; 7 | 8 | module.exports = nextConfig; 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.formatOnSave": true, 4 | "editor.formatOnPaste": false, 5 | "prettier.useEditorConfig": false, 6 | "prettier.useTabs": false, 7 | "prettier.configPath": ".prettierrc.json" 8 | } 9 | -------------------------------------------------------------------------------- /app/api/comments/route.tsx: -------------------------------------------------------------------------------- 1 | // app/api/comments/route.ts 2 | import { type NextRequest } from "next/server"; 3 | import { cosmic } from "@/cosmic/client"; 4 | 5 | export async function POST(request: NextRequest) { 6 | const res = await request.json(); 7 | const data = await cosmic.objects.insertOne(res.comment); 8 | return Response.json(data); 9 | } 10 | -------------------------------------------------------------------------------- /app/signup/page.tsx: -------------------------------------------------------------------------------- 1 | import SignUpClient from "@/cosmic/blocks/user-management/SignUpClient"; 2 | import { signUp } from "@/cosmic/blocks/user-management/actions"; 3 | 4 | export default function SignUpPage() { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { ThemeProvider as NextThemesProvider } from "next-themes"; 5 | import { type ThemeProviderProps } from "next-themes/dist/types"; 6 | 7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 8 | return {children}; 9 | } 10 | -------------------------------------------------------------------------------- /app/forgot-password/page.tsx: -------------------------------------------------------------------------------- 1 | import ForgotPasswordForm from "@/cosmic/blocks/user-management/ForgotPasswordForm"; 2 | import { forgotPassword } from "@/cosmic/blocks/user-management/actions"; 3 | 4 | export default function ForgotPasswordPage() { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /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": "app/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/cosmic/utils" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/verify/page.tsx: -------------------------------------------------------------------------------- 1 | // app/verify/page.tsx 2 | import { Suspense } from "react"; 3 | import VerifyClient from "@/cosmic/blocks/user-management/VerifyClient"; 4 | import { Loader2 } from "lucide-react"; 5 | 6 | export default function VerifyPage() { 7 | return ( 8 | 11 | } 12 | > 13 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /cosmic/client.ts: -------------------------------------------------------------------------------- 1 | import { createBucketClient } from "@cosmicjs/sdk"; 2 | 3 | // Make sure to add/update your ENV variables 4 | export const cosmic = createBucketClient({ 5 | bucketSlug: process.env.COSMIC_BUCKET_SLUG || "COSMIC_BUCKET_SLUG", 6 | readKey: process.env.COSMIC_READ_KEY || "COSMIC_READ_KEY", 7 | writeKey: process.env.COSMIC_WRITE_KEY || "COSMIC_WRITE_KEY", 8 | apiEnvironment: 9 | (process.env.COSMIC_API_ENVIRONMENT as "production" | "staging") || 10 | "production", 11 | }); 12 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # .env.local 2 | COSMIC_BUCKET_SLUG=change_to_your_bucket_slug 3 | COSMIC_READ_KEY=change_to_your_bucket_read_key 4 | COSMIC_WRITE_KEY=change_to_your_bucket_write_key 5 | 6 | NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=change_to_your_stripe_public_key 7 | STRIPE_SECRET_KEY=change_to_your_stripe_secret_key 8 | 9 | RESEND_API_KEY=change_to_your_resend_api_key 10 | NEXT_PUBLIC_APP_URL=change_to_your_app_url 11 | NEXT_PUBLIC_APP_NAME="Change to your app name" 12 | SUPPORT_EMAIL=change_to_your_support_email 13 | CONTACT_EMAIL=change_to_your_contact_email -------------------------------------------------------------------------------- /.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 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /app/dashboard/page.tsx: -------------------------------------------------------------------------------- 1 | // app/dashboard/page.tsx 2 | import { Suspense } from "react"; 3 | import DashboardClient from "@/cosmic/blocks/user-management/DashboardClient"; 4 | import { Loader2 } from "lucide-react"; 5 | 6 | export default function DashboardPage() { 7 | return ( 8 |
9 | 12 | } 13 | > 14 | 15 | 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /app/login/page.tsx: -------------------------------------------------------------------------------- 1 | // app/login/page.tsx 2 | import { Suspense } from "react"; 3 | import LoginClient from "@/cosmic/blocks/user-management/LoginClient"; 4 | import { login } from "@/cosmic/blocks/user-management/actions"; 5 | import { Loader2 } from "lucide-react"; 6 | 7 | export default function LoginPage() { 8 | return ( 9 |
10 | 13 | } 14 | > 15 | 16 | 17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /app/contact/page.tsx: -------------------------------------------------------------------------------- 1 | // app/contact/page.tsx 2 | import { ContactForm } from "@/cosmic/blocks/contact-form/ContactForm"; 3 | export default async function ContactPage() { 4 | return ( 5 |
6 |
7 |
8 |

9 | Contact 10 |

11 |
12 | 13 |
14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /app/reset-password/page.tsx: -------------------------------------------------------------------------------- 1 | // app/reset-password/page.tsx 2 | import { redirect } from "next/navigation"; 3 | import ResetPasswordForm from "@/cosmic/blocks/user-management/ResetPasswordForm"; 4 | import { resetPassword } from "@/cosmic/blocks/user-management/actions"; 5 | 6 | export default function ResetPasswordPage({ 7 | searchParams, 8 | }: { 9 | searchParams: { token?: string }; 10 | }) { 11 | const token = searchParams.token; 12 | 13 | if (!token) { 14 | redirect("/login"); 15 | } 16 | 17 | return ( 18 |
19 | 20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /components/tailwind-indicator.tsx: -------------------------------------------------------------------------------- 1 | export function TailwindIndicator() { 2 | if (process.env.NODE_ENV === "production") return null; 3 | 4 | return ( 5 |
6 |
xs
7 |
sm
8 |
md
9 |
lg
10 |
xl
11 |
2xl
12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /cosmic/blocks/image-gallery/ImageGallery.tsx: -------------------------------------------------------------------------------- 1 | import { ImageGalleryClient } from "./ImageGalleryClient"; 2 | import { cosmic } from "@/cosmic/client"; 3 | 4 | export async function ImageGallery({ 5 | query, 6 | className, 7 | status, 8 | }: { 9 | query: any; 10 | className?: string; 11 | status?: "draft" | "published" | "any"; 12 | }) { 13 | const { object: page } = await cosmic.objects 14 | .findOne(query) 15 | .props("slug,title,metadata") 16 | .depth(1) 17 | .status(status ? status : "published"); 18 | if (!page.metadata.gallery?.length) return <>; 19 | return ( 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /app/events/[slug]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SingleEvent } from "@/cosmic/blocks/events/SingleEvent"; 2 | import { cosmic } from "@/cosmic/client"; 3 | 4 | export const revalidate = 60; 5 | 6 | export async function generateStaticParams() { 7 | const { objects: events } = await cosmic.objects.find({ 8 | type: "events", 9 | }); 10 | return events.map((event: { slug: string }) => ({ 11 | slug: event.slug, 12 | })); 13 | } 14 | 15 | export default async function SingleEventPage({ 16 | params, 17 | }: { 18 | params: { slug: string }; 19 | }) { 20 | return ( 21 |
22 | 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /cosmic/blocks/events/EventsList.tsx: -------------------------------------------------------------------------------- 1 | import { cosmic } from "@/cosmic/client"; 2 | import { EventCard, EventCardType } from "./EventCard"; 3 | 4 | export async function EventsList({ 5 | query, 6 | className, 7 | preview, 8 | }: { 9 | query: any; 10 | className?: string; 11 | preview?: boolean; 12 | }) { 13 | const { objects: events } = await cosmic.objects 14 | .find(query) 15 | .props("title,slug,metadata") 16 | .depth(1) 17 | .status(preview ? "any" : "published"); 18 | 19 | return ( 20 |
21 | {events?.map((event: EventCardType) => { 22 | return ; 23 | })} 24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /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 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /cosmic/blocks/ecommerce/ProductList.tsx: -------------------------------------------------------------------------------- 1 | import { cosmic } from "@/cosmic/client"; 2 | import { ProductCard, ProductType } from "./ProductCard"; 3 | 4 | export async function ProductList({ 5 | query, 6 | className, 7 | status, 8 | }: { 9 | query: any; 10 | className?: string; 11 | status?: "draft" | "published" | "any"; 12 | }) { 13 | const { objects: products } = await cosmic.objects 14 | .find(query) 15 | .props("id,slug,title,metadata") 16 | .depth(1) 17 | .status(status ? status : "published"); 18 | 19 | return ( 20 |
21 | {products.map((product: ProductType) => { 22 | return ; 23 | })} 24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /app/events/page.tsx: -------------------------------------------------------------------------------- 1 | // app/events/page.tsx 2 | import { EventsList } from "@/cosmic/blocks/events/EventsList"; 3 | 4 | export default async function EventListPage() { 5 | return ( 6 |
7 |
8 |
9 |

10 | Upcoming Events 11 |

12 | 16 |
17 |
18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /app/blog/[slug]/page.tsx: -------------------------------------------------------------------------------- 1 | // app/blog/[slug]/page.tsx 2 | import { SingleBlog } from "@/cosmic/blocks/blog/SingleBlog"; 3 | import { cosmic } from "@/cosmic/client"; 4 | 5 | export const revalidate = 60; 6 | 7 | export async function generateStaticParams() { 8 | const { objects: posts } = await cosmic.objects.find({ 9 | type: "blog-posts", 10 | }); 11 | return posts.map((post: { slug: string }) => ({ 12 | slug: post.slug, 13 | })); 14 | } 15 | 16 | export default async function BlogPost({ 17 | params, 18 | searchParams, 19 | }: { 20 | params: { slug: string }; 21 | searchParams?: { [key: string]: string | string[] | undefined }; 22 | }) { 23 | return ( 24 |
25 | 26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /cosmic/elements/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 "@/cosmic/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 | -------------------------------------------------------------------------------- /app/blog/page.tsx: -------------------------------------------------------------------------------- 1 | // app/blog/page.tsx 2 | import { BlogList } from "@/cosmic/blocks/blog/BlogList"; 3 | 4 | export default async function BlogPage() { 5 | return ( 6 |
7 |
8 |
9 |

10 | Blog 11 |

12 |
13 | 20 |
21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /app/services/[slug]/page.tsx: -------------------------------------------------------------------------------- 1 | // app/shop/[slug]/page.tsx 2 | import { SingleProduct } from "@/cosmic/blocks/ecommerce/SingleProduct"; 3 | import { cosmic } from "@/cosmic/client"; 4 | 5 | export const revalidate = 60; 6 | 7 | export async function generateStaticParams() { 8 | const { objects: products } = await cosmic.objects.find({ 9 | type: "products", 10 | }); 11 | return products.map((product: { slug: string }) => ({ 12 | slug: product.slug, 13 | })); 14 | } 15 | export default async function SingleProductPage({ 16 | params, 17 | searchParams, 18 | }: { 19 | params: { slug: string }; 20 | searchParams: { 21 | success?: string; 22 | }; 23 | }) { 24 | return ( 25 |
26 | 30 |
31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /cosmic/blocks/blog/BlogList.tsx: -------------------------------------------------------------------------------- 1 | import { BlogCard, PostType } from "./BlogCard"; 2 | import { cosmic } from "@/cosmic/client"; 3 | 4 | export async function BlogList({ 5 | query, 6 | sort, 7 | limit, 8 | skip, 9 | className, 10 | status, 11 | }: { 12 | query: any; 13 | sort?: string; 14 | limit?: number; 15 | skip?: number; 16 | className?: string; 17 | status?: "draft" | "published" | "any"; 18 | }) { 19 | const { objects: posts } = await cosmic.objects 20 | .find(query) 21 | .props("id,slug,title,metadata") 22 | .depth(1) 23 | .sort(sort ? sort : "-order") 24 | .limit(limit ? limit : 100) 25 | .skip(skip ? skip : 0) 26 | .status(status ? status : "published"); 27 | 28 | return ( 29 |
30 | {posts.map((post: PostType) => { 31 | return ; 32 | })} 33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /cosmic/blocks/team/TeamList.tsx: -------------------------------------------------------------------------------- 1 | import { cosmic } from "@/cosmic/client"; 2 | import { TeamCard, MemberType } from "./TeamCard"; 3 | 4 | export async function TeamList({ 5 | query, 6 | sort, 7 | limit, 8 | skip, 9 | className, 10 | status, 11 | }: { 12 | query: any; 13 | sort?: string; 14 | limit?: number; 15 | skip?: number; 16 | className?: string; 17 | status?: "draft" | "published" | "any"; 18 | }) { 19 | const { objects: members } = await cosmic.objects 20 | .find(query) 21 | .props("id,slug,title,metadata") 22 | .depth(1) 23 | .sort(sort ? sort : "-order") 24 | .limit(limit ? limit : 100) 25 | .skip(skip ? skip : 0) 26 | .status(status ? status : "published"); 27 | return ( 28 |
29 | {members.map((member: MemberType) => { 30 | return ; 31 | })} 32 |
33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /cosmic/elements/TextArea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/cosmic/utils"; 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 |