Skip to content

Commit

Permalink
[Refacto] Refactoring app with Vitejs React-ts and MUI (#3)
Browse files Browse the repository at this point in the history
Playground for increase my skills (@LedruRomane) in ViteJs, React,
Typescript and MUI / MUI integration.

# [V1] 
![Capture d’écran 2024-01-02 à 15 44
19](https://github.com/Elao/ca-mache-quoi/assets/113915173/75ff79c9-1ae6-4e1f-8690-cc7f3887cc62)
LedruRomane authored May 16, 2024
2 parents cae43f5 + b1dde0f commit 936f6c1
Showing 72 changed files with 5,190 additions and 26,936 deletions.
77 changes: 77 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:json/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: ['./tsconfig.json', './tsconfig.node.json'],
tsconfigRootDir: __dirname,
},
plugins: ['react-refresh'],
settings: {
react: {
version: 'detect',
},
},
rules: {
// Basics
'no-unused-vars': 'off',

// Forbids console.log (prevent accidental commits)
'no-console': ['error', { 'allow': ['debug', 'info', 'warn', 'error'] }],

// React Refresh
'react-refresh/only-export-components': 'off',

// React
// https://eslint.org/docs/latest/rules/jsx-quotes
'jsx-quotes': ['error', 'prefer-double'],
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-curly-spacing.md
'react/jsx-curly-spacing': ['error', {
when: 'never',
children: true
}],

// TypeScript
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/comma-dangle': ['error', {
// https://eslint.org/docs/latest/rules/comma-dangle#options
arrays: 'always-multiline',
objects: 'always-multiline',
imports: 'always-multiline',
exports: 'always-multiline',
functions: 'always-multiline',
// https://typescript-eslint.io/rules/comma-dangle/
enums: 'always-multiline',
generics: 'always-multiline',
tuples: 'always-multiline'
}],

'@typescript-eslint/member-delimiter-style': ['error', {
// https://typescript-eslint.io/rules/member-delimiter-style/
multiline: {
delimiter: 'none',
requireLast: false
},
singleline: {
delimiter: 'comma',
requireLast: false
},
multilineDetection: 'brackets'
}],

"@typescript-eslint/no-unused-vars": [
"error"
],
},
}
38 changes: 38 additions & 0 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Lint

on:
push:
branches:
- main
pull_request:
types: [ opened, synchronize, reopened, ready_for_review ]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:

lint_front:
name: 'Lint Front'
runs-on: ubuntu-latest
timeout-minutes: 15
# Do not run on WIP or Draft PRs
if: "!github.event.pull_request || (!contains(github.event.pull_request.labels.*.name, 'WIP') && github.event.pull_request.draft == false)"

steps:

- name: 'Checkout'
uses: actions/checkout@v3

- name: 'Install dependencies'
run: |
make install@integration
- name: 'ESLint front'
run: |
make lint.eslint@integration
- name: 'TypeScript check front'
run: |
make lint.tsc@integration
23 changes: 23 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
48 changes: 48 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# --------------------------------#
# "make" command
# --------------------------------#

-include ./make/text.mk
-include ./make/help.mk
-include ./make/url.mk

.SILENT:
.PHONY: build

## Setup - Install dependencies
install:
npm install

install@integration:
npm install --color=always --no-progress --no-audit --no-fund

## Setup - Update dependencies
update:
npm update

## Serve - Serve the whole app
serve: export APP_RUNTIME_ENV ?= development
serve:
npx vite --host=mache.ela.ooo --port=63286

## Build - Build
build: export APP_RUNTIME_ENV ?= development
build:
npx tsc && npx vite build

## Tests - Lint
lint: lint.eslint lint.tsc

lint.eslint:
npx eslint . --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0

lint.tsc:
npx tsc --noemit

lint@integration: lint.eslint@integration lint.tsc@integration

lint.eslint@integration:
npx eslint ./

lint.tsc@integration:
npx tsc --noemit
70 changes: 0 additions & 70 deletions README copie.md

This file was deleted.

95 changes: 62 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,53 +1,82 @@
# Ça-mâche-quoi
ça-mâche-quoi Front
===========

Internal project dedicated to the culinary satisfaction of the Elao Magic Team
🍕 🍔 🥙 🥗 🌯 🍣 🍛 🍟
ça-mâche-quoi front is a React web application in TypeScript.

<!-- INDEX -->
<details open="open">
<summary>Index</summary>
<ol>
<li><a href="#setup">Development</a></li>
<li><a href="#needs">Needs</a></li>
</ol>
</details>
## Installation

```shell
make install
```

## Development

### Setup
Start the dev server / watcher:

Once you have cloned your project, make sure that you are in the root directory and run this command in your terminal:
```shell
make serve
````

Linting:

```shell
make lint
```
npm install
```

### Usage
## Configuration

The app exposes some configuration variables through env vars.
By default, it loads vars from env files depending on the context, in the
following order:

1. `.env`
1. `.env.local`
1. `.env.{production,staging,development}`
1. `.env.{production,staging,development}.local`

Last defined value wins. Actual env var always wins.

Once all the necessary dependencies have been installed, your can launch the app by running this command in your terminal:
> **Note**:
> You can also load another specific env file determined by the `ENV_FILE` var, for instance:
>
> ENV_FILE=.env.production make serve

To add new configuration variables, add these to the main `.env` file
and provide a development value (if relevent) in the `.env.development` file.

## Build

Build for production using:

```shell
make build@production
```
npm start

Build for staging using:

```shell
make build@staging
```

## Needs
## Serve

Serve a build:

(EN)
- As a user I would like to decide where to eat.
- As a user I would like to select multiple cuisine types to fill my slot machine.
- As a user I would like to select all the cuisinte types when I click on a button.
- As a user I would like to start my slot machine when I click on a button.
- As a user I would like to obtain a result among my options (cuisine type I would like to eat).
- As a user I would like to be able to share the result.
```shell
make serve.static
```

(FR)
- En tant qu’utilisateur je veux pouvoir choisir où me restaurer.
- En tant qu’utilisateur je veux pouvoir sélectionner un ensemble de catégories (types de cuisine) pour remplir ma roulette.
- En tant qu'utilisateur je veux pouvoir cocher tout les cases en cliquant sur un bouton
- En tant qu’utilisateur je veux pouvoir déclencher ma roulette en cliquant sur un bouton.
- En tant qu’utilisateur je veux obtenir un résultat (type de cuisine à manger).
- En tant qu'utilisateur je veux pouvoir partager le résultat de ma roulette.
## Going further

- [Assets](res/doc/assets.md)
- [Routes](res/doc/routes.md)

## References

- [TypeScript](https://www.typescriptlang.org/)
- [TypeScript Cheatscheet](https://react-typescript-cheatsheet.netlify.app/)
- [Apollo GraphQL Client](https://www.apollographql.com/docs/react/)
- [Using Apollo with TypeScript](https://www.apollographql.com/docs/react/development-testing/static-typing/)

---
⬆︎ [**Back to README.md**](../README.md)
5 changes: 5 additions & 0 deletions assets/app.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
body {
display: flex;
flex: 1;
height: 100vh;
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes
30 changes: 30 additions & 0 deletions doc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# React + TypeScript + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

## Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:

- Configure the top-level `parserOptions` property like this:

```js
export default {
// other rules...
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: ['./tsconfig.json', './tsconfig.node.json'],
tsconfigRootDir: __dirname,
},
}
```

- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
12 changes: 12 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Ça mâche quoi</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
72 changes: 72 additions & 0 deletions make/help.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
########
# Help #
########

.DEFAULT_GOAL := help

MACHE_HELP = \
Usage: make [$(MACHE_COLOR_INFO)command$(MACHE_COLOR_RESET)] \
$(call mache_help_section, Help) \
$(call mache_help,help,This help)

define mache_help_section
\n\n$(MACHE_COLOR_COMMENT)$(strip $(1)):$(MACHE_COLOR_RESET)
endef

define mache_help
\n $(MACHE_COLOR_INFO)$(1)$(MACHE_COLOR_RESET) $(2)
endef

help:
@printf "\n$(MACHE_HELP)"
@awk ' \
BEGIN { \
sectionsName[1] = "Commands" ; \
sectionsCount = 1 ; \
} \
/^[-a-zA-Z0-9_.@%\/+]+:/ { \
if (match(lastLine, /^## (.*)/)) { \
command = substr($$1, 1, index($$1, ":") - 1) ; \
section = substr(lastLine, RSTART + 3, index(lastLine, " - ") - 4) ; \
if (section) { \
message = substr(lastLine, index(lastLine, " - ") + 3, RLENGTH) ; \
sectionIndex = 0 ; \
for (i = 1; i <= sectionsCount; i++) { \
if (sectionsName[i] == section) { \
sectionIndex = i ; \
} \
} \
if (!sectionIndex) { \
sectionIndex = sectionsCount++ + 1 ; \
sectionsName[sectionIndex] = section ; \
} \
} else { \
message = substr(lastLine, RSTART + 3, RLENGTH) ; \
sectionIndex = 1 ; \
} \
if (length(command) > sectionsCommandLength[sectionIndex]) { \
sectionsCommandLength[sectionIndex] = length(command) ; \
} \
sectionCommandIndex = sectionsCommandCount[sectionIndex]++ + 1; \
helpsCommand[sectionIndex, sectionCommandIndex] = command ; \
helpsMessage[sectionIndex, sectionCommandIndex] = message ; \
} \
} \
{ lastLine = $$0 } \
END { \
for (i = 1; i <= sectionsCount; i++) { \
if (sectionsCommandCount[i]) { \
printf "\n\n$(MACHE_COLOR_COMMENT)%s:$(MACHE_COLOR_RESET)", sectionsName[i] ; \
for (j = 1; j <= sectionsCommandCount[i]; j++) { \
printf "\n $(MACHE_COLOR_INFO)%-" sectionsCommandLength[i] "s$(MACHE_COLOR_RESET) %s", helpsCommand[i, j], helpsMessage[i, j] ; \
} \
} \
} \
} \
' $(MAKEFILE_LIST)
@printf "\n\n"
@printf "$(if $(MACHE_HELP_PROJECT),$(MACHE_HELP_PROJECT)\n\n)"
.PHONY: help

help.project:
@printf "$(if $(MACHE_HELP_PROJECT),\n$(MACHE_HELP_PROJECT)\n\n)"
141 changes: 141 additions & 0 deletions make/text.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
##########
# Colors #
##########

MACHE_COLOR_RESET := \033[0m
MACHE_COLOR_ERROR := \033[31m
MACHE_COLOR_INFO := \033[32m
MACHE_COLOR_WARNING := \033[33m
MACHE_COLOR_COMMENT := \033[36m

######################
# Special Characters #
######################

# Usage:
# $(call mache_message, Foo$(,) bar) = Foo, bar
# $(call mache_message, $(lp)Foo bar) = (Foo bar
# $(call mache_message, Foo$(rp) bar) = Foo) bar

, := ,
lp := (
rp := )

########
# Time #
########

# Usage:
# $(call mache_time) = 11:06:20

define mache_time
`date -u +%T`
endef

###########
# Message #
###########

# Usage:
# $(call mache_message, Foo bar) = Foo bar
# $(call mache_message_success, Foo bar) = (っ◕‿◕)っ Foo bar
# $(call mache_message_warning, Foo bar) = ¯\_(ツ)_/¯ Foo bar
# $(call mache_message_error, Foo bar) = (╯°□°)╯︵ ┻━┻ Foo bar

define mache_message
printf "$(MACHE_COLOR_INFO)$(strip $(1))$(MACHE_COLOR_RESET)\n"
endef

define mache_message_success
printf "$(MACHE_COLOR_INFO)(っ◕‿◕)っ $(strip $(1))$(MACHE_COLOR_RESET)\n"
endef

define mache_message_warning
printf "$(MACHE_COLOR_WARNING)¯\_(ツ)_/¯ $(strip $(1))$(MACHE_COLOR_RESET)\n"
endef

define mache_message_error
printf "$(MACHE_COLOR_ERROR)(╯°□°)╯︵ ┻━┻ $(strip $(1))$(MACHE_COLOR_RESET)\n"
endef

#######
# Log #
#######

# Usage:
# $(call mache_log, Foo bar) = [11:06:20] [target] Foo bar
# $(call mache_log_warning, Foo bar) = [11:06:20] [target] ¯\_(ツ)_/¯ Foo bar
# $(call mache_log_error, Foo bar) = [11:06:20] [target] (╯°□°)╯︵ ┻━┻ Foo bar

define mache_log
printf "[$(MACHE_COLOR_COMMENT)$(call mache_time)$(MACHE_COLOR_RESET)] [$(MACHE_COLOR_COMMENT)$(@)$(MACHE_COLOR_RESET)] " ; $(call mache_message, $(1))
endef

define mache_log_warning
printf "[$(MACHE_COLOR_COMMENT)$(call mache_time)$(MACHE_COLOR_RESET)] [$(MACHE_COLOR_COMMENT)$(@)$(MACHE_COLOR_RESET)] " ; $(call mache_message_warning, $(1))
endef

define mache_log_error
printf "[$(MACHE_COLOR_COMMENT)$(call mache_time)$(MACHE_COLOR_RESET)] [$(MACHE_COLOR_COMMENT)$(@)$(MACHE_COLOR_RESET)] " ; $(call mache_message_error, $(1))
endef

###########
# Confirm #
###########

# Usage:
# $(call mache_confirm, Foo bar) = ༼ つ ◕_◕ ༽つ Foo bar (y/N):
# $(call mache_confirm, Bar foo, y) = ༼ つ ◕_◕ ༽つ Foo bar (Y/n):

define mache_confirm
$(if $(CONFIRM),, \
printf "$(MACHE_COLOR_INFO) ༼ つ ◕_◕ ༽つ $(MACHE_COLOR_WARNING)$(strip $(1)) $(MACHE_COLOR_RESET)$(MACHE_COLOR_WARNING)$(if $(filter y,$(2)),(Y/n),(y/N))$(MACHE_COLOR_RESET): " ; \
read CONFIRM ; \
case $$CONFIRM in $(if $(filter y,$(2)), \
[nN]$(rp) printf "\n" ; exit 1 ;; *$(rp) ;;, \
[yY]$(rp) ;; *$(rp) printf "\n" ; exit 1 ;; \
) esac \
)
endef

################
# Conditionals #
################

# Usage:
# $(call mache_error_if_not, $(FOO), FOO has not been specified) = (╯°□°)╯︵ ┻━┻ FOO has not been specified

define mache_error_if_not
$(if $(strip $(1)),, \
$(call mache_message_error, $(strip $(2))) ; exit 1 \
)
endef

# Usage:
# $(call mache_confirm_if, $(FOO), Foo bar) = ༼ つ ◕_◕ ༽つ Foo bar (y/N):

define mache_confirm_if
$(if $(strip $(1)), \
$(call mache_confirm, $(strip $(2)))
)
endef

# Usage:
# $(call mache_confirm_if_not, $(FOO), Foo bar) = ༼ つ ◕_◕ ༽つ Foo bar (y/N):

define mache_confirm_if_not
$(if $(strip $(1)),, \
$(call mache_confirm, $(strip $(2)))
)
endef

##########
# Random #
##########

# Usage:
# $(call mache_rand, 8) = 8th56zp2

define mache_rand
`cat /dev/urandom | LC_ALL=C tr -dc 'a-z0-9' | fold -w $(strip $(1)) | head -n 1`
endef
6 changes: 6 additions & 0 deletions make/url.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
########
# Help #
########

MACHE_HELP_PROJECT = $(MACHE_COLOR_COMMENT)┏(°.°)┛┗(°.°)┓$(MACHE_COLOR_RESET) ♪♫ Let's party ♫♪ $(MACHE_COLOR_COMMENT)┗(°.°)┛┏(°.°)┓$(MACHE_COLOR_RESET)\n
MACHE_HELP_PROJECT += $(call mache_help,Front, http://mache.ela.ooo:63286)
30,249 changes: 3,931 additions & 26,318 deletions package-lock.json

Large diffs are not rendered by default.

61 changes: 28 additions & 33 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,40 +1,35 @@
{
"name": "my-app",
"version": "0.1.0",
"name": "new",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.0",
"sass": "^1.49.9",
"web-vitals": "^2.1.4"
},
"version": "0.0.0",
"type": "module",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint new --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
"dependencies": {
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.15.2",
"@mui/material": "^5.15.2",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
"devDependencies": {
"@types/react": "^18.2.46",
"@types/react-dom": "^18.2.17",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.55.0",
"eslint-plugin-json": "^3.1.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"sass": "^1.69.6",
"typescript": "^5.2.2",
"vite": "^5.0.8"
}
}
Binary file modified public/favicon.ico
Binary file not shown.
43 changes: 0 additions & 43 deletions public/index.html

This file was deleted.

Binary file removed public/logo192.png
Binary file not shown.
Binary file removed public/logo512.png
Binary file not shown.
25 changes: 0 additions & 25 deletions public/manifest.json

This file was deleted.

3 changes: 0 additions & 3 deletions public/robots.txt

This file was deleted.

Binary file removed public/taco.png
Binary file not shown.
23 changes: 0 additions & 23 deletions src/App.js

This file was deleted.

8 changes: 0 additions & 8 deletions src/App.test.js

This file was deleted.

20 changes: 20 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import '@assets/app.scss'
import AppLayout from '@app/layouts/AppLayout.tsx';
import { ThemeProvider } from '@mui/material/styles';
import { CssBaseline } from '@mui/material';
import { customTheme } from '@app/theme.ts';
import RestaurantsOptions from '@app/pages/RestaurantsOptions.tsx';

function App() {

return (
<ThemeProvider theme={customTheme}>
<CssBaseline />
<AppLayout>
<RestaurantsOptions />
</AppLayout>
</ThemeProvider>
)
}

export default App
Binary file removed src/assets/images/hamburger.png
Binary file not shown.
Binary file removed src/assets/images/pizza.png
Binary file not shown.
Binary file removed src/assets/images/salad.png
Binary file not shown.
Binary file removed src/assets/images/sushi.png
Binary file not shown.
Binary file removed src/assets/images/taco.png
Binary file not shown.
Binary file removed src/assets/images/takeout-box.png
Binary file not shown.
11 changes: 0 additions & 11 deletions src/assets/styles/app.scss

This file was deleted.

15 changes: 0 additions & 15 deletions src/assets/styles/base/fonts.scss

This file was deleted.

10 changes: 0 additions & 10 deletions src/assets/styles/base/layout.scss

This file was deleted.

7 changes: 0 additions & 7 deletions src/assets/styles/base/variables.scss

This file was deleted.

49 changes: 0 additions & 49 deletions src/assets/styles/components/checkbox-button.scss

This file was deleted.

7 changes: 0 additions & 7 deletions src/assets/styles/components/form.scss

This file was deleted.

20 changes: 0 additions & 20 deletions src/assets/styles/generic/button.scss

This file was deleted.

10 changes: 0 additions & 10 deletions src/assets/styles/generic/footer.scss

This file was deleted.

28 changes: 0 additions & 28 deletions src/assets/styles/generic/select-all.scss

This file was deleted.

25 changes: 0 additions & 25 deletions src/assets/styles/generic/titles.scss

This file was deleted.

11 changes: 0 additions & 11 deletions src/components/Button.js

This file was deleted.

11 changes: 0 additions & 11 deletions src/components/Footer.js

This file was deleted.

14 changes: 14 additions & 0 deletions src/components/Footer/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { styled } from '@mui/material';

export default function Footer() {
return <StyledFooter role="contentinfo">
Made with 🌮 at elao - © 2024
</StyledFooter>;
}

const StyledFooter = styled('footer')(({ theme }) => theme.unstable_sx({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
color: theme.palette.common.white,
}));
70 changes: 0 additions & 70 deletions src/components/Form.js

This file was deleted.

42 changes: 42 additions & 0 deletions src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { styled } from '@mui/material';

export default function Header() {

return <StyledHeader>
<h1>
<span>Ça mâche</span>
<span>quoi ?</span>
</h1>
</StyledHeader>;
}

const StyledHeader = styled('header')(({ theme }) => theme.unstable_sx({
'& h1': {
margin: '60px 0px 130px 130px',
fontSize: '52px',
color: theme.palette.common.white,
fontFamily: 'Poppins',

'& span': {
position: 'relative',
zIndex: '1',

'&:last-of-type': {
top: '59px',
right: '65px',
},

'&:before': {
content: '""',
position: 'absolute',
top: '30px',
left: '10px',
width: '100%',
height: '33px',
background: theme.palette.secondary.light,
zIndex: '-1',
},
},
},
}));

12 changes: 0 additions & 12 deletions src/components/Title.js

This file was deleted.

251 changes: 251 additions & 0 deletions src/components/UI/Button/RestaurantButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
import {
Box,
Button,
ClickAwayListener,
Grow,
IconButton,
MenuItem,
MenuList,
Paper,
Popper,
styled,
Typography,
} from '@mui/material';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import { useRef, useState } from 'react';

interface Props {
id: number
name: string
website: string
image: string
pickedRestaurantIds: number[]
setPickedRestaurantIds: (id: number[]) => void
}

export function RestaurantButton({
id,
name,
website,
image,
pickedRestaurantIds,
setPickedRestaurantIds,
}: Props) {
const selected = pickedRestaurantIds.includes(id);
const selectedStyle = selected ?
{
width: '300px',
margin: '20px',
border: '4px solid currentColor',
} : {
width: '300px',
margin: '20px',
};
const [open, setOpen] = useState(false);
const anchorRef = useRef<HTMLButtonElement>(null);

function addRestaurantToPool(id: number) {
setPickedRestaurantIds([...pickedRestaurantIds, id]);
}

function removeRestaurantFromPool(id: number) {
setPickedRestaurantIds(pickedRestaurantIds.filter(restaurantId => id !== restaurantId));
}

function toggleRestaurant(value: boolean) {
if (value) {
addRestaurantToPool(id);
} else {
removeRestaurantFromPool(id);
}
}

function handleClick() {
toggleRestaurant(!selected);
}

const handleToggle = () => {
setOpen((prevOpen) => !prevOpen);
};

const handleClose = (event: Event) => {
if (
anchorRef.current &&
anchorRef.current.contains(event.target as HTMLElement)
) {
return;
}

setOpen(false);
};

const handleMenuItemClick = (
website: string,
) => {
window.open(website, '_blank');
setOpen(false);
};

return <StyledBox>
<StyledButton
focusRipple
key={name}
style={selectedStyle}
onClick={handleClick}
>
<ImageSrc style={{ backgroundImage: `url(${image})` }} />
<ImageBackdrop className="MuiImageBackdrop-root" />
<Image>
<Typography
component="span"
variant="subtitle1"
color="inherit"
sx={{
position: 'relative',
p: 4,
pt: 2,
pb: (theme) => `calc(${theme.spacing(1)} + 6px)`,
}}
>
{name}
<ImageMarked className="MuiImageMarked-root" />
</Typography>
</Image>
</StyledButton>
<StyledIconButton
color="primary"
aria-label="See website"
aria-controls={open ? 'split-button-menu' : undefined}
aria-expanded={open ? 'true' : undefined}
aria-haspopup="menu"
onClick={handleToggle}
ref={anchorRef}
opened={open}
>
<MoreVertIcon />
</StyledIconButton>
<Popper
sx={{
zIndex: 1,
}}
open={open}
anchorEl={anchorRef.current}
role={undefined}
transition
disablePortal
placement="bottom-start"
>
{({ TransitionProps }) => (
<Grow
{...TransitionProps}
>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<StyledMenuList id="split-button-menu" autoFocusItem>
<StyledMenuItem
onClick={() => handleMenuItemClick(website)}
>
Visit website
</StyledMenuItem>
</StyledMenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</StyledBox>
}

const StyledMenuList = styled(MenuList)(({ theme }) => ({
color: theme.palette.text.primary,
backgroundColor: theme.palette.primary.main,
marginTop: '-20px',
}));

const StyledMenuItem = styled(MenuItem)(({ theme }) => ({
color: theme.palette.secondary.contrastText,
'&:hover': {
color: theme.palette.common.white,
},
}));

const StyledIconButton = styled(IconButton)<{
opened: boolean
}>(({ theme, opened }) => ({
color: theme.palette.common.white,
backgroundColor: theme.palette.primary.main,
width: '40px',
height: '40px',
position: 'absolute',
zIndex: 2,
opacity: opened ? 0 : 1,
}));

const StyledBox = styled(Box)({
display: 'flex',
flexDirection: 'column',
});

const StyledButton = styled(Button)(({ theme }) => ({
position: 'relative',
height: 200,
[theme.breakpoints.down('sm')]: {
width: '100% !important', // Overrides inline-style
height: 100,
},
'&:hover, &.Mui-focusVisible': {
zIndex: 1,
'& .MuiImageBackdrop-root': {
opacity: 0.15,
},
'& .MuiImageMarked-root': {
opacity: 0,
},
'& .MuiTypography-root': {
border: '4px solid currentColor',
},
},
}));

const Image = styled('span')(({ theme }) => ({
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: theme.palette.common.white,
}));

const ImageBackdrop = styled('span')(({ theme }) => ({
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
backgroundColor: theme.palette.common.black,
opacity: 0.4,
transition: theme.transitions.create('opacity'),
}));

const ImageMarked = styled('span')(({ theme }) => ({
height: 3,
width: 18,
backgroundColor: theme.palette.common.white,
position: 'absolute',
bottom: -2,
left: 'calc(50% - 9px)',
transition: theme.transitions.create('opacity'),
}));

const ImageSrc = styled('span')({
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
backgroundSize: 'cover',
backgroundPosition: 'center 40%',
});
18 changes: 18 additions & 0 deletions src/components/UI/Button/ValidateButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Fab, styled } from '@mui/material'

const FabStyled = styled(Fab)(({ theme }) => ({
position: 'fixed',
right: 0,
backgroundColor: theme.palette.primary.main,
'&:hover': {
backgroundColor: theme.palette.primary.light,
},
}));

export default function ValidateButton() {
return (
<FabStyled>
Valider
</FabStyled>
)
}
65 changes: 65 additions & 0 deletions src/components/UI/RestaurantCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Button, Card, CardActions, CardContent, CardMedia, Typography } from '@mui/material';
import ToggleSwitch from '@app/components/UI/ToggleSwitch.tsx';


interface Props {
id: number
name: string
description: string
website: string
image: string
pickedRestaurantIds: number[]
setPickedRestaurantIds: (id: number[]) => void
}

export function RestaurantCard({
id,
name,
description,
website,
image,
pickedRestaurantIds,
setPickedRestaurantIds,
}: Props) {

function addRestaurantToPool (id: number) {
setPickedRestaurantIds([...pickedRestaurantIds, id]);
}

function removeRestaurantFromPool (id: number) {
setPickedRestaurantIds(pickedRestaurantIds.filter(restaurantId => id !== restaurantId));
}

const selected = pickedRestaurantIds.includes(id);

function toggleRestaurant (value: boolean) {
if (value) {
addRestaurantToPool(id);
} else {
removeRestaurantFromPool(id);
}
}

return <>
<Card sx={{ maxWidth: 345 }}>
<CardMedia
sx={{ height: 140 }}
component="img"
image={image}
alt={name}
/>
<CardContent>
<Typography gutterBottom variant="h5" component="div">
{name}
</Typography>
<Typography variant="body2" color="text.secondary">
{description}
</Typography>
</CardContent>
<CardActions>
<ToggleSwitch label="Ajouter au pool de selection" checked={selected} setChecked={toggleRestaurant} />
<Button size="small" href={website} target="_blank">Website</Button>
</CardActions>
</Card>
</>
}
42 changes: 42 additions & 0 deletions src/components/UI/ToggleSwitch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react'
import { FormGroup, FormControlLabel, Switch, styled } from '@mui/material'

interface Props {
label: string
checked: boolean
setChecked: (value: boolean) => void
}

export default function ToggleSwitch({
label,
checked,
setChecked,
}: Props) {

const handleSwitch = (event: React.ChangeEvent<HTMLInputElement>) => {
setChecked(event.target.checked);
}

return (
<FormGroup
style={{
marginBottom: '20px',
}}
>
<LabelStyled
control={
<Switch checked={checked} onChange={handleSwitch} />
}
label={label}
/>
</FormGroup>
);
}


const LabelStyled = styled(FormControlLabel)(({ theme }) => ({
color: theme.palette.secondary.contrastText,
'& .MuiSwitch-track': {
backgroundColor: theme.palette.secondary.contrastText,
},
}));
42 changes: 0 additions & 42 deletions src/data/categories.json

This file was deleted.

24 changes: 24 additions & 0 deletions src/data/restaurants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const data = {
"restaurants": [
{
"id": 1,
"name": "Restaurant 1",
"website": "https://mui.com/material-ui/react-card/",
"image": "https://mui.com/static/images/cards/contemplative-reptile.jpg",
},
{
"id": 2,
"name": "Restaurant 2",
"website": "https://mui.com/material-ui/react-card/",
"image": "https://mui.com/static/images/cards/contemplative-reptile.jpg",
},
{
"id": 3,
"name": "Restaurant 3",
"website": "https://mui.com/material-ui/react-card/",
"image": "https://mui.com/static/images/cards/contemplative-reptile.jpg",
},
],
};

export default data;
13 changes: 0 additions & 13 deletions src/index.css

This file was deleted.

20 changes: 0 additions & 20 deletions src/index.js

This file was deleted.

31 changes: 31 additions & 0 deletions src/layouts/AppLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { PropsWithChildren } from 'react';
import Header from '@app/components/Header/Header.tsx';
import Footer from '@app/components/Footer/Footer.tsx';
import { Container, styled } from '@mui/material';
import background from '@images/background.svg';

export default function AppLayout({ children }: PropsWithChildren) {
return <StyledContainer>
<Header />
<main>
{children}
</main>
<Footer />
</StyledContainer>;
}


const StyledContainer = styled(Container)(({ theme }) => theme.unstable_sx({
display: 'flex',
flexDirection: 'column',
minHeight: '100vh',
minWidth: '100vw',
main: {
flex: 1,
},
backgroundImage: `url(${background})`,
backgroundRepeat: 'no-repeat',
backgroundPosition: 'right bottom',
backgroundSize: 'auto 100%',
backgroundColor: theme.palette.secondary.main,
}))
1 change: 0 additions & 1 deletion src/logo.svg

This file was deleted.

10 changes: 10 additions & 0 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from '@app/App.tsx'
import '@assets/app.scss';

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
51 changes: 51 additions & 0 deletions src/pages/RestaurantsOptions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Box, styled } from '@mui/material';
import { useEffect, useState } from 'react';
import data from '@app/data/restaurants.ts';
import { RestaurantButton } from '@app/components/UI/Button/RestaurantButton.tsx';
import ToggleSwitch from '@app/components/UI/ToggleSwitch.tsx';
import ValidateButton from '@app/components/UI/Button/ValidateButton.tsx';

export interface Restaurant {
id: number
name: string
website: string
image: string
}

export default function RestaurantsOptions() {
const [pickedRestaurantIds, setPickedRestaurantIds] = useState<number[]>([]);
const [allSelected, setAllSelected] = useState<boolean>(false);

const dataRestaurants = data.restaurants;

useEffect(() => {
if (allSelected) {
setPickedRestaurantIds(dataRestaurants.map(restaurant => restaurant.id));
} else {
setPickedRestaurantIds([]);
}
},[allSelected, dataRestaurants]);

return <>
<ToggleSwitch label="Tout sélectionner" checked={allSelected} setChecked={setAllSelected}/>
<StyledContainer>
<ValidateButton/>
{dataRestaurants && dataRestaurants.map((restaurant: Restaurant) => {
return <RestaurantButton
key={restaurant.id}
id={restaurant.id}
name={restaurant.name}
website={restaurant.website}
image={restaurant.image}
pickedRestaurantIds={pickedRestaurantIds}
setPickedRestaurantIds={setPickedRestaurantIds}
/>
})}
</StyledContainer>
</>
}

const StyledContainer = styled(Box)(({ theme }) => theme.unstable_sx({
display: 'flex',
}));

13 changes: 0 additions & 13 deletions src/reportWebVitals.js

This file was deleted.

5 changes: 0 additions & 5 deletions src/setupTests.js

This file was deleted.

71 changes: 71 additions & 0 deletions src/theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { createTheme } from '@mui/material';
import PoppinsRegular from '@assets/fonts/poppins-regular.woff2';
import PoppinsBold from '@assets/fonts/poppins-bold.woff2';
import PoppinsSemiBold from '@assets/fonts/poppins-semibold.woff2';

const poppinsRegular = {
fontFamily: 'Poppins',
fontStyle: 'normal',
fontDisplay: 'swap',
fontWeight: 400,
src: `url(${PoppinsRegular}) format('woff2')`,
}

const poppinsBold = {
fontFamily: 'Poppins',
fontStyle: 'normal',
fontDisplay: 'swap',
fontWeight: 700,
src: `url(${PoppinsBold}) format('woff2')`,
}

const poppinsSemiBold = {
fontFamily: 'Poppins',
fontStyle: 'normal',
fontDisplay: 'swap',
fontWeight: 600,
src: `url(${PoppinsSemiBold}) format('woff2')`,
}

const theme = createTheme({
typography: {
fontFamily: [
'Poppins',
'sans-serif',
].join(','),
},
components: {
MuiCssBaseline: {
styleOverrides: {
html: [
{'@font-face': poppinsRegular},
{'@font-face': poppinsSemiBold},
{'@font-face': poppinsBold},
],
},
},
},
});

export const customTheme = createTheme({
...theme,
palette: {
primary: {
main: '#5135d1',
dark: '#3e2ca1',
light: '#bdb3ec',
contrastText: '#181A41',
},
secondary: {
main: '#181a41',
dark: '#2b2371',
light: '#3e2ca1',
contrastText: '#f3f2f9',
},
common: {
white: '#FFFFFF',
black: '#0C0D24',
},
},
});

1 change: 1 addition & 0 deletions src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite/client" />
43 changes: 43 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@app/*": [
"src/*"
],
"@assets/*": [
"assets/*"
],
"@images/*": [
"assets/images/*"
],
"@styles/*": [
"assets/styles/*"
],
},
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,

/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,

// https://www.typescriptlang.org/tsconfig#strictNullChecks
"strictNullChecks": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
12 changes: 12 additions & 0 deletions tsconfig.node.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": [
"vite.config.ts"
]
}
22 changes: 22 additions & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
],
resolve: {
alias: {
'@app': '/src',
'@assets': '/assets',
'@images': '/assets/images',
'@scss': '/assets/styles',
},
},
server: {
host: '0.0.0.0',
port: 63286,
strictPort: true,
},
})

0 comments on commit 936f6c1

Please sign in to comment.