diff --git a/docs/en.md b/docs/en.md deleted file mode 100644 index 5e27457..0000000 --- a/docs/en.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: 'Mapado Best practices' ---- - -Here is a list of our best practices - -[React](./react) - -[Redux](./redux) - -[React-redux](./react-redux) - -[JS & Frontend Testing](./testing) diff --git a/docs/en/react-redux.md b/docs/en/react-redux.md deleted file mode 100644 index 05ec55d..0000000 --- a/docs/en/react-redux.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -title: 'React-Redux' ---- - -This best practice list is more a list of corrected "bad practices" that we had in our codebase and fixed. - -### Injecting components - -Imagine we need to inject a component based upon some context, for exemple if the user is logged in. - -Here is our redux store - -```js -const store = { - [isLogged: boolean]: true, - [username?: string]: 'Michel', -}; -``` - -Imagine we have a `Layout` component which need to display a `UserInfo` or `Anonymous` component based upon the `isLogged` value. - -The `Layout` component SHOULD NOT decide which component to display but SHOULD use an intermediate redux container which will do this job. - -Content of the state SHOULD be injected as close as possible of its usage (ex. here with the `username`). - -๐Ÿ‘Ž - -```js {12,14,18-19,22-23,27-28} -import React from 'react'; -import { connect } from 'react-redux'; - -function Anonymous() { - return (
Hello anonymous
); -} - -function UserInfo({ username }) { - return (
Hello {username}
); -} - -function Layout ({ username, UserInfoComponent = UserInfo }) { - return ( - {} - ); -} -Layout.propTypes = { - UserInfoComponent: React.elementType, - username: null, -}; -Layout.defaultProps = { - UserInfoComponent: UserInfo, - username: PropTypes.string, -} - -const mapStateToProps = state => ({ - username: state.app.username, - UserInfoComponent: state.app.isLogged ? UserInfo : Anonymous, -}); - -export default connect(mapStateToProps)(Layout); -``` - -๐Ÿ‘ - -```js {9,16-18} -import React from 'react'; -import { useSelector } from 'react-redux'; - -function Anonymous() { - return (
Hello anonymous
); -} - -function UserInfo({ username }) { - const username = useSelector(state => state.app.username); - - return (
Hello {username}
); -} - -// enfin affichons notre composant `Layout` -function Layout() { - const isLogged = useSelector(state => state.app.isLogged); - - return isLogged? :; -} -``` - -It is way more readable, understandable and efficient now with redux hooks. diff --git a/docs/en/react.mdx b/docs/en/react.mdx deleted file mode 100644 index d0d30cd..0000000 --- a/docs/en/react.mdx +++ /dev/null @@ -1,204 +0,0 @@ ---- -title: 'ReactJS' ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -This best practice list is more a list of corrected "bad practices" that we had in our codebase and fixed. - -### Method naming - -A method "handling" an event `onSomething` SHOULD start with `handle`. - -```js {2,8} -class Foo() { - handleClick() { - // do something - } - - render() { - return ( - - ); - } -} -``` - -### bind(this) in the `render` method - -Binding and this in Javascript [is not easy](http://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/). - -The new ECMAScript functionnality allow us to keep it like other programming languages and does not force us to know the "internals", and it's pretty cool (even if it's better to know how it works underneath). - -For this reason, `binding` and method call SHOULD be the most "easy" and "logical" way for beginners. - -It makes it easier to read and understand the code for experienced programmer too. - - - - -```js {7} -class Foo extends Component { - handleClick(foo) { - // do someting - } - - render() { - this.handleClick.bind(this, this.props.foo); - - return ; - } -} -``` - - - - -```js {5} -class Foo extends Component { - constructor(props) { - super(props); - - this.handleClick = this.handleClick.bind(this); - } - - handleClick() { - const foo = this.props.foo; - // do someting - } - - render() { - return ; - } -} -``` - - - - -### Class element: anonymous function in the `render` method - -Same as the `bind` call, we MUST NOT create anonymous functions in the `render` method. - -:::note Explanations - -- Anonymous functions are regenerated on each call of the `render` function, which consume useless resources. -- The `render` method is less readable, because there will be business logic in it. - -::: - - - - -```js {14-16} -class Foo extends PureComponent { - constructor(props) { - super(props); - - this.state = { - foo: false, - }; - } - - render() { - return ( -
- -
- ); - } -} -``` - -
- - -```js {5,12-16,21} -class Foo extends PureComponent { - constructor(props) { - super(props); - - this.handleButtonClick = this.handleButtonClick.bind(this); - - this.state = { - foo: false, - }; - } - - handleButtonClick() { - this.setState((prevState) => ({ - foo: !prevState.foo, - })); - } - - render() { - return ( -
- -
- ); - } -} -``` - -
-
- -#### Exception - -The only exception is if we are iterating over a list and if we must pass an item of this list as parameter. - -We MUST limit the call to calling a class method with for readability. - -We CAN do it this way: - -```js {20-21} -class Foo extends PureComponent { - constructor(props) { - super(props); - - this.handleButtonClick = this.handleButtonClick.bind(this); - - this.state = { - btnClicked: null, - }; - } - - handleButtonClick(item) { - this.setState({ - btnClicked: item, - }); - } - - render() { - return (
- {this.props.myList.map(item => - - } -
); - } -} -``` diff --git a/docs/en/redux.md b/docs/en/redux.md deleted file mode 100644 index 6e9822a..0000000 --- a/docs/en/redux.md +++ /dev/null @@ -1,270 +0,0 @@ ---- -title: 'Redux' ---- - -At [Mapado](https://www.mapado.com), we are using more and more React and Redux. As they are very permissive librairies, each developer has its own way of coding with them. - -We needed to standardize how we do it. It's now done! - -This document is still "in progress", we are not necessarily sure of some of our choices, but they seem to be the best for us right now. - -Moreover, we would welcome your comments if you have different use cases, either by opening [an issue](https://github.com/mapado/best-practices/issues). - -### When to use Redux - -Redux **MUST ONLY** be used when there is need to share information between all application. - -React can already contain informations in its local state, and the [context API](https://reactjs.org/docs/context.html) allow to share data in different components. - -Learn more: ["You might not need Redux" by Dan Abramov](https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367). - -### Libraries we use with Redux - -- Selectors: [reselect](https://github.com/reactjs/reselect) -- Data structure: [immutable-js](https://facebook.github.io/immutable-js/) -- Middleware for async actions: [redux-thunk](https://github.com/gaearon/redux-thunk) - -### Redux Style Guide - -Redux maintainers wrote the [Redux Style Guide](https://redux.js.org/style-guide/style-guide). This is the official style guide for writing Redux code. It is a really good guide to know how to architecture our code and which pattern to follow. - -### State immutability - -States **MUST** be an immutable-js `Map` or `Record`. - -Redux mark as essential to [not mutate state](https://redux.js.org/style-guide/style-guide#do-not-mutate-state) - -### State initialization - -The "state" **SHOULD** be initialized by default with every key it can use. - -> It allows components props to be set every time, without having to test `if null` cases. -> -> It frees the "containers" to avoid code like this: `state.foo.get('bar') || false`. - -We **CAN** use an immutable `Record` to forbid any new key in the state, and to give us some syntaxic sugar (`state.foo.bar` instead of `state.foo.get('bar')`). - -#### Removing keys - -We **MUST NOT** delete key from state. - -> If we delete a key, we will be in the same case as mentioned before. - -Prefer `state.set('foo', false)`, or `state.set('foo', null)` instead of `state.remove('foo')`. - -### Dispatch in actions - -We **SHOULD NOT** send too many `dispatch` in actions. - -We **SHOULD NOT** send two successive `dispatch` if there is no need to refresh the view. - -> Calling `dispatch` will refresh React components. - -This job is more suited for the reducer: - -๐Ÿ‘Ž - -```js {21-28} -function reducer(state, action) { - switch (action.type) { - case 'FOO': - return foo(state, action.foo); - case 'BAR': - return bar(state, action.bar); - } -} - -function simpleAction() { - return (dispatch) => { - dispatch({ - type: 'FOO', - foo: 'foo', - }); - }; -} - -function complexAction() { - return (dispatch) => { - dispatch({ - type: 'FOO', - foo: 'foo', - }); - dispatch({ - type: 'BAR', - bar: 'bar', - }); - }; -} -``` - -๐Ÿ‘ - -```js {21-25} -function reducer(state, action) { - switch (action.type) { - case 'FOO': - return foo(state, action.foo); - case 'FOO and BAR': - return fooAndBar(state, action.foo, action.bar); - } -} - -function simpleAction() { - return (dispatch) => { - dispatch({ - type: 'FOO', - foo: 'foo', - }); - }; -} - -function complexAction() { - return (dispatch) => { - dispatch({ - type: 'FOO and BAR', - foo: 'foo', - bar: 'bar', - }); - }; -} -``` - -cf. [Avoid Dispatching Many Actions Sequentially](https://redux.js.org/style-guide/style-guide#avoid-dispatching-many-actions-sequentially) - -### Data action status - -When we make an async call, we often need to know the state of this call (to display a loader or an error message, for example). - -Let's call "data key" the key in which the data will be stored in our redux state once fetched. - -We **COULD** store the _status_ of its data in another redux state key. - -The data key concerning the status **MUST** have the following form: `--`. - -> The `-` separator here does the same job as they do in BEM. -> -> As our states are immutable-js objects, using `-` here is not really inconvenient. - -`` is the name of the action performed on the data, for example "articleList-fetch-status". - -The value **MUST** be a `Map` containing a `status` key and an `error` key. - -#### Value of the `status` key - -The `status` key value **SHOULD** take one of the following value: - -- null -- IN_PROGRESS -- SUCCEEDED -- FAILED - -It **CAN** also take a custom value, for example: - -- IN_PROGRESS_BOTTOM -- IN_PROGRESS_TOP - -#### Value of the `error` key - -The error **MUST** be in the status object and its key must be `error`. - -Its default value **MUST** be `null`. - -Its value **SHOULD** be a string or an object. - -The error logic is managed in the component. - -#### Special case : performing operations on a list item - -Operations on a list item **MUST** follow the same logic, but **CAN** be stored in a `Map`. - -The keys of these `Map` **SHOULD** be the concerned item id. - -Example: - -```js -const state = { - ticketList: [], - - 'ticketList-fetch-status': { - status: 'SUCCEEDED', - error: null, - }, - - 'ticketList-processingPrinting-status': { - 8: { - status: 'IN_PROGRESS', - error: null, - }, - 9: { - status: 'ERROR', - error: 'No more paper', - }, - }, -}; -``` - -### Selector vs complex state - -The state **SHOULD** be used to store "raw" datas. - -A selector **SHOULD** be used to "group" / "filter" / "sort" in other words, "work" on raw datas to send them to the component. - -You can check out the [reselect](https://github.com/reactjs/reselect) documentation to find out about selectors. - -cf. [Use Selector Functions to Extract and Transform Data](https://react-redux.js.org/using-react-redux/connect-mapstate#use-selector-functions-to-extract-and-transform-data) - -### List vs Map vs Collection - -We **SHOULD** suffix the key name by the type of data: - -- If it's an article `List`, the key should be named `articleList`. -- If it's an article `Map`, the key should be named `articleMap`. -- If it's a custom `FooBarCollection` object containing articles, the key should be named `articleFooBarCollection`. - -We need to keep the naming of the "status" key like previously. - -In our example, the status key will be named `articleFooBarCollection-fetch-status`. - -### Nesting state - -The state **SHOULD NOT** have more than one nesting level. - -๐Ÿ‘Ž - -```js {1,3-6} - article: { - items: [ /* some articles */ ], - 'fetch-status': { - status: 'IN_PROGRESS', - error: null, - } - }, - tag: { - items: [ /* some tags */ ], - 'fetch-status': { - status: 'SUCCEEDED', - error: null, - } - }, -``` - -๐Ÿ‘ - -```js {2-5} - articleList: [ /* some articles */ ], - 'articleList-fetch-status': { - status: 'IN_PROGRESS', - error: null, - }, - tagList: [ /* some tags */ ], - 'tagList-fetch-status': { - status: 'SUCCEEDED', - error: null, - }, -``` - -### Mono-state vs multi-state - -We **SHOULD** have only one "state" in a simple application. - -We **CAN** split **functionally** the states in a complex application. diff --git a/docs/en/testing.md b/docs/en/testing.md deleted file mode 100644 index bf67f12..0000000 --- a/docs/en/testing.md +++ /dev/null @@ -1,346 +0,0 @@ ---- -title: 'JS & Frontend testing' ---- - -This is mostly a reminder on how we create UI / JS tests at Mapado, and which libraries we use. - -The configuration of each of those libraries will not be explained here, as it will depend on your project. - -### Libraries - -- [Jest](https://jestjs.io/) is our basic testing and assertion library -- [Enzyme](https://airbnb.io/enzyme/) is a library for testing React components - -For mocking data, we use a few more libraries depending on the case. - -To mock redux stores (ie. to test actions or reducers), you can use [redux-mock-store](https://github.com/dmitry-zaets/redux-mock-store). - -To mock async calls, we use [nock](https://github.com/nock/nock) or [fetch-mock](http://www.wheresrhys.co.uk/fetch-mock/) - -### How to test - -The basic idea revolves around the idea of the [Test Pyramid](https://martinfowler.com/articles/practical-test-pyramid.html), ie. find the right balance between fast, easy to code, but isolated unit tests, and slow, harder to code, but more integrated functional tests. - -- Unit tests are easy and quick to code, so you can easily test your utils functions, and add more tests if one case was not handled by your function - -- Integration tests should test that your React componends render without error, should test interactions, etc... Currently, we use mostly Enzyme for that, but it has its shortcomings, especially when testing interactions between multiple components, so maybe another library would be better. [More information here](https://medium.com/homeaway-tech-blog/integration-testing-in-react-21f92a55a894) - -- [Snapshot tests](https://jestjs.io/docs/en/snapshot-testing) is a functionality offered by JEST that allows to serialize a function / a React component, save it in a file, and on subsequents runs will check if the the function / component has changed what it will output. - It's a quick way to test React components, but it can be easy to add regressions. - -- Functional tests should be rendered on real browsers (or real virtual browsers, with Browserstack or Puppeteer), but as they're slow to run, and more prone to errors, they should only test critical paths. - -### Unit testing with Jest - -```js -function getFullname(lastname, firstname) { - const normalizedLastname = lastname || ''; - const normalizedFirstname = firstname || ''; - - const label = `${normalizedLastname} ${normalizedFirstname}`; - return label.trim(); -} - -describe('utils', () => { - it('can craft a fullname', () => { - expect(getFullname()).toEqual(''); - expect(getFullname('lastname')).toEqual('lastname'); - expect(getFullname(null, 'firstname')).toEqual('firstname'); - expect(getFullname('lastname', 'firstname')).toEqual('lastname firstname'); - }); -}); -``` - -### Snapshot testing with Jest - -When using `toMatchSnapshot`, on the first run, Jest will create the snapshot and write it on a file in the same directory as your test file. - -On following Jest runs, Jest will snapshot your component / function again and compare it against the snapshot stored in the file. - -If the snapshot is the same, the test will pass, else, if the function / component does not render the same thing, the test will fail. - -If the change is wanted, you can just update the stored snapshot with `jest -u`. Just be careful the change is really wanted and is not, in fact, a regression. - -```js -describe('CustomerRow', () => { - test('CustomerRow renders correctly', () => { - const customerJson = { - firstname: null, - customerType: 'organization', - phone: ['+412273579317'], - email: [], - lastname: 'AM STRAM GRAM / LE THร‰ร‚TRE', - _id: '1498-1342', - }; - - const element = ; - - const tree = renderer.create(element).toJSON(); - expect(tree).toMatchSnapshot(); - }); - - test('CustomerRow renders correctly with an invalid phone number', () => { - const customerJson = { - firstname: null, - customerType: 'organization', - phone: ['+333', '+3387', '+3337', '+3338', '+3329'], - email: [], - lastname: 'Mairie - Service des Affaires culturelles', - _id: '1498-848', - }; - - const element = ; - - const tree = renderer.create(element).toJSON(); - expect(tree).toMatchSnapshot(); - }); -}); -``` - -### Integration testing with Jest and Enzyme - -A few examples of the kind of things you can test with Enzyme: - -Here, we test that a component will call a function to get its data, and, while it does not have its data yet, will display a loader. - -```js -describe('MyComponent', () => { - it('should render a loading', () => { - const getInitialData = jest.fn(); - const onSubmit = jest.fn(); - - const renderedComponent = mount( - - ); - - expect(getInitialData.mock.calls.length).toEqual(1); - expect(onSubmit.mock.calls.length).toEqual(0); - expect(renderedComponent.find('.loader').length).toEqual(1); - }); - -``` - -We can also test that our component renders specific strings depending on its props - -```js -describe('CustomerRow', () => { - test('The customer main information are displayed', () => { - const customerJson = { - firstname: 'Stephen', - phone: ['+33600000000'], - email: ['stephen.fry@futurama.fr'], - lastname: 'Fry', - }; - - const renderedComponent = mount(); - - expect(renderedComponent.contains('Stephen')).toBe(true); - expect(renderedComponent.contains('Fry')).toBe(true); - expect(renderedComponent.contains('stephen.fry@futurama.fr')).toBe(true); - expect(renderedComponent.contains('06 00 00 00 00')).toBe(true); - }); -}); -``` - -We can also test interactions between components, containers and redux stores - -```js -import React from 'react'; -import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; -import { mount } from 'enzyme'; -import PriceListContainer from '../containers/PriceListContainer'; -import PriceList from '../components/PriceList'; - -const middleWares = [thunk]; -const mockStore = configureMockStore(middleWares); - -// Crafting the initial redux state, which the PriceListContainer will pass to the PriceList component -const cart = new Cart({ - '@type': 'Cart', - currency: 'EUR', -}); -const defaultState = { - booking: Map({ - currentCart: cart, - }), -}; - -const defaultState = { - booking: { - cart, - }, -}; - -const store = mockStore(defaultState); -const container = mount( - -); - -const priceListComponent = container.find(PriceList); - -// The PriceListContainer will render the PriceList component -expect(priceListComponent.exists()).toBe(true); - -// ... with the proper props -expect(priceListComponent.prop('cart')).toEqual(cart); -expect(priceListComponent.prop('currency')).toBe('YOLO'); - -// And by doing so, will call a Redux action -expect(store.getActions()).toEqual([{ type: FETCH_OFFER_LIST }]); - -// Manually call a property of the component (in this case, this function is also passed to the component from the container) -component.prop('refreshOfferList')((cart) => cart); - -// Test that another action will be triggered and sent to redux -expect(store.getActions()).toEqual([ - { type: FETCH_OFFER_LIST }, - { type: FETCH_OFFER_LIST }, -]); -``` - -You can simulate DOM actions, and check that the component will trigger the right actions. In the following example, clicking on a radio input to select an item, and then on a submit button will trigger a prop, with the selected items list as parameters. - -```js -describe('ItemSelectorModal', () => { - it('should call onSelectedItem with an item after selecting it', () => { - const mockOnCancel = jest.fn; - const mockOnSelectedItem = jest.fn(); - const renderedComponent = mount( - - ); - - renderedComponent - .find('ul li input[type="radio"]') - .at(1) - .simulate('change', { - target: { - value: 'item1', - checked: true, - }, - }); - - renderedComponent.find('button[type="submit"]').at(0).simulate('click'); - - expect(mockOnSelectedItem.mock.calls.length).toEqual(1); - expect(mockOnSelectedItem.mock.calls[0][0]).toBe('item1'); - }); -}); -``` - -You can also test async calls of your component's props, event if the Enzyme and Jest API are not always ideal. - -```js -import React from 'react'; -import { mount } from 'enzyme'; - -import PostMessageForm from './PostMessageForm'; - -jest.useFakeTimers(); - -describe('PostMessageForm', () => { - it('should call the onMessageCreated function on submit', (done) => { - const mockOnMessageCreated = jest.fn(); - const renderedComponent = mount( - - ); - - renderedComponent - .find('textarea') - .simulate('change', { target: { value: 'Lorem ipsum dolor' } }); - renderedComponent.find("input[type='checkbox']").simulate('click'); - renderedComponent.find('form').simulate('submit'); - - // In this component implementation, the submit will call a function within a setTimeout BEFORE calling the onMessageCreated prop. The way to handle it with jest might differ with an AJAX call. - jest.runOnlyPendingTimers(); - - setImmediate(() => { - // Force the component to update - renderedComponent.update(); - expect(mockOnMessageCreated).toBeCalled(); - expect(mockOnMessageCreated).toHaveBeenCalledTimes(1); - done(); - }); - }); -}); -``` - -### Functional testing with Puppeteer - -[Puppeteer](https://github.com/GoogleChrome/puppeteer) is a headless Chrome browser that you can use with CLI or with a script. It allows you to load web pages, and test stuff / take screenshots as if you were in a real browser. - -Examples of what you can do with Puppeteer, but check its API to know what it's capable of: - -```js -import PageFactory from '../pageFactory'; -import { setDefaultOptions } from 'expect-puppeteer'; - -describe('Home', () => { - let page; - - beforeAll(async () => { - page = await PageFactory(global.browser); - }); - - test('/fr/ is redirected on home page', async () => { - const response = await page.gotoPath('/fr/'); - expect(response.ok).toBeTruthy(); - expect(response.url()).toEqual(`${HOST}/`); - }); - - // We can test specific selectors exist, or specific texts are displayed - test('buckets / thumbnails / regions / cities', async () => { - const response = await page.gotoPath('/'); - expect(response.ok).toBeTruthy(); - - expect(await page.count('.mpd-vignette')).toEqual(0); - expect(await page.count('.mpd-bucket')).toEqual(0); - expect(await page.count('.regions li')).toEqual(13); - expect(await page.count('.cities li')).toEqual(12); - await expect(page).toMatchElement('.cities li', { text: 'Paris' }); - }); - - // We can also test that our forms work correctly - test('search', async () => { - expect(await page.count('.mpd-block-tab')).toEqual(3); - expect(await page.count('form[name=what-to-do]')).toEqual(1); - - await page.fillInputHidden( - 'input[name="activitysearch[addressTree]"]', - 'Millau (12100)' - ); - await expect(page).toClick('button', { text: 'RECHERCHER' }); - await page.waitUntilPageLoaded(); - expect(await page.url()).toEqual(`${HOST}/millau/`); - - await page.gotoHomepage(); - await page.fillInputHidden( - 'input[name="activitysearch[addressTree]"]', - 'Paris (75000)' - ); - await expect(page).toClick('button', { text: 'RECHERCHER' }); - - // The button will redirect, so we wait for the next page - await page.waitUntilPageLoaded(); - - // The redirection was done - expect(await page.url()).toEqual(`${HOST}/paris/`); - - // Make sure the content is loaded - await page.waitForSelector('.mpd-bucket__title'); - await expect(page).toMatchElement('.mpd-bucket__title', { - text: /paris/gi, - }); - expect(await page.count('.mpd-bucket')).toEqual(5); - }); -}); -``` diff --git a/docs/fr.md b/docs/js.md similarity index 100% rename from docs/fr.md rename to docs/js.md diff --git a/docs/fr/react-redux.md b/docs/js/react-redux.md similarity index 100% rename from docs/fr/react-redux.md rename to docs/js/react-redux.md diff --git a/docs/fr/react-router.mdx b/docs/js/react-router.mdx similarity index 100% rename from docs/fr/react-router.mdx rename to docs/js/react-router.mdx diff --git a/docs/fr/react.mdx b/docs/js/react.mdx similarity index 100% rename from docs/fr/react.mdx rename to docs/js/react.mdx diff --git a/docs/fr/redux.md b/docs/js/redux.md similarity index 100% rename from docs/fr/redux.md rename to docs/js/redux.md diff --git a/docs/fr/style.mdx b/docs/js/style.mdx similarity index 100% rename from docs/fr/style.mdx rename to docs/js/style.mdx diff --git a/docs/fr/testing.md b/docs/js/testing.md similarity index 100% rename from docs/fr/testing.md rename to docs/js/testing.md diff --git a/docs/php.md b/docs/php.md new file mode 100644 index 0000000..fd41559 --- /dev/null +++ b/docs/php.md @@ -0,0 +1,11 @@ +--- +title: 'Mapado Best practices for PHP' +--- + +Here is a list of our best practices + +[PHP](./php) + +[Symfony](./symfony) + +[API-Platform](./api-platform) \ No newline at end of file diff --git a/docs/php/api-plaform.md b/docs/php/api-plaform.md new file mode 100644 index 0000000..4570a6a --- /dev/null +++ b/docs/php/api-plaform.md @@ -0,0 +1,5 @@ +--- +title: 'API-Platform' +--- + + diff --git a/docs/php/php.md b/docs/php/php.md new file mode 100644 index 0000000..ab6e8ed --- /dev/null +++ b/docs/php/php.md @@ -0,0 +1,5 @@ +--- +title: 'PHP' +--- + + diff --git a/docs/php/symfony.md b/docs/php/symfony.md new file mode 100644 index 0000000..09e2596 --- /dev/null +++ b/docs/php/symfony.md @@ -0,0 +1,5 @@ +--- +title: 'Symfony' +--- + + diff --git a/docusaurus.config.js b/docusaurus.config.js index fa15531..c218e4a 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -15,15 +15,15 @@ module.exports = { }, links: [ { - to: 'docs/fr/', - activeBasePath: 'docs/fr/', - label: 'Franรงais', + to: 'docs/js/', + activeBasePath: 'docs/js/', + label: 'JS/TS', position: 'left', }, { - to: 'docs/en/', - activeBasePath: 'docs/en/', - label: 'English', + to: 'docs/php/', + activeBasePath: 'docs/php/', + label: 'PHP', position: 'left', }, { diff --git a/package.json b/package.json index 15b5e60..7b21cb2 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "private": true, "scripts": { - "start": "docusaurus start", + "start": "NODE_OPTIONS=--openssl-legacy-provider docusaurus start --host=0.0.0.0", "build": "docusaurus build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy" @@ -27,4 +27,4 @@ "last 1 safari version" ] } -} \ No newline at end of file +} diff --git a/sidebars.js b/sidebars.js index b362fad..668a2cc 100644 --- a/sidebars.js +++ b/sidebars.js @@ -1,12 +1,12 @@ module.exports = { - fr: [ - 'fr', - 'fr/style', - 'fr/react', - 'fr/redux', - 'fr/react-redux', - 'fr/react-router', - 'fr/testing', + js: [ + 'js', + 'js/style', + 'js/react', + 'js/redux', + 'js/react-redux', + 'js/react-router', + 'js/testing', ], - en: ['en', 'en/react', 'en/redux', 'en/react-redux', 'en/testing'], + php: ['php', 'php/php', 'php/symfony', 'php/api-plaform'], }; diff --git a/src/pages/index.js b/src/pages/index.js index 322b1d1..ee66858 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -72,9 +72,9 @@ function Home() { 'button button--outline button--secondary button--lg', styles.getStarted )} - to={useBaseUrl('docs/fr/')} + to={useBaseUrl('docs/js/')} > - En franรงais + JS / TS   - In english + PHP