Skip to content
Merged
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
27 changes: 16 additions & 11 deletions packages/frontend/src/index.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&family=Sora:wght@400;500;600;700&family=Manrope:wght@400;500;600;700&display=swap');
Comment thread
LucasSantana-Dev marked this conversation as resolved.

@import 'tailwindcss';

Expand Down Expand Up @@ -54,18 +54,23 @@
--color-sidebar-border: hsl(var(--sidebar-border));
--color-sidebar-ring: hsl(var(--sidebar-ring));

/* Discord blurple accent palette */
/* Neon Lucky brand palette (neon pink/orange) */
--color-lucky-neon-pink: #ec4899;
--color-lucky-neon-orange: #fb923c;
--color-lucky-neon-purple: #8b5cf6;

/* Discord blurple accent palette (legacy) */
--color-lucky-blurple-700: #4752c4;
--color-lucky-blurple-600: #5865f2;
--color-lucky-blurple-500: #6d7af4;
--color-lucky-blurple-400: #8891f6;
--color-lucky-blurple-200: #c0c5fb;

/* Semantic brand tokens (for bg-lucky-brand, text-lucky-brand, etc.) */
--color-lucky-brand: #5865f2;
--color-lucky-brand-strong: #4752c4;
--color-lucky-accent: #5865f2;
--color-lucky-accent-soft: #6d7af4;
/* Semantic brand tokens (now using neon pink) */
--color-lucky-brand: #ec4899;
--color-lucky-brand-strong: #db2777;
--color-lucky-accent: #fb923c;
--color-lucky-accent-soft: #fdba74;
Comment thread
LucasSantana-Dev marked this conversation as resolved.

/* Status colors */
--color-lucky-success: #23a55a;
Expand Down Expand Up @@ -118,8 +123,8 @@
--color-lucky-blue: #5865f2;
--color-lucky-purple: #5865f2;

--font-lucky-display: 'Inter', 'Segoe UI', system-ui, sans-serif;
--font-lucky-body: 'Inter', 'Segoe UI', system-ui, sans-serif;
--font-lucky-display: 'Sora', 'Segoe UI', system-ui, sans-serif;
--font-lucky-body: 'Manrope', 'Segoe UI', system-ui, sans-serif;
--font-lucky-mono: 'JetBrains Mono', 'SFMono-Regular', Menlo, Monaco, Consolas,
'Liberation Mono', 'Courier New', monospace;
--font-sans: var(--font-lucky-body);
Expand Down Expand Up @@ -372,8 +377,8 @@
--lucky-warning: var(--color-lucky-warning);
--lucky-info: var(--color-lucky-info);

--lucky-gradient-brand: linear-gradient(135deg, #5865f2 0%, #4752c4 100%);
--lucky-gradient-brand-soft: linear-gradient(135deg, rgb(88 101 242 / 0.15) 0%, rgb(71 82 196 / 0.05) 100%);
--lucky-gradient-brand: linear-gradient(135deg, #ec4899 0%, #fb923c 100%);
--lucky-gradient-brand-soft: linear-gradient(135deg, rgb(236 72 153 / 0.15) 0%, rgb(251 146 60 / 0.05) 100%);

--lucky-red: var(--lucky-brand);
--lucky-bg-primary: var(--lucky-surface-canvas);
Expand Down
91 changes: 64 additions & 27 deletions packages/frontend/src/pages/Landing.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, test, expect, vi, beforeEach } from 'vitest'
import { render, screen, waitFor } from '@testing-library/react'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import Landing from './Landing'
import { useAuthStore } from '@/stores/authStore'
Expand All @@ -9,7 +9,21 @@ import { api } from '@/services/api'

vi.mock('@/stores/authStore')
vi.mock('@/hooks/usePageMetadata')
vi.mock('framer-motion')
vi.mock('framer-motion', async () => {
const React = await import('react')
const passthrough = (tag: string) =>
React.forwardRef(({ children, ...props }: any, ref: any) =>
React.createElement(tag, { ...props, ref }, children),
)
return {
motion: new Proxy(
{},
{ get: (_t, prop: string) => passthrough(prop) },
),
AnimatePresence: ({ children }: any) => children,
useReducedMotion: vi.fn(() => false),
}
})
vi.mock('@/services/api')

const mockLogin = vi.fn()
Expand Down Expand Up @@ -49,33 +63,40 @@ describe('Landing', () => {

render(<Landing />)

const container = screen.getByText(/The all-in-one Discord bot for your community/i)
const container = screen.getByText(/All-in-one Discord bot/i)
.closest('.lucky-shell')

expect(container).toBeInTheDocument()
})

test('renders hero section with headline and CTA buttons', () => {
test('renders hero section with logo, headline and CTA buttons', () => {
render(<Landing />)

const headline = screen.getByRole('heading', {
name: /The all-in-one Discord bot for your community/i,
})
// Check for logo
const logo = screen.getByAltText('Lucky Bot')
expect(logo).toBeInTheDocument()

// Check for headline
const headline = screen.getByText(/All-in-one Discord bot/i)
expect(headline).toBeInTheDocument()

const subheadline = screen.getByText(/for your community/i)
expect(subheadline).toBeInTheDocument()

// Check for CTA buttons
const addToDiscordBtn = screen.getByRole('button', { name: /Add to Discord/i })
const openDashboardBtn = screen.getByRole('button', { name: /Open Dashboard/i })

expect(addToDiscordBtn).toBeInTheDocument()
expect(openDashboardBtn).toBeInTheDocument()
})

test('displays hero subtitle', () => {
test('displays hero subtitle with "Built for vibes" tagline', () => {
render(<Landing />)

expect(
screen.getByText(
/Music, moderation, custom commands, auto-mod, and a full web dashboard./i
/Music, moderation, custom commands, auto-mod, and a full web dashboard\. Built for vibes\./i
)
).toBeInTheDocument()
})
Expand All @@ -97,7 +118,7 @@ describe('Landing', () => {
})
})

test('renders stats strip with live data', async () => {
test('renders stats strip with live data and icons', async () => {
render(<Landing />)

// Wait for the stats labels to be rendered
Expand Down Expand Up @@ -129,26 +150,32 @@ describe('Landing', () => {
})
})

test('FAQ accordion contains expandable details elements', () => {
test('FAQ items are expandable via button clicks', async () => {
render(<Landing />)

const faqSection = screen.getByText(/Frequently Asked Questions/)
expect(faqSection).toBeInTheDocument()
const user = userEvent.setup()

// Get the first FAQ button
const firstQuestion = screen.getByText('Is Lucky free?')
const detailsElement = firstQuestion.closest('details')
const firstButton = firstQuestion.closest('button')

expect(detailsElement).toBeInTheDocument()
expect(detailsElement).toHaveClass('rounded-lg', 'border', 'border-lucky-border')
expect(firstButton).toBeInTheDocument()

// Click to expand
await user.click(firstButton!)

// Answer should be visible
const answer = screen.getByText('Yes, Lucky is completely free with no premium tier.')
expect(answer).toBeInTheDocument()
})

test('renders footer with all link sections', () => {
test('renders footer with logo and all link sections', () => {
render(<Landing />)

expect(screen.getByText('Lucky')).toBeInTheDocument()
// Check for footer logo
const footerLogo = screen.getAllByAltText('Lucky').find(el => el.closest('footer'))
expect(footerLogo).toBeInTheDocument()

expect(screen.getByText('Links')).toBeInTheDocument()
expect(screen.getByText('Support')).toBeInTheDocument()
})
Expand Down Expand Up @@ -177,18 +204,16 @@ describe('Landing', () => {
})

test('renders Add to Discord button with correct href when clicked', async () => {
const openSpy = vi.spyOn(window, 'open').mockReturnValue(null)
render(<Landing />)

const addToDiscordBtn = screen.getByRole('button', { name: /Add to Discord/i })
expect(addToDiscordBtn).toBeInTheDocument()

vi.spyOn(window, 'open').mockReturnValue(null)

const user = userEvent.setup()
await user.click(addToDiscordBtn)
fireEvent.click(addToDiscordBtn)

await waitFor(() => {
expect(window.open).toHaveBeenCalledWith(
expect(openSpy).toHaveBeenCalledWith(
expect.stringContaining('discord.com/oauth2/authorize'),
'_blank'
)
Expand All @@ -199,9 +224,7 @@ describe('Landing', () => {
render(<Landing />)

const openDashboardBtn = screen.getByRole('button', { name: /Open Dashboard/i })

const user = userEvent.setup()
await user.click(openDashboardBtn)
fireEvent.click(openDashboardBtn)

await waitFor(() => {
expect(mockLogin).toHaveBeenCalled()
Expand Down Expand Up @@ -242,10 +265,24 @@ describe('Landing', () => {
test('renders all footer columns with proper hierarchy', () => {
render(<Landing />)

const footerText = screen.getByText('Discord bot with music, moderation, and more.')
const footerText = screen.getByText('Discord bot with music, moderation, and more. Built for vibes.')
expect(footerText).toBeInTheDocument()

const needHelpText = screen.getByText(/Need help\? Join our Discord community\./)
expect(needHelpText).toBeInTheDocument()
})

test('renders Powerful Features heading', () => {
render(<Landing />)

const featuresHeading = screen.getByRole('heading', { name: /Powerful Features/i })
expect(featuresHeading).toBeInTheDocument()
})

test('renders Frequently Asked Questions heading', () => {
render(<Landing />)

const faqHeading = screen.getByRole('heading', { name: /Frequently Asked Questions/i })
expect(faqHeading).toBeInTheDocument()
})
})
Loading
Loading