DEV Community

Cover image for (Part 2) Build a Simple Chat Character Gallery: Project Setup
James
James

Posted on • Originally published at hashnode.com

(Part 2) Build a Simple Chat Character Gallery: Project Setup

📚 Tutorial Series: Build a Chat Character Gallery

  1. Introduction & Overview
  2. Project Setup - You’re here

Part 2: Project Setup

In the previous post, we talked about the concept behind the Chat Character Gallery, what it is, what features it includes, and what you’ll build by the end of this tutorial series.

Now, let’s roll up our sleeves and dive into the tech stack and project setup. In this part, we’ll go over the tools we’ll be using and set up the foundation of our app so we can start building in the next steps.

Here’s the tools that we will use to build our project:

Tech Stack:

  • Next.js
  • TypeScript
  • TailwindCSS
  • Axios

API:
Companion AI - An API designed to generate natural, human-like responses for chatbots


🚀 Step 1: Initialize Your Project

npx create-next-app@latest chat-character-gallery
cd chat-character-gallery
Enter fullscreen mode Exit fullscreen mode

Choose Yes for everything except for Turbopack and import alias

Add tailwind.config.js to your directory and add this file

/** @type {import('tailwindcss').Config} */
const config = {
  darkMode: ['class'],
  content: [
    './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
    './src/components/**/*.{js,ts,jsx,tsx,mdx}',
    './src/app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {
      colors: {
        border: 'hsl(var(--border))',
        input: 'hsl(var(--input))',
        ring: 'hsl(var(--ring))',
        background: 'hsl(var(--background))',
        foreground: 'hsl(var(--foreground))',
        primary: {
          50: '#fef2f2',
          100: '#fee2e2',
          200: '#fecaca',
          300: '#fca5a5',
          400: '#f87171',
          500: '#ef4444',
          600: '#dc2626',
          700: '#b91c1c',
          800: '#991b1b',
          900: '#7f1d1d',
          DEFAULT: '#ef4444',
          foreground: '#ffffff',
        },
        secondary: {
          50: '#f0f9ff',
          100: '#e0f2fe',
          200: '#bae6fd',
          300: '#7dd3fc',
          400: '#38bdf8',
          500: '#0ea5e9',
          600: '#0284c7',
          700: '#0369a1',
          800: '#075985',
          900: '#0c4a6e',
          DEFAULT: '#0ea5e9',
          foreground: '#ffffff',
        },
        accent: {
          50: '#fdf4ff',
          100: '#fae8ff',
          200: '#f5d0fe',
          300: '#f0abfc',
          400: '#e879f9',
          500: '#d946ef',
          600: '#c026d3',
          700: '#a21caf',
          800: '#86198f',
          900: '#701a75',
          DEFAULT: '#d946ef',
          foreground: '#ffffff',
        },
        success: {
          50: '#f0fdf4',
          100: '#dcfce7',
          200: '#bbf7d0',
          300: '#86efac',
          400: '#4ade80',
          500: '#22c55e',
          600: '#16a34a',
          700: '#15803d',
          800: '#166534',
          900: '#14532d',
          DEFAULT: '#22c55e',
          foreground: '#ffffff',
        },
        warning: {
          50: '#fffbeb',
          100: '#fef3c7',
          200: '#fde68a',
          300: '#fcd34d',
          400: '#fbbf24',
          500: '#f59e0b',
          600: '#d97706',
          700: '#b45309',
          800: '#92400e',
          900: '#78350f',
          DEFAULT: '#f59e0b',
          foreground: '#ffffff',
        },
        destructive: {
          50: '#fef2f2',
          100: '#fee2e2',
          200: '#fecaca',
          300: '#fca5a5',
          400: '#f87171',
          500: '#ef4444',
          600: '#dc2626',
          700: '#b91c1c',
          800: '#991b1b',
          900: '#7f1d1d',
          DEFAULT: '#ef4444',
          foreground: '#ffffff',
        },
        muted: {
          DEFAULT: '#f8fafc',
          foreground: '#475569',
        },
        popover: {
          DEFAULT: '#ffffff',
          foreground: '#0f172a',
        },
        card: {
          DEFAULT: '#ffffff',
          foreground: '#0f172a',
        },
        // Vibrant marketplace colors
        marketplace: {
          purple: '#8b5cf6',
          pink: '#ec4899',
          orange: '#f97316',
          teal: '#14b8a6',
          lime: '#84cc16',
          rose: '#f43f5e',
          indigo: '#6366f1',
          cyan: '#06b6d4',
        },
      },
      borderRadius: {
        lg: 'var(--radius)',
        md: 'calc(var(--radius) - 2px)',
        sm: 'calc(var(--radius) - 4px)',
      },
      fontFamily: {
        sans: ['Inter', 'system-ui', 'sans-serif'],
        display: ['Poppins', 'system-ui', 'sans-serif'],
        mono: ['JetBrains Mono', 'monospace'],
      },
      backgroundImage: {
        'gradient-primary': 'linear-gradient(135deg, #ef4444 0%, #d946ef 100%)',
        'gradient-secondary':
          'linear-gradient(135deg, #0ea5e9 0%, #8b5cf6 100%)',
        'gradient-accent': 'linear-gradient(135deg, #d946ef 0%, #f97316 100%)',
        'gradient-marketplace':
          'linear-gradient(135deg, #8b5cf6 0%, #ec4899 50%, #f97316 100%)',
        'gradient-card':
          'linear-gradient(135deg, rgba(139, 92, 246, 0.1) 0%, rgba(236, 72, 153, 0.1) 100%)',
      },
      animation: {
        'fade-in': 'fadeIn 0.5s ease-in-out',
        'slide-up': 'slideUp 0.3s ease-out',
        'bounce-gentle': 'bounceGentle 2s infinite',
        'pulse-glow': 'pulseGlow 2s ease-in-out infinite',
      },
      keyframes: {
        fadeIn: {
          '0%': { opacity: '0' },
          '100%': { opacity: '1' },
        },
        slideUp: {
          '0%': { transform: 'translateY(10px)', opacity: '0' },
          '100%': { transform: 'translateY(0)', opacity: '1' },
        },
        bounceGentle: {
          '0%, 100%': { transform: 'translateY(0)' },
          '50%': { transform: 'translateY(-5px)' },
        },
        pulseGlow: {
          '0%, 100%': { boxShadow: '0 0 0 0 rgba(217, 70, 239, 0.4)' },
          '50%': { boxShadow: '0 0 0 10px rgba(217, 70, 239, 0)' },
        },
      },
    },
  },
  plugins: [import('tailwindcss-animate')],
};

export default config;
Enter fullscreen mode Exit fullscreen mode

Please update the global.css as well:

@import "tailwindcss";

@custom-variant dark (&:is(.dark *));

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --font-sans: var(--font-geist-sans);
  --font-mono: var(--font-geist-mono);
  --color-sidebar-ring: var(--sidebar-ring);
  --color-sidebar-border: var(--sidebar-border);
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
  --color-sidebar-accent: var(--sidebar-accent);
  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
  --color-sidebar-primary: var(--sidebar-primary);
  --color-sidebar-foreground: var(--sidebar-foreground);
  --color-sidebar: var(--sidebar);
  --color-chart-5: var(--chart-5);
  --color-chart-4: var(--chart-4);
  --color-chart-3: var(--chart-3);
  --color-chart-2: var(--chart-2);
  --color-chart-1: var(--chart-1);
  --color-ring: var(--ring);
  --color-input: var(--input);
  --color-border: var(--border);
  --color-destructive: var(--destructive);
  --color-accent-foreground: var(--accent-foreground);
  --color-accent: var(--accent);
  --color-muted-foreground: var(--muted-foreground);
  --color-muted: var(--muted);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-secondary: var(--secondary);
  --color-primary-foreground: var(--primary-foreground);
  --color-primary: var(--primary);
  --color-popover-foreground: var(--popover-foreground);
  --color-popover: var(--popover);
  --color-card-foreground: var(--card-foreground);
  --color-card: var(--card);
  --radius-sm: calc(var(--radius) - 4px);
  --radius-md: calc(var(--radius) - 2px);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) + 4px);
}

:root {
  --radius: 0.75rem;
  --background: oklch(0.985 0.002 247.839);
  --foreground: oklch(0.141 0.005 247.839);
  --card: oklch(1 0 0);
  --card-foreground: oklch(0.141 0.005 247.839);
  --popover: oklch(1 0 0);
  --popover-foreground: oklch(0.141 0.005 247.839);
  --primary: oklch(0.623 0.214 259.815);
  --primary-foreground: oklch(0.97 0.001 106.424);
  --secondary: oklch(0.586 0.144 245.096);
  --secondary-foreground: oklch(0.97 0.001 106.424);
  --muted: oklch(0.967 0.001 106.424);
  --muted-foreground: oklch(0.556 0.002 106.424);
  --accent: oklch(0.714 0.203 292.717);
  --accent-foreground: oklch(0.97 0.001 106.424);
  --destructive: oklch(0.637 0.237 25.331);
  --border: oklch(0.92 0.004 286.32);
  --input: oklch(0.92 0.004 286.32);
  --ring: oklch(0.623 0.214 259.815);
  --chart-1: oklch(0.646 0.222 41.116);
  --chart-2: oklch(0.6 0.118 184.704);
  --chart-3: oklch(0.398 0.07 227.392);
  --chart-4: oklch(0.828 0.189 84.429);
  --chart-5: oklch(0.769 0.188 70.08);
  --sidebar: oklch(0.985 0.002 247.839);
  --sidebar-foreground: oklch(0.141 0.005 247.839);
  --sidebar-primary: oklch(0.623 0.214 259.815);
  --sidebar-primary-foreground: oklch(0.97 0.001 106.424);
  --sidebar-accent: oklch(0.967 0.001 106.424);
  --sidebar-accent-foreground: oklch(0.141 0.005 247.839);
  --sidebar-border: oklch(0.92 0.004 286.32);
  --sidebar-ring: oklch(0.623 0.214 259.815);
}

.dark {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
  --card: oklch(0.205 0 0);
  --card-foreground: oklch(0.985 0 0);
  --popover: oklch(0.205 0 0);
  --popover-foreground: oklch(0.985 0 0);
  --primary: oklch(0.922 0 0);
  --primary-foreground: oklch(0.205 0 0);
  --secondary: oklch(0.269 0 0);
  --secondary-foreground: oklch(0.985 0 0);
  --muted: oklch(0.269 0 0);
  --muted-foreground: oklch(0.708 0 0);
  --accent: oklch(0.269 0 0);
  --accent-foreground: oklch(0.985 0 0);
  --destructive: oklch(0.704 0.191 22.216);
  --border: oklch(1 0 0 / 10%);
  --input: oklch(1 0 0 / 15%);
  --ring: oklch(0.556 0 0);
  --chart-1: oklch(0.488 0.243 264.376);
  --chart-2: oklch(0.696 0.17 162.48);
  --chart-3: oklch(0.769 0.188 70.08);
  --chart-4: oklch(0.627 0.265 303.9);
  --chart-5: oklch(0.645 0.246 16.439);
  --sidebar: oklch(0.205 0 0);
  --sidebar-foreground: oklch(0.985 0 0);
  --sidebar-primary: oklch(0.488 0.243 264.376);
  --sidebar-primary-foreground: oklch(0.985 0 0);
  --sidebar-accent: oklch(0.269 0 0);
  --sidebar-accent-foreground: oklch(0.985 0 0);
  --sidebar-border: oklch(1 0 0 / 10%);
  --sidebar-ring: oklch(0.556 0 0);
}

@layer base {
  * {
    @apply border-border outline-ring/50;
  }
  body {
    @apply bg-background text-foreground;
  }
}
Enter fullscreen mode Exit fullscreen mode

📦 Step 2: Install other dependencies

pnpm add axios @tanstack/react-query clsx tailwind-merge
Enter fullscreen mode Exit fullscreen mode

axios - Promise based HTTP client for browsers

@tanstack/react-query - Powerful asynchronous state management for TS/JS or React

clsx and tailwind-merge - Packages to be used to merge TailwindCSS


🎨 Step 3: Set up ESLint

Refer to this documentation for more about ESLint in NextJs: NextJs - ESLint

Install these devDependencies:

pnpm add -D typescript-eslint @eslint/js globals eslint-plugin-react
Enter fullscreen mode Exit fullscreen mode

🧪  Step 4: Update next.config.ts and tsconfig.json

These are just to make sure you’re using the same config as what I’m using. Feel free to edit these to your preference later:

Update next.config.ts

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  reactStrictMode: true,
  transpilePackages: [],
  experimental: {
    typedRoutes: true,
  },
  devIndicators: false,
};

export default nextConfig;
Enter fullscreen mode Exit fullscreen mode

Update tsconfig.json

{
  "compilerOptions": {
    "target": "es2020",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [{ "name": "next" }],
    "baseUrl": ".",
    "paths": {
      "@/modules/*": ["src/modules/*"],
      "@/components/*": ["src/components/*"],
      "@/*": ["src/*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}
Enter fullscreen mode Exit fullscreen mode

🤖 Step 5: Setup your .env

Here’s the example of .env file. Please make sure to not push .env to your Git.

NEXT_PUBLIC_API_URL=YOUR_API_URL
NEXT_PUBLIC_X_RAPID_API_KEY=YOUR_X_RAPID_API_KEY
NEXT_PUBLIC_X_RAPID_API_HOST=YOUR_X_RAPID_API_HOST
NEXT_PUBLIC_API_KEY=YOUR_API_KEY
Enter fullscreen mode Exit fullscreen mode

Next would be to Sign up to the API that we will be using for this project, which is Companion AI API.

Please follow their tutorial to get your API Key.

New posts will be released every 2–3 days, so make sure to subscribe or bookmark this page to stay updated!

Please check Part 3 here:
(Part 3) Build a Simple Chat Character Gallery: Creating Card Component

Top comments (0)