snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
---
title: 모범 사례
---
이 문서는 백엔드 작업 시 따를 모범 사례를 설명합니다.
## 모듈형 접근법 따르기
백엔드는 NestJS로 작업할 때 기본 원칙 중 하나인 모듈형 접근법을 따릅니다. 코드베이스를 깔끔하고 체계적으로 유지하려면 코드를 재사용 가능한 모듈로 분리하십시오.
각 모듈은 특정 기능을 캡슐화하고 명확하게 정의된 범위를 가져야 합니다. 이 모듈형 접근법은 관심사의 명확한 분리를 가능하게 하고 불필요한 복잡성을 제거합니다.
## 모듈에서 사용할 서비스를 노출시키십시오.
항상 명확하고 단일한 책임을 가진 서비스를 생성하여 코드 가독성과 유지보수성을 향상시킵니다. 서비스의 이름을 일관되게 설명적으로 지정하십시오.
다른 모듈에서 사용하고자 하는 서비스를 노출해야 합니다. 다른 모듈에 서비스를 노출하는 것은 NestJS의 강력한 의존성 주입 시스템을 통해 가능하며, 구성 요소 간의 느슨한 결합을 촉진합니다.
## `any` 타입 사용 피하기
변수를 `any`로 선언하면 TypeScript의 타입 검사자가 타입 검사를 수행하지 않으므로 변수에 모든 유형의 값을 할당할 수 있게 됩니다. TypeScript는 값에 따라 변수의 타입을 추론하기 위해 타입 추론을 사용합니다. 이를 `any`로 선언하면 TypeScript는 더 이상 타입을 추론할 수 없습니다. 이는 개발 중 타입 관련 오류를 잡기 어렵게 만들어 런타임 오류로 이어지고, 코드의 유지보수성과 신뢰성이 떨어지며, 다른 사람이 이해하기도 어려워집니다.
이것이 모든 것이 타입을 가져야 하는 이유입니다. 따라서 이름(first name)과 성(last name)을 가진 새 개체를 만든다면, 다루는 개체의 구조를 정의하는 이름과 성을 포함한 인터페이스나 타입을 만들어야 합니다.

View File

@@ -0,0 +1,39 @@
---
title: 사용자 지정 개체
---
개체는 조직 고유의 데이터(레코드, 속성 및 값)를 저장할 수 있는 구조입니다. Twenty에는 표준 개체와 사용자 지정 개체가 모두 제공됩니다.
표준 개체는 모든 사용자에게 제공되는 속성이 있는 내장 개체입니다. Twenty의 표준 개체 예시로는 Company(회사)와 Person(사람)이 있습니다. 표준 개체는 모든 Twenty 사용자에게 제공되는 표준 필드를 가지고 있으며, 예를 들어 Company.displayName과 같습니다.
사용자 지정 개체는 조직 고유의 정보를 저장하기 위해 생성할 수 있는 개체입니다. 이들은 내장되어 있지 않으며, 워크스페이스의 구성원이 표준 개체로는 적합하지 않은 정보를 보관하기 위해 사용자 지정 개체를 생성하고 사용자 정의할 수 있습니다.
## 고급 스키마
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/custom-object-schema.png" alt="고급 스키마" />
</div>
<br />
## 작동 원리
사용자 지정 개체는 개체의 형태, 이름 및 유형을 결정하는 메타데이터 테이블에서 비롯됩니다. 이 모든 정보는 테이블로 구성된 메타데이터 스키마 데이터베이스에 있습니다.
* **DataSource**: 데이터가 존재하는 위치를 설명합니다.
* **Object**: 개체를 설명하고 DataSource와 연결됩니다.
* **Field**: 개체의 필드를 설명하고 개체와 연결됩니다.
사용자 지정 개체를 추가하려면 /metadata API를 쿼리하십시오. 이는 메타데이터를 해당 API에 따라 업데이트하고, 메타데이터를 기반으로 GraphQL 스키마를 계산하여 나중에 사용할 수 있도록 GQL 캐시에 저장합니다.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/add-custom-objects.jpeg" alt="/metadata API를 쿼리하여 사용자 지정 개체를 추가하십시오." />
</div>
<br />
데이터를 가져오려면 /graphql 엔드포인트를 통해 쿼리를 수행하고 Query Resolver를 통해 전달하는 과정이 포함됩니다.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/custom-object-schema.png" alt="/graphql 엔드포인트를 쿼리하여 데이터를 가져오십시오." />
</div>

View File

@@ -0,0 +1,46 @@
---
title: 기능 플래그
---
기능 플래그는 실험적 기능을 숨기는 데 사용됩니다. Twenty의 경우, 사용자 수준이 아니라 워크스페이스 수준에서 설정됩니다.
## 새로운 기능 플래그 추가
`FeatureFlagKey.ts`에 기능 플래그 추가:
```ts
type FeatureFlagKey =
| 'IS_FEATURENAME_ENABLED'
| ...;
```
또한 `feature-flag.entity.ts`의 열거형에 추가하십시오:
```ts
enum FeatureFlagKeys {
IsFeatureNameEnabled = 'IS_FEATURENAME_ENABLED',
...
}
```
**백엔드** 기능에 기능 플래그를 적용하려면 다음을 사용하십시오:
```ts
@Gate({
featureFlag: 'IS_FEATURENAME_ENABLED',
})
```
**프론트엔드** 기능에 기능 플래그를 적용하려면 다음을 사용하십시오:
```ts
const isFeatureNameEnabled = useIsFeatureEnabled('IS_FEATURENAME_ENABLED');
```
## 배포를 위한 기능 플래그 구성
테이블 `core.featureFlag`의 해당 레코드 변경:
| id | 키 | workspaceId | 값 |
| -- | ------------------------ | ----------- | --- |
| 임의 | `IS_FEATURENAME_ENABLED` | WorkspaceID | `참` |

View File

@@ -0,0 +1,125 @@
---
title: 폴더 아키텍처
info: 서버 폴더 아키텍처에 대한 상세 분석
---
백엔드 디렉토리 구조는 다음과 같습니다:
```
server
└───ability
└───constants
└───core
└───database
└───decorators
└───filters
└───guards
└───health
└───integrations
└───metadata
└───workspace
└───utils
```
## 권한
각 엔티티에 대한 권한을 정의하고 핸들러를 포함합니다.
## 데코레이터
추가 기능을 위한 NestJS 커스텀 데코레이터를 정의합니다.
자세한 내용은 [커스텀 데코레이터](https://docs.nestjs.com/custom-decorators)를 참조하세요.
## 필터
GraphQL 엔드포인트에서 발생할 수 있는 예외를 처리하기 위한 예외 필터를 포함합니다.
## 가드
자세한 내용은 [가드](https://docs.nestjs.com/guards)를 참조하세요.
## 상태
데이터베이스가 예상대로 작동하는지 확인하기 위해 JSON을 반환하는 공개 API(healthz)를 포함합니다.
## 메타데이터
커스텀 객체를 정의하고 GraphQL API (graphql/metadata)를 제공합니다.
## 워크스페이스
메타데이터 기반의 커스텀 GraphQL 스키마를 생성하고 제공합니다.
### 워크스페이스 디렉터리 구조
```
workspace
└───workspace-schema-builder
└───factories
└───graphql-types
└───database
└───interfaces
└───object-definitions
└───services
└───storage
└───utils
└───workspace-resolver-builder
└───factories
└───interfaces
└───workspace-query-builder
└───factories
└───interfaces
└───workspace-query-runner
└───interfaces
└───utils
└───workspace-datasource
└───workspace-manager
└───workspace-migration-runner
└───utils
└───workspace.module.ts
└───workspace.factory.spec.ts
└───workspace.factory.ts
```
작업 공간 디렉토리의 루트에는 개별 작업 공간을 위한 스키마를 맞춤화하는 `createGraphQLSchema` 함수가 포함된 `workspace.factory.ts` 파일이 있습니다. 이 함수는 메타데이터를 사용하여 작업 공간별 스키마를 생성합니다. 스키마와 리졸버 구조를 분리하여, 이러한 개별 요소를 결합하는 `makeExecutableSchema` 함수를 사용합니다.
이 전략은 조직화뿐만 아니라 성능 및 확장성을 향상시키기 위해 생성된 타입 정의를 캐시하는 최적화에도 기여합니다.
### 작업 공간 스키마 빌더
GraphQL 스키마를 생성하며 다음을 포함합니다:
#### 팩토리:
GraphQL 관련 구성 요소를 생성하는 특수 생성자입니다.
* type.factory는 `TypeMapperService`를 사용하여 필드 메타데이터를 GraphQL 타입으로 변환합니다.
* type-definition.factory는 `objectMetadata`에서 파생된 GraphQL 입력 또는 출력 객체를 만듭니다.
#### GraphQL 타입
열거형, 입력, 객체, 스칼라를 포함하며, 스키마 구축의 기초로 사용됩니다.
#### 인터페이스 및 객체 정의
GraphQL 엔티티를 위한 청사진을 포함하며, `MONEY`나 `URL`과 같은 사전 정의와 커스텀 타입을 포함합니다.
#### 서비스
FieldMetadataType과 적절한 GraphQL 스칼라 또는 쿼리 수정자를 연결하는 서비스가 포함되어 있습니다.
#### 저장소
GraphQL 타입의 중복을 방지하기 위해 재사용 가능한 타입 정의를 포함하는 `TypeDefinitionsStorage` 클래스를 포함합니다.
### 작업 공간 리졸버 빌더
GraphQL 스키마를 쿼리하고 변경하기 위한 리졸버 함수를 만듭니다.
이 디렉토리의 각 팩토리는 다양한 테이블에 걸쳐 적응 가능한 `FindManyResolverFactory`와 같은 명확한 리졸버 타입을 생성할 책임이 있습니다.
### 작업 공간 쿼리 실행기
데이터베이스에서 생성된 쿼리를 실행하고 결과를 구문 분석합니다.

View File

@@ -0,0 +1,41 @@
---
title: 메시지 큐
---
큐는 비동기 작업을 수행할 수 있게 합니다. 이들은 회원 가입 시 환영 이메일 보내기와 같은 백그라운드 작업을 수행하는 데 사용될 수 있습니다.
각 사용 사례는 `MessageQueueServiceBase`로부터 확장된 자체 큐 클래스를 가집니다.
현재, 큐 드라이버로 `bull-mq`[bull-mq](https://bullmq.io/)만 지원합니다.
## 새 큐를 생성하고 사용하는 단계
1. 새 큐에 대한 큐 이름을 열거형 `MESSAGE_QUEUES`에 추가합니다.
2. 큐 이름을 종속성 토큰으로 하여 큐의 팩토리 구현을 제공합니다.
3. 생성한 큐를 큐 이름을 종속성 토큰으로 하여 필요한 모듈/서비스에 주입합니다.
4. 프로듀서와 마찬가지로 토큰 기반 주입을 사용하는 워커 클래스를 추가합니다.
### 사용 예시
```ts
class Resolver {
constructor(@Inject(MESSAGE_QUEUES.custom) private queue: MessageQueueService) {}
async onSomeAction() {
//business logic
await this.queue.add(someData);
}
}
//async worker
class CustomWorker {
constructor(@Inject(MESSAGE_QUEUES.custom) private queue: MessageQueueService) {
this.initWorker();
}
async initWorker() {
await this.queue.work(async ({ id, data }) => {
//worker logic
});
}
}
```

View File

@@ -0,0 +1,87 @@
---
title: 백엔드 명령어
icon: terminal
---
## 유용한 명령어
이 명령어들은 packages/twenty-server 폴더에서 실행되어야 합니다.
다른 폴더에서 `npx nx {command} twenty-server` (또는 `npx nx run twenty-server:{command}`)를 실행할 수 있습니다.
### 최초 설정
```
npx nx database:reset twenty-server # setup the database with dev seeds
```
### 서버 시작
```
npx nx run twenty-server:start
```
### Lint
```
npx nx run twenty-server:lint # pass --fix to fix lint errors
```
### 테스트
```
npx nx run twenty-server:test:unit # run unit tests
npx nx run twenty-server:test:integration # run integration tests
```
참고: 데이터베이스를 재설정한 뒤 통합 테스트를 실행해야 하는 경우 `npx nx run twenty-server:test:integration:with-db-reset`을 실행할 수 있습니다.
### 데이터베이스 재설정
데이터베이스를 재설정하고 시드 하려면 다음 명령어를 실행하십시오:
```bash
npx nx run twenty-server:database:reset
```
### 마이그레이션
#### Core/Metadata 스키마의 객체용 (TypeORM)
```bash
npx nx run twenty-server:database:migrate:generate
```
## 기술 스택
Twenty는 주로 NestJS를 백엔드에 사용합니다.
Prisma가 우리가 처음으로 사용한 ORM이었습니다. 그러나 사용자들이 사용자 지정 필드와 사용자 지정 객체를 만들 수 있도록 하려면 세밀하게 제어할 수 있어야 하므로 더 낮은 수준이 더 합리적이었습니다. 현재 프로젝트는 TypeORM을 사용합니다.
다음은 현재 기술 스택의 모습입니다.
**코어**
* [NestJS](https://nestjs.com/)
* [TypeORM](https://typeorm.io/)
* [GraphQL Yoga](https://the-guild.dev/graphql/yoga-server)
**데이터베이스**
* [Postgres](https://www.postgresql.org/)
**서드파티 연동**
* 버그 추적을 위한 [Sentry](https://sentry.io/welcome/)
**테스트**
* [Jest](https://jestjs.io/)
**도구**
* [Yarn](https://yarnpkg.com/)
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
**개발**
* [AWS EKS](https://aws.amazon.com/eks/)

View File

@@ -0,0 +1,85 @@
---
title: Zapier 앱
---
[Zapier](https://zapier.com/)을 사용하여 Twenty를 3000개 이상의 앱과 손쉽게 동기화하세요. 작업을 자동화하고 생산성을 높여 고객 관계를 강화하세요!
## Zapier 소개
Zapier는 팀이 매일 사용하는 앱을 연결하여 워크플로우를 자동화할 수 있게 하는 도구입니다. Zapier의 기본 개념은 자동화 워크플로우인 Zaps이며, 이는 트리거와 액션을 포함합니다.
Zapier 작동 방식에 대해 [여기](https://zapier.com/how-it-works)에서 더 알아볼 수 있습니다.
## 설정
### 1단계: Zapier 패키지 설치
```bash
cd packages/twenty-zapier
yarn
```
### 2단계: CLI로 로그인
Zapier 자격 증명을 사용하여 CLI로 로그인하세요:
```bash
zapier login
```
### 3단계: 환경 변수 설정
`packages/twenty-zapier` 폴더에서 아래 명령을 실행하세요:
```bash
cp .env.example .env
```
애플리케이션을 로컬로 실행하고, [http://localhost:3000/settings/api-webhooks](http://localhost:3000/settings/api-webhooks)에 접속하여 API 키를 생성하세요.
.env 파일의 **YOUR_API_KEY** 값을 방금 생성한 API 키로 바꾸세요.
## 개발
<Warning>
`zapier` 명령어를 실행하기 전에 반드시 `yarn build`를 실행하세요.
</Warning>
### 테스트
```bash
yarn test
```
### Lint
```bash
yarn format
```
### 코드를 편집할 때 감시 및 컴파일
```bash
yarn watch
```
### Zapier 앱 검증
```bash
yarn validate
```
### Zapier 앱 배포
```bash
yarn deploy
```
### 모든 Zapier CLI 명령 목록
```bash
zapier
```

View File

@@ -0,0 +1,79 @@
---
title: 버그, 요청 및 Pull Request
icon: bug
info: 이슈를 보고하고, 기능을 요청하고, 코드에 기여하세요
---
## 버그 신고
버그를 보고하려면 [GitHub에서 이슈를 생성해 주세요](https://github.com/twentyhq/twenty/issues/new).
[Discord](https://discord.gg/cx5n4Jzs57)에서 도움을 요청할 수도 있습니다.
## 기능 요청
버그인지 확신이 없고 기능 요청에 더 가깝다고 느끼신다면, 대신 [토론을 열어 주세요](https://github.com/twentyhq/twenty/discussions/new).
## Pull Request 제출하기
Twenty에 코드로 기여하는 첫 단계는 Pull Request(PR)입니다.
### 시작하기 전에
1. 관련 작업이 있는지 [기존 이슈](https://github.com/twentyhq/twenty/issues)를 확인하세요
2. 새 기능의 경우 먼저 이슈를 열어 논의하세요
3. 우리의 [행동 강령](https://github.com/twentyhq/twenty/blob/main/.github/CODE_OF_CONDUCT.md)을 검토하세요
### 포크하고 클론하기
1. GitHub에서 저장소를 포크하세요
2. 포크한 저장소를 클론하세요:
```bash
git clone https://github.com/YOUR_USERNAME/twenty.git
cd twenty
```
3. upstream 원격을 추가하세요:
```bash
git remote add upstream https://github.com/twentyhq/twenty.git
```
### 브랜치 만들기
```bash
git checkout -b feature/your-feature-name
```
알기 쉬운 브랜치 이름을 사용하세요:
* `feature/add-export-button`
* `fix/login-redirect-issue`
* `docs/update-api-guide`
### 변경 사항 적용하기
1. 깔끔하고 문서화가 잘 된 코드를 작성하세요
2. 기존 코드 스타일을 따르세요
3. 새 기능에 대한 테스트를 추가하세요
4. 필요하다면 문서를 업데이트하세요
### PR 제출하기
1. 브랜치를 푸시하세요:
```bash
git push origin feature/your-feature-name
```
2. GitHub에서 PR을 열어 주세요
3. PR 템플릿을 작성하세요
4. 관련 이슈를 연결하세요
### PR 체크리스트
* [ ] 코드가 프로젝트 스타일 가이드라인을 따릅니다
* [ ] 로컬에서 테스트가 통과합니다
* [ ] 문서가 업데이트되었습니다
* [ ] PR 설명이 변경 사항을 설명합니다

View File

@@ -0,0 +1,329 @@
---
title: 모범 사례
icon: star
---
이 문서는 프론트엔드 작업 시 따라야 할 모범 사례를 설명합니다.
## 상태 관리
React와 Jotai는 코드베이스에서 상태 관리를 처리합니다.
### Jotai 아톰으로 상태 저장하기
상태를 저장하는 데 필요한 만큼의 atom을 만드는 것이 좋은 습관입니다.
<Warning>
프롭스 드릴링에 너무 많은 노력을 기울이기보다는 추가 아톰을 사용하는 것이 좋습니다.
</Warning>
```tsx
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
export const myAtomState = createAtomState<string>({
key: 'myAtomState',
defaultValue: 'default value',
});
export const MyComponent = () => {
const [myAtom, setMyAtom] = useAtomState(myAtomState);
return (
<div>
<input
value={myAtom}
onChange={(e) => setMyAtom(e.target.value)}
/>
</div>
);
}
```
### 상태 저장에 `useRef`를 사용하지 마십시오
상태 저장에 `useRef`를 사용하지 않도록 주의하십시오.
상태를 저장하려면 `useState`를 사용하거나 `useAtomState`와 함께 Jotai 아톰을 사용해야 합니다.
일부 리렌더링을 방지하기 위해 `useRef`가 필요하다고 느낄 경우 [리렌더링 관리 방법](#managing-re-renders)을 참조하십시오.
## 리렌더링 관리
리액트에서 리렌더링은 관리하기 어려울 수 있습니다.
불필요한 리렌더링을 피하기 위해 따라야 할 몇 가지 규칙이 있습니다.
리렌더링의 원인을 이해하면 항상 리렌더링을 피할 수 있다는 점을 명심하십시오.
### 루트 레벨에서 작업하기
새 기능에서 리렌더링을 피하는 것은 이제 루트 수준에서 문제를 제거함으로써 쉬워졌습니다.
`PageChangeEffect` 사이드카 컴포넌트에는 페이지 변경 시 실행할 모든 로직을 담고 있는 하나의 `useEffect`만 있습니다.
그렇게 하면 리렌더링을 트리거할 수 있는 곳이 오직 한 곳뿐이라는 것을 알 수 있습니다.
### 코드베이스에 `useEffect`를 추가하기 전에 항상 신중히 고려하세요.
리렌더링은 종종 불필요한 `useEffect` 때문에 발생합니다.
`useEffect`가 정말 필요한지, 아니면 로직을 이벤트 핸들러 함수로 옮길 수 있는지 고민해 보아야 합니다.
일반적으로 `handleClick` 또는 `handleChange` 함수로 로직을 옮기는 것은 어렵지 않다는 것을 알게 될 것입니다.
Apollo와 같은 라이브러리에서 `onCompleted`, `onError` 등을 찾을 수 있습니다.
### 형제 컴포넌트를 사용해 `useEffect` 또는 데이터 페칭 로직 추출하기
루트 컴포넌트에 `useEffect`를 추가해야 한다고 느껴진다면, 사이드카 컴포넌트로 추출하는 것을 고려해야 합니다.
Apollo 훅을 사용하여 데이터 페칭 로직에 동일한 규칙을 적용할 수 있습니다.
```tsx
// ❌ Bad, will cause re-renders even if data is not changing,
// because useEffect needs to be re-evaluated
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
useEffect(() => {
if(someDependency !== data) {
setData(someDependency);
}
}, [someDependency]);
return <div>{data}</div>;
};
export const App = () => (
<PageComponent />
);
```
```tsx
// ✅ Good, will not cause re-renders if data is not changing,
// because useEffect is re-evaluated in another sibling component
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
return <div>{data}</div>;
};
export const PageData = () => {
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
useEffect(() => {
if(someDependency !== data) {
setData(someDependency);
}
}, [someDependency]);
return <></>;
};
export const App = () => (
<>
<PageData />
<PageComponent />
</>
);
```
### 아톰 가족 상태 및 선택자 사용하기
아톰 가족 상태와 선택자는 리렌더링을 피하기 위한 훌륭한 방법입니다.
항목 목록을 저장해야 할 때 유용합니다.
### `React.memo(MyComponent)`를 사용하지 마십시오
`React.memo()`를 사용하지 마십시오. 이것은 리렌더링의 원인을 해결하지 않으며, 대신 리렌더 체인을 끊어 의도치 않은 동작을 유발하고 코드 리팩토링을 매우 어렵게 만듭니다.
### `useCallback` 또는 `useMemo` 사용 제한
이들은 종종 필요하지 않으며, 성능 향상을 위해 코드 읽기와 유지보수를 어렵게 만듭니다.
## Console.logs
`console.log` 문은 개발 중에 변수 값과 코드 흐름에 대한 실시간 정보를 제공합니다. 그러나 생산 코드에 남아 있으면 여러 문제를 일으킬 수 있습니다.
1. **성능**: 과도한 로깅은 특히 클라이언트 측 애플리케이션의 경우 런타임 성능에 영향을 미칠 수 있습니다.
2. **보안**: 민감한 데이터 로깅은 브라우저 콘솔을 검사하는 누구나 중요한 정보를 노출할 수 있습니다.
3. **깔끔함**: 콘솔을 로그로 가득 채우면 개발자나 도구가 필요한 중요한 경고나 오류를 가릴 수 있습니다.
4. **전문성**: 사용자가 콘솔을 확인하고 수많은 로그를 보면 코드의 품질과 정교함을 의심할 수 있습니다.
생산 환경에 코드를 올리기 전에 모든 `console.logs`를 제거해야 합니다.
## 이름 지정
### 변수 이름 지정
변수 이름은 변수의 목적이나 기능을 정확히 설명해야 합니다.
#### 일반적인 이름의 문제
프로그래밍에서 일반적인 이름은 특수성이 부족하여 모호성을 유발하고 코드 가독성을 감소시킵니다. 이름이 변수나 함수의 목적을 전달하지 않으면 개발자가 코드의 의도를 깊이 조사해야 합니다. 이로 인해 디버깅 시간이 증가하고 오류에 더 많이 노출되며 유지보수와 협업에 어려움이 생길 수 있습니다. 반면에 설명적인 이름을 사용하면 코드가 자가 설명적이 되고 탐색하기 쉬워져 코드 품질과 개발자 생산성을 향상시킵니다.
```tsx
// ❌ Bad, uses a generic name that doesn't communicate its
// purpose or content clearly
const [value, setValue] = useState('');
```
```tsx
// ✅ Good, uses a descriptive name
const [email, setEmail] = useState('');
```
#### 변수 이름에서 피해야 할 단어들
* 더미
### 이벤트 핸들러
이벤트 핸들러 이름은 `handle`로 시작해야 하고, `on`은 컴포넌트 속성의 이벤트 이름 지정에 사용됩니다.
```tsx
// ❌ Bad
const onEmailChange = (val: string) => {
// ...
};
```
```tsx
// ✅ Good
const handleEmailChange = (val: string) => {
// ...
};
```
## 선택적 Props
선택적 프로퍼티에 대한 기본값 전달을 피하십시오.
**예시**
다음과 같은 `EmailField` 컴포넌트를 보십시오:
```tsx
type EmailFieldProps = {
value: string;
disabled?: boolean;
};
const EmailField = ({ value, disabled = false }: EmailFieldProps) => (
<TextInput value={value} disabled={disabled} fullWidth />
);
```
**사용법**
```tsx
// ❌ Bad, passing in the same value as the default value adds no value
const Form = () => <EmailField value="username@email.com" disabled={false} />;
```
```tsx
// ✅ Good, assumes the default value
const Form = () => <EmailField value="username@email.com" />;
```
## 프로퍼티로써 컴포넌트
가능한 한, 인스턴스화되지 않은 컴포넌트를 prop으로 전달하여 자식이 필요한 prop을 스스로 결정할 수 있도록 하세요.
가장 일반적인 예는 아이콘 컴포넌트입니다.
```tsx
const SomeParentComponent = () => <MyComponent Icon={MyIcon} />;
// In MyComponent
const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => {
const theme = useTheme();
return (
<div>
<MyIcon size={theme.icon.size.md}>
</div>
)
};
```
React가 컴포넌트를 컴포넌트로 인식하려면, PascalCase를 사용하여 나중에 `<MyIcon>`으로 인스턴스화해야 합니다.
## Prop 드릴링: 최소화하기
React에서 prop 드릴링은 상태 변수 및 그 설정자를 중간 컴포넌트가 사용하지 않더라도 여러 컴포넌트 계층에 걸쳐 전달하는 관행을 의미합니다. 가끔 필요할 수 있지만, 과도한 prop 드릴링은 다음과 같은 문제를 초래할 수 있습니다:
1. **읽기 어려움**: prop이 어디서 시작되었고 어디에서 사용되는지를 추적하는 것이 깊게 중첩된 컴포넌트 구조에서는 복잡해질 수 있습니다.
2. **유지보수의 어려움**: 한 컴포넌트의 prop 구조의 변화는, 해당 prop을 직접 사용하지 않더라도 여러 컴포넌트에서의 조정이 필요할 수 있습니다.
3. **컴포넌트 재사용성 감소**: 많은 prop을 전달하기 위한 컴포넌트는 그 용도가 덜 일반적이게 되며, 다양한 컨텍스트에서 재사용하기 어려워집니다.
과도한 prop 드릴링을 사용하고 있다고 느껴진다면, [상태 관리 최적 관행](#state-management)을 참조하세요.
## 가져오기
가져올 때는, 지정된 별칭을 사용하고 전체나 상대 경로를 지정하지 않도록 하세요.
**별칭 처리**
```js
{
alias: {
"~": path.resolve(__dirname, "src"),
"@": path.resolve(__dirname, "src/modules"),
"@testing": path.resolve(__dirname, "src/testing"),
},
}
```
**사용법**
```tsx
// ❌ Bad, specifies the entire relative path
import {
CatalogDecorator
} from '../../../../../testing/decorators/CatalogDecorator';
import {
ComponentDecorator
} from '../../../../../testing/decorators/ComponentDecorator';
```
```tsx
// ✅ Good, utilises the designated aliases
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from 'twenty-ui/testing';
```
## 스키마 유효성 검사
[Zod](https://github.com/colinhacks/zod)는 타이핑되지 않은 객체를 위한 스키마 유효성 검사기입니다:
```js
const validationSchema = z
.object({
exist: z.boolean(),
email: z
.string()
.email('Email must be a valid email'),
password: z
.string()
.regex(PASSWORD_REGEX, 'Password must contain at least 8 characters'),
})
.required();
type Form = z.infer<typeof validationSchema>;
```
## 파괴적 변경
테스트가 아직 광범위하게 통합되지 않았으므로, 변경 사항이 다른 부분에 문제를 일으키지 않았는지 철저한 수동 테스트를 통해 보장하세요.

View File

@@ -0,0 +1,110 @@
---
title: 폴더 아키텍처
icon: folder-tree
info: 우리의 폴더 아키텍처를 자세히 살펴보기
---
이 가이드에서는 프로젝트 디렉토리 구조의 세부 사항과 그것이 Twenty의 구조화와 유지관리성에 어떻게 기여하는지 살펴봅니다.
이 폴더 아키텍처 관례를 따르면 특정 기능과 관련된 파일을 더 쉽게 찾을 수 있고, 애플리케이션의 확장성과 유지관리성을 보장하기가 쉬워집니다.
```
front
└───modules
│ └───module1
│ │ └───submodule1
│ └───module2
│ └───ui
│ │ └───display
│ │ └───inputs
│ │ │ └───buttons
│ │ └───...
└───pages
└───...
```
## 페이지
애플리케이션 라우트로 정의된 최상위 컴포넌트를 포함합니다. 모듈 폴더에서 더 하위 수준의 컴포넌트를 가져옵니다(자세한 내용은 아래를 참조하세요).
## 모듈
각 모듈은 특정 컴포넌트, 상태 및 운영 로직을 포함하는 기능 또는 기능 그룹을 나타냅니다.
모두 아래 구조를 따라야 합니다. 모듈 안에 모듈을 중첩할 수 있으며(이를 서브모듈이라고 하며), 동일한 규칙이 적용됩니다.
```
module1
└───components
│ └───component1
│ └───component2
└───constants
└───contexts
└───graphql
│ └───fragments
│ └───queries
│ └───mutations
└───hooks
│ └───internal
└───states
│ └───selectors
└───types
└───utils
```
### 컨텍스트
컨텍스트는 각 레벨에서 수동으로 props를 전달하지 않고, 컴포넌트 트리를 통해 데이터를 전달할 수 있는 방법입니다.
자세한 내용은 [React Context](https://react.dev/reference/react#context-hooks)를 참조하세요.
### GraphQL
프래그먼트, 쿼리 및 뮤테이션이 포함됩니다.
자세한 내용은 [GraphQL](https://graphql.org/learn/)을 참조하세요.
* 프래그먼트
프래그먼트는 쿼리의 재사용 가능한 조각으로, 여러 장소에서 사용할 수 있습니다. 프래그먼트를 사용하면 코드 중복을 피하기가 더 쉬워집니다.
자세한 내용은 [GraphQL 프래그먼트](https://graphql.org/learn/queries/#fragments)를 참조하세요.
* 쿼리
자세한 내용은 [GraphQL 쿼리](https://graphql.org/learn/queries/)를 참조하세요.
* 뮤테이션
자세한 내용은 [GraphQL 뮤테이션](https://graphql.org/learn/queries/#mutations)을 참조하세요.
### 훅
자세한 내용은 [훅](https://react.dev/learn/reusing-logic-with-custom-hooks)을 참조하세요.
### 상태
상태 관리 로직이 포함되어 있습니다. [Jotai](https://jotai.org)가 이것을 처리합니다.
* 셀렉터: 파생 atom(`createAtomSelector` 사용)은 다른 atom으로부터 값을 계산하며 자동으로 메모이제이션됩니다.
React의 내장 상태 관리는 구성 요소 내에서의 상태를 여전히 처리합니다.
### 유틸
재사용 가능한 순수 함수를 포함해야 합니다. 그렇지 않으면 `hooks` 폴더에 커스텀 훅을 만들어야 합니다.
## UI
애플리케이션에서 사용되는 모든 재사용 가능한 UI 컴포넌트를 포함합니다.
이 폴더는 `data`, `display`, `feedback`, `input`과 같은 특정 유형의 컴포넌트에 대한 하위 폴더를 포함할 수 있습니다. 각 컴포넌트는 자기 완결적이고 재사용 가능해야 하며, 애플리케이션의 다른 부분에서 사용할 수 있어야 합니다.
`modules` 폴더의 다른 컴포넌트에서 UI 컴포넌트를 분리함으로써 일관된 디자인을 유지하고, 코드베이스의 다른 부분(비즈니스 로직)에 영향을 주지 않고 UI를 변경하기가 더 쉬워집니다.
## 인터페이스 및 종속성
`ui` 폴더를 제외한 모든 모듈에서 다른 모듈 코드를 가져올 수 있습니다. 이렇게 하면 코드를 쉽게 테스트할 수 있습니다.
### 내부
각 부분(훅, 상태, ...) 모듈의 각 부분은 모듈 내에서만 사용하는 `internal` 폴더를 가질 수 있습니다.

View File

@@ -0,0 +1,91 @@
---
title: 프론트엔드 명령어
icon: terminal
---
## 유용한 명령어
### 앱 시작하기
```bash
npx nx start twenty-front
```
### API GraphQL 스키마를 기반으로 GraphQL 스키마 재생성
```bash
npx nx run twenty-front:graphql:generate --configuration=metadata
```
또는
```bash
npx nx run twenty-front:graphql:generate
```
### 린트
```bash
npx nx run twenty-front:lint # pass --fix to fix lint errors
```
## 번역
```bash
npx nx run twenty-front:lingui:extract
npx nx run twenty-front:lingui:compile
```
### 테스트
```bash
npx nx run twenty-front:test # run jest tests
npx nx run twenty-front:storybook:serve:dev # run storybook
npx nx run twenty-front:storybook:test # run tests # (needs yarn storybook:serve:dev to be running)
npx nx run twenty-front:storybook:coverage # (needs yarn storybook:serve:dev to be running)
```
## 기술 스택
프로젝트는 최소한의 보일러플레이트 코드로 깔끔하고 단순한 스택을 가지고 있습니다.
**앱**
* [React](https://react.dev/)
* [Apollo](https://www.apollographql.com/docs/)
* [GraphQL Codegen](https://the-guild.dev/graphql/codegen)
* [Jotai](https://jotai.org/)
* [TypeScript](https://www.typescriptlang.org/)
**테스트**
* [Jest](https://jestjs.io/)
* [Storybook](https://storybook.js.org/)
**도구**
* [Yarn](https://yarnpkg.com/)
* [Craco](https://craco.js.org/docs/)
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
## 아키텍처
### 라우팅
[React Router](https://reactrouter.com/)가 라우팅을 처리합니다.
불필요한 [재렌더링](/l/ko/developers/contribute/capabilities/frontend-development/best-practices-front#managing-re-renders)을 피하기 위해 모든 라우팅 로직은 `PageChangeEffect`의 `useEffect`에 있습니다.
### 상태 관리
[Jotai](https://jotai.org/)가 상태 관리를 처리합니다.
상태 관리에 대한 자세한 정보는 [최고의 관례](/l/ko/developers/contribute/capabilities/frontend-development/best-practices-front#state-management)를 참조하십시오.
## 테스트
[Jest](https://jestjs.io/)는 유닛 테스트 도구로 사용되고 [Storybook](https://storybook.js.org/)은 컴포넌트 테스트에 사용됩니다.
Jest는 주로 유틸리티 함수 테스트에 사용되며, 직접 컴포넌트를 테스트하지는 않습니다.
Storybook은 개별 컴포넌트의 동작을 테스트하고 디자인 시스템을 표시하는 데 사용됩니다.

View File

@@ -0,0 +1,178 @@
---
title: 단축키
---
## 소개
단축키를 들으려면 일반적으로 `onKeyDown` 이벤트 리스너를 사용합니다.
그러나 `twenty-front`에서는 동일한 시점에 장착된 다른 구성 요소에서 사용되는 같은 단축키 간에 충돌이 발생할 수 있습니다.
예를 들어, Enter 키를 듣는 페이지가 있고, Enter 키를 듣는 모달이 있으며, 그 모달 내에서 Enter 키를 듣는 Select 구성요소가 있다면, 모두 동시에 마운트될 때 충돌이 발생할 수 있습니다.
## `useScopedHotkeys` 훅
이 문제를 해결하기 위해, 우리는 충돌 없이 단축키를 들을 수 있게 하는 커스텀 훅을 만들었습니다.
이를 구성 요소에 배치하면 구성 요소가 마운트될 때 및 지정한 **단축키 스코프**가 활성화될 때만 단축키를 듣습니다.
## 실제로 단축키를 감지하려면 어떻게 하나요?
단축키 청취를 설정하는 데 두 가지 단계가 있습니다:
1. 단축키를 들을 [단축키 스코프](#what-is-a-hotkey-scope-)를 설정합니다.
2. 단축키를 듣기 위해 `useScopedHotkeys` 훅을 사용합니다.
기본 페이지에서도 단축키 스코프를 설정해야 합니다. 왼쪽 메뉴나 명령 메뉴와 같은 다른 UI 요소도 단축키를 들을 수 있기 때문입니다.
## 단축키 사용 사례
일반적으로, 두 가지 단축키를 필요로 하는 사용 사례가 있습니다:
1. 페이지 또는 페이지에 마운트된 구성 요소
2. 사용자 작업으로 인해 포커스를 차지하는 모달 형 구성 요소
두 번째 사용 사례는 재귀적으로 발생할 수 있습니다: 예를 들어, 모달의 드롭다운.
### 페이지에서 단축키 듣기
예시:
```tsx
const PageListeningEnter = () => {
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
// 1. Set the hotkey scope in a useEffect
useEffect(() => {
setHotkeyScopeAndMemorizePreviousScope(
ExampleHotkeyScopes.ExampleEnterPage,
);
// Revert to the previous hotkey scope when the component is unmounted
return () => {
goBackToPreviousHotkeyScope();
};
}, [goBackToPreviousHotkeyScope, setHotkeyScopeAndMemorizePreviousScope]);
// 2. Use the useScopedHotkeys hook
useScopedHotkeys(
Key.Enter,
() => {
// Some logic executed on this page when the user presses Enter
// ...
},
ExampleHotkeyScopes.ExampleEnterPage,
);
return <div>My page that listens for Enter</div>;
};
```
### 모달 형 구성 요소에서 단축키 듣기
이 예시에서는 부모에게 모달을 닫으라고 알리기 위해 Escape 키를 듣는 모달 구성 요소를 사용합니다.
여기서 사용자 상호작용은 범위를 변경합니다.
```tsx
const ExamplePageWithModal = () => {
const [showModal, setShowModal] = useState(false);
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
const handleOpenModalClick = () => {
// 1. Set the hotkey scope when user opens the modal
setShowModal(true);
setHotkeyScopeAndMemorizePreviousScope(
ExampleHotkeyScopes.ExampleModal,
);
};
const handleModalClose = () => {
// 1. Revert to the previous hotkey scope when the modal is closed
setShowModal(false);
goBackToPreviousHotkeyScope();
};
return <div>
<h1>My page with a modal</h1>
<button onClick={handleOpenModalClick}>Open modal</button>
{showModal && <MyModalComponent onClose={handleModalClose} />}
</div>;
};
```
그러면 모달 구성 요소에서:
```tsx
const MyDropdownComponent = ({ onClose }: { onClose: () => void }) => {
// 2. Use the useScopedHotkeys hook to listen for Escape.
// Note that escape is a common hotkey that could be used by many other components
// So it's important to use a hotkey scope to avoid conflicts
useScopedHotkeys(
Key.Escape,
() => {
onClose()
},
ExampleHotkeyScopes.ExampleModal,
);
return <div>My modal component</div>;
};
```
단순히 마운트/언마운트 시에 `useEffect`를 사용하는 것만으로는 충돌을 피하기에 충분한지 확신이 서지 않을 때는 이 패턴을 사용하는 것이 중요합니다.
그러한 충돌은 디버깅하기 어려우며, `useEffect`를 사용할 때 더 자주 발생할 수 있습니다.
## 단축키 스코프란 무엇인가요?
단축키 스코프는 단축키가 활성 상태인 컨텍스트를 나타내는 문자열입니다. 일반적으로 열거형으로 인코딩됩니다.
단축키 스코프를 변경하면 해당 스코프를 듣고 있는 단축키는 활성화되고 다른 스코프를 듣고 있는 단축키는 비활성화됩니다.
한 번에 하나의 스코프만 설정할 수 있습니다.
예를 들어 각 페이지의 단축키 스코프는 `PageHotkeyScope` 열거형에 정의되어 있습니다:
```tsx
export enum PageHotkeyScope {
Settings = 'settings',
CreateWorkspace = 'create-workspace',
SignInUp = 'sign-in-up',
CreateProfile = 'create-profile',
PlanRequired = 'plan-required',
ShowPage = 'show-page',
PersonShowPage = 'person-show-page',
CompanyShowPage = 'company-show-page',
CompaniesPage = 'companies-page',
PeoplePage = 'people-page',
OpportunitiesPage = 'opportunities-page',
ProfilePage = 'profile-page',
WorkspaceMemberPage = 'workspace-member-page',
TaskPage = 'task-page',
}
```
내부적으로, 현재 선택한 스코프는 애플리케이션 전반에 걸쳐 공유되는 Jotai atom에 저장됩니다:
```tsx
export const currentHotkeyScopeState = createState<HotkeyScope>({
key: 'currentHotkeyScopeState',
defaultValue: INITIAL_HOTKEYS_SCOPE,
});
```
하지만 이 atom은 수동으로 처리해서는 안 됩니다! 다음 섹션에서 사용하는 방법을 배웁니다.
## 내부적으로 어떻게 작동합니까?
[react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) 위에 얇은 래퍼를 만들어 성능을 높이고 불필요한 재랜더링을 방지합니다.
또한 핫키 스코프 상태를 처리하고 애플리케이션 전반에서 사용할 수 있도록 Jotai atom을 만듭니다.

View File

@@ -0,0 +1,8 @@
---
title: Storybook
description: Twenty의 UI 컴포넌트 라이브러리 둘러보기
---
스토리북에서 완전한 컴포넌트 라이브러리와 문서를 확인하세요.
[Storybook 열기 →](https://storybook.twenty.com)

View File

@@ -0,0 +1,291 @@
---
title: 스타일 가이드
icon: paintbrush
---
이 문서에는 코드 작성 시 따라야 할 규칙이 포함되어 있습니다.
여기서 목표는 읽기 쉽고 유지 관리가 쉬운 일관된 코드베이스를 갖는 것입니다.
이를 위해서는 너무 간결하기보다는 약간 더 장황한 것이 낫습니다.
항상 사람들이 코드를 작성하는 것보다 더 자주 읽는다는 점을 염두에 두십시오. 특히 누구나 기여할 수 있는 오픈 소스 프로젝트에서는 더욱 그렇습니다.
여기에 정의되지 않은 많은 규칙이 있지만 린터에 의해 자동으로 확인됩니다.
## 리액트
### 함수형 컴포넌트 사용
항상 TSX 함수형 컴포넌트를 사용하십시오.
기본 `import`를 `const`와 함께 사용하지 마십시오. 읽기 어렵고 코드 자동 완성으로 import하기 어렵습니다.
```tsx
// ❌ Bad, harder to read, harder to import with code completion
const MyComponent = () => {
return <div>Hello World</div>;
};
export default MyComponent;
// ✅ Good, easy to read, easy to import with code completion
export function MyComponent() {
return <div>Hello World</div>;
};
```
### 프로퍼티
props의 유형을 만들고 내보낼 필요가 없으면 `(ComponentName)Props`라고 명명하십시오.
props 구조 분해를 사용하십시오.
```tsx
// ❌ Bad, no type
export const MyComponent = (props) => <div>Hello {props.name}</div>;
// ✅ Good, type
type MyComponentProps = {
name: string;
};
export const MyComponent = ({ name }: MyComponentProps) => <div>Hello {name}</div>;
```
#### prop 유형을 정의하기 위해 `React.FC` 또는 `React.FunctionComponent`를 사용하지 마십시오.
```tsx
/* ❌ - Bad, defines the component type annotations with `FC`
* - With `React.FC`, the component implicitly accepts a `children` prop
* even if it's not defined in the prop type. This might not always be
* desirable, especially if the component doesn't intend to render
* children.
*/
const EmailField: React.FC<{
value: string;
}> = ({ value }) => <TextInput value={value} disabled fullWidth />;
```
```tsx
/* ✅ - Good, a separate type (OwnProps) is explicitly defined for the
* component's props
* - This method doesn't automatically include the children prop. If
* you want to include it, you have to specify it in OwnProps.
*/
type EmailFieldProps = {
value: string;
};
const EmailField = ({ value }: EmailFieldProps) => (
<TextInput value={value} disabled fullWidth />
);
```
#### JSX 요소에서 단일 변수 prop 확산 방지
JSX 요소에서 `{...props}`와 같은 단일 변수 prop 확산을 피하십시오. 이 관행은 구성 요소가 수신되는 props를 명확하게 하지 않아 가독성과 유지 관리가 어려운 코드를 생성하는 경우가 많습니다.
```tsx
/* ❌ - Bad, spreads a single variable prop into the underlying component
*/
const MyComponent = (props: OwnProps) => {
return <OtherComponent {...props} />;
}
```
```tsx
/* ✅ - Good, Explicitly lists all props
* - Enhances readability and maintainability
*/
const MyComponent = ({ prop1, prop2, prop3 }: MyComponentProps) => {
return <OtherComponent {...{ prop1, prop2, prop3 }} />;
};
```
이유:
* 한눈에 코드가 전달하는 props를 명확하게 하여 이해하고 유지하기 쉽습니다.
* 이것은 구성 요소가 각자의 props로 긴밀하게 결합되는 것을 방지하는 데 도움이 됩니다.
* props를 명시적으로 나열하면 Linting 도구에서 잘못된 철자나 사용되지 않는 props를 쉽게 식별할 수 있습니다.
## 자바스크립트
### nullish 병합 연산자 `??` 사용
```tsx
// ❌ Bad, can return 'default' even if value is 0 or ''
const value = process.env.MY_VALUE || 'default';
// ✅ Good, will return 'default' only if value is null or undefined
const value = process.env.MY_VALUE ?? 'default';
```
### 옵셔널 체이닝 `?.` 사용
```tsx
// ❌ Bad
onClick && onClick();
// ✅ Good
onClick?.();
```
## 타입스크립트
### `interface` 대신 `type` 사용
항상 `interface` 대신 `type`을 사용하십시오. 두 개의 기능은 거의 항상 중첩되며, `type`이 더 유연합니다.
```tsx
// ❌ Bad
interface MyInterface {
name: string;
}
// ✅ Good
type MyType = {
name: string;
};
```
### 열거형 대신 문자열 리터럴 사용
[String literals](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types)는 TypeScript에서 열거형과 같은 값을 처리하는 주요 방법입니다. 이들은 Pick과 Omit으로 확장하기 더 쉽고, 특히 코드 자동 완성 기능을 사용할 때 더 나은 개발자 경험을 제공합니다.
타입스크립트가 열거형 사용을 피하도록 권장하는 이유는 [여기](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#enums)에서 확인할 수 있습니다.
```tsx
// ❌ Bad, utilizes an enum
enum Color {
Red = "red",
Green = "green",
Blue = "blue",
}
let color = Color.Red;
```
```tsx
// ✅ Good, utilizes a string literal
let color: "red" | "green" | "blue" = "red";
```
#### GraphQL 및 내부 라이브러리
GraphQL 코드 생성기가 생성한 열거형을 사용해야 합니다.
내부 라이브러리를 사용할 때도 열거형을 사용하는 것이 좋으며, 내부 API와 관련이 없는 문자열 리터럴 유형을 노출할 필요가 없습니다.
예시:
```TSX
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
setHotkeyScopeAndMemorizePreviousScope(
RelationPickerHotkeyScope.RelationPicker,
);
```
## 스타일링
### StyledComponents 사용
[Linaria styled](https://github.com/callstack/linaria)로 구성 요소를 스타일링하십시오.
```tsx
// ❌ Bad
<div className="my-class">Hello World</div>
```
```tsx
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
```
스타일드 컴포넌트 앞에 "Styled"를 붙여 실제 컴포넌트와 구분하십시오.
```tsx
// ❌ Bad
const Title = styled.div`
color: red;
`;
```
```tsx
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
```
### 테마
대부분의 구성 요소 스타일링에는 테마를 활용하는 것이 선호되는 접근 방식입니다.
#### 측정 단위
스타일드 컴포넌트 내에서 `px` 또는 `rem` 값을 직접 사용하는 것을 피하십시오. 필요한 값은 일반적으로 테마에 이미 정의되어 있으므로 이러한 목적으로 테마를 활용하는 것이 좋습니다.
#### 색상
새로운 색상을 도입하지 말고 테마에서 기존 팔레트를 사용하십시오. 팔레트가 맞지 않는 경우가 있다면 팀이 수정할 수 있도록 댓글을 남겨야 합니다.
```tsx
// ❌ Bad, directly specifies style values without utilizing the theme
const StyledButton = styled.button`
color: #333333;
font-size: 1rem;
font-weight: 400;
margin-left: 4px;
border-radius: 50px;
`;
```
```tsx
// ✅ Good, utilizes the theme
const StyledButton = styled.button`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.regular};
margin-left: ${({ theme }) => theme.spacing(1)};
border-radius: ${({ theme }) => theme.border.rounded};
`;
```
## No-Type Import 강제 실행
타입 import를 피하십시오. 이 표준을 강제하기 위해 Oxlint 규칙이 모든 타입 import를 검사하고 보고합니다. 이는 TypeScript 코드의 일관성과 가독성을 유지하는 데 도움이 됩니다.
```tsx
// ❌ Bad
import { type Meta, type StoryObj } from '@storybook/react';
// ❌ Bad
import type { Meta, StoryObj } from '@storybook/react';
// ✅ Good
import { Meta, StoryObj } from '@storybook/react';
```
### No-Type Import의 이유
* **일관성**: 타입 import를 피하고, 타입과 값 import 모두의 단일 접근 방식을 사용하여 모듈 import 스타일의 일관성을 유지합니다.
* **가독성**: No-Type Import는 값을 가져오거나 타입을 가져오는 것을 명확히 하여 코드 가독성을 향상시킵니다. 이는 모호성을 줄이고 가져온 기호의 목적을 이해하기 쉽게 만듭니다.
* **유지관리성**: 이는 코드베이스 유지관리를 향상시킵니다. 개발자가 코드를 검토하거나 수정할 때 타입 전용 import를 식별하고 찾을 수 있습니다.
### Oxlint 규칙
Oxlint 규칙인 `typescript/consistent-type-imports`는 타입 import 금지 표준을 강제합니다. 이 규칙은 모든 타입 import 위반에 대해 오류나 경고를 생성합니다.
이 규칙은 의도치 않은 타입 import가 발생하는 드문 경계 사례를 구체적으로 다룹니다. 타입스크립트 자체는 [TypeScript 3.8 릴리스 노트](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html)에서 이 관행을 권장하지 않습니다. 대부분의 상황에서 타입 전용 import를 사용할 필요가 없습니다.
코드가 이 규칙을 준수하도록 개발 워크플로의 일부로 Oxlint를 실행하십시오.

View File

@@ -0,0 +1,60 @@
---
title: Figma와 함께 작업하기
info: Twenty의 Figma와 협업하는 방법을 배우기
---
Figma는 디자이너와 개발자 간의 소통 장벽을 허무는 협업 인터페이스 디자인 도구입니다.
이 가이드는 Figma와 협업하는 방법을 설명합니다.
## 접근하기
1. **공유 링크에 접근:** 프로젝트의 Figma 파일에 [여기서](https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty) 접근할 수 있습니다.
2. **로그인:** 이미 로그인하지 않은 경우, Figma에서 로그인 하라는 요청을 받을 것입니다.
전문 모드 및 전용 프레임 선택 기능과 같이 로그인한 사용자에게만 제공되는 주요 기능이 있습니다.
<Warning>
계정 없이는 효과적으로 협업할 수 없습니다.
</Warning>
## Figma 구조
왼쪽 사이드바에서 Twenty의 Figma의 다양한 페이지에 접근할 수 있습니다. 이들은 이렇게 구성되어 있습니다:
* **구성 요소 페이지:** 첫 번째 페이지입니다. 디자이너는 디자인 파일 전반에 걸쳐 재사용 가능한 디자인 요소를 생성하고 조직하는 데 사용합니다. 예를 들어, 버튼, 아이콘, 심벌 또는 기타 재사용 가능한 구성 요소가 있습니다. 디자인 전체에서 일관성을 유지하는 역할을 합니다.
* **메인 페이지:** 두 번째 페이지는 프로젝트의 완전한 사용자 인터페이스를 보여주는 메인 페이지입니다. 전체 앱 프로토타입을 사용하려면 ***재생*** 버튼을 누르세요.
* **기능 페이지:** 다른 페이지는 일반적으로 진행중인 기능에 전용되어 있습니다. 이 페이지들은 애플리케이션이나 웹사이트의 특정 기능 또는 모듈의 디자인을 포함하고 있습니다. 일반적으로 아직 진행 중인 상태입니다.
## 유용한 팁
읽기 전용 접근 권한으로는 디자인을 편집할 수 없지만, 디자인을 코드로 변환하는 데 유용한 모든 기능에 접근할 수 있습니다.
### 개발 모드 사용
Figma의 Dev Mode는 쉬운 디자인 탐색, 효과적인 자산 관리, 효율적인 통신 도구, 도구 상자 통합, 빠른 코드 스니펫, 주요 레이어 정보를 제공하여 디자이너와 개발자 간의 격차를 줄입니다. Dev Mode에 대해 더 알고 싶으시면 [여기](https://www.figma.com/dev-mode/)를 방문하세요.
툴바의 오른쪽에서 "개발자" 모드로 전환하여 디자인 사양을 보고 CSS를 복사하고 자산에 접근하세요.
### 프로토타입 사용
캔버스의 아무 요소나 클릭하고 인터페이스의 오른쪽 상단 모서리에 있는 "재생" 버튼을 눌러 프로토타입 보기에 접근하세요. 프로토타입 모드는 디자인을 최종 제품처럼 상호작용할 수 있게 합니다. 이를 통해 화면 간의 흐름과 버튼, 링크, 메뉴 등과 같은 인터페이스 요소가 상호작용할 때 어떻게 작동하는지 보여줍니다.
1. **전환 및 애니메이션 이해하기:** 프로토타입 모드에서는 디자이너가 화면 또는 UI 요소 간에 추가한 전환이나 애니메이션을 볼 수 있어 개발자에게 의도된 동작 및 스타일에 대한 명확한 시각적 지침을 제공합니다.
2. **실행 명확화:** 프로토타입은 모호성을 줄이는 데 도움을 줄 수 있습니다. 개발자는 특정 요소의 기능이나 모양을 보다 잘 이해하기 위해 이를 상호작용할 수 있습니다.
Figma 플랫폼 학습에 대한 포괄적인 세부 정보와 지침을 얻으시려면 [공식 Figma 문서](https://help.figma.com/hc/en-us)를 방문하세요.
### 거리 측정
요소를 선택하고 `Option` 키(Mac) 또는 `Alt` 키(Windows)를 누른 상태에서 다른 요소 위에 마우스를 올리면 요소 간 거리를 확인할 수 있습니다.
### VSCode용 Figma 확장 프로그램 (추천)
[VS Code용 Figma](https://marketplace.visualstudio.com/items?itemName=figma.figma-vscode-extension) 확장 프로그램은 텍스트 편집기에서 벗어나지 않고 디자인 파일 탐색 및 점검, 디자이너와의 협업, 변경 사항 추적, 구현 속도 향상을 가능하게 합니다.
추천하는 확장 프로그램의 일부입니다.
## 협업
1. **댓글 사용하기:** 툴바 왼쪽의 말풍선 아이콘을 클릭하여 댓글 기능을 사용할 수 있습니다.
2. **커서 채팅:** Figma의 멋진 기능 중 하나는 커서 채팅입니다. Figma를 다른 누군가와 동시에 사용하는 것을 보면 `;` (Mac)이나 `/` (Windows) 키를 눌러 메시지를 보낼 수 있습니다.

View File

@@ -0,0 +1,319 @@
---
title: 로컬 설정
icon: laptop-code
description: 콘트리뷰터(기여자)나 호기심 많은 개발자를 위한 가이드로서, Twenty를 로컬에서 실행하고자 하는 분들에게 드리는 안내입니다.
---
## 사전 준비
<Tabs>
<Tab title="Linux 및 macOS">
Twenty를 설치하고 사용하기 전에, 먼저 컴퓨터에 다음을 설치하세요:
* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [Node v24.5.0](https://nodejs.org/en/download)
* [yarn v4](https://yarnpkg.com/getting-started/install)
* [nvm](https://github.com/nvm-sh/nvm/blob/master/README.md)
<Warning>
`npm`은 사용할 수 없으며, 대신 `yarn`을 사용해야 합니다. Yarn은 이제 Node.js와 함께 제공되기 때문에 별도로 설치할 필요가 없습니다.
아직 하지 않았다면 `corepack enable`을 실행하여 Yarn을 사용할 수 있도록 설정하세요.
</Warning>
</Tab>
<Tab title="윈도우 (WSL)">
1. WSL 설치
PowerShell을 관리자 권한으로 열고 다음을 실행하세요:
```powershell
wsl --install
```
이제 컴퓨터를 재시작하라는 프롬프트가 나타날 것입니다. 그렇지 않으면 수동으로 재시작하세요.
다시 시작 후 PowerShell 창이 열리고 Ubuntu가 설치됩니다. 이 작업은 다소 시간이 걸릴 수 있습니다.
Ubuntu 설치 시 사용자 이름과 암호를 만드는 프롬프트가 나타납니다.
2. Git 설치 및 구성
```bash
sudo apt-get install git
git config --global user.name "Your Name"
git config --global user.email "youremail@domain.com"
```
3. nvm, node.js 및 yarn 설치
<Warning>
적절한 `node` 버전을 설치하기 위해 `nvm`을 사용하세요. `.nvmrc`는 모든 기여자가 동일한 버전을 사용하도록 보장합니다.
</Warning>
```bash
sudo apt-get install curl
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
```
nvm을 사용하려면 터미널을 닫았다가 다시 여세요. 그런 다음 다음 명령어를 실행하세요.
```bash
nvm install # installs recommended node version
nvm use # use recommended node version
corepack enable
```
</Tab>
</Tabs>
---
## 1단계: Git 복제
터미널에서 다음 명령어를 실행하세요.
<Tabs>
<Tab title="SSH (권장)">
아직 SSH 키를 설정하지 않은 경우, [여기](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/about-ssh)를 참조하여 설정하는 방법을 배우세요.
```bash
git clone git@github.com:twentyhq/twenty.git
```
</Tab>
<Tab title="HTTPS">
```bash
git clone https://github.com/twentyhq/twenty.git
```
</Tab>
</Tabs>
## 2단계: 루트 위치 설정
```bash
cd twenty
```
다음 단계의 모든 명령어는 프로젝트 루트에서 실행하세요.
## 3단계: PostgreSQL 데이터베이스 설정
<Tabs>
<Tab title="리눅스">
**옵션 1 (권장):** 데이터베이스를 로컬에서 프로비저닝하려면:
Linux 머신에 PostgreSQL을 설치하려면 다음 링크를 사용하세요: [PostgreSQL 설치](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
참고: 권한 오류를 피하기 위해 `psql` 명령어 앞에 `sudo -u postgres`를 추가해야 할 수 있습니다.
**옵션 2:** 도커가 설치된 경우:
```bash
make -C packages/twenty-docker postgres-on-docker
```
</Tab>
<Tab title="맥 OS">
**옵션 1 (권장):** `brew`로 로컬에서 데이터베이스를 프로비저닝하려면:
```bash
brew install postgresql@16
export PATH="/opt/homebrew/opt/postgresql@16/bin:$PATH"
brew services start postgresql@16
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
PostgreSQL 서버가 실행 중인지 확인하려면 다음을 실행하세요:
```bash
brew services list
```
macOS에서 Homebrew로 설치할 때
설치 프로그램이 기본적으로 `postgres` 사용자를 생성하지 않을 수 있습니다. 대신 사용자의 macOS
사용자 이름(예: "john")과 일치하는 PostgreSQL 역할을 생성합니다.# PostgreSQL 접속psql postgres
또는
psql -U $(whoami) -d postgres
```bash
# Connect to PostgreSQL
psql postgres
or
psql -U $(whoami) -d postgres
```
psql 프롬프트에서(postgres=#), 다음을 실행하세요:
```bash
# List existing PostgreSQL roles
\du
```
출력은 다음과 비슷해야 합니다:
```bash
Role name | Attributes | Member of
-----------+-------------+-----------
john | Superuser | {}
```
`postgres` 역할이 나열되지 않으면 다음 단계를 계속하십시오.
`postgres` 역할을 수동으로 만드세요:
```bash
CREATE ROLE postgres WITH SUPERUSER LOGIN;
```
이는 로그인 액세스가 있는 슈퍼유저 역할 `postgres`를 생성합니다.
```bash
Role name | Attributes | Member of
-----------+-------------+-----------
postgres | Superuser | {}
john | Superuser | {}
```
**옵션 2:** 도커가 설치된 경우:
```bash
make -C packages/twenty-docker postgres-on-docker
```
</Tab>
<Tab title="윈도우 (WSL)">
다음 단계는 모두 WSL 터미널(가상 머신 내)에서 실행됩니다.
**옵션 1:** PostgreSQL을 로컬에서 프로비저닝하려면:
Linux 가상 머신에 PostgreSQL을 설치하려면 다음 링크를 사용하세요: [PostgreSQL 설치](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
참고: 권한 오류를 피하기 위해 `psql` 명령어 앞에 `sudo -u postgres`를 추가해야 할 수 있습니다.
**옵션 2:** 도커가 설치된 경우:
WSL에서 Docker를 실행하면 추가적인 복잡성이 발생합니다.
이 옵션은 [Docker Desktop WSL2](https://docs.docker.com/desktop/wsl)를 켠 상태에서의 추가 설정 단계를 수반할 수 있다는 점에서 편안할 때만 사용하십시오.
```bash
make -C packages/twenty-docker postgres-on-docker
```
</Tab>
</Tabs>
이제 `localhost:5432`에서 데이터베이스에 접속할 수 있습니다.
위의 Docker 옵션을 사용했다면, 기본 자격 증명은 사용자 `postgres`와 비밀번호 `postgres`입니다. 네이티브 PostgreSQL 설치의 경우, 로컬 머신에 구성된 자격 증명과 역할을 사용하세요.
## 4단계: Redis 데이터베이스 (캐시) 설정
Twenty는 최상의 성능을 제공하기 위해 Redis 캐시가 필요합니다.
<Tabs>
<Tab title="리눅스">
**옵션 1:** Redis를 로컬에서 프로비저닝하려면:
리눅스 기기에 Redis를 설치하려면 다음 링크를 사용하십시오: [Redis 설치](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/)
**옵션 2:** 도커가 설치된 경우:
```bash
make -C packages/twenty-docker redis-on-docker
```
</Tab>
<Tab title="맥 OS">
**옵션 1 (권장):** `brew`로 Redis를 로컬에서 프로비저닝하려면:
```bash
brew install redis
```
Redis 서버를 시작하세요:
```bash
brew services start redis
```
**옵션 2:** 도커가 설치된 경우:
```bash
make -C packages/twenty-docker redis-on-docker
```
</Tab>
<Tab title="윈도우 (WSL)">
**옵션 1:** Redis를 로컬에서 프로비저닝하려면:
Linux 가상 머신에 Redis를 설치하려면 다음 링크를 사용하세요: [Redis 설치](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/)
**옵션 2:** 도커가 설치된 경우:
```bash
make -C packages/twenty-docker redis-on-docker
```
</Tab>
</Tabs>
클라이언트 GUI가 필요하다면 [Redis Insight](https://redis.io/insight/)를 권장합니다(무료 버전 제공).
## 5단계: 환경 변수 설정
프로젝트를 구성하기 위해 환경 변수나 `.env` 파일을 사용하세요. 자세한 내용은 [여기](/l/ko/developers/self-host/capabilities/setup)에서 확인하세요.
`/front`와 `/server`의 `.env.example` 파일을 복사하세요:
```bash
cp ./packages/twenty-front/.env.example ./packages/twenty-front/.env
cp ./packages/twenty-server/.env.example ./packages/twenty-server/.env
```
<Info>
**멀티 워크스페이스 모드:** 기본적으로 Twenty는 하나의 워크스페이스만 생성할 수 있는 단일 워크스페이스 모드로 실행됩니다. 멀티 워크스페이스 지원을 활성화하려면(서브도메인 기반 기능을 테스트할 때 유용합니다), 서버의 `.env` 파일에서 `IS_MULTIWORKSPACE_ENABLED=true`로 설정하세요. 자세한 내용은 [멀티 워크스페이스 모드](/l/ko/developers/self-host/capabilities/setup#multi-workspace-mode)를 참조하세요.
</Info>
## 6단계: 의존성 설치
Twenty 서버를 빌드하고 데이터베이스에 몇 가지 데이터를 시드하기 위해, 다음 명령어를 실행하세요:
```bash
yarn
```
`npm`이나 `pnpm`은 작동하지 않음을 주의하세요.
## 7단계: 프로젝트 실행
<Tabs>
<Tab title="리눅스">
리눅스 배포판에 따라 Redis 서버가 자동으로 시작될 수 있습니다.
시작되지 않는다면, 각 배포판에 맞는 [Redis 설치 가이드](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/)를 확인하세요.
</Tab>
<Tab title="맥 OS">
Redis가 이미 실행 중이어야 합니다. 그렇지 않으면 다음을 실행하세요:
```bash
brew services start redis
```
</Tab>
<Tab title="윈도우 (WSL)">
리눅스 배포판에 따라 Redis 서버가 자동으로 시작될 수 있습니다.
시작되지 않는다면, 각 배포판에 맞는 [Redis 설치 가이드](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/)를 확인하세요.
</Tab>
</Tabs>
다음 명령어로 데이터베이스를 설정하세요:
```bash
npx nx database:reset twenty-server
```
서버, 워커 및 프론트엔드 서비스를 시작하세요:
```bash
npx nx start twenty-server
npx nx worker twenty-server
npx nx start twenty-front
```
또한, 모든 서비스를 한 번에 시작할 수도 있습니다:
```bash
npx nx start
```
## 8단계: Twenty 사용하기
**프론트엔드**
Twenty의 프론트엔드는 [http://localhost:3001](http://localhost:3001)에서 실행됩니다.
기본 데모 계정을 사용하여 로그인할 수 있습니다: `tim@apple.dev` (비밀번호: `tim@apple.dev`)
**백엔드**
* Twenty의 서버는 [http://localhost:3000](http://localhost:3000)에서 실행됩니다.
* GraphQL API는 [http://localhost:3000/graphql](http://localhost:3000/graphql)에서 액세스할 수 있습니다
* REST API는 [http://localhost:3000/rest](http://localhost:3000/rest)에서 액세스할 수 있습니다
## 문제 해결
문제가 발생하면 [문제 해결](/l/ko/developers/self-host/capabilities/troubleshooting)에서 해결책을 확인하십시오.

View File

@@ -0,0 +1,77 @@
---
title: 명령
icon: terminal
description: Twenty 개발에 유용한 명령어.
---
`npx nx`를 사용해 리포지토리 루트에서 명령을 실행할 수 있습니다. 명시적으로 대상 지정하려면 `npx nx run {project}:{command}`를 사용하세요.
## 앱 시작하기
```bash
npx nx start twenty-front # Frontend dev server (http://localhost:3001)
npx nx start twenty-server # Backend server (http://localhost:3000)
npx nx run twenty-server:worker # Background worker
```
## 데이터베이스
```bash
npx nx database:reset twenty-server # Reset and seed database
npx nx run twenty-server:database:migrate:prod # Run migrations
npx nx run twenty-server:database:migrate:generate --name <name> --type <fast|slow> # Generate a migration
```
## 린팅
```bash
npx nx lint:diff-with-main twenty-front # Lint changed files (fastest)
npx nx lint:diff-with-main twenty-server
npx nx lint twenty-front --configuration=fix # Auto-fix
```
## 타입 검사
```bash
npx nx typecheck twenty-front
npx nx typecheck twenty-server
```
## 테스트
```bash
# Frontend
npx nx test twenty-front # Jest unit tests
npx nx storybook:build twenty-front # Build Storybook
npx nx storybook:test twenty-front # Storybook tests
# Backend
npx nx run twenty-server:test:unit # Unit tests
npx nx run twenty-server:test:integration # Integration tests
npx nx run twenty-server:test:integration:with-db-reset # Integration with DB reset
# Single file (fastest)
npx jest path/to/test.test.ts --config=packages/{project}/jest.config.mjs
```
## GraphQL
```bash
npx nx run twenty-front:graphql:generate # Regenerate types
npx nx run twenty-front:graphql:generate --configuration=metadata # Metadata schema
```
## 번역
```bash
npx nx run twenty-front:lingui:extract # Extract strings
npx nx run twenty-front:lingui:compile # Compile translations
```
## 빌드
```bash
npx nx build twenty-shared # Must be built first
npx nx build twenty-front
npx nx build twenty-server
```

View File

@@ -0,0 +1,31 @@
---
title: 기여
description: Twenty의 오픈 소스 개발에 기여하세요.
---
<Frame>
<img src="/images/user-guide/github/github-header.png" alt="AI" />
</Frame>
## 개요
Twenty는 오픈 소스로, 커뮤니티의 기여를 환영합니다. 버그를 수정하든, 기능을 추가하든, 문서를 개선하든, 여러분의 기여는 모두에게 더 나은 Twenty를 만드는 데 도움이 됩니다.
## 기여 방법
* **버그 신고**: 문제를 식별하고 문서화하는 데 도움을 주세요
* **기능 제안**: 새로운 기능을 제안하고 구현하세요
* **문서 개선**: 문서를 더 명확하고 유용하게 만드세요
* **프런트엔드 개발**: React 기반 UI 작업에 참여하세요
* **백엔드 개발**: NestJS 서버에 기여하세요
## 시작하기
<CardGroup cols={2}>
<Card title="버그 보고 및 요청" icon="bug" href="/l/ko/developers/contribute/capabilities/bug-and-requests">
문제를 보고하거나 기능을 요청하세요
</Card>
<Card title="프론트엔드 개발" icon="browser" href="/l/ko/developers/contribute/capabilities/frontend-development/folder-architecture-front">
UI에 기여하세요
</Card>
</CardGroup>

View File

@@ -0,0 +1,176 @@
---
title: 스타일 가이드
icon: paintbrush
description: Twenty에 기여하기 위한 코드 컨벤션과 모범 사례.
---
## 리액트
### 함수형 컴포넌트만 사용
항상 네임드 export가 있는 TSX 함수형 컴포넌트를 사용하십시오.
```tsx
// ❌ Bad
const MyComponent = () => {
return <div>Hello World</div>;
};
export default MyComponent;
// ✅ Good
export function MyComponent() {
return <div>Hello World</div>;
};
```
### 프로퍼티
이름이 `{ComponentName}Props`인 타입을 생성하십시오. 구조 분해 할당을 사용하십시오. `React.FC`를 사용하지 마십시오.
```tsx
type MyComponentProps = {
name: string;
};
export const MyComponent = ({ name }: MyComponentProps) => <div>Hello {name}</div>;
```
### 단일 변수 prop 스프레딩 금지
```tsx
// ❌ Bad
const MyComponent = (props: MyComponentProps) => <Other {...props} />;
// ✅ Good
const MyComponent = ({ prop1, prop2 }: MyComponentProps) => <Other {...{ prop1, prop2 }} />;
```
## 상태 관리
### 전역 상태를 위한 Jotai 아톰
```tsx
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
export const myAtomState = createAtomState<string>({
key: 'myAtomState',
defaultValue: 'default value',
});
```
* prop 드릴링보다 아톰을 선호하십시오.
* 상태에는 `useRef`를 사용하지 말고 — `useState` 또는 아톰을 사용하십시오
* 목록에는 아톰 패밀리와 셀렉터를 사용하십시오
### 불필요한 리렌더링을 피하십시오
* `useEffect`와 데이터 패칭을 형제 사이드카 컴포넌트로 분리하십시오
* `useEffect`보다 이벤트 핸들러(`handleClick`, `handleChange`)를 선호하십시오
* `React.memo()`를 사용하지 말고 — 근본 원인을 수정하십시오
* `useCallback` / `useMemo` 사용을 제한하십시오
```tsx
// ❌ Bad — useEffect in the same component causes re-renders
export const Page = () => {
const [data, setData] = useAtomState(dataState);
const [dep] = useAtomState(depState);
useEffect(() => { setData(dep); }, [dep]);
return <div>{data}</div>;
};
// ✅ Good — extract into sibling
export const PageData = () => {
const [data, setData] = useAtomState(dataState);
const [dep] = useAtomState(depState);
useEffect(() => { setData(dep); }, [dep]);
return <></>;
};
export const Page = () => {
const [data] = useAtomState(dataState);
return <div>{data}</div>;
};
```
## 타입스크립트
* **`interface`보다 `type`** — 더 유연하고 조합하기 쉽습니다
* **열거형보다 문자열 리터럴** — GraphQL codegen enum 및 내부 라이브러리 API는 예외
* **`any` 금지** — 엄격한 TypeScript를 강제합니다
* **타입 import 금지** — 일반 import를 사용하십시오 (Oxlint `typescript/consistent-type-imports`로 강제)
* **[Zod](https://github.com/colinhacks/zod)을 사용**하여 타입이 없는 객체를 런타임에 검증하십시오
## 자바스크립트
```tsx
// Use nullish-coalescing (??) instead of ||
const value = process.env.MY_VALUE ?? 'default';
// Use optional chaining
onClick?.();
```
## 이름 지정
* **변수**: camelCase, 의미를 잘 드러내도록 작성 (`value`가 아닌 `email`, `fm`이 아닌 `fieldMetadata`)
* **상수**: SCREAMING_SNAKE_CASE
* **타입/클래스**: PascalCase
* **파일/디렉터리**: kebab-case (`.component.tsx`, `.service.ts`, `.entity.ts`)
* **이벤트 핸들러**: `handleClick` (핸들러 함수 이름은 `onClick`이 아님)
* **컴포넌트 props**: 컴포넌트 이름을 접두사로 붙이십시오 (`ButtonProps`)
* **스타일드 컴포넌트**: `Styled` 접두사를 붙이십시오 (`StyledTitle`)
## 스타일링
[Linaria](https://github.com/callstack/linaria) 스타일드 컴포넌트를 사용하십시오. 테마 값을 사용하십시오 — 하드코딩된 `px`, `rem` 또는 색상은 피하십시오.
```tsx
// ❌ Bad
const StyledButton = styled.button`
color: #333333;
font-size: 1rem;
margin-left: 4px;
`;
// ✅ Good
const StyledButton = styled.button`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
margin-left: ${({ theme }) => theme.spacing(1)};
`;
```
## 가져오기
상대 경로 대신 별칭을 사용하십시오:
```tsx
// ❌ Bad
import { Foo } from '../../../../../testing/decorators/Foo';
// ✅ Good
import { Foo } from '~/testing/decorators/Foo';
import { Bar } from '@/modules/bar/components/Bar';
```
## 폴더 구조
```
front
└── modules/ # Feature modules
│ └── module1/
│ ├── components/
│ ├── constants/
│ ├── contexts/
│ ├── graphql/ (fragments, queries, mutations)
│ ├── hooks/
│ ├── states/ (atoms, selectors)
│ ├── types/
│ └── utils/
└── pages/ # Route-level components
└── ui/ # Reusable UI components (display, input, feedback, ...)
```
* 모듈은 다른 모듈에서 import할 수 있지만, `ui/`는 의존성이 없어야 합니다
* 모듈 전용 코드는 `internal/` 하위 폴더를 사용하십시오
* 컴포넌트는 300줄 이하, 서비스는 500줄 이하