├── README.md
├── client
├── .env
├── .env.production
├── .eslintrc.json
├── .gitignore
├── .prettierrc.json
├── README.md
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── apps
│ │ └── front-office
│ │ │ ├── account
│ │ │ ├── middleware
│ │ │ │ └── index.tsx
│ │ │ └── user
│ │ │ │ └── index.ts
│ │ │ ├── design-system
│ │ │ ├── components
│ │ │ │ ├── Button
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── Form
│ │ │ │ │ ├── BaseForm.tsx
│ │ │ │ │ ├── BaseInput.tsx
│ │ │ │ │ ├── DatePickerInput.tsx
│ │ │ │ │ ├── EmailInput.tsx
│ │ │ │ │ ├── FloatInput.tsx
│ │ │ │ │ ├── InputError.tsx
│ │ │ │ │ ├── InputLabel.tsx
│ │ │ │ │ ├── IntegerInput.tsx
│ │ │ │ │ ├── NumberInput.tsx
│ │ │ │ │ ├── PasswordInput.tsx
│ │ │ │ │ ├── SelectInput.tsx
│ │ │ │ │ ├── SubmitButton.tsx
│ │ │ │ │ ├── SwitchButton.tsx
│ │ │ │ │ ├── TextAreaInput.tsx
│ │ │ │ │ └── TextInput.tsx
│ │ │ │ ├── Icons
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── Indicators
│ │ │ │ │ ├── ErrorHandler
│ │ │ │ │ │ ├── index.tsx
│ │ │ │ │ │ └── types.tsx
│ │ │ │ │ ├── Loader
│ │ │ │ │ │ ├── index.tsx
│ │ │ │ │ │ └── types.tsx
│ │ │ │ │ └── LoadingErrorHandler
│ │ │ │ │ │ ├── index.tsx
│ │ │ │ │ │ └── types.tsx
│ │ │ │ └── Toast
│ │ │ │ │ └── index.tsx
│ │ │ ├── index.ts
│ │ │ └── layouts
│ │ │ │ ├── AccountLayout
│ │ │ │ └── index.tsx
│ │ │ │ ├── BaseLayout
│ │ │ │ └── index.tsx
│ │ │ │ ├── Footer
│ │ │ │ └── index.tsx
│ │ │ │ ├── Header
│ │ │ │ └── index.tsx
│ │ │ │ └── Root.tsx
│ │ │ ├── file-manager
│ │ │ ├── Kernel
│ │ │ │ ├── Kernel.ts
│ │ │ │ ├── Kernel.types.ts
│ │ │ │ ├── KernelTree.ts
│ │ │ │ └── index.ts
│ │ │ ├── actions
│ │ │ │ ├── createDirectory.ts
│ │ │ │ └── index.ts
│ │ │ ├── components
│ │ │ │ ├── Content
│ │ │ │ │ ├── Content.styles.tsx
│ │ │ │ │ ├── Content.tsx
│ │ │ │ │ ├── ContentNode
│ │ │ │ │ │ ├── ContentNode.tsx
│ │ │ │ │ │ ├── ContentNode.types.ts
│ │ │ │ │ │ ├── DirectoryNode.tsx
│ │ │ │ │ │ ├── FileNode.tsx
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ ├── ContentOverlay.tsx
│ │ │ │ │ ├── NodesList.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── FileManager
│ │ │ │ │ ├── FileManager.styles.ts
│ │ │ │ │ ├── FileManager.tsx
│ │ │ │ │ ├── FileManager.types.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── LoadingProgressBar.tsx
│ │ │ │ ├── Sidebar
│ │ │ │ │ ├── Sidebar.styles.tsx
│ │ │ │ │ ├── Sidebar.tsx
│ │ │ │ │ ├── Sidebar.types.ts
│ │ │ │ │ ├── SidebarNode
│ │ │ │ │ │ ├── SidebarNode.styles.tsx
│ │ │ │ │ │ ├── SidebarNode.tsx
│ │ │ │ │ │ ├── SidebarNode.types.ts
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ ├── SidebarSkeleton.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── Toolbar
│ │ │ │ │ ├── Buttons
│ │ │ │ │ │ ├── CreateDirectoryButton.tsx
│ │ │ │ │ │ ├── CreateDirectoryModal.tsx
│ │ │ │ │ │ └── HomeDirectoryButton.tsx
│ │ │ │ │ ├── Toolbar.styles.tsx
│ │ │ │ │ ├── Toolbar.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ └── index.ts
│ │ │ ├── contexts
│ │ │ │ ├── KernelContext.tsx
│ │ │ │ └── index.ts
│ │ │ ├── hooks
│ │ │ │ ├── index.ts
│ │ │ │ ├── useCurrentDirectoryNode.ts
│ │ │ │ ├── useKernel.tsx
│ │ │ │ ├── useLoading.tsx
│ │ │ │ └── useNodeWatcher.ts
│ │ │ ├── index.ts
│ │ │ ├── services
│ │ │ │ └── file-manager-service.ts
│ │ │ ├── types
│ │ │ │ └── FileManagerServiceInterface.ts
│ │ │ └── utils
│ │ │ │ ├── data.json
│ │ │ │ ├── data.ts
│ │ │ │ └── helpers.ts
│ │ │ ├── front-office-modules.json
│ │ │ ├── front-office-provider.ts
│ │ │ ├── home
│ │ │ ├── components
│ │ │ │ └── HomePage
│ │ │ │ │ ├── HomePage.tsx
│ │ │ │ │ └── index.tsx
│ │ │ ├── provider.ts
│ │ │ ├── routes.ts
│ │ │ └── services
│ │ │ │ └── home-service.ts
│ │ │ └── utils
│ │ │ ├── locales.ts
│ │ │ ├── preload.tsx
│ │ │ ├── router.ts
│ │ │ ├── setupPage.tsx
│ │ │ ├── types.ts
│ │ │ └── urls.ts
│ ├── index.tsx
│ ├── react-app-env.d.ts
│ ├── setupTests.ts
│ └── shared
│ │ ├── apps-list.ts
│ │ ├── assets
│ │ └── images
│ │ │ └── flags
│ │ │ ├── sa.png
│ │ │ └── uk.png
│ │ ├── config.ts
│ │ └── definitions.d.ts
├── tsconfig.json
└── yarn.lock
└── server
├── .gitignore
├── data
├── Applications
│ ├── reggae.hal
│ ├── synthesize_bluetooth_ford.png
│ └── uniform_affirm.ace
├── Library
│ ├── product_alabama_granite.litcoffee
│ ├── towels_hybrid.glb
│ └── volkswagen_data.xap
├── System
│ ├── cheese_sunt_mini.in
│ ├── gutkowski_until_guatemala.x3d
│ └── tiny_grocery_developer.smf
├── Users
│ ├── credential_van_indexing.clkk
│ ├── dice_gray.odi
│ └── incredible_yowza.au
├── boot
│ ├── excepting.age
│ ├── nihonium_by.held
│ └── toys_cobalt_north.u8hdr
├── etc
│ ├── cruiser_loan_cotton.gpx
│ ├── engineer_outdoors_geez.dwd
│ ├── latin_cotton.cpp
│ └── net
│ │ ├── bike_franklin.sea
│ │ ├── lead_necessitatibus.f77
│ │ ├── plymouth_minivan_whose.vhd
│ │ └── sys
│ │ ├── etc
│ │ ├── bedfordshire_haptic_diesel.mks
│ │ ├── east.apk
│ │ ├── inverse_red_west.3g2
│ │ └── lib
│ │ │ ├── balboa_electric.wqd
│ │ │ ├── calculate_expanded.fm
│ │ │ ├── mnt
│ │ │ ├── designer_olive_gadolinium.mid
│ │ │ ├── future_incredible_perferendis.kdbx
│ │ │ ├── payment_tesla.uvm
│ │ │ └── private
│ │ │ │ ├── dynamic_district.webapp
│ │ │ │ ├── east_qatar.pct
│ │ │ │ ├── market_east_payment.aab
│ │ │ │ └── root
│ │ │ │ ├── boot
│ │ │ │ ├── collaboration.m3a
│ │ │ │ ├── matrix_kelvin_suv.xslt
│ │ │ │ ├── media
│ │ │ │ │ ├── male.cbt
│ │ │ │ │ ├── petty.mml
│ │ │ │ │ └── south.uvg
│ │ │ │ └── torp.pgn
│ │ │ │ ├── hatchback_uruguay.joda
│ │ │ │ ├── lead_ford_borders.mods
│ │ │ │ └── planner_chair_fresno.rusd
│ │ │ └── northwest_diesel_international.cdmid
│ │ ├── ram_screen_ugh.ser
│ │ ├── sexy_calculating_west.jsx
│ │ └── vancouver_unbranded.pkg
├── lost+found
│ ├── borders_nonconforming_bentley.texi
│ ├── integrated.pya
│ ├── opt
│ │ └── bin
│ │ │ ├── etc
│ │ │ └── namedb
│ │ │ │ ├── country_port_female.emf
│ │ │ │ ├── invoice.tif
│ │ │ │ ├── switchable_metrics.std
│ │ │ │ └── usr
│ │ │ │ └── libdata
│ │ │ │ ├── berkshire_investment_burg.jardiff
│ │ │ │ ├── minivan_empower_beneficial.webapp
│ │ │ │ ├── oak_notwithstanding.esf
│ │ │ │ └── usr
│ │ │ │ └── ports
│ │ │ │ ├── frozen_haltom.so
│ │ │ │ ├── sexualise_cotton_recusandae.lzh
│ │ │ │ ├── specialist.ts
│ │ │ │ └── usr
│ │ │ │ ├── bifurcated_deposit.hvp
│ │ │ │ ├── generic.ei6
│ │ │ │ ├── lib
│ │ │ │ ├── baby_samarium.xns
│ │ │ │ ├── data_olive_global.otf
│ │ │ │ └── senior.sxm
│ │ │ │ └── primary_interactions.mp2
│ │ │ ├── interfaces_chlorine_partnerships.sgi
│ │ │ ├── minivan_stuff_carbon.uvvt
│ │ │ └── turquoise_slippery_whether.ott
│ └── rolls.gxt
├── media
│ ├── assistant.3g2
│ ├── deposit_markets.xop
│ └── electric_wisconsin.ipfix
├── opt
│ ├── northeast_ashburn.ssml
│ ├── pascal_director.gram
│ └── withdrawal_schaumburg_potassium.mp4a
├── private
│ └── var
│ │ ├── north_deliberately_artistic.mxl
│ │ ├── pascal_hat_funk.bed
│ │ ├── table.icm
│ │ └── usr
│ │ └── sbin
│ │ ├── Network
│ │ ├── and_world.ufd
│ │ ├── female.x_b
│ │ ├── usr
│ │ │ └── share
│ │ │ │ ├── access.atomcat
│ │ │ │ ├── buckinghamshire_narrowcast.c11amz
│ │ │ │ ├── considering_money.dart
│ │ │ │ └── etc
│ │ │ │ └── ppp
│ │ │ │ ├── executive.silo
│ │ │ │ ├── lib
│ │ │ │ ├── account_male.ftc
│ │ │ │ ├── cisgender_user_lats.blb
│ │ │ │ ├── diadem.fti
│ │ │ │ └── usr
│ │ │ │ │ └── local
│ │ │ │ │ └── bin
│ │ │ │ │ ├── architect_worriedly.ecma
│ │ │ │ │ ├── boundary.qxt
│ │ │ │ │ ├── urban.jpgm
│ │ │ │ │ └── var
│ │ │ │ │ └── yp
│ │ │ │ │ ├── System
│ │ │ │ │ ├── burrito_pouros.mpga
│ │ │ │ │ ├── speakerphone.dxr
│ │ │ │ │ └── wolf_industrial_personal.scs
│ │ │ │ │ ├── californium_deposit.wvx
│ │ │ │ │ ├── choke_lead.fm
│ │ │ │ │ └── ugh_responsive.rif
│ │ │ │ ├── male.skm
│ │ │ │ └── redefine_oh.ustar
│ │ └── west.ras
│ │ ├── east_dedicated_spur.p7m
│ │ ├── north_ranch_massachusetts.tex
│ │ └── oklahoma_brand.otp
├── root
│ ├── coordinator_marketing.wbs
│ ├── female_colorado_circulation.nb
│ └── trans_usability_outdoors.mpc
├── sbin
│ ├── mansfield.sv4cpio
│ ├── pickup_payment_dynamic.1km
│ └── salmon.sti
├── usr
│ ├── generic_northeast.msty
│ ├── hertz.svd
│ ├── northeast.psb
│ └── proc
│ │ ├── Network
│ │ ├── flexibility_thrifty.xls
│ │ ├── forint.csh
│ │ ├── tennessee_gray.sdw
│ │ └── var
│ │ │ ├── apropos_account.xop
│ │ │ ├── electric_xenogender.bdoc
│ │ │ ├── meter_bus_northwest.disposition-notification
│ │ │ └── private
│ │ │ ├── satisfied_rubidium_division.sis
│ │ │ ├── south.slt
│ │ │ ├── to.kpr
│ │ │ └── usr
│ │ │ ├── Network
│ │ │ ├── blues_future.qxd
│ │ │ ├── dicta.djv
│ │ │ ├── false_and_driver.pfx
│ │ │ └── private
│ │ │ │ ├── fresh_west.mp2
│ │ │ │ ├── modern.txt
│ │ │ │ └── wrongly_suv_quirkily.mpkg
│ │ │ ├── east_olive.sru
│ │ │ ├── pasture.cdkey
│ │ │ └── sports.mesh
│ │ ├── luettgen_buckinghamshire.xlam
│ │ ├── road.x3dv
│ │ └── yuck_bicycle_across.m21
└── var
│ ├── Network
│ ├── cambridgeshire.arc
│ ├── configure_station_hybrid.pyv
│ ├── optimizing.mpeg
│ └── root
│ │ ├── boot
│ │ ├── cab.fxpl
│ │ ├── duh_pole_redundant.qam
│ │ ├── northwest_southwest_admired.ace
│ │ └── usr
│ │ │ ├── Users
│ │ │ ├── bmw_intelligent.z6
│ │ │ ├── light_electronics.fm
│ │ │ ├── var
│ │ │ │ ├── principal_tuna.dms
│ │ │ │ ├── programming_withdrawal_convertible.mxmf
│ │ │ │ ├── southeast_term_wireless.senmlx
│ │ │ │ └── usr
│ │ │ │ │ ├── compress.key
│ │ │ │ │ ├── falkland.xhvml
│ │ │ │ │ ├── indexing.ttml
│ │ │ │ │ └── srv
│ │ │ │ │ ├── accountability_cotton.pfm
│ │ │ │ │ ├── chug_bellingham_hatchback.pm
│ │ │ │ │ └── kissingly.drle
│ │ │ └── vortals_green_ack.1km
│ │ │ ├── online_maximize_rubber.raml
│ │ │ ├── png.ktz
│ │ │ └── portal_engineer_interactions.pcx
│ │ ├── dakota_yuck_tan.vst
│ │ ├── kneecap_male_nimble.spq
│ │ └── phased_factors_viral.com
│ ├── bandwidth.ipfix
│ ├── meh.mcurl
│ └── summon_fiat.zmm
├── package.json
├── src
├── config.ts
├── controllers
│ └── file-manager
│ │ ├── createDirectory.ts
│ │ ├── index.ts
│ │ └── listDirectory.ts
├── generator.ts
├── index.ts
├── routes.ts
└── utils
│ ├── node.ts
│ └── paths.ts
├── tsconfig.json
└── yarn.lock
/README.md:
--------------------------------------------------------------------------------
1 | # React File Manager
2 |
3 | This is a practical guide to build a file manager using React, Typescript, Express, and MongoDB.
4 |
5 | You can see the full articles in [dev.to](https://dev.to/hassanzohdy/lets-create-a-file-manager-from-scratch-with-react-and-typescript-chapter-i-a-good-way-to-expand-your-experience-5g4k)
6 |
7 | ## What you learn use in this project
8 |
9 | - Recursions
10 | - Create Custom React Hooks
11 | - Working With Higher Order Functions
12 | - Working With Getters and Setters
13 | - Working with Express for backend
14 | - Working with apis in React.
15 | - Working with file system in Node.js.
16 | - Properly structuring your files.
17 | - Scaling up your code base from simple to complex without losing track of it.
18 | - How to refactor your code and enhance it while you develop a project.
19 |
--------------------------------------------------------------------------------
/client/.env:
--------------------------------------------------------------------------------
1 | # Disable ESlint error
2 | ESLINT_NO_DEV_ERRORS=true
3 |
4 | # App Name
5 | REACT_APP_NAME="file-manager"
6 |
7 | # App Description
8 | REACT_APP_DESCRIPTION=
9 |
10 | # App Code Name
11 | REACT_APP_CODE_NAME="fm"
12 |
13 | # Branch Name
14 | REACT_APP_BRANCH_NAME="main"
15 |
16 | # App default locale code
17 | REACT_APP_DEFAULT_LOCALE_CODE=en
18 |
19 | # App Fallback locale code
20 | REACT_APP_FALLBACK_LOCALE_CODE=en
21 |
22 | # App default direction
23 | REACT_APP_DEFAULT_DIRECTION=ltr
24 |
25 | # App Primary Color
26 | REACT_APP_PRIMARY_COLOR=#000
27 |
28 | # App API URL
29 | REACT_APP_API_URL=http://localhost:8001
30 |
31 | # App API Key
32 | REACT_APP_API_KEY=
33 |
34 |
--------------------------------------------------------------------------------
/client/.env.production:
--------------------------------------------------------------------------------
1 | # Production Env File
2 | # Use yarn build:prod or npm run build:prod to use this file for building instead of .env file
3 |
4 | # Generate Source map
5 | GENERATE_SOURCEMAP=false
6 |
7 | # App Name
8 | REACT_APP_NAME="file-manager"
9 |
10 | # App Code Name
11 | REACT_APP_CODE_NAME="fm"
12 |
13 | # App Description
14 | REACT_APP_DESCRIPTION=
15 |
16 | # App default locale code
17 | REACT_APP_DEFAULT_LOCALE_CODE=en
18 |
19 | # App Fallback locale code
20 | REACT_APP_FALLBACK_LOCALE_CODE=en
21 |
22 | # App default direction
23 | REACT_APP_DEFAULT_DIRECTION=ltr
24 |
25 | # App Primary Color
26 | REACT_APP_PRIMARY_COLOR=#000
27 |
28 | # App Production Public URL
29 | PUBLIC_URL=
30 |
31 | # App Production Base Path
32 | REACT_APP_PRODUCTION_BASE_PATH=/
33 |
34 | # App API URL
35 | REACT_APP_API_URL=
36 |
37 | # App API Key
38 | REACT_APP_API_KEY=
--------------------------------------------------------------------------------
/client/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es2021": true,
5 | "jest": true
6 | },
7 | "extends": [
8 | "react-app",
9 | "react-app/jest",
10 | "eslint:recommended",
11 | "plugin:react-hooks/recommended",
12 | "plugin:react/recommended",
13 | "plugin:@typescript-eslint/recommended",
14 | "plugin:prettier/recommended"
15 | ],
16 | "parser": "@typescript-eslint/parser",
17 | "parserOptions": {
18 | "ecmaFeatures": {
19 | "jsx": true
20 | },
21 | "ecmaVersion": "latest",
22 | "sourceType": "module"
23 | },
24 | "plugins": ["react", "@typescript-eslint", "prettier", "unused-imports"],
25 | "rules": {
26 | "@typescript-eslint/no-explicit-any": "off",
27 | "@typescript-eslint/no-unused-vars": "off",
28 | "no-unused-vars": "off",
29 | "unused-imports/no-unused-vars": [
30 | "warn",
31 | {
32 | "vars": "all",
33 | "varsIgnorePattern": "^_",
34 | "args": "after-used",
35 | "argsIgnorePattern": "^_"
36 | }
37 | ],
38 | "react/react-in-jsx-scope": "off",
39 | "unused-imports/no-unused-imports": "error"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
--------------------------------------------------------------------------------
/client/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "tabWidth": 2,
4 | "printWidth": 80,
5 | "singleQuote": false,
6 | "arrowParens": "avoid",
7 | "trailingComma": "all",
8 | "bracketSameLine": true,
9 | "endOfLine": "auto"
10 | }
11 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35 |
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39 |
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "file-manager",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@emotion/react": "11.10.0",
7 | "@emotion/styled": "11.10.0",
8 | "@mantine/core": "^5.3.0",
9 | "@mantine/dropzone": "^5.3.0",
10 | "@mantine/hooks": "^5.3.0",
11 | "@mantine/modals": "^5.3.0",
12 | "@mantine/notifications": "^5.3.0",
13 | "@mantine/nprogress": "^5.3.0",
14 | "@mantine/spotlight": "^5.3.0",
15 | "@mongez/cache": "^1.0.13",
16 | "@mongez/config": "^1.0.20",
17 | "@mongez/dom": "^1.1.0",
18 | "@mongez/events": "^1.0.9",
19 | "@mongez/http": "^1.0.24",
20 | "@mongez/localization": "^1.0.20",
21 | "@mongez/react": "^1.0.25",
22 | "@mongez/react-atom": "^1.4.5",
23 | "@mongez/react-form": "^1.5.17",
24 | "@mongez/react-helmet": "^1.0.9",
25 | "@mongez/react-router": "^1.0.59",
26 | "@mongez/react-wizard": "^1.1.17",
27 | "@mongez/reinforcements": "^1.0.28",
28 | "@mongez/supportive-is": "^1.0.6",
29 | "@mongez/user": "^1.0.9",
30 | "@mongez/validator": "^1.0.13",
31 | "@tabler/icons": "^1.95.1",
32 | "crypto-js": "^4.1.1",
33 | "react": "^18.2.0",
34 | "react-dom": "^18.2.0",
35 | "react-scripts": "5.0.1",
36 | "web-vitals": "^3.0.1"
37 | },
38 | "scripts": {
39 | "postinstall": "npx link-module-alias",
40 | "start": "npx react-scripts start",
41 | "build": "npx react-scripts build",
42 | "build:prod": "npx env-cmd -f ./.env.production react-scripts build",
43 | "test": "npx react-scripts test",
44 | "dev": "unlink yarn.lock && yarn update && yarn install && yarn start",
45 | "update": "npx ncu -u",
46 | "lint": "npx eslint -c ./.eslintrc.json ./src",
47 | "fix": "npx eslint --fix -c ./.eslintrc.json ./src",
48 | "format": "npx prettier --write ./**/*.{js,jsx,ts,tsx,css,md,json} --config ./.prettierrc.json",
49 | "eject": "npx react-scripts eject"
50 | },
51 | "_moduleAliases": {
52 | "apps": "./src/apps",
53 | "shared": "./src/shared",
54 | "design-system": "./src/apps/front-office/design-system",
55 | "assets": "./src/shared/assets",
56 | "user": "./src/apps/front-office/account/user",
57 | "app": "./src/apps/front-office"
58 | },
59 | "devDependencies": {
60 | "@faker-js/faker": "^7.5.0",
61 | "@mongez/react-wizard": "^1.1.17",
62 | "@testing-library/jest-dom": "^5.16.5",
63 | "@testing-library/react": "^13.4.0",
64 | "@testing-library/user-event": "^14.4.3",
65 | "@types/crypto-js": "^4.1.1",
66 | "@types/jest": "^29.0.0",
67 | "@types/node": "^18.7.16",
68 | "@types/react": "^18.0.18",
69 | "@types/react-dom": "^18.0.6",
70 | "@typescript-eslint/eslint-plugin": "^5.36.2",
71 | "@typescript-eslint/parser": "^5.36.2",
72 | "babel-plugin-prismjs": "^2.1.0",
73 | "env-cmd": "^10.1.0",
74 | "eslint": "^8.23.0",
75 | "eslint-config-prettier": "^8.5.0",
76 | "eslint-config-react-app": "^7.0.1",
77 | "eslint-plugin-prettier": "^4.2.1",
78 | "eslint-plugin-react": "^7.31.8",
79 | "eslint-plugin-unused-imports": "^2.0.0",
80 | "link-module-alias": "^1.2.0",
81 | "npm-check-updates": "^16.1.0",
82 | "prettier": "^2.7.1",
83 | "prettier-plugin-organize-imports": "^3.1.1",
84 | "typescript": "^4.8.3"
85 | },
86 | "browserslist": {
87 | "production": [
88 | ">0.2%",
89 | "not dead",
90 | "not op_mini all"
91 | ],
92 | "development": [
93 | "last 1 chrome version",
94 | "last 1 firefox version",
95 | "last 1 safari version"
96 | ]
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hassanzohdy/file-manager-react/b24f3f70f9c90c567acda672b46cf663a8398066/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
18 |
19 |
28 | %REACT_APP_NAME%
29 |
30 |
31 |
32 |
33 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/client/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hassanzohdy/file-manager-react/b24f3f70f9c90c567acda672b46cf663a8398066/client/public/logo192.png
--------------------------------------------------------------------------------
/client/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hassanzohdy/file-manager-react/b24f3f70f9c90c567acda672b46cf663a8398066/client/public/logo512.png
--------------------------------------------------------------------------------
/client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/account/middleware/index.tsx:
--------------------------------------------------------------------------------
1 | import { Redirect } from "@mongez/react-router";
2 | import user from "user";
3 |
4 | export function Guardian() {
5 | if (!user.isLoggedIn()) {
6 | return ;
7 | }
8 |
9 | return null;
10 | }
11 |
12 | export function ReverseGuardian() {
13 | if (user.isLoggedIn()) {
14 | return ;
15 | }
16 |
17 | return null;
18 | }
19 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/account/user/index.ts:
--------------------------------------------------------------------------------
1 | import cache from "@mongez/cache";
2 | import {
3 | setCurrentUser,
4 | User as BaseUser,
5 | UserCacheDriverInterface,
6 | UserInterface,
7 | } from "@mongez/user";
8 |
9 | class User extends BaseUser implements UserInterface {
10 | /**
11 | * Cache driver
12 | */
13 | protected cacheDriver: UserCacheDriverInterface = cache;
14 | /**
15 | * {@inheritDoc}
16 | */
17 | public getCacheKey(): string {
18 | return "usr";
19 | }
20 | }
21 |
22 | const user: User = new User();
23 |
24 | // boot the user
25 | user.boot();
26 |
27 | // update current user instance to be used from other packages
28 | setCurrentUser(user);
29 |
30 | export default user;
31 |
32 | // now you can directly import the user anywhere in the application using `import user from 'user';` thanks to the module alias
33 | // @see `_moduleAliases` in package.json file
34 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Button/index.tsx:
--------------------------------------------------------------------------------
1 | export default function BaseButton(
2 | props: React.DetailedHTMLProps<
3 | React.ButtonHTMLAttributes,
4 | HTMLButtonElement
5 | >,
6 | ) {
7 | return ;
8 | }
9 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Form/BaseForm.tsx:
--------------------------------------------------------------------------------
1 | import { forwardRef } from "react";
2 |
3 | const BaseForm = forwardRef(function BaseForm({ props }: any, ref: any) {
4 | return ;
5 | });
6 |
7 | export default BaseForm;
8 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Form/BaseInput.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from "@mantine/core";
2 | import { FormInputProps, useFormInput } from "@mongez/react-form";
3 | import { requiredRule } from "@mongez/validator";
4 | import InputError from "./InputError";
5 | import InputLabel from "./InputLabel";
6 |
7 | export default function BaseInput(props: FormInputProps) {
8 | const {
9 | name,
10 | id,
11 | value,
12 | label,
13 | placeholder,
14 | required,
15 | onChange,
16 | onBlur,
17 | error,
18 | autoFocus,
19 | otherProps,
20 | } = useFormInput(props);
21 |
22 | return (
23 | <>
24 |
25 |
26 | {label}
27 |
28 |
39 | {error && }
40 |
41 | >
42 | );
43 | }
44 |
45 | BaseInput.defaultProps = {
46 | type: "text",
47 | rules: [requiredRule],
48 | };
49 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Form/DatePickerInput.tsx:
--------------------------------------------------------------------------------
1 | import { FormInputProps, HiddenInput, useFormInput } from "@mongez/react-form";
2 | import InputLabel from "./InputLabel";
3 |
4 | export type DatePickerInputProps = FormInputProps;
5 |
6 | export default function DatePickerInput(props: DatePickerInputProps) {
7 | const { name, onChange, label, value, placeholder, required, otherProps } =
8 | useFormInput(props);
9 |
10 | return (
11 | <>
12 |
13 | {label}
14 |
15 |
22 |
23 | >
24 | );
25 | }
26 |
27 | DatePickerInput.defaultProps = {
28 | //
29 | };
30 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Form/EmailInput.tsx:
--------------------------------------------------------------------------------
1 | import { FormInputProps } from "@mongez/react-form";
2 | import { emailRule, requiredRule } from "@mongez/validator";
3 | import BaseInput from "./BaseInput";
4 |
5 | export default function EmailInput(props: FormInputProps) {
6 | return ;
7 | }
8 |
9 | EmailInput.defaultProps = {
10 | type: "email",
11 | rules: [requiredRule, emailRule],
12 | };
13 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Form/FloatInput.tsx:
--------------------------------------------------------------------------------
1 | import { FormInputProps } from "@mongez/react-form";
2 | import { floatRule, maxRule, minRule, requiredRule } from "@mongez/validator";
3 | import BaseInput from "./BaseInput";
4 |
5 | export default function FloatInput(props: FormInputProps) {
6 | return ;
7 | }
8 |
9 | FloatInput.defaultProps = {
10 | type: "float",
11 | rules: [requiredRule, minRule, maxRule, floatRule],
12 | };
13 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Form/InputError.tsx:
--------------------------------------------------------------------------------
1 | import { InputError as FormInputErrorProps } from "@mongez/react-form";
2 |
3 | export type InputErrorProps = {
4 | error: FormInputErrorProps;
5 | };
6 |
7 | export default function InputError({ error }: InputErrorProps) {
8 | if (error === null) return null;
9 |
10 | return (
11 | <>
12 |
13 |
{error.errorMessage}
14 |
15 | >
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Form/InputLabel.tsx:
--------------------------------------------------------------------------------
1 | export default function InputLabel({ children, required, ...props }: any) {
2 | if (!children) return null;
3 |
4 | return (
5 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Form/IntegerInput.tsx:
--------------------------------------------------------------------------------
1 | import { FormInputProps } from "@mongez/react-form";
2 | import { integerRule, maxRule, minRule, requiredRule } from "@mongez/validator";
3 | import BaseInput from "./BaseInput";
4 |
5 | export default function IntegerInput(props: FormInputProps) {
6 | return ;
7 | }
8 |
9 | IntegerInput.defaultProps = {
10 | type: "integer",
11 | rules: [requiredRule, minRule, maxRule, integerRule],
12 | };
13 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Form/NumberInput.tsx:
--------------------------------------------------------------------------------
1 | import { FormInputProps } from "@mongez/react-form";
2 | import { maxRule, minRule, numberRule, requiredRule } from "@mongez/validator";
3 | import BaseInput from "./BaseInput";
4 |
5 | export default function NumberInput(props: FormInputProps) {
6 | return ;
7 | }
8 |
9 | NumberInput.defaultProps = {
10 | type: "text",
11 | rule: "number",
12 | rules: [requiredRule, minRule, maxRule, numberRule],
13 | };
14 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Form/PasswordInput.tsx:
--------------------------------------------------------------------------------
1 | import { FormInputProps } from "@mongez/react-form";
2 | import {
3 | lengthRule,
4 | matchElementRule,
5 | maxLengthRule,
6 | minLengthRule,
7 | requiredRule,
8 | } from "@mongez/validator";
9 | import BaseInput from "./BaseInput";
10 |
11 | export default function PasswordInput(props: FormInputProps) {
12 | return ;
13 | }
14 |
15 | PasswordInput.defaultProps = {
16 | type: "password",
17 | rules: [
18 | requiredRule,
19 | minLengthRule,
20 | lengthRule,
21 | maxLengthRule,
22 | matchElementRule,
23 | ],
24 | };
25 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Form/SelectInput.tsx:
--------------------------------------------------------------------------------
1 | import { FormInputProps, useFormInput } from "@mongez/react-form";
2 | import {
3 | lengthRule,
4 | maxLengthRule,
5 | minLengthRule,
6 | requiredRule,
7 | } from "@mongez/validator";
8 |
9 | export default function SelectInput(props: FormInputProps) {
10 | const { id, label, error, placeholder, onChange, name, value } =
11 | useFormInput(props);
12 |
13 | return (
14 | <>
15 | {label && }
16 |
19 | {error && error.errorMessage}
20 | >
21 | );
22 | }
23 |
24 | SelectInput.defaultProps = {
25 | type: "select",
26 | multiple: false,
27 | rules: [requiredRule, minLengthRule, maxLengthRule, lengthRule],
28 | };
29 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Form/SubmitButton.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@mantine/core";
2 | import { useForm } from "@mongez/react-form";
3 | import { useEffect, useState } from "react";
4 |
5 | type SubmitButtonProps = {
6 | children: React.ReactNode;
7 | [key: string]: any;
8 | };
9 |
10 | export default function SubmitButton({
11 | children,
12 | ...props
13 | }: SubmitButtonProps) {
14 | const [isSubmitting, submitting] = useState(false);
15 | const [isDisabled, disable] = useState(false);
16 | const formProvider = useForm();
17 |
18 | useEffect(() => {
19 | if (!formProvider) return;
20 |
21 | const onSubmit = formProvider.form.on("submit", () => {
22 | submitting(formProvider.form.isSubmitting());
23 | disable(formProvider.form.isSubmitting());
24 | });
25 |
26 | const inValidControls = formProvider.form.on("invalidControls", () => {
27 | disable(true);
28 | });
29 |
30 | const validControl = formProvider.form.on("validControls", () => {
31 | disable(false);
32 | });
33 |
34 | return () => {
35 | onSubmit.unsubscribe();
36 | validControl.unsubscribe();
37 | inValidControls.unsubscribe();
38 | };
39 | }, [formProvider]);
40 |
41 | return (
42 | <>
43 |
52 | >
53 | );
54 | }
55 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Form/SwitchButton.tsx:
--------------------------------------------------------------------------------
1 | import { FormInputProps, HiddenInput, useFormInput } from "@mongez/react-form";
2 | import Is from "@mongez/supportive-is";
3 | import { useState } from "react";
4 | import InputLabel from "./InputLabel";
5 |
6 | export type CheckboxProps = FormInputProps & {
7 | defaultChecked?: boolean;
8 | checked?: boolean;
9 | };
10 |
11 | export default function SwitchButton(props: CheckboxProps) {
12 | const [enabled, setEnabled] = useState(
13 | Is.bool(props.checked)
14 | ? props.checked
15 | : Is.bool(props.defaultChecked)
16 | ? props.defaultChecked
17 | : false,
18 | );
19 | const { value, label, name, id, setValue } = useFormInput(props);
20 |
21 | const updateSwitch = (newState: any) => {
22 | setEnabled(newState);
23 |
24 | setValue(newState ? props.value || props.defaultValue || 1 : null);
25 | };
26 |
27 | const toggle = () => {
28 | const newState = !enabled;
29 |
30 | setEnabled(newState);
31 | setValue(newState ? props.value || props.defaultValue || 1 : null);
32 | };
33 |
34 | return (
35 | <>
36 | {enabled && }
37 |
38 | {label}
39 |
40 |
41 |
42 | >
43 | );
44 | }
45 |
46 | SwitchButton.defaultProps = {};
47 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Form/TextAreaInput.tsx:
--------------------------------------------------------------------------------
1 | import { FormInputProps } from "@mongez/react-form";
2 | import BaseInput from "./BaseInput";
3 |
4 | export default function TextAreaInput(props: FormInputProps) {
5 | return ;
6 | }
7 |
8 | TextAreaInput.defaultProps = {
9 | rows: 5,
10 | type: "textarea",
11 | };
12 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Form/TextInput.tsx:
--------------------------------------------------------------------------------
1 | import { FormInputProps } from "@mongez/react-form";
2 | import {
3 | lengthRule,
4 | maxLengthRule,
5 | maxRule,
6 | minLengthRule,
7 | minRule,
8 | patternRule,
9 | requiredRule,
10 | } from "@mongez/validator";
11 | import BaseInput from "./BaseInput";
12 |
13 | export default function TextInput(props: FormInputProps) {
14 | return ;
15 | }
16 |
17 | TextInput.defaultProps = {
18 | type: "text",
19 | rules: [
20 | requiredRule,
21 | minLengthRule,
22 | maxLengthRule,
23 | lengthRule,
24 | minRule,
25 | maxRule,
26 | patternRule,
27 | ],
28 | };
29 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Icons/index.tsx:
--------------------------------------------------------------------------------
1 | // import all icons list here
2 |
3 | const HomeIcon = null;
4 |
5 | export { HomeIcon };
6 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Indicators/ErrorHandler/index.tsx:
--------------------------------------------------------------------------------
1 | import { ErrorHandlerProps } from "./types";
2 |
3 | export default function ErrorHandler({ error }: ErrorHandlerProps) {
4 | return ErrorHandler {error}
;
5 | }
6 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Indicators/ErrorHandler/types.tsx:
--------------------------------------------------------------------------------
1 | export type ErrorHandlerProps = {
2 | /**
3 | * The error that should be parsed and displayed
4 | */
5 | error: any;
6 | };
7 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Indicators/Loader/index.tsx:
--------------------------------------------------------------------------------
1 | import { LoaderProps } from "./types";
2 |
3 | export default function Loader({ isLoading = true }: LoaderProps) {
4 | if (!isLoading) return null;
5 |
6 | return Loader
;
7 | }
8 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Indicators/Loader/types.tsx:
--------------------------------------------------------------------------------
1 | export type LoaderProps = {
2 | /**
3 | * Determine whether to display the loader component
4 | *
5 | * @default true
6 | */
7 | isLoading?: boolean;
8 | };
9 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Indicators/LoadingErrorHandler/index.tsx:
--------------------------------------------------------------------------------
1 | import ErrorHandler from "./../ErrorHandler";
2 | import Loader from "./../Loader";
3 | import { LoadingErrorHandlerProps } from "./types";
4 |
5 | export default function LoadingErrorHandler({
6 | error = null,
7 | isLoading = undefined,
8 | }: LoadingErrorHandlerProps) {
9 | if (isLoading !== undefined) {
10 | return ;
11 | }
12 |
13 | if (error) {
14 | return ;
15 | }
16 |
17 | return null;
18 | }
19 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Indicators/LoadingErrorHandler/types.tsx:
--------------------------------------------------------------------------------
1 | export type LoadingErrorHandlerProps = {
2 | /**
3 | * Error to be handled
4 | */
5 | error?: any;
6 | /**
7 | * Is loading component
8 | */
9 | isLoading?: boolean | undefined;
10 | };
11 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/components/Toast/index.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | NotificationProps,
3 | showNotification,
4 | updateNotification,
5 | } from "@mantine/notifications";
6 | import { Random } from "@mongez/reinforcements";
7 | import { IconCheck, IconX } from "@tabler/icons";
8 | import React from "react";
9 |
10 | export function toastLoading(title: React.ReactNode, message: React.ReactNode) {
11 | const id = Random.id();
12 | showNotification({
13 | id,
14 | loading: true,
15 | title,
16 | message,
17 | autoClose: false,
18 | disallowClose: true,
19 | });
20 |
21 | return {
22 | success: (
23 | title: React.ReactNode,
24 | message: React.ReactNode,
25 | notificationProps: Partial = {
26 | color: "green",
27 | autoClose: 5000,
28 | },
29 | ) => {
30 | updateNotification({
31 | id,
32 | title,
33 | message,
34 | icon: ,
35 | ...notificationProps,
36 | });
37 | },
38 | error: (
39 | title: React.ReactNode,
40 | message: React.ReactNode,
41 | notificationProps: Partial = {
42 | color: "red",
43 | autoClose: 5000,
44 | },
45 | ) => {
46 | updateNotification({
47 | id,
48 | title,
49 | message,
50 | icon: ,
51 | ...notificationProps,
52 | });
53 | },
54 | };
55 | }
56 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/index.ts:
--------------------------------------------------------------------------------
1 | // design system
2 | export const theme = {};
3 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/layouts/AccountLayout/index.tsx:
--------------------------------------------------------------------------------
1 | import { BasicComponentProps } from "app/utils/types";
2 | import BaseLayout from "../BaseLayout";
3 |
4 | export default function AccountLayout({ children }: BasicComponentProps) {
5 | return (
6 | <>
7 |
8 |
9 | {children}
10 |
11 | >
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/layouts/BaseLayout/index.tsx:
--------------------------------------------------------------------------------
1 | import { BasicComponentProps } from "app/utils/types";
2 | import Footer from "../Footer";
3 | import Header from "../Header";
4 |
5 | export default function BaseLayout({ children }: BasicComponentProps) {
6 | return (
7 | <>
8 |
9 |
10 | {children}
11 |
12 | >
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/layouts/Footer/index.tsx:
--------------------------------------------------------------------------------
1 | export default function Footer() {
2 | return (
3 | <>
4 | Footer
5 | >
6 | );
7 | }
8 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/layouts/Header/index.tsx:
--------------------------------------------------------------------------------
1 | export default function Header() {
2 | return (
3 | <>
4 | Header
5 | >
6 | );
7 | }
8 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/design-system/layouts/Root.tsx:
--------------------------------------------------------------------------------
1 | import { AppShell, MantineProvider } from "@mantine/core";
2 | import { NotificationsProvider } from "@mantine/notifications";
3 | import { BasicComponentProps } from "../../utils/types";
4 |
5 | /**
6 | * The root should be used with react-router's configuration for rootComponent.
7 | * So it will wrap the entire app.
8 | * It can be useful for single operations as this component will only render once in the entire application life cycle.
9 | * You may for instance fetch settings from the server before loading the app or a Bearer token to work with the API.
10 | */
11 | export default function Root({ children }: BasicComponentProps) {
12 | return (
13 | <>
14 |
15 |
16 | {children}
17 |
18 |
19 | >
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/file-manager/Kernel/Kernel.ts:
--------------------------------------------------------------------------------
1 | import events, { EventSubscription } from "@mongez/events";
2 | import { createDirectory } from "../actions";
3 | import fileManagerService from "../services/file-manager-service";
4 | import { KernelEvents, Node } from "./Kernel.types";
5 | import KernelTree from "./KernelTree";
6 |
7 | export default class Kernel {
8 | /**
9 | * Root path
10 | */
11 | public rootPath = "/";
12 |
13 | /**
14 | * Current directory path
15 | */
16 | public currentDirectoryPath = "/";
17 |
18 | /**
19 | * Current directory node
20 | */
21 | public currentDirectoryNode?: Node;
22 |
23 | /**
24 | * Kernel nodes tree
25 | */
26 | public tree: KernelTree;
27 |
28 | /**
29 | * Root node
30 | */
31 | public rootNode?: Node;
32 |
33 | /**
34 | * Constructor
35 | */
36 | public constructor(rootPath: string) {
37 | this.rootPath = rootPath;
38 |
39 | this.tree = new KernelTree(this);
40 | }
41 |
42 | /**
43 | * Get kernel actions
44 | */
45 | public get actions() {
46 | // we added the following line to disable the annoying eslint message
47 | // as we can not use the this keyword in any getters i.e createDirectory.
48 | // eslint-disable-next-line @typescript-eslint/no-this-alias
49 | const kernel = this;
50 |
51 | return {
52 | navigateTo: this.load.bind(this),
53 | get createDirectory() {
54 | return createDirectory(kernel);
55 | },
56 | };
57 | }
58 |
59 | /**
60 | * Set root path
61 | */
62 | public setRootPath(rootPath: string): Kernel {
63 | this.rootPath = rootPath;
64 | return this;
65 | }
66 |
67 | /**
68 | * Load the given path
69 | */
70 | public load(path: string): Promise {
71 | // trigger loading event
72 | this.trigger("loading");
73 |
74 | return new Promise((resolve, reject) => {
75 | fileManagerService
76 | .list(path)
77 | .then(response => {
78 | this.currentDirectoryPath = path;
79 |
80 | if (response.data.node.path === this.rootPath) {
81 | this.tree.setRootNode(response.data.node);
82 | this.rootNode = response.data.node;
83 | } else {
84 | this.tree.setNode(response.data.node);
85 | }
86 |
87 | // trigger load event as the directory has been loaded successfully.
88 | this.trigger("load", response.data.node);
89 |
90 | // if the current directory is not as the same loaded directory path,
91 | // then we'll trigger directory changed event.
92 | if (response.data.node.path !== this.currentDirectoryNode?.path) {
93 | this.trigger("directoryChange", response.data.node);
94 | }
95 |
96 | this.currentDirectoryNode = response.data.node;
97 |
98 | resolve(this.currentDirectoryNode as Node);
99 | })
100 | .catch(reject);
101 | });
102 | }
103 |
104 | /**
105 | * Add event listener to the given event
106 | */
107 | public on(event: KernelEvents, callback: any): EventSubscription {
108 | return events.subscribe(`file-manger.${event}`, callback);
109 | }
110 |
111 | /**
112 | * Trigger the given event
113 | */
114 | public trigger(event: KernelEvents, ...args: any[]): void {
115 | events.trigger(`file-manger.${event}`, ...args);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/file-manager/Kernel/Kernel.types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * File Manager node is the primary data structure for the File Manager.
3 | * It can be a directory or a file.
4 | * It contains the following properties:
5 | */
6 | export type Node = {
7 | /**
8 | * Node Name
9 | */
10 | name: string;
11 | /**
12 | * Node full path to root
13 | */
14 | path: string;
15 | /**
16 | * Node size in bits
17 | */
18 | size: number;
19 | /**
20 | * Is node directory
21 | */
22 | isDirectory: boolean;
23 | /**
24 | * Node children
25 | * This should be present (event with empty array) if the node is directory
26 | */
27 | children?: Node[];
28 | /**
29 | * Get children directories
30 | */
31 | directories?: Node[];
32 | /**
33 | * Get children files
34 | */
35 | files?: Node[];
36 | };
37 |
38 | /**
39 | * Kernel events
40 | */
41 | export type KernelEvents =
42 | | "loading"
43 | | "load"
44 | | "directoryChange"
45 | | "nodeChange"
46 | | "nodeDestroy"
47 | | "newNode";
48 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/file-manager/Kernel/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./Kernel";
2 | export * from "./Kernel.types";
3 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/file-manager/actions/createDirectory.ts:
--------------------------------------------------------------------------------
1 | import { toastLoading } from "design-system/components/Toast";
2 | import Kernel from "../Kernel";
3 | import fileManagerService from "../services/file-manager-service";
4 |
5 | export default function createDirectory(kernel: Kernel) {
6 | return function create(
7 | directoryName: string,
8 | directoryPath: string = kernel.currentDirectoryNode?.path as string,
9 | ) {
10 | return new Promise((resolve, reject) => {
11 | const loader = toastLoading(
12 | "Creating directory...",
13 | "We are creating your directory, please wait a moment.",
14 | );
15 |
16 | fileManagerService
17 | .createDirectory(directoryName, directoryPath)
18 | .then(response => {
19 | loader.success("Success!", "Your directory has been created.");
20 |
21 | kernel.tree.setNode(response.data.node);
22 |
23 | resolve(response.data.node);
24 | })
25 | .catch(error => {
26 | loader.error("Error", error.response.data.error);
27 | reject(error);
28 | });
29 | });
30 | };
31 | }
32 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/file-manager/actions/index.ts:
--------------------------------------------------------------------------------
1 | export { default as createDirectory } from "./createDirectory";
2 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/file-manager/components/Content/Content.styles.tsx:
--------------------------------------------------------------------------------
1 | import styled from "@emotion/styled";
2 |
3 | export const ContentWrapper = styled.div`
4 | label: ContentWrapper;
5 | position: relative;
6 | height: 300px;
7 | overflow-y: auto;
8 | overflow-x: hidden;
9 | `;
10 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/file-manager/components/Content/Content.tsx:
--------------------------------------------------------------------------------
1 | import { Card } from "@mantine/core";
2 | import { ContentWrapper } from "./Content.styles";
3 | import ContentOverlay from "./ContentOverlay";
4 | import NodesList from "./NodesList";
5 |
6 | export default function Content() {
7 | return (
8 | <>
9 |
10 |
11 |
12 |
13 |
14 |
15 | >
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/file-manager/components/Content/ContentNode/ContentNode.tsx:
--------------------------------------------------------------------------------
1 | export default function ContentNode() {
2 | return ContentNode
;
3 | }
4 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/file-manager/components/Content/ContentNode/ContentNode.types.ts:
--------------------------------------------------------------------------------
1 | import { Node } from "app/file-manager/Kernel";
2 |
3 | export type FileNodeProps = {
4 | node: Node;
5 | };
6 |
7 | export type DirectoryNodeProps = {
8 | node: Node;
9 | };
10 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/file-manager/components/Content/ContentNode/DirectoryNode.tsx:
--------------------------------------------------------------------------------
1 | import { NavLink, useMantineTheme } from "@mantine/core";
2 | import { IconFolder } from "@tabler/icons";
3 | import { useKernel } from "app/file-manager/hooks";
4 | import { DirectoryNodeProps } from "./ContentNode.types";
5 |
6 | export default function DirectoryNode({ node }: DirectoryNodeProps) {
7 | const theme = useMantineTheme();
8 | const kernel = useKernel();
9 |
10 | return (
11 | kernel.load(node.path)}
17 | label={
18 | <>
19 |
25 | {node.name}
26 | >
27 | }
28 | />
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/file-manager/components/Content/ContentNode/FileNode.tsx:
--------------------------------------------------------------------------------
1 | import { NavLink, useMantineTheme } from "@mantine/core";
2 | import { IconFileInfo as Icon } from "@tabler/icons";
3 | import { FileNodeProps } from "./ContentNode.types";
4 |
5 | export default function FileNode({ node }: FileNodeProps) {
6 | const theme = useMantineTheme();
7 | return (
8 |
15 |
21 | {node.name}
22 | >
23 | }
24 | />
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/file-manager/components/Content/ContentNode/index.ts:
--------------------------------------------------------------------------------
1 | export { default as DirectoryNode } from "./DirectoryNode";
2 | export { default as FileNode } from "./FileNode";
3 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/file-manager/components/Content/ContentOverlay.tsx:
--------------------------------------------------------------------------------
1 | import { LoadingOverlay } from "@mantine/core";
2 | import { useLoading } from "app/file-manager/hooks";
3 |
4 | export default function ContentOverlay() {
5 | const isLoading = useLoading();
6 | return ;
7 | }
8 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/file-manager/components/Content/NodesList.tsx:
--------------------------------------------------------------------------------
1 | import { Grid } from "@mantine/core";
2 | import {
3 | useCurrentDirectoryNode,
4 | useNodeWatcher,
5 | } from "app/file-manager/hooks";
6 | import { DirectoryNode, FileNode } from "./ContentNode";
7 |
8 | export default function NodesList() {
9 | const currentDirectoryNode = useCurrentDirectoryNode();
10 |
11 | const node = useNodeWatcher(currentDirectoryNode);
12 |
13 | return (
14 | <>
15 |
16 | {node?.directories?.map(node => (
17 |
18 |
19 |
20 | ))}
21 | {node?.files?.map(node => (
22 |
23 |
24 |
25 | ))}
26 |
27 | >
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/file-manager/components/Content/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./Content";
2 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/file-manager/components/FileManager/FileManager.styles.ts:
--------------------------------------------------------------------------------
1 | import styled from "@emotion/styled";
2 |
3 | export const BodyWrapper = styled.div`
4 | label: BodyWrapper;
5 | margin-top: 1rem;
6 | `;
7 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/file-manager/components/FileManager/FileManager.tsx:
--------------------------------------------------------------------------------
1 | import { Grid } from "@mantine/core";
2 | import Content from "app/file-manager/components/Content";
3 | import LoadingProgressBar from "app/file-manager/components/LoadingProgressBar";
4 | import Sidebar from "app/file-manager/components/Sidebar";
5 | import Toolbar from "app/file-manager/components/Toolbar";
6 | import { KernelContext } from "app/file-manager/contexts";
7 | import Kernel from "app/file-manager/Kernel";
8 | import { useEffect, useRef } from "react";
9 | import { BodyWrapper } from "./FileManager.styles";
10 | import { FileManagerProps } from "./FileManager.types";
11 |
12 | export default function FileManager({ rootPath }: FileManagerProps) {
13 | const { current: kernel } = useRef(new Kernel(rootPath as string));
14 |
15 | // load root directory
16 | useEffect(() => {
17 | if (!rootPath) return;
18 |
19 | kernel.load(rootPath);
20 | }, [rootPath, kernel]);
21 |
22 | return (
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | );
38 | }
39 |
40 | FileManager.defaultProps = {
41 | rootPath: "/",
42 | };
43 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/file-manager/components/FileManager/FileManager.types.ts:
--------------------------------------------------------------------------------
1 | import { Node } from "app/file-manager/Kernel";
2 |
3 | export type FileManagerProps = {
4 | /**
5 | * Root path to open in the file manager
6 | *
7 | * @default "/"
8 | */
9 | rootPath?: string;
10 | /**
11 | * Callback for when a file/directory is selected
12 | */
13 | onSelect?: (node: Node) => void;
14 | /**
15 | * Callback for when a file/directory is double clicked
16 | */
17 | onDoubleClick?: (node: Node) => void;
18 | /**
19 | * Callback for when a file/directory is right clicked
20 | */
21 | onRightClick?: (node: Node) => void;
22 | /**
23 | * Callback for when a file/directory is copied
24 | */
25 | onCopy?: (node: Node) => void;
26 | /**
27 | * Callback for when a file/directory is cut
28 | */
29 | onCut?: (node: Node) => void;
30 | /**
31 | * Callback for when a file/directory is pasted
32 | * The old node will contain the old path and the new node will contain the new path
33 | */
34 | onPaste?: (node: Node, oldNode: Node) => void;
35 | /**
36 | * Callback for when a file/directory is deleted
37 | */
38 | onDelete?: (node: Node) => void;
39 | /**
40 | * Callback for when a file/directory is renamed
41 | * The old node will contain the old path/name and the new node will contain the new path/name
42 | */
43 | onRename?: (node: Node, oldNode: Node) => void;
44 | /**
45 | * Callback for when a directory is created
46 | */
47 | onCreateDirectory?: (directory: Node) => void;
48 | /**
49 | * Callback for when file(s) is uploaded
50 | */
51 | onUpload?: (files: Node[]) => void;
52 | /**
53 | * Callback for when a file is downloaded
54 | */
55 | onDownload?: (node: Node) => void;
56 | };
57 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/file-manager/components/FileManager/index.ts:
--------------------------------------------------------------------------------
1 | import FileManager from "./FileManager";
2 |
3 | export default FileManager;
4 |
--------------------------------------------------------------------------------
/client/src/apps/front-office/file-manager/components/LoadingProgressBar.tsx:
--------------------------------------------------------------------------------
1 | import { Progress } from "@mantine/core";
2 | import { useKernel } from "app/file-manager/hooks";
3 | import { useEffect, useState } from "react";
4 |
5 | export default function LoadingProgressBar() {
6 | const kernel = useKernel();
7 | const [progress, setProgress] = useState(0);
8 |
9 | useEffect(() => {
10 | // let's create an interval that will update progress every 300ms
11 | let interval: ReturnType;
12 |
13 | // we'll listen for loading state
14 | const loadingEvent = kernel.on("loading", () => {
15 | setProgress(5);
16 |
17 | interval = setInterval(() => {
18 | // we'll increase it by 10% every 100ms
19 | // if it's more than 100% we'll set it to 100%
20 | setProgress(progress => {
21 | if (progress >= 100) {
22 | clearInterval(interval);
23 | return 100;
24 | }
25 |
26 | return progress + 2;
27 | });
28 | }, 100);
29 | });
30 |
31 | // now let's listen when the loading is finished
32 | const loadEvent = kernel.on("load", () => {
33 | // clear the interval
34 | setProgress(100);
35 |
36 | setTimeout(() => {
37 | clearInterval(interval);
38 |
39 | // set progress to 0
40 | setProgress(0);
41 | }, 300);
42 | });
43 |
44 | // unsubscribe events on unmount or when use effect dependencies change
45 | return () => {
46 | loadingEvent.unsubscribe();
47 | loadEvent.unsubscribe();
48 | };
49 | }, [kernel]);
50 |
51 | const mapProgressColor = () => {
52 | if (progress < 25) {
53 | return "blue";
54 | }
55 |
56 | if (progress < 50) {
57 | return "indigo";
58 | }
59 |
60 | if (progress < 75) {
61 | return "lime";
62 | }
63 |
64 | return "green";
65 | };
66 |
67 | return (
68 |