Skip to content

kusitms-bugi/FE-OLD

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

93 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐Ÿ“ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

packages/
โ”œโ”€โ”€ api/          # API ๊ด€๋ จ ๋กœ์ง ๋ฐ ํ›…
โ”œโ”€โ”€ ui/           # UI ์ปดํฌ๋„ŒํŠธ ๋ฐ ์Šคํƒ€์ผ
โ”œโ”€โ”€ config/       # ๊ณตํ†ต ์„ค์ •
โ””โ”€โ”€ tsconfig/     # TypeScript ์„ค์ •

๐Ÿ“ Git Commit Convention

1. Branch Naming Rule

Branch ์ด๋ฆ„์€ ์ž‘์—… ๋ชฉ์ ๊ณผ ์—ฐ๊ด€๋œ ์ด์Šˆ ๋ฒˆํ˜ธ๋ฅผ ํฌํ•จํ•˜๋Š” ๋ฐฉ์‹

<ํƒ€์ž…>/<์ด์Šˆ ๋ฒˆํ˜ธ>-<๊ฐ„๋‹จํ•œ ์„ค๋ช…>

- feature/1234-add-user-login
- bugfix/5678-fix-login-error
- release/1.2.0

Branch Type

  • feature/ - ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ ์‹œ
  • bugfix/ - ๋ฒ„๊ทธ ์ˆ˜์ • ์‹œ
  • hotfix/ - ๊ธด๊ธ‰ํ•œ ๋ฒ„๊ทธ ์ˆ˜์ • ์‹œ (๋ณดํ†ต ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ๋ฐœ์ƒ)
  • release/ - ๋ฆด๋ฆฌ์ฆˆ ์ค€๋น„ ์‹œ
  • chore/ - ๋นŒ๋“œ ๋ฐ ๊ธฐํƒ€ ์ž‘์—… ์ž๋™ํ™”, ๋ฌธ์„œ ์ž‘์—… ๋“ฑ ์ฝ”๋“œ์™€ ๊ด€๋ จ ์—†๋Š” ์ž‘์—…

๐Ÿ”ง API ํŒจํ‚ค์ง€์— ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ์ถ”๊ฐ€ํ•˜๊ธฐ

1. ์ƒˆ๋กœ์šด API ์—”๋“œํฌ์ธํŠธ ์ถ”๊ฐ€

1.1 ํƒ€์ž… ์ •์˜ ์ถ”๊ฐ€

// packages/api/src/types/index.ts
export interface NewFeatureResponse {
  id: string;
  name: string;
  status: 'active' | 'inactive';
  createdAt: string;
}

1.2 API ํด๋ผ์ด์–ธํŠธ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€

// packages/api/src/client/api-client.ts
export class ElectronAPIClient {
  // ๊ธฐ์กด ๋ฉ”์„œ๋“œ๋“ค...

  async getNewFeature(id: string): Promise<NewFeatureResponse> {
    if (typeof window !== 'undefined' && (window as any).electronAPI) {
      return await (window as any).electronAPI.getNewFeature(id);
    }
    throw new Error('Electron API not available');
  }
}

export class WebAPIClient {
  // ๊ธฐ์กด ๋ฉ”์„œ๋“œ๋“ค...

  async getNewFeature(id: string): Promise<NewFeatureResponse> {
    const response = await fetch(`${this.baseURL}/new-feature/${id}`);
    if (!response.ok) {
      throw new Error('Failed to fetch new feature');
    }
    return await response.json();
  }
}

1.3 React Query ํ›… ์ถ”๊ฐ€

// packages/api/src/hooks/use-new-feature.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { createAPIClient } from '../../index';
import { NewFeatureResponse } from '../types';

let apiClient: ReturnType<typeof createAPIClient> | null = null;

function getApiClient() {
  if (!apiClient) {
    apiClient = createAPIClient();
  }
  return apiClient;
}

// ์กฐํšŒ ํ›…
export function useNewFeature(id: string) {
  return useQuery({
    queryKey: ['newFeature', id],
    queryFn: () => getApiClient().getNewFeature(id),
    enabled: !!id,
    staleTime: 5 * 60 * 1000, // 5๋ถ„
    retry: 3,
  });
}

// ์ƒ์„ฑ/์ˆ˜์ •/์‚ญ์ œ ๋ฎคํ…Œ์ด์…˜ ํ›…
export function useNewFeatureMutation() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (data: Partial<NewFeatureResponse>) =>
      getApiClient().createNewFeature(data),
    onSuccess: () => {
      // ๊ด€๋ จ ์ฟผ๋ฆฌ ๋ฌดํšจํ™”
      queryClient.invalidateQueries({ queryKey: ['newFeature'] });
    },
  });
}

1.4 ํ›… ์ต์ŠคํฌํŠธ ์ถ”๊ฐ€

// packages/api/src/hooks/index.ts
export * from './use-new-feature';
// ๊ธฐ์กด ์ต์ŠคํฌํŠธ๋“ค...

1.5 ๋ฉ”์ธ ์ต์ŠคํฌํŠธ ์—…๋ฐ์ดํŠธ

// packages/api/index.ts
// ๊ธฐ์กด ์ต์ŠคํฌํŠธ๋“ค...
export * from './src/hooks/use-new-feature';

2. ์ƒˆ๋กœ์šด ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜ ์ถ”๊ฐ€

2.1 ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜ ์ž‘์„ฑ

// packages/api/src/utils/new-feature-utils.ts
export function formatNewFeatureName(name: string): string {
  return name.trim().toLowerCase().replace(/\s+/g, '-');
}

export function validateNewFeatureData(data: any): boolean {
  return data && typeof data.name === 'string' && data.name.length > 0;
}

2.2 ์œ ํ‹ธ๋ฆฌํ‹ฐ ์ต์ŠคํฌํŠธ ์ถ”๊ฐ€

// packages/api/src/utils/index.ts
export * from './new-feature-utils';
// ๊ธฐ์กด ์ต์ŠคํฌํŠธ๋“ค...

๐ŸŽจ UI ํŒจํ‚ค์ง€์— ์ƒˆ๋กœ์šด ์ปดํฌ๋„ŒํŠธ ์ถ”๊ฐ€ํ•˜๊ธฐ

1. ์ƒˆ๋กœ์šด ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ

1.1 ์ปดํฌ๋„ŒํŠธ ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ

packages/ui/src/components/NewComponent/
โ”œโ”€โ”€ NewComponent.tsx
โ”œโ”€โ”€ NewComponent.css (์„ ํƒ์‚ฌํ•ญ)
โ””โ”€โ”€ index.ts (์„ ํƒ์‚ฌํ•ญ)

1.2 ์ปดํฌ๋„ŒํŠธ ๊ตฌํ˜„

// packages/ui/src/components/NewComponent/NewComponent.tsx
import React from 'react';
import { cn } from '../../utils/cn';

interface NewComponentProps {
  title: string;
  description?: string;
  variant?: 'default' | 'primary' | 'secondary';
  size?: 'sm' | 'md' | 'lg';
  className?: string;
  children?: React.ReactNode;
}

export function NewComponent({
  title,
  description,
  variant = 'default',
  size = 'md',
  className,
  children,
}: NewComponentProps) {
  return (
    <div
      className={cn(
        'rounded-lg border p-4',
        {
          'bg-white border-gray-200': variant === 'default',
          'bg-blue-50 border-blue-200': variant === 'primary',
          'bg-gray-50 border-gray-300': variant === 'secondary',
          'p-2 text-sm': size === 'sm',
          'p-4 text-base': size === 'md',
          'p-6 text-lg': size === 'lg',
        },
        className
      )}
    >
      <h3 className="font-semibold text-gray-900">{title}</h3>
      {description && (
        <p className="mt-1 text-sm text-gray-600">{description}</p>
      )}
      {children}
    </div>
  );
}

1.3 ์ปดํฌ๋„ŒํŠธ ์ต์ŠคํฌํŠธ ์ถ”๊ฐ€

// packages/ui/src/components/NewComponent/index.ts
export { NewComponent } from './NewComponent';

2. Storybook ์Šคํ† ๋ฆฌ ์ถ”๊ฐ€

2.1 ์Šคํ† ๋ฆฌ ํŒŒ์ผ ์ƒ์„ฑ

// packages/ui/src/stories/NewComponent.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { NewComponent } from '../components/NewComponent/NewComponent';

const meta: Meta<typeof NewComponent> = {
  title: 'Components/NewComponent',
  component: NewComponent,
  parameters: {
    layout: 'centered',
  },
  tags: ['autodocs'],
  argTypes: {
    variant: {
      control: { type: 'select' },
      options: ['default', 'primary', 'secondary'],
    },
    size: {
      control: { type: 'select' },
      options: ['sm', 'md', 'lg'],
    },
  },
};

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
  args: {
    title: '๊ธฐ๋ณธ ์ปดํฌ๋„ŒํŠธ',
    description: '์ด๊ฒƒ์€ ๊ธฐ๋ณธ ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค.',
  },
};

export const Primary: Story = {
  args: {
    title: 'Primary ์ปดํฌ๋„ŒํŠธ',
    description: '์ด๊ฒƒ์€ Primary ์Šคํƒ€์ผ์˜ ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค.',
    variant: 'primary',
  },
};

export const Large: Story = {
  args: {
    title: 'Large ์ปดํฌ๋„ŒํŠธ',
    description: '์ด๊ฒƒ์€ Large ํฌ๊ธฐ์˜ ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค.',
    size: 'lg',
  },
};

3. ํŒจํ‚ค์ง€ ์ต์ŠคํฌํŠธ ์—…๋ฐ์ดํŠธ

3.1 ๋ฉ”์ธ ์ต์ŠคํฌํŠธ ์ถ”๊ฐ€

// packages/ui/index.tsx
import './src/styles/globals.css';

export * from './src/components/Button/Button';
export * from './src/components/Header/Header';
export * from './src/components/Page/Page';
export * from './src/components/Alert/Alert';
export * from './src/components/AnimatedBox/AnimatedBox';
export * from './src/components/NewComponent/NewComponent'; // ์ƒˆ๋กœ ์ถ”๊ฐ€

3.2 package.json exports ์—…๋ฐ์ดํŠธ

// packages/ui/package.json
{
  "exports": {
    ".": "./index.tsx",
    "./tokens.css": "./src/styles/tokens.css",
    "./globals.css": "./src/styles/globals.css",
    "./theme.css": "./src/styles/theme.css",
    "./Button": "./src/components/Button/Button.tsx",
    "./AnimatedBox": "./src/components/AnimatedBox/AnimatedBox.tsx",
    "./Header": "./src/components/Header/Header.tsx",
    "./Page": "./src/components/Page/Page.tsx",
    "./Alert": "./src/components/Alert/Alert.tsx",
    "./NewComponent": "./src/components/NewComponent/NewComponent.tsx"
  }
}

๐Ÿงช ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€ํ•˜๊ธฐ

API ํŒจํ‚ค์ง€ ํ…Œ์ŠคํŠธ

// packages/api/src/hooks/__tests__/use-new-feature.test.ts
import { renderHook, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useNewFeature } from '../use-new-feature';

const createWrapper = () => {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: { retry: false },
      mutations: { retry: false },
    },
  });
  return ({ children }: { children: React.ReactNode }) => (
    <QueryClientProvider client={queryClient}>
      {children}
    </QueryClientProvider>
  );
};

describe('useNewFeature', () => {
  it('should fetch new feature data', async () => {
    const { result } = renderHook(() => useNewFeature('test-id'), {
      wrapper: createWrapper(),
    });

    await waitFor(() => {
      expect(result.current.isSuccess).toBe(true);
    });
  });
});

UI ํŒจํ‚ค์ง€ ํ…Œ์ŠคํŠธ

// packages/ui/src/components/NewComponent/__tests__/NewComponent.test.tsx
import { render, screen } from '@testing-library/react';
import { NewComponent } from '../NewComponent';

describe('NewComponent', () => {
  it('renders with title', () => {
    render(<NewComponent title="Test Title" />);
    expect(screen.getByText('Test Title')).toBeInTheDocument();
  });

  it('renders with description', () => {
    render(
      <NewComponent
        title="Test Title"
        description="Test Description"
      />
    );
    expect(screen.getByText('Test Description')).toBeInTheDocument();
  });
});

๐Ÿ“ ๊ฐœ๋ฐœ ์›Œํฌํ”Œ๋กœ์šฐ

1. ๊ฐœ๋ฐœ ์‹œ์ž‘

# ์ „์ฒด ํ”„๋กœ์ ํŠธ ์˜์กด์„ฑ ์„ค์น˜
npm install

# ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹œ์ž‘ (์›น)
npm run dev

# Storybook ์‹œ์ž‘ (UI ํŒจํ‚ค์ง€)
cd packages/ui
npm run storybook

2. ์ƒˆ ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ

  1. API ํŒจํ‚ค์ง€: ํƒ€์ž… ์ •์˜ โ†’ API ํด๋ผ์ด์–ธํŠธ โ†’ React Query ํ›… โ†’ ์ต์ŠคํฌํŠธ
  2. UI ํŒจํ‚ค์ง€: ์ปดํฌ๋„ŒํŠธ ๊ตฌํ˜„ โ†’ Storybook ์Šคํ† ๋ฆฌ โ†’ ์ต์ŠคํฌํŠธ โ†’ ํ…Œ์ŠคํŠธ

3. ์ฝ”๋“œ ํ’ˆ์งˆ ๊ด€๋ฆฌ

# ํƒ€์ž… ์ฒดํฌ
npm run typecheck

# ๋ฆฐํŒ…
npm run lint

# ํฌ๋งทํŒ…
npm run format

# ํ…Œ์ŠคํŠธ
npm run test

๐Ÿ”„ ํŒจํ‚ค์ง€ ๊ฐ„ ์˜์กด์„ฑ

API ํŒจํ‚ค์ง€ ์‚ฌ์šฉ๋ฒ•

// apps/web/src/App.tsx ๋˜๋Š” apps/electron/layers/renderer/src/App.tsx
import { useHealth, useVersion, createAPIClient } from 'api';

function App() {
  const { data: health } = useHealth();
  const { data: version } = useVersion();

  return (
    <div>
      <p>Health: {health?.status}</p>
      <p>Version: {version?.version}</p>
    </div>
  );
}

UI ํŒจํ‚ค์ง€ ์‚ฌ์šฉ๋ฒ•

// apps/web/src/App.tsx ๋˜๋Š” apps/electron/layers/renderer/src/App.tsx
import { Button, Header, Page, NewComponent } from 'ui';
import 'ui/globals.css';

function App() {
  return (
    <Page>
      <Header title="My App" />
      <NewComponent
        title="Welcome"
        description="This is a new component"
        variant="primary"
      />
      <Button>Click me</Button>
    </Page>
  );
}

๐Ÿš€ ๋ฐฐํฌ ๋ฐ ๋นŒ๋“œ

ํŒจํ‚ค์ง€ ๋นŒ๋“œ

# ์ „์ฒด ํ”„๋กœ์ ํŠธ ๋นŒ๋“œ
npm run build

# ํŠน์ • ํŒจํ‚ค์ง€๋งŒ ๋นŒ๋“œ
cd packages/api && npm run typecheck
cd packages/ui && npm run build-storybook

Electron ์•ฑ ๋นŒ๋“œ

cd apps/electron
npm run build

About

๐ŸขKusitms 32nd ๊ฑฐ๋ถ€๊ธฐ๋ฆฐ Frontend Repository๐Ÿข

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •