arrow-left

Only this pageAll pages
gitbookPowered by GitBook
1 of 29

latest

Loading...

Dev Guidelines

Loading...

Loading...

Loading...

Loading...

Loading...

Architecture

Backend

Frontend

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Infrastructure

Loading...

REST API

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

GRAPHQL

Loading...

Pull requests guidelines

Wondering how to write a good PR up to the organisation's standards?

Pull requests are official record of all changes we make. As an open source organisation PRs might be read from multiple people and not only the assigned reviewers. Future contributors are likely to search and come back to past PRs for a variety of reasons, therefore, it is vital that the important changes are not only in the code but also included in the description. Always think about the reviewers and potential future readers!

hashtag
Think twice about the title and description before submitting for review

The title is what usually appears in the version control. Thus, it must be a full, clear and imperative sentence.

Bad title example:

  • Deleting API endpoint and adding new one- not specific enough, not imperative, bad wording.

Good title example:

  • Delete the /foo/bar REST endpoint and replace it with /foo/baz

The latter example describes the specific change made, improves the searchability and gives enough information to the reader which allows them to skim through the changes rather than spending more time going through the rest of the description.

Keep the body as informative as possible. Start with outlining the problem you are trying to solve/the feature you want to add.

All readers must get a perfect impression of what the change is and reason/motivation behind it. Think about any attachments that might be useful. Add screenshots if the changeset leads to visual difference in the frontend, add any external link/references to any design docs that might help the reader to better understand context of/changes in the PR. However, try to keep those to a minimal and outline as much as possible in the written description as any external resources can easily be deleted in the future or problems with access permissions might occur.

Do not skip any important information. Outline with clarity the alternative solutions and the reasons for avoiding those. Similarly, it is vital to explain any shortcomings of the approach that you took to solving the problem, what can possibly go wrong in the future and how we can possibly overcome those issues if you can think of any.

hashtag
Keep the changeset as small as possible

10 lines of code = 10 issues, 500 lines of code = LGTM!... code reviews in a nutshell...

Your PR changeset size should be just right, i.e. one self-contained change per PR! Furthermore, you should strive to limit your changeset to 250-300 lines of code where possible (, ). This is beneficial for both the reviewers and authors. As a reviewer you will be able to go through the changes much quicker and provide a better feedback as the quantity of changes you are reviewing is less and the amount of context needed is proportional to the size. Vice-versa, as an author you will get good feedback fast and hoping for less potentially introduced bugs. Moreover, changes you will be required to make upon receiving the feedback would be less and easier to do, i.e. you will have to do less work if something is rejected. Small PRs are also easier to design and the mergeability and simplicity of rolling back the changes increase substantially.

hashtag
How to introduce smaller PRs?

hashtag
Break down your issues!

For example: you want to add a new feature for creating a product, your changes will include a new public API endpoint and its backend implementation. Instead of dumping all of your changes in a massive PR, you can break down your issue similarly to:

  • A differential with any Database schema changes required.

  • A differential with any protobuf changes required.

  • Initial commit with stubs for the backend.

Stacking PRs greatly improves the efficiency of the above process, you can see more examples how to this .

Tests for new functionality must go in the same PR as the functional changes, do not try to reduce the changeset size by splitting unit tests from functional changes.

Do not mix changes - refactoring, bug fixing and enhancements must not go in the same differential.

hashtag
Large PRs

It is inevitable that sometimes larger PRs will be needed - when refactoring code, code added by automation, deleting files etc. This is always fine as long as you have considered all options how to avoid the massive change but nothing seems to help.

hashtag
Do not break the builds!

The system's functionality must always be intact after merging the changes. We understand it is impossible for this rule not to be broken ever. But, introducing PRs that intentionally add a breaking change (even for a short period of time until your subsequent PR is merged) is unacceptable! There are a number of ways to avoid those and if unsure, ask your friendly community members for how to approach the problem.

hashtag
Sources

Branching strategy

The general approach to branching used within the organisation.

Inherits the process from https://nvie.com/posts/a-successful-git-branching-model/arrow-up-right​‌

​​🌞 Good branch names:‌

  • nice-kebab-cased-titles

  • fixes-footer-links

  • 4411290-setup-state-management-integration

  • feature/new-design

  • hotfix/db-connection

  • release-1.2.3

​​⛈ Bad branch names:‌

  • patch-1 - not enough context

  • camelCasedBranchNames - camelcase

  • PascalCasedBranchNames

A series of differentials containing the backend implementation and respective unit tests.
  • A differential with any changes required to the public API.

  • sourcearrow-up-right
    sourcearrow-up-right
    herearrow-up-right
    The (written) unwritten guide to pull requestsarrow-up-right
    Optimal pull request sizearrow-up-right
    Best Practices for Code Reviewarrow-up-right
    The anatomy of a perfect pull requestarrow-up-right
    Google's Engineering Practices documentationarrow-up-right
    - pascal case
  • long-titles-above-80-chars-{.....} - too long

  • #58/something - shell understands it as comment

  • Branching model

    Merges

    ​​​

    ​​​

    Endpoints

    Starting a task

    Guidelines

    Schema

    Local Development

    hashtag
    Local development

    hashtag
    Setup local dev environment

    hashtag
    Start development server

    Visit

    hashtag
    Start dev server via Docker Compose

    Install the binary via

    hashtag
    Start the container in foreground

    hashtag
    Start the container in background

    Stop the docker container with docker-compose down

    hashtag
    Linting

    Introduction

    Dev docs for the Podkrepi.bg organisation.

    hashtag
    Who is this documentation oriented for?

    Our documentation is publicly accessible for everyone to read and contribute to. All current and future contributors are encouraged to read the Dev Guidelines section as a starting point.

    Production

    hashtag
    Production environment

    hashtag
    Build frontend

    git clone [email protected]:daritelska-platforma/frontend.git
    cd frontend
    
    # Symlink dev environment
    ln -s .env.example .env
    
    # Install dependencies
    yarn
    hashtag
    Weekly dev meetings

    Our weekly development meeting takes place every Saturday between 6pm - 7:30pm EEST (UTC+3) in the discord server of the organisation. All issues regarding development can be discussed during these meetings and everyone is welcome to join!

    Authentication

    http://localhost:3040/arrow-up-right
    https://docs.docker.com/compose/install/arrow-up-right
    hashtag
    Build Docker image
    yarn build
    docker build . \
        --file ./Dockerfile \
        --target production \
        --build-arg NODE_ENV=production
    yarn dev
    docker-compose up
    docker-compose up -d
    docker-compose logs -f
    yarn lint
    yarn lint:styles
    yarn format
    yarn type-check

    Campaigns

    hashtag
    Get Campaigns

    GET https://api.podkrepi.bg/v1/campaigns

    This endpoint allows you to get campaigns.

    Healthcheck

    hashtag
    Health check

    GET https://api.podkrepi.localhost/api/v1/healthcheck

    {
      "status": 200
    }
    {
      "status": 500
    }

    Environments

    .env

    ## General ##	
    #############	
    
    COMPOSE_PROJECT_NAME=podkrepi	
    NODE_ENV=development # development, production	
    TARGET_ENV=development # development, production	
    
    ## API ##	
    #########	
    
    API_URL=https://api.podkrepi.localhost/	
    
    ## APP ##	
    #########	
    
    APP_URL=http://localhost:3040	
    APP_PORT=3040	
    
    ## Next Auth ##	
    #############	
    
    NEXTAUTH_URL=https://http://localhost:3040	
    JWT_SECRET=!Change__Me!	
    
    ## Discord ##	
    #############	
    
    DISCORD_CLIENT_ID=	
    DISCORD_CLIENT_SECRET=

    hashtag
    Headers
    Name
    Type
    Description

    Authentication

    string

    Authentication token to track down who is emptying our stocks.

    [
        {
            "id": "
    
    {
        "error": "Not Found"
    

    hashtag
    Get Campaign by ID

    GET https://api.podkrepi.bg/v1/campaigns/:id

    This endpoint allows you to get campaigns.

    hashtag
    Path Parameters

    Name
    Type
    Description

    id

    string

    ID of the cake to get, for free of course.

    hashtag
    Headers

    Name
    Type
    Description

    Authentication

    string

    Authentication token to track down who is emptying our stocks.

    Recognising Contributions

    We're integrated with bot

    Comment on , asking @all-contributors bot to add a contributor:

    <contribution>: See the for a list of valid contribution types.

    {
        "id": "5572f770-a434-4ed8-9a91-d94474d26c8e"
        "name": "Campaign name"
    }
    {
        "error": "Not Found"
    }
    5572f770-a434-4ed8-9a91-d94474d26c8e
    "
    "name": "Campaign name"
    },
    {
    "id": "97692474-2482-460f-86ec-2b84954f989b"
    "name": "Campaign name"
    }
    ]
    }
    @all-contributors please add @<username> for <contributions>
    https://allcontributors.org/arrow-up-right
    this issuearrow-up-right
    Emoji Key (Contribution Types Reference)arrow-up-right

    Forms

    hashtag
    Form definition

    hashtag
    Form usage

    hashtag
    Validation

    hashtag
    Default translations

    Simple strings are mapped directly to their respective translation

    hashtag
    Default translations with interpolation

    Complex translation keys are being evaluated upon translation

    hashtag
    Custom translations in validation schema

    Commonly used translations with the same translation key

    hashtag
    Inline translations in validation schema

    Custom translations with keys defined right next to the form

    Translations (i18n)

    hashtag
    Translation namespaces

    Default namespace is called common and contains translations used on all pages (Layout, Nav, etc) and is stored at frontend/public/locales/{locale}/common.json

    Namespaces (scopes, domains) are stored in separate json files at frontend/public/locales/{locale}/{namespace}.json One namespace can combine the translations keys from several pages with common reusable strings ex. auth scope collects keys for login and register pages.

    hashtag
    Translation keys

    It is preferred to use for translation keys and extract another level of nesting when the common prefix of the keys is above 3 or makes sense to be separated as new keys might be added in the future.

    • Namespace is separated with :

    • Translation nesting levels are separated with .

    • Words in a translation key are separated with -

    domain:pages.nested-level.another-nested-level.translation-key

    hashtag
    Usage in React

    Usage of translation hook useTranslation is preferred over usage of <Trans /> component, whenever possible.

    hashtag
    Usage in components

    hashtag
    SSR preloading i18n in pages

    hashtag

    Deployment to Kubernetes

    hashtag
    Prerequisites

    • Install kubectl https://kubernetes.io/docs/tasks/tools/arrow-up-right

    • Install kustomize

    hashtag
    Frontend

    hashtag
    Staging

    You may trigger staging release at any point of time from the master branch by:

    This will update the deployment using ghcr.io/podkrepi-bg/frontend:master image

    hashtag
    Production

    hashtag
    Create new release

    The following command will:

    • Bump the version in package.json

    • Tag the latest master branch

    • [postversion

    hashtag
    Apply manifests

    Once the image has been built by the GitHub Actions and is present in the Docker image repository you may trigger the actual deployment to the cluster.

    hashtag
    Backend

    hashtag
    Staging

    You may trigger staging release at any point of time from the master branch by:

    This will update the deployment using ghcr.io/podkrepi-bg/api:master image

    hashtag
    Production

    hashtag
    Create new release

    The following command will:

    • Bump the version in package.json

    • Tag the latest master branch

    • [postversion

    hashtag
    Apply manifests

    Once the image has been built by the GitHub Actions and is present in the Docker image repository you may trigger the actual deployment to the cluster.

    hashtag
    Manual deployment

    If you want to set a specific version for the deployment image you can do that by editing backend/manifests/overlays/production/kustomization.yaml

    import React from 'react'
    import * as yup from 'yup'
    import { useTranslation } from 'next-i18next'
    import { Grid, TextField, Button } from '@material-ui/core'
    
    import { AlertStore } from 'stores/AlertStore'
    import useForm, { translateError, customValidators } from 'common/form/useForm'
    
    export type FormData = {
      email: string
    }
    
    const validationSchema: yup.SchemaOf<FormData> = yup.object().defined().shape({
      email: yup.string().email().required(),
    })
    
    const defaults: FormData = {
      email: '',
    }
    
    export type MyFormProps = { initialValues?: FormData }
    
    export default function MyForm({ initialValues = defaults }: MyFormProps) {
      const { t } = useTranslation()
    
      const { formik } = useForm({
        initialValues,
        validationSchema,
        onSubmit: (values) => {
          console.log(values)
        },
      })
    
      return (
        <form onSubmit={formik.handleSubmit}>
          <Grid container spacing={3}>
            <Grid item xs={12}>
              <TextField
                type="text"
                fullWidth
                label={t('auth:fields.email')}
                name="email"
                size="small"
                variant="outlined"
                autoFocus
                error={Boolean(formik.errors.email)}
                helperText={translateError(formik.errors.email)}
                value={formik.values.email}
                onBlur={formik.handleBlur}
                onChange={formik.handleChange}
              />
            </Grid>
            <Grid item xs={12}>
              <Button fullWidth type="submit" color="primary" variant="contained">
                {t('auth:cta.login')}
              </Button>
            </Grid>
          </Grid>
        </form>
      )
    }
    <MyForm />
    
    <MyForm initailValues={{email: '[email protected]'}} />
    kebab-casearrow-up-right
    ] Push local tags to the remote origin
  • Update the image newTag version in frontend/manifests/overlays/production/kustomization.yaml

  • Commit and push the manifest update

  • ] Push local tags to the remote origin
  • Update the image newTag version in backend/manifests/overlays/production/kustomization.yaml

  • Commit and push the manifest update

  • https://kubectl.docs.kubernetes.io/installation/kustomize/arrow-up-right
    https://github.com/orgs/podkrepi-bg/packages/container/package/frontendarrow-up-right
    https://github.com/orgs/podkrepi-bg/packages/container/package/apiarrow-up-right
    {
      "invalid": "Field is invalid",
      "required": "Required field"
    }
    setLocale({
      mixed: {
        default: 'validation:invalid',
        required: 'validation:required',
      },
      string: {
        email: 'validation:email',
      },
    })
    {
      "field-too-short": "Field should be at least {{min}} symbols",
      "field-too-long": "Field should be maximum {{max}} symbols"
    }
    setLocale({
      string: {
        min: ({ min }: { min: number }) => ({
          key: 'validation:field-too-short',
          values: { min },
        }),
        max: ({ max }: { max: number }) => ({
          key: 'validation:field-too-long',
          values: { max },
        }),
      },
    })
    yup.string().min(6 customValidators.passwordMin)
    const validationSchema: yup.SchemaOf<FormData> = yup
      .object()
      .defined()
      .shape({
        password: yup.string().min(6, ({ min }) => ({
          key: 'validation:password-min',
          values: { min },
        })),
      })
    {
      "cta": {
        "login": "Log In",
        "register": "Register",
        "send": "Send",
        "reset": "Reset"
      },
      "fields": {
        "email": "Email",
        "password": "Password",
        "confirm-password": "Confirm Password",
        "first-name": "First name",
        "last-name": "Last name"
      },
      "pages": {
        "forgotten-password": {
          "instructions": "To reset your password, please type your email address below.",
          "greeting": "Hello {{name}}!"
        }
      }
    }
    import { useTranslation } from 'next-i18next'
    
    export default function CustomComponent() {
      const { t } = uxseTranslation()
    
      return (
        <div>
          <h1>{t('nav.custom-page')}</h1>
          <h2>{t('auth:pages.forgotten-password.greeting', { name: 'Interpolation' })}</h2>
          <p>{t('auth:pages.forgotten-password.instructions')}</p>
        </div>
      )
    }
    import { GetStaticProps } from 'next'
    import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
    
    import Page from 'components/forgottenPassword/ForgottenPasswordPage'
    
    export const getStaticProps: GetStaticProps = async ({ locale }) => ({
      props: {
        ...(await serverSideTranslations(locale ?? 'bg', ['common', 'auth'])), // List used namespaces
      },
    })
    
    export default Page
    cd podkrepi.bg/frontend
    kubectl apply -k manifests/overlays/development
    cd podkrepi.bg/frontend
    kubectl apply -k manifests/overlays/production
    cd podkrepi.bg/frontend
    kubectl apply -k manifests/overlays/production
    cd podkrepi.bg/backend
    kubectl apply -k manifests/overlays/development
    cd podkrepi.bg/backend
    kubectl apply -k manifests/overlays/production
    cd podkrepi.bg/backend
    kubectl apply -k manifests/overlays/production
    images:
    - name: ghcr.io/podkrepi-bg/api/migrations
      newTag: v0.3.3
    - name: ghcr.io/podkrepi-bg/api
      newTag: v0.3.3

    Contact Request

    hashtag
    Create contact request

    POST https://api.podkrepi.localhost/api/v1/contact

    hashtag
    Request Body

    Name
    Type
    Description

    hashtag
    Fetch contact requests

    GET https://api.podkrepi.localhost/api/v1/contact

    This endpoint allows you to get free cakes.

    hashtag
    Headers

    Name
    Type
    Description

    hashtag
    Fetch specific contact request

    GET https://api.podkrepi.localhost/api/v1/contact/:id

    hashtag
    Path Parameters

    Name
    Type
    Description

    hashtag
    Headers

    Name
    Type
    Description

    hashtag
    Delete contact request

    DELETE https://api.podkrepi.localhost/api/v1/contact/:id

    hashtag
    Path Parameters

    Name
    Type
    Description

    hashtag
    Headers

    Name
    Type
    Description

    string

    message

    string

    firstName

    string

    lastName

    string

    email

    string

    phone

    string

    Authentication

    string

    Authentication token to track down who is emptying our stocks.

    ID

    string

    Authentication

    string

    id

    string

    Authentication

    string

    {
      "id": "112cb853-b0b9-482e-a944-cf4fca5566b7",
      "firstName": "John",
      "lastName": "Doe",
      "email": "[email protected]",
      "company": "Camplight",
      "phone": "+359888888888",
      "message": "I wanna help",
      "createdAt": "2021-03-19T00:36:50.076597738Z",
      "updatedAt": "2021-03-19T00:36:50.076597738Z",
      "deletedAt": null
    }
    {
      "statusCode": 400,
      "error": "email: non zero value required;firstName: non zero value required;lastName: non zero value required;message: non zero value required;phone: non zero value required",
      "validation": [
        {
          "field": "firstName",
          "message": "firstName: non zero value required",
          "validator": "required",
          "customMessage": false
        },
        {
          "field": "lastName",
          "message": "lastName: non zero value required",
          "validator": "required",
          "customMessage": false
        },
        {
          "field": "email",
          "message": "email: non zero value required",
          "validator": "required",
          "customMessage": false
        },
        {
          "field": "phone",
          "message": "phone: non zero value required",
          "validator": "required",
          "customMessage": false
        },
        {
          "field": "message",
          "message": "message: non zero value required",
          "validator": "required",
          "customMessage": false
        }
      ]
    }

    company

    [
      {
        "id": "0d6ff16e-ae4c-48e4-868c-05095d923053",
        "firstName": "John",
        "lastName": "Doe",
        "email": "[email protected]",
        "company": "Camplight",
        "phone": "",
        "message": "I wanna help",
        "createdAt": "2021-03-19T00:11:21.031966Z",
        "updatedAt": "2021-03-19T00:11:21.031966Z",
        "deletedAt": null
      },
      {
        "id": "76e6e2f7-b3f2-4df4-9185-cc44185e7c86",
        "firstName": "John",
        "lastName": "Doe",
        "email": "[email protected]",
        "company": "ACME",
        "phone": "+359888888888",
        "message": "I wanna help",
        "createdAt": "2021-03-19T00:20:31.355736Z",
        "updatedAt": "2021-03-19T00:20:31.355736Z",
        "deletedAt": null
      }
    ]
    {
      "id": "112cb853-b0b9-482e-a944-cf4fca5566b7",
      "firstName": "John",
      "lastName": "Doe",
      "email": "[email protected]",
      "company": "Camplight",
      "phone": "+359888888888",
      "message": "I wanna help",
      "createdAt": "2021-03-19T00:36:50.076597738Z",
      "updatedAt": "2021-03-19T00:36:50.076597738Z",
      "deletedAt": null
    }
    {
      "error": "No contact found",
      "status": 404
    }
    {
      "status": 200
    }
    {
      "status": 404
    }

    Support Request

    hashtag
    Create support request

    POST https://api.podkrepi.localhost/api/v1/support-request

    hashtag
    Request Body

    Name
    Type
    Description

    hashtag
    Fetch support requests

    GET https://api.podkrepi.localhost/api/v1/support-request

    This endpoint allows you to get free cakes.

    hashtag
    Headers

    Name
    Type
    Description

    hashtag
    Fetch specific support request

    GET https://api.podkrepi.localhost/api/v1/support-request/:id

    hashtag
    Path Parameters

    Name
    Type
    Description

    hashtag
    Headers

    Name
    Type
    Description

    hashtag
    Delete support request

    DELETE https://api.podkrepi.localhost/api/v1/support-request/:id

    hashtag
    Path Parameters

    Name
    Type
    Description

    hashtag
    Headers

    Name
    Type
    Description

    person

    object

    support_data

    object

    Authentication

    string

    Authentication token to track down who is emptying our stocks.

    ID

    string

    Authentication

    string

    id

    string

    Authentication

    string

    {
      "id": "d1451c92-0e86-463a-bf1c-2e4554a77f30",
      "person": {
        "email": "[email protected]",
        "name": "John Doe",
        "phone": "+3598777777777",
        "address": "6 John Doe Str",
        "terms": true,
        "newsletter": true
      },
      "support_data": {
        "roles": {
          "benefactor": true,
          "partner": true,
          "associationMember": false,
          "promoter": false,
          "volunteer": true
        },
        "benefactor": {
          "campaignBenefactor": true,
          "platformBenefactor": false
        },
        "partner": {
          "npo": false,
          "bussiness": true,
          "other": false,
          "otherText": "aaaaa"
        },
        "volunteer": {
          "backend": false,
          "frontend": true,
          "marketing": false,
          "designer": true,
          "projectManager": false,
          "devOps": true,
          "financesAndAccounts": true,
          "lawyer": false,
          "qa": false
        },
        "associationMember": {
          "isMember": true
        },
        "promoter": {
          "mediaPartner": false,
          "ambassador": false,
          "other": true,
          "otherText": "bbbbb"
        }
      },
      "createdAt": "2021-04-07T12:57:09.42898025Z",
      "updatedAt": "2021-04-07T12:57:09.42898025Z",
      "deletedAt": null
    }
    {
      "statusCode": 400,
      "error": "Person.email: non zero value required;Person.name: non zero value required;Person.phone: non zero value required",
      "validation": [
        {
          "field": "Person.email",
          "message": "Person.email: non zero value required",
          "validator": "required",
          "customMessage": false
        },
        {
          "field": "Person.name",
          "message": "Person.name: non zero value required",
          "validator": "required",
          "customMessage": false
        },
        {
          "field": "Person.phone",
          "message": "Person.phone: non zero value required",
          "validator": "required",
          "customMessage": false
        }
      ]
    }
    [
      {
        "id": "17222da9-b550-4e82-a6c6-88100bf54675",
        "person": {
          "email": "",
          "name": "",
          "phone": "",
          "address": "",
          "terms": false,
          "newsletter": false
        },
        "support_data": {
          "roles": {
            "benefactor": false,
            "partner": false,
            "associationMember": false,
            "promoter": false,
            "volunteer": false
          },
          "benefactor": {
            "campaignBenefactor": false,
            "platformBenefactor": false
          },
          "partner": {
            "npo": false,
            "bussiness": false,
            "other": false,
            "otherText": ""
          },
          "volunteer": {
            "backend": false,
            "frontend": false,
            "marketing": false,
            "designer": false,
            "projectManager": false,
            "devOps": false,
            "financesAndAccounts": false,
            "lawyer": false,
            "qa": false
          },
          "associationMember": {
            "isMember": false
          },
          "promoter": {
            "mediaPartner": false,
            "ambassador": false,
            "other": false,
            "otherText": ""
          }
        },
        "createdAt": "2021-04-07T09:16:28.026936Z",
        "updatedAt": "2021-04-07T09:16:28.026936Z",
        "deletedAt": null
      }
    ]
    {
      "id": "17222da9-b550-4e82-a6c6-88100bf54675",
      "person": {
        "email": "",
        "name": "",
        "phone": "",
        "address": "",
        "terms": false,
        "newsletter": false
      },
      "support_data": {
        "roles": {
          "benefactor": false,
          "partner": false,
          "associationMember": false,
          "promoter": false,
          "volunteer": false
        },
        "benefactor": {
          "campaignBenefactor": false,
          "platformBenefactor": false
        },
        "partner": {
          "npo": false,
          "bussiness": false,
          "other": false,
          "otherText": ""
        },
        "volunteer": {
          "backend": false,
          "frontend": false,
          "marketing": false,
          "designer": false,
          "projectManager": false,
          "devOps": false,
          "financesAndAccounts": false,
          "lawyer": false,
          "qa": false
        },
        "associationMember": {
          "isMember": false
        },
        "promoter": {
          "mediaPartner": false,
          "ambassador": false,
          "other": false,
          "otherText": ""
        }
      },
      "createdAt": "2021-04-07T09:16:28.026936Z",
      "updatedAt": "2021-04-07T09:16:28.026936Z",
      "deletedAt": null
    }
    {
      "error": "No support request found",
      "status": 404
    }
    {
      "status": 200
    }
    {
      "status": 404
    }

    React Guidelines

    hashtag
    Imports

    A common way to sort the imports in the file is by their source: external, absolute, relative separated by an empty line. Each of those groups can be sorted by line length, but that's not super important.

    hashtag
    File structure

    Inherits AirBnb naming convention

    Use PascalCase for React components and camelCase for their instances

    hashtag
    Naming convention react components

    Pascal cased file names src/components/GenericForm.tsx

    Filename and default component of the file should have the same name.

    hashtag
    Naming convention non react components

    Camel cased file names src/utils/hooks/useUser.ts

    hashtag
    Naming convention folders

    Lowercase kebab cased folders src/components/common/password-reset/ResetForm.tsx

    hashtag
    Naming convention pages

    Lowercase kebab cased files located in src/pages/sample-page.tsx which correspond to /sample-page url.

    hashtag
    Types

    The common convention is that the main type of the component's props is called after the component itself with suffix -Props. Prop types of AdvancedForm becomes AdvancedFormProps.

    hashtag
    Components

    hashtag
    Preferred export style 🌞

    • Nice IDE support and readability

    hashtag
    Alternative export styles

    • Named function

      ⛅ Allows attaching static props to the function

    • Const arrow function

      🌞 Nice for locally defined components

    hashtag
    Styles

    There are three common ways to style a component:

    hashtag
    Styles using the

    Single component that inherits all sizing props from MUI

    🌞 Nice for quick layouts that should follow the theme

    ⛅Not the best for custom scenarios with more than _six props passed to it. Use hooks instead

    ⛅ Not nice when the children have clear nesting structure of more than _three levels. Use hooks or scss instead

    hashtag
    Styles using useStyles() hook

    🌞 Nice for very specific styling that leverages theme methods and props

    ⛅ Too verbose for simple use cases, if it contains less than 2 css rules. Use Box instead

    ⛅ Not the best when dealing with styling of deep nested structures within the same component. Use scss instead

    hashtag
    Styles using SCSS files

    Next.js supports out of the box. Read more at

    File convention is based on a suffix .module.scss (ex. about.module.scss)

    🌞 Nice when dealing with complex nested structures that are scoped in a single component. When dealing with sub-components we're not sure if some of the rules will be left unused.

    ⛅ Too verbose for simple use cases, if it contains less than 2 css rules in a dedicated file. Use Box instead

    ⛈️ Cannot use theme support or theme variables Use hook instead

    import React, { useState } from 'react'
    import { useTranslation } from 'next-i18next'
    
    import Nav from 'components/layout/Nav'
    import Layout from 'components/layout/Layout'
    
    import SimpleForm from './SimpleForm'
    import styles from './advanced.module.scss'
    ⛅ Okay for default exports, but not preferred
  • Unnamed arrow function ⛈️

    Discouraged

    https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/display-name.mdarrow-up-right

  • Class components ⛈️

    Discouraged as hooks cannot be used inside the class components

  • https://github.com/airbnb/javascript/tree/master/react#namingarrow-up-right
    <Box /> componentarrow-up-right
    https://material-ui.com/system/basics/#all-inclusivearrow-up-right
    SCSSarrow-up-right
    https://nextjs.org/docs/basic-features/built-in-css-support#sass-supportarrow-up-right
    export default () => <div>page</div>
    class Page extends React.Component {
      render() {
        return <div>page</div>
      }
    }
    export default function GenericForm() {}
    type AdvancedFormProps = React.PropsWithChildren({
      title?: string
      age?: number
    })
    
    export default function AdvancedForm({ title = 'Nice', children, age }: AdvancedFormProps) {
      return (
        <div title={title} data-age={age}>
          {children}
        </div>
      )
    }
    export default function RegisterPage() {
      return <div>page</div>
    }
    function RegisterPage() {
      return <div>page</div>
    }
    
    Register.getInitialProps = async (ctx) => {
      return { stars: 128 }
    }
    
    export default RegisterPage
    const RegisterForm = () => <form>page</form>
    
    export default function RegisterPage() {
      return <RegisterForm />
    }
    <Box component="nav" px={5} mt={2}>
      <a>{t('nav.forgottenPassword')}</p>
    </Box>
    <Box component="nav" px={5} pb={12} mt={2} mb={4} lineHeight={2} letterSpacing={none} fontSize={20}>
      <Box component="span" px={5} pb={12} mt={2} mb={4} lineHeight={2} letterSpacing={none} fontSize={17}>
        <a>{t('nav.forgottenPassword')}</p>
      </Box>
      <Box component="span" px={5} pb={12} mt={2} mb={4} lineHeight={2} letterSpacing={none} fontSize={13}>
        <a>{t('nav.forgottenPassword')}</p>
      </Box>
    </Box>
    const useStyles = makeStyles((theme) =>
      createStyles({
        pageTitle: {
          display: 'flex',
          flexDirection: 'column',
          alignItems: 'center',
          padding: theme.spacing(4),
          margin: theme.spacing(5, 3, 4),
          color: theme.palette.secondary.main,
          backgroundColor: theme.palette.primary.main,
          '&:hover': {
            color: theme.palette.secondary.dark,
          },
        },
        // ...
      }),
    )
    
    export default function SomeBox() {
      const classes = useStyles()
      return (
        <Box className={classes.pageTitle}>
          <p>{t('nav.forgottenPassword')}</p>
        </Box>
      )
    }
    @import 'styles/variables';
    
    .page {
      color: $text-color;
    
      .nav {
        background-color: $nav-color;
    
        a {
          text-decoration: none;
          text-transform: uppercase;
        }
      }
    }
    import styles from './about.module.scss'
    
    <Box className={styles.page}>
      <p>{t('nav.forgottenPassword')}</p>
    </Box>
    @import 'styles/variables';
    
    a {
      text-decoration: none;
    }
    const RegisterPage = () => <form>page</form>
    
    export default RegisterPage

    Definition of done

    An unwritten contract signed by everyone in the organisation.

    We use the definition of done to asses whether or not an enhancement can be considered to be complete (shippable to production).

    hashtag
    Do not mix DoD with acceptance criteria.

    DoD is our general framework to derive and end-to-end process which ensures every common piece of work meets the organisation's quality standards. Whereas, AC is a set of strictly defined targets for each individual (functional) piece of work that must be met before a task is considered to be complete.

    hashtag
    Why is DoD important to us?

    It is vital that every contributor in our organisation is aware of and understands our DoD. This helps us:

    • Complete all tasks without introducing any gaps in the process.

    • Follow with clarity where we are on our roadmap and what has been completed.

    • Know what is expected from every contributor.

    hashtag
    Framework

    The following is a general approach towards DoD, each team is encouraged to derive their own process suitable for the team's functionality in the organisation.

    hashtag
    Development

    1. Code and unit tests for functionality completed.

    2. Assumption of meeting Acceptance Criteria.

    3. Premerge [if applicable] tests and code reviews passed.

    hashtag
    Release

    1. All features included in the release are marked as completed. Where a feature is not yet completed, the feature is postponed for the next release.

    2. Update changelog and release documentation where applicable.

    3. Current environment is "green" - all unit, functional, integration, E2E and other possible tests are passing.

    Meet a certain quality of our work which leads to:
    • Lowering the amount of time spend for reworks.

    • Delivering on our promise to create a platform which makes a difference in our society.

    Postmerge [if applicable] tests passed.
  • Project builds with no failures.

  • Project deployed to dev environment.

  • Initial QA performed by the developer. [if this step fails, go back to step 1]

  • Feature documentation [if applicable] and tests documentation [if applicable] written.

  • Feature QA'ed by a QA engineer against the acceptance criteria. [if this step fails, go back to step 1]

  • Official documentation updated.

  • Feature marked as completed.

  • Release acceptance criteria is met. [If this step fails, go back to step 1]

  • QA functionality of the platform. [If this step fails, go back to step 1]

  • QA if any experimental features are exposed to the end user. [If this step fails, go back to step 1]

  • Promote the release to the next environment in the pipeline.

  • Release successfully deployed to new environment. [If this step fails, go back to step 1]

  • Release marked as completed.