{"id":19692850,"url":"https://github.com/tiger-githubb/event-platform","last_synced_at":"2026-06-05T20:31:27.665Z","repository":{"id":249313596,"uuid":"831006383","full_name":"tiger-githubb/Event-platform","owner":"tiger-githubb","description":null,"archived":false,"fork":false,"pushed_at":"2024-07-23T20:29:57.000Z","size":3633,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-11-21T20:03:57.591Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://tiger-event.vercel.app/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tiger-githubb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-07-19T12:53:14.000Z","updated_at":"2024-08-05T10:27:56.000Z","dependencies_parsed_at":"2025-02-27T09:57:02.319Z","dependency_job_id":"d67e54df-082b-4531-bc82-0a2efb89fd14","html_url":"https://github.com/tiger-githubb/Event-platform","commit_stats":null,"previous_names":["tiger-githubb/event-platform"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/tiger-githubb/Event-platform","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tiger-githubb%2FEvent-platform","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tiger-githubb%2FEvent-platform/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tiger-githubb%2FEvent-platform/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tiger-githubb%2FEvent-platform/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tiger-githubb","download_url":"https://codeload.github.com/tiger-githubb/Event-platform/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tiger-githubb%2FEvent-platform/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33959537,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-05T02:00:06.157Z","response_time":120,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-11T19:14:33.088Z","updated_at":"2026-06-05T20:31:27.643Z","avatar_url":"https://github.com/tiger-githubb.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cbr /\u003e\n    \u003ca href=\"https://youtu.be/zgGhzuBZOQg\" target=\"_blank\"\u003e\n      \u003cimg src=\"https://github.com/adrianhajdin/event_platform/assets/151519281/548975af-f0ed-4103-8834-fe93cf91862e\" alt=\"Project Banner\"\u003e\n    \u003c/a\u003e\n  \u003cbr /\u003e\n\n  \u003cdiv\u003e\n    \u003cimg src=\"https://img.shields.io/badge/-Next_JS_14-black?style=for-the-badge\u0026logoColor=white\u0026logo=nextdotjs\u0026color=000000\" alt=\"Next.js\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/-TypeScript-black?style=for-the-badge\u0026logoColor=white\u0026logo=typescript\u0026color=3178C6\" alt=\"TypeScript\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/-Stripe-black?style=for-the-badge\u0026logoColor=white\u0026logo=stripe\u0026color=008CDD\" alt=\"stripe\" /\u003e\n  \u003c/div\u003e\n\n  \u003ch3 align=\"center\"\u003eA Full Stack Next 14 Events App\u003c/h3\u003e\n\n   \u003cdiv align=\"center\"\u003e\n     Build this project step by step with our detailed tutorial on \u003ca href=\"https://www.youtube.com/@javascriptmastery/videos\" target=\"_blank\"\u003e\u003cb\u003eJavaScript Mastery\u003c/b\u003e\u003c/a\u003e YouTube. Join the JSM family!\n    \u003c/div\u003e\n\u003c/div\u003e\n\n## 📋 \u003ca name=\"table\"\u003eTable of Contents\u003c/a\u003e\n\n1. 🤖 [Introduction](#introduction)\n2. ⚙️ [Tech Stack](#tech-stack)\n3. 🔋 [Features](#features)\n4. 🤸 [Quick Start](#quick-start)\n5. 🕸️ [Snippets](#snippets)\n6. 🔗 [Links](#links)\n7. 🚀 [More](#more)\n\n## 🚨 Tutorial\n\nThis repository contains the code corresponding to an in-depth tutorial available on our YouTube channel, \u003ca href=\"https://www.youtube.com/@javascriptmastery/videos\" target=\"_blank\"\u003e\u003cb\u003eJavaScript Mastery\u003c/b\u003e\u003c/a\u003e. \n\nIf you prefer visual learning, this is the perfect resource for you. Follow our tutorial to learn how to build projects like these step-by-step in a beginner-friendly manner!\n\n\u003ca href=\"https://youtu.be/zgGhzuBZOQg\" target=\"_blank\"\u003e\u003cimg src=\"https://github.com/sujatagunale/EasyRead/assets/151519281/1736fca5-a031-4854-8c09-bc110e3bc16d\" /\u003e\u003c/a\u003e\n\n## \u003ca name=\"introduction\"\u003e🤖 Introduction\u003c/a\u003e\n\nBuilt on Next.js 14, the events application stands as a comprehensive, full-stack platform for managing events. It serves as a hub, spotlighting diverse events taking place globally. Featuring seamless payment processing through Stripe, you have the capability to purchase tickets for any event or even initiate and manage your own events.\n\nIf you're getting started and need assistance or face any bugs, join our active Discord community with over 27k+ members. It's a place where people help each other out.\n\n\u003ca href=\"https://discord.com/invite/n6EdbFJ\" target=\"_blank\"\u003e\u003cimg src=\"https://github.com/sujatagunale/EasyRead/assets/151519281/618f4872-1e10-42da-8213-1d69e486d02e\" /\u003e\u003c/a\u003e\n\n## \u003ca name=\"tech-stack\"\u003e⚙️ Tech Stack\u003c/a\u003e\n\n- Node.js\n- Next.js\n- TypeScript\n- TailwindCSS\n- Stripe\n- Zod\n- React Hook Form\n- Shadcn\n- uploadthing\n\n## \u003ca name=\"features\"\u003e🔋 Features\u003c/a\u003e\n\n👉 **Authentication (CRUD) with Clerk:** User management through Clerk, ensuring secure and efficient authentication.\n\n👉 **Events (CRUD):** Comprehensive functionality for creating, reading, updating, and deleting events, giving users full control over event management.\n- **Create Events:** Users can effortlessly generate new events, providing essential details such as title, date, location, and any additional information.\n- **Read Events:** Seamless access to a detailed view of all events, allowing users to explore event specifics, including descriptions, schedules, and related information.\n- **Update Events:** Empowering users to modify event details dynamically, ensuring that event information remains accurate and up-to-date.\n- **Delete Events:** A straightforward process for removing events from the system, giving administrators the ability to manage and curate the platform effectively.\n        \n👉 **Related Events:** Smartly connects events that are related and displaying on the event details page, making it more engaging for users\n    \n👉 **Organized Events:** Efficient organization of events, ensuring a structured and user-friendly display for the audience, i.e., showing events created by the user on the user profile\n    \n👉 **Search \u0026 Filter:** Empowering users with a robust search and filter system, enabling them to easily find the events that match their preferences.\n    \n👉 **New Category:** Dynamic categorization allows for the seamless addition of new event categories, keeping your platform adaptable.\n    \n👉 **Checkout and Pay with Stripe:** Smooth and secure payment transactions using Stripe, enhancing user experience during the checkout process.\n    \n👉 **Event Orders:** Comprehensive order management system, providing a clear overview of all event-related transactions.\n    \n👉 **Search Orders:** Quick and efficient search functionality for orders, facilitating easy tracking and management.\n\nand many more, including code architecture and reusability \n\n## \u003ca name=\"quick-start\"\u003e🤸 Quick Start\u003c/a\u003e\n\nFollow these steps to set up the project locally on your machine.\n\n**Prerequisites**\n\nMake sure you have the following installed on your machine:\n\n- [Git](https://git-scm.com/)\n- [Node.js](https://nodejs.org/en)\n- [npm](https://www.npmjs.com/) (Node Package Manager)\n\n**Cloning the Repository**\n\n```bash\ngit clone https://github.com/your-username/your-project.git\ncd your-project\n```\n\n**Installation**\n\nInstall the project dependencies using npm:\n\n```bash\nnpm install\n```\n\n**Set Up Environment Variables**\n\nCreate a new file named `.env` in the root of your project and add the following content:\n\n```env\n#NEXT\nNEXT_PUBLIC_SERVER_URL=\n\n#CLERK\nNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=\nCLERK_SECRET_KEY=\nNEXT_CLERK_WEBHOOK_SECRET=\n\nNEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in\nNEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up\nNEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/\nNEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/\n\n#MONGODB\nMONGODB_URI=\n\n#UPLOADTHING\nUPLOADTHING_SECRET=\nUPLOADTHING_APP_ID=\n\n#STRIPE\nSTRIPE_SECRET_KEY=\nSTRIPE_WEBHOOK_SECRET=\nNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=\n```\n\nReplace the placeholder values with your actual credentials \n\n**Running the Project**\n\n```bash\nnpm start\n```\n\nOpen [http://localhost:3000](http://localhost:3000) in your browser to view the project.\n\n## \u003ca name=\"snippets\"\u003e🕸️ Snippets\u003c/a\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003eglobals.css\u003c/code\u003e\u003c/summary\u003e\n\n```css\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n  :root {\n    --background: 0 0% 100%;\n    --foreground: 222.2 84% 4.9%;\n\n    --card: 0 0% 100%;\n    --card-foreground: 222.2 84% 4.9%;\n\n    --popover: 0 0% 100%;\n    --popover-foreground: 222.2 84% 4.9%;\n\n    --primary: 222.2 47.4% 11.2%;\n    --primary-foreground: 210 40% 98%;\n\n    --secondary: 210 40% 96.1%;\n    --secondary-foreground: 222.2 47.4% 11.2%;\n\n    --muted: 210 40% 96.1%;\n    --muted-foreground: 215.4 16.3% 46.9%;\n\n    --accent: 210 40% 96.1%;\n    --accent-foreground: 222.2 47.4% 11.2%;\n\n    --destructive: 0 84.2% 60.2%;\n    --destructive-foreground: 210 40% 98%;\n\n    --border: 214.3 31.8% 91.4%;\n    --input: 214.3 31.8% 91.4%;\n    --ring: 222.2 84% 4.9%;\n\n    --radius: 0.5rem;\n  }\n\n  .dark {\n    --background: 222.2 84% 4.9%;\n    --foreground: 210 40% 98%;\n\n    --card: 222.2 84% 4.9%;\n    --card-foreground: 210 40% 98%;\n\n    --popover: 222.2 84% 4.9%;\n    --popover-foreground: 210 40% 98%;\n\n    --primary: 210 40% 98%;\n    --primary-foreground: 222.2 47.4% 11.2%;\n\n    --secondary: 217.2 32.6% 17.5%;\n    --secondary-foreground: 210 40% 98%;\n\n    --muted: 217.2 32.6% 17.5%;\n    --muted-foreground: 215 20.2% 65.1%;\n\n    --accent: 217.2 32.6% 17.5%;\n    --accent-foreground: 210 40% 98%;\n\n    --destructive: 0 62.8% 30.6%;\n    --destructive-foreground: 210 40% 98%;\n\n    --border: 217.2 32.6% 17.5%;\n    --input: 217.2 32.6% 17.5%;\n    --ring: 212.7 26.8% 83.9%;\n  }\n}\n\n* {\n  list-style: none;\n  padding: 0;\n  margin: 0;\n  scroll-behavior: smooth;\n}\n\nbody {\n  font-family: var(--font-poppins)\n}\n\n.filter-grey {\n  filter: brightness(0) saturate(100%) invert(47%) sepia(0%) saturate(217%)\n    hue-rotate(32deg) brightness(98%) contrast(92%);\n}\n\n/* ========================================== TAILWIND STYLES */\n@layer utilities {\n  .wrapper {\n    @apply max-w-7xl lg:mx-auto p-5 md:px-10 xl:px-0 w-full;\n  }\n\n  .flex-center {\n    @apply flex justify-center items-center;\n  }\n\n  .flex-between {\n    @apply flex justify-between items-center;\n  }\n\n  /* TYPOGRAPHY */\n  /* 64 */\n  .h1-bold {\n    @apply font-bold text-[40px] leading-[48px] lg:text-[48px] lg:leading-[60px]  xl:text-[58px] xl:leading-[74px];\n  }\n\n  /* 40 */\n  .h2-bold {\n    @apply font-bold text-[32px] leading-[40px] lg:text-[36px] lg:leading-[44px] xl:text-[40px] xl:leading-[48px];\n  }\n\n  .h2-medium {\n    @apply font-medium text-[32px] leading-[40px] lg:text-[36px] lg:leading-[44px] xl:text-[40px] xl:leading-[48px];\n  }\n\n  /* 36 */\n  .h3-bold {\n    @apply font-bold text-[28px] leading-[36px] md:text-[36px] md:leading-[44px];\n  }\n\n  .h3-medium {\n    @apply font-medium text-[28px] leading-[36px] md:text-[36px] md:leading-[44px];\n  }\n\n  /* 32 */\n  .h4-medium {\n    @apply font-medium text-[32px] leading-[40px];\n  }\n\n  /* 28 */\n  .h5-bold {\n    @apply font-bold text-[28px] leading-[36px];\n  }\n\n  /* 24 */\n  .p-bold-24 {\n    @apply font-bold text-[24px] leading-[36px];\n  }\n\n  .p-medium-24 {\n    @apply font-medium text-[24px] leading-[36px];\n  }\n\n  .p-regular-24 {\n    @apply font-normal text-[24px] leading-[36px];\n  }\n\n  /* 20 */\n  .p-bold-20 {\n    @apply font-bold text-[20px] leading-[30px] tracking-[2%];\n  }\n\n  .p-semibold-20 {\n    @apply text-[20px] font-semibold leading-[30px] tracking-[2%];\n  }\n\n  .p-medium-20 {\n    @apply text-[20px] font-medium leading-[30px];\n  }\n\n  .p-regular-20 {\n    @apply text-[20px] font-normal leading-[30px] tracking-[2%];\n  }\n\n  /* 18 */\n  .p-semibold-18 {\n    @apply text-[18px] font-semibold leading-[28px] tracking-[2%];\n  }\n\n  .p-medium-18 {\n    @apply text-[18px] font-medium leading-[28px];\n  }\n\n  .p-regular-18 {\n    @apply text-[18px] font-normal leading-[28px] tracking-[2%];\n  }\n\n  /* 16 */\n  .p-bold-16 {\n    @apply text-[16px] font-bold leading-[24px];\n  }\n\n  .p-medium-16 {\n    @apply text-[16px] font-medium leading-[24px];\n  }\n\n  .p-regular-16 {\n    @apply text-[16px] font-normal leading-[24px];\n  }\n\n  /* 14 */\n  .p-semibold-14 {\n    @apply text-[14px] font-semibold leading-[20px];\n  }\n\n  .p-medium-14 {\n    @apply text-[14px] font-medium leading-[20px];\n  }\n\n  .p-regular-14 {\n    @apply text-[14px] font-normal leading-[20px];\n  }\n\n  /* 12 */\n  .p-medium-12 {\n    @apply text-[12px] font-medium leading-[20px];\n  }\n\n  /* SHADCN OVERRIDES */\n  .select-field {\n    @apply w-full bg-grey-50 h-[54px] placeholder:text-grey-500 rounded-full p-regular-16 px-5 py-3 border-none focus-visible:ring-transparent focus:ring-transparent !important;\n  }\n\n  .input-field {\n    @apply bg-grey-50 h-[54px] focus-visible:ring-offset-0 placeholder:text-grey-500 rounded-full p-regular-16 px-4 py-3 border-none focus-visible:ring-transparent !important;\n  }\n\n  .textarea {\n    @apply bg-grey-50 flex flex-1 placeholder:text-grey-500 p-regular-16 px-5 py-3 border-none focus-visible:ring-transparent !important;\n  }\n\n  .button {\n    @apply rounded-full h-[54px] p-regular-16;\n  }\n\n  .select-item {\n    @apply py-3 cursor-pointer  focus:bg-primary-50;\n  }\n\n  .toggle-switch {\n    @apply bg-gray-300 !important;\n  }\n}\n\n/* ========================================== CLERK STYLES */\n.cl-logoImage {\n  height: 38px;\n}\n\n.cl-userButtonBox {\n  flex-direction: row-reverse;\n}\n\n.cl-userButtonOuterIdentifier {\n  font-size: 16px;\n}\n\n.cl-userButtonPopoverCard {\n  right: 4px !important;\n}\n\n.cl-formButtonPrimary:hover,\n.cl-formButtonPrimary:focus,\n.cl-formButtonPrimary:active {\n  background-color: #705CF7\n}\n\n/* ========================================== REACT-DATEPICKER STYLES */\n.datePicker {\n  width: 100%;\n}\n\n.react-datepicker__input-container input {\n  background-color: transparent;\n  width: 100%;\n  outline: none;\n  margin-left: 16px;\n}\n\n.react-datepicker__day--selected {\n  background-color: #624cf5 !important;\n  color: #ffffff !important;\n  border-radius: 4px;\n}\n\n.react-datepicker__time-list-item--selected {\n  background-color: #624cf5 !important;\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003etailwind.config.ts\u003c/code\u003e\u003c/summary\u003e\n\n```typescript\n/** @type {import('tailwindcss').Config} */\nimport { withUt } from 'uploadthing/tw';\n\nmodule.exports = withUt({\n  darkMode: ['class'],\n  content: [\n    './pages/**/*.{ts,tsx}',\n    './components/**/*.{ts,tsx}',\n    './app/**/*.{ts,tsx}',\n    './src/**/*.{ts,tsx}',\n  ],\n  theme: {\n    container: {\n      center: true,\n      padding: '2rem',\n      screens: {\n        '2xl': '1400px',\n      },\n    },\n    extend: {\n      colors: {\n        primary: {\n          500: '#624CF5',\n          50: ' #F6F8FD',\n          DEFAULT: '#624CF5',\n          foreground: 'hsl(var(--primary-foreground))',\n        },\n        coral: {\n          500: '#15BF59',\n        },\n\n        grey: {\n          600: '#545454', // Subdued - color name in figma\n          500: '#757575',\n          400: '#AFAFAF', // Disabled - color name in figma\n          50: '#F6F6F6', // White Grey - color name in figma\n        },\n        black: '#000000',\n        white: '#FFFFFF',\n        border: 'hsl(var(--border))',\n        input: 'hsl(var(--input))',\n        ring: 'hsl(var(--ring))',\n        foreground: 'hsl(var(--foreground))',\n        secondary: {\n          DEFAULT: 'hsl(var(--secondary))',\n          foreground: 'hsl(var(--secondary-foreground))',\n        },\n        destructive: {\n          DEFAULT: 'hsl(var(--destructive))',\n          foreground: 'hsl(var(--destructive-foreground))',\n        },\n        muted: {\n          DEFAULT: 'hsl(var(--muted))',\n          foreground: 'hsl(var(--muted-foreground))',\n        },\n        accent: {\n          DEFAULT: 'hsl(var(--accent))',\n          foreground: 'hsl(var(--accent-foreground))',\n        },\n        popover: {\n          DEFAULT: 'hsl(var(--popover))',\n          foreground: 'hsl(var(--popover-foreground))',\n        },\n        card: {\n          DEFAULT: 'hsl(var(--card))',\n          foreground: 'hsl(var(--card-foreground))',\n        },\n      },\n      fontFamily: {\n        poppins: ['var(--font-poppins)'],\n      },\n      backgroundImage: {\n        'dotted-pattern': \"url('/assets/images/dotted-pattern.png')\",\n        'hero-img': \"url('/assets/images/hero.png')\",\n      },\n      borderRadius: {\n        lg: 'var(--radius)',\n        md: 'calc(var(--radius) - 2px)',\n        sm: 'calc(var(--radius) - 4px)',\n      },\n      keyframes: {\n        'accordion-down': {\n          from: { height: '0' },\n          to: { height: 'var(--radix-accordion-content-height)' },\n        },\n        'accordion-up': {\n          from: { height: 'var(--radix-accordion-content-height)' },\n          to: { height: '0' },\n        },\n      },\n      animation: {\n        'accordion-down': 'accordion-down 0.2s ease-out',\n        'accordion-up': 'accordion-up 0.2s ease-out',\n      },\n    },\n  },\n  plugins: [require('tailwindcss-animate')],\n});\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003eClerk webhook\u003c/code\u003e\u003c/summary\u003e\n\n```typescript\nimport { Webhook } from 'svix'\nimport { headers } from 'next/headers'\nimport { WebhookEvent } from '@clerk/nextjs/server'\nimport { createUser, deleteUser, updateUser } from '@/lib/actions/user.actions'\nimport { clerkClient } from '@clerk/nextjs'\nimport { NextResponse } from 'next/server'\n \nexport async function POST(req: Request) {\n \n  // You can find this in the Clerk Dashboard -\u003e Webhooks -\u003e choose the webhook\n  const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET\n \n  if (!WEBHOOK_SECRET) {\n    throw new Error('Please add WEBHOOK_SECRET from Clerk Dashboard to .env or .env.local')\n  }\n \n  // Get the headers\n  const headerPayload = headers();\n  const svix_id = headerPayload.get(\"svix-id\");\n  const svix_timestamp = headerPayload.get(\"svix-timestamp\");\n  const svix_signature = headerPayload.get(\"svix-signature\");\n \n  // If there are no headers, error out\n  if (!svix_id || !svix_timestamp || !svix_signature) {\n    return new Response('Error occured -- no svix headers', {\n      status: 400\n    })\n  }\n \n  // Get the body\n  const payload = await req.json()\n  const body = JSON.stringify(payload);\n \n  // Create a new Svix instance with your secret.\n  const wh = new Webhook(WEBHOOK_SECRET);\n \n  let evt: WebhookEvent\n \n  // Verify the payload with the headers\n  try {\n    evt = wh.verify(body, {\n      \"svix-id\": svix_id,\n      \"svix-timestamp\": svix_timestamp,\n      \"svix-signature\": svix_signature,\n    }) as WebhookEvent\n  } catch (err) {\n    console.error('Error verifying webhook:', err);\n    return new Response('Error occured', {\n      status: 400\n    })\n  }\n \n  // Get the ID and type\n  const { id } = evt.data;\n  const eventType = evt.type;\n \n  if(eventType === 'user.created') {\n    const { id, email_addresses, image_url, first_name, last_name, username } = evt.data;\n\n    const user = {\n      clerkId: id,\n      email: email_addresses[0].email_address,\n      username: username!,\n      firstName: first_name,\n      lastName: last_name,\n      photo: image_url,\n    }\n\n    const newUser = await createUser(user);\n\n    if(newUser) {\n      await clerkClient.users.updateUserMetadata(id, {\n        publicMetadata: {\n          userId: newUser._id\n        }\n      })\n    }\n\n    return NextResponse.json({ message: 'OK', user: newUser })\n  }\n\n  if (eventType === 'user.updated') {\n    const {id, image_url, first_name, last_name, username } = evt.data\n\n    const user = {\n      firstName: first_name,\n      lastName: last_name,\n      username: username!,\n      photo: image_url,\n    }\n\n    const updatedUser = await updateUser(id, user)\n\n    return NextResponse.json({ message: 'OK', user: updatedUser })\n  }\n\n  if (eventType === 'user.deleted') {\n    const { id } = evt.data\n\n    const deletedUser = await deleteUser(id!)\n\n    return NextResponse.json({ message: 'OK', user: deletedUser })\n  }\n \n  return new Response('', { status: 200 })\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003euser.actions.ts\u003c/code\u003e\u003c/summary\u003e\n\n```typescript\n'use server'\n\nimport { revalidatePath } from 'next/cache'\n\nimport { connectToDatabase } from '@/lib/database'\nimport User from '@/lib/database/models/user.model'\nimport Order from '@/lib/database/models/order.model'\nimport Event from '@/lib/database/models/event.model'\nimport { handleError } from '@/lib/utils'\n\nimport { CreateUserParams, UpdateUserParams } from '@/types'\n\nexport async function createUser(user: CreateUserParams) {\n  try {\n    await connectToDatabase()\n\n    const newUser = await User.create(user)\n    return JSON.parse(JSON.stringify(newUser))\n  } catch (error) {\n    handleError(error)\n  }\n}\n\nexport async function getUserById(userId: string) {\n  try {\n    await connectToDatabase()\n\n    const user = await User.findById(userId)\n\n    if (!user) throw new Error('User not found')\n    return JSON.parse(JSON.stringify(user))\n  } catch (error) {\n    handleError(error)\n  }\n}\n\nexport async function updateUser(clerkId: string, user: UpdateUserParams) {\n  try {\n    await connectToDatabase()\n\n    const updatedUser = await User.findOneAndUpdate({ clerkId }, user, { new: true })\n\n    if (!updatedUser) throw new Error('User update failed')\n    return JSON.parse(JSON.stringify(updatedUser))\n  } catch (error) {\n    handleError(error)\n  }\n}\n\nexport async function deleteUser(clerkId: string) {\n  try {\n    await connectToDatabase()\n\n    // Find user to delete\n    const userToDelete = await User.findOne({ clerkId })\n\n    if (!userToDelete) {\n      throw new Error('User not found')\n    }\n\n    // Unlink relationships\n    await Promise.all([\n      // Update the 'events' collection to remove references to the user\n      Event.updateMany(\n        { _id: { $in: userToDelete.events } },\n        { $pull: { organizer: userToDelete._id } }\n      ),\n\n      // Update the 'orders' collection to remove references to the user\n      Order.updateMany({ _id: { $in: userToDelete.orders } }, { $unset: { buyer: 1 } }),\n    ])\n\n    // Delete user\n    const deletedUser = await User.findByIdAndDelete(userToDelete._id)\n    revalidatePath('/')\n\n    return deletedUser ? JSON.parse(JSON.stringify(deletedUser)) : null\n  } catch (error) {\n    handleError(error)\n  }\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003eorder.model.ts\u003c/code\u003e\u003c/summary\u003e\n  \n```typescript\nimport { Schema, model, models, Document } from 'mongoose'\n\nexport interface IOrder extends Document {\n  createdAt: Date\n  stripeId: string\n  totalAmount: string\n  event: {\n    _id: string\n    title: string\n  }\n  buyer: {\n    _id: string\n    firstName: string\n    lastName: string\n  }\n}\n\nexport type IOrderItem = {\n  _id: string\n  totalAmount: string\n  createdAt: Date\n  eventTitle: string\n  eventId: string\n  buyer: string\n}\n\nconst OrderSchema = new Schema({\n  createdAt: {\n    type: Date,\n    default: Date.now,\n  },\n  stripeId: {\n    type: String,\n    required: true,\n    unique: true,\n  },\n  totalAmount: {\n    type: String,\n  },\n  event: {\n    type: Schema.Types.ObjectId,\n    ref: 'Event',\n  },\n  buyer: {\n    type: Schema.Types.ObjectId,\n    ref: 'User',\n  },\n})\n\nconst Order = models.Order || model('Order', OrderSchema)\n\nexport default Order\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003eFileUploader.tsx\u003c/code\u003e\u003c/summary\u003e\n\n```typescript\n'use client'\n\nimport { useCallback, Dispatch, SetStateAction } from 'react'\nimport type { FileWithPath } from '@uploadthing/react'\nimport { useDropzone } from '@uploadthing/react/hooks'\nimport { generateClientDropzoneAccept } from 'uploadthing/client'\n\nimport { Button } from '@/components/ui/button'\nimport { convertFileToUrl } from '@/lib/utils'\n\ntype FileUploaderProps = {\n  onFieldChange: (url: string) =\u003e void\n  imageUrl: string\n  setFiles: Dispatch\u003cSetStateAction\u003cFile[]\u003e\u003e\n}\n\nexport function FileUploader({ imageUrl, onFieldChange, setFiles }: FileUploaderProps) {\n  const onDrop = useCallback((acceptedFiles: FileWithPath[]) =\u003e {\n    setFiles(acceptedFiles)\n    onFieldChange(convertFileToUrl(acceptedFiles[0]))\n  }, [])\n\n  const { getRootProps, getInputProps } = useDropzone({\n    onDrop,\n    accept: 'image/*' ? generateClientDropzoneAccept(['image/*']) : undefined,\n  })\n\n  return (\n    \u003cdiv\n      {...getRootProps()}\n      className=\"flex-center bg-dark-3 flex h-72 cursor-pointer flex-col overflow-hidden rounded-xl bg-grey-50\"\u003e\n      \u003cinput {...getInputProps()} className=\"cursor-pointer\" /\u003e\n\n      {imageUrl ? (\n        \u003cdiv className=\"flex h-full w-full flex-1 justify-center \"\u003e\n          \u003cimg\n            src={imageUrl}\n            alt=\"image\"\n            width={250}\n            height={250}\n            className=\"w-full object-cover object-center\"\n          /\u003e\n        \u003c/div\u003e\n      ) : (\n        \u003cdiv className=\"flex-center flex-col py-5 text-grey-500\"\u003e\n          \u003cimg src=\"/assets/icons/upload.svg\" width={77} height={77} alt=\"file upload\" /\u003e\n          \u003ch3 className=\"mb-2 mt-2\"\u003eDrag photo here\u003c/h3\u003e\n          \u003cp className=\"p-medium-12 mb-4\"\u003eSVG, PNG, JPG\u003c/p\u003e\n          \u003cButton type=\"button\" className=\"rounded-full\"\u003e\n            Select from computer\n          \u003c/Button\u003e\n        \u003c/div\u003e\n      )}\n    \u003c/div\u003e\n  )\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003eDeleteConfirmation.tsx\u003c/code\u003e\u003c/summary\u003e\n\n```typescript\n'use client'\n\nimport { useTransition } from 'react'\nimport { usePathname } from 'next/navigation'\nimport Image from 'next/image'\n\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n  AlertDialogTrigger,\n} from '@/components/ui/alert-dialog'\n\nimport { deleteEvent } from '@/lib/actions/event.actions'\n\nexport const DeleteConfirmation = ({ eventId }: { eventId: string }) =\u003e {\n  const pathname = usePathname()\n  let [isPending, startTransition] = useTransition()\n\n  return (\n    \u003cAlertDialog\u003e\n      \u003cAlertDialogTrigger\u003e\n        \u003cImage src=\"/assets/icons/delete.svg\" alt=\"edit\" width={20} height={20} /\u003e\n      \u003c/AlertDialogTrigger\u003e\n\n      \u003cAlertDialogContent className=\"bg-white\"\u003e\n        \u003cAlertDialogHeader\u003e\n          \u003cAlertDialogTitle\u003eAre you sure you want to delete?\u003c/AlertDialogTitle\u003e\n          \u003cAlertDialogDescription className=\"p-regular-16 text-grey-600\"\u003e\n            This will permanently delete this event\n          \u003c/AlertDialogDescription\u003e\n        \u003c/AlertDialogHeader\u003e\n\n        \u003cAlertDialogFooter\u003e\n          \u003cAlertDialogCancel\u003eCancel\u003c/AlertDialogCancel\u003e\n\n          \u003cAlertDialogAction\n            onClick={() =\u003e\n              startTransition(async () =\u003e {\n                await deleteEvent({ eventId, path: pathname })\n              })\n            }\u003e\n            {isPending ? 'Deleting...' : 'Delete'}\n          \u003c/AlertDialogAction\u003e\n        \u003c/AlertDialogFooter\u003e\n      \u003c/AlertDialogContent\u003e\n    \u003c/AlertDialog\u003e\n  )\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003eevent.action.ts\u003c/code\u003e\u003c/summary\u003e\n\n```typescript\n'use server'\n\nimport { revalidatePath } from 'next/cache'\n\nimport { connectToDatabase } from '@/lib/database'\nimport Event from '@/lib/database/models/event.model'\nimport User from '@/lib/database/models/user.model'\nimport Category from '@/lib/database/models/category.model'\nimport { handleError } from '@/lib/utils'\n\nimport {\n  CreateEventParams,\n  UpdateEventParams,\n  DeleteEventParams,\n  GetAllEventsParams,\n  GetEventsByUserParams,\n  GetRelatedEventsByCategoryParams,\n} from '@/types'\n\nconst getCategoryByName = async (name: string) =\u003e {\n  return Category.findOne({ name: { $regex: name, $options: 'i' } })\n}\n\nconst populateEvent = (query: any) =\u003e {\n  return query\n    .populate({ path: 'organizer', model: User, select: '_id firstName lastName' })\n    .populate({ path: 'category', model: Category, select: '_id name' })\n}\n\n// CREATE\nexport async function createEvent({ userId, event, path }: CreateEventParams) {\n  try {\n    await connectToDatabase()\n\n    const organizer = await User.findById(userId)\n    if (!organizer) throw new Error('Organizer not found')\n\n    const newEvent = await Event.create({ ...event, category: event.categoryId, organizer: userId })\n    revalidatePath(path)\n\n    return JSON.parse(JSON.stringify(newEvent))\n  } catch (error) {\n    handleError(error)\n  }\n}\n\n// GET ONE EVENT BY ID\nexport async function getEventById(eventId: string) {\n  try {\n    await connectToDatabase()\n\n    const event = await populateEvent(Event.findById(eventId))\n\n    if (!event) throw new Error('Event not found')\n\n    return JSON.parse(JSON.stringify(event))\n  } catch (error) {\n    handleError(error)\n  }\n}\n\n// UPDATE\nexport async function updateEvent({ userId, event, path }: UpdateEventParams) {\n  try {\n    await connectToDatabase()\n\n    const eventToUpdate = await Event.findById(event._id)\n    if (!eventToUpdate || eventToUpdate.organizer.toHexString() !== userId) {\n      throw new Error('Unauthorized or event not found')\n    }\n\n    const updatedEvent = await Event.findByIdAndUpdate(\n      event._id,\n      { ...event, category: event.categoryId },\n      { new: true }\n    )\n    revalidatePath(path)\n\n    return JSON.parse(JSON.stringify(updatedEvent))\n  } catch (error) {\n    handleError(error)\n  }\n}\n\n// DELETE\nexport async function deleteEvent({ eventId, path }: DeleteEventParams) {\n  try {\n    await connectToDatabase()\n\n    const deletedEvent = await Event.findByIdAndDelete(eventId)\n    if (deletedEvent) revalidatePath(path)\n  } catch (error) {\n    handleError(error)\n  }\n}\n\n// GET ALL EVENTS\nexport async function getAllEvents({ query, limit = 6, page, category }: GetAllEventsParams) {\n  try {\n    await connectToDatabase()\n\n    const titleCondition = query ? { title: { $regex: query, $options: 'i' } } : {}\n    const categoryCondition = category ? await getCategoryByName(category) : null\n    const conditions = {\n      $and: [titleCondition, categoryCondition ? { category: categoryCondition._id } : {}],\n    }\n\n    const skipAmount = (Number(page) - 1) * limit\n    const eventsQuery = Event.find(conditions)\n      .sort({ createdAt: 'desc' })\n      .skip(skipAmount)\n      .limit(limit)\n\n    const events = await populateEvent(eventsQuery)\n    const eventsCount = await Event.countDocuments(conditions)\n\n    return {\n      data: JSON.parse(JSON.stringify(events)),\n      totalPages: Math.ceil(eventsCount / limit),\n    }\n  } catch (error) {\n    handleError(error)\n  }\n}\n\n// GET EVENTS BY ORGANIZER\nexport async function getEventsByUser({ userId, limit = 6, page }: GetEventsByUserParams) {\n  try {\n    await connectToDatabase()\n\n    const conditions = { organizer: userId }\n    const skipAmount = (page - 1) * limit\n\n    const eventsQuery = Event.find(conditions)\n      .sort({ createdAt: 'desc' })\n      .skip(skipAmount)\n      .limit(limit)\n\n    const events = await populateEvent(eventsQuery)\n    const eventsCount = await Event.countDocuments(conditions)\n\n    return { data: JSON.parse(JSON.stringify(events)), totalPages: Math.ceil(eventsCount / limit) }\n  } catch (error) {\n    handleError(error)\n  }\n}\n\n// GET RELATED EVENTS: EVENTS WITH SAME CATEGORY\nexport async function getRelatedEventsByCategory({\n  categoryId,\n  eventId,\n  limit = 3,\n  page = 1,\n}: GetRelatedEventsByCategoryParams) {\n  try {\n    await connectToDatabase()\n\n    const skipAmount = (Number(page) - 1) * limit\n    const conditions = { $and: [{ category: categoryId }, { _id: { $ne: eventId } }] }\n\n    const eventsQuery = Event.find(conditions)\n      .sort({ createdAt: 'desc' })\n      .skip(skipAmount)\n      .limit(limit)\n\n    const events = await populateEvent(eventsQuery)\n    const eventsCount = await Event.countDocuments(conditions)\n\n    return { data: JSON.parse(JSON.stringify(events)), totalPages: Math.ceil(eventsCount / limit) }\n  } catch (error) {\n    handleError(error)\n  }\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003eorder.action.ts\u003c/code\u003e\u003c/summary\u003e\n\n```typescript\n\"use server\"\n\nimport Stripe from 'stripe';\nimport { CheckoutOrderParams, CreateOrderParams, GetOrdersByEventParams, GetOrdersByUserParams } from \"@/types\"\nimport { redirect } from 'next/navigation';\nimport { handleError } from '../utils';\nimport { connectToDatabase } from '../database';\nimport Order from '../database/models/order.model';\nimport Event from '../database/models/event.model';\nimport {ObjectId} from 'mongodb';\nimport User from '../database/models/user.model';\n\nexport const checkoutOrder = async (order: CheckoutOrderParams) =\u003e {\n  const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n\n  const price = order.isFree ? 0 : Number(order.price) * 100;\n\n  try {\n    const session = await stripe.checkout.sessions.create({\n      line_items: [\n        {\n          price_data: {\n            currency: 'usd',\n            unit_amount: price,\n            product_data: {\n              name: order.eventTitle\n            }\n          },\n          quantity: 1\n        },\n      ],\n      metadata: {\n        eventId: order.eventId,\n        buyerId: order.buyerId,\n      },\n      mode: 'payment',\n      success_url: `${process.env.NEXT_PUBLIC_SERVER_URL}/profile`,\n      cancel_url: `${process.env.NEXT_PUBLIC_SERVER_URL}/`,\n    });\n\n    redirect(session.url!)\n  } catch (error) {\n    throw error;\n  }\n}\n\nexport const createOrder = async (order: CreateOrderParams) =\u003e {\n  try {\n    await connectToDatabase();\n    \n    const newOrder = await Order.create({\n      ...order,\n      event: order.eventId,\n      buyer: order.buyerId,\n    });\n\n    return JSON.parse(JSON.stringify(newOrder));\n  } catch (error) {\n    handleError(error);\n  }\n}\n\n// GET ORDERS BY EVENT\nexport async function getOrdersByEvent({ searchString, eventId }: GetOrdersByEventParams) {\n  try {\n    await connectToDatabase()\n\n    if (!eventId) throw new Error('Event ID is required')\n    const eventObjectId = new ObjectId(eventId)\n\n    const orders = await Order.aggregate([\n      {\n        $lookup: {\n          from: 'users',\n          localField: 'buyer',\n          foreignField: '_id',\n          as: 'buyer',\n        },\n      },\n      {\n        $unwind: '$buyer',\n      },\n      {\n        $lookup: {\n          from: 'events',\n          localField: 'event',\n          foreignField: '_id',\n          as: 'event',\n        },\n      },\n      {\n        $unwind: '$event',\n      },\n      {\n        $project: {\n          _id: 1,\n          totalAmount: 1,\n          createdAt: 1,\n          eventTitle: '$event.title',\n          eventId: '$event._id',\n          buyer: {\n            $concat: ['$buyer.firstName', ' ', '$buyer.lastName'],\n          },\n        },\n      },\n      {\n        $match: {\n          $and: [{ eventId: eventObjectId }, { buyer: { $regex: RegExp(searchString, 'i') } }],\n        },\n      },\n    ])\n\n    return JSON.parse(JSON.stringify(orders))\n  } catch (error) {\n    handleError(error)\n  }\n}\n\n// GET ORDERS BY USER\nexport async function getOrdersByUser({ userId, limit = 3, page }: GetOrdersByUserParams) {\n  try {\n    await connectToDatabase()\n\n    const skipAmount = (Number(page) - 1) * limit\n    const conditions = { buyer: userId }\n\n    const orders = await Order.distinct('event._id')\n      .find(conditions)\n      .sort({ createdAt: 'desc' })\n      .skip(skipAmount)\n      .limit(limit)\n      .populate({\n        path: 'event',\n        model: Event,\n        populate: {\n          path: 'organizer',\n          model: User,\n          select: '_id firstName lastName',\n        },\n      })\n\n    const ordersCount = await Order.distinct('event._id').countDocuments(conditions)\n\n    return { data: JSON.parse(JSON.stringify(orders)), totalPages: Math.ceil(ordersCount / limit) }\n  } catch (error) {\n    handleError(error)\n  }\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003eorders/page.tsx\u003c/code\u003e\u003c/summary\u003e\n\n```typescript\nimport Search  from '@/components/shared/Search'\nimport { getOrdersByEvent } from '@/lib/actions/order.actions'\nimport { formatDateTime, formatPrice } from '@/lib/utils'\nimport { SearchParamProps } from '@/types'\nimport { IOrderItem } from '@/lib/database/models/order.model'\n\nconst Orders = async ({ searchParams }: SearchParamProps) =\u003e {\n  const eventId = (searchParams?.eventId as string) || ''\n  const searchText = (searchParams?.query as string) || ''\n\n  const orders = await getOrdersByEvent({ eventId, searchString: searchText })\n\n  return (\n    \u003c\u003e\n      \u003csection className=\" bg-primary-50 bg-dotted-pattern bg-cover bg-center py-5 md:py-10\"\u003e\n        \u003ch3 className=\"wrapper h3-bold text-center sm:text-left \"\u003eOrders\u003c/h3\u003e\n      \u003c/section\u003e\n\n      \u003csection className=\"wrapper mt-8\"\u003e\n        \u003cSearch placeholder=\"Search buyer name...\" /\u003e\n      \u003c/section\u003e\n\n      \u003csection className=\"wrapper overflow-x-auto\"\u003e\n        \u003ctable className=\"w-full border-collapse border-t\"\u003e\n          \u003cthead\u003e\n            \u003ctr className=\"p-medium-14 border-b text-grey-500\"\u003e\n              \u003cth className=\"min-w-[250px] py-3 text-left\"\u003eOrder ID\u003c/th\u003e\n              \u003cth className=\"min-w-[200px] flex-1 py-3 pr-4 text-left\"\u003eEvent Title\u003c/th\u003e\n              \u003cth className=\"min-w-[150px] py-3 text-left\"\u003eBuyer\u003c/th\u003e\n              \u003cth className=\"min-w-[100px] py-3 text-left\"\u003eCreated\u003c/th\u003e\n              \u003cth className=\"min-w-[100px] py-3 text-right\"\u003eAmount\u003c/th\u003e\n            \u003c/tr\u003e\n          \u003c/thead\u003e\n          \u003ctbody\u003e\n            {orders \u0026\u0026 orders.length === 0 ? (\n              \u003ctr className=\"border-b\"\u003e\n                \u003ctd colSpan={5} className=\"py-4 text-center text-gray-500\"\u003e\n                  No orders found.\n                \u003c/td\u003e\n              \u003c/tr\u003e\n            ) : (\n              \u003c\u003e\n                {orders \u0026\u0026\n                  orders.map((row: IOrderItem) =\u003e (\n                    \u003ctr\n                      key={row._id}\n                      className=\"p-regular-14 lg:p-regular-16 border-b \"\n                      style={{ boxSizing: 'border-box' }}\u003e\n                      \u003ctd className=\"min-w-[250px] py-4 text-primary-500\"\u003e{row._id}\u003c/td\u003e\n                      \u003ctd className=\"min-w-[200px] flex-1 py-4 pr-4\"\u003e{row.eventTitle}\u003c/td\u003e\n                      \u003ctd className=\"min-w-[150px] py-4\"\u003e{row.buyer}\u003c/td\u003e\n                      \u003ctd className=\"min-w-[100px] py-4\"\u003e\n                        {formatDateTime(row.createdAt).dateTime}\n                      \u003c/td\u003e\n                      \u003ctd className=\"min-w-[100px] py-4 text-right\"\u003e\n                        {formatPrice(row.totalAmount)}\n                      \u003c/td\u003e\n                    \u003c/tr\u003e\n                  ))}\n              \u003c/\u003e\n            )}\n          \u003c/tbody\u003e\n        \u003c/table\u003e\n      \u003c/section\u003e\n    \u003c/\u003e\n  )\n}\n\nexport default Orders\n```\n\n\u003c/details\u003e\n\n## \u003ca name=\"links\"\u003e🔗 Links\u003c/a\u003e\n\nAll assets used in the project can be found [here](https://drive.google.com/file/d/1hoRwUtTFIiuOXPw-SDYj6wk4hZTMcYmL/view?usp=sharing)\n\n## \u003ca name=\"more\"\u003e🚀 More\u003c/a\u003e\n\n**Advance your skills with Next.js 14 Pro Course**\n\nEnjoyed creating this project? Dive deeper into our PRO courses for a richer learning adventure. They're packed with detailed explanations, cool features, and exercises to boost your skills. Give it a go!\n\n\u003ca href=\"https://jsmastery.pro/next14\" target=\"_blank\"\u003e\n\u003cimg src=\"https://github.com/sujatagunale/EasyRead/assets/151519281/557837ce-f612-4530-ab24-189e75133c71\" alt=\"Project Banner\"\u003e\n\u003c/a\u003e\n\n\u003cbr /\u003e\n\u003cbr /\u003e\n\n**Accelerate your professional journey with Expert Training program**\n\nAnd if you're hungry for more than just a course and want to understand how we learn and tackle tech challenges, hop into our personalized masterclass. We cover best practices, different web skills, and offer mentorship to boost your confidence. Let's learn and grow together!\n\n\u003ca href=\"https://www.jsmastery.pro/masterclass\" target=\"_blank\"\u003e\n\u003cimg src=\"https://github.com/sujatagunale/EasyRead/assets/151519281/fed352ad-f27b-400d-9b8f-c7fe628acb84\" alt=\"Project Banner\"\u003e\n\u003c/a\u003e\n\n#\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftiger-githubb%2Fevent-platform","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftiger-githubb%2Fevent-platform","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftiger-githubb%2Fevent-platform/lists"}