├── .babelrc.js
├── .eslintignore
├── .eslintrc
├── .github
├── FUNDING.yml
└── workflows
│ ├── phpcs.yml
│ └── phpunit.yml
├── .gitignore
├── .php_cs
├── .wp-env.json
├── LICENSE.txt
├── README.md
├── Rest-API-Docs.MD
├── assets
├── images
│ ├── wp-react-kit-logo-full.png
│ └── wp-react-kit-logo.png
└── js
│ ├── version-replace.js
│ └── zip.js
├── changelog.txt
├── composer.json
├── composer.lock
├── includes
├── Abstracts
│ ├── BaseModel.php
│ ├── DBMigrator.php
│ ├── DBSeeder.php
│ └── RESTController.php
├── Admin
│ └── Menu.php
├── Assets
│ └── Manager.php
├── Blocks
│ └── Manager.php
├── Common
│ └── Keys.php
├── Databases
│ ├── Migrations
│ │ ├── JobTypeMigration.php
│ │ └── JobsMigration.php
│ └── Seeder
│ │ ├── JobTypeSeeder.php
│ │ ├── JobsSeeder.php
│ │ └── Manager.php
├── Jobs
│ ├── Job.php
│ ├── JobStatus.php
│ ├── JobType.php
│ └── Manager.php
├── REST
│ ├── Api.php
│ ├── CompaniesController.php
│ ├── JobTypesController.php
│ └── JobsController.php
├── Setup
│ └── Installer.php
├── Traits
│ ├── InputSanitizer.php
│ └── Queryable.php
└── User
│ └── Hooks.php
├── jest-unit.config.js
├── job-place.php
├── languages
└── jobplace.pot
├── package-lock.json
├── package.json
├── phpcs.xml
├── phpunit.xml.dist
├── postcss.config.js
├── src
├── App.tsx
├── blocks
│ └── header
│ │ ├── block.json
│ │ ├── edit.tsx
│ │ ├── editor.scss
│ │ ├── index.ts
│ │ ├── save.tsx
│ │ └── style.scss
├── components
│ ├── badge
│ │ ├── Badge.stories.tsx
│ │ ├── Badge.tsx
│ │ └── __tests__
│ │ │ └── Badge.test.tsx
│ ├── button
│ │ ├── Button.stories.tsx
│ │ └── Button.tsx
│ ├── dashboard
│ │ └── Dashboard.tsx
│ ├── date-picker
│ │ ├── DatePicker.stories.tsx
│ │ ├── DatePicker.tsx
│ │ └── DatePickerData.ts
│ ├── inputs
│ │ ├── Input.stories.tsx
│ │ ├── Input.tsx
│ │ ├── InputLabel.tsx
│ │ ├── Select2Input.stories.tsx
│ │ ├── Select2Input.tsx
│ │ ├── SwitchCheckbox.tsx
│ │ ├── SwitchInput.stories.tsx
│ │ └── TextEditor.tsx
│ ├── jobs
│ │ ├── JobCard.tsx
│ │ ├── JobForm.tsx
│ │ ├── JobFormSidebar.tsx
│ │ ├── JobSubmit.tsx
│ │ ├── ListItemMenu.tsx
│ │ ├── SelectCheckBox.tsx
│ │ └── use-table-data.tsx
│ ├── layout
│ │ ├── Header.tsx
│ │ ├── Layout.tsx
│ │ ├── NavMenu.tsx
│ │ └── PageHeading.tsx
│ ├── loading
│ │ ├── BarChartLoading.stories.tsx
│ │ ├── BarChartLoading.tsx
│ │ ├── DashboardCardLoading.stories.tsx
│ │ ├── DashboardCardLoading.tsx
│ │ ├── LineChartLoading.stories.tsx
│ │ ├── LineChartLoading.tsx
│ │ ├── Loading.stories.tsx
│ │ ├── Loading.tsx
│ │ ├── OverlayLoading.tsx
│ │ ├── SettingsLoading.stories.tsx
│ │ ├── SettingsLoading.tsx
│ │ ├── SettingsSectionLoading.tsx
│ │ ├── TableLoading.stories.tsx
│ │ └── TableLoading.tsx
│ ├── modal
│ │ ├── Modal.stories.tsx
│ │ └── Modal.tsx
│ ├── page-partials
│ │ ├── SelectListItem.tsx
│ │ └── SelectedItem.tsx
│ ├── pagination
│ │ ├── Pagination.stories.tsx
│ │ └── Pagination.tsx
│ ├── spinner
│ │ ├── Spinner.stories.tsx
│ │ └── Spinner.tsx
│ ├── svg
│ │ ├── LogoIcon.tsx
│ │ ├── SvgCircleDefaultIcon.tsx
│ │ ├── SvgCirclePrimaryIcon.tsx
│ │ ├── SvgCircleSuccessIcon.tsx
│ │ └── SvgCircleWarningIcon.tsx
│ ├── tab
│ │ ├── Tab.stories.tsx
│ │ └── Tab.tsx
│ ├── table
│ │ ├── Table.stories.tsx
│ │ ├── Table.tsx
│ │ └── TableInterface.ts
│ └── tooltip
│ │ ├── ProNotExistTooltip.tsx
│ │ ├── Tooltip.tsx
│ │ └── tooltip-style.scss
├── data
│ ├── jobs
│ │ ├── actions.ts
│ │ ├── controls.ts
│ │ ├── default-state.ts
│ │ ├── endpoint.ts
│ │ ├── index.ts
│ │ ├── reducer.ts
│ │ ├── resolvers.ts
│ │ ├── selectors.ts
│ │ ├── types.ts
│ │ └── utils.ts
│ └── store.ts
├── hooks
│ ├── use-window-width.tsx
│ ├── useConfirmReload.tsx
│ ├── useMenuFix.tsx
│ └── useOutsideClick.tsx
├── index.tsx
├── interfaces
│ ├── index.ts
│ └── jobs.ts
├── pages
│ ├── HomePage.tsx
│ └── jobs
│ │ ├── CreateJob.tsx
│ │ ├── EditJob.tsx
│ │ └── JobsPage.tsx
├── routes
│ └── index.ts
├── style
│ ├── main.scss
│ └── tailwind.css
└── utils
│ ├── DateHelper.ts
│ ├── MenuFix.ts
│ ├── NumberFormat.ts
│ ├── Select2Helper.ts
│ ├── StringHelper.ts
│ ├── global-data.ts
│ ├── http.ts
│ └── text-parser.ts
├── tailwind.config.js
├── templates
├── app.php
└── blocks
│ └── header
│ └── markup.php
├── tests
├── e2e
│ ├── global.setup.ts
│ ├── gutenberg-test-plugin-disables-the-css-animations
│ │ └── gutenberg-test-plugin-disables-the-css-animations.php
│ ├── playwright.config.ts
│ ├── specs
│ │ ├── blocks
│ │ │ └── header.spec.ts
│ │ ├── env.spec.ts
│ │ └── example.spec.ts
│ └── unit
│ │ └── example.test.ts
├── phpunit
│ ├── Api
│ │ ├── CompanyRestApiTest.php
│ │ └── JobRestApiTest.php
│ ├── Install
│ │ └── RunnerTest.php
│ ├── Jobs
│ │ └── JobManagerTest.php
│ ├── bootstrap.php
│ └── wp-config.php
└── unit
│ └── config
│ └── testing-library.js
├── tsconfig.json
└── webpack.config.js
/.babelrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | [
4 | '@wordpress/babel-plugin-import-jsx-pragma',
5 | {
6 | scopeVariable: 'createElement',
7 | scopeVariableFrag: 'Fragment',
8 | source: '@wordpress/element',
9 | isDefault: false,
10 | },
11 | ],
12 | [
13 | '@babel/plugin-transform-react-jsx',
14 | {
15 | pragma: 'createElement',
16 | pragmaFrag: 'Fragment',
17 | },
18 | ],
19 | ],
20 | presets: [
21 | ['@babel/preset-env', { targets: { node: 'current' } }],
22 | ['@babel/preset-typescript'],
23 | ],
24 | };
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/*.min.js
2 | **/node_modules/**
3 | **/vendor/**
4 | **/build/**
5 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "plugin:@wordpress/eslint-plugin/recommended"
4 | ],
5 | "rules": {
6 | "prettier/prettier": [
7 | "error",
8 | {
9 | "endOfLine": "auto",
10 | "parenSpacing": true,
11 | "tabWidth": 4,
12 | "useTabs": false,
13 | "singleQuote": true,
14 | "trailingComma": "es5",
15 | "bracketSpacing": true,
16 | "jsxBracketSameLine": false,
17 | "semi": true,
18 | "arrowParens": "always"
19 | }
20 | ],
21 | "@wordpress/i18n-text-domain": [
22 | "error",
23 | {
24 | "allowedTextDomain": [
25 | "jobplace"
26 | ]
27 | }
28 | ]
29 | }
30 | }
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | patreon: maniruzzaman
4 |
--------------------------------------------------------------------------------
/.github/workflows/phpcs.yml:
--------------------------------------------------------------------------------
1 | name: PHPCS check
2 | on:
3 | pull_request:
4 | push:
5 | branches:
6 | - trunk
7 | - develop
8 | - main
9 |
10 | concurrency:
11 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
12 | cancel-in-progress: true
13 |
14 | jobs:
15 | phpcs:
16 | name: PHPCS check
17 | runs-on: ubuntu-latest
18 | steps:
19 | - name: Checkout code
20 | uses: actions/checkout@v3
21 |
22 | - name: Setup PHP
23 | uses: "shivammathur/setup-php@v2"
24 | with:
25 | php-version: "7.4"
26 | ini-values: "memory_limit=1G"
27 | coverage: none
28 | tools: cs2pr
29 |
30 | - name: Install Composer dependencies
31 | uses: "ramsey/composer-install@v2"
32 |
33 | - name: Run PHPCS checks
34 | continue-on-error: true
35 | run: composer run phpcs --report-checkstyle=./phpcs-report.xml
36 |
37 | - name: Show PHPCS results in PR
38 | run: cs2pr ./phpcs-report.xml
--------------------------------------------------------------------------------
/.github/workflows/phpunit.yml:
--------------------------------------------------------------------------------
1 | name: PHPUnit Tests
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches:
7 | - trunk
8 | - develop
9 | - main
10 |
11 | # Cancels all previous workflow runs for pull requests that have not completed.
12 | concurrency:
13 | # The concurrency group contains the workflow name and the branch name for pull requests
14 | # or the commit hash for any other events.
15 | group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
16 | cancel-in-progress: true
17 |
18 | jobs:
19 | test:
20 | name: phpunit tests
21 | runs-on: ubuntu-latest
22 | strategy:
23 | fail-fast: false
24 |
25 | steps:
26 | - uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
27 |
28 | - name: Use desired version of php (7.4 with xdebug)
29 | uses: shivammathur/setup-php@v2
30 | with:
31 | php-version: '7.4'
32 | coverage: xdebug
33 |
34 | - name: Composer install and build
35 | run: |
36 | composer install
37 | composer update
38 | composer dump-autoload -o
39 |
40 | - name: Running the phpunit tests
41 | continue-on-error: true
42 | run: composer run test
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /vendor
3 | /build
4 | .phpunit.result.cache
5 | .DS_Store
6 | /dist
7 |
8 | # Playwright
9 | /test-results/
10 | /playwright-report/
11 | /playwright/.cache/
12 | /artifacts/
--------------------------------------------------------------------------------
/.php_cs:
--------------------------------------------------------------------------------
1 | exclude( 'node_modules' )
7 | ->exclude( 'vendors' )
8 | ->exclude( 'assets' )
9 | ->exclude( 'languages' )
10 | ->exclude( 'src' )
11 | ->exclude( 'bin' )
12 | ->in( __DIR__ );
13 |
14 | $config = PhpCsFixer\Config::create()
15 | ->registerCustomFixers( [
16 | new WeDevs\Fixer\SpaceInsideParenthesisFixer(),
17 | new WeDevs\Fixer\BlankLineAfterClassOpeningFixer(),
18 | ] )
19 | ->setRiskyAllowed( true )
20 | ->setUsingCache( false )
21 | ->setRules( WeDevs\Fixer\Fixer::rules() )
22 | ->setFinder( $finder );
23 |
24 | return $config;
25 |
--------------------------------------------------------------------------------
/.wp-env.json:
--------------------------------------------------------------------------------
1 | {
2 | "phpVersion": "7.4",
3 | "plugins": [
4 | "."
5 | ],
6 | "config": {
7 | "WP_DEBUG_LOG": true,
8 | "WP_DEBUG_DISPLAY": true
9 | },
10 | "env": {
11 | "development": {},
12 | "tests": {
13 | "port": 8889,
14 | "plugins": [
15 | ".",
16 | "./tests/e2e/gutenberg-test-plugin-disables-the-css-animations"
17 | ],
18 | "config": {
19 | "WP_TESTS_DOMAIN": "localhost"
20 | }
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Maniruzzaman Akash
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/assets/images/wp-react-kit-logo-full.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ManiruzzamanAkash/wp-react-kit/17ee15156914638201cd70c6c6c7af2b2297805b/assets/images/wp-react-kit-logo-full.png
--------------------------------------------------------------------------------
/assets/images/wp-react-kit-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ManiruzzamanAkash/wp-react-kit/17ee15156914638201cd70c6c6c7af2b2297805b/assets/images/wp-react-kit-logo.png
--------------------------------------------------------------------------------
/assets/js/version-replace.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs-extra');
2 | const replace = require('replace-in-file');
3 |
4 | const pluginFiles = ['includes/**/*', 'templates/*', 'src/*', 'job-place.php'];
5 |
6 | const { version } = JSON.parse(fs.readFileSync('package.json'));
7 |
8 | replace({
9 | files: pluginFiles,
10 | from: /JOBPLACE_SINCE/g,
11 | to: version,
12 | });
13 |
--------------------------------------------------------------------------------
/assets/js/zip.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs-extra');
2 | const path = require('path');
3 | const { exec } = require('child_process');
4 | const util = require('util');
5 | const chalk = require('chalk');
6 | const _ = require('lodash');
7 |
8 | const asyncExec = util.promisify(exec);
9 |
10 | const pluginFiles = [
11 | 'assets/',
12 | 'build/',
13 | 'includes/',
14 | 'languages/',
15 | 'templates/',
16 | 'changelog.txt',
17 | 'job-place.php',
18 | ];
19 |
20 | const removeFiles = [
21 | 'assets/js/version-replace.js',
22 | 'assets/js/zip.js',
23 | 'composer.json',
24 | 'composer.lock',
25 | ];
26 |
27 | const dest = `dist/`;
28 | const zipFile = `wp-react-kit.zip`;
29 | const allowedVendorFiles = {};
30 | fs.removeSync(`${dest}${zipFile}`);
31 |
32 | exec(
33 | 'rm -rf versions && rm *.zip',
34 | {
35 | cwd: 'dist',
36 | },
37 | () => {
38 | const composerFile = `composer.json`;
39 | fs.removeSync(dest);
40 | const fileList = [...pluginFiles];
41 | fs.mkdirp(dest);
42 |
43 | fileList.forEach((file) => {
44 | fs.copySync(file, `${dest}/${file}`);
45 | });
46 |
47 | // copy composer.json file
48 | try {
49 | if (fs.pathExistsSync(composerFile)) {
50 | fs.copySync(composerFile, `${dest}/composer.json`);
51 | } else {
52 | fs.copySync(`composer.json`, `${dest}/composer.json`);
53 | }
54 | } catch (err) {
55 | console.error(err);
56 | }
57 |
58 | console.log(`Finished copying files.`);
59 |
60 | asyncExec(
61 | 'composer install --optimize-autoloader --no-dev',
62 | {
63 | cwd: dest,
64 | },
65 | () => {
66 | console.log(
67 | `Installed composer packages in ${dest} directory.`
68 | );
69 |
70 | removeFiles.forEach((file) => {
71 | fs.removeSync(`${dest}/${file}`);
72 | });
73 |
74 | // Put vendor files.
75 | Object.keys(allowedVendorFiles).forEach((composerPackage) => {
76 | const packagePath = path.resolve(
77 | `${dest}/vendor/${composerPackage}`
78 | );
79 |
80 | if (!fs.existsSync(packagePath)) {
81 | return;
82 | }
83 |
84 | const list = fs.readdirSync(packagePath);
85 | const deletables = _.difference(
86 | list,
87 | allowedVendorFiles[composerPackage]
88 | );
89 |
90 | deletables.forEach((deletable) => {
91 | fs.removeSync(path.resolve(packagePath, deletable));
92 | });
93 | });
94 |
95 | console.log(`Making zip file ${zipFile}...`);
96 |
97 | asyncExec(
98 | `zip -r ${zipFile} ${dest} *`,
99 | {
100 | cwd: dest,
101 | },
102 | () => {
103 | fileList.forEach((file) => {
104 | fs.removeSync(`${dest}/${file}`);
105 | });
106 | fs.removeSync(`${dest}/vendor`);
107 | console.log(
108 | chalk.green(
109 | `${zipFile} is ready inside ${dest} folder.`
110 | )
111 | );
112 | }
113 | ).catch((error) => {
114 | console.log(chalk.red(`Could not make ${zipFile}.`));
115 | console.log(error);
116 | });
117 | }
118 | ).catch((error) => {
119 | console.log(
120 | chalk.red(`Could not install composer in ${dest} directory.`)
121 | );
122 | console.log(error);
123 | });
124 | }
125 | );
126 |
--------------------------------------------------------------------------------
/changelog.txt:
--------------------------------------------------------------------------------
1 | ### Changelogs
2 | **v0.9.0 - 20/12/2024**
3 |
4 | 1. **Fix:** Updated PHP version support > 8.0 and some more library support
5 | 1. **Fix:** When Editing a Job, last job is being edited
6 | 1. **Update:** Tested upto WordPress 6.7.1
7 |
8 | **v0.8.0 - 24/05/2023**
9 |
10 | 1. New feature: WordPress Playwright test-e2e-utils added.
11 | 1. New feature: Some Gutenberg blocks has support for Playwright test.
12 |
13 | **v0.7.0 - 01/01/2023**
14 |
15 | 1. Fix: Dynamic block renderer issue.
16 | 1. Fix: Asset registering multiple times issue.
17 |
18 | **v0.5.0 - 14/11/2022**
19 |
20 | 1. New Feature : Job Create.
21 | 2. New Feature : Job Update.
22 | 3. New Feature : Job Delete.
23 | 4. New Feature : Job Status change.
24 | 5. New API: Company dropdown list.
25 | 6. New: Updated logo icon and plugin name.
26 | 7. New Components: Input Text-Editor, Improved design.
27 | 8. Refactor: Refactored codebase and updated docs.
28 | 9. New: Job type seeder.
29 | 10. Chore: Zip file generator.
30 | 11. Chore: i18n localization generator.
31 |
32 | **v0.4.1 - 18/08/2022**
33 |
34 | 1. Added Jest Unit Test Setup.
35 | 2. Added some dummy Jest Unit Test.
36 | 3. Fix #11 - Version naming while installing.
37 |
38 | **v0.4.0 - 12/08/2022**
39 |
40 | 1. Added many re-usable general components.
41 | 1. Header Component refactored and re-designed.
42 | 1. WP-Data setup and made Job Store.
43 | 1. Job List Page frontend added.
44 |
45 | **v0.3.1 - 11/08/2022**
46 |
47 | 1. PHPUnit Test cases setup.
48 | 1. PHPUnit Test cases added for Job Manager and Job REST API's.
49 |
50 | **v0.3.0 - 02/08/2022**
51 |
52 | 1. Necessary traits to handle - sanitization, query.
53 | 1. Advanced setup for migration, seeder, REST API.
54 | 1. Jobs, Job Types REST API developed.
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "akash/wp-react-kit",
3 | "description": "A simple starter kit to work in WordPress plugin development using WordPress Rest API, WP-script and many more...",
4 | "type": "wordpress-plugin",
5 | "license": "GPL-2.0-or-later",
6 | "authors": [
7 | {
8 | "name": "ManiruzzamanAkash",
9 | "email": "manirujjamanakash@gmail.com"
10 | }
11 | ],
12 | "config": {
13 | "allow-plugins": {
14 | "dealerdirect/phpcodesniffer-composer-installer": true
15 | }
16 | },
17 | "require": {
18 | "php": ">=7.0.0"
19 | },
20 | "require-dev": {
21 | "wp-coding-standards/wpcs": "^3.0",
22 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.0",
23 | "tareq1988/wp-php-cs-fixer": "dev-master",
24 | "phpcompatibility/phpcompatibility-wp": "dev-master",
25 | "wp-phpunit/wp-phpunit": "^6.0",
26 | "yoast/phpunit-polyfills": "^1.0",
27 | "phpunit/phpunit": "^9.5"
28 | },
29 | "autoload": {
30 | "psr-4": {
31 | "Akash\\JobPlace\\": "includes/",
32 | "Akash\\JobPlace\\Tests\\": "tests/phpunit/"
33 | }
34 | },
35 | "scripts": {
36 | "phpcs": [
37 | "phpcs -p -s"
38 | ],
39 | "phpcbf": [
40 | "phpcbf -p"
41 | ],
42 | "test": [
43 | "vendor/bin/phpunit"
44 | ],
45 | "test:all": [
46 | "phpcs -p -s & vendor/bin/phpunit"
47 | ]
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/includes/Abstracts/BaseModel.php:
--------------------------------------------------------------------------------
1 | db = $wpdb;
68 | $this->table = $wpdb->prefix . $this->table;
69 | }
70 |
71 | /**
72 | * Convert item dataset to array.
73 | *
74 | * @since 0.3.0
75 | *
76 | * @param object $item
77 | *
78 | * @return array
79 | */
80 | abstract public static function to_array( object $item ): array;
81 | }
82 |
--------------------------------------------------------------------------------
/includes/Abstracts/DBMigrator.php:
--------------------------------------------------------------------------------
1 | header( 'X-WP-Total', (int) $total_items );
59 |
60 | $max_pages = ceil( $total_items / $per_page );
61 |
62 | $response->header( 'X-WP-TotalPages', (int) $max_pages );
63 | $base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->base ) ) );
64 |
65 | if ( $page > 1 ) {
66 | $prev_page = $page - 1;
67 | if ( $prev_page > $max_pages ) {
68 | $prev_page = $max_pages;
69 | }
70 | $prev_link = add_query_arg( 'page', $prev_page, $base );
71 | $response->link_header( 'prev', $prev_link );
72 | }
73 | if ( $max_pages > $page ) {
74 | $next_page = $page + 1;
75 | $next_link = add_query_arg( 'page', $next_page, $base );
76 | $response->link_header( 'next', $next_link );
77 | }
78 |
79 | return $response;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/includes/Admin/Menu.php:
--------------------------------------------------------------------------------
1 | register_styles( $this->get_styles() );
31 | $this->register_scripts( $this->get_scripts() );
32 | }
33 |
34 | /**
35 | * Get all styles.
36 | *
37 | * @since 0.2.0
38 | *
39 | * @return array
40 | */
41 | public function get_styles(): array {
42 | return [
43 | 'job-place-css' => [
44 | 'src' => JOB_PLACE_BUILD . '/index.css',
45 | 'version' => JOB_PLACE_VERSION,
46 | 'deps' => [],
47 | ],
48 | ];
49 | }
50 |
51 | /**
52 | * Get all scripts.
53 | *
54 | * @since 0.2.0
55 | *
56 | * @return array
57 | */
58 | public function get_scripts(): array {
59 | $dependency = require_once JOB_PLACE_DIR . '/build/index.asset.php';
60 |
61 | return [
62 | 'job-place-app' => [
63 | 'src' => JOB_PLACE_BUILD . '/index.js',
64 | 'version' => $dependency['version'],
65 | 'deps' => $dependency['dependencies'],
66 | 'in_footer' => true,
67 | ],
68 | ];
69 | }
70 |
71 | /**
72 | * Register styles.
73 | *
74 | * @since 0.2.0
75 | *
76 | * @return void
77 | */
78 | public function register_styles( array $styles ) {
79 | foreach ( $styles as $handle => $style ) {
80 | wp_register_style( $handle, $style['src'], $style['deps'], $style['version'] );
81 | }
82 | }
83 |
84 | /**
85 | * Register scripts.
86 | *
87 | * @since 0.2.0
88 | *
89 | * @return void
90 | */
91 | public function register_scripts( array $scripts ) {
92 | foreach ( $scripts as $handle =>$script ) {
93 | wp_register_script( $handle, $script['src'], $script['deps'], $script['version'], $script['in_footer'] );
94 | }
95 | }
96 |
97 | /**
98 | * Enqueue admin styles and scripts.
99 | *
100 | * @since 0.2.0
101 | * @since 0.3.0 Loads the JS and CSS only on the Job Place admin page.
102 | *
103 | * @return void
104 | */
105 | public function enqueue_admin_assets() {
106 | // Check if we are on the admin page and page=jobplace.
107 | if ( ! is_admin() || ! isset( $_GET['page'] ) || sanitize_text_field( wp_unslash( $_GET['page'] ) ) !== 'jobplace' ) {
108 | return;
109 | }
110 |
111 | wp_enqueue_style( 'job-place-css' );
112 | wp_enqueue_script( 'job-place-app' );
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/includes/Blocks/Manager.php:
--------------------------------------------------------------------------------
1 | register_block_metas();
39 |
40 | if ( $is_pre_wp_6 ) {
41 | // Remove the filter after we register the blocks
42 | remove_filter( 'plugins_url', [ $this, 'filter_plugins_url' ], 10, 2 );
43 | }
44 | }
45 |
46 | /**
47 | * Register all block by block jsons.
48 | *
49 | * @since 0.7.0
50 | *
51 | * @return void
52 | */
53 | protected function register_block_metas(): void {
54 | $blocks = [
55 | 'header/',
56 | ];
57 |
58 | foreach ( $blocks as $block ) {
59 | $block_folder = JOB_PLACE_PATH . '/build/blocks/' . $block;
60 | $block_options = [];
61 | $markup_file_path = JOB_PLACE_TEMPLATE_PATH . '/blocks/' . $block . 'markup.php';
62 |
63 | if ( file_exists( $markup_file_path ) ) {
64 | $block_options['render_callback'] = function( $attributes, $content, $block ) use ( $markup_file_path ) {
65 | $context = $block->context;
66 | ob_start();
67 | include $markup_file_path;
68 | return ob_get_clean();
69 | };
70 | }
71 |
72 | register_block_type_from_metadata( $block_folder, $block_options );
73 | }
74 | }
75 |
76 | /**
77 | * Filter the plugins_url to allow us to use assets from theme.
78 | *
79 | * @since 0.7.0
80 | *
81 | * @param string $url The plugins url
82 | * @param string $path The path to the asset.
83 | *
84 | * @return string The overridden url to the block asset.
85 | */
86 | public function filter_plugins_url( string $url, string $path ): string {
87 | $file = preg_replace( '/\.\.\//', '', $path );
88 | return trailingslashit( get_stylesheet_directory_uri() ) . $file;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/includes/Common/Keys.php:
--------------------------------------------------------------------------------
1 | get_charset_collate();
23 |
24 | $schema_job_types = "CREATE TABLE IF NOT EXISTS `{$wpdb->jobplace_job_types}` (
25 | `id` int(11) NOT NULL AUTO_INCREMENT,
26 | `name` varchar(255) NOT NULL,
27 | `slug` varchar(255) NOT NULL,
28 | `description` varchar(255) NOT NULL,
29 | `created_at` datetime NOT NULL,
30 | `updated_at` datetime NOT NULL,
31 | PRIMARY KEY (`id`),
32 | UNIQUE KEY `slug` (`slug`)
33 | ) $charset_collate;";
34 |
35 | // Create the tables.
36 | dbDelta( $schema_job_types );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/includes/Databases/Migrations/JobsMigration.php:
--------------------------------------------------------------------------------
1 | get_charset_collate();
23 |
24 | $schema_jobs = "CREATE TABLE IF NOT EXISTS `{$wpdb->jobplace_jobs}` (
25 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
26 | `title` varchar(255) NOT NULL,
27 | `slug` varchar(255) NOT NULL,
28 | `company_id` bigint(20) unsigned NOT NULL,
29 | `job_type_id` int(10) unsigned NOT NULL,
30 | `description` mediumtext NOT NULL,
31 | `is_active` tinyint(1) NOT NULL DEFAULT 1,
32 | `created_by` bigint(20) unsigned NOT NULL,
33 | `updated_by` bigint(20) unsigned NULL,
34 | `deleted_by` bigint(20) unsigned NULL,
35 | `created_at` datetime NOT NULL,
36 | `updated_at` datetime NOT NULL,
37 | `deleted_at` datetime NULL,
38 | PRIMARY KEY (`id`),
39 | KEY `company_id` (`company_id`),
40 | UNIQUE KEY `slug` (`slug`),
41 | KEY `is_active` (`is_active`),
42 | KEY `job_type_id` (`job_type_id`),
43 | KEY `created_by` (`created_by`),
44 | KEY `updated_by` (`updated_by`)
45 | ) $charset_collate";
46 |
47 | // Create the tables.
48 | dbDelta( $schema_jobs );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/includes/Databases/Seeder/JobTypeSeeder.php:
--------------------------------------------------------------------------------
1 | 'Full time',
35 | 'slug' => 'full-time',
36 | 'description' => 'This is a full time job post.',
37 | 'created_at' => current_datetime()->format( 'Y-m-d H:i:s' ),
38 | 'updated_at' => current_datetime()->format( 'Y-m-d H:i:s' ),
39 | ],
40 | [
41 | 'name' => 'Part time',
42 | 'slug' => 'part-time',
43 | 'description' => 'This is a part time job post.',
44 | 'created_at' => current_datetime()->format( 'Y-m-d H:i:s' ),
45 | 'updated_at' => current_datetime()->format( 'Y-m-d H:i:s' ),
46 | ],
47 | [
48 | 'name' => 'Remote',
49 | 'slug' => 'remote',
50 | 'description' => 'This is a remote job post.',
51 | 'created_at' => current_datetime()->format( 'Y-m-d H:i:s' ),
52 | 'updated_at' => current_datetime()->format( 'Y-m-d H:i:s' ),
53 | ],
54 | [
55 | 'name' => 'Contractual',
56 | 'slug' => 'contractual',
57 | 'description' => 'This is a contractual job post.',
58 | 'created_at' => current_datetime()->format( 'Y-m-d H:i:s' ),
59 | 'updated_at' => current_datetime()->format( 'Y-m-d H:i:s' ),
60 | ],
61 | ];
62 |
63 | // Create each of the job_types.
64 | foreach ( $job_types as $job_type ) {
65 | $wpdb->insert(
66 | $wpdb->prefix . 'jobplace_job_types',
67 | $job_type
68 | );
69 | }
70 |
71 | // Update that seeder already runs.
72 | update_option( Keys::JOB_TYPE_SEEDER_RAN, true );
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/includes/Databases/Seeder/JobsSeeder.php:
--------------------------------------------------------------------------------
1 | 'First Job Post',
35 | 'slug' => 'first-job-post',
36 | 'description' => 'This is a simple job post.',
37 | 'is_active' => 1,
38 | 'company_id' => 1,
39 | 'job_type_id' => 1,
40 | 'created_by' => get_current_user_id(),
41 | 'created_at' => current_datetime()->format( 'Y-m-d H:i:s' ),
42 | 'updated_at' => current_datetime()->format( 'Y-m-d H:i:s' ),
43 | ],
44 | ];
45 |
46 | // Create each of the jobs.
47 | foreach ( $jobs as $job ) {
48 | $wpdb->insert(
49 | $wpdb->prefix . 'jobplace_jobs',
50 | $job
51 | );
52 | }
53 |
54 | // Update that seeder already runs.
55 | update_option( Keys::JOB_SEEDER_RAN, true );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/includes/Databases/Seeder/Manager.php:
--------------------------------------------------------------------------------
1 | run();
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/includes/Jobs/Job.php:
--------------------------------------------------------------------------------
1 | '',
32 | 'slug' => '',
33 | 'description' => '',
34 | 'company_id' => 0,
35 | 'is_active' => 1,
36 | 'job_type_id' => null,
37 | 'created_by' => get_current_user_id(),
38 | 'created_at' => current_datetime()->format( 'Y-m-d H:i:s' ),
39 | 'updated_at' => current_datetime()->format( 'Y-m-d H:i:s' ),
40 | ];
41 |
42 | $data = wp_parse_args( $data, $defaults );
43 |
44 | // Sanitize template data
45 | return [
46 | 'title' => $this->sanitize( $data['title'], 'text' ),
47 | 'slug' => $this->sanitize( $data['slug'], 'text' ),
48 | 'description' => $this->sanitize( $data['description'], 'block' ),
49 | 'company_id' => $this->sanitize( $data['company_id'], 'number' ),
50 | 'is_active' => $this->sanitize( $data['is_active'], 'switch' ),
51 | 'job_type_id' => $this->sanitize( $data['job_type_id'], 'number' ),
52 | 'created_by' => $this->sanitize( $data['created_by'], 'number' ),
53 | 'created_at' => $this->sanitize( $data['created_at'], 'text' ),
54 | 'updated_at' => $this->sanitize( $data['updated_at'], 'text' ),
55 | ];
56 | }
57 |
58 | /**
59 | * Jobs item to a formatted array.
60 | *
61 | * @since 0.3.0
62 | *
63 | * @param object $job
64 | *
65 | * @return array
66 | */
67 | public static function to_array( ?object $job ): array {
68 | $job_type = static::get_job_type( $job );
69 |
70 | $data = [
71 | 'id' => (int) $job->id,
72 | 'title' => $job->title,
73 | 'slug' => $job->slug,
74 | 'job_type' => $job_type,
75 | 'is_remote' => static::get_is_remote( $job_type ),
76 | 'status' => JobStatus::get_status_by_job( $job ),
77 | 'company' => static::get_job_company( $job ),
78 | 'description' => $job->description,
79 | 'created_at' => $job->created_at,
80 | 'updated_at' => $job->updated_at,
81 | ];
82 |
83 | return $data;
84 | }
85 |
86 | /**
87 | * Get job type of a job.
88 | *
89 | * @since 0.3.0
90 | *
91 | * @param object $job
92 | *
93 | * @return object|null
94 | */
95 | public static function get_job_type( ?object $job ): ?object {
96 | $job_type = new JobType();
97 |
98 | $columns = 'id, name, slug';
99 | return $job_type->get( (int) $job->job_type_id, $columns );
100 | }
101 |
102 | /**
103 | * Get if job is a remote job or not.
104 | *
105 | * We'll fetch this from job_type_id.
106 | * If job type is for remote, then it's a remote job.
107 | *
108 | * @param object $job_type
109 | * @return boolean
110 | */
111 | public static function get_is_remote( ?object $job_type ): bool {
112 | if ( empty( $job_type ) ) {
113 | return false;
114 | }
115 |
116 | return $job_type->slug === 'remote';
117 | }
118 |
119 | /**
120 | * Get company of a job.
121 | *
122 | * @since 0.3.0
123 | *
124 | * @param object $job
125 | *
126 | * @return null | array
127 | */
128 | public static function get_job_company( ?object $job ): ?array {
129 | if ( empty( $job->company_id ) ) {
130 | return null;
131 | }
132 |
133 | $user = get_user_by( 'id', $job->company_id );
134 |
135 | if ( empty( $user ) ) {
136 | return null;
137 | }
138 |
139 | return [
140 | 'id' => $job->company_id,
141 | 'name' => $user->display_name,
142 | 'avatar_url' => get_avatar_url( $user->ID ),
143 | ];
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/includes/Jobs/JobStatus.php:
--------------------------------------------------------------------------------
1 | deleted_at ) ) {
42 | return self::TRASHED;
43 | }
44 |
45 | if ( $job->is_active ) {
46 | return self::PUBLISHED;
47 | }
48 |
49 | return self::DRAFT;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/includes/Jobs/JobType.php:
--------------------------------------------------------------------------------
1 | (int) $job_type->id,
33 | 'name' => $job_type->name,
34 | 'slug' => $job_type->slug,
35 | 'description' => $job_type->description,
36 | 'created_at' => $job_type->created_at,
37 | 'updated_at' => $job_type->updated_at,
38 | ];
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/includes/REST/Api.php:
--------------------------------------------------------------------------------
1 | class_map = apply_filters(
32 | 'jobplace_rest_api_class_map',
33 | [
34 | \Akash\JobPlace\REST\JobTypesController::class,
35 | \Akash\JobPlace\REST\JobsController::class,
36 | \Akash\JobPlace\REST\CompaniesController::class,
37 | ]
38 | );
39 |
40 | // Init REST API routes.
41 | add_action( 'rest_api_init', array( $this, 'register_rest_routes' ), 10 );
42 | }
43 |
44 | /**
45 | * Register REST API routes.
46 | *
47 | * @since 0.3.0
48 | *
49 | * @return void
50 | */
51 | public function register_rest_routes(): void {
52 | foreach ( $this->class_map as $controller ) {
53 | $this->$controller = new $controller();
54 | $this->$controller->register_routes();
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/includes/REST/CompaniesController.php:
--------------------------------------------------------------------------------
1 | namespace, '/' . $this->base . '/dropdown//',
40 | [
41 | [
42 | 'methods' => WP_REST_Server::READABLE,
43 | 'callback' => [ $this, 'get_items_dropdown' ],
44 | 'permission_callback' => [ $this, 'check_permission' ],
45 | ],
46 | ]
47 | );
48 | }
49 |
50 | /**
51 | * Retrieves a collection of companies for dropdown.
52 | *
53 | * @since 0.5.0
54 | *
55 | * @param WP_REST_Request $request Full details about the request.
56 | * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
57 | */
58 | public function get_items_dropdown( $request ): ?WP_REST_Response {
59 | //phpcs:disable
60 | $query = new WP_User_Query(
61 | [
62 | 'meta_key' => 'user_type',
63 | 'meta_value' => 'company',
64 | 'fields' => [
65 | 'ID',
66 | 'user_login',
67 | 'user_email',
68 | 'display_name',
69 | ],
70 | ]
71 | );
72 | //phpcs:enable
73 |
74 | $users = [];
75 |
76 | foreach ( $query->results as $user ) {
77 | $users[] = $this->prepare_dropdown_response_for_collection( $user, $request );
78 | }
79 |
80 | return rest_ensure_response( $users );
81 | }
82 |
83 | /**
84 | * Prepare dropdown response for collection.
85 | *
86 | * @since 0.5.0
87 | *
88 | * @param WP_User $item User object.
89 | * @param WP_REST_Request $request Request object.
90 | *
91 | * @return array
92 | */
93 | public function prepare_dropdown_response_for_collection( $item, $request ) {
94 | $user = $item;
95 | $data = [];
96 | $data['id'] = $user->id;
97 | $data['name'] = $user->display_name;
98 | $data['email'] = $user->user_email;
99 | $data['username'] = $user->user_login;
100 |
101 | return $data;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/includes/Setup/Installer.php:
--------------------------------------------------------------------------------
1 | add_version();
24 |
25 | // Register and create tables.
26 | $this->register_table_names();
27 | $this->create_tables();
28 |
29 | // Make this administrator user as company.
30 | $this->make_admin_as_company();
31 |
32 | // Run the database seeders.
33 | $seeder = new \Akash\JobPlace\Databases\Seeder\Manager();
34 | $seeder->run();
35 | }
36 |
37 | /**
38 | * Make administrator user as company.
39 | *
40 | * @since 0.5.0
41 | *
42 | * @return void
43 | */
44 | private function make_admin_as_company() {
45 | update_user_meta( get_current_user_id(), 'user_type', 'company' );
46 | }
47 |
48 | /**
49 | * Register table names.
50 | *
51 | * @since 0.3.0
52 | *
53 | * @return void
54 | */
55 | private function register_table_names(): void {
56 | global $wpdb;
57 |
58 | // Register the tables to wpdb global.
59 | $wpdb->jobplace_job_types = $wpdb->prefix . 'jobplace_job_types';
60 | $wpdb->jobplace_jobs = $wpdb->prefix . 'jobplace_jobs';
61 | }
62 |
63 | /**
64 | * Add time and version on DB.
65 | *
66 | * @since 0.3.0
67 | * @since 0.4.1 Fixed #11 - Version Naming.
68 | *
69 | * @return void
70 | */
71 | public function add_version(): void {
72 | $installed = get_option( Keys::JOB_PLACE_INSTALLED );
73 |
74 | if ( ! $installed ) {
75 | update_option( Keys::JOB_PLACE_INSTALLED, time() );
76 | }
77 |
78 | update_option( Keys::JOB_PLACE_VERSION, JOB_PLACE_VERSION );
79 | }
80 |
81 | /**
82 | * Create necessary database tables.
83 | *
84 | * @since JOB_PLACE_
85 | *
86 | * @return void
87 | */
88 | public function create_tables() {
89 | if ( ! function_exists( 'dbDelta' ) ) {
90 | require_once ABSPATH . 'wp-admin/includes/upgrade.php';
91 | }
92 |
93 | // Run the database table migrations.
94 | \Akash\JobPlace\Databases\Migrations\JobTypeMigration::migrate();
95 | \Akash\JobPlace\Databases\Migrations\JobsMigration::migrate();
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/includes/Traits/InputSanitizer.php:
--------------------------------------------------------------------------------
1 | ID ) ) {
39 | $user_type = get_user_meta( $user->ID, 'user_type', true );
40 | } else {
41 | $user_type = '';
42 | }
43 | ?>
44 |
58 |
2 |
10 |
11 |
12 |
13 |
14 |
15 | ./tests/phpunit/
16 | ./tests/phpunit/wp-config.php
17 |
18 |
19 |
20 |
26 |
27 | inc
28 |
29 |
30 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('autoprefixer'),
4 | require('tailwindcss'),
5 | require('cssnano'),
6 | ],
7 | };
8 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { HashRouter, Routes, Route } from 'react-router-dom';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import Header from './components/layout/Header';
10 | import routes from './routes';
11 |
12 | const App = () => {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 | {routes.map((route, index) => (
20 | }
24 | />
25 | ))}
26 |
27 |
28 |
29 | );
30 | };
31 |
32 | export default App;
33 |
--------------------------------------------------------------------------------
/src/blocks/header/block.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/block.json",
3 | "apiVersion": 2,
4 | "title": "ReactKit Header",
5 | "name": "wrc/header",
6 | "category": "formatting",
7 | "attributes": {
8 | "title": {
9 | "type": "string",
10 | "default": "WP React Kit Text Header"
11 | },
12 | "description": {
13 | "type": "string",
14 | "default": "WP React Kit Description"
15 | },
16 | "bgColor": {
17 | "type": "string",
18 | "default": "#f5f5f5"
19 | },
20 | "padding": {
21 | "type": "object",
22 | "default": {
23 | "top": "10px",
24 | "left": "30px",
25 | "right": "30px",
26 | "bottom": "10px"
27 | }
28 | }
29 | },
30 | "example": {
31 | "attributes": {
32 | "title": "WP React Kit Text Header",
33 | "description": "WP React Kit Description",
34 | "bgColor": "#f5f5f5",
35 | "padding": {
36 | "top": "10px",
37 | "left": "30px",
38 | "right": "30px",
39 | "bottom": "10px"
40 | }
41 | }
42 | },
43 | "supports": {
44 | "html": false
45 | },
46 | "editorScript": "file:./index.js",
47 | "style": "file:./style-index.css"
48 | }
49 |
--------------------------------------------------------------------------------
/src/blocks/header/edit.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies.
3 | */
4 | import { __ } from "@wordpress/i18n";
5 | import { InspectorControls, RichText, useBlockProps } from "@wordpress/block-editor";
6 | import { PanelBody, ColorPicker, __experimentalBoxControl as BoxControl } from '@wordpress/components';
7 |
8 | /**
9 | * Internal dependencies.
10 | */
11 | import "./editor.scss";
12 |
13 | /**
14 | * The edit function describes the structure of your block in the context of the
15 | * editor. This represents what the editor will render when the block is used.
16 | *
17 | * @see https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#edit
18 | * @return {WPElement} Element to render.
19 | */
20 | export default function Edit({ attributes, setAttributes }) {
21 | const { title, description, bgColor, padding } = attributes;
22 |
23 | return (
24 |
31 |
setAttributes({ title })}
37 | />
38 |
39 | setAttributes({ description })}
45 | />
46 |
47 |
48 |
52 | setAttributes({ bgColor })}
55 | enableAlpha
56 | defaultValue={bgColor}
57 | clearable={false}
58 | />
59 |
60 |
64 | setAttributes({ padding })}
68 | />
69 |
70 |
71 |
72 | );
73 | }
74 |
--------------------------------------------------------------------------------
/src/blocks/header/editor.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ManiruzzamanAkash/wp-react-kit/17ee15156914638201cd70c6c6c7af2b2297805b/src/blocks/header/editor.scss
--------------------------------------------------------------------------------
/src/blocks/header/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Registers a new block provided a unique name and an object defining its behavior.
3 | *
4 | * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
5 | */
6 | import { registerBlockType } from '@wordpress/blocks';
7 |
8 | /**
9 | * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files.
10 | * All files containing `style` keyword are bundled together. The code used
11 | * gets applied both to the front of your site and to the editor.
12 | *
13 | * @see https://www.npmjs.com/package/@wordpress/scripts#using-css
14 | */
15 | import './style.scss';
16 |
17 | /**
18 | * Internal dependencies
19 | */
20 | import edit from './edit';
21 | import save from './save';
22 | import json from './block.json';
23 |
24 | const { name, ...settings } = json;
25 |
26 | /**
27 | * Every block starts by registering a new block type definition.
28 | *
29 | * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
30 | */
31 | registerBlockType(name, {
32 | ...settings,
33 |
34 | /**
35 | * @see ./edit.js
36 | */
37 | edit,
38 |
39 | /**
40 | * @see ./save.js
41 | */
42 | save,
43 | });
44 |
--------------------------------------------------------------------------------
/src/blocks/header/save.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * The save function defines the way in which the different attributes should
3 | * be combined into the final markup, which is then serialized by the block
4 | * editor into `post_content`.
5 | *
6 | * @see https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#save
7 | * @return {WPElement} Element to render.
8 | */
9 | export default function save() {
10 | return null;
11 | }
--------------------------------------------------------------------------------
/src/blocks/header/style.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ManiruzzamanAkash/wp-react-kit/17ee15156914638201cd70c6c6c7af2b2297805b/src/blocks/header/style.scss
--------------------------------------------------------------------------------
/src/components/badge/Badge.stories.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentStory, ComponentMeta } from '@storybook/react';
2 |
3 | import Badge from './Badge';
4 | import { faPlusCircle } from '@fortawesome/free-solid-svg-icons';
5 |
6 | export default {
7 | title: 'Common/Badge',
8 | component: Badge,
9 | } as ComponentMeta;
10 |
11 | const Template: ComponentStory = (args) => ;
12 |
13 | export const DefaultBadge = Template.bind({});
14 | DefaultBadge.args = {
15 | text: 'Simple Badge',
16 | type: 'default',
17 | };
18 |
19 | export const PrimaryBadge = Template.bind({});
20 | PrimaryBadge.args = {
21 | text: 'Primary Badge',
22 | type: 'primary',
23 | };
24 |
25 | export const WarningBadge = Template.bind({});
26 | WarningBadge.args = {
27 | text: 'Warning Badge',
28 | type: 'warning',
29 | };
30 |
31 | export const IconBadge = Template.bind({});
32 | IconBadge.args = {
33 | text: 'Icon Badge',
34 | type: 'primary',
35 | icon: faPlusCircle,
36 | hasIcon: true,
37 | customClass: 'border border-solid p-2 border-gray',
38 | };
39 |
--------------------------------------------------------------------------------
/src/components/badge/Badge.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { useEffect, useState } from '@wordpress/element';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import SvgCircleDefaultIcon from '../svg/SvgCircleDefaultIcon';
10 | import SvgCirclePrimaryIcon from '../svg/SvgCirclePrimaryIcon';
11 | import SvgCircleSuccessIcon from '../svg/SvgCircleSuccessIcon';
12 | import SvgCircleWarningIcon from '../svg/SvgCircleWarningIcon';
13 |
14 | export interface BadgeProps {
15 | /**
16 | * Badge text.
17 | */
18 | text: string | JSX.Element;
19 |
20 | /**
21 | * Badge type - primary, success, warning, default.
22 | */
23 | type?: string;
24 |
25 | /**
26 | * Custom class for badge area.
27 | */
28 | customClass?: string;
29 |
30 | /**
31 | * Will icon show or not.
32 | */
33 | hasIcon?: boolean;
34 |
35 | /**
36 | * Icon if any icon shows.
37 | */
38 | icon?: undefined | JSX.Element;
39 | }
40 |
41 | /**
42 | * Get Badge Default Props.
43 | */
44 | export const BadgeDefaultProps = {
45 | text: '',
46 | type: 'default',
47 | customClass: '',
48 | hasIcon: false,
49 | icon: undefined,
50 | };
51 |
52 | const Badge = (props: BadgeProps) => {
53 | const { text, type, customClass, hasIcon, icon } = props;
54 |
55 | const [svgIcon, setSvgIcon] = useState(<>>);
56 |
57 | useEffect(() => {
58 | if (typeof icon !== 'undefined' && icon !== <>>) {
59 | setSvgIcon(icon);
60 | }
61 |
62 | switch (type) {
63 | case 'success':
64 | setSvgIcon(SvgCircleSuccessIcon);
65 | break;
66 |
67 | case 'warning':
68 | setSvgIcon(SvgCircleWarningIcon);
69 | break;
70 |
71 | case 'primary':
72 | setSvgIcon(SvgCirclePrimaryIcon);
73 | break;
74 |
75 | case 'default':
76 | setSvgIcon(SvgCircleDefaultIcon);
77 | break;
78 |
79 | default:
80 | setSvgIcon(SvgCirclePrimaryIcon);
81 | break;
82 | }
83 | }, [type]);
84 |
85 | const getBadgeClassName = () => {
86 | let className =
87 | 'rounded-md ml-0 px-3 text-center py-2 w-auto min-w-[80px] whitespace-nowrap inline-block';
88 |
89 | switch (type) {
90 | case 'success':
91 | className += ' bg-success-lite';
92 | break;
93 |
94 | case 'warning':
95 | className += ' bg-warning-lite';
96 | break;
97 |
98 | case 'default':
99 | className += ' bg-gray-liter';
100 | break;
101 |
102 | default:
103 | className += ' bg-white';
104 | break;
105 | }
106 |
107 | if (typeof customClass !== 'undefined' && customClass.length) {
108 | className += ` ${customClass}`;
109 | }
110 |
111 | return className;
112 | };
113 |
114 | return (
115 |
116 | {hasIcon && {svgIcon}}
117 |
118 | {text}
119 |
120 | );
121 | };
122 |
123 | Badge.defaultProps = BadgeDefaultProps;
124 |
125 | export default Badge;
126 |
--------------------------------------------------------------------------------
/src/components/badge/__tests__/Badge.test.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { render, screen } from '@testing-library/react';
5 |
6 | /**
7 | * Internal dependencies.
8 | */
9 | import Badge from '../Badge';
10 | import SvgCirclePrimaryIcon from '../../svg/SvgCirclePrimaryIcon';
11 |
12 | const props = {
13 | text: 'Simple Badge',
14 | type: 'primary',
15 | };
16 |
17 | const renderBadge = (customProps = {}) => {
18 | return render();
19 | };
20 |
21 | describe('Badge', () => {
22 | it('should render without crashing', () => {
23 | renderBadge();
24 | const badgeText = screen.getByText(props.text);
25 | expect(badgeText).toBeInTheDocument();
26 | });
27 |
28 | // Check hasIcon prop and svgIcon is given or not.
29 | it('should render svg icon if hasIcon is true', () => {
30 | const { container } = renderBadge({
31 | hasIcon: true,
32 | svgIcon: SvgCirclePrimaryIcon,
33 | });
34 |
35 | const svgIcon = container.querySelector('svg');
36 | expect(svgIcon).toBeInTheDocument();
37 | });
38 |
39 | // Check if customClass is given and the class is present or not.
40 | it('should render with custom class if it has been given', () => {
41 | const customClass = 'bg-red-500';
42 |
43 | const { container } = renderBadge({
44 | customClass,
45 | });
46 |
47 | expect(container.firstChild).toHaveClass(customClass);
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/src/components/button/Button.stories.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentStory, ComponentMeta } from '@storybook/react';
2 | import { faPlus } from '@fortawesome/free-solid-svg-icons';
3 |
4 | import Button from './Button';
5 |
6 | // More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
7 | export default {
8 | title: 'Common/Button',
9 | component: Button,
10 | // More on argTypes: https://storybook.js.org/docs/react/api/argtypes
11 | argTypes: {
12 | backgroundColor: { control: 'color' },
13 | },
14 | } as ComponentMeta;
15 |
16 | // More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
17 | const Template: ComponentStory = (args) => ;
18 |
19 | export const Primary = Template.bind({});
20 | // More on args: https://storybook.js.org/docs/react/writing-stories/args
21 | Primary.args = {
22 | type: 'primary',
23 | text: 'Button',
24 | };
25 |
26 | export const Secondary = Template.bind({});
27 | Secondary.args = {
28 | type: 'default',
29 | text: 'Default Button',
30 | };
31 |
32 | export const Warning = Template.bind({});
33 | Warning.args = {
34 | type: 'warning',
35 | text: 'Warning Button',
36 | };
37 |
38 | export const Large = Template.bind({});
39 | Large.args = {
40 | text: 'Large Button',
41 | };
42 |
43 | export const Small = Template.bind({});
44 | Small.args = {
45 | text: 'Small Button',
46 | };
47 |
48 | export const CustomButton = Template.bind({});
49 | CustomButton.args = {
50 | text: 'Custom Button Example',
51 | type: 'default',
52 | buttonCustomClass:
53 | 'bg-red-400 hover:bg-white hover:text-black text-white hover:bg-gray-liter',
54 | };
55 |
56 | export const OutlineButton = Template.bind({});
57 | OutlineButton.args = {
58 | text: 'Outline Button',
59 | type: 'primary',
60 | outline: true,
61 | };
62 |
63 | export const IconButtonRight = Template.bind({});
64 | IconButtonRight.args = {
65 | text: 'Icon Button Right',
66 | icon: faPlus,
67 | iconPosition: 'right',
68 | type: 'primary',
69 | outline: true,
70 | };
71 |
72 | export const IconButtonLeft = Template.bind({});
73 | IconButtonLeft.args = {
74 | text: 'Icon Button Left',
75 | icon: faPlus,
76 | iconPosition: 'left',
77 | type: 'primary',
78 | outline: true,
79 | };
80 |
--------------------------------------------------------------------------------
/src/components/dashboard/Dashboard.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { __ } from '@wordpress/i18n';
5 | import { useNavigate } from 'react-router-dom';
6 |
7 | /**
8 | * Internal dependencies.
9 | */
10 | import Button from '../button/Button';
11 |
12 | const Dashboard = () => {
13 | const navigate = useNavigate();
14 | return (
15 |
16 |
17 |
18 | {__('Dashboard', 'jobplace')}
19 |
20 |
21 | {__('Edit Dashboard component at ', 'jobplace')}
22 | src/components/Dashboard.jsx
23 |
24 |
25 |
26 |
33 |
34 |
35 | );
36 | };
37 |
38 | export default Dashboard;
39 |
--------------------------------------------------------------------------------
/src/components/date-picker/DatePicker.stories.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentStory, ComponentMeta } from '@storybook/react';
2 |
3 | import DatePicker from './DatePicker';
4 |
5 | export default {
6 | title: 'Common/DatePicker',
7 | component: DatePicker,
8 | } as ComponentMeta;
9 |
10 | const Template: ComponentStory = (args) => (
11 |
12 | );
13 |
14 | export const DefaultDatePicker = Template.bind({});
15 | DefaultDatePicker.args = {
16 | type: 'date-picker',
17 | };
18 |
19 | export const DateRangePicker = Template.bind({});
20 | DateRangePicker.args = {
21 | type: 'date-range-picker',
22 | };
23 |
--------------------------------------------------------------------------------
/src/components/date-picker/DatePickerData.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { __ } from '@wordpress/i18n';
5 | import { add, endOfWeek, startOfMonth, startOfYear, sub } from 'date-fns';
6 |
7 | /**
8 | * Internal dependencies
9 | */
10 | import {
11 | getCurrentDate,
12 | getSubOrAddDaysDate,
13 | getFormattedDate,
14 | } from '../../../utils/DateHelper';
15 |
16 | export interface IDatePickerData {
17 | /**
18 | * Date Range option name.
19 | */
20 | name: string;
21 |
22 | /**
23 | * Date Range option slug.
24 | */
25 | value: string;
26 |
27 | /**
28 | * Start date of the date range.
29 | */
30 | startDate: string;
31 |
32 | /**
33 | * End date of the date range.
34 | */
35 | endDate: string;
36 | }
37 |
38 | export default function DatePickerData(): Array {
39 | const currentDate = getCurrentDate();
40 |
41 | return [
42 | {
43 | name: __('Today', 'cp'),
44 | value: 'today',
45 | startDate: currentDate,
46 | endDate: currentDate,
47 | },
48 | {
49 | name: __('Yesterday', 'cp'),
50 | value: 'yesterday',
51 | startDate: getSubOrAddDaysDate('sub', 1),
52 | endDate: getSubOrAddDaysDate('sub', 1),
53 | },
54 | {
55 | name: __('This Month', 'cp'),
56 | value: 'thisMonth',
57 | startDate: getFormattedDate(startOfMonth(new Date())),
58 | endDate: getFormattedDate(
59 | sub(add(startOfMonth(new Date()), { months: 1 }), {
60 | seconds: 1,
61 | })
62 | ),
63 | },
64 | {
65 | name: __('Last 7 Days', 'cp'),
66 | value: 'last7Days',
67 | startDate: getSubOrAddDaysDate('sub', 7),
68 | endDate: currentDate,
69 | },
70 | {
71 | name: __('Last 30 Days', 'cp'),
72 | value: 'last30Days',
73 | startDate: getSubOrAddDaysDate('sub', 30),
74 | endDate: currentDate,
75 | },
76 | {
77 | name: __('Last Week', 'cp'),
78 | value: 'lastWeek',
79 | startDate: getFormattedDate(
80 | sub(endOfWeek(new Date()), { days: 14 })
81 | ),
82 | endDate: getFormattedDate(sub(endOfWeek(new Date()), { days: 8 })),
83 | },
84 | {
85 | name: __('Last Month', 'cp'),
86 | value: 'lastMonth',
87 | startDate: getFormattedDate(
88 | sub(startOfMonth(new Date()), { months: 1 })
89 | ),
90 | endDate: getFormattedDate(
91 | sub(startOfMonth(new Date()), { days: 1 })
92 | ),
93 | },
94 | {
95 | name: __('Last Quarter', 'cp'),
96 | value: 'lastQuarter',
97 | startDate: getFormattedDate(
98 | sub(startOfMonth(new Date()), { months: 3 })
99 | ),
100 | endDate: getFormattedDate(
101 | sub(startOfMonth(new Date()), { days: 1 })
102 | ),
103 | },
104 | {
105 | name: __('Last Year', 'cp'),
106 | value: 'lastYear',
107 | startDate: getFormattedDate(
108 | sub(startOfYear(new Date()), { years: 1 })
109 | ),
110 | endDate: getFormattedDate(
111 | sub(startOfYear(new Date()), { days: 1 })
112 | ),
113 | },
114 | {
115 | name: __('Last 6 months', 'cp'),
116 | value: 'last6Months',
117 | startDate: getFormattedDate(
118 | sub(startOfMonth(new Date()), { months: 6 })
119 | ),
120 | endDate: getFormattedDate(
121 | sub(startOfMonth(new Date()), { days: 1 })
122 | ),
123 | },
124 | {
125 | name: __('Last 12 months', 'cp'),
126 | value: 'last12Months',
127 | startDate: getFormattedDate(
128 | sub(startOfMonth(new Date()), { months: 12 })
129 | ),
130 | endDate: getFormattedDate(
131 | sub(startOfMonth(new Date()), { days: 1 })
132 | ),
133 | },
134 | {
135 | name: __('All Time', 'cp'),
136 | value: 'allTime',
137 | startDate: '-1',
138 | endDate: '-1',
139 | },
140 | {
141 | name: __('Custom Range', 'cp'),
142 | value: 'customRange',
143 | startDate: '',
144 | endDate: '',
145 | },
146 | ];
147 | }
148 |
--------------------------------------------------------------------------------
/src/components/inputs/Input.stories.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentStory, ComponentMeta } from '@storybook/react';
2 |
3 | import Input from './Input';
4 |
5 | export default {
6 | title: 'Common/Input/Input',
7 | component: Input,
8 | } as ComponentMeta;
9 |
10 | const Template: ComponentStory = (args) => ;
11 |
12 | export const DefaultInput = Template.bind({});
13 | DefaultInput.args = {
14 | type: 'text',
15 | };
16 |
17 | export const TextareaInput = Template.bind({});
18 | TextareaInput.args = {
19 | type: 'textarea',
20 | };
21 |
--------------------------------------------------------------------------------
/src/components/inputs/InputLabel.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies.
3 | */
4 | import { twMerge } from 'tailwind-merge';
5 |
6 | interface IInputLabel {
7 | /**
8 | * Input level children props.
9 | */
10 | children: React.ReactNode | string;
11 |
12 | /**
13 | * Input html for attribute.
14 | */
15 | htmlFor?: string;
16 |
17 | /**
18 | * Custom Class name.
19 | */
20 | className?: string;
21 |
22 | /**
23 | * Label tooltip (if has any)
24 | */
25 | tooltip?: string | React.ReactNode | undefined;
26 | }
27 |
28 | export default function InputLabel({
29 | children = <>>,
30 | htmlFor,
31 | className = '',
32 | }: IInputLabel) {
33 | return (
34 |
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/inputs/Select2Input.stories.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentStory, ComponentMeta } from '@storybook/react';
2 |
3 | import Select2Input from './Select2Input';
4 |
5 | export default {
6 | title: 'Common/Input/Select2Input',
7 | component: Select2Input,
8 | } as ComponentMeta;
9 |
10 | const Template: ComponentStory = (args) => (
11 |
12 | );
13 |
14 | export const DefaultSelect2Input = Template.bind({});
15 | DefaultSelect2Input.args = {
16 | options: [
17 | {
18 | label: 'Apple',
19 | value: 'apple',
20 | },
21 | {
22 | label: 'Banana',
23 | value: 'banana',
24 | },
25 | ],
26 | placeholder: 'Select Fruits...',
27 | };
28 |
--------------------------------------------------------------------------------
/src/components/inputs/Select2Input.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import Select from 'react-select';
5 | import makeAnimated from 'react-select/animated';
6 | import { __ } from '@wordpress/i18n';
7 |
8 | /**
9 | * Internal dependencies
10 | */
11 | import {
12 | formatSelect2Data,
13 | getSelectedOption,
14 | } from '../../utils/Select2Helper';
15 |
16 | export interface Select2SingleRow {
17 | /**
18 | * Select2 option label.
19 | */
20 | label: string;
21 |
22 | /**
23 | * Select2 option value.
24 | */
25 | value: string;
26 | }
27 |
28 | export interface ISelect2Input {
29 | /**
30 | * Select2 options.
31 | */
32 | options: Array;
33 |
34 | /**
35 | * Placeholder text.
36 | */
37 | placeholder?: string;
38 |
39 | /**
40 | * Is Multi-Select or not.
41 | */
42 | isMulti?: boolean;
43 |
44 | /**
45 | * Default selected value.
46 | */
47 | defaultValue?: any;
48 |
49 | /**
50 | * On change select2 input.
51 | */
52 | onChange?: (val: any) => void;
53 | }
54 |
55 | const Select2Input = (props: ISelect2Input) => {
56 | const { options, isMulti, placeholder, defaultValue, onChange } = props;
57 |
58 | const animatedComponents = makeAnimated();
59 |
60 | const styles = {
61 | container: (base: any) => ({
62 | ...base,
63 | width: '100%',
64 | height: '100%',
65 | margin: '0',
66 | padding: '0',
67 | border: 'none',
68 | borderRadius: '0',
69 | boxShadow: 'none',
70 | backgroundColor: 'transparent',
71 | '&:hover': {
72 | border: 'none',
73 | boxShadow: 'none',
74 | backgroundColor: 'transparent',
75 | },
76 | }),
77 | control: (base: any, state: any) => ({
78 | ...base,
79 | borderColor: state.isFocused ? '#787878' : '#dddddd',
80 | boxShadow: 'none',
81 | '&:hover': {
82 | borderColor: '#787878',
83 | },
84 | height: '28px',
85 | minHeight: '36px',
86 | fontSize: '12px',
87 | }),
88 | menuList: (base: any) => ({
89 | ...base,
90 | width: 'auto',
91 | minWidth: '200px',
92 | backgroundColor: '#FFFFFF',
93 | fontSize: '12px',
94 | }),
95 | multiValueLabel: (base: any) => ({
96 | ...base,
97 | color: '#787878',
98 | fontWeight: 'normal',
99 | }),
100 | valueContainer: (base: any) => ({
101 | ...base,
102 | padding: '0',
103 | cursor: 'pointer',
104 | paddingLeft: '12px',
105 | }),
106 | indicatorSeparator: (base: any) => ({
107 | ...base,
108 | marginLeft: '10px',
109 | }),
110 | };
111 |
112 | return (
113 |