Only this pageAll pages
Powered 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...

Introduction

Dev docs for the Podkrepi.bg organisation.

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.

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!

Branching strategy

The general approach to branching used within the organisation.

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

​​🌞 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 - pascal case

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.

@all-contributors please add @<username> for <contributions>
https://allcontributors.org/
this issue
Emoji Key (Contribution Types Reference)

long-titles-above-80-chars-{.....} - too long

  • #58/something - shell understands it as comment

  • Branching model

    Merges

    ​​​

    ​​​

    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).

    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.

    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.

    • 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.

    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.

    1. Code and unit tests for functionality completed.

    2. Assumption of meeting Acceptance Criteria.

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

    4. Postmerge [if applicable] tests passed.

    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.

    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.

  • Framework

    Development

    Release

    Forms

    Simple strings are mapped directly to their respective translation

    Complex translation keys are being evaluated upon translation

    Commonly used translations with the same translation key

    Custom translations with keys defined right next to the form

    Endpoints

    Form definition

    Form usage

    Validation

    Default translations

    Default translations with interpolation

    Custom translations in validation schema

    Inline translations in validation schema

    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: 'test@example.com'}} />
    {
      "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 },
        })),
      })

    Translations (i18n)

    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.

    Translation keys

    It is preferred to use kebab-case 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

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

    Environments

    Usage in React

    Usage in components

    SSR preloading i18n in pages

    {
      "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

    Campaigns

    Get Campaigns

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

    This endpoint allows you to get campaigns.

    Headers

    Name
    Type
    Description

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

    This endpoint allows you to get campaigns.

    Name
    Type
    Description
    Name
    Type
    Description

    .env

    Guidelines

    Starting a task

    Authentication

    string

    Authentication token to track down who is emptying our stocks.

    id

    string

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

    Authentication

    string

    Authentication token to track down who is emptying our stocks.

    Get Campaign by ID

    Path Parameters

    Headers

    [
        {
            "id": "5572f770-a434-4ed8-9a91-d94474d26c8e"
            "name": "Campaign name"
        },
        {
            "id": "97692474-2482-460f-86ec-2b84954f989b"
            "name": "Campaign name"
        }
    ]
    {
        "error": "Not Found"
    }
    ## 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=
    {
        "id": "5572f770-a434-4ed8-9a91-d94474d26c8e"
        "name": "Campaign name"
    }
    {
        "error": "Not Found"
    }

    Local Development

    Visit

    Install the binary via

    Stop the docker container with docker-compose down

    Healthcheck

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

    Production

    Health check

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

    Authentication

    Schema

    Local development

    Setup local dev environment

    Start development server

    Start dev server via Docker Compose

    Start the container in foreground

    Start the container in background

    Linting

    http://localhost:3040/
    https://docs.docker.com/compose/install/
    yarn build

    Production environment

    Build frontend

    Build Docker image

    git clone git@github.com:daritelska-platforma/frontend.git
    cd frontend
    
    # Symlink dev environment
    ln -s .env.example .env
    
    # Install dependencies
    yarn
    yarn dev
    docker-compose up
    docker-compose up -d
    docker-compose logs -f
    yarn lint
    yarn lint:styles
    yarn format
    yarn type-check
    docker build . \
        --file ./Dockerfile \
        --target production \
        --build-arg NODE_ENV=production

    Contact Request

    Create contact request

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

    Request Body

    Name
    Type
    Description

    email

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

    This endpoint allows you to get free cakes.

    Name
    Type
    Description

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

    Name
    Type
    Description
    Name
    Type
    Description

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

    Name
    Type
    Description
    Name
    Type
    Description

    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!

    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.

    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.

    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.

    • A series of differentials containing the backend implementation and respective unit tests.

    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.

    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.

    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.

    Deployment to Kubernetes

    • Install kubectl

    • Install kustomize

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

    A differential with any changes required to the public API.

    Keep the changeset as small as possible

    How to introduce smaller PRs?

    Break down your issues!

    Large PRs

    Do not break the builds!

    Sources

    source
    source
    here
    The (written) unwritten guide to pull requests
    Optimal pull request size
    Best Practices for Code Review
    The anatomy of a perfect pull request
    Google's Engineering Practices documentation

    string

    phone

    string

    company

    string

    message

    string

    firstName

    string

    lastName

    string

    Authentication

    string

    Authentication token to track down who is emptying our stocks.

    ID

    string

    Authentication

    string

    id

    string

    Authentication

    string

    Fetch contact requests

    Headers

    Fetch specific contact request

    Path Parameters

    Headers

    Delete contact request

    Path Parameters

    Headers

    {
      "id": "112cb853-b0b9-482e-a944-cf4fca5566b7",
      "firstName": "John",
      "lastName": "Doe",
      "email": "john.doe@example.com",
      "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
        }
      ]
    }
    This will update the deployment using ghcr.io/podkrepi-bg/frontend:master image

    The following command will:

    • Bump the version in package.json

    • Tag the latest master branch

    • [postversion] 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

    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.

    https://github.com/orgs/podkrepi-bg/packages/container/package/frontend

    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

    The following command will:

    • Bump the version in package.json

    • Tag the latest master branch

    • [postversion] 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

    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.

    https://github.com/orgs/podkrepi-bg/packages/container/package/api

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

    Prerequisites

    Frontend

    Staging

    https://kubernetes.io/docs/tasks/tools/
    https://kubectl.docs.kubernetes.io/installation/kustomize/

    Production

    Create new release

    Apply manifests

    Backend

    Staging

    Production

    Create new release

    Apply manifests

    Manual deployment

    [
      {
        "id": "0d6ff16e-ae4c-48e4-868c-05095d923053",
        "firstName": "John",
        "lastName": "Doe",
        "email": "john.doe@example.com",
        "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": "john.doe@example.com",
        "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": "john.doe@example.com",
      "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
    }
    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

    React Guidelines

    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.

    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'

    File structure

    Inherits AirBnb naming convention https://github.com/airbnb/javascript/tree/master/react#naming

    Use PascalCase for React components and camelCase for their instances

    Naming convention react components

    Pascal cased file names src/components/GenericForm.tsx

    export default function GenericForm() {}

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

    Naming convention non react components

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

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

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

    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.

    • Nice IDE support and readability

    • Named function

      ⛅ Allows attaching static props to the function

    • Const arrow function

      🌞 Nice for locally defined components

      ⛅ Okay for default exports, but not preferred

    There are three common ways to style a component:

    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

    🌞 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

    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

    Unnamed arrow function ⛈️

    Discouraged

  • Class components ⛈️

    Discouraged as hooks cannot be used inside the class components

  • 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 />
    }
    const RegisterPage = () => <form>page</form>
    
    export default RegisterPage
    <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;
    }

    Naming convention folders

    Naming convention pages

    Types

    Components

    Preferred export style 🌞

    Alternative export styles

    Styles

    Styles using the

    Styles using useStyles() hook

    Styles using SCSS files

    https://material-ui.com/system/basics/#all-inclusive
    SCSS
    https://nextjs.org/docs/basic-features/built-in-css-support#sass-support
    https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/display-name.md
    <Box /> component
    export default () => <div>page</div>
    class Page extends React.Component {
      render() {
        return <div>page</div>
      }
    }

    Support Request

    Create support request

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

    Request Body

    Name
    Type
    Description

    person

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

    This endpoint allows you to get free cakes.

    Name
    Type
    Description

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

    Name
    Type
    Description
    Name
    Type
    Description

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

    Name
    Type
    Description
    Name
    Type
    Description

    object

    support_data

    object

    Authentication

    string

    Authentication token to track down who is emptying our stocks.

    ID

    string

    Authentication

    string

    id

    string

    Authentication

    string

    Fetch support requests

    Headers

    Fetch specific support request

    Path Parameters

    Headers

    Delete support request

    Path Parameters

    Headers

    {
      "id": "d1451c92-0e86-463a-bf1c-2e4554a77f30",
      "person": {
        "email": "test@example.com",
        "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
    }