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: Cele mai bune practici
---
Acest document prezintă cele mai bune practici pe care ar trebui să le urmați atunci când lucrați la backend.
## Urmați o abordare modulară
Backend-ul urmează o abordare modulară, care este un principiu fundamental atunci când lucrați cu NestJS. Asigurați-vă că vă împărțiți codul în module reutilizabile pentru a menține o bază de cod curată și organizată.
Fiecare modul ar trebui să encapsuleze o anumită funcționalitate sau caracteristică și să aibă un domeniu de aplicare bine definit. Această abordare modulară permite o clară separare a preocupărilor și elimină complexitățile inutile.
## Expuneți servicii pentru utilizare în module
Creați întotdeauna servicii care au o responsabilitate clară și unică, ceea ce îmbunătățește lizibilitatea și întreținerea codului. Denumirea serviciilor trebuie să fie descriptivă și consecventă.
De asemenea, ar trebui să expuneți serviciile pe care doriți să le utilizați în alte module. Expunerea serviciilor la alte module este posibilă prin sistemul de injecție de dependențe puternic al NestJS și promovează cuplarea slabă între componente.
## Evitați utilizarea tipului `any`
Când declarați o variabilă ca fiind `any`, verificatorul de tip al TypeScript nu realizează niciun control de tip, făcând posibilă atribuirea oricărui tip de valori variabilei. TypeScript utilizează inferența tipului pentru a determina tipul variabilei pe baza valorii. Declarându-l ca `any`, TypeScript nu mai poate deduce tipul. Acest lucru îngreunează depistarea erorilor de tip în timpul dezvoltării, ceea ce duce la erori la rulare și face codul mai greu de întreținut, mai puțin fiabil și mai greu de înțeles pentru ceilalți.
De aceea, totul ar trebui să aibă un tip. Astfel, dacă creați un nou obiect cu un prenume și un nume de familie, ar trebui să creați o interfață sau tip care să conțină un prenume și un nume de familie care să definească forma obiectului pe care îl manipulați.

View File

@@ -0,0 +1,39 @@
---
title: Obiecte personalizate
---
Obiectele sunt structuri care îți permit să stochezi date (înregistrări, atribute și valori) specifice organizației tale. Twenty oferă atât obiecte standard, cât și personalizate.
Obiectele standard sunt obiecte încorporate cu un set de atribute disponibile pentru toți utilizatorii. Exemple de obiecte standard în Twenty includ Companie și Persoană. Obiectele standard au câmpuri standard care sunt disponibile pentru toți utilizatorii Twenty, cum ar fi Company.displayName.
Obiectele personalizate sunt acelea pe care le poți crea pentru a stoca informații unice pentru organizația ta. Nu sunt încorporate; membrii workspace-ului tău pot crea și personaliza obiecte personalizate pentru a deține informații pentru care obiectele standard nu sunt adecvate.
## Schema la nivel înalt
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/custom-object-schema.png" alt="Schema la nivel înalt" />
</div>
<br />
## Cum funcționează
Obiectele personalizate provin din tabele de metadate care determină forma, numele și tipul obiectelor. Toate aceste informații sunt prezente în baza de date de schema de metadate, constând din tabele:
* **DataSource**: Detalii privind locul unde sunt prezente datele.
* **Object**: Descrie obiectul și îl leagă de un DataSource.
* **Field**: Prezintă câmpurile unui Obiect și le conectează la Obiect.
Pentru a adăuga un obiect personalizat, membrul workspace-ului va interoga API-ul /metadata. Aceasta actualizează metadatele în consecință și calculează o schemă GraphQL pe baza metadatelor, stocând-o în cache-ul GQL pentru utilizare ulterioară.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/add-custom-objects.jpeg" alt="Interoghează API-ul /metadata pentru a adăuga obiecte personalizate" />
</div>
<br />
Pentru a prelua datele, procesul implică realizarea de interogări prin endpoint-ul /graphql și transmiterea lor prin Query Resolver.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/custom-object-schema.png" alt="Interoghează endpoint-ul /graphql pentru a prelua date" />
</div>

View File

@@ -0,0 +1,46 @@
---
title: Steaguri de caracteristici
---
Steagurile de caracteristici sunt utilizate pentru a ascunde caracteristici experimentale. Pentru Twenty, acestea sunt setate la nivel de spațiu de lucru și nu la nivel de utilizator.
## Adăugarea unui nou steag de caracteristici
În `FeatureFlagKey.ts` adăugați steagul de caracteristici:
```ts
type FeatureFlagKey =
| 'IS_FEATURENAME_ENABLED'
| ...;
```
Adăugați-l și în enumul din `feature-flag.entity.ts`:
```ts
enum FeatureFlagKeys {
IsFeatureNameEnabled = 'IS_FEATURENAME_ENABLED',
...
}
```
Pentru a aplica un steag de caracteristici la o funcționalitate de **backend**, utilizați:
```ts
@Gate({
featureFlag: 'IS_FEATURENAME_ENABLED',
})
```
Pentru a aplica un steag de caracteristici la o funcționalitate de **frontend**, utilizați:
```ts
const isFeatureNameEnabled = useIsFeatureEnabled('IS_FEATURENAME_ENABLED');
```
## Configurați steagurile de caracteristici pentru implementare
Modificați înregistrarea corespunzătoare în tabelul `core.featureFlag`:
| id | cheie | IdSpațiuDeLucru | valoare |
| --------- | ------------------------ | --------------- | ---------- |
| Aleatoriu | `IS_FEATURENAME_ENABLED` | IdSpațiuDeLucru | `adevărat` |

View File

@@ -0,0 +1,125 @@
---
title: Arhitectura Folderului
info: O privire detaliată asupra arhitecturii folderului serverului nostru
---
Structura directorului backend este următoarea:
```
server
└───ability
└───constants
└───core
└───database
└───decorators
└───filters
└───guards
└───health
└───integrations
└───metadata
└───workspace
└───utils
```
## Abilitate
Definește permisiunile și include gestionari pentru fiecare entitate.
## Decoratori
Definește decoratori personalizați în NestJS pentru funcționalitate adăugată.
Vezi [decoratori personalizați](https://docs.nestjs.com/custom-decorators) pentru mai multe detalii.
## Filtre
Include filtre de excepție pentru a gestiona excepțiile care ar putea apărea în punctele finale GraphQL.
## Paznici
Vezi [paznici](https://docs.nestjs.com/guards) pentru mai multe detalii.
## Sănătate
Include o API REST disponibilă public (healthz) care returnează un JSON pentru a verifica dacă baza de date funcționează așa cum este de așteptat.
## Metadate
Definește obiecte personalizate și face disponibilă o API GraphQL (graphql/metadata).
## Spațiu de lucru
Generează și servește o schemă GraphQL personalizată pe baza metadatelor.
### Structura Directorului de Spațiu de Lucru
```
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
```
Rădăcina directorului workspace include `workspace.factory.ts`, un fișier care conține funcția `createGraphQLSchema`. Această funcție generează schema specifică spațiului de lucru folosind metadatele pentru a personaliza o schemă pentru spațiile de lucru individuale. Prin separarea construcției de schema și rezolvatori, folosim funcția `makeExecutableSchema`, care combină aceste elemente discrete.
Această strategie nu este doar despre organizare, ci ajută și la optimizare, cum ar fi caching-ul definițiilor de tip generate pentru a îmbunătăți performanța și scalabilitatea.
### Constructor Schema Spațiu de Lucru
Generează schema GraphQL și include:
#### Fabrici:
Constructori specializați pentru a genera construcții legate de GraphQL.
* Tipul.fabrică traduce metadatele câmpului în tipuri GraphQL folosind `TypeMapperService`.
* Tipul-definiție.fabrică creează obiecte GraphQL de intrare sau ieșire derivate din `objectMetadata`.
#### Tipuri GraphQL
Include enumerări, inputuri, obiecte și scalari și servește ca blocuri de construcție pentru construcția schema.
#### Interfețe și Definiții de Obiect
Conține planurile pentru entitățile GraphQL și include atât tipuri predefinite, cât și personalizate, precum `MONEY` sau `URL`.
#### Servicii
Conține serviciul responsabil pentru asocierea FieldMetadataType cu scalarul GraphQL corespunzător sau modificatorii de interogare.
#### Stocare
Include clasa `TypeDefinitionsStorage` care conține definiții de tip reutilizabile, prevenind duplicarea tipurilor GraphQL.
### Constructor Rezolvator Spațiu de Lucru
Creează funcții de rezolvare pentru interogarea și modificarea schemei GraphQL.
Fiecare fabrică din acest director este responsabilă pentru producerea unui tip de rezolvator distinct, cum ar fi `FindManyResolverFactory`, proiectată pentru aplicare adaptabilă în diverse tabele.
### Executor Întrebări Spațiu de Lucru
Rulează interogările generate pe baza de date și parcurge rezultatul.

View File

@@ -0,0 +1,41 @@
---
title: Coada de mesaje
---
Cozile facilitează desfășurarea operațiunilor asincrone. Acestea pot fi folosite pentru a efectua sarcini de fundal precum trimiterea unui e-mail de bun venit la înregistrare.
Fiecare caz de utilizare va avea propria clasă de coadă extinsă din `MessageQueueServiceBase`.
În prezent, acceptăm doar `bull-mq`[bull-mq](https://bullmq.io/) ca driver de coadă.
## Pași pentru a crea și utiliza o coadă nouă
1. Adăugați un nume pentru noua coadă sub enum `MESSAGE_QUEUES`.
2. Furnizează implementarea de tip factory a cozii, folosind numele cozii ca token de dependență.
3. Injectează coada pe care ai creat-o în modulul/serviciul necesar, folosind numele cozii ca token de dependență.
4. Adăugați o clasă lucrător cu injectare bazată pe simbol, la fel ca producătorul.
### Exemplu de utilizare
```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: Comenzi Backend
icon: terminal
---
## Comenzi utile
Aceste comenzi ar trebui să fie executate din dosarul packages/twenty-server.
Din orice alt dosar poți rula `npx nx {command} twenty-server` (sau `npx nx run twenty-server:{command}`).
### Configurare de la prima utilizare
```
npx nx database:reset twenty-server # setup the database with dev seeds
```
### Pornirea serverului
```
npx nx run twenty-server:start
```
### Lint
```
npx nx run twenty-server:lint # pass --fix to fix lint errors
```
### Test
```
npx nx run twenty-server:test:unit # run unit tests
npx nx run twenty-server:test:integration # run integration tests
```
Notă: poți rula `npx nx run twenty-server:test:integration:with-db-reset` în cazul în care trebuie să resetezi baza de date înainte de a rula testele de integrare.
### Resetarea bazei de date
Dacă vrei să resetezi și să configurezi baza de date, poți rula comanda următoare:
```bash
npx nx run twenty-server:database:reset
```
### Migrații
#### Pentru obiectele din schematizările de Bază/Metadate (TypeORM)
```bash
npx nx run twenty-server:database:migrate:generate
```
## Tehnologii Utilizate
Twenty folosește în principal NestJS pentru backend.
Prisma a fost primul ORM pe care l-am folosit. Dar pentru a permite utilizatorilor să creeze câmpuri și obiecte personalizate, un nivel inferior a făcut mai mult sens, deoarece trebuie să avem un control detaliat. Proiectul folosește acum TypeORM.
Iată cum arată acum stack-ul tehnologic.
**Nucleu**
* [NestJS](https://nestjs.com/)
* [TypeORM](https://typeorm.io/)
* [GraphQL Yoga](https://the-guild.dev/graphql/yoga-server)
**Bază de date**
* [Postgres](https://www.postgresql.org/)
**Integrări terțe**
* [Sentry](https://sentry.io/welcome/) pentru urmărirea erorilor
**Testare**
* [Jest](https://jestjs.io/)
**Instrumente**
* [Yarn](https://yarnpkg.com/)
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
**Dezvoltare**
* [AWS EKS](https://aws.amazon.com/eks/)

View File

@@ -0,0 +1,85 @@
---
title: Aplicația Zapier
---
Sincronizează fără efort Twenty cu peste 3000 de aplicații folosind [Zapier](https://zapier.com/). Automatizează sarcinile, stimulează productivitatea și îmbunătățește relațiile cu clienții!
## Despre Zapier
Zapier este un instrument care îți permite să automatizezi fluxurile de lucru conectând aplicațiile pe care echipa ta le folosește zilnic. Conceptul de bază al Zapier este fluxurile de automatizare, numite Zaps, care includ triggeri și acțiuni.
Poți afla mai multe despre cum funcționează Zapier [aici](https://zapier.com/how-it-works).
## Configurare
### Pasul 1: Instalează pachetele Zapier
```bash
cd packages/twenty-zapier
yarn
```
### Pasul 2: Autentificare cu CLI-ul
Folosește acreditările Zapier pentru a te autentifica folosind CLI-ul:
```bash
zapier login
```
### Pasul 3: Setează variabilele de mediu
Din folderul `packages/twenty-zapier`, rulează:
```bash
cp .env.example .env
```
Rulează aplicația local, accesează [http://localhost:3000/settings/api-webhooks](http://localhost:3000/settings/api-webhooks) și generează o cheie API.
Înlocuiește valoarea **YOUR_API_KEY** din fișierul `.env` cu cheia API pe care tocmai ai generat-o.
## Dezvoltare
<Warning>
Asigură-te că rulezi `yarn build` înainte de orice comandă `zapier`.
</Warning>
### Test
```bash
yarn test
```
### Lint
```bash
yarn format
```
### Urmărește și compilează pe măsură ce editezi codul
```bash
yarn watch
```
### Validează aplicația Zapier
```bash
yarn validate
```
### Desfășoară aplicația Zapier
```bash
yarn deploy
```
### Listează toate comenzile CLI Zapier
```bash
zapier
```

View File

@@ -0,0 +1,79 @@
---
title: Bug-uri, solicitări și pull request-uri
icon: bug
info: Raportați probleme, solicitați funcționalități și contribuiți cu cod
---
## Raportare Erori
Pentru a raporta o eroare, te rog [creează o problemă pe GitHub](https://github.com/twentyhq/twenty/issues/new).
Poți de asemenea să ceri ajutor pe [Discord](https://discord.gg/cx5n4Jzs57).
## Cereri de Funcționalități
Dacă nu ești sigur că este o eroare și simți că este mai aproape de o cerere de funcționalitate, atunci ar trebui probabil să [deschizi în loc o discuție](https://github.com/twentyhq/twenty/discussions/new).
## Trimiteți un pull request
Contribuirea cu cod la Twenty începe cu un pull request (PR).
### Înainte de a începe
1. Verificați [problemele existente](https://github.com/twentyhq/twenty/issues) pentru lucrări conexe
2. Pentru funcționalități noi, deschideți mai întâi o problemă pentru a discuta
3. Consultați [Codul de conduită](https://github.com/twentyhq/twenty/blob/main/.github/CODE_OF_CONDUCT.md)
### Faceți fork și clonați
1. Faceți fork al repository-ului pe GitHub
2. Clonați fork-ul:
```bash
git clone https://github.com/YOUR_USERNAME/twenty.git
cd twenty
```
3. Adăugați remote-ul upstream:
```bash
git remote add upstream https://github.com/twentyhq/twenty.git
```
### Creați o ramură
```bash
git checkout -b feature/your-feature-name
```
Folosiți nume descriptive pentru ramuri:
* `feature/add-export-button`
* `fix/login-redirect-issue`
* `docs/update-api-guide`
### Faceți modificările
1. Scrieți cod curat, bine documentat
2. Respectați stilul de cod existent
3. Adăugați teste pentru funcționalitatea nouă
4. Actualizați documentația dacă este necesar
### Trimiteți PR-ul
1. Faceți push pentru ramura dvs.:
```bash
git push origin feature/your-feature-name
```
2. Deschideți un PR pe GitHub
3. Completați șablonul PR
4. Asociați problemele corelate
### Listă de verificare PR
* [ ] Codul respectă ghidurile de stil ale proiectului
* [ ] Testele trec local
* [ ] Documentația este actualizată
* [ ] Descrierea PR-ului explică modificările

View File

@@ -0,0 +1,329 @@
---
title: Cele mai bune practici
icon: star
---
Acest document prezintă cele mai bune practici pe care ar trebui să le urmați atunci când lucrați la frontend.
## Managementul stării
React și Jotai se ocupă de managementul stării în cod.
### Folosiți atomi Jotai pentru a stoca starea
Este o bună practică să creezi atâția atomi câți ai nevoie pentru a-ți stoca starea.
<Warning>
Este mai bine să folosești atomi suplimentari decât să încerci să fii prea concis recurgând la prop drilling.
</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>
);
}
```
### Nu folosiți `useRef` pentru a stoca starea
Evitați utilizarea `useRef` pentru a stoca starea.
Dacă doriți să stocați starea, ar trebui să folosiți `useState` sau atomii Jotai cu `useAtomState`.
Vezi [cum să gestionezi re-randările](#managing-re-renders) dacă ți se pare că ai nevoie de `useRef` pentru a preveni apariția unor re-randări.
## Gestionarea re-randărilor
Re-render-urile pot fi greu de gestionat în React.
Aici sunt câteva reguli de urmat pentru a evita re-render-urile inutile.
Ține minte că poți **întotdeauna** evita re-randările înțelegând cauza lor.
### Lucrați la nivelul rădăcinii
Evitarea re-randărilor în funcționalități noi este acum ușoară prin eliminarea lor de la rădăcină.
Componenta secundară `PageChangeEffect` conține doar un `useEffect` ce deține toată logica de execuție la o schimbare de pagină.
În acest fel știți că există doar un loc care poate declanșa un re-render.
### Gândiți-vă de două ori înainte de a adăuga `useEffect` în baza de cod
Re-render-urile sunt adesea cauzate de `useEffect` inutile.
Ar trebui să vă gândiți dacă aveți nevoie de `useEffect` sau dacă puteți muta logica într-o funcție de handler de eveniment.
Vă va fi, în general, ușor să mutați logica într-o funcție `handleClick` sau `handleChange`.
De asemenea, le puteți găsi în biblioteci precum Apollo: `onCompleted`, `onError`, etc.
### Folosiți o componentă adiacentă pentru a extrage `useEffect` sau logica de interogare de date
Dacă simțiți nevoia să adăugați un `useEffect` în componenta rădăcină, ar trebui să luați în considerare extragerea sa într-o componentă secundară.
Puteți aplica același principiu pentru logica de interogare de date, folosind hooks cu 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 />
</>
);
```
### Folosiți stări de familie de atomi și selectoare
Stările de familie de atomi și selectoarele sunt o metodă excelentă de a evita re-randările.
Sunt utile când trebuie să stocați o listă de elemente.
### Nu ar trebui să folosiți `React.memo(MyComponent)`
Evitați utilizarea `React.memo()` deoarece nu rezolvă cauza re-render-ului, ci întrerupe lanțul re-render, ceea ce poate duce la comportamente neașteptate și face codul foarte greu de refactorizat.
### Limitați utilizarea `useCallback` sau `useMemo`
Acestea sunt adesea inutile și vor face codul mai greu de citit și menținut pentru un câștig în performanță care nu este sesizabil.
## Console.logs
Instrucțiunile `console.log` sunt valoroase în timpul dezvoltării, oferind informații în timp real despre valorile variabilelor și fluxul de cod. Totuși, lăsându-le în codul de producție pot duce la mai multe probleme:
1. **Performanță**: Logările excesive pot afecta performanța de execuție, în special în aplicațiile pe partea de client.
2. **Siguranță**: Logarea datelor sensibile poate expune informații critice oricui inspectează consola browser-ului.
3. **Claritate**: Umplerea consolei cu logări poate ascunde avertismente sau erori importante pe care dezvoltatorii sau uneltele le trebuie să le vadă.
4. **Profesionalism**: Utilizatorii finali sau clienții care verifică consola și văd o multitudine de instrucțiuni de log s-ar putea să pună la îndoială calitatea și finisajul codului.
Asigurați-vă că eliminați toate `console.log`-urile înainte de a trimite codul în producție.
## Denumiri
### Denumire de variabilă
Denumirile variabilelor trebuie să descrie cu precizie scopul sau funcția variabilei.
#### Problema cu denumirile generice
Denumirile generice în programare nu sunt ideale deoarece nu au specificitate, ducând la ambiguitate și la reducerea lizibilității codului. Astfel de denumiri nu reușesc să transmită scopul variabilei sau funcției, ceea ce îngreunează înțelegerea intenției codului de către dezvoltatori fără o investigație mai profundă. Acest lucru poate duce la creșterea timpului de depanare, la o mai mare susceptibilitate la erori și dificultăți în întreținere și colaborare. Între timp, denumirile descriptive fac codul auto-explicativ și mai ușor de navigat, îmbunătățind calitatea codului și productivitatea dezvoltatorilor.
```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('');
```
#### Câteva cuvinte de evitat în denumirile de variabile
* dummy
### Handlere de evenimente
Numele handlerelor de evenimente ar trebui să înceapă cu `handle`, în timp ce `on` este un prefix folosit pentru a denumi evenimentele în prop-urile componentelor.
```tsx
// ❌ Bad
const onEmailChange = (val: string) => {
// ...
};
```
```tsx
// ✅ Good
const handleEmailChange = (val: string) => {
// ...
};
```
## Props opționale
Evitați să transmiteți valoarea implicită pentru un props opțional.
**EXEMPLU**
Țineți cont de componenta `EmailField` definită mai jos:
```tsx
type EmailFieldProps = {
value: string;
disabled?: boolean;
};
const EmailField = ({ value, disabled = false }: EmailFieldProps) => (
<TextInput value={value} disabled={disabled} fullWidth />
);
```
**Utilizare**
```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" />;
```
## Componentă ca props
Încercați, pe cât posibil, să transmiteți componente neinstanțiate ca props, astfel încât copiii să poată decide singuri de ce props au nevoie să transmită.
Cel mai frecvent exemplu pentru aceasta sunt componentele de pictograme:
```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>
)
};
```
Pentru ca React să înțeleagă că componenta este o componentă, trebuie să folosești PascalCase, pentru a o instanția ulterior cu `<MyIcon>`
## Prop Drilling: Păstrați-l Minimal
Prop drilling, în contextul React, se referă la practica de a transmite variabile de stare și setatorii acestora prin multe straturi de componente, chiar dacă componentele intermediare nu le folosesc. Deși uneori necesar, prop drilling excesiv poate duce la:
1. **Citibilitate Scăzută**: Urmărirea de unde provine un prop sau unde este utilizat poate deveni complexă într-o structură de componente foarte profundă.
2. **Provocări de Mentenanță**: Schimbările în structura props a unei componente pot necesita ajustări în mai multe componente, chiar dacă acestea nu utilizează direct prop-ul.
3. **Reutilizabilitate Redusă a Componentelor**: O componentă ce primește multe props doar pentru a le transmite mai departe devine mai puțin de uz general și mai greu de reutilizat în contexte diferite.
Dacă simțiți că folosiți prea mult prop drilling, consultați [cele mai bune practici de gestionare a stării](#state-management).
## Importuri
Când importați, optați pentru pseudonimele desemnate în loc să specificați căi complete sau relative.
**Pseudonimele**
```js
{
alias: {
"~": path.resolve(__dirname, "src"),
"@": path.resolve(__dirname, "src/modules"),
"@testing": path.resolve(__dirname, "src/testing"),
},
}
```
**Utilizare**
```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';
```
## Validarea Schemelor
[Zod](https://github.com/colinhacks/zod) este validatorul de scheme pentru obiectele netipizate:
```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>;
```
## Schimbări Majore
Efectuați întotdeauna teste manuale temeinice înainte de a continua pentru a garanta că modificările nu au cauzat întreruperi în altă parte, având în vedere că testele nu au fost încă integrate extensiv.

View File

@@ -0,0 +1,110 @@
---
title: Arhitectura Folderului
icon: folder-tree
info: O privire detaliată asupra arhitecturii folderului nostru
---
În acest ghid, veți explora detaliile structurii directoarelor proiectului și modul în care aceasta contribuie la organizarea și mentenabilitatea Twenty.
Respectând convenția de arhitectură a folderului, este mai ușor să găsești fișierele legate de funcționalități specifice și să te asiguri că aplicația este scalabilă și ușor de întreținut.
```
front
└───modules
│ └───module1
│ │ └───submodule1
│ └───module2
│ └───ui
│ │ └───display
│ │ └───inputs
│ │ │ └───buttons
│ │ └───...
└───pages
└───...
```
## Pagini
Include componentele de nivel superior definite de rutele aplicației. Importă componente de nivel mai scăzut din folderul de module (mai multe detalii mai jos).
## Module
Fiecare modul reprezintă o funcționalitate sau un grup de funcționalități, cuprinzând componentele, stările și logica operațională specifice.
Toate ar trebui să urmeze structura de mai jos. Poți introduce module în cadrul altor module (denumite submodule), iar aceleași reguli se vor aplica.
```
module1
└───components
│ └───component1
│ └───component2
└───constants
└───contexts
└───graphql
│ └───fragments
│ └───queries
│ └───mutations
└───hooks
│ └───internal
└───states
│ └───selectors
└───types
└───utils
```
### Contexturi
Un context este o modalitate de a transmite date prin arborele de componente fără a fi necesar să transmiți manual proprietățile la fiecare nivel.
Vezi [React Context](https://react.dev/reference/react#context-hooks) pentru mai multe detalii.
### GraphQL
Include fragmente, interogări și mutații.
Vezi [GraphQL](https://graphql.org/learn/) pentru mai multe detalii.
* Fragmente
Un fragment este o bucată reutilizabilă dintr-o interogare, pe care o poți folosi în diferite locuri. Folosind fragmente este mai ușor să eviți duplicarea codului.
Vezi [GraphQL Fragments](https://graphql.org/learn/queries/#fragments) pentru mai multe detalii.
* Interogări
Vezi [GraphQL Queries](https://graphql.org/learn/queries/) pentru mai multe detalii.
* Mutații
Vezi [GraphQL Mutations](https://graphql.org/learn/queries/#mutations) pentru mai multe detalii.
### Hook-uri
Vezi [Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) pentru mai multe detalii.
### Stări
Conține logica de gestionare a stării. [Jotai](https://jotai.org) gestionează aceasta.
* Selectori: Atomi derivați (folosind `createAtomSelector`) calculează valori din alți atomi și sunt memoizați automat.
Managementul de stare încorporat în React gestionează încă starea în cadrul unei componente.
### Unelte
Ar trebui să conțină doar funcții pure, reutilizabile. În caz contrar, creează hook-uri personalizate în folderul `hooks`.
## UI
Conține toate componentele UI reutilizabile folosite în aplicație.
Acest folder poate conține subfoldere, precum `data`, `afișaj`, `feedback` și `intrare` pentru tipuri specifice de componente. Fiecare componentă ar trebui să fie autonomă și reutilizabilă, astfel încât să o poți folosi în diferite părți ale aplicației.
Prin separarea componentelor UI de celelalte componente din folderul `modules`, este mai ușor să menții un design consistent și să faci modificări la interfața grafică fără a afecta alte părți (logica de afaceri) ale codului.
## Interfață și dependențe
Poți importa codul altor module din orice modul, cu excepția folderului `ui`. Acest lucru va menține codul ușor de testat.
### Intern
Fiecare parte (hook-uri, stări, ...) dintr-un modul poate avea un folder `intern`, care conține părți care sunt doar utilizate în cadrul modulului.

View File

@@ -0,0 +1,91 @@
---
title: Comenzi Frontend
icon: terminal
---
## Comenzi utile
### Pornirea aplicației
```bash
npx nx start twenty-front
```
### Regenerează schema graphql pe baza schemei graphql a API-ului
```bash
npx nx run twenty-front:graphql:generate --configuration=metadata
```
SAU
```bash
npx nx run twenty-front:graphql:generate
```
### Lint
```bash
npx nx run twenty-front:lint # pass --fix to fix lint errors
```
## Traduceri
```bash
npx nx run twenty-front:lingui:extract
npx nx run twenty-front:lingui:compile
```
### Test
```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)
```
## Tehnologii Utilizate
Proiectul are un stack curat și simplu, cu cod boilerplate minim.
**Aplicație**
* [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/)
**Testare**
* [Jest](https://jestjs.io/)
* [Storybook](https://storybook.js.org/)
**Instrumente**
* [Yarn](https://yarnpkg.com/)
* [Craco](https://craco.js.org/docs/)
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
## Arhitectură
### Rutare
[React Router](https://reactrouter.com/) se ocupă de rutare.
Pentru a evita [re-redări](/l/ro/developers/contribute/capabilities/frontend-development/best-practices-front#managing-re-renders) inutile, toată logica de rutare se află în `useEffect` în `PageChangeEffect`.
### Managementul stării
[Jotai](https://jotai.org/) se ocupă de managementul stării.
Consultați [cele mai bune practici](/l/ro/developers/contribute/capabilities/frontend-development/best-practices-front#state-management) pentru mai multe informații despre managementul stării.
## Testare
[Jest](https://jestjs.io/) servește ca instrument pentru testarea unităților, în timp ce [Storybook](https://storybook.js.org/) este pentru testarea componentelor.
Jest este utilizat în principal pentru testarea funcțiilor utilitare, nu a componentelor în sine.
Storybook este pentru testarea comportamentului componentelor izolate, precum și pentru afișarea sistemului de design.

View File

@@ -0,0 +1,178 @@
---
title: Comenzi rapide
---
## Introducere
Atunci când aveți nevoie să ascultați o comandă rapidă, în mod normal ați folosi evenimentul `onKeyDown`.
În `twenty-front` totuși, s-ar putea să aveți conflicte între aceleași comenzi rapide folosite în componente diferite, montate simultan.
De exemplu, dacă aveți o pagină care ascultă tasta Enter, și un modal care ascultă aceeași tastă, cu o componentă Select în interiorul acelui modal care ascultă și ea tasta Enter, s-ar putea să apară un conflict când toate sunt montate simultan.
## Hook-ul `useScopedHotkeys`
Pentru a gestiona această problemă, avem un hook personalizat care face posibilă ascultarea comenzilor rapide fără niciun conflict.
Îl așezați într-o componentă și va asculta comenzile rapide doar când componenta este montată ȘI când **domeniul comenzii rapide** specificat este activ.
## Cum detectezi scurtăturile de la tastatură în practică?
Sunt două etape implicate în configurarea ascultării comenzilor rapide:
1. Setați [domeniul comenzii rapide](#what-is-a-hotkey-scope-) care va asculta comenzi rapide
2. Folosiți `useScopedHotkeys` pentru a asculta comenzile rapide
Setarea domeniilor comenzilor rapide este necesară chiar și în paginile simple, deoarece alte elemente ale UI, cum ar fi meniul din stânga sau meniul de comandă, s-ar putea să asculte și ele comenzi rapide.
## Cazuri de utilizare pentru comenzi rapide
În general, veți avea două cazuri de utilizare care necesită comenzi rapide:
1. Într-o pagină sau componentă montată într-o pagină
2. Într-o componentă de tip modal care preia focusul datorită unei acțiuni a utilizatorului
Al doilea caz de utilizare poate apărea recursiv: un dropdown într-un modal, de exemplu.
### Ascultarea comenzilor rapide într-o pagină
Exemplu:
```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>;
};
```
### Ascultarea comenzilor rapide într-o componentă de tip modal
În acest exemplu vom folosi o componentă modal care ascultă tasta Escape pentru a-i spune părintelui să o închidă.
Aici interacțiunea utilizatorului modifică domeniul.
```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>;
};
```
Apoi în componenta modal:
```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>;
};
```
Este important să folosiți acest tipar când nu sunteți sigur că doar utilizând un useEffect la montare/demontare va fi suficient pentru a evita conflictele.
Aceste conflicte pot fi greu de depanat și s-ar putea să apară mai des decât credeți cu useEffects.
## Ce este un domeniu al comenzilor rapide?
Un domeniu al comenzilor rapide este un șir de caractere care reprezintă un context în care comenzile rapide sunt active. Este în general codificat sub forma unui enum.
Când schimbați domeniul comenzii rapide, comenzile rapide care ascultă acest domeniu vor fi activate, iar cele care ascultă alte domenii vor fi dezactivate.
Puteți seta doar un singur domeniu la un moment dat.
Ca exemplu, domeniile comenzilor rapide pentru fiecare pagină sunt definite în enumul `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',
}
```
Intern, domeniul selectat în prezent este stocat într-un atom Jotai care este partajat în toată aplicația:
```tsx
export const currentHotkeyScopeState = createState<HotkeyScope>({
key: 'currentHotkeyScopeState',
defaultValue: INITIAL_HOTKEYS_SCOPE,
});
```
Însă acest atom nu ar trebui niciodată să fie gestionat manual! Vom vedea cum să o folosim în secțiunea următoare.
## Cum funcționează intern?
Am făcut un wrapper subțire peste [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) care îl face mai performant și evită re-rendările inutile.
De asemenea, creăm un atom Jotai pentru a gestiona starea domeniului comenzilor rapide și pentru a o face disponibilă oriunde în aplicație.

View File

@@ -0,0 +1,8 @@
---
title: Storybook
description: Răsfoiește biblioteca de componente UI Twenty
---
Vizualizați biblioteca noastră completă de componente și documentația în Storybook.
[Deschideți Storybook →](https://storybook.twenty.com)

View File

@@ -0,0 +1,291 @@
---
title: Ghid de stil
icon: paintbrush
---
Acest document include regulile ce trebuie urmate la scrierea codului.
Scopul aici este de a avea o bază de cod coerentă, ușor de citit și ușor de întreținut.
Pentru aceasta, este mai bine să fie mai detaliat decât prea concis.
Ține mereu minte că oamenii citesc codul mai des decât îl scriu, mai ales într-un proiect open-source, unde oricine poate contribui.
Există multe reguli care nu sunt definite aici, dar care sunt verificate automat de linters.
## React
### Folosește componente funcționale
Utilizează întotdeauna componente funcționale TSX.
Nu folosi `import` implicit cu `const`, deoarece este mai greu de citit și mai greu de importat cu completarea de cod.
```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>;
};
```
### Proprietăți
Creează tipul proprietăților și numește-l `(NumeComponentă)Props` dacă nu este necesar să o exporți.
Folosește destructurarea 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>;
```
#### Ferește-te de utilizarea `React.FC` sau `React.FunctionComponent` pentru a defini tipurile props
```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 />
);
```
#### Fără Împrăștierea de Proprietăți cu o singură variabilă în Elemente JSX
Evită utilizarea împrăștierii de proprietăți cu o singură variabilă în elemente JSX, cum ar fi `{...props}`. Această practică adesea duce la cod mai puțin lizibil și mai greu de întreținut deoarece nu este clar ce proprietăți primește componenta.
```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 }} />;
};
```
Raționament:
* La o privire rapidă, este mai evident ce proprietăți sunt transmise de cod, făcându-l mai ușor de înțeles și întreținut.
* Ajută la prevenirea unei cuplări strânse între componente prin proprietățile lor.
* Instrumentele de linting fac mai ușor să identifici proprietăți sprellate greșit sau neutilizate atunci când enumeri explicit proprietățile.
## JavaScript
### Utilizează operatorul de coalescență nulă `??`
```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';
```
### Folosește accesarea opțională `?.`
```tsx
// ❌ Bad
onClick && onClick();
// ✅ Good
onClick?.();
```
## TypeScript
### Utilizați `type` în loc de `interface`
Utilizați întotdeauna `type` în loc de `interface`, deoarece aproape întotdeauna se suprapun, iar `type` este mai flexibil.
```tsx
// ❌ Bad
interface MyInterface {
name: string;
}
// ✅ Good
type MyType = {
name: string;
};
```
### Folosește litere de șir în loc de enum
[Literele de șir](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) sunt metoda standard pentru manipularea valorilor similare cu enum în TypeScript. Sunt mai ușor de extins cu Pick și Omit, oferind o experiență mai bună dezvoltatorilor, mai ales cu completarea codului.
Poți vedea de ce TypeScript recomandă evitarea enumurilor [aici](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 și biblioteci interne
Ar trebui să folosești enumurile generate de GraphQL codegen.
Este de asemenea mai bine să folosești un enum atunci când utilizezi o bibliotecă internă, astfel încât biblioteca internă să nu trebuiască să expose un tip de literă de șir care nu este legată de API-ul intern.
Exemplu:
```TSX
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
setHotkeyScopeAndMemorizePreviousScope(
RelationPickerHotkeyScope.RelationPicker,
);
```
## Stilizare
### Folosește ComponentaStilizată
Stilizează componentele cu [Linaria styled](https://github.com/callstack/linaria).
```tsx
// ❌ Bad
<div className="my-class">Hello World</div>
```
```tsx
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
```
Prefixează componentele stilizate cu „Styled” pentru a le diferenția de componentele „reale”.
```tsx
// ❌ Bad
const Title = styled.div`
color: red;
`;
```
```tsx
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
```
### Tematica
Utilizarea temei pentru majoritatea stilizării componentelor este abordarea preferată.
#### Unități de măsură
Evită utilizarea directă a valorilor `px` sau `rem` în componentele stilizate. Valorile necesare sunt de regulă deja definite în temă, așa că este recomandat să utilizezi tema pentru aceste scopuri.
#### Culori
Abține-te să introduci culori noi; în schimb, folosește paleta existentă din temă. În situația în care paleta nu se potrivește, te rugăm să lași un comentariu pentru ca echipa să poată remedia acest aspect.
```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};
`;
```
## Impunerea Neprecizării Importurilor de Tip
Evită importurile de tip. Pentru a impune acest standard, o regulă Oxlint verifică și raportează orice importuri de tip. Acest lucru ajută la menținerea consistenței și lizibilității în codul 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';
```
### De ce Fără Importuri de Tip
* **Consistență**: Evitând importurile de tip și folosind o singură abordare atât pentru importurile de tip cât și de valoare, baza de cod rămâne consistentă în stilul său de import module.
* **Lizibilitate**: Importurile doar de tip îmbunătățesc lizibilitatea codului, făcând clar când importați valori sau tipuri. Aceasta reduce ambiguitatea și face mai ușor de înțeles scopul simbolurilor importate.
* **Întreținere**: Îmbunătățește întreținerea bazei de cod, deoarece dezvoltatorii pot identifica și localiza importurile doar de tip când revizuiesc sau modifică codul.
### Regula Oxlint
O regulă Oxlint, `typescript/consistent-type-imports`, impune standardul fără importuri de tip. Această regulă va genera erori sau avertismente pentru orice încălcare a importurilor de tip.
Rețineți că această regulă abordează în mod specific cazurile-limită rare în care apar importuri de tip neintenționate. TypeScript descurajează el însuși această practică, așa cum este menționat în notele de lansare [TypeScript 3.8](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html). În majoritatea situațiilor, nu ar trebui să aveți nevoie de importuri doar de tip.
Pentru a vă asigura că codul respectă această regulă, rulați Oxlint ca parte a fluxului de lucru de dezvoltare.

View File

@@ -0,0 +1,61 @@
---
title: Lucrează cu Figma
info: Află cum poți colabora cu Figma de la Twenty.
---
Figma este un instrument de design de interfață colaborativ care ajută la eliminarea barierelor de comunicare între designeri și dezvoltatori.
Acest ghid explică cum poți colabora cu Figma.
## Acces
1. **Accesează linkul partajat:** Poți accesa fișierul Figma al proiectului [aici](https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty).
2. **Autentificare:** Dacă nu ești deja autentificat, Figma te va solicita să faci acest lucru.
Funcțiile principale sunt disponibile doar pentru utilizatorii autentificați, cum ar fi modul dezvoltator și capacitatea de a selecta un cadru dedicat.
<Warning>
Nu vei putea colabora eficient fără un cont.
</Warning>
## Structura Figma
În bara laterală din stânga, poți accesa diferitele pagini din Figma de la Twenty. Acestea sunt organizate astfel:
* **Pagina de componente:** Aceasta este prima pagină. Designerul o folosește pentru a crea și organiza elementele de design reutilizabile folosite în întregul fișier de design. De exemplu, butoane, pictograme, simboluri sau oricare alte componente reutilizabile. Aceasta servește la menținerea consistenței în cadrul designului.
* **Pagina principală:** A doua pagină este pagina principală, care afișează interfața completă a proiectului. Poți apăsa ***Play*** pentru a utiliza prototipul complet al aplicației.
* **Pagini de funcționalitate:** Celelalte pagini sunt de obicei dedicate funcționalităților în curs de dezvoltare. Acestea conțin designul caracteristicilor specifice sau modulelor aplicației sau site-ului web. De obicei, sunt încă în dezvoltare.
## Sfaturi utile
Cu acces doar pentru citire, nu poți edita designul, dar poți accesa toate funcțiile care vor fi utile pentru a converti designurile în cod.
### Folosește Modul Dev
Modul Dev al Figma îmbunătățește productivitatea dezvoltatorilor prin oferirea unei navigări facile în design, gestionarea eficientă a resurselor, instrumente de comunicare eficace, integrări de toolbox, fragmente rapide de cod și informații esențiale despre straturi, reducând decalajul între design și dezvoltare. Poți afla mai multe despre Modul Dev [aici](https://www.figma.com/dev-mode/).
Comută la modul "Developer" în partea dreaptă a barei de instrumente pentru a vedea specificațiile de design, a copia CSS și a accesa resursele.
### Folosește Prototipul
Fă clic pe orice element de pe pânză și apasă butonul “Play” din colțul de sus dreapta al interfeței pentru a accesa vizualizarea prototip. Modul Prototip îți permite să interacționezi cu designul ca și cum ar fi produsul final. Acesta demonstrează fluxul între ecrane și cum elementele de interfață, cum ar fi butoanele, link-urile sau meniurile, se comportă la interacțiune.
1. **Înțelegerea tranzițiilor și animațiilor:** În modul de prototip, poți vizualiza orice tranziții sau animații adăugate de designer între ecrane sau elementele UI, oferind indicații vizuale clare dezvoltatorilor despre comportamentul și stilul dorit.
2. **Clarificarea implementării:** Un prototip poate ajuta, de asemenea, la reducerea ambiguităților. Dezvoltatorii pot interacționa cu acesta pentru a înțelege mai bine funcționalitatea sau aspectul elementelor particulare.
Pentru mai multe detalii și îndrumări despre învățarea platformei Figma, poți vizita [Documentația oficială Figma](https://help.figma.com/hc/en-us).
### Măsoară distanțele
Selectează un element, ține apăsată tasta `Option` (Mac) sau `Alt` (Windows), apoi plasează cursorul peste un alt element pentru a vedea distanța dintre ele.
### Extensia Figma pentru VSCode (Recomandat)
[Figma pentru VS Code](https://marketplace.visualstudio.com/items?itemName=figma.figma-vscode-extension)
îți permite să navighezi și să inspectezi fișierele de design, să colaborezi cu designeri, să urmărești schimbările și să grăbești implementarea - totul fără a părăsi editorul de text.
Face parte din extensiile noastre recomandate.
## Colaborare
1. **Utilizarea comentariilor:** Ești binevenit să folosești funcția de comentarii făcând clic pe pictograma de bule din partea stângă a barei de instrumente.
2. **Chat cu Cursor:** O caracteristică plăcută a Figma este Chatul cu Cursor. Apasă `;` pe Mac și `/` pe Windows pentru a trimite un mesaj dacă vezi pe altcineva folosind Figma în același timp cu tine.

View File

@@ -0,0 +1,318 @@
---
title: Configurare locală
icon: laptop-code
description: Ghidul pentru contribuitori (sau dezvoltatori curioși) care doresc să ruleze Twenty local.
---
## Cerințe
<Tabs>
<Tab title="Linux și macOS">
Înainte de a instala și utiliza Twenty, asigurați-vă că instalați următoarele pe computerul dvs.:
* [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` nu va funcționa, ar trebui să folosiți `yarn` în schimb. Yarn este acum livrat cu Node.js, așa că nu este nevoie să-l instalați separat.
Trebuie doar să rulați `corepack enable` pentru a activa Yarn dacă nu ați făcut acest lucru deja.
</Warning>
</Tab>
<Tab title="Windows (WSL)">
1. Instalați WSL
Deschideți PowerShell ca Administrator și rulați:
```powershell
wsl --install
```
Ar trebui să vedeți acum un mesaj pentru a reporni computerul. Dacă nu, reporniți-l manual.
La repornire, o fereastră PowerShell se va deschide și va instala Ubuntu. Acest lucru poate dura ceva timp.
Veți vedea un mesaj pentru a crea un nume de utilizator și o parolă pentru instalarea Ubuntu.
2. Instalați și configurați git
```bash
sudo apt-get install git
git config --global user.name "Your Name"
git config --global user.email "youremail@domain.com"
```
3. Instalați nvm, node.js și yarn
<Warning>
Folosiți `nvm` pentru a instala versiunea corectă de `node`. Fișierul `.nvmrc` asigură că toți contribuitorii folosesc aceeași versiune.
</Warning>
```bash
sudo apt-get install curl
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
```
Închideți și redeschideți terminalul pentru a utiliza nvm. Apoi rulați următoarele comenzi.
```bash
nvm install # installs recommended node version
nvm use # use recommended node version
corepack enable
```
</Tab>
</Tabs>
---
## Pasul 1: Clonarea Git
Rulați în terminalul dvs. comanda următoare.
<Tabs>
<Tab title="SSH (Recomandat)">
Dacă nu ați configurat deja cheile SSH, puteți învăța cum să faceți acest lucru [aici](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>
## Pasul 2: Poziționați-vă la rădăcină
```bash
cd twenty
```
Trebuie să rulați toate comenzile în pașii următori de la rădăcina proiectului.
## Pasul 3: Configurarea unei baze de date PostgreSQL
<Tabs>
<Tab title="Linux">
**Opțiunea 1 (preferată):** Pentru a configura local baza de date:
Folosiți următorul link pentru a instala PostgreSQL pe mașina dvs. Linux: [Instalare PostgreSQL](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
Notă: Ar putea fi nevoie să adăugați `sudo -u postgres` la comandă înainte de `psql` pentru a evita erorile de permisiune.
**Opțiunea 2:** Dacă aveți docker instalat:
```bash
make -C packages/twenty-docker postgres-on-docker
```
</Tab>
<Tab title="Mac OS">
**Opțiunea 1 (preferată):** Pentru a configura local baza de date cu `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;"
```
Puteți verifica dacă serverul PostgreSQL este activ executând:
```bash
brew services list
```
Instalatorul s-ar putea să nu creeze implicit utilizatorul `postgres` la instalarea
prin Homebrew pe macOS. În schimb, creează un rol PostgreSQL care se potrivește cu numele de utilizator al
macOS-ului dvs. (de ex., "john").
Pentru a verifica și crea utilizatorul `postgres` dacă este necesar, urmați acești pași:
```bash
# Connect to PostgreSQL
psql postgres
or
psql -U $(whoami) -d postgres
```
Odată ce ajungeți la promptul psql (postgres=#), rulați:
```bash
# List existing PostgreSQL roles
\du
```
Veți vedea un rezultat similar cu:
```bash
Role name | Attributes | Member of
-----------+-------------+-----------
john | Superuser | {}
```
Dacă nu vedeți un rol `postgres` listat, mergeți la pasul următor.
Creați rolul `postgres` manual:
```bash
CREATE ROLE postgres WITH SUPERUSER LOGIN;
```
Acest lucru creează un rol de superutilizator numit `postgres` cu drept de autentificare.
```bash
Role name | Attributes | Member of
-----------+-------------+-----------
postgres | Superuser | {}
john | Superuser | {}
```
**Opțiunea 2:** Dacă aveți docker instalat:
```bash
make -C packages/twenty-docker postgres-on-docker
```
</Tab>
<Tab title="Windows (WSL)">
Toți pașii următori trebuie rulați în terminalul WSL (în cadrul mașinii dvs. virtuale)
**Opțiunea 1:** Pentru a configura local PostgreSQL:
Folosiți următorul link pentru a instala PostgreSQL pe mașina dvs. virtuală Linux: [Instalare PostgreSQL](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
Notă: Ar putea fi nevoie să adăugați `sudo -u postgres` la comandă înainte de `psql` pentru a evita erorile de permisiune.
**Opțiunea 2:** Dacă aveți docker instalat:
Rularea Docker pe WSL adaugă un strat suplimentar de complexitate.
Folosiți această opțiune doar dacă sunteți confortabil cu pașii suplimentari implicați, inclusiv activarea [Docker Desktop WSL2](https://docs.docker.com/desktop/wsl).
```bash
make -C packages/twenty-docker postgres-on-docker
```
</Tab>
</Tabs>
Acum puteți accesa baza de date la `localhost:5432`.
Dacă ați folosit opțiunea Docker de mai sus, datele implicite de autentificare sunt utilizatorul `postgres` și parola `postgres`. Pentru instalările native PostgreSQL, folosiți datele de autentificare și rolurile configurate pe mașina dvs.
## Pasul 4: Configurați o bază de date Redis (cache)
Twenty necesită un cache Redis pentru a oferi cea mai bună performanță.
<Tabs>
<Tab title="Linux">
**Opțiunea 1:** Pentru a configura local Redis:
Folosiți următorul link pentru a instala Redis pe mașina dvs. Linux: [Instalare Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/)
**Opțiunea 2:** Dacă aveți docker instalat:
```bash
make -C packages/twenty-docker redis-on-docker
```
</Tab>
<Tab title="Mac OS">
**Opțiunea 1 (preferată):** Pentru a configura local Redis cu `brew`:
```bash
brew install redis
```
Porniți serverul Redis:
```bash
brew services start redis
```
**Opțiunea 2:** Dacă aveți docker instalat:
```bash
make -C packages/twenty-docker redis-on-docker
```
</Tab>
<Tab title="Windows (WSL)">
**Opțiunea 1:** Pentru a configura local Redis:
Folosiți următorul link pentru a instala Redis pe mașina dvs. virtuală Linux: [Instalare Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/)
**Opțiunea 2:** Dacă aveți docker instalat:
```bash
make -C packages/twenty-docker redis-on-docker
```
</Tab>
</Tabs>
Dacă aveți nevoie de o interfață grafică pentru client, vă recomandăm [Redis Insight](https://redis.io/insight/) (versiune gratuită disponibilă).
## Pasul 5: Configurați variabilele de mediu
Utilizați variabile de mediu sau fișiere `.env` pentru a configura proiectul dvs. Mai multe informații [aici](/l/ro/developers/self-host/capabilities/setup).
Copiați fișierele `.env.example` din `/front` și `/server`:
```bash
cp ./packages/twenty-front/.env.example ./packages/twenty-front/.env
cp ./packages/twenty-server/.env.example ./packages/twenty-server/.env
```
<Info>
**Modul Multi-Workspace:** În mod implicit, Twenty rulează în modul cu un singur spațiu de lucru, în care poate fi creat un singur spațiu de lucru. Pentru a activa suportul multi-spațiu de lucru (util pentru testarea funcționalităților bazate pe subdomenii), setați `IS_MULTIWORKSPACE_ENABLED=true` în fișierul `.env` al serverului. Consultați [Modul Multi-Workspace](/l/ro/developers/self-host/capabilities/setup#multi-workspace-mode) pentru detalii.
</Info>
## Pasul 6: Instalarea dependențelor
Pentru a construi serverul Twenty și a adăuga date în baza dvs. de date, rulați următoarea comandă:
```bash
yarn
```
Rețineți că `npm` sau `pnpm` nu vor funcționa
## Pasul 7: Rularea proiectului
<Tabs>
<Tab title="Linux">
În funcție de distribuția Linux pe care o folosiți, serverul Redis s-ar putea să fie pornit automat.
Dacă nu, verificați [ghidul de instalare Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/) pentru distribuția dvs.
</Tab>
<Tab title="Mac OS">
Redis ar trebui să fie deja pornit. Dacă nu, rulați:
```bash
brew services start redis
```
</Tab>
<Tab title="Windows (WSL)">
În funcție de distribuția Linux pe care o folosiți, serverul Redis s-ar putea să fie pornit automat.
Dacă nu, verificați [ghidul de instalare Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/) pentru distribuția dvs.
</Tab>
</Tabs>
Setați baza de date cu următoarea comandă:
```bash
npx nx database:reset twenty-server
```
Porniți serverul, worker-ul și serviciile frontend:
```bash
npx nx start twenty-server
npx nx worker twenty-server
npx nx start twenty-front
```
Alternativ, puteți porni toate serviciile odată:
```bash
npx nx start
```
## Pasul 8: Utilizați Twenty
**Front-end**
Frontend-ul Twenty va rula la [http://localhost:3001](http://localhost:3001).
Vă puteți loga folosind contul demo implicit: `tim@apple.dev` (parolă: `tim@apple.dev`)
**Back-end**
* Serverul Twenty va fi operativ la [http://localhost:3000](http://localhost:3000)
* API-ul GraphQL poate fi accesat la [http://localhost:3000/graphql](http://localhost:3000/graphql)
* API-ul REST poate fi accesat la [http://localhost:3000/rest](http://localhost:3000/rest)
## Depanare
Dacă întâmpinați vreo problemă, verificați [Depanare](/l/ro/developers/self-host/capabilities/troubleshooting) pentru soluții.