merge dev in staging (#37)
69
.circleci/config.yml
Normal file
@ -0,0 +1,69 @@
|
||||
version: 2.1
|
||||
|
||||
orbs:
|
||||
kubernetes: circleci/kubernetes@1.0.0
|
||||
helm: circleci/helm@2.0.1
|
||||
|
||||
jobs:
|
||||
|
||||
build-push-docker-image:
|
||||
docker:
|
||||
- image: cimg/base:stable
|
||||
environment:
|
||||
TAG: << pipeline.git.tag >>
|
||||
steps:
|
||||
- checkout
|
||||
- add_ssh_keys:
|
||||
fingerprints:
|
||||
- "39:25:57:64:62:43:1f:98:b1:5e:75:53:87:d8:e7:71"
|
||||
- run: cp $HOME/.ssh/id_rsa_3925576462431f98b15e755387d8e771 id_rsa
|
||||
- setup_remote_docker:
|
||||
version: 20.10.12
|
||||
docker_layer_caching: true
|
||||
- run: docker login rg.fr-par.scw.cloud/lecoffre -u nologin -p $SCW_SECRET_KEY
|
||||
- run: docker build --tag rg.fr-par.scw.cloud/lecoffre/front:$TAG .
|
||||
- run: docker push rg.fr-par.scw.cloud/lecoffre/front:$TAG
|
||||
|
||||
|
||||
deploy-docker-image:
|
||||
docker:
|
||||
- image: cimg/base:stable
|
||||
environment:
|
||||
TAG: << pipeline.git.tag >>
|
||||
steps:
|
||||
- checkout
|
||||
- kubernetes/install-kubeconfig:
|
||||
kubeconfig: KUBECONFIG_DATA
|
||||
- helm/install-helm-client
|
||||
- run:
|
||||
name: Deploy
|
||||
command: >
|
||||
helm upgrade
|
||||
lecoffre-front devops/ -i -f devops/values.yaml
|
||||
-n lecoffre
|
||||
--create-namespace
|
||||
--set lecoffreFront.image.repository='rg.fr-par.scw.cloud/lecoffre/front'
|
||||
--set lecoffreFront.image.tag=$TAG
|
||||
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build-and-register:
|
||||
jobs:
|
||||
- build-push-docker-image:
|
||||
filters:
|
||||
tags:
|
||||
only: /^v.*/
|
||||
branches:
|
||||
ignore: /.*/
|
||||
- deploy-docker-image:
|
||||
requires:
|
||||
- build-push-docker-image
|
||||
context:
|
||||
- staging
|
||||
filters:
|
||||
tags:
|
||||
only: /^v.*/
|
||||
branches:
|
||||
ignore: /.*/
|
||||
|
6
.env.example
Normal file
@ -0,0 +1,6 @@
|
||||
BACK_API_PROTOCOL=
|
||||
BACK_API_HOSTNAME=
|
||||
BACK_API_PORT=
|
||||
BACK_API_ROOT_URL=
|
||||
BACK_API_VERSION=
|
||||
|
9
.eslintrc.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals",
|
||||
"rules": {
|
||||
"react/no-unescaped-entities": "off",
|
||||
"@next/next/no-page-custom-font": "off",
|
||||
"react/no-children-prop": "off"
|
||||
}
|
||||
|
||||
}
|
41
.gitignore
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
dist/
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
dist/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
.env
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
node_modules
|
||||
id_rsa
|
10
.prettierrc.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"printWidth": 140,
|
||||
"tabWidth": 4,
|
||||
"useTabs": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "all",
|
||||
"endOfLine": "lf",
|
||||
"semi": true,
|
||||
"bracketSameLine": true
|
||||
}
|
9
.vscode/custom.code-snippets
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"Media queries": {
|
||||
"prefix": "media",
|
||||
"body": [
|
||||
"@media(max-width: \\$screen-$1){$2}"
|
||||
],
|
||||
"description": "media queries"
|
||||
},
|
||||
}
|
37
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "vscode.json-language-features"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"terminal.integrated.env.linux": {
|
||||
"PATH": "${env:HOME}/elrondsdk/vendor-rust/bin:${env:HOME}/elrondsdk/erdpy-venv/bin:${env:HOME}/elrondsdk/vmtools:${env:HOME}/elrondsdk/nodejs/latest/bin:${env:PATH}",
|
||||
"VIRTUAL_ENV": "${env:HOME}/elrondsdk/erdpy-venv",
|
||||
"RUSTUP_HOME": "${env:HOME}/elrondsdk/vendor-rust",
|
||||
"CARGO_HOME": "${env:HOME}/elrondsdk/vendor-rust"
|
||||
},
|
||||
"terminal.integrated.env.osx": {
|
||||
"PATH": "${env:HOME}/elrondsdk/vendor-rust/bin:${env:HOME}/elrondsdk/erdpy-venv/bin:${env:HOME}/elrondsdk/vmtools:${env:HOME}/elrondsdk/nodejs/latest/bin:${env:PATH}",
|
||||
"VIRTUAL_ENV": "${env:HOME}/elrondsdk/erdpy-venv",
|
||||
"RUSTUP_HOME": "${env:HOME}/elrondsdk/vendor-rust",
|
||||
"CARGO_HOME": "${env:HOME}/elrondsdk/vendor-rust"
|
||||
},
|
||||
"terminal.integrated.environmentChangesIndicator": "on",
|
||||
"terminal.integrated.inheritEnv": true,
|
||||
"workbench.dialogs.customEnabled": true,
|
||||
"rust-client.rustupPath": "${env:HOME}/elrondsdk/vendor-rust/bin/rustup",
|
||||
"rust-client.rlsPath": "${env:HOME}/elrondsdk/vendor-rust/bin/rls",
|
||||
"rust-client.disableRustup": true,
|
||||
"rust-client.autoStartRls": false,
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
}
|
44
Dockerfile
Normal file
@ -0,0 +1,44 @@
|
||||
# Install dependencies only when needed
|
||||
FROM node:19-alpine AS deps
|
||||
|
||||
WORKDIR leCoffre-front
|
||||
|
||||
COPY package.json ./
|
||||
|
||||
RUN apk update && apk add openssh-client git
|
||||
|
||||
COPY id_rsa /root/.ssh/id_rsa
|
||||
RUN chmod 600 ~/.ssh/id_rsa
|
||||
RUN eval "$(ssh-agent -s)" && ssh-add /root/.ssh/id_rsa
|
||||
RUN ssh-keyscan github.com smart-chain-fr/leCoffre-resources.git >> /root/.ssh/known_hosts
|
||||
|
||||
RUN npm install --frozen-lockfile
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM node:19-alpine AS builder
|
||||
|
||||
WORKDIR leCoffre-front
|
||||
|
||||
COPY --from=deps leCoffre-front/node_modules ./node_modules
|
||||
COPY --from=deps leCoffre-front/package.json package.json
|
||||
COPY tsconfig.json tsconfig.json
|
||||
COPY src src
|
||||
|
||||
RUN npm run build
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM node:19-alpine AS production
|
||||
|
||||
WORKDIR leCoffre-front
|
||||
|
||||
RUN adduser -D lecoffreuser --uid 10000 && chown -R lecoffreuser .
|
||||
|
||||
COPY public ./public
|
||||
COPY --from=builder --chown=lecoffreuser leCoffre-front/node_modules ./node_modules
|
||||
COPY --from=builder --chown=lecoffreuser leCoffre-front/.next ./.next
|
||||
COPY --from=builder --chown=lecoffreuser leCoffre-front/package.json ./package.json
|
||||
|
||||
USER lecoffreuser
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
EXPOSE 3000
|
40
README.md
@ -1,2 +1,38 @@
|
||||
# leCoffre-front
|
||||
[owner: Hugo Lextrait] Le Coffre front
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
|
||||
|
||||
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
|
23
devops/.helmignore
Normal file
@ -0,0 +1,23 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
24
devops/Chart.yaml
Normal file
@ -0,0 +1,24 @@
|
||||
apiVersion: v2
|
||||
name: leCoffre-front
|
||||
description: A Helm chart for Kubernetes
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
#
|
||||
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||
# to be deployed.
|
||||
#
|
||||
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||
type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.0.1
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: 0.5.0
|
71
devops/templates/lecoffre-front.yaml
Normal file
@ -0,0 +1,71 @@
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: lecoffre-front
|
||||
namespace: {{ .Values.namespace }}
|
||||
{{if .Values.lecoffreFront.ingress.annotations}}
|
||||
annotations:
|
||||
{{toYaml .Values.lecoffreFront.ingress.annotations | indent 4 }}
|
||||
{{end}}
|
||||
spec:
|
||||
tls:
|
||||
- hosts: {{ .Values.lecoffreFront.ingress.tls.hosts }}
|
||||
secretName: {{ .Values.lecoffreFront.ingress.tls.secretName }}
|
||||
rules:
|
||||
- host: {{ .Values.lecoffreFront.ingress.host }}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: lecoffre-front-svc
|
||||
port:
|
||||
number: 80
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: lecoffre-front-svc
|
||||
namespace: {{ .Values.namespace }}
|
||||
labels:
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
name: http
|
||||
targetPort: 3000
|
||||
selector:
|
||||
app: lecoffre-front
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: lecoffre-front
|
||||
namespace: {{ .Values.namespace }}
|
||||
labels:
|
||||
app: lecoffre-front
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: lecoffre-front
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
{{toYaml .Values.lecoffreFront.vault.annotations | indent 8 }}
|
||||
labels:
|
||||
app: lecoffre-front
|
||||
spec:
|
||||
serviceAccountName: {{ .Values.lecoffreFront.serviceAccountName }}
|
||||
imagePullSecrets:
|
||||
- name: docker-pull-secret
|
||||
containers:
|
||||
- name: lecoffre-front
|
||||
image: "{{ .Values.lecoffreFront.image.repository }}:v{{ .Chart.AppVersion }}"
|
||||
{{if .Values.lecoffreFront.resources}}
|
||||
resources:
|
||||
{{toYaml .Values.lecoffreFront.resources | indent 10}}
|
||||
{{end}}
|
||||
imagePullPolicy: {{ .Values.lecoffreFront.image.pullPolicy }}
|
||||
command: [{{ .Values.lecoffreFront.command }}]
|
14
devops/templates/service-account.yaml
Normal file
@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ .Values.lecoffreFront.serviceAccountName }}
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ .Values.lecoffreFront.serviceAccountName }}-token
|
||||
annotations:
|
||||
kubernetes.io/service-account.name: {{ .Values.lecoffreFront.serviceAccountName }}
|
||||
type: kubernetes.io/service-account-token
|
44
devops/values.yaml
Normal file
@ -0,0 +1,44 @@
|
||||
dockerPullSecret: secret/data/lecoffre-front-stg/config/dockerpullsecret
|
||||
|
||||
namespace: lecoffre
|
||||
|
||||
lecoffreFront:
|
||||
serviceAccountName: lecoffre-front-sa
|
||||
command: "'sh', '-c', '. /vault/secrets/envs && npm run start'"
|
||||
vault:
|
||||
role : custom_lecoffre-front_injector_rol
|
||||
server: https://vault-stg.smart-chain.fr
|
||||
annotations:
|
||||
vault.hashicorp.com/agent-pre-populate-only: "true"
|
||||
vault.hashicorp.com/agent-inject: "true"
|
||||
vault.hashicorp.com/agent-inject-secret-envs: secret/data/lecoffre-front-stg/config/envs
|
||||
vault.hashicorp.com/role: custom_lecoffre-front_injector_rol
|
||||
vault.hashicorp.com/agent-inject-template-envs: |
|
||||
{{ with secret "secret/data/lecoffre-front-stg/config/envs" }}
|
||||
{{ range $k, $v := .Data.data }}
|
||||
export {{ $k }}="{{ $v }}"
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
imagePullSecrets:
|
||||
- name: docker-pull-secret
|
||||
image:
|
||||
pullPolicy: Always
|
||||
repository: "rg.fr-par.scw.cloud/lecoffre/front"
|
||||
resources:
|
||||
requests:
|
||||
cpu: 200m
|
||||
memory: 1Gi
|
||||
limits:
|
||||
memory: 2Gi
|
||||
ingress:
|
||||
host: app.stg.lecoffre.smart-chain.fr
|
||||
tls:
|
||||
hosts:
|
||||
- app.stg.lecoffre.smart-chain.fr
|
||||
secretName: app-tls
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: nginx
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/from-to-www-redirect: "true"
|
||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||
|
19
next.config.js
Normal file
@ -0,0 +1,19 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
|
||||
const nextConfig = {
|
||||
reactStrictMode: false,
|
||||
publicRuntimeConfig: {
|
||||
// Will be available on both server and client
|
||||
BACK_API_PROTOCOL: process.env.BACK_API_PROTOCOL,
|
||||
BACK_API_HOST: process.env.BACK_API_HOST,
|
||||
BACK_API_PORT: process.env.BACK_API_PORT,
|
||||
BACK_API_ROOT_URL: process.env.BACK_API_ROOT_URL,
|
||||
BACK_API_VERSION: process.env.BACK_API_VERSION,
|
||||
FRONT_APP_HOST: process.env.FRONT_APP_HOST,
|
||||
FRONT_APP_PORT: process.env.FRONT_APP_PORT,
|
||||
IDNOT_AUTHORIZE_ENDPOINT: process.env.IDNOT_AUTHORIZE_ENDPOINT,
|
||||
IDNOT_CLIENT_ID: process.env.IDNOT_CLIENT_ID,
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
4669
package-lock.json
generated
Normal file
36
package.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "lecoffre-front",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next build && next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"format": "prettier --write src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/styled": "^11.10.6",
|
||||
"@mui/material": "^5.11.13",
|
||||
"@types/node": "18.15.1",
|
||||
"@types/react": "18.0.28",
|
||||
"@types/react-dom": "18.0.11",
|
||||
"class-validator": "^0.14.0",
|
||||
"classnames": "^2.3.2",
|
||||
"dotenv": "^16.0.3",
|
||||
"eslint": "8.36.0",
|
||||
"eslint-config-next": "13.2.4",
|
||||
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.40",
|
||||
"form-data": "^4.0.0",
|
||||
"next": "13.2.4",
|
||||
"prettier": "^2.8.7",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-select": "^5.7.2",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"sass": "^1.59.2",
|
||||
"typedi": "^0.10.0",
|
||||
"typescript": "4.9.5"
|
||||
}
|
||||
}
|
22
public/favicon.svg
Normal file
@ -0,0 +1,22 @@
|
||||
<svg width="44" height="56" viewBox="0 0 44 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_3466_113133)">
|
||||
<path d="M43.8828 9.98633C43.9035 9.4084 43.5713 8.81895 42.8538 8.51041L24.3282 0.58968C22.8379 -0.0481226 21.0845 -0.0481226 19.5942 0.58968L1.17241 8.46666C0.443378 8.7798 0.0996282 9.37616 0.115778 9.96331V46.1454L4.25693 48.091L6.73008 46.9098L18.4499 51.7336V54.1858L22.0396 55.869L25.6617 54.1398V51.7544L37.3584 46.9397L39.8201 48.0887L43.8874 46.1477V9.98633H43.8828Z" fill="#C5B2D4"/>
|
||||
<path d="M22.0369 55.9998L18.3318 54.2614V51.8115L6.73196 47.036L4.2565 48.2172L0 46.2209V9.85003C0.0322987 9.21222 0.454488 8.6481 1.12584 8.36259L19.5499 0.483307C21.061 -0.163706 22.8651 -0.163706 24.374 0.483307L42.8995 8.40403C43.5617 8.68725 43.9723 9.24216 43.9977 9.87075V46.2232L39.8173 48.2195L37.3534 47.0682L25.7743 51.8345V54.2153L22.0369 55.9998ZM18.5625 54.1163L22.0369 55.7465L25.5459 54.0725V51.6825L37.3626 46.8173L39.8196 47.9639L43.7716 46.0781V10.101H43.767V9.98127C43.7924 9.39412 43.4325 8.88296 42.8096 8.61587L24.284 0.69514C22.8305 0.073455 21.0956 0.073455 19.6422 0.69514L1.21812 8.57212C0.583683 8.84382 0.216862 9.36189 0.233012 9.96055V10.078V46.0712L4.25881 47.9616L6.72966 46.7827L18.5671 51.6549V54.1117L18.5625 54.1163Z" fill="#4E1480"/>
|
||||
<path d="M18.0574 49.0688C17.5222 49.0688 16.9823 48.9629 16.4724 48.7441L4.4412 43.6325C2.94854 42.9993 1.98419 41.5441 1.98419 39.9254L1.97266 17.4319C1.97266 16.0711 2.65093 14.8116 3.79061 14.0633C4.93029 13.315 6.35835 13.1906 7.61108 13.7294L19.6539 18.9171C21.135 19.5549 22.0924 21.0078 22.0924 22.6195V45.037C22.0924 46.3932 21.4141 47.6504 20.2814 48.401C19.61 48.8454 18.8349 49.0711 18.0551 49.0711L18.0574 49.0688ZM6.00999 14.0863C5.36632 14.0863 4.72727 14.2728 4.17127 14.6366C3.22769 15.256 2.66246 16.3014 2.66477 17.4296L2.67631 39.9231C2.67631 41.2632 3.47685 42.4697 4.71343 42.9947L16.7447 48.1063C17.7805 48.5461 18.9617 48.4402 19.9007 47.8185C20.842 47.1991 21.4026 46.1561 21.4026 45.0324V22.6149C21.4026 21.2795 20.609 20.0775 19.3816 19.5479L7.33885 14.3603C6.91204 14.1761 6.45986 14.0863 6.00999 14.0863Z" fill="white"/>
|
||||
<path d="M3.09274 20.3118V19.1352C3.09274 19.1052 3.07428 19.0776 3.0466 19.0661L1.58855 18.4375C1.53779 18.4168 1.48242 18.4536 1.48242 18.5066V19.6832C1.48242 19.7131 1.50088 19.7407 1.52856 19.7523L2.98431 20.3808C3.03506 20.4016 3.09043 20.3647 3.09043 20.3118H3.09274Z" fill="white"/>
|
||||
<path d="M3.01696 20.7323C2.95929 20.7323 2.90392 20.7208 2.84855 20.6978L1.3928 20.0715C1.23823 20.0047 1.13672 19.8527 1.13672 19.6846V18.508C1.13672 18.3653 1.20824 18.234 1.3259 18.1558C1.44586 18.0775 1.59582 18.0637 1.72502 18.1212L3.18307 18.7498C3.33764 18.8166 3.43915 18.9686 3.43915 19.1366V20.3132C3.43915 20.456 3.36763 20.5872 3.24767 20.6655C3.17846 20.7116 3.09771 20.7346 3.01696 20.7346V20.7323ZM1.82883 19.505L2.74704 19.9011V19.3116L1.82883 18.9156V19.505Z" fill="white"/>
|
||||
<path d="M3.14743 38.7512V37.5746C3.14743 37.5447 3.12897 37.5171 3.10129 37.5055L1.64323 36.877C1.59248 36.8562 1.53711 36.8931 1.53711 36.946V38.1226C1.53711 38.1526 1.55557 38.1802 1.58325 38.1917L3.039 38.8203C3.08975 38.841 3.14512 38.8042 3.14512 38.7512H3.14743Z" fill="white"/>
|
||||
<path d="M3.0713 39.1721C3.01362 39.1721 2.95825 39.1606 2.90519 39.1376L1.44713 38.509C1.29256 38.4422 1.19336 38.2902 1.19336 38.1221V36.9455C1.19336 36.8051 1.26257 36.6715 1.38254 36.5933C1.5025 36.515 1.65015 36.5012 1.78166 36.5587L3.23971 37.1873C3.39428 37.2541 3.49349 37.4061 3.49349 37.5741V38.7507C3.49349 38.8935 3.42197 39.0247 3.30431 39.103C3.2351 39.1491 3.15435 39.1721 3.0713 39.1721ZM1.88317 37.9448L2.80137 38.3409V37.7514L1.88317 37.3554V37.9448Z" fill="white"/>
|
||||
<path d="M22.415 26.6204V25.4438C22.415 25.4138 22.3965 25.3862 22.3689 25.3747L20.9108 24.7461C20.8601 24.7254 20.8047 24.7622 20.8047 24.8152V25.9918C20.8047 26.0217 20.8231 26.0493 20.8508 26.0608L22.3066 26.6894C22.3573 26.7102 22.4127 26.6733 22.4127 26.6204H22.415Z" fill="white"/>
|
||||
<path d="M22.3392 27.0412C22.2816 27.0412 22.2262 27.0297 22.1708 27.0067L20.7151 26.3781C20.5605 26.3113 20.459 26.1594 20.459 25.9913V24.8147C20.459 24.6719 20.5305 24.5407 20.6482 24.4624C20.7681 24.3841 20.9158 24.3703 21.0473 24.4279L22.5053 25.0565C22.6599 25.1232 22.7614 25.2752 22.7614 25.4433V26.6199C22.7614 26.7626 22.6899 26.8939 22.5699 26.9722C22.5007 27.0182 22.42 27.0412 22.3392 27.0412ZM21.1511 25.814L22.0693 26.21V25.6206L21.1511 25.2245V25.814Z" fill="white"/>
|
||||
<path d="M22.4677 45.0618V43.8852C22.4677 43.8552 22.4493 43.8276 22.4216 43.8161L20.9635 43.1875C20.9128 43.1668 20.8574 43.2036 20.8574 43.2566V44.4332C20.8574 44.4631 20.8759 44.4907 20.9036 44.5023L22.3593 45.1308C22.4101 45.1516 22.4654 45.1147 22.4654 45.0618H22.4677Z" fill="white"/>
|
||||
<path d="M22.3916 45.4823C22.3339 45.4823 22.2786 45.4708 22.2232 45.4478L20.7674 44.8215C20.6129 44.7547 20.5137 44.6027 20.5137 44.4346V43.258C20.5137 43.1176 20.5829 42.984 20.7028 42.9058C20.8228 42.8275 20.9728 42.8137 21.102 42.8712L22.56 43.4998C22.7146 43.5666 22.8138 43.7186 22.8138 43.8866V45.0632C22.8138 45.206 22.7423 45.3372 22.6223 45.4155C22.5531 45.4616 22.4724 45.4846 22.3916 45.4846V45.4823ZM21.2035 44.255L22.1217 44.6511V44.0616L21.2035 43.6656V44.255Z" fill="white"/>
|
||||
<path d="M8.47164 24.259C6.02617 25.2099 5.44248 29.5617 7.16585 33.9803C8.88921 38.3989 12.269 41.2126 14.7145 40.2616C17.16 39.3107 17.7437 34.9589 16.0203 30.5403C14.2969 26.1217 10.9171 23.308 8.47164 24.259ZM12.5989 32.3478C12.5713 32.2442 12.5367 32.1406 12.4974 32.037C12.4144 31.8228 12.3129 31.6294 12.1975 31.4567L12.7835 27.0911C13.7248 28.0098 14.5876 29.3177 15.1944 30.8742C15.7827 32.3823 16.0364 33.8813 15.9857 35.1661L12.5989 32.3478ZM9.10377 25.804C9.92738 25.4839 10.8802 25.6935 11.8122 26.3036L11.2216 30.6946C11.1178 30.6808 11.0163 30.69 10.924 30.7268C10.8664 30.7498 10.8156 30.7821 10.7671 30.8212L7.43347 28.0467C7.70339 26.9437 8.26862 26.1263 9.10377 25.804ZM8.04945 33.651C7.45192 32.1152 7.19815 30.591 7.26044 29.29L10.458 31.9518C10.4811 32.205 10.5457 32.479 10.6518 32.753C10.7187 32.9234 10.7971 33.08 10.8825 33.2251L10.3334 37.3075C9.4406 36.398 8.62852 35.1362 8.04945 33.651ZM11.3001 38.1341L11.8445 34.077C11.976 34.1092 12.1052 34.1069 12.2229 34.0609C12.3752 34.001 12.4905 33.879 12.5713 33.7132L15.8219 36.4187C15.5566 37.5515 14.9867 38.3897 14.1377 38.7212C13.2772 39.0551 12.2713 38.811 11.2978 38.1341H11.3001Z" fill="white"/>
|
||||
<path d="M12.1448 31.9302C12.5924 32.6509 12.5878 33.4591 12.1656 33.7147C11.7434 33.9703 11.0167 33.595 10.6199 32.8466C10.1977 32.0523 10.1885 31.3339 10.583 31.0852C11.0421 30.7951 11.6904 31.1957 12.1471 31.9325L12.1448 31.9302Z" fill="#3FA79E"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_3466_113133">
|
||||
<rect width="44" height="56" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 6.6 KiB |
15
public/manifest.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"short_name": "lecoffre",
|
||||
"name": "lecoffre",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicon.ico",
|
||||
"sizes": "32x32 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "light",
|
||||
"background_color": "light"
|
||||
}
|
29
src/front/Api/Auth/IdNot/index.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { Service } from "typedi";
|
||||
import BaseApiService from "@Front/Api/BaseApiService";
|
||||
import { FrontendVariables } from "@Front/Config/VariablesFront";
|
||||
|
||||
@Service()
|
||||
export default class Auth extends BaseApiService {
|
||||
private static instance: Auth;
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static getInstance(): Auth {
|
||||
return (this.instance = this.instance ?? new this());
|
||||
}
|
||||
|
||||
public async getIdnotJwt(autorizationCode: string | string[]): Promise<any> {
|
||||
const variables = FrontendVariables.getInstance();
|
||||
const baseBackUrl =
|
||||
variables.BACK_API_PROTOCOL + variables.BACK_API_HOST + (variables.BACK_API_PORT ? ":" + variables.BACK_API_PORT : "");
|
||||
const url = new URL(`${baseBackUrl}/api/v1/idnot-user/${autorizationCode}`);
|
||||
try {
|
||||
return await this.postRequest<any>(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
}
|
139
src/front/Api/BaseApiService.ts
Normal file
@ -0,0 +1,139 @@
|
||||
import { FrontendVariables } from "@Front/Config/VariablesFront";
|
||||
|
||||
export enum ContentType {
|
||||
JSON = "application/json",
|
||||
FORM_DATA = "multipart/form-data;",
|
||||
}
|
||||
export default abstract class BaseApiService {
|
||||
private static baseUrl: string;
|
||||
protected readonly variables = FrontendVariables.getInstance();
|
||||
|
||||
protected constructor() {
|
||||
BaseApiService.baseUrl ??=
|
||||
FrontendVariables.getInstance().BACK_API_PROTOCOL +
|
||||
FrontendVariables.getInstance().BACK_API_HOST +
|
||||
(FrontendVariables.getInstance().BACK_API_PORT ? ":" + FrontendVariables.getInstance().BACK_API_PORT : "") +
|
||||
FrontendVariables.getInstance().BACK_API_ROOT_URL +
|
||||
FrontendVariables.getInstance().BACK_API_VERSION;
|
||||
}
|
||||
|
||||
protected getBaseUrl() {
|
||||
return BaseApiService.baseUrl;
|
||||
}
|
||||
|
||||
protected buildHeaders(contentType: ContentType) {
|
||||
const headers = new Headers();
|
||||
|
||||
if (contentType === ContentType.JSON) {
|
||||
headers.set("Content-Type", contentType);
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
protected buildBody(body: { [key: string]: unknown }): string {
|
||||
return JSON.stringify(body);
|
||||
}
|
||||
|
||||
protected async getRequest<T>(url: URL) {
|
||||
const request = async () =>
|
||||
await fetch(url, {
|
||||
method: "GET",
|
||||
headers: this.buildHeaders(ContentType.JSON),
|
||||
});
|
||||
return this.sendRequest<T>(request);
|
||||
}
|
||||
|
||||
protected async postRequest<T>(url: URL, body: { [key: string]: unknown } = {}) {
|
||||
return this.sendRequest<T>(
|
||||
async () =>
|
||||
await fetch(url, {
|
||||
method: "POST",
|
||||
headers: this.buildHeaders(ContentType.JSON),
|
||||
body: this.buildBody(body),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
protected async postRequestFormData<T>(url: URL, body: FormData) {
|
||||
return this.sendRequest<T>(
|
||||
async () =>
|
||||
await fetch(url, {
|
||||
method: "POST",
|
||||
headers: this.buildHeaders(ContentType.FORM_DATA),
|
||||
body,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
protected async putRequest<T>(url: URL, body: { [key: string]: unknown } = {}) {
|
||||
const request = async () =>
|
||||
await fetch(url, {
|
||||
method: "PUT",
|
||||
headers: this.buildHeaders(ContentType.JSON),
|
||||
body: this.buildBody(body),
|
||||
});
|
||||
|
||||
return this.sendRequest<T>(request);
|
||||
}
|
||||
|
||||
protected async patchRequest<T>(url: URL, body: { [key: string]: unknown } = {}) {
|
||||
const request = async () =>
|
||||
await fetch(url, {
|
||||
method: "PATCH",
|
||||
headers: this.buildHeaders(ContentType.JSON),
|
||||
body: this.buildBody(body),
|
||||
});
|
||||
|
||||
return this.sendRequest<T>(request);
|
||||
}
|
||||
|
||||
protected async deleteRequest<T>(url: URL, body: { [key: string]: unknown } = {}) {
|
||||
const request = async () =>
|
||||
await fetch(url, {
|
||||
method: "DELETE",
|
||||
headers: this.buildHeaders(ContentType.JSON),
|
||||
body: this.buildBody(body),
|
||||
});
|
||||
|
||||
return this.sendRequest<T>(request);
|
||||
}
|
||||
|
||||
protected async putFormDataRequest<T>(url: URL, body: FormData) {
|
||||
const request = async () =>
|
||||
await fetch(url, {
|
||||
method: "PUT",
|
||||
headers: this.buildHeaders(ContentType.FORM_DATA),
|
||||
body,
|
||||
});
|
||||
|
||||
return this.sendRequest<T>(request);
|
||||
}
|
||||
|
||||
private async sendRequest<T>(request: () => Promise<Response>): Promise<T> {
|
||||
const response = await request();
|
||||
return this.processResponse<T>(response, request);
|
||||
}
|
||||
|
||||
protected async processResponse<T>(response: Response, request: () => Promise<Response>): Promise<T> {
|
||||
let responseJson: any | null;
|
||||
try {
|
||||
responseJson = await response.json();
|
||||
} catch (err: unknown) {
|
||||
responseJson = null;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
return Promise.reject(response);
|
||||
}
|
||||
|
||||
return responseJson as T;
|
||||
}
|
||||
|
||||
protected onError(error: unknown) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IResponse {
|
||||
http_status: number;
|
||||
}
|
5
src/front/Api/LeCoffreApi/Customer/BaseCustomer.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import BaseApiService from "@Front/Api/BaseApiService";
|
||||
|
||||
export default abstract class BaseNotary extends BaseApiService {
|
||||
protected readonly namespaceUrl = this.getBaseUrl().concat("/customers");
|
||||
}
|
51
src/front/Api/LeCoffreApi/Customer/Users/Users.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { Service } from "typedi";
|
||||
import BaseNotary from "../BaseCustomer";
|
||||
import User from "le-coffre-resources/dist/Notary";
|
||||
|
||||
@Service()
|
||||
export default class Users extends BaseNotary {
|
||||
private static instance: Users;
|
||||
private readonly baseURl = this.namespaceUrl.concat("/Users");
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static getInstance() {
|
||||
if (!this.instance) {
|
||||
return new Users();
|
||||
} else {
|
||||
return this.instance;
|
||||
}
|
||||
}
|
||||
|
||||
public async get(): Promise<User[]> {
|
||||
const url = new URL(this.baseURl);
|
||||
try {
|
||||
return await this.getRequest<User[]>(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async getOne(uid: string): Promise<User> {
|
||||
const url = new URL(this.baseURl.concat("/").concat(uid));
|
||||
try {
|
||||
return await this.getRequest<User>(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
// public async post(params: User): Promise<User> {
|
||||
// const url = new URL(this.baseURl);
|
||||
// try {
|
||||
// return await this.postRequest<User>(url, params);
|
||||
// } catch (err) {
|
||||
// this.onError(err);
|
||||
// return Promise.reject(err);
|
||||
// }
|
||||
// }
|
||||
}
|
5
src/front/Api/LeCoffreApi/Notary/BaseNotary.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import BaseApiService from "@Front/Api/BaseApiService";
|
||||
|
||||
export default abstract class BaseNotary extends BaseApiService {
|
||||
protected readonly namespaceUrl = this.getBaseUrl().concat("/notary");
|
||||
}
|
66
src/front/Api/LeCoffreApi/Notary/Users/Users.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { Service } from "typedi";
|
||||
import BaseNotary from "../BaseNotary";
|
||||
import User from "le-coffre-resources/dist/Notary";
|
||||
|
||||
@Service()
|
||||
export default class Users extends BaseNotary {
|
||||
private static instance: Users;
|
||||
private readonly baseURl = this.namespaceUrl.concat("/Users");
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static getInstance() {
|
||||
if (!this.instance) {
|
||||
return new Users();
|
||||
} else {
|
||||
return this.instance;
|
||||
}
|
||||
}
|
||||
|
||||
public async get(): Promise<User[]> {
|
||||
const url = new URL(this.baseURl);
|
||||
try {
|
||||
return await this.getRequest<User[]>(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async getAuthorizationCode(): Promise<any> {
|
||||
try {
|
||||
const url = new URL(`https://qual-connexion.idnot.fr/IdPOAuth2/authorize/idnot_idp_v1?
|
||||
client_id=4501646203F3EF67
|
||||
&redirect_uri=https://app.stg.lecoffre.smart-chain.fr/
|
||||
&scope=openid,profile,offline_access
|
||||
&response_type=code`);
|
||||
// const url = new URL("https://jsonplaceholder.typicode.com/todos/1");
|
||||
await this.getRequest(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async getByUid(uid: string): Promise<User> {
|
||||
const url = new URL(this.baseURl.concat("/").concat(uid));
|
||||
try {
|
||||
return await this.getRequest<User>(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
// public async post(params: User): Promise<User> {
|
||||
// const url = new URL(this.baseURl);
|
||||
// try {
|
||||
// return await this.postRequest<User>(url, params);
|
||||
// } catch (err) {
|
||||
// this.onError(err);
|
||||
// return Promise.reject(err);
|
||||
// }
|
||||
// }
|
||||
}
|
5
src/front/Api/LeCoffreApi/SuperAdmin/BaseSuperAdmin.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import BaseApiService from "@Front/Api/BaseApiService";
|
||||
|
||||
export default abstract class BaseSuperAdmin extends BaseApiService {
|
||||
protected readonly namespaceUrl = this.getBaseUrl().concat("/super-admin");
|
||||
}
|
92
src/front/Api/LeCoffreApi/SuperAdmin/Customers/Customers.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import { Contact, Customer } from "le-coffre-resources/dist/SuperAdmin";
|
||||
import { Service } from "typedi";
|
||||
|
||||
import BaseSuperAdmin from "../BaseSuperAdmin";
|
||||
import { ECivility } from "le-coffre-resources/dist/Customer/Contact";
|
||||
|
||||
// TODO Type get query params -> Where + inclue + orderby
|
||||
export interface IGetCustomersparams {
|
||||
where?: {};
|
||||
include?: {};
|
||||
}
|
||||
|
||||
// TODO Type getbyuid query params
|
||||
|
||||
export type IPutCustomersParams = {
|
||||
uid?: Customer["uid"];
|
||||
contact?: Customer["contact"];
|
||||
};
|
||||
|
||||
export interface IPostCustomersParams {
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
email: string;
|
||||
cell_phone_number: string;
|
||||
civility: ECivility;
|
||||
address?: Contact["address"];
|
||||
}
|
||||
|
||||
@Service()
|
||||
export default class Customers extends BaseSuperAdmin {
|
||||
private static instance: Customers;
|
||||
private readonly baseURl = this.namespaceUrl.concat("/customers");
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static getInstance() {
|
||||
if (!this.instance) {
|
||||
return new this();
|
||||
} else {
|
||||
return this.instance;
|
||||
}
|
||||
}
|
||||
|
||||
public async get(q: IGetCustomersparams): Promise<Customer[]> {
|
||||
const url = new URL(this.baseURl);
|
||||
const query = { q };
|
||||
Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
|
||||
try {
|
||||
return await this.getRequest<Customer[]>(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description : Create a Customer
|
||||
*/
|
||||
public async post(body: any): Promise<Customer> {
|
||||
const url = new URL(this.baseURl);
|
||||
try {
|
||||
return await this.postRequest<Customer>(url, body);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async getByUid(uid: string, q?: any): Promise<Customer> {
|
||||
const url = new URL(this.baseURl.concat(`/${uid}`));
|
||||
const query = { q };
|
||||
if (q) Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
|
||||
try {
|
||||
return await this.getRequest<Customer>(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async put(uid: string, body: IPutCustomersParams): Promise<Customer> {
|
||||
const url = new URL(this.baseURl.concat(`/${uid}`));
|
||||
try {
|
||||
return await this.putRequest<Customer>(url, body);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
}
|
92
src/front/Api/LeCoffreApi/SuperAdmin/DeedTypes/DeedTypes.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import { Service } from "typedi";
|
||||
import { DeedType } from "le-coffre-resources/dist/Notary";
|
||||
import BaseSuperAdmin from "../BaseSuperAdmin";
|
||||
|
||||
// TODO Type get query params -> Where + inclue + orderby
|
||||
export interface IGetDeedTypesParams {
|
||||
q?: {};
|
||||
}
|
||||
|
||||
// TODO Type getbyuid query params
|
||||
|
||||
export type IPutDeedTypesParams = {
|
||||
uid?: DeedType["uid"];
|
||||
name?: DeedType["name"];
|
||||
description?: DeedType["description"];
|
||||
deed?: DeedType["deed"];
|
||||
office?: DeedType["office"];
|
||||
archived_at?: DeedType["archived_at"];
|
||||
deed_type_has_document_types?: DeedType["deed_type_has_document_types"];
|
||||
};
|
||||
|
||||
@Service()
|
||||
export default class DeedTypes extends BaseSuperAdmin {
|
||||
private static instance: DeedTypes;
|
||||
private readonly baseURl = this.namespaceUrl.concat("/deed-types");
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static getInstance() {
|
||||
if (!this.instance) {
|
||||
return new this();
|
||||
} else {
|
||||
return this.instance;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description : Get all DeedTypes
|
||||
*/
|
||||
public async get(q: IGetDeedTypesParams): Promise<DeedType[]> {
|
||||
const url = new URL(this.baseURl);
|
||||
Object.entries(q).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
|
||||
try {
|
||||
return await this.getRequest<DeedType[]>(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description : Get a folder by uid
|
||||
*/
|
||||
public async getByUid(uid: string, q?: any): Promise<DeedType> {
|
||||
const url = new URL(this.baseURl.concat(`/${uid}`));
|
||||
if (q) Object.entries(q).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
|
||||
try {
|
||||
return await this.getRequest<DeedType>(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description : Create a deed
|
||||
*/
|
||||
// public async post(body: IPostDeedTypesParams): Promise<OfficeFolder> {
|
||||
// const url = new URL(this.baseURl);
|
||||
// try {
|
||||
// return await this.postRequest<OfficeFolder>(url, body);
|
||||
// } catch (err) {
|
||||
// this.onError(err);
|
||||
// return Promise.reject(err);
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* @description : Update the folder description
|
||||
*/
|
||||
public async put(uid: string, body: IPutDeedTypesParams): Promise<DeedType> {
|
||||
const url = new URL(this.baseURl.concat(`/${uid}`));
|
||||
try {
|
||||
return await this.putRequest<DeedType>(url, body);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
}
|
92
src/front/Api/LeCoffreApi/SuperAdmin/Deeds/Deeds.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import { Service } from "typedi";
|
||||
import { Deed, OfficeFolder } from "le-coffre-resources/dist/Notary";
|
||||
import BaseSuperAdmin from "../BaseSuperAdmin";
|
||||
|
||||
// TODO Type get query params -> Where + inclue + orderby
|
||||
export interface IGetDeedsParams {
|
||||
q?: {};
|
||||
}
|
||||
|
||||
// TODO Type getbyuid query params
|
||||
|
||||
export type IPutDeedsParams = {
|
||||
uid?: OfficeFolder["uid"];
|
||||
folder_number?: OfficeFolder["folder_number"];
|
||||
name?: OfficeFolder["name"];
|
||||
description?: OfficeFolder["description"];
|
||||
archived_description?: OfficeFolder["archived_description"];
|
||||
status?: OfficeFolder["status"];
|
||||
deed_has_document_types?: Deed["deed_has_document_types"];
|
||||
};
|
||||
|
||||
@Service()
|
||||
export default class Deeds extends BaseSuperAdmin {
|
||||
private static instance: Deeds;
|
||||
private readonly baseURl = this.namespaceUrl.concat("/deeds");
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static getInstance() {
|
||||
if (!this.instance) {
|
||||
return new this();
|
||||
} else {
|
||||
return this.instance;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description : Get all deeds
|
||||
*/
|
||||
public async get(q: IGetDeedsParams): Promise<Deed[]> {
|
||||
const url = new URL(this.baseURl);
|
||||
Object.entries(q).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
|
||||
try {
|
||||
return await this.getRequest<Deed[]>(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description : Get a folder by uid
|
||||
*/
|
||||
public async getByUid(uid: string, q?: any): Promise<Deed> {
|
||||
const url = new URL(this.baseURl.concat(`/${uid}`));
|
||||
if (q) Object.entries(q).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
|
||||
try {
|
||||
return await this.getRequest<Deed>(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description : Create a deed
|
||||
*/
|
||||
// public async post(body: IPostDeedsParams): Promise<OfficeFolder> {
|
||||
// const url = new URL(this.baseURl);
|
||||
// try {
|
||||
// return await this.postRequest<OfficeFolder>(url, body);
|
||||
// } catch (err) {
|
||||
// this.onError(err);
|
||||
// return Promise.reject(err);
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* @description : Update the folder description
|
||||
*/
|
||||
public async put(uid: string, body: IPutDeedsParams): Promise<Deed> {
|
||||
const url = new URL(this.baseURl.concat(`/${uid}`));
|
||||
try {
|
||||
return await this.putRequest<Deed>(url, body);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
import { DocumentType } from "le-coffre-resources/dist/SuperAdmin";
|
||||
import { Service } from "typedi";
|
||||
|
||||
import BaseSuperAdmin from "../BaseSuperAdmin";
|
||||
|
||||
// TODO Type get query params -> Where + inclue + orderby
|
||||
export interface IGetDocumentTypesparams {
|
||||
where?: {};
|
||||
include?: {};
|
||||
}
|
||||
|
||||
// TODO Type getbyuid query params
|
||||
|
||||
export type IPutDocumentTypesParams = {
|
||||
};
|
||||
|
||||
export interface IPostDocumentTypesParams {
|
||||
name: string;
|
||||
public_description: string;
|
||||
private_description: string;
|
||||
office: {
|
||||
uid: string;
|
||||
};
|
||||
}
|
||||
|
||||
@Service()
|
||||
export default class DocumentTypes extends BaseSuperAdmin {
|
||||
private static instance: DocumentTypes;
|
||||
private readonly baseURl = this.namespaceUrl.concat("/document-types");
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static getInstance() {
|
||||
if (!this.instance) {
|
||||
return new this();
|
||||
} else {
|
||||
return this.instance;
|
||||
}
|
||||
}
|
||||
|
||||
public async get(q: IGetDocumentTypesparams): Promise<DocumentType[]> {
|
||||
const url = new URL(this.baseURl);
|
||||
Object.entries(q).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
|
||||
try {
|
||||
return await this.getRequest<DocumentType[]>(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description : Create a Document
|
||||
*/
|
||||
public async post(body: IPostDocumentTypesParams): Promise<DocumentType> {
|
||||
const url = new URL(this.baseURl);
|
||||
try {
|
||||
return await this.postRequest<DocumentType>(url, body as any);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async getByUid(uid: string, q?: any): Promise<DocumentType> {
|
||||
const url = new URL(this.baseURl.concat(`/${uid}`));
|
||||
const query = { q };
|
||||
if (q) Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
|
||||
try {
|
||||
return await this.getRequest<DocumentType>(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async put(uid: string, body: IPutDocumentTypesParams): Promise<DocumentType> {
|
||||
const url = new URL(this.baseURl.concat(`/${uid}`));
|
||||
try {
|
||||
return await this.putRequest<DocumentType>(url, body);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
}
|
95
src/front/Api/LeCoffreApi/SuperAdmin/Documents/Documents.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import { Document } from "le-coffre-resources/dist/SuperAdmin";
|
||||
import { Service } from "typedi";
|
||||
|
||||
import BaseSuperAdmin from "../BaseSuperAdmin";
|
||||
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
|
||||
|
||||
// TODO Type get query params -> Where + inclue + orderby
|
||||
export interface IGetDocumentsparams {
|
||||
where?: {};
|
||||
include?: {};
|
||||
}
|
||||
|
||||
// TODO Type getbyuid query params
|
||||
|
||||
export type IPutDocumentsParams = {
|
||||
document_status?: EDocumentStatus;
|
||||
refused_reason?: string;
|
||||
};
|
||||
|
||||
export interface IPostDocumentsParams {}
|
||||
|
||||
@Service()
|
||||
export default class Documents extends BaseSuperAdmin {
|
||||
private static instance: Documents;
|
||||
private readonly baseURl = this.namespaceUrl.concat("/documents");
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static getInstance() {
|
||||
if (!this.instance) {
|
||||
return new this();
|
||||
} else {
|
||||
return this.instance;
|
||||
}
|
||||
}
|
||||
|
||||
public async get(q: IGetDocumentsparams): Promise<Document[]> {
|
||||
const url = new URL(this.baseURl);
|
||||
const query = { q };
|
||||
if (q) Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
|
||||
try {
|
||||
return await this.getRequest<Document[]>(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description : Create a Document
|
||||
*/
|
||||
public async post(body: any): Promise<Document> {
|
||||
const url = new URL(this.baseURl);
|
||||
try {
|
||||
return await this.postRequest<Document>(url, body);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async getByUid(uid: string, q?: any): Promise<Document> {
|
||||
const url = new URL(this.baseURl.concat(`/${uid}`));
|
||||
const query = { q };
|
||||
if (q) Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
|
||||
try {
|
||||
return await this.getRequest<Document>(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async put(uid: string, body: IPutDocumentsParams): Promise<Document> {
|
||||
const url = new URL(this.baseURl.concat(`/${uid}`));
|
||||
try {
|
||||
return await this.putRequest<Document>(url, body);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async delete(uid: string): Promise<Document> {
|
||||
const url = new URL(this.baseURl.concat(`/${uid}`));
|
||||
try {
|
||||
return await this.deleteRequest<Document>(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
}
|
101
src/front/Api/LeCoffreApi/SuperAdmin/Files/Files.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import { File } from "le-coffre-resources/dist/SuperAdmin";
|
||||
import Container, { Service } from "typedi";
|
||||
|
||||
import BaseSuperAdmin from "../BaseSuperAdmin";
|
||||
import CryptoService from "@Front/Services/CryptoService/CryptoService";
|
||||
|
||||
// TODO Type get query params -> Where + inclue + orderby
|
||||
export interface IGetFilesparams {
|
||||
where?: {};
|
||||
include?: {};
|
||||
}
|
||||
|
||||
// TODO Type getbyuid query params
|
||||
|
||||
export type IPutFilesParams = {};
|
||||
|
||||
export interface IPostFilesParams {}
|
||||
|
||||
@Service()
|
||||
export default class Files extends BaseSuperAdmin {
|
||||
private static instance: Files;
|
||||
private readonly baseURl = this.namespaceUrl.concat("/files");
|
||||
|
||||
private constructor(private cryptoService: CryptoService) {
|
||||
super();
|
||||
}
|
||||
|
||||
public static getInstance() {
|
||||
if (!this.instance) {
|
||||
return new this(Container.get(CryptoService));
|
||||
} else {
|
||||
return this.instance;
|
||||
}
|
||||
}
|
||||
|
||||
public async get(q: IGetFilesparams): Promise<File[]> {
|
||||
const url = new URL(this.baseURl);
|
||||
const query = { q };
|
||||
if (q) Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
|
||||
try {
|
||||
const files = await this.getRequest<File[]>(url);
|
||||
files.forEach(async (file) => {
|
||||
file.file_path = await this.cryptoService.decrypt(file.file_path!, file.iv);
|
||||
});
|
||||
return files;
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description : Create a File
|
||||
*/
|
||||
public async post(body: any): Promise<File> {
|
||||
const url = new URL(this.baseURl);
|
||||
try {
|
||||
return await this.postRequestFormData<File>(url, body);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async getByUid(uid: string, q?: any): Promise<File> {
|
||||
const url = new URL(this.baseURl.concat(`/${uid}`));
|
||||
const query = { q };
|
||||
if (q) Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
|
||||
try {
|
||||
const file = await this.getRequest<File>(url);
|
||||
file.file_path = await this.cryptoService.decrypt(file.file_path!, file.iv);
|
||||
return file;
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async put(uid: string, body: IPutFilesParams): Promise<File> {
|
||||
const url = new URL(this.baseURl.concat(`/${uid}`));
|
||||
try {
|
||||
return await this.putRequest<File>(url, body);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description : Delete a folder only if the folder don't contains customers
|
||||
*/
|
||||
public async delete(uid: string): Promise<File> {
|
||||
const url = new URL(this.baseURl.concat(`/${uid}`));
|
||||
try {
|
||||
return await this.deleteRequest<File>(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
}
|
150
src/front/Api/LeCoffreApi/SuperAdmin/Folders/Folders.ts
Normal file
@ -0,0 +1,150 @@
|
||||
import { Service } from "typedi";
|
||||
import User, { Customer, DeedType, Office, OfficeFolder } from "le-coffre-resources/dist/Notary";
|
||||
import BaseSuperAdmin from "../BaseSuperAdmin";
|
||||
import { EFolderStatus } from "le-coffre-resources/dist/Customer/OfficeFolder";
|
||||
|
||||
// TODO Type get query params -> Where + inclue + orderby
|
||||
export interface IGetFoldersParams {
|
||||
q?: {
|
||||
select?: {};
|
||||
where?: {};
|
||||
include?: {};
|
||||
};
|
||||
}
|
||||
|
||||
export interface IPostFoldersParams {
|
||||
folder_number: OfficeFolder["folder_number"];
|
||||
name: OfficeFolder["name"];
|
||||
description: OfficeFolder["description"];
|
||||
deed: {
|
||||
deed_type: {
|
||||
uid: DeedType["uid"];
|
||||
};
|
||||
};
|
||||
office: {
|
||||
uid: Office["uid"];
|
||||
};
|
||||
office_folder_has_stakeholder: {
|
||||
user_stakeholder: {
|
||||
uid: User["uid"];
|
||||
};
|
||||
}[];
|
||||
}
|
||||
|
||||
export type IPutFoldersParams = {
|
||||
uid?: OfficeFolder["uid"];
|
||||
folder_number?: OfficeFolder["folder_number"];
|
||||
name?: OfficeFolder["name"];
|
||||
description?: OfficeFolder["description"];
|
||||
archived_description?: OfficeFolder["archived_description"];
|
||||
status?: OfficeFolder["status"];
|
||||
office_folder_has_stakeholder?: OfficeFolder["office_folder_has_stakeholder"];
|
||||
office_folder_has_customers?: { customer: { uid: Customer["uid"] } }[];
|
||||
};
|
||||
|
||||
@Service()
|
||||
export default class Folders extends BaseSuperAdmin {
|
||||
private static instance: Folders;
|
||||
private readonly baseURl = this.namespaceUrl.concat("/folders");
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static getInstance() {
|
||||
if (!this.instance) {
|
||||
return new this();
|
||||
} else {
|
||||
return this.instance;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description : Get all folders
|
||||
*/
|
||||
public async get(q: IGetFoldersParams): Promise<OfficeFolder[]> {
|
||||
const url = new URL(this.baseURl);
|
||||
Object.entries(q).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
|
||||
try {
|
||||
return await this.getRequest<OfficeFolder[]>(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description : Get a folder by uid
|
||||
*/
|
||||
public async getByUid(uid: string, q?: any): Promise<OfficeFolder> {
|
||||
const url = new URL(this.baseURl.concat(`/${uid}`));
|
||||
if (q) Object.entries(q).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
|
||||
try {
|
||||
return await this.getRequest<OfficeFolder>(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description : Create a folder
|
||||
*/
|
||||
public async post(body: any): Promise<OfficeFolder> {
|
||||
const url = new URL(this.baseURl);
|
||||
try {
|
||||
return await this.postRequest<OfficeFolder>(url, body);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description : Update the folder description
|
||||
*/
|
||||
public async put(uid: string, body: IPutFoldersParams): Promise<OfficeFolder> {
|
||||
const url = new URL(this.baseURl.concat(`/${uid}`));
|
||||
try {
|
||||
return await this.putRequest<OfficeFolder>(url, body);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description : Delete a folder only if the folder don't contains customers
|
||||
*/
|
||||
public async delete(uid: string): Promise<OfficeFolder> {
|
||||
const url = new URL(this.baseURl.concat(`/${uid}`));
|
||||
const targetedFolder = await this.getByUid(uid);
|
||||
if (targetedFolder.office_folder_has_customers) return Promise.reject(`The folder ${uid} contains customers`);
|
||||
try {
|
||||
return await this.deleteRequest<OfficeFolder>(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async archive(uid: string, body: IPutFoldersParams): Promise<OfficeFolder> {
|
||||
body.status = EFolderStatus.ARCHIVED;
|
||||
try {
|
||||
return await this.put(uid, body);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async restore(uid: string, body: IPutFoldersParams): Promise<OfficeFolder> {
|
||||
body.status = EFolderStatus.LIVE;
|
||||
try {
|
||||
return await this.put(uid, body);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
}
|
94
src/front/Api/LeCoffreApi/SuperAdmin/Users/Users.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import { Service } from "typedi";
|
||||
import User from "le-coffre-resources/dist/SuperAdmin";
|
||||
import BaseSuperAdmin from "../BaseSuperAdmin";
|
||||
|
||||
// TODO Type get query params -> Where + inclue + orderby
|
||||
export interface IGetUsersparams {
|
||||
where?:{},
|
||||
include?:{},
|
||||
select?:{},
|
||||
}
|
||||
|
||||
// TODO Type getbyuid query params
|
||||
|
||||
export type IPutUsersParams = {
|
||||
uid?: User["uid"];
|
||||
idNot?: User["idNot"];
|
||||
contact?: User["contact"];
|
||||
office_membership?: User["office_membership"];
|
||||
office_folder_has_stakeholders?: User["office_folder_has_stakeholders"];
|
||||
documents?: User["documents"];
|
||||
};
|
||||
|
||||
@Service()
|
||||
export default class Users extends BaseSuperAdmin {
|
||||
private static instance: Users;
|
||||
private readonly baseURl = this.namespaceUrl.concat("/users");
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static getInstance() {
|
||||
if (!this.instance) {
|
||||
return new this();
|
||||
} else {
|
||||
return this.instance;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description : Get all Users
|
||||
*/
|
||||
public async get(q: IGetUsersparams): Promise<User[]> {
|
||||
const url = new URL(this.baseURl);
|
||||
const query = { q };
|
||||
Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
|
||||
try {
|
||||
return await this.getRequest<User[]>(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description : Get a folder by uid
|
||||
*/
|
||||
public async getByUid(uid: string, q?: any): Promise<User> {
|
||||
const url = new URL(this.baseURl.concat(`/${uid}`));
|
||||
if (q) Object.entries(q).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
|
||||
try {
|
||||
return await this.getRequest<User>(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description : Create a User
|
||||
*/
|
||||
// public async post(body: IPostDeedsParams): Promise<OfficeFolder> {
|
||||
// const url = new URL(this.baseURl);
|
||||
// try {
|
||||
// return await this.postRequest<OfficeFolder>(url, body);
|
||||
// } catch (err) {
|
||||
// this.onError(err);
|
||||
// return Promise.reject(err);
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* @description : Update the folder description
|
||||
*/
|
||||
public async put(uid: string, body: IPutUsersParams): Promise<User> {
|
||||
const url = new URL(this.baseURl.concat(`/${uid}`));
|
||||
try {
|
||||
return await this.putRequest<User>(url, body);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
}
|
6
src/front/Assets/Icons/burger.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21 18.5H7" stroke="#101010" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21 14.5H3" stroke="#101010" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21 10.5H7" stroke="#101010" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21 6.5H3" stroke="#101010" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 518 B |
4
src/front/Assets/Icons/check-valid.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 7.33366L8 9.33366L14.6667 2.66699" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14 8V12.6667C14 13.0203 13.8595 13.3594 13.6095 13.6095C13.3594 13.8595 13.0203 14 12.6667 14H3.33333C2.97971 14 2.64057 13.8595 2.39052 13.6095C2.14048 13.3594 2 13.0203 2 12.6667V3.33333C2 2.97971 2.14048 2.64057 2.39052 2.39052C2.64057 2.14048 2.97971 2 3.33333 2H10.6667" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 598 B |
3
src/front/Assets/Icons/check.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.2251 3.36088C13.4893 3.59571 13.5131 4.00024 13.2783 4.26442L6.4516 11.9444C6.33015 12.0811 6.15607 12.1592 5.97326 12.1592C5.79045 12.1592 5.61637 12.0811 5.49492 11.9444L2.08159 8.10442C1.84676 7.84024 1.87055 7.43571 2.13474 7.20088C2.39892 6.96606 2.80344 6.98985 3.03827 7.25403L5.97326 10.5559L12.3216 3.41403C12.5564 3.14985 12.9609 3.12606 13.2251 3.36088Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 497 B |
3
src/front/Assets/Icons/chevron.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.5 18L15.5 12L9.5 6" stroke="#101010" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 218 B |
22
src/front/Assets/Icons/coffre.svg
Normal file
@ -0,0 +1,22 @@
|
||||
<svg width="44" height="56" viewBox="0 0 44 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_3026_107168)">
|
||||
<path d="M43.8828 9.98633C43.9035 9.4084 43.5713 8.81895 42.8538 8.51041L24.3282 0.58968C22.8379 -0.0481226 21.0845 -0.0481226 19.5942 0.58968L1.17241 8.46666C0.443378 8.7798 0.0996282 9.37616 0.115778 9.96331V46.1454L4.25693 48.091L6.73008 46.9098L18.4499 51.7336V54.1858L22.0396 55.869L25.6617 54.1398V51.7544L37.3584 46.9397L39.8201 48.0887L43.8874 46.1477V9.98633H43.8828Z" fill="#C5B2D4"/>
|
||||
<path d="M22.0369 55.9998L18.3318 54.2614V51.8115L6.73196 47.036L4.2565 48.2172L0 46.2209V9.85003C0.0322987 9.21222 0.454488 8.6481 1.12584 8.36259L19.5499 0.483307C21.061 -0.163706 22.8651 -0.163706 24.374 0.483307L42.8995 8.40403C43.5617 8.68725 43.9723 9.24216 43.9977 9.87075V46.2232L39.8173 48.2195L37.3534 47.0682L25.7743 51.8345V54.2153L22.0369 55.9998ZM18.5625 54.1163L22.0369 55.7465L25.5459 54.0725V51.6825L37.3626 46.8173L39.8196 47.9639L43.7716 46.0781V10.101H43.767V9.98127C43.7924 9.39412 43.4325 8.88296 42.8096 8.61587L24.284 0.69514C22.8305 0.073455 21.0956 0.073455 19.6422 0.69514L1.21812 8.57212C0.583683 8.84382 0.216862 9.36189 0.233012 9.96055V10.078V46.0712L4.25881 47.9616L6.72966 46.7827L18.5671 51.6549V54.1117L18.5625 54.1163Z" fill="#4E1480"/>
|
||||
<path d="M18.0574 49.0688C17.5222 49.0688 16.9823 48.9629 16.4724 48.7441L4.4412 43.6325C2.94854 42.9993 1.98419 41.5441 1.98419 39.9254L1.97266 17.4319C1.97266 16.0711 2.65093 14.8116 3.79061 14.0633C4.93029 13.315 6.35835 13.1906 7.61108 13.7294L19.6539 18.9171C21.135 19.5549 22.0924 21.0078 22.0924 22.6195V45.037C22.0924 46.3932 21.4141 47.6504 20.2814 48.401C19.61 48.8454 18.8349 49.0711 18.0551 49.0711L18.0574 49.0688ZM6.00999 14.0863C5.36632 14.0863 4.72727 14.2728 4.17127 14.6366C3.22769 15.256 2.66246 16.3014 2.66477 17.4296L2.67631 39.9231C2.67631 41.2632 3.47685 42.4697 4.71343 42.9947L16.7447 48.1063C17.7805 48.5461 18.9618 48.4402 19.9007 47.8185C20.842 47.1991 21.4026 46.1561 21.4026 45.0324V22.6149C21.4026 21.2795 20.609 20.0775 19.3816 19.5479L7.33885 14.3603C6.91204 14.1761 6.45986 14.0863 6.00999 14.0863Z" fill="white"/>
|
||||
<path d="M3.09274 20.3118V19.1352C3.09274 19.1052 3.07428 19.0776 3.0466 19.0661L1.58855 18.4375C1.53779 18.4168 1.48242 18.4536 1.48242 18.5066V19.6832C1.48242 19.7131 1.50088 19.7407 1.52856 19.7523L2.98431 20.3808C3.03506 20.4016 3.09043 20.3647 3.09043 20.3118H3.09274Z" fill="white"/>
|
||||
<path d="M3.01696 20.7323C2.95929 20.7323 2.90392 20.7208 2.84855 20.6978L1.3928 20.0715C1.23823 20.0047 1.13672 19.8527 1.13672 19.6846V18.508C1.13672 18.3653 1.20824 18.234 1.3259 18.1558C1.44586 18.0775 1.59582 18.0637 1.72502 18.1212L3.18307 18.7498C3.33764 18.8166 3.43915 18.9686 3.43915 19.1366V20.3132C3.43915 20.456 3.36763 20.5872 3.24767 20.6655C3.17846 20.7116 3.09771 20.7346 3.01696 20.7346V20.7323ZM1.82883 19.505L2.74704 19.9011V19.3116L1.82883 18.9156V19.505Z" fill="white"/>
|
||||
<path d="M3.14743 38.7512V37.5746C3.14743 37.5447 3.12897 37.5171 3.10129 37.5055L1.64323 36.877C1.59248 36.8562 1.53711 36.8931 1.53711 36.946V38.1226C1.53711 38.1526 1.55557 38.1802 1.58325 38.1917L3.039 38.8203C3.08975 38.841 3.14512 38.8042 3.14512 38.7512H3.14743Z" fill="white"/>
|
||||
<path d="M3.0713 39.1721C3.01362 39.1721 2.95825 39.1606 2.90519 39.1376L1.44713 38.509C1.29256 38.4422 1.19336 38.2902 1.19336 38.1221V36.9455C1.19336 36.8051 1.26257 36.6715 1.38254 36.5933C1.5025 36.515 1.65015 36.5012 1.78166 36.5587L3.23971 37.1873C3.39428 37.2541 3.49349 37.4061 3.49349 37.5741V38.7507C3.49349 38.8935 3.42197 39.0247 3.30431 39.103C3.2351 39.1491 3.15435 39.1721 3.0713 39.1721ZM1.88317 37.9448L2.80137 38.3409V37.7514L1.88317 37.3554V37.9448Z" fill="white"/>
|
||||
<path d="M22.415 26.6204V25.4438C22.415 25.4138 22.3965 25.3862 22.3689 25.3747L20.9108 24.7461C20.8601 24.7254 20.8047 24.7622 20.8047 24.8152V25.9918C20.8047 26.0217 20.8231 26.0493 20.8508 26.0608L22.3066 26.6894C22.3573 26.7102 22.4127 26.6733 22.4127 26.6204H22.415Z" fill="white"/>
|
||||
<path d="M22.3392 27.0412C22.2816 27.0412 22.2262 27.0297 22.1708 27.0067L20.7151 26.3781C20.5605 26.3113 20.459 26.1594 20.459 25.9913V24.8147C20.459 24.6719 20.5305 24.5407 20.6482 24.4624C20.7681 24.3841 20.9158 24.3703 21.0473 24.4279L22.5053 25.0565C22.6599 25.1232 22.7614 25.2752 22.7614 25.4433V26.6199C22.7614 26.7626 22.6899 26.8939 22.5699 26.9722C22.5007 27.0182 22.42 27.0412 22.3392 27.0412ZM21.1511 25.814L22.0693 26.21V25.6206L21.1511 25.2245V25.814Z" fill="white"/>
|
||||
<path d="M22.4677 45.0618V43.8852C22.4677 43.8552 22.4493 43.8276 22.4216 43.8161L20.9635 43.1875C20.9128 43.1668 20.8574 43.2036 20.8574 43.2566V44.4332C20.8574 44.4631 20.8759 44.4907 20.9036 44.5023L22.3593 45.1308C22.4101 45.1516 22.4654 45.1147 22.4654 45.0618H22.4677Z" fill="white"/>
|
||||
<path d="M22.3916 45.4823C22.3339 45.4823 22.2786 45.4708 22.2232 45.4478L20.7674 44.8215C20.6129 44.7547 20.5137 44.6027 20.5137 44.4346V43.258C20.5137 43.1176 20.5829 42.984 20.7028 42.9058C20.8228 42.8275 20.9728 42.8137 21.102 42.8712L22.56 43.4998C22.7146 43.5666 22.8138 43.7186 22.8138 43.8866V45.0632C22.8138 45.206 22.7423 45.3372 22.6223 45.4155C22.5531 45.4616 22.4724 45.4846 22.3916 45.4846V45.4823ZM21.2035 44.255L22.1217 44.6511V44.0616L21.2035 43.6656V44.255Z" fill="white"/>
|
||||
<path d="M8.47164 24.259C6.02617 25.2099 5.44248 29.5617 7.16585 33.9803C8.88921 38.3989 12.269 41.2126 14.7145 40.2616C17.16 39.3107 17.7437 34.9589 16.0203 30.5403C14.2969 26.1217 10.9171 23.308 8.47164 24.259ZM12.5989 32.3478C12.5713 32.2442 12.5367 32.1406 12.4974 32.037C12.4144 31.8228 12.3129 31.6294 12.1975 31.4567L12.7835 27.0911C13.7248 28.0098 14.5876 29.3177 15.1944 30.8742C15.7827 32.3823 16.0364 33.8813 15.9857 35.1661L12.5989 32.3478ZM9.10377 25.804C9.92738 25.4839 10.8802 25.6935 11.8122 26.3036L11.2216 30.6946C11.1178 30.6808 11.0163 30.69 10.924 30.7268C10.8664 30.7498 10.8156 30.7821 10.7671 30.8212L7.43347 28.0467C7.70339 26.9437 8.26862 26.1263 9.10377 25.804ZM8.04945 33.651C7.45192 32.1152 7.19815 30.591 7.26044 29.29L10.458 31.9518C10.4811 32.205 10.5457 32.479 10.6518 32.753C10.7187 32.9234 10.7971 33.08 10.8825 33.2251L10.3334 37.3075C9.4406 36.398 8.62852 35.1362 8.04945 33.651ZM11.3001 38.1341L11.8445 34.077C11.976 34.1092 12.1052 34.1069 12.2229 34.0609C12.3752 34.001 12.4905 33.879 12.5713 33.7132L15.8219 36.4187C15.5566 37.5515 14.9867 38.3897 14.1377 38.7212C13.2772 39.0551 12.2713 38.811 11.2978 38.1341H11.3001Z" fill="white"/>
|
||||
<path d="M12.1448 31.9302C12.5924 32.6509 12.5878 33.4591 12.1656 33.7147C11.7434 33.9703 11.0167 33.595 10.6199 32.8466C10.1977 32.0523 10.1885 31.3339 10.583 31.0852C11.0421 30.7951 11.6904 31.1957 12.1471 31.9325L12.1448 31.9302Z" fill="#3FA79E"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_3026_107168">
|
||||
<rect width="44" height="56" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 6.6 KiB |
4
src/front/Assets/Icons/cross.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18 6L6 18" stroke="#101010" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6 6L18 18" stroke="#101010" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 311 B |
6
src/front/Assets/Icons/deposit-document.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.5 2.5H7.5C6.83696 2.5 6.20107 2.76339 5.73223 3.23223C5.26339 3.70107 5 4.33696 5 5V25C5 25.663 5.26339 26.2989 5.73223 26.7678C6.20107 27.2366 6.83696 27.5 7.5 27.5H22.5C23.163 27.5 23.7989 27.2366 24.2678 26.7678C24.7366 26.2989 25 25.663 25 25V10L17.5 2.5Z" stroke="#320756" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M15 22.5V15" stroke="#C5B2D4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11.25 18.75H18.75" stroke="#C5B2D4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M17.5 2.5V10H25" stroke="#320756" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 786 B |
4
src/front/Assets/Icons/disconnect.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18.8601 6.64001C20.1185 7.8988 20.9754 9.50246 21.3224 11.2482C21.6694 12.994 21.491 14.8034 20.8098 16.4478C20.1285 18.0921 18.9749 19.4976 17.4949 20.4864C16.015 21.4752 14.275 22.0029 12.4951 22.0029C10.7152 22.0029 8.97527 21.4752 7.49529 20.4864C6.01532 19.4976 4.86176 18.0921 4.18049 16.4478C3.49921 14.8034 3.32081 12.994 3.66784 11.2482C4.01487 9.50246 4.87174 7.8988 6.13012 6.64001" stroke="#999999" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12.5 2V12" stroke="#999999" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 694 B |
4
src/front/Assets/Icons/document-check.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M32 16C32 24.8366 24.8366 32 16 32C7.16344 32 0 24.8366 0 16C0 7.16344 7.16344 0 16 0C24.8366 0 32 7.16344 32 16ZM4 16C4 22.6274 9.37258 28 16 28C22.6274 28 28 22.6274 28 16C28 9.37258 22.6274 4 16 4C9.37258 4 4 9.37258 4 16Z" fill="#3FA79E"/>
|
||||
<path d="M22 12L13.75 20.5714L10 16.6753" stroke="#3FA79E" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 483 B |
3
src/front/Assets/Icons/export.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.66667 4V5.33333H3.33333V12.6667H10.6667V9.33333H12V13.3333C12 13.7015 11.7015 14 11.3333 14H2.66667C2.29848 14 2 13.7015 2 13.3333V4.66667C2 4.29848 2.29848 4 2.66667 4H6.66667ZM14 2V7.33333H12.6667V4.27533L7.47133 9.47133L6.52867 8.52867L11.7233 3.33333H8.66667V2H14Z" fill="#000091"/>
|
||||
</svg>
|
After Width: | Height: | Size: 442 B |
5
src/front/Assets/Icons/info.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="6" cy="6" r="6" fill="#FFB017"/>
|
||||
<path d="M6 3V5.66667" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="6" cy="9" r="1" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 295 B |
3
src/front/Assets/Icons/left-arrow.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 6L9 12L15 18" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 211 B |
27
src/front/Assets/Icons/loader.svg
Normal file
@ -0,0 +1,27 @@
|
||||
<svg version="1.1" id="L4" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 100 100" enable-background="new 0 0 0 0" xml:space="preserve">
|
||||
<circle fill="#000000" stroke="none" cx="6" cy="50" r="6">
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
dur="1s"
|
||||
values="0;1;0"
|
||||
repeatCount="indefinite"
|
||||
begin="0.1"/>
|
||||
</circle>
|
||||
<circle fill="#000000" stroke="none" cx="26" cy="50" r="6">
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
dur="1s"
|
||||
values="0;1;0"
|
||||
repeatCount="indefinite"
|
||||
begin="0.2"/>
|
||||
</circle>
|
||||
<circle fill="#000000" stroke="none" cx="46" cy="50" r="6">
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
dur="1s"
|
||||
values="0;1;0"
|
||||
repeatCount="indefinite"
|
||||
begin="0.3"/>
|
||||
</circle>
|
||||
</svg>
|
After Width: | Height: | Size: 834 B |
3
src/front/Assets/Icons/loop.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.4351 10.0629H10.7124L10.4563 9.81589C11.3528 8.77301 11.8925 7.4191 11.8925 5.94625C11.8925 2.66209 9.23042 0 5.94625 0C2.66209 0 0 2.66209 0 5.94625C0 9.23042 2.66209 11.8925 5.94625 11.8925C7.4191 11.8925 8.77301 11.3528 9.81589 10.4563L10.0629 10.7124V11.4351L14.6369 16L16 14.6369L11.4351 10.0629ZM5.94625 10.0629C3.66838 10.0629 1.82962 8.22413 1.82962 5.94625C1.82962 3.66838 3.66838 1.82962 5.94625 1.82962C8.22413 1.82962 10.0629 3.66838 10.0629 5.94625C10.0629 8.22413 8.22413 10.0629 5.94625 10.0629Z" fill="#939393"/>
|
||||
</svg>
|
After Width: | Height: | Size: 644 B |
5
src/front/Assets/Icons/notification.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18 8.5C18 6.9087 17.3679 5.38258 16.2426 4.25736C15.1174 3.13214 13.5913 2.5 12 2.5C10.4087 2.5 8.88258 3.13214 7.75736 4.25736C6.63214 5.38258 6 6.9087 6 8.5C6 15.5 3 17.5 3 17.5H21C21 17.5 18 15.5 18 8.5Z" stroke="#101010" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.73 21.5C13.5542 21.8031 13.3019 22.0547 12.9982 22.2295C12.6946 22.4044 12.3504 22.4965 12 22.4965C11.6496 22.4965 11.3054 22.4044 11.0018 22.2295C10.6982 22.0547 10.4458 21.8031 10.27 21.5" stroke="#101010" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 11.1666H8.00667" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 801 B |
3
src/front/Assets/Icons/pen.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17 3.5003C17.2626 3.23766 17.5744 3.02932 17.9176 2.88718C18.2608 2.74503 18.6286 2.67188 19 2.67188C19.3714 2.67187 19.7392 2.74503 20.0824 2.88718C20.4256 3.02932 20.7374 3.23766 21 3.5003C21.2626 3.76295 21.471 4.07475 21.6131 4.41791C21.7553 4.76107 21.8284 5.12887 21.8284 5.5003C21.8284 5.87174 21.7553 6.23953 21.6131 6.58269C21.471 6.92585 21.2626 7.23766 21 7.5003L7.5 21.0003L2 22.5003L3.5 17.0003L17 3.5003Z" stroke="#939393" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 616 B |
3
src/front/Assets/Icons/plus.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 5V19M4 12H18" stroke="#BD4B91" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 213 B |
3
src/front/Assets/Icons/right-arrow.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 18L15 12L9 6" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 210 B |
12
src/front/Assets/Icons/tool-tip.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_2395_56631)">
|
||||
<path d="M10.9999 20.1667C16.0625 20.1667 20.1666 16.0627 20.1666 11C20.1666 5.93743 16.0625 1.83337 10.9999 1.83337C5.93731 1.83337 1.83325 5.93743 1.83325 11C1.83325 16.0627 5.93731 20.1667 10.9999 20.1667Z" stroke="#101010" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11 14.6667V11" stroke="#101010" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11 7.33337H11.0092" stroke="#101010" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2395_56631">
|
||||
<rect width="22" height="22" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 765 B |
6
src/front/Assets/Icons/trash.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 6H5H21" stroke="#939393" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 6V4C8 3.46957 8.21071 2.96086 8.58579 2.58579C8.96086 2.21071 9.46957 2 10 2H14C14.5304 2 15.0391 2.21071 15.4142 2.58579C15.7893 2.96086 16 3.46957 16 4V6M19 6V20C19 20.5304 18.7893 21.0391 18.4142 21.4142C18.0391 21.7893 17.5304 22 17 22H7C6.46957 22 5.96086 21.7893 5.58579 21.4142C5.21071 21.0391 5 20.5304 5 20V6H19Z" stroke="#939393" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14 11V17" stroke="#939393" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10 11V17" stroke="#939393" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 830 B |
4
src/front/Assets/Icons/user.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20 21.5V19.5C20 18.4391 19.5786 17.4217 18.8284 16.6716C18.0783 15.9214 17.0609 15.5 16 15.5H8C6.93913 15.5 5.92172 15.9214 5.17157 16.6716C4.42143 17.4217 4 18.4391 4 19.5V21.5" stroke="#101010" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 11.5C14.2091 11.5 16 9.70914 16 7.5C16 5.29086 14.2091 3.5 12 3.5C9.79086 3.5 8 5.29086 8 7.5C8 9.70914 9.79086 11.5 12 11.5Z" stroke="#101010" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 598 B |
12
src/front/Assets/Icons/warning.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_3175_67304)">
|
||||
<path d="M5.74016 1.33203H11.2602L15.1668 5.2387V10.7587L11.2602 14.6654H5.74016L1.8335 10.7587V5.2387L5.74016 1.33203Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.5 10.668H8.50667" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.5 5.33203V7.9987" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_3175_67304">
|
||||
<rect width="16" height="16" fill="white" transform="translate(0.5)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 702 B |
3
src/front/Assets/docs/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Documentation
|
||||
|
||||
This is the documentation served at `https://<BASE_PATH>/documentation`
|
67
src/front/Assets/docs/content.md
Normal file
@ -0,0 +1,67 @@
|
||||
## Usage
|
||||
|
||||
Once sign up, a `Project ID` is generated for your project, to use within your app to make requests to tezoslink.net.
|
||||
|
||||
Then, add the tezoslink.net RPC endpoint to your prefered Tezos JS library.
|
||||
|
||||
> i.e with [Sotez](https://github.com/AndrewKishino/sotez) :
|
||||
>
|
||||
> ```js
|
||||
> const sotez = new Sotez("https://<NETWORK>.tezoslink.net/v1/<YOUR_PROJECT_ID>");
|
||||
> ```
|
||||
|
||||
## Networks
|
||||
|
||||
Use one of these endpoints as your Tezos client provider.
|
||||
|
||||
| NETWORK | DESCRIPTION | URL |
|
||||
| ------- | ----------- | -------------------------------------------------- |
|
||||
| Mainnet | JSON/RPC | https://mainnet.tezoslink.net/v1/<YOUR_PROJECT_ID> |
|
||||
| Testnet | JSON/RPC | https://testnet.tezoslink.net/v1/<YOUR_PROJECT_ID> |
|
||||
|
||||
## Make requests
|
||||
|
||||
```bash
|
||||
# Be sure to replace YOUR-PROJECT-ID with a Project ID from your Tezos Link dashboard
|
||||
$ curl https://mainnet.tezoslink.net/v1/<YOUR_PROJECT_ID>/chains/main/blocks/head
|
||||
```
|
||||
|
||||
You should receive the last received block.
|
||||
|
||||
## Security
|
||||
|
||||
The `Project ID` authorize requests.
|
||||
|
||||
## RPC Endpoints
|
||||
|
||||
### Whitelisted
|
||||
|
||||
All requests of type `/chains/main/blocks(.*?)` are accepted.
|
||||
|
||||
> Example of valid paths:
|
||||
>
|
||||
> - `/chains/main/blocks/head/context/contracts/<ADDRESS>/balance`
|
||||
> - `/chains/main/blocks/head/context/contracts/<ADDRESS>/delegate`
|
||||
> - `/chains/main/blocks/head/context/contracts/<ADDRESS>/manager_key`
|
||||
> - `/chains/main/blocks/head/context/contracts/<ADDRESS>/counter`
|
||||
> - `/chains/main/blocks/head/context/delegates/<ADDRESS>`
|
||||
> - `/chains/main/blocks/head/header`
|
||||
> - `/chains/main/blocks/head/votes/proposals`
|
||||
> - `/chains/main/blocks/head/votes/current_quorum`
|
||||
|
||||
[More about the Tezos `JSON/RPC` endpoints](https://tezos.gitlab.io/api/rpc.html)
|
||||
|
||||
## Nodes
|
||||
|
||||
Tezos has three types of nodes:
|
||||
|
||||
- Full mode (default mode)
|
||||
- **Rolling mode**
|
||||
- **Archive mode**
|
||||
|
||||
We use two types of mode:
|
||||
|
||||
- **Archive** to store the whole blockchain. Archive is the heaviest mode as it keeps the whole chain data to be able to query any information stored on the chain since the genesis. It is particularly suitable for indexers or block explorer, that is why we use archive nodes.
|
||||
- **Rolling** to store last blocks (and scale them faster)
|
||||
|
||||
> [More about history modes](https://blog.nomadic-labs.com/introducing-snapshots-and-history-modes-for-the-tezos-node.html)
|
6
src/front/Assets/docs/menu.md
Normal file
@ -0,0 +1,6 @@
|
||||
- [Usage](##usage)
|
||||
- [Networks](#networks)
|
||||
- [Make Requests](#make-requests)
|
||||
- [Security](##security)
|
||||
- [RPC Endpoints](#rpc-endpoints)
|
||||
- [Nodes](#nodes)
|
BIN
src/front/Assets/images/create-folder/right-image.png
Normal file
After Width: | Height: | Size: 191 KiB |
BIN
src/front/Assets/images/validate_anchoring.gif
Normal file
After Width: | Height: | Size: 1.3 MiB |
38
src/front/Assets/logo.svg
Normal file
@ -0,0 +1,38 @@
|
||||
<svg width="174" height="39" viewBox="0 0 174 39" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_3896_98053)">
|
||||
<path d="M25.5282 5.84779C25.541 5.51073 25.347 5.16635 24.9299 4.98683L14.1642 0.368742C13.2988 -0.00312242 12.2797 -0.00312242 11.4124 0.368742L0.707113 4.96118C0.282634 5.14253 0.0832014 5.49058 0.0941793 5.83497V26.9287L2.50017 28.0626L3.93828 27.3738L10.7483 30.1857V31.6164L12.8341 32.5982L14.94 31.5907V30.2004L21.739 27.3921L23.1698 28.0626L25.5337 26.9305V5.84779H25.5282Z" fill="#C5B2D4"/>
|
||||
<path d="M12.8314 32.7007L10.6541 31.6767V30.2497L3.93741 27.4763L2.49748 28.1651L0 26.9872L0.00365931 5.7433C0.0311041 5.37143 0.279937 5.04536 0.669653 4.87683L11.3768 0.284394C12.2605 -0.094798 13.3162 -0.094798 14.1999 0.284394L24.9656 4.90248C25.3517 5.06734 25.5932 5.38975 25.6188 5.75612H25.6206V26.989L23.1671 28.1633L21.7344 27.4928L15.0306 30.2626V31.6493L12.8332 32.7007H12.8314ZM10.837 31.5595L12.8314 32.4974L14.8458 31.5339V30.1398L14.9025 30.116L21.7381 27.2931L23.1652 27.9618L25.4377 26.8736V5.9393H25.434V5.84405C25.4486 5.51065 25.2456 5.22305 24.8924 5.07101L14.1267 0.452924C13.2888 0.0938821 12.2861 0.0938821 11.4481 0.452924L0.741009 5.04536C0.382398 5.19924 0.173817 5.49233 0.182965 5.83122V5.92465V26.87L2.49748 27.9599L3.93192 27.273L3.97035 27.2876L10.837 30.1233V31.5558V31.5595Z" fill="#4E1480"/>
|
||||
<path d="M10.5185 28.7041C10.1984 28.7041 9.8745 28.64 9.56895 28.5099L2.57602 25.5295C1.68132 25.1485 1.10315 24.2729 1.10315 23.3002L1.09766 10.186C1.09766 9.36715 1.50567 8.61059 2.18813 8.15996C2.87059 7.70933 3.72687 7.63422 4.47886 7.96029L11.4773 10.9847C12.3647 11.3675 12.9392 12.2413 12.9392 13.2104V26.2806C12.9392 27.0958 12.533 27.8523 11.8542 28.3029C11.4517 28.5704 10.9869 28.7059 10.5204 28.7059L10.5185 28.7041ZM9.78485 28.0043C10.3648 28.2516 11.0254 28.1912 11.5505 27.8431C12.0756 27.4951 12.3903 26.9107 12.3903 26.2787V13.2085C12.3903 12.4593 11.9475 11.7852 11.2596 11.4884L4.26113 8.46405C3.68113 8.21308 3.01879 8.2717 2.49002 8.61975C1.96125 8.9678 1.64655 9.554 1.64655 10.186L1.65204 23.3002C1.65204 24.0531 2.09848 24.729 2.79009 25.0239L9.78302 28.0043H9.78485Z" fill="white"/>
|
||||
<path d="M13.052 15.5426V14.8557C13.052 14.8374 13.041 14.8227 13.0246 14.8154L12.1774 14.449C12.1482 14.4362 12.1152 14.4582 12.1152 14.4893V15.1763C12.1152 15.1946 12.1262 15.2093 12.1427 15.2166L12.9898 15.5829C13.0191 15.5958 13.052 15.5738 13.052 15.5426Z" fill="white"/>
|
||||
<path d="M13.0073 15.861C12.9634 15.861 12.9213 15.8519 12.8792 15.8336L12.0339 15.4672C11.9168 15.4177 11.8418 15.3023 11.8418 15.1741V14.4871C11.8418 14.3791 11.8949 14.2802 11.9845 14.2197C12.0742 14.1611 12.1876 14.1519 12.2864 14.1941L13.1335 14.5604C13.2506 14.6099 13.3256 14.7253 13.3256 14.8535V15.5405C13.3256 15.6485 13.2726 15.7475 13.1811 15.8079C13.128 15.8427 13.0677 15.861 13.0073 15.861ZM12.3907 15.0239L12.7767 15.1906V15.0074L12.3907 14.8407V15.0239Z" fill="white"/>
|
||||
<path d="M13.0833 26.2946V25.6077C13.0833 25.5893 13.0723 25.5747 13.0558 25.5674L12.2087 25.201C12.1794 25.1882 12.1465 25.2101 12.1465 25.2413V25.9282C12.1465 25.9465 12.1575 25.9612 12.1739 25.9685L13.0211 26.3349C13.0503 26.3477 13.0833 26.3257 13.0833 26.2946Z" fill="white"/>
|
||||
<path d="M13.0385 26.6156C12.9965 26.6156 12.9544 26.6065 12.9123 26.59L12.0652 26.2236C11.9481 26.1723 11.873 26.0569 11.873 25.9305V25.2436C11.873 25.1355 11.9261 25.0366 12.0158 24.9761C12.1054 24.9157 12.2189 24.9083 12.3177 24.9505L13.1648 25.3168C13.2819 25.3663 13.3569 25.4817 13.3569 25.6099V26.2969C13.3569 26.405 13.3038 26.5039 13.2142 26.5625C13.1611 26.5973 13.1007 26.6156 13.0385 26.6156ZM12.4219 25.7785L12.808 25.9452V25.7601L12.4219 25.5934V25.7785Z" fill="white"/>
|
||||
<path d="M4.94763 14.1684C3.52599 14.7217 3.1875 17.2606 4.18832 19.8362C5.18914 22.4136 7.15419 24.0531 8.57583 23.498C9.99747 22.943 10.336 20.4059 9.33514 17.8303C8.33432 15.2547 6.36927 13.6134 4.94763 14.1684ZM7.34631 18.8854C7.32984 18.825 7.30971 18.7645 7.28593 18.7041C7.23836 18.5795 7.17798 18.466 7.11211 18.3652L7.45243 15.8189C7.99949 16.3538 8.50082 17.1177 8.85394 18.0245C9.19608 18.9038 9.34246 19.7776 9.31318 20.5268L7.34448 18.8836L7.34631 18.8854ZM5.31539 15.0715C5.79293 14.8847 6.34731 15.0074 6.89072 15.3628L6.54675 17.9237C6.48637 17.9164 6.42782 17.9219 6.37293 17.942C6.34 17.9549 6.30889 17.9732 6.28145 17.997L4.34385 16.3795C4.5012 15.7365 4.8287 15.2602 5.31356 15.0715H5.31539ZM4.70246 19.6457C4.35482 18.7499 4.20845 17.8614 4.24504 17.1031L6.10397 18.6546C6.11861 18.803 6.1552 18.9624 6.21741 19.1218C6.25583 19.2207 6.30157 19.3123 6.35097 19.3965L6.03261 21.7761C5.51299 21.2449 5.04094 20.5103 4.70429 19.6438L4.70246 19.6457ZM6.59249 22.2597L6.90902 19.8948C6.98586 19.9131 7.06088 19.9131 7.12858 19.8856C7.2164 19.8508 7.2841 19.7794 7.33167 19.6823L9.2217 21.2595C9.06801 21.919 8.73684 22.4099 8.24284 22.6023C7.74151 22.7964 7.15785 22.6554 6.59249 22.2597Z" fill="white"/>
|
||||
<path d="M6.96684 18.6397C7.22665 19.0592 7.22482 19.53 6.97782 19.6802C6.73081 19.8304 6.30999 19.6106 6.07946 19.1746C5.83428 18.7112 5.82879 18.2935 6.0575 18.147C6.32463 17.9784 6.69971 18.2111 6.96684 18.6397Z" fill="#3FA79E"/>
|
||||
<path d="M92.327 11.9149C92.4917 11.2921 92.7661 10.7023 93.1504 10.1436C93.5346 9.58484 94.0012 9.09573 94.5501 8.67258C95.099 8.25125 95.7174 7.92152 96.4017 7.68338C97.0878 7.44524 97.816 7.32617 98.5826 7.32617H102.59L102.041 9.60682H100.284C99.3694 9.60682 98.6101 9.83214 98.0063 10.2791C97.4025 10.7279 97.0274 11.2738 96.881 11.9131L96.3047 14.1938H99.7902L99.2138 16.4744H95.7284L92.9015 27.9326H88.3457L92.3252 11.9131L92.327 11.9149Z" fill="#4E1480"/>
|
||||
<path d="M102.839 11.9149C103.003 11.2921 103.278 10.7023 103.662 10.1436C104.046 9.58484 104.513 9.09573 105.062 8.67258C105.611 8.25125 106.229 7.92152 106.913 7.68338C107.6 7.44524 108.328 7.32617 109.094 7.32617H113.101L112.552 9.60682H110.796C109.881 9.60682 109.122 9.83214 108.518 10.2791C107.914 10.7279 107.539 11.2738 107.393 11.9131L106.816 14.1938H110.302L109.726 16.4744H106.24L103.413 27.9326H98.8574L102.837 11.9131L102.839 11.9149Z" fill="#4E1480"/>
|
||||
<path d="M122.873 11.9158L122.296 14.1964H120.567C119.652 14.1964 118.893 14.4217 118.289 14.8687C117.686 15.3175 117.301 15.8634 117.137 16.5027L114.31 27.9334H109.727L112.553 16.5302C112.7 15.9257 112.96 15.345 113.336 14.7844C113.712 14.2257 114.174 13.7311 114.723 13.3006C115.272 12.8702 115.894 12.5313 116.59 12.284C117.285 12.0367 118.035 11.9121 118.84 11.9121H122.874L122.873 11.9158Z" fill="#4E1480"/>
|
||||
<path d="M126.332 27.9336C124.978 27.9336 123.948 27.6038 123.243 26.9444C122.539 26.2849 122.186 25.4514 122.186 24.4439C122.186 24.1325 122.231 23.7661 122.323 23.3448L124.024 16.4754C124.189 15.8525 124.463 15.2627 124.848 14.704C125.232 14.1453 125.698 13.6598 126.247 13.2476C126.796 12.8355 127.418 12.5112 128.114 12.2713C128.809 12.0331 129.541 11.9141 130.309 11.9141H133.767C135.121 11.9141 136.146 12.2401 136.841 12.8904C137.536 13.5407 137.884 14.3706 137.884 15.3781C137.884 15.7078 137.838 16.0833 137.747 16.5047L136.622 21.066H127.482L126.906 23.3741C126.741 24.0152 126.846 24.5556 127.223 24.9953C127.598 25.4349 128.242 25.6547 129.158 25.6547H133.769L133.22 27.9354H126.332V27.9336ZM128.061 18.7835H132.644L133.193 16.49C133.339 15.8525 133.248 15.3103 132.918 14.8652C132.589 14.4182 132.104 14.1965 131.464 14.1965C131.153 14.1965 130.842 14.2552 130.531 14.3742C130.22 14.4933 129.936 14.6563 129.68 14.8652C129.424 15.074 129.2 15.3195 129.008 15.6016C128.816 15.8837 128.683 16.1804 128.611 16.49L128.062 18.7835H128.061Z" fill="#4E1480"/>
|
||||
<path d="M140.686 27.9345V23.3457H145.269V27.9345H140.686Z" fill="#4E1480"/>
|
||||
<path d="M152.101 11.9149H156.684L152.704 27.9344H148.121L152.101 11.9149ZM153.226 7.32617H157.809L157.26 9.60682H152.677L153.226 7.32617Z" fill="#3FA79E"/>
|
||||
<path d="M162.447 27.9336C161.093 27.9336 160.063 27.6038 159.358 26.9444C158.654 26.2849 158.301 25.4514 158.301 24.4439C158.301 24.1325 158.347 23.7661 158.438 23.3448L160.14 16.4754C160.304 15.8525 160.579 15.2627 160.963 14.704C161.347 14.1453 161.814 13.6598 162.363 13.2476C162.912 12.8355 163.534 12.5112 164.229 12.2713C164.924 12.0331 165.656 11.9141 166.424 11.9141H169.882C171.236 11.9141 172.261 12.2438 172.956 12.9033C173.652 13.5627 173.999 14.3871 173.999 15.3763C173.999 15.6877 173.953 16.054 173.862 16.4754L172.16 23.3448C171.996 23.9859 171.721 24.5813 171.337 25.1308C170.953 25.6804 170.486 26.1658 169.937 26.5871C169.388 27.0085 168.77 27.3382 168.086 27.5763C167.4 27.8145 166.671 27.9336 165.903 27.9336H162.445H162.447ZM169.281 16.5028C169.445 15.8617 169.363 15.3176 169.034 14.8688C168.704 14.42 168.219 14.1965 167.579 14.1965C166.939 14.1965 166.344 14.4219 165.795 14.8688C165.246 15.3176 164.889 15.8635 164.725 16.5028L163.023 23.3448C162.858 23.9859 162.941 24.5318 163.27 24.9788C163.599 25.4276 164.084 25.6511 164.725 25.6511C165.036 25.6511 165.347 25.5925 165.658 25.4734C165.969 25.3543 166.252 25.1895 166.509 24.9788C166.765 24.7681 166.984 24.5263 167.167 24.2515C167.35 23.9768 167.487 23.6745 167.579 23.3448L169.281 16.5028Z" fill="#3FA79E"/>
|
||||
<path d="M83.5743 13.3998C80.0815 12.504 76.4387 14.9495 75.4378 18.8605C74.437 22.7715 76.457 26.6696 79.9516 27.5654C83.4444 28.4612 87.0873 26.0157 88.0881 22.1047C89.0889 18.1937 87.069 14.2955 83.5743 13.3998ZM82.8754 21.1594C82.912 21.0752 82.9431 20.9873 82.9669 20.8957C83.0144 20.707 83.0291 20.5183 83.0126 20.3369L86.6664 17.8841C87.1385 19.0565 87.2501 20.4322 86.8988 21.8098C86.5567 23.1452 85.8339 24.2663 84.899 25.0649L82.8736 21.1594H82.8754ZM83.2578 14.7865C84.4324 15.0869 85.4003 15.8105 86.0718 16.7795L82.396 19.247C82.2844 19.1737 82.1618 19.1188 82.0264 19.084C81.9441 19.062 81.8618 19.051 81.7794 19.0473L79.7869 15.2023C80.8518 14.6582 82.0648 14.4805 83.2578 14.7865ZM76.6912 19.1939C77.0388 17.8346 77.7816 16.6971 78.7404 15.8966L80.6524 19.5841C80.5078 19.7618 80.3962 19.9779 80.334 20.2197C80.2956 20.3699 80.2791 20.5201 80.2809 20.6667L76.8631 22.9602C76.4423 21.8226 76.3545 20.5091 76.6912 19.1939ZM77.4267 24.0886L80.8225 21.8098C80.9543 21.9123 81.1061 21.9911 81.2744 22.0332C81.4922 22.0882 81.7117 22.0809 81.9185 22.0241L83.8616 25.7739C82.7839 26.3417 81.5471 26.5304 80.334 26.219C79.1045 25.9039 78.1 25.1272 77.4267 24.0886Z" fill="#3FA79E"/>
|
||||
<path d="M81.4103 27.8402C80.9182 27.8402 80.4223 27.7798 79.9301 27.6534C78.2157 27.2137 76.7941 26.0524 75.9305 24.3854C75.0687 22.7221 74.862 20.751 75.3523 18.8367C75.8409 16.9224 76.9679 15.2939 78.5231 14.2498C80.082 13.2038 81.8842 12.8704 83.6004 13.31C87.1371 14.2168 89.19 18.1718 88.18 22.1267C87.3091 25.5303 84.4475 27.8402 81.4122 27.8402H81.4103ZM82.1074 13.3045C80.9053 13.3045 79.7051 13.6782 78.6256 14.4036C77.107 15.4222 76.0074 17.014 75.5298 18.8843C75.0523 20.7547 75.2517 22.6781 76.0934 24.3029C76.9332 25.9241 78.3127 27.0507 79.9759 27.4775C83.4138 28.3605 87.0146 25.9388 88.0007 22.0827C88.9869 18.2267 86.9926 14.3707 83.5528 13.4896C83.0753 13.3668 82.5905 13.3064 82.1056 13.3064L82.1074 13.3045ZM81.5073 26.4572C81.1048 26.4572 80.7041 26.4077 80.3125 26.307C79.1105 25.9992 78.0584 25.228 77.3503 24.1381L77.3009 24.063L80.8267 21.6962L80.8797 21.7365C81.0078 21.8354 81.1469 21.9051 81.2987 21.9435C81.4945 21.993 81.6939 21.9912 81.8952 21.9344L81.9665 21.9142L83.9865 25.8105L83.906 25.8527C83.1467 26.252 82.3252 26.4554 81.5073 26.4554V26.4572ZM77.5534 24.1143C78.2359 25.1254 79.2276 25.8399 80.3583 26.1311C81.4853 26.4206 82.6527 26.2832 83.7395 25.7354L81.8714 22.1322C81.6628 22.178 81.4561 22.1762 81.253 22.1249C81.0993 22.0864 80.9547 22.0186 80.8212 21.9234L77.5552 24.1161L77.5534 24.1143ZM84.8739 25.2097L82.7752 21.1632L82.7935 21.1229C82.8301 21.0386 82.8594 20.9562 82.8814 20.8737C82.9253 20.7015 82.9399 20.5238 82.9253 20.3462L82.9198 20.293L86.7127 17.7468L86.7547 17.8512C87.2487 19.0803 87.3329 20.4945 86.9908 21.8336C86.6541 23.147 85.9534 24.2901 84.9617 25.1346L84.8739 25.2097ZM82.9783 21.1558L84.9306 24.9203C85.8472 24.1069 86.4968 23.0261 86.8133 21.7878C87.1371 20.5238 87.0694 19.1921 86.6267 18.0234L83.1101 20.3828C83.1192 20.5641 83.1028 20.7437 83.057 20.9177C83.0369 20.9965 83.0113 21.0752 82.9783 21.154V21.1558ZM76.8179 23.1012L76.7776 22.9932C76.333 21.7878 76.2727 20.4671 76.602 19.1719C76.9387 17.853 77.6778 16.6641 78.6805 15.827L78.7683 15.7537L80.7608 19.5951L80.7224 19.6409C80.5797 19.8167 80.479 20.0183 80.4223 20.2417C80.3875 20.381 80.3693 20.522 80.3729 20.6631V20.7125L76.8197 23.0994L76.8179 23.1012ZM76.6917 19.1939L76.7795 19.2159C76.4684 20.4359 76.5123 21.6779 76.9094 22.8191L80.1881 20.6191C80.1881 20.478 80.2082 20.337 80.243 20.1996C80.3016 19.9706 80.4022 19.7618 80.5412 19.5768L78.7098 16.0431C77.7803 16.851 77.096 17.9739 76.7776 19.2177L76.6898 19.1958L76.6917 19.1939ZM82.3983 19.357L82.3471 19.324C82.241 19.2544 82.1276 19.2031 82.005 19.1719C81.9336 19.1536 81.8586 19.1426 81.7763 19.139H81.7232L79.663 15.1639L79.7453 15.1217C80.8797 14.5429 82.1019 14.3963 83.2802 14.6986C84.4421 14.9953 85.4319 15.6988 86.1473 16.7283L86.2004 16.8052L82.3965 19.3588L82.3983 19.357ZM81.8385 18.9594C81.9135 18.9649 81.983 18.9778 82.0507 18.9943C82.1715 19.0254 82.2867 19.073 82.3947 19.1371L85.9424 16.7557C85.2581 15.8032 84.3231 15.1547 83.2345 14.8744C82.1276 14.5905 80.9804 14.7187 79.91 15.2426L81.8366 18.9594H81.8385Z" fill="#3FA79E"/>
|
||||
<path d="M34.8332 7.37695H39.4165L34.8607 25.7046H41.7219L41.173 27.9852H29.7285L34.8332 7.37695Z" fill="#4E1480"/>
|
||||
<path d="M49.1575 7.37695H60.6019L60.053 9.6576H53.1919L51.4628 16.5545H57.7202L57.1713 18.8351H50.9139L49.1849 25.7046H56.0461L55.4972 27.9852H44.0527L49.1575 7.37695Z" fill="#4E1480"/>
|
||||
<path d="M65.7887 23.3974C65.6241 24.0385 65.7064 24.5844 66.0357 25.0314C66.3651 25.4802 66.8499 25.7037 67.4903 25.7037C68.826 25.7037 69.7865 24.9343 70.372 23.3956H72.6499C72.1742 24.9343 71.4424 26.0847 70.4544 26.8449C69.4663 27.6051 68.1124 27.9843 66.3925 27.9843H65.2124C63.8585 27.9843 62.8284 27.6546 62.1239 26.9951C61.4195 26.3357 61.0664 25.5022 61.0664 24.4947C61.0664 24.1833 61.1121 23.8169 61.2036 23.3956L62.9052 16.5261C63.0699 15.9033 63.3443 15.3135 63.7285 14.7547C64.1128 14.196 64.5793 13.7106 65.1282 13.2984C65.6771 12.8863 66.2992 12.562 66.9945 12.3221C67.6897 12.0839 68.4216 11.9648 69.1901 11.9648H70.3702C71.8522 11.9648 72.9134 12.2268 73.5538 12.7489C74.1942 13.2709 74.5144 14.0531 74.5144 15.0991C74.5144 15.5388 74.4595 16.0151 74.3497 16.528H72.0718C72.273 15.0075 71.6967 14.2473 70.3427 14.2473C69.7024 14.2473 69.1077 14.4726 68.5588 14.9196C68.0099 15.3684 67.6532 15.9143 67.4885 16.5536L65.7869 23.3956L65.7887 23.3974Z" fill="#4E1480"/>
|
||||
<path d="M134.897 37.6796C134.613 37.6796 134.251 37.5881 134.081 37.3829V37.5734H133.6V32.6934H134.094V34.6113C134.331 34.2559 134.666 33.9848 135.135 33.9848C135.925 33.9848 136.425 34.7487 136.425 35.758C136.425 36.7674 135.905 37.676 134.897 37.676V37.6796ZM134.97 34.4556C134.621 34.4556 134.397 34.6461 134.245 34.8439C134.121 35.0088 134.094 35.0821 134.094 35.2598V36.7765C134.094 37.0605 134.344 37.2785 134.818 37.2785C135.543 37.2785 135.91 36.6447 135.91 35.8679C135.91 35.0912 135.601 34.4574 134.968 34.4574L134.97 34.4556Z" fill="#320756"/>
|
||||
<path d="M138.482 37.8194C138.284 38.4056 137.955 38.9331 137.086 38.9991L137.046 38.5576C137.585 38.5503 137.922 38.2077 138.067 37.6875L136.795 34.0879H137.335L138.034 36.211C138.165 36.6067 138.277 36.9822 138.317 37.1086H138.33C138.376 36.9236 138.482 36.581 138.601 36.1927L139.247 34.0897H139.715L138.484 37.8212L138.482 37.8194Z" fill="#320756"/>
|
||||
<path d="M144.456 37.5768V33.664C144.456 32.8506 143.984 32.378 143.139 32.378C142.601 32.378 142.129 32.6198 141.799 33.0485C141.591 33.3013 141.558 33.4222 141.558 33.7519V37.5768H140.734V31.7625H141.503V32.6858C141.942 32.0373 142.557 31.5977 143.402 31.5977C144.511 31.5977 145.279 32.2791 145.279 33.5321V37.5768H144.456Z" fill="#3FA79E"/>
|
||||
<path d="M149.219 37.7854C147.408 37.7854 146.65 36.2247 146.65 34.6529C146.65 33.0812 147.485 31.5645 149.263 31.5645C151.042 31.5645 151.832 33.0812 151.832 34.6639C151.832 36.2467 150.998 37.7854 149.219 37.7854ZM149.263 32.2789C147.957 32.2789 147.507 33.5868 147.507 34.6529C147.507 35.7191 147.891 37.06 149.219 37.06C150.548 37.06 150.976 35.6971 150.976 34.6639C150.976 33.6308 150.591 32.2789 149.263 32.2789Z" fill="#3FA79E"/>
|
||||
<path d="M154.873 37.7637C153.841 37.7637 153.391 37.0493 153.391 36.181V32.5649H152.414V31.8725H153.391V30.5975L154.214 30.3008V31.8725H155.795V32.5649H154.214V36.0821C154.214 36.7196 154.533 37.0493 155.137 37.0493C155.389 37.0493 155.609 36.9724 155.773 36.8844L155.927 37.5659C155.664 37.6758 155.257 37.7637 154.873 37.7637Z" fill="#3FA79E"/>
|
||||
<path d="M157.661 37.6416C157.32 37.6416 157.09 37.3998 157.09 37.0591C157.09 36.7184 157.309 36.4766 157.65 36.4766C157.99 36.4766 158.221 36.7184 158.221 37.0591C158.221 37.3998 158.001 37.6416 157.661 37.6416Z" fill="#4E1480"/>
|
||||
<path d="M159.418 37.5761V29.8164H160.318V37.5761H159.418Z" fill="#4E1480"/>
|
||||
<path d="M164.127 30.6407V37.5761H163.216V30.6407H160.658V29.8164H166.718V30.6407H164.127Z" fill="#4E1480"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_3896_98053">
|
||||
<rect width="174" height="39" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 17 KiB |
112
src/front/Components/DesignSystem/Button/classes.module.scss
Normal file
@ -0,0 +1,112 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
border: 1px solid;
|
||||
gap: 12px;
|
||||
box-sizing: border-box;
|
||||
height: fit-content;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: transparent;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
&[variant="primary"] {
|
||||
color: $white;
|
||||
background-color: $purple-flash;
|
||||
border-color: $purple-flash;
|
||||
padding: 24px 48px;
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
line-height: 22px;
|
||||
|
||||
&:hover {
|
||||
border-color: $purple-hover;
|
||||
background-color: $purple-hover;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
border-color: $purple-soft;
|
||||
background-color: $purple-soft;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
&[variant="secondary"] {
|
||||
color: $white;
|
||||
background-color: $red-flash;
|
||||
border-color: $red-flash;
|
||||
padding: 24px 48px;
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
line-height: 22px;
|
||||
|
||||
&:hover {
|
||||
border-color: $re-hover;
|
||||
background-color: $re-hover;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
border-color: $red-soft;
|
||||
background-color: $red-soft;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
&[variant="ghost"] {
|
||||
color: $pink-flash;
|
||||
background-color: transparent;
|
||||
border-color: $pink-flash;
|
||||
padding: 24px 48px;
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
line-height: 22px;
|
||||
|
||||
svg {
|
||||
path {
|
||||
stroke: $white;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: $pink-hover;
|
||||
color: $pink-hover;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
border-color: $pink-soft;
|
||||
background-color: $pink-soft;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
&[fullwidthattr="true"] {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&[touppercase="false"] {
|
||||
text-transform: inherit;
|
||||
}
|
||||
|
||||
&[variant="line"] {
|
||||
color: $pink-flash;
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
padding: 0;
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
line-height: 22px;
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
}
|
58
src/front/Components/DesignSystem/Button/index.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import Image from "next/image";
|
||||
import React, { CSSProperties } from "react";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
import classNames from "classnames";
|
||||
|
||||
export enum EButtonVariant {
|
||||
PRIMARY = "primary",
|
||||
SECONDARY = "secondary",
|
||||
GHOST = "ghost",
|
||||
LINE = "line",
|
||||
}
|
||||
|
||||
type IProps = {
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement> | undefined;
|
||||
children?: React.ReactNode;
|
||||
variant?: EButtonVariant;
|
||||
fullwidth?: boolean;
|
||||
icon?: string;
|
||||
iconstyle?: CSSProperties;
|
||||
disabled?: boolean;
|
||||
type?: "button" | "submit";
|
||||
isloading?: string;
|
||||
iconposition?: "left" | "right";
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export default function Button(props: IProps) {
|
||||
let {
|
||||
variant = EButtonVariant.PRIMARY,
|
||||
disabled = false,
|
||||
type = "button",
|
||||
isloading = "false",
|
||||
fullwidth = false,
|
||||
iconposition = "right",
|
||||
onClick,
|
||||
children,
|
||||
icon,
|
||||
iconstyle,
|
||||
className = "",
|
||||
} = props;
|
||||
|
||||
const fullwidthattr = fullwidth.toString();
|
||||
const isloadingattr = isloading.toString();
|
||||
|
||||
const attributes = { ...props, variant, disabled, type, isloadingattr, fullwidthattr };
|
||||
delete attributes.fullwidth;
|
||||
delete attributes.icon;
|
||||
delete attributes.iconstyle;
|
||||
delete attributes.iconposition;
|
||||
return (
|
||||
<button {...attributes} onClick={onClick} className={classNames(classes["root"], className)} type={type}>
|
||||
{icon && iconposition === "left" && <Image src={icon} style={iconstyle} alt={"button icon"} />}
|
||||
{children}
|
||||
{icon && iconposition === "right" && <Image src={icon} style={iconstyle} alt={"button icon"} />}
|
||||
</button>
|
||||
);
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
input[type="checkbox"] {
|
||||
appearance: none;
|
||||
background-color: transparent;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid $turquoise-flash;
|
||||
border-radius: 2px;
|
||||
margin-right: 16px;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
}
|
||||
|
||||
input[type="checkbox"]::before {
|
||||
content: url("../../../Assets/Icons/check.svg");
|
||||
place-content: flex-start;
|
||||
display: grid;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: $turquoise-flash;
|
||||
border-radius: 2px;
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked::before {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
34
src/front/Components/DesignSystem/CheckBox/index.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import React from "react";
|
||||
|
||||
import { IOption } from "../Select";
|
||||
import Tooltip from "../ToolTip";
|
||||
import Typography, { ITypo, ITypoColor } from "../Typography";
|
||||
import classes from "./classes.module.scss";
|
||||
|
||||
type IProps = {
|
||||
name?: string;
|
||||
option: IOption;
|
||||
toolTip?: string;
|
||||
};
|
||||
|
||||
export default class CheckBox extends React.Component<IProps> {
|
||||
static defaultProps = {
|
||||
toolTip: "",
|
||||
};
|
||||
|
||||
public override render(): JSX.Element {
|
||||
return (
|
||||
<Typography typo={ITypo.P_ERR_16} color={ITypoColor.BLACK}>
|
||||
<label className={classes["root"]}>
|
||||
<input
|
||||
type="checkbox"
|
||||
name={this.props.name ?? (this.props.option.value as string)}
|
||||
value={this.props.option.value as string}
|
||||
/>
|
||||
{this.props.option.label}
|
||||
{this.props.toolTip && <Tooltip className={classes["tooltip"]} text={this.props.toolTip} />}
|
||||
</label>
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
.root {
|
||||
padding: 24px;
|
||||
background-color: white;
|
||||
border: 1px dashed #e7e7e7;
|
||||
|
||||
height: fit-content;
|
||||
&[data-drag-over="true"] {
|
||||
border: 1px dashed grey;
|
||||
}
|
||||
|
||||
.top-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.left {
|
||||
margin-right: 28px;
|
||||
}
|
||||
|
||||
.separator {
|
||||
background-color: #939393;
|
||||
width: 1px;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.right {
|
||||
margin-left: 18px;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.documents-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
margin-top: 16px;
|
||||
|
||||
.file-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.left-part {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.cross{
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-container {
|
||||
margin-top: 16px;
|
||||
.add-button {
|
||||
.add-document {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
216
src/front/Components/DesignSystem/DepositDocument/index.tsx
Normal file
@ -0,0 +1,216 @@
|
||||
import DepositDocumentIcon from "@Assets/Icons/deposit-document.svg";
|
||||
import PlusIcon from "@Assets/Icons/plus.svg";
|
||||
import CrossIcon from "@Assets/Icons/cross.svg";
|
||||
import DocumentCheckIcon from "@Assets/Icons/document-check.svg";
|
||||
import Image from "next/image";
|
||||
import React from "react";
|
||||
|
||||
import Button, { EButtonVariant } from "../Button";
|
||||
import Tooltip from "../ToolTip";
|
||||
import Typography, { ITypo, ITypoColor } from "../Typography";
|
||||
import classes from "./classes.module.scss";
|
||||
import { Document, File as FileCustomer } from "le-coffre-resources/dist/Customer";
|
||||
import Files from "@Front/Api/LeCoffreApi/SuperAdmin/Files/Files";
|
||||
|
||||
type IProps = {
|
||||
title: string;
|
||||
dateAsked: Date;
|
||||
defaultFiles?: FileCustomer[];
|
||||
onChange?: (files: File[]) => void;
|
||||
document: Document;
|
||||
};
|
||||
|
||||
type IFile = {
|
||||
index: number;
|
||||
file: File;
|
||||
};
|
||||
|
||||
type IState = {
|
||||
files: IFile[];
|
||||
isDragOver: boolean;
|
||||
currentFiles?: FileCustomer[];
|
||||
};
|
||||
|
||||
export default class DepositDocument extends React.Component<IProps, IState> {
|
||||
private inputRef = React.createRef<HTMLInputElement>();
|
||||
private index = 0;
|
||||
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
files: [],
|
||||
isDragOver: false,
|
||||
currentFiles: this.props.defaultFiles,
|
||||
};
|
||||
|
||||
this.addDocument = this.addDocument.bind(this);
|
||||
this.onFileChange = this.onFileChange.bind(this);
|
||||
this.removeFile = this.removeFile.bind(this);
|
||||
this.onDragOver = this.onDragOver.bind(this);
|
||||
this.onDragDrop = this.onDragDrop.bind(this);
|
||||
this.onDragLeave = this.onDragLeave.bind(this);
|
||||
}
|
||||
|
||||
public override render(): JSX.Element {
|
||||
return (
|
||||
<div
|
||||
className={classes["root"]}
|
||||
onDragOver={this.onDragOver}
|
||||
onDrop={this.onDragDrop}
|
||||
onDragLeave={this.onDragLeave}
|
||||
data-drag-over={this.state.isDragOver.toString()}>
|
||||
<input type="file" ref={this.inputRef} hidden onChange={this.onFileChange} />
|
||||
<div className={classes["top-container"]}>
|
||||
<div className={classes["left"]}>
|
||||
<Image src={DepositDocumentIcon} alt="Deposit document" />
|
||||
</div>
|
||||
<div className={classes["separator"]} />
|
||||
<div className={classes["right"]}>
|
||||
<Typography typo={ITypo.P_SB_16} color={ITypoColor.BLACK} className={classes["title"]}>
|
||||
{this.props.title} <Tooltip text={"Blabla"} />
|
||||
</Typography>
|
||||
<Typography typo={ITypo.CAPTION_14} color={ITypoColor.GREY}>
|
||||
Demandé par le notaire le {this.formatDate(this.props.dateAsked)}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
{this.state.files.length > 0 && (
|
||||
<div className={classes["documents-container"]}>
|
||||
{this.state.files.map((file) => {
|
||||
const fileObj = file.file;
|
||||
return (
|
||||
<div className={classes["file-container"]} key={fileObj.name + file.index}>
|
||||
<div className={classes["left-part"]}>
|
||||
<Image src={DocumentCheckIcon} alt="Document check" />
|
||||
<Typography typo={ITypo.P_16} color={ITypoColor.GREY}>
|
||||
{this.shortName(fileObj.name)}
|
||||
</Typography>
|
||||
</div>
|
||||
<Image
|
||||
src={CrossIcon}
|
||||
alt="Cross icon"
|
||||
className={classes["cross"]}
|
||||
onClick={this.removeFile}
|
||||
data-file={file.index}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={classes["bottom-container"]}>
|
||||
<Button variant={EButtonVariant.LINE} className={classes["add-button"]} onClick={this.addDocument}>
|
||||
<Typography typo={ITypo.P_SB_16} color={ITypoColor.PINK_FLASH} className={classes["add-document"]}>
|
||||
Ajouter un document <Image src={PlusIcon} alt="Plus icon" />
|
||||
</Typography>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public override componentDidMount(): void {
|
||||
if (this.props.defaultFiles) {
|
||||
this.setState({
|
||||
files: this.props.defaultFiles.map((file) => ({
|
||||
index: this.index++,
|
||||
file: new File([""], file.file_path ?? "", {}),
|
||||
})),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private shortName(name: string): string {
|
||||
const maxLength = 20;
|
||||
if (name.length > maxLength) {
|
||||
return name.substring(0, maxLength / 2) + "..." + name.substring(name.length - maxLength / 2, name.length);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private onDragOver(event: React.DragEvent<HTMLDivElement>) {
|
||||
if (!this.state.isDragOver) {
|
||||
this.setState({
|
||||
isDragOver: true,
|
||||
});
|
||||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
private onDragLeave(event: React.DragEvent<HTMLDivElement>) {
|
||||
this.setState({
|
||||
isDragOver: false,
|
||||
});
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
private async onDragDrop(event: React.DragEvent<HTMLDivElement>) {
|
||||
event.preventDefault();
|
||||
this.setState({
|
||||
isDragOver: false,
|
||||
});
|
||||
const file = event.dataTransfer.files[0];
|
||||
if (file) this.addFile(file);
|
||||
}
|
||||
|
||||
private async addFile(file: File) {
|
||||
this.setState({
|
||||
files: [
|
||||
...this.state.files,
|
||||
{
|
||||
index: this.index++,
|
||||
file: file,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (this.props.onChange) this.props.onChange(this.state.files.map((file) => file.file));
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file, file.name);
|
||||
const query = JSON.stringify({ document: { uid: this.props.document.uid } });
|
||||
formData.append("q", query);
|
||||
|
||||
const newFile = await Files.getInstance().post(formData);
|
||||
const files = this.state.currentFiles ? [...this.state.currentFiles, newFile] : [newFile];
|
||||
this.setState({
|
||||
currentFiles: files,
|
||||
});
|
||||
}
|
||||
|
||||
private async removeFile(e: any) {
|
||||
const image = e.target as HTMLElement;
|
||||
const indexToRemove = image.getAttribute("data-file");
|
||||
if (!indexToRemove) return;
|
||||
// const file = this.state.files.find((file) => file.index === parseInt(indexToRemove));
|
||||
this.setState({
|
||||
files: this.state.files.filter((file) => file.index !== parseInt(indexToRemove)),
|
||||
});
|
||||
|
||||
if (this.props.onChange) this.props.onChange(this.state.files.map((file) => file.file));
|
||||
// TODO Finir la suppression de fichier
|
||||
// const deletedFileUid = this.props.document.files?.find((file) => file.file_path === newFile.file_path)?.uid;
|
||||
// console.log({ deletedFileUid });
|
||||
// await Files.getInstance().delete(file?.uid);
|
||||
}
|
||||
|
||||
private async onFileChange() {
|
||||
if (!this.inputRef.current) return;
|
||||
const files = this.inputRef.current.files;
|
||||
if (!files) return;
|
||||
const file = files[0];
|
||||
|
||||
if (file) this.addFile(file);
|
||||
}
|
||||
|
||||
private addDocument() {
|
||||
if (!this.inputRef.current) return;
|
||||
this.inputRef.current.click();
|
||||
}
|
||||
|
||||
private formatDate(date: Date) {
|
||||
return date.toLocaleDateString("fr-FR");
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
border: 1px solid $grey-medium;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
&.DEPOSITED {
|
||||
cursor: pointer;
|
||||
border: 1px solid $orange-soft;
|
||||
&:hover {
|
||||
border: 1px solid $orange-soft;
|
||||
outline: 1px solid $orange-soft;
|
||||
}
|
||||
}
|
||||
&.VALIDATED {
|
||||
cursor: pointer;
|
||||
border: 1px solid $green-soft;
|
||||
&:hover {
|
||||
border: 1px solid $green-soft;
|
||||
outline: 1px solid $green-soft;
|
||||
}
|
||||
}
|
||||
|
||||
.valid-radius {
|
||||
background-color: $green-flash;
|
||||
padding: 6px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
.trash {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
import ValidIcon from "@Assets/Icons/check-valid.svg";
|
||||
import TrashIcon from "@Assets/Icons/trash.svg";
|
||||
import classNames from "classnames";
|
||||
import { Document } from "le-coffre-resources/dist/Customer";
|
||||
import Image from "next/image";
|
||||
import { NextRouter, useRouter } from "next/router";
|
||||
import React from "react";
|
||||
|
||||
import Typography, { ITypo } from "../../Typography";
|
||||
import WarningBadge from "../../WarningBadge";
|
||||
import classes from "./classes.module.scss";
|
||||
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
|
||||
|
||||
type IProps = {
|
||||
folderUid: string;
|
||||
document: {
|
||||
uid?: Document["uid"];
|
||||
document_type?: Document["document_type"];
|
||||
document_status: Document["document_status"];
|
||||
folder?: Document["folder"];
|
||||
files?: Document["files"];
|
||||
};
|
||||
openDeletionModal?: (uid: Document["uid"]) => void;
|
||||
};
|
||||
|
||||
type IPropsClass = IProps & {
|
||||
router: NextRouter;
|
||||
};
|
||||
|
||||
type IState = {};
|
||||
|
||||
class DocumentNotaryClass extends React.Component<IPropsClass, IState> {
|
||||
public constructor(props: IPropsClass) {
|
||||
super(props);
|
||||
this.onOpenDeletionModal = this.onOpenDeletionModal.bind(this);
|
||||
this.onClick = this.onClick.bind(this);
|
||||
}
|
||||
public override render(): JSX.Element {
|
||||
return (
|
||||
<div className={classNames(classes["root"], classes[this.props.document.document_status])} onClick={this.onClick}>
|
||||
<div>
|
||||
<Typography typo={ITypo.P_SB_16}>{this.props.document?.document_type?.name}</Typography>
|
||||
<Typography typo={ITypo.CAPTION_14}>{this.getDocumentsTitle()}</Typography>
|
||||
</div>
|
||||
{this.renderIcon()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private getDocumentsTitle() {
|
||||
const documentFiles = this.props.document.files;
|
||||
if (documentFiles) {
|
||||
if (documentFiles.length === 1) {
|
||||
const fileName = documentFiles[0]?.file_path?.split("/").pop();
|
||||
if (fileName && fileName.length > 20) {
|
||||
return `${fileName.substring(0, 7)}...${fileName.substring(fileName.length - 7, fileName.length)}`;
|
||||
} else {
|
||||
return fileName;
|
||||
}
|
||||
} else {
|
||||
return `${documentFiles.length} documents déposés`;
|
||||
}
|
||||
} else {
|
||||
return "Aucun document déposé";
|
||||
}
|
||||
}
|
||||
|
||||
private onClick() {
|
||||
if (this.props.document.document_status !== EDocumentStatus.VALIDATED && this.props.document.document_status !== EDocumentStatus.DEPOSITED) return;
|
||||
this.props.router.push(`/folders/${this.props.folderUid}/documents/${this.props.document.uid}`);
|
||||
}
|
||||
|
||||
private renderIcon(): JSX.Element {
|
||||
switch (this.props.document.document_status) {
|
||||
case "VALIDATED":
|
||||
return (
|
||||
<div className={classes["valid-radius"]}>
|
||||
<Image src={ValidIcon} alt="valid icon" />
|
||||
</div>
|
||||
);
|
||||
case EDocumentStatus.DEPOSITED:
|
||||
return <WarningBadge />;
|
||||
default:
|
||||
return <Image src={TrashIcon} alt="trash icon" className={classes["trash"]} onClick={this.onOpenDeletionModal} />;
|
||||
}
|
||||
}
|
||||
|
||||
private onOpenDeletionModal(): void {
|
||||
if (!this.props.openDeletionModal) return;
|
||||
this.props.openDeletionModal(this.props.document.uid ?? "");
|
||||
}
|
||||
}
|
||||
|
||||
export default function DocumentNotary(props: IProps): JSX.Element {
|
||||
const router = useRouter();
|
||||
return <DocumentNotaryClass {...props} router={router} />;
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
.root {
|
||||
height: inherit;
|
||||
min-height: inherit;
|
||||
.file-container {
|
||||
height: inherit;
|
||||
min-height: inherit;
|
||||
.image {
|
||||
width: 100%;
|
||||
height: inherit;
|
||||
min-height: inherit;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.pdf {
|
||||
width: 100%;
|
||||
min-height: inherit;
|
||||
height: inherit;
|
||||
}
|
||||
}
|
||||
}
|
32
src/front/Components/DesignSystem/FilePreview/index.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import React from "react";
|
||||
|
||||
import Typography, { ITypo, ITypoColor } from "../Typography";
|
||||
import classes from "./classes.module.scss";
|
||||
|
||||
type IProps = {
|
||||
href: string;
|
||||
};
|
||||
type IState = {};
|
||||
|
||||
export default class FilePreview extends React.Component<IProps, IState> {
|
||||
override render() {
|
||||
const type = this.props.href.split(".").pop();
|
||||
return (
|
||||
<div className={classes["root"]}>
|
||||
{!type && (
|
||||
<Typography typo={ITypo.H1} color={ITypoColor.BLACK}>
|
||||
Erreur lors du chargement du fichier
|
||||
</Typography>
|
||||
)}
|
||||
{type && (
|
||||
<div className={classes["file-container"]}>
|
||||
{type === "pdf" && (
|
||||
<embed src={this.props.href} width="100%" height="100%" type="application/pdf" className={classes["pdf"]} />
|
||||
)}
|
||||
{type !== "pdf" && <img src={this.props.href} alt="File preview" className={classes["image"]} />}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
background-color: var(--grey-soft);
|
||||
padding: 24px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
gap: 32px;
|
||||
width: 100%;
|
||||
|
||||
.text-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
> :first-child {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: $screen-ls) {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
@media (max-width: $screen-s) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
&.single-information {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-icon-container {
|
||||
cursor: pointer;
|
||||
margin-left: 48px;
|
||||
}
|
||||
|
||||
@media (max-width: $screen-s) {
|
||||
flex-wrap: wrap;
|
||||
|
||||
.edit-icon-container {
|
||||
margin-left: 0px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
import PenICon from "@Assets/Icons/pen.svg";
|
||||
import { IDashBoardFolder } from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard";
|
||||
import Module from "@Front/Config/Module";
|
||||
import classNames from "classnames";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import React from "react";
|
||||
|
||||
import Typography, { ITypo } from "../Typography";
|
||||
import classes from "./classes.module.scss";
|
||||
|
||||
type IProps = {
|
||||
folder: IDashBoardFolder;
|
||||
type: EFolderBoxInformationType;
|
||||
isArchived?: boolean;
|
||||
};
|
||||
|
||||
export enum EFolderBoxInformationType {
|
||||
INFORMATIONS = "informations",
|
||||
DESCRIPTION = "description",
|
||||
ARCHIVED_DESCRIPTION = "archivedDescription",
|
||||
}
|
||||
|
||||
export default function FolderBoxInformation(props: IProps) {
|
||||
const { isArchived = false, type } = props;
|
||||
const editDescriptionPath = Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.Folder.pages.EditDescription.props.path.replace("[folderUid]", props.folder.uid ?? "");
|
||||
const editInformationsPath = Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.Folder.pages.EditInformations.props.path.replace("[folderUid]", props.folder.uid ?? "");
|
||||
|
||||
const path = type === EFolderBoxInformationType.DESCRIPTION ? editDescriptionPath : editInformationsPath;
|
||||
return (
|
||||
<div className={classNames(classes["root"], type !== EFolderBoxInformationType.INFORMATIONS && classes["single-information"])}>
|
||||
<div className={classes["content"]}>{renderContentByType(props.folder, type)}</div>
|
||||
{!isArchived && (
|
||||
<Link href={path} className={classes["edit-icon-container"]}>
|
||||
<Image src={PenICon} alt="edit informations" />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
function renderContentByType(folder: IDashBoardFolder, type: EFolderBoxInformationType) {
|
||||
switch (type) {
|
||||
case EFolderBoxInformationType.DESCRIPTION:
|
||||
return (
|
||||
<div className={classes["text-container"]}>
|
||||
<Typography typo={ITypo.NAV_INPUT_16}>Note dossier</Typography>
|
||||
<Typography typo={ITypo.P_18}>{folder.description ?? ""}</Typography>
|
||||
</div>
|
||||
);
|
||||
case EFolderBoxInformationType.ARCHIVED_DESCRIPTION:
|
||||
return (
|
||||
<div className={classes["text-container"]}>
|
||||
<Typography typo={ITypo.NAV_INPUT_16}>Note archive</Typography>
|
||||
<Typography typo={ITypo.P_18}>{folder.archived_description ?? ""}</Typography>
|
||||
</div>
|
||||
);
|
||||
case EFolderBoxInformationType.INFORMATIONS:
|
||||
return (
|
||||
<>
|
||||
<div className={classes["text-container"]}>
|
||||
<Typography typo={ITypo.NAV_INPUT_16}>Intitulé du dossier</Typography>
|
||||
<Typography typo={ITypo.P_18}>{folder.name ?? ""}</Typography>
|
||||
</div>
|
||||
<div className={classes["text-container"]}>
|
||||
<Typography typo={ITypo.NAV_INPUT_16}>Numéro de dossier</Typography>
|
||||
<Typography typo={ITypo.P_18}>{folder.folder_number ?? ""}</Typography>
|
||||
</div>
|
||||
<div className={classes["text-container"]}>
|
||||
<Typography typo={ITypo.NAV_INPUT_16}>Type d'acte</Typography>
|
||||
<Typography typo={ITypo.P_18}>{folder.deed?.deed_type?.name ?? ""}</Typography>
|
||||
</div>
|
||||
<div className={classes["text-container"]}>
|
||||
<Typography typo={ITypo.NAV_INPUT_16}>Ouverture du dossier</Typography>
|
||||
<Typography typo={ITypo.P_18}>{formatDate(folder.created_at)}</Typography>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function formatDate(date: Date | null): string {
|
||||
if (!date) return "...";
|
||||
if (!(date instanceof Date)) date = new Date(date);
|
||||
return date.toLocaleDateString("fr-FR", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
display: inline-flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 24px;
|
||||
border: 1px solid $grey-medium;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: $grey-medium;
|
||||
}
|
||||
|
||||
.left-side {
|
||||
display: inline-flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.warning {
|
||||
margin-left: 32px;
|
||||
}
|
||||
}
|
||||
}
|
49
src/front/Components/DesignSystem/FolderContainer/index.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import ChevronIcon from "@Assets/Icons/chevron.svg";
|
||||
import { IDashBoardFolder } from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard";
|
||||
import Image from "next/image";
|
||||
import React from "react";
|
||||
|
||||
import Typography, { ITypo } from "../Typography";
|
||||
import WarningBadge from "../WarningBadge";
|
||||
import classes from "./classes.module.scss";
|
||||
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
|
||||
|
||||
type IProps = {
|
||||
folder: IDashBoardFolder;
|
||||
onSelectedFolder?: (folder: IDashBoardFolder) => void;
|
||||
};
|
||||
type IState = {};
|
||||
|
||||
export default class FolderContainer extends React.Component<IProps, IState> {
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
this.onSelectedFolder = this.onSelectedFolder.bind(this);
|
||||
}
|
||||
|
||||
public override render(): JSX.Element {
|
||||
return (
|
||||
<div className={classes["root"]} onClick={this.onSelectedFolder}>
|
||||
<div className={classes["left-side"]}>
|
||||
<Typography typo={ITypo.P_16}>
|
||||
{this.props.folder.folder_number.concat(" - ").concat(this.props.folder.name)}
|
||||
</Typography>
|
||||
{this.countPendingDocuments() > 0 && (
|
||||
<div className={classes["warning"]}>
|
||||
<WarningBadge />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Image alt="chevron" src={ChevronIcon} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private countPendingDocuments(): number {
|
||||
if (!this.props.folder.documents) return 0;
|
||||
return this.props.folder.documents?.filter((document) => document.document_status === EDocumentStatus.DEPOSITED).length ?? 0;
|
||||
}
|
||||
|
||||
private onSelectedFolder(): void {
|
||||
this.props.onSelectedFolder && this.props.onSelectedFolder(this.props.folder);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
.active {
|
||||
background-color: var(--grey-medium);
|
||||
}
|
||||
}
|
61
src/front/Components/DesignSystem/FolderList/index.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
import { IDashBoardFolder } from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import React from "react";
|
||||
|
||||
import FolderContainer from "../FolderContainer";
|
||||
import classes from "./classes.module.scss";
|
||||
import Module from "@Front/Config/Module";
|
||||
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
|
||||
|
||||
type IProps = {
|
||||
folders: IDashBoardFolder[];
|
||||
isArchived: boolean;
|
||||
onSelectedFolder?: (folder: IDashBoardFolder) => void;
|
||||
onCloseLeftSide?: () => void;
|
||||
};
|
||||
|
||||
type IPropsClass = IProps & {
|
||||
selectedFolder: string;
|
||||
};
|
||||
|
||||
type IState = {};
|
||||
|
||||
class FolderListClass extends React.Component<IPropsClass, IState> {
|
||||
public override render(): JSX.Element {
|
||||
const redirectPath: string = this.props.isArchived
|
||||
? Module.getInstance().get().modules.pages.Folder.pages.FolderArchived.pages.FolderInformation.props.path
|
||||
: Module.getInstance().get().modules.pages.Folder.pages.FolderInformation.props.path;
|
||||
return (
|
||||
<div className={classes["root"]}>
|
||||
{this.props.folders.sort((folder) => {
|
||||
const pendingDocuments = (folder.documents ?? []).filter((document) => document.document_status === EDocumentStatus.DEPOSITED);
|
||||
return pendingDocuments.length >= 1 ? -1 : 1;
|
||||
}).sort((folder1, folder2) => {
|
||||
return folder1.created_at! < folder2.created_at! ? -1 : 1;
|
||||
}).sort((folder1, folder2) => {
|
||||
return folder1.folder_number! < folder2.folder_number! ? -1 : 1;
|
||||
}).map((folder) => {
|
||||
return (
|
||||
<div
|
||||
onClick={this.props.onCloseLeftSide}
|
||||
key={folder.uid}
|
||||
className={folder.uid === this.props.selectedFolder ? classes["active"] : ""}>
|
||||
<Link href={redirectPath.replace("[folderUid]", folder.uid ?? "")}>
|
||||
<FolderContainer folder={folder} onSelectedFolder={this.props.onSelectedFolder} />;
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
;
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default function FolderList(props: IProps) {
|
||||
const router = useRouter();
|
||||
let { folderUid } = router.query;
|
||||
folderUid = folderUid as string;
|
||||
return <FolderListClass {...props} selectedFolder={folderUid} />;
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
.searchbar {
|
||||
padding: 40px 24px 24px 24px;
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
import { IDashBoardFolder } from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard";
|
||||
import Link from "next/link";
|
||||
import React from "react";
|
||||
|
||||
import Button from "../Button";
|
||||
import FolderList from "../FolderList";
|
||||
import SearchBar from "../SearchBar";
|
||||
import classes from "./classes.module.scss";
|
||||
import Module from "@Front/Config/Module";
|
||||
|
||||
type IProps = {
|
||||
folders: IDashBoardFolder[];
|
||||
isArchived: boolean;
|
||||
onSelectedFolder?: (folder: IDashBoardFolder) => void;
|
||||
onCloseLeftSide?: () => void;
|
||||
};
|
||||
type IState = {
|
||||
filteredFolders: IDashBoardFolder[];
|
||||
};
|
||||
|
||||
export default class FolderListContainer extends React.Component<IProps, IState> {
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
filteredFolders: this.props.folders,
|
||||
};
|
||||
this.filterFolders = this.filterFolders.bind(this);
|
||||
}
|
||||
|
||||
public override render(): JSX.Element {
|
||||
const navigatePath = Module.getInstance().get().modules.pages.Folder.pages.CreateFolder.props.path;
|
||||
return (
|
||||
<div className={classes["root"]}>
|
||||
<div>
|
||||
<div className={classes["searchbar"]}>
|
||||
<SearchBar folders={this.props.folders} onChange={this.filterFolders} placeholder="Chercher un dossier" />
|
||||
</div>
|
||||
<FolderList
|
||||
folders={this.state.filteredFolders}
|
||||
onSelectedFolder={this.props.onSelectedFolder && this.props.onSelectedFolder}
|
||||
onCloseLeftSide={this.props.onCloseLeftSide}
|
||||
isArchived={this.props.isArchived}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
{!this.props.isArchived && (
|
||||
<Link href={navigatePath}>
|
||||
<Button fullwidth={true}>Créer un dossier</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private filterFolders(folders: IDashBoardFolder[]): IDashBoardFolder[] {
|
||||
this.setState({ filteredFolders: folders });
|
||||
return folders;
|
||||
}
|
||||
}
|
154
src/front/Components/DesignSystem/Form/Elements/BaseField.tsx
Normal file
@ -0,0 +1,154 @@
|
||||
import { ChangeEvent, Component, createRef } from "react";
|
||||
|
||||
import { FormContext, IFormContext } from "..";
|
||||
// elements
|
||||
import Validators, { IValidationTypes } from "../Validators/Validators";
|
||||
|
||||
export type IError = {
|
||||
message: string;
|
||||
validator: string;
|
||||
value: string | number | readonly string[];
|
||||
args: any[];
|
||||
isErrored?: (hasError: boolean) => void;
|
||||
};
|
||||
|
||||
export type INewBasefieldProps = {
|
||||
onChange?: (event: ChangeEvent<HTMLInputElement & HTMLSelectElement & HTMLTextAreaElement>) => void;
|
||||
name: string;
|
||||
regex?: RegExp;
|
||||
onCancel?: () => void;
|
||||
disableValidation?: boolean;
|
||||
onErrors?: (errors: IError[]) => void;
|
||||
fieldRef?: React.RefObject<any>;
|
||||
};
|
||||
|
||||
export type IProps = IValidationTypes & React.InputHTMLAttributes<HTMLInputElement> & INewBasefieldProps;
|
||||
|
||||
type IState = {
|
||||
value?: string | number | readonly string[];
|
||||
errors: IError[];
|
||||
};
|
||||
|
||||
export default abstract class BaseField<P extends IProps> extends Component<P, IState> {
|
||||
public static override contextType = FormContext;
|
||||
public override context: IFormContext | null = null;
|
||||
public fieldRef: React.RefObject<any> = createRef();
|
||||
|
||||
static defaultProps: Partial<IProps> = {
|
||||
disableValidation: false,
|
||||
};
|
||||
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.validate = this.validate.bind(this);
|
||||
|
||||
this.state = {
|
||||
value: this.props.value ?? this.props.defaultValue ?? "",
|
||||
errors: [],
|
||||
};
|
||||
}
|
||||
|
||||
public override componentDidMount() {
|
||||
this.context?.setField(this.props.name, this);
|
||||
}
|
||||
|
||||
public override componentDidUpdate(prevProps: P) {
|
||||
if (prevProps.value !== this.props.value || prevProps.defaultValue !== this.props.defaultValue) {
|
||||
this.setState({ value: this.props.value ?? this.props.defaultValue ?? "" });
|
||||
}
|
||||
}
|
||||
|
||||
public override componentWillUnmount() {
|
||||
this.context?.unSetField(this.props.name);
|
||||
}
|
||||
|
||||
public async onBlur(event: React.FocusEvent<HTMLInputElement, Element>) {
|
||||
// this.validate();
|
||||
// if (this.props.onBlur) {
|
||||
// this.props.onBlur(event);
|
||||
// }
|
||||
}
|
||||
|
||||
public async validate(isOnSubmit?: boolean) {
|
||||
if (this.props.disableValidation) return;
|
||||
if (this.props.readOnly) return;
|
||||
|
||||
const errorArray: IError[] = [];
|
||||
const props: { [key: string]: any } = this.props;
|
||||
const validators = Object.entries(Validators).filter(([key]) => props[key]);
|
||||
|
||||
const isValidable = isOnSubmit
|
||||
? this.props.required || (this.state.value && this.state.value !== "")
|
||||
: this.state.value && this.state.value !== "";
|
||||
|
||||
if (isValidable) {
|
||||
const validations = await Promise.all(
|
||||
validators.map(async ([key, validator]) => {
|
||||
const validation = await (validator.validate as any)(this.state.value, ...(props[key].args ?? []));
|
||||
if (props[key].isErrored) {
|
||||
props[key].isErrored(!validation);
|
||||
}
|
||||
return [key, validator, validation];
|
||||
}),
|
||||
);
|
||||
|
||||
const unValidateds = validations.filter(([key, validator, validation]) => !validation);
|
||||
const errors: IError[] = unValidateds.map(([key, unValidated]) => {
|
||||
let message = unValidated.message;
|
||||
if (typeof props[key] === "object" && props[key].message) message = props[key].message;
|
||||
return { message, validator: key, value: this.state.value!, args: props[key].args ?? [] };
|
||||
});
|
||||
|
||||
errorArray.push(...errors);
|
||||
} else {
|
||||
validators.forEach(async ([key]) => {
|
||||
if (props[key].isErrored) {
|
||||
props[key].isErrored(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({ errors: errorArray });
|
||||
this.onErrors(errorArray);
|
||||
return errorArray;
|
||||
}
|
||||
|
||||
public setErrors(errors: IError[]) {
|
||||
this.setState({ ...this.state, errors });
|
||||
}
|
||||
|
||||
/**
|
||||
* It is automatically called by the parent form when the user cancelled the
|
||||
* form and all of its changes.
|
||||
*
|
||||
* Override the method for custom cancelling logic, or pass a custom onCancel
|
||||
* callback.
|
||||
*/
|
||||
public cancel() {
|
||||
if (this.props.onCancel) {
|
||||
this.props.onCancel();
|
||||
}
|
||||
}
|
||||
|
||||
public onErrors(errors: IError[]) {
|
||||
if (this.props.onErrors) {
|
||||
this.props.onErrors(errors);
|
||||
}
|
||||
}
|
||||
|
||||
protected onChange(event: ChangeEvent<HTMLInputElement & HTMLSelectElement & HTMLTextAreaElement>) {
|
||||
if (this.props.regex) {
|
||||
if (!this.props.regex.test(event.currentTarget.value)) {
|
||||
event.currentTarget.value = event.currentTarget.value.substring(0, event.currentTarget.value.length - 1);
|
||||
}
|
||||
}
|
||||
this.setState({ value: event.currentTarget.value }, () => {
|
||||
this.validate();
|
||||
this.context?.onFieldChange(this.props.name, this);
|
||||
});
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(event);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
position: relative;
|
||||
|
||||
textarea {
|
||||
resize: none;
|
||||
height: auto;
|
||||
box-sizing: border-box;
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.input {
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 24px;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
height: 70px;
|
||||
border: 1px solid $grey-medium;
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
background: white;
|
||||
opacity: 0.6;
|
||||
~ .fake-placeholder {
|
||||
color: rgba($grey, 0.6);
|
||||
/**
|
||||
TODO
|
||||
* 1. Add styles so the placeholder has the same color as the background when disabled
|
||||
*/
|
||||
// background: transparent;
|
||||
}
|
||||
}
|
||||
&:focus {
|
||||
~ .fake-placeholder {
|
||||
transform: translateY(-35px);
|
||||
}
|
||||
}
|
||||
|
||||
&:not([data-value=""]) {
|
||||
~ .fake-placeholder {
|
||||
transform: translateY(-35px);
|
||||
}
|
||||
}
|
||||
|
||||
&[type="number"] {
|
||||
&:focus {
|
||||
~ .fake-placeholder {
|
||||
transform: translateY(-35px);
|
||||
}
|
||||
}
|
||||
|
||||
&:not([data-value=""]) {
|
||||
~ .fake-placeholder {
|
||||
transform: translateY(-35px);
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-inner-spin-button,
|
||||
&::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* For Chrome, Safari, and Opera */
|
||||
&::-webkit-inner-spin-button,
|
||||
&::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
/* For IE 10+ */
|
||||
&::-ms-inner-spin-button,
|
||||
&::-ms-outer-spin-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:not([data-value=""]) {
|
||||
~ .fake-placeholder {
|
||||
transform: translateY(-35px);
|
||||
}
|
||||
}
|
||||
|
||||
~ .fake-placeholder {
|
||||
z-index: 2;
|
||||
top: 35%;
|
||||
margin-left: 8px;
|
||||
padding: 0 16px;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
background: $white;
|
||||
transition: transform 0.3s ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.textarea {
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 24px;
|
||||
gap: 10px;
|
||||
|
||||
width: 100%;
|
||||
height: 70px;
|
||||
border: 1px solid $grey-medium;
|
||||
|
||||
~ .fake-placeholder {
|
||||
z-index: 2;
|
||||
top: -12px;
|
||||
margin-left: 8px;
|
||||
padding: 0 16px;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
background: $white;
|
||||
transform: translateY(35px);
|
||||
transition: transform 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
~ .fake-placeholder {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
}
|
||||
|
||||
&:not([data-value=""]) {
|
||||
~ .fake-placeholder {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
import Validators from "../../Validators/Validators";
|
||||
import BaseField, { IProps as IBaseFieldProps } from "../BaseField";
|
||||
import classes from "./classes.module.scss";
|
||||
|
||||
export type IProps = IBaseFieldProps & {
|
||||
fakeplaceholder: string;
|
||||
textarea?: boolean;
|
||||
};
|
||||
|
||||
// @ts-ignore TODO: typing error on IProps (validator class?? cf Massi 22/02/23)
|
||||
export default class InputField extends BaseField<IProps> {
|
||||
static override defaultProps: Partial<IBaseFieldProps> = {
|
||||
...BaseField.defaultProps,
|
||||
required: true
|
||||
}
|
||||
|
||||
public override render(): ReactNode {
|
||||
let pattern;
|
||||
|
||||
if (this.props.type === "number") {
|
||||
pattern = "(^[0-9]*)(\\.{0,1})([0-9]*)$";
|
||||
}
|
||||
|
||||
if (this.props.pattern) {
|
||||
pattern = this.props.pattern;
|
||||
}
|
||||
|
||||
if (this.props.fieldRef) {
|
||||
this.fieldRef = this.props.fieldRef;
|
||||
}
|
||||
|
||||
// we always need to control the input so we need to set the value as "" by default
|
||||
const value = this.state.value ?? "";
|
||||
|
||||
if (this.props.textarea === true) {
|
||||
return (
|
||||
<Typography typo={ITypo.NAV_INPUT_16} color={ITypoColor.GREY}>
|
||||
<div className={classes["root"]}>
|
||||
<textarea
|
||||
maxLength={this.props.maxLength}
|
||||
name={this.props.name}
|
||||
required={this.props.required}
|
||||
ref={this.props.fieldRef}
|
||||
rows={4}
|
||||
data-value={value}
|
||||
onChange={this.onChange}
|
||||
data-has-validation-errors={this.state.errors.length > 0}
|
||||
className={
|
||||
this.props.className ? [classes["textarea"], classes[this.props.className]].join(" ") : classes["textarea"]
|
||||
}
|
||||
value={value}
|
||||
/>
|
||||
<div className={classes["fake-placeholder"]}>{this.props.fakeplaceholder} {!this.props.required && " (Facultatif)"}</div>
|
||||
</div>
|
||||
</Typography>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Typography typo={ITypo.NAV_INPUT_16} color={ITypoColor.GREY}>
|
||||
<div className={classes["root"]}>
|
||||
<input
|
||||
{...this.getHtmlAttributes()}
|
||||
ref={this.props.fieldRef}
|
||||
pattern={pattern}
|
||||
onChange={this.onChange}
|
||||
onBlur={this.onBlur}
|
||||
data-value={value}
|
||||
data-has-validation-errors={this.state.errors.length > 0}
|
||||
className={
|
||||
this.props.className ? [classes["input"], classes[this.props.className]].join(" ") : classes["input"]
|
||||
}
|
||||
value={value}
|
||||
/>
|
||||
<div className={classes["fake-placeholder"]}>{this.props.fakeplaceholder} {!this.props.required && " (Facultatif)"}</div>
|
||||
</div>
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public override componentDidMount() {
|
||||
this.setState({
|
||||
value: this.props.defaultValue ?? "",
|
||||
})
|
||||
}
|
||||
|
||||
// We filter the props we'll pass to the primitive input as they're useless for it
|
||||
// It also avoids the console warning because of passing useless props to a primitive DOM element
|
||||
private getHtmlAttributes() {
|
||||
const htmlAttributes = { ...this.props };
|
||||
|
||||
delete htmlAttributes.disableValidation;
|
||||
delete htmlAttributes.onErrors;
|
||||
delete htmlAttributes.fieldRef;
|
||||
delete htmlAttributes.className;
|
||||
delete htmlAttributes.defaultValue;
|
||||
|
||||
for (const validator in Validators) {
|
||||
delete (htmlAttributes as { [key: string]: any })[validator];
|
||||
}
|
||||
|
||||
return htmlAttributes;
|
||||
}
|
||||
}
|
77
src/front/Components/DesignSystem/Form/InputField/index.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
import { ReactNode } from "react";
|
||||
|
||||
import Validators from "../Validators/Validators";
|
||||
//import { IProps as IBaseFieldProps } from "../.";
|
||||
import classes from "./classes.module.scss";
|
||||
import BaseField, { IProps as IBaseFieldProps } from "../Elements/BaseField";
|
||||
|
||||
export type IProps = IBaseFieldProps & {
|
||||
large?: boolean;
|
||||
};
|
||||
|
||||
export default class InputField extends BaseField<IProps> {
|
||||
public override render(): ReactNode {
|
||||
let pattern;
|
||||
|
||||
if (this.props.type === "number") {
|
||||
pattern = "(^[0-9]*)(\\.{0,1})([0-9]*)$";
|
||||
}
|
||||
|
||||
if (this.props.pattern) {
|
||||
pattern = this.props.pattern;
|
||||
}
|
||||
|
||||
if (this.props.fieldRef) {
|
||||
this.fieldRef = this.props.fieldRef;
|
||||
}
|
||||
|
||||
// we always need to control the input so we need to set the value as "" by default
|
||||
const value = this.state.value ?? "";
|
||||
|
||||
if (this.props.large === true) {
|
||||
return (
|
||||
<textarea
|
||||
maxLength={this.props.maxLength}
|
||||
name={this.props.name}
|
||||
required={this.props.required}
|
||||
ref={this.props.fieldRef}
|
||||
rows={4}
|
||||
value={value}
|
||||
onChange={this.onChange}
|
||||
data-has-validation-errors={this.state.errors.length > 0}
|
||||
className={this.props.className ? [classes["textarea"], classes[this.props.className]].join(" ") : classes["textarea"]}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<input
|
||||
{...this.getHtmlAttributes()}
|
||||
ref={this.props.fieldRef}
|
||||
pattern={pattern}
|
||||
onChange={this.onChange}
|
||||
value={value}
|
||||
data-has-validation-errors={this.state.errors.length > 0}
|
||||
className={this.props.className ? [classes["input"], classes[this.props.className]].join(" ") : classes["input"]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// We filter the props we'll pass to the primitive input as they're useless for it
|
||||
// It also avoids the console warning because of passing useless props to a primitive DOM element
|
||||
private getHtmlAttributes() {
|
||||
const htmlAttributes = { ...this.props };
|
||||
|
||||
delete htmlAttributes.disableValidation;
|
||||
delete htmlAttributes.onErrors;
|
||||
delete htmlAttributes.fieldRef;
|
||||
delete htmlAttributes.className;
|
||||
delete htmlAttributes.defaultValue;
|
||||
|
||||
for (const validator in Validators) {
|
||||
delete (htmlAttributes as { [key: string]: any })[validator];
|
||||
}
|
||||
|
||||
return htmlAttributes;
|
||||
}
|
||||
}
|
180
src/front/Components/DesignSystem/Form/Validators/Validators.ts
Normal file
@ -0,0 +1,180 @@
|
||||
import { isEmail, isNotEmpty, isNumberString, isString, max, maxLength, min, minLength } from "class-validator";
|
||||
|
||||
const Validators = {
|
||||
/**
|
||||
* **Parameters** : boolean
|
||||
*
|
||||
* This validator verifies the value is not empty
|
||||
*/
|
||||
required: { validate: isNotEmpty, message: "validation_messages.field_required" },
|
||||
|
||||
/**
|
||||
* **Parameters** : boolean
|
||||
*
|
||||
* This validator verifies the value is a number
|
||||
*/
|
||||
numbersOnly: { validate: isNumberString, message: "validation_messages.only_numbers" },
|
||||
|
||||
/**
|
||||
* **Parameters** : boolean
|
||||
*
|
||||
* This validator verifies the value is a number
|
||||
*/
|
||||
intOnly: {
|
||||
validate: function (value: string) {
|
||||
const regex = /^[0-9]*$/;
|
||||
return regex.test(value);
|
||||
},
|
||||
message: "validation_messages.only_integers",
|
||||
},
|
||||
|
||||
/**
|
||||
* **Parameters** : number
|
||||
*
|
||||
* This validator verifies the number is not below the parameter
|
||||
*/
|
||||
minNumber: { validate: (value: string, minVal: number) => min(Number(value), minVal), message: "validation_messages.below_min" },
|
||||
|
||||
/**
|
||||
* **Parameters** : number
|
||||
*
|
||||
* This validator verifies the number is not above the parameter
|
||||
*/
|
||||
maxNumber: { validate: (value: string, maxVal: number) => max(Number(value), maxVal), message: "validation_messages.above_max" },
|
||||
|
||||
/**
|
||||
* **Parameters** : number
|
||||
*
|
||||
* This validator verifies the string minimum length is conform to the parameter
|
||||
*/
|
||||
minLength: {
|
||||
validate: minLength,
|
||||
message: "validation_messages.min_string_length",
|
||||
},
|
||||
|
||||
/**
|
||||
* **Parameters** : number
|
||||
*
|
||||
* This validator verifies the string maximum length is conform to the parameter
|
||||
*/
|
||||
maxLength: {
|
||||
validate: maxLength,
|
||||
message: "validation_messages.max_string_length",
|
||||
},
|
||||
/**
|
||||
* **Parameters** : boolean
|
||||
*
|
||||
* This validator verifies the input's value is a string.
|
||||
*/
|
||||
isString: { validate: (value: string) => isString(value), message: "validation_messages.only_letters" },
|
||||
|
||||
/**
|
||||
* **Parameters** : boolean
|
||||
*
|
||||
* This validator verifies the input's value is conform to the tag regex.
|
||||
*/
|
||||
isTag: {
|
||||
validate: function (value: string) {
|
||||
const regex = /^[a-zA-Z0-9][a-zA-Z0-9 ]*(,[a-zA-Z0-9][a-zA-Z0-9 ]*)*$/;
|
||||
const isValid = regex.test(value);
|
||||
if (!isValid) return false;
|
||||
|
||||
const splittedTag = value.split(",");
|
||||
if (splittedTag.length !== new Set(splittedTag).size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
message: "validation_messages.not_valid_tag",
|
||||
},
|
||||
/**
|
||||
* **Parameters** : boolean
|
||||
*
|
||||
* This validator verifies the input's value is a valid email.
|
||||
*
|
||||
* If the **input is empty, it is considered valid**. If you do not wish this
|
||||
* to happen please refer to the `required` validator.
|
||||
*/
|
||||
isEmail: {
|
||||
validate: (value: string) => (Boolean(value) ? isEmail(value) : true),
|
||||
message: "validation_messages.invalid_email",
|
||||
},
|
||||
|
||||
isPseudo: {
|
||||
validate: (value: string) => {
|
||||
const pseudoRegex = /^[a-zA-Z][a-zA-Z0-9_-]{2,19}$/;
|
||||
return pseudoRegex.test(value);
|
||||
},
|
||||
message: "validation_messages.is_pseudo",
|
||||
},
|
||||
|
||||
noSpaceInString: {
|
||||
validate: (value: string) => {
|
||||
const regex = /^\S*$/;
|
||||
return regex.test(value);
|
||||
},
|
||||
message: "validation_messages.no_space_in_string",
|
||||
},
|
||||
|
||||
isPositiveNumber: {
|
||||
validate: (value: string) => {
|
||||
let nbr = parseFloat(value);
|
||||
return !(isNaN(nbr) || nbr <= 0);
|
||||
},
|
||||
message: "validation_messages.positive_number",
|
||||
},
|
||||
|
||||
floatPrecision: {
|
||||
validate: (value: string, precision: number) => {
|
||||
// If value is not a float
|
||||
if (isNaN(parseFloat(value))) return false;
|
||||
let splittedValue = value.split(".");
|
||||
// If there is no decimals
|
||||
if (!splittedValue[1]) return true;
|
||||
// If there is more decimals than the required precision
|
||||
if (splittedValue[1].length > precision) return false;
|
||||
return true;
|
||||
},
|
||||
message: "validation_messages.float_precision",
|
||||
},
|
||||
|
||||
isUrl: {
|
||||
validate: (value: string, root: string | string[]) => {
|
||||
try {
|
||||
const url = new URL(value);
|
||||
if (root) {
|
||||
if (typeof root === "string") {
|
||||
return url.hostname === root || url.hostname === `www.${root}`;
|
||||
} else {
|
||||
return root.some((r) => url.hostname === r || url.hostname === `www.${r}`);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
message: "validation_messages.invalid_url",
|
||||
},
|
||||
|
||||
// isUniqueEmail: { TODO : uncomment and implement DB request
|
||||
// validate: async (value: string, actual: string) => {
|
||||
// try {
|
||||
// const users = await AppUser.getInstance().getUsers({email: value});
|
||||
// if (!users.metadata.count) return true;
|
||||
// if (users.data.length > 1) return false;
|
||||
// if (users.data[0]?.email === actual) return true;
|
||||
// return false;
|
||||
// } catch {
|
||||
// return true;
|
||||
// }
|
||||
// },
|
||||
// message: "validation_messages.unique_email",
|
||||
// },
|
||||
};
|
||||
|
||||
export default Validators;
|
||||
export type IValidationTypes = Partial<
|
||||
Record<keyof typeof Validators, boolean | Partial<{ message: string; args: any[]; isErrored: (errored: boolean) => void }>>
|
||||
>;
|
191
src/front/Components/DesignSystem/Form/index.tsx
Normal file
@ -0,0 +1,191 @@
|
||||
import React, { ReactNode } from "react";
|
||||
|
||||
import BaseField, { IProps as IBaseFieldProps, IError } from "./Elements/BaseField";
|
||||
|
||||
export type IBaseField = BaseField<IBaseFieldProps>;
|
||||
|
||||
export type IFormContext = {
|
||||
setField: (name: string, field: IBaseField) => void;
|
||||
unSetField: (name: string) => void;
|
||||
onFieldChange: (name: string, field: IBaseField) => void;
|
||||
};
|
||||
|
||||
type IFields = {
|
||||
[key: string]: IBaseField;
|
||||
};
|
||||
|
||||
export type IApiFormErrors = {
|
||||
[fieldName: string]: string;
|
||||
};
|
||||
|
||||
export type IFormErrors = {
|
||||
[key: string]: {
|
||||
field: IBaseField;
|
||||
errors: IError[] | undefined;
|
||||
};
|
||||
};
|
||||
|
||||
type IState = {};
|
||||
export type IProps = {
|
||||
onFieldChange?: (name: string, field: IBaseField, errors: IFormErrors | null) => void;
|
||||
onSubmit?: (
|
||||
e: React.FormEvent<HTMLFormElement> | null,
|
||||
values: { [key: string]: string },
|
||||
onApiErrors: (apiFormErrors: IApiFormErrors | null) => void,
|
||||
) => void;
|
||||
onValidated?: () => void;
|
||||
onErrors?: (errors: IFormErrors) => void;
|
||||
/**
|
||||
* @description Url, No redirection without action
|
||||
*/
|
||||
action?: string;
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
export const FormContext = React.createContext<IFormContext>({ setField: () => {}, unSetField: () => {}, onFieldChange: () => {} });
|
||||
|
||||
export default class Form extends React.Component<IProps, IState> {
|
||||
protected fields: IFields = {};
|
||||
private formRef: React.RefObject<HTMLFormElement>;
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {};
|
||||
this.setField = this.setField.bind(this);
|
||||
this.unSetField = this.unSetField.bind(this);
|
||||
this.onFieldChange = this.onFieldChange.bind(this);
|
||||
this.onSubmit = this.onSubmit.bind(this);
|
||||
this.onSubmitErrorApi = this.onSubmitErrorApi.bind(this);
|
||||
this.formRef = React.createRef();
|
||||
}
|
||||
|
||||
public override render() {
|
||||
return (
|
||||
<FormContext.Provider
|
||||
value={{
|
||||
setField: this.setField,
|
||||
unSetField: this.unSetField,
|
||||
onFieldChange: this.onFieldChange,
|
||||
}}>
|
||||
<form className={this.props.className} ref={this.formRef} onSubmit={this.onSubmit} action={this.props.action ?? ""}>
|
||||
{this.props.children}
|
||||
</form>
|
||||
</FormContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
public async onSubmit(e: React.FormEvent<HTMLFormElement> | null) {
|
||||
if (!this.props.action) e?.preventDefault();
|
||||
|
||||
const errors = await this.validate();
|
||||
|
||||
if (errors) {
|
||||
e?.preventDefault();
|
||||
|
||||
this.onErrors(errors);
|
||||
|
||||
return { errors };
|
||||
}
|
||||
|
||||
if (this.props.onValidated) this.props.onValidated();
|
||||
|
||||
const allChildren = this.getAllChildrenFields(e);
|
||||
const elementsValues = allChildren
|
||||
.filter((e) => {
|
||||
return e.getAttribute("type") !== "radio" && e.getAttribute("type") !== "checkbox";
|
||||
})
|
||||
.reduce((obj, element) => ({ ...obj, [element.getAttribute("name") ?? ""]: (element as any).value }), {});
|
||||
|
||||
const radioInputs = allChildren.filter((e) => e.getAttribute("type") === "radio").filter((e) => (e as any).checked);
|
||||
const radioInputsValues = radioInputs.reduce(
|
||||
(obj, element) => ({ ...obj, [element.getAttribute("name") ?? ""]: (element as any).value }),
|
||||
{},
|
||||
);
|
||||
|
||||
const checkBoxesInput = allChildren.filter((e) => e.getAttribute("type") === "checkbox").filter((e) => (e as any).checked);
|
||||
const checkBoxesValues = checkBoxesInput.reduce((obj, element) => {
|
||||
const inputName = element.getAttribute("name") ?? "";
|
||||
const inputValue = (element as any).value as string;
|
||||
const newValue = (obj as any)[inputName] as string[] ?? [];
|
||||
newValue.push(inputValue);
|
||||
return {
|
||||
...obj,
|
||||
[inputName]: newValue,
|
||||
};
|
||||
}, {});
|
||||
|
||||
const allInputs = {
|
||||
...elementsValues,
|
||||
...radioInputsValues,
|
||||
...checkBoxesValues,
|
||||
};
|
||||
|
||||
// Deleting empty input
|
||||
delete (allInputs as any)[""];
|
||||
if (this.props.onSubmit) {
|
||||
this.props.onSubmit(e, allInputs, this.onSubmitErrorApi);
|
||||
}
|
||||
|
||||
return { values: elementsValues };
|
||||
}
|
||||
|
||||
protected onSubmitErrorApi(apiFormErrors: IApiFormErrors | null) {
|
||||
if (!apiFormErrors) return;
|
||||
const errors: IFormErrors = {};
|
||||
for (const [key, message] of Object.entries(apiFormErrors)) {
|
||||
if (!this.fields[key]) continue;
|
||||
this.fields[key]?.setErrors([
|
||||
{
|
||||
message,
|
||||
validator: "",
|
||||
value: this.fields[key]?.state.value ?? "",
|
||||
args: [],
|
||||
},
|
||||
]);
|
||||
}
|
||||
this.onErrors(errors);
|
||||
}
|
||||
|
||||
protected async validate() {
|
||||
const errors = (
|
||||
await Promise.all(
|
||||
Object.entries(this.fields).map(async ([name, field]) => {
|
||||
return { name, validation: await field.validate(true), field };
|
||||
}),
|
||||
)
|
||||
).filter(({ validation }) => validation?.length);
|
||||
|
||||
if (!errors.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const errorsObject: IFormErrors = {};
|
||||
errors.forEach(({ name, validation, field }) => (errorsObject[name] = { errors: validation, field }));
|
||||
return errorsObject;
|
||||
}
|
||||
|
||||
protected onErrors(errors: IFormErrors) {
|
||||
if (this.props.onErrors) this.props.onErrors(errors);
|
||||
}
|
||||
|
||||
protected setField(name: string, field: IBaseField) {
|
||||
this.fields[name] = field;
|
||||
}
|
||||
|
||||
protected unSetField(name: string) {
|
||||
delete this.fields[name];
|
||||
}
|
||||
|
||||
protected async onFieldChange(name: string, field: IBaseField) {
|
||||
if (this.props.onFieldChange) {
|
||||
const errors = await this.validate();
|
||||
this.props.onFieldChange(name, field, errors);
|
||||
}
|
||||
}
|
||||
|
||||
private getAllChildrenFields(e: React.FormEvent<HTMLFormElement> | null): Element[] {
|
||||
return Array.from(((e?.target as HTMLFormElement) ?? this.formRef.current).elements);
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: $white;
|
||||
box-shadow: $shadow-nav;
|
||||
padding: 24px;
|
||||
position: absolute;
|
||||
top: 83px;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
text-align: center;
|
||||
> *:not(:last-child) {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.separator {
|
||||
width: 100%;
|
||||
border: 1px solid $grey-medium;
|
||||
}
|
||||
}
|
||||
|
||||
.background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: transparent;
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import LogOutButton from "@Front/Components/DesignSystem/LogOutButton";
|
||||
import Module from "@Front/Config/Module";
|
||||
import React from "react";
|
||||
|
||||
import NavigationLink from "../../NavigationLink";
|
||||
import classes from "./classes.module.scss";
|
||||
|
||||
type IProps = {
|
||||
isOpen: boolean;
|
||||
closeModal: () => void;
|
||||
};
|
||||
type IState = {};
|
||||
|
||||
export default class BurgerModal extends React.Component<IProps, IState> {
|
||||
// TODO isEnabled depending on role given by DB
|
||||
public override render(): JSX.Element | null {
|
||||
if (!this.props.isOpen) return null;
|
||||
return (
|
||||
<>
|
||||
<div className={classes["background"]} onClick={this.props.closeModal} />
|
||||
<div className={classes["root"]}>
|
||||
<NavigationLink
|
||||
path={Module.getInstance().get().modules.pages.Folder.props.path}
|
||||
text="Dossiers en cours"
|
||||
routesActive={[
|
||||
Module.getInstance().get().modules.pages.Folder.pages.FolderInformation.props.path,
|
||||
Module.getInstance().get().modules.pages.Folder.pages.CreateFolder.props.path,
|
||||
]}
|
||||
/>
|
||||
<NavigationLink
|
||||
path={Module.getInstance().get().modules.pages.Folder.pages.FolderArchived.props.path}
|
||||
text="Dossiers archivés"
|
||||
routesActive={[Module.getInstance().get().modules.pages.Folder.pages.FolderArchived.props.path]}
|
||||
/>
|
||||
<div className={classes["separator"]} />
|
||||
<LogOutButton />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
.burger-icon {
|
||||
cursor: pointer;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
import classes from "./classes.module.scss";
|
||||
import Image from "next/image";
|
||||
import BurgerIcon from "@Assets/Icons/burger.svg";
|
||||
import CrossIcon from "@Assets/Icons/cross.svg";
|
||||
import BurgerModal from "./BurgerModal";
|
||||
|
||||
type IProps = {
|
||||
isModalOpen: boolean;
|
||||
openBurgerMenu: () => void;
|
||||
closeBurgerMenu: () => void;
|
||||
};
|
||||
type IState = {
|
||||
// isModalOpen: boolean;
|
||||
};
|
||||
|
||||
export default class BurgerMenu extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public override render(): JSX.Element {
|
||||
return (
|
||||
<div className={classes["root"]}>
|
||||
<Image
|
||||
alt="burger"
|
||||
src={this.props.isModalOpen ? CrossIcon : BurgerIcon}
|
||||
className={classes["burger-icon"]}
|
||||
onClick={this.props.openBurgerMenu}
|
||||
/>
|
||||
{this.props.isModalOpen && <BurgerModal isOpen={this.props.isModalOpen} closeModal={this.props.closeBurgerMenu} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
position: relative;
|
||||
width: fit-content;
|
||||
margin: auto;
|
||||
height: 83px;
|
||||
padding: 10px 16px;
|
||||
|
||||
.content {
|
||||
margin: auto;
|
||||
}
|
||||
.underline {
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background-color: $black;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&.desactivated{
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
import classNames from "classnames";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import React from "react";
|
||||
|
||||
import Typography, { ITypo } from "../../Typography";
|
||||
import classes from "./classes.module.scss";
|
||||
|
||||
type IProps = {
|
||||
text: string | JSX.Element;
|
||||
path: string;
|
||||
isActive?: boolean;
|
||||
routesActive?: string[];
|
||||
};
|
||||
|
||||
type IPropsClass = IProps;
|
||||
|
||||
type IStateClass = {};
|
||||
|
||||
class HeaderLinkClass extends React.Component<IPropsClass, IStateClass> {
|
||||
public override render(): JSX.Element {
|
||||
if (this.props.path !== "" && this.props.path !== undefined) {
|
||||
return (
|
||||
<Link href={this.props.path} className={classNames(classes["root"], this.props.isActive && classes["active"])}>
|
||||
<div className={classes["content"]}>
|
||||
<Typography typo={this.props.isActive ? ITypo.P_SB_18 : ITypo.NAV_HEADER_18}>{this.props.text}</Typography>
|
||||
</div>
|
||||
{this.props.isActive && <div className={classes["underline"]} />}
|
||||
</Link>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className={classNames(classes["root"], classes["desactivated"])}>
|
||||
<div className={classes["content"]}>
|
||||
<Typography typo={ITypo.NAV_HEADER_18}>{this.props.text}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function HeaderLink(props: IProps) {
|
||||
const router = useRouter();
|
||||
const { pathname } = router;
|
||||
let isActive = props.path === pathname;
|
||||
if(props.routesActive){
|
||||
for (const routeActive of props.routesActive) {
|
||||
if (isActive) break;
|
||||
isActive = pathname.includes(routeActive);
|
||||
}
|
||||
}
|
||||
return <HeaderLinkClass {...props} isActive={isActive} />;
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
display: inline-flex;
|
||||
@media screen and (max-width: $screen-ls) {
|
||||
display: none;
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
import Module from "@Front/Config/Module";
|
||||
import React from "react";
|
||||
|
||||
import HeaderLink from "../HeaderLink";
|
||||
import classes from "./classes.module.scss";
|
||||
|
||||
type IProps = {};
|
||||
type IState = {};
|
||||
|
||||
export default class Navigation extends React.Component<IProps, IState> {
|
||||
public override render(): JSX.Element {
|
||||
return (
|
||||
<div className={classes["root"]}>
|
||||
<HeaderLink
|
||||
text={"Dossiers en cours"}
|
||||
path={Module.getInstance().get().modules.pages.Folder.props.path}
|
||||
routesActive={[
|
||||
Module.getInstance().get().modules.pages.Folder.pages.FolderInformation.props.path,
|
||||
Module.getInstance().get().modules.pages.Folder.pages.CreateFolder.props.path,
|
||||
]}
|
||||
/>
|
||||
<HeaderLink
|
||||
text={"Dossiers archivés"}
|
||||
path={Module.getInstance().get().modules.pages.Folder.pages.FolderArchived.props.path}
|
||||
routesActive={[Module.getInstance().get().modules.pages.Folder.pages.FolderArchived.props.path]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
position: relative;
|
||||
width: fit-content;
|
||||
margin: auto;
|
||||
|
||||
.content {
|
||||
align-content: center;
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
import React from "react";
|
||||
import classes from "./classes.module.scss";
|
||||
import Link from "next/link";
|
||||
import classNames from "classnames";
|
||||
import { useRouter } from "next/router";
|
||||
import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
|
||||
|
||||
type IProps = {
|
||||
text: string | JSX.Element;
|
||||
path?: string;
|
||||
onClick?: () => void;
|
||||
isEnabled?: boolean;
|
||||
isActive?: boolean;
|
||||
routesActive?: string[];
|
||||
};
|
||||
|
||||
type IPropsClass = IProps;
|
||||
type IStateClass = {};
|
||||
|
||||
class NavigationLinkClass extends React.Component<IPropsClass, IStateClass> {
|
||||
static defaultProps = { isEnabled: true };
|
||||
public override render(): JSX.Element | null {
|
||||
if (!this.props.isEnabled) return null;
|
||||
return (
|
||||
<Link
|
||||
href={this.props.path ?? ""}
|
||||
className={classNames(classes["root"], this.props.isActive && [classes["active"]])}
|
||||
onClick={this.props.onClick}>
|
||||
<div className={classes["content"]}>
|
||||
<Typography typo={this.props.isActive ? ITypo.P_SB_18 : ITypo.NAV_HEADER_18}>{this.props.text}</Typography>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default function NavigationLink(props: IProps) {
|
||||
const router = useRouter();
|
||||
const { pathname } = router;
|
||||
let isActive = props.path === pathname;
|
||||
if(props.routesActive){
|
||||
for (const routeActive of props.routesActive) {
|
||||
if (isActive) break;
|
||||
isActive = pathname.includes(routeActive);
|
||||
}
|
||||
}
|
||||
return <NavigationLinkClass {...props} isActive={isActive} />;
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 390px;
|
||||
max-height: 80vh;
|
||||
background-color: $white;
|
||||
box-shadow: $shadow-nav;
|
||||
padding: 24px;
|
||||
position: absolute;
|
||||
top: 107px;
|
||||
right: 56px;
|
||||
animation: smooth-appear 0.2s ease forwards;
|
||||
|
||||
@keyframes smooth-appear {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.notification-header {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
justify-content: space-between;
|
||||
.close-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.notification-body {
|
||||
margin-top: 24px;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
.missing-notification {
|
||||
padding: 56px 0;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $screen-s) {
|
||||
width: 100%;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: transparent;
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
import React from "react";
|
||||
import classes from "./classes.module.scss";
|
||||
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||
import CloseIcon from "@Assets/Icons/cross.svg";
|
||||
import Image from "next/image";
|
||||
import ToastHandler from "@Front/Components/DesignSystem/Toasts/ToastsHandler";
|
||||
import Toasts, { IToast } from "@Front/Stores/Toasts";
|
||||
|
||||
type IProps = {
|
||||
isOpen: boolean;
|
||||
closeModal: () => void;
|
||||
};
|
||||
type IState = {
|
||||
toastList: IToast[] | null;
|
||||
};
|
||||
|
||||
export default class NotificationModal extends React.Component<IProps, IState> {
|
||||
private removeOnToastChange: () => void = () => {};
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
toastList: Toasts.getInstance().toasts,
|
||||
};
|
||||
this.handleToastChange = this.handleToastChange.bind(this);
|
||||
}
|
||||
|
||||
public override render(): JSX.Element | null {
|
||||
if (!this.props.isOpen) return null;
|
||||
return (
|
||||
<>
|
||||
<div className={classes["background"]} onClick={this.props.closeModal} />
|
||||
<div className={classes["root"]}>
|
||||
<div className={classes["notification-header"]}>
|
||||
<Typography typo={ITypo.P_16}>Notifications</Typography>
|
||||
<div className={classes["close-icon"]} onClick={this.props.closeModal}>
|
||||
<Image src={CloseIcon} alt="Close notification modal" className={classes["close-icon"]}></Image>
|
||||
</div>
|
||||
</div>
|
||||
;
|
||||
<div className={classes["notification-body"]}>
|
||||
<>
|
||||
{Toasts.getInstance().toasts.length === 0 ? (
|
||||
<div className={classes["missing-notification"]}>
|
||||
<Typography typo={ITypo.P_18} color={ITypoColor.GREY}>
|
||||
Vous n'avez pas de notifications.
|
||||
</Typography>
|
||||
</div>
|
||||
) : (
|
||||
<ToastHandler />
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
public override componentDidMount() {
|
||||
this.removeOnToastChange = Toasts.getInstance().onChange(this.handleToastChange);
|
||||
}
|
||||
|
||||
public override componentWillUnmount() {
|
||||
this.removeOnToastChange();
|
||||
}
|
||||
|
||||
private handleToastChange(toastList: IToast[] | null) {
|
||||
this.setState({
|
||||
toastList,
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
.icon-container {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
.notification-icon {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
.notification-dot {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
import React from "react";
|
||||
import classes from "./classes.module.scss";
|
||||
import Image from "next/image";
|
||||
import NotificationIcon from "@Assets/Icons/notification.svg";
|
||||
import Toasts, { IToast } from "@Front/Stores/Toasts";
|
||||
import NotificationModal from "./NotificationModal";
|
||||
import InfoIcon from "@Assets/Icons/info.svg";
|
||||
|
||||
type IProps = {
|
||||
isModalOpen: boolean;
|
||||
openNotificationModal: () => void;
|
||||
closeNotificationModal: () => void;
|
||||
};
|
||||
type IState = {
|
||||
hasNotifications: boolean;
|
||||
toastList: IToast[] | null;
|
||||
};
|
||||
|
||||
export default class Notifications extends React.Component<IProps, IState> {
|
||||
private removeOnToastChange: () => void = () => {};
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
toastList: Toasts.getInstance().toasts, //TODO : Get from bbd
|
||||
hasNotifications: Toasts.getInstance().toasts.length > 0, // TODO: Change this when we have notification stored in bbd, unread notifications
|
||||
};
|
||||
this.handleToastChange = this.handleToastChange.bind(this);
|
||||
}
|
||||
|
||||
public override render(): JSX.Element {
|
||||
return (
|
||||
<div className={classes["root"]}>
|
||||
<div className={classes["icon-container"]} onClick={this.props.openNotificationModal}>
|
||||
<Image alt="notifications" src={NotificationIcon} className={classes["notification-icon"]} />
|
||||
{this.state.hasNotifications && (
|
||||
<Image className={classes["notification-dot"]} src={InfoIcon} alt="Unread notification" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{this.props.isModalOpen && (
|
||||
<NotificationModal isOpen={this.props.isModalOpen} closeModal={this.props.closeNotificationModal} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public override componentDidMount() {
|
||||
this.removeOnToastChange = Toasts.getInstance().onChange(this.handleToastChange);
|
||||
}
|
||||
|
||||
public override componentWillUnmount() {
|
||||
this.removeOnToastChange();
|
||||
}
|
||||
|
||||
private handleToastChange(toastList: IToast[] | null) {
|
||||
this.setState({
|
||||
toastList,
|
||||
hasNotifications: toastList ? toastList.length > 0 : false,
|
||||
});
|
||||
}
|
||||
}
|