Skip to content

Commit

Permalink
v7.0.0
Browse files Browse the repository at this point in the history
Rewrote everything for React 19. Dep updates.
  • Loading branch information
neonexus authored Jan 24, 2025
2 parents 1747059 + cda0c67 commit 95c09d0
Show file tree
Hide file tree
Showing 31 changed files with 2,041 additions and 1,241 deletions.
1 change: 0 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,6 @@
],
"react/destructuring-assignment": "off",
"react/forbid-component-props": "off",
"react/forbid-prop-types": "off",
"react/jsx-handler-names": "off",
"react/jsx-indent-props": "off",
"react/jsx-max-depth": "off",
Expand Down
19 changes: 18 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

# [v7.0.0](https://github.com/neonexus/sails-react-bootstrap-webpack/compare/v6.1.0...v7.0.0) (2025-01-23)
### Features

* Updated to React 19.
* Rewrote most (nearly all) custom React elements to work with v19 (due to removal of `PropTypes`).
* Fixed (read: silenced) SASS deprecation warnings in Webpack config.
* Fixed security audits.
* Updated dependencies.
* Set minimum Node version to v22.13.

### Breaking Changes

* Because of the upgrade to React 19, there are fairly major changes to the custom React elements.

# [v6.1.0](https://github.com/neonexus/sails-react-bootstrap-webpack/compare/v6.0.1...v6.1.0) (2024-11-18)
### Features

Expand All @@ -17,10 +31,13 @@
# [v6.0.0](https://github.com/neonexus/sails-react-bootstrap-webpack/compare/v5.3.4...v6.0.0) (2024-11-06)
### Features

* Removed unneeded index.jsx.
* Made self-update a little smarter.
* Updated dependencies.

### Breaking Changes

* Removed unneeded index.jsx.

# [v5.3.4](https://github.com/neonexus/sails-react-bootstrap-webpack/compare/v5.3.3...v5.3.4) (2024-05-03)
### Features

Expand Down
31 changes: 22 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# sails-react-bootstrap-webpack

[![Sails version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fneonexus%2Fsails-react-bootstrap-webpack%2Fv6.1.0%2Fpackage.json&query=%24.dependencies.sails&label=Sails&logo=sailsdotjs)](https://sailsjs.com)
[![React version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fneonexus%2Fsails-react-bootstrap-webpack%2Fv6.1.0%2Fpackage.json&query=%24.devDependencies.react&label=React&logo=react)](https://react.dev)
[![Bootstrap version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fneonexus%2Fsails-react-bootstrap-webpack%2Fv6.1.0%2Fpackage.json&query=%24.devDependencies.bootstrap&label=Bootstrap&logo=bootstrap&logoColor=white)](https://getbootstrap.com)
[![Webpack version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fneonexus%2Fsails-react-bootstrap-webpack%2Fv6.1.0%2Fpackage.json&query=%24.devDependencies.webpack&label=Webpack&logo=webpack)](https://webpack.js.org)
[![Sails version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fneonexus%2Fsails-react-bootstrap-webpack%2Fv7.0.0%2Fpackage.json&query=%24.dependencies.sails&label=Sails&logo=sailsdotjs)](https://sailsjs.com)
[![React version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fneonexus%2Fsails-react-bootstrap-webpack%2Fv7.0.0%2Fpackage.json&query=%24.devDependencies.react&label=React&logo=react)](https://react.dev)
[![Bootstrap version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fneonexus%2Fsails-react-bootstrap-webpack%2Fv7.0.0%2Fpackage.json&query=%24.devDependencies.bootstrap&label=Bootstrap&logo=bootstrap&logoColor=white)](https://getbootstrap.com)
[![Webpack version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fneonexus%2Fsails-react-bootstrap-webpack%2Fv7.0.0%2Fpackage.json&query=%24.devDependencies.webpack&label=Webpack&logo=webpack)](https://webpack.js.org)

Latest version only:
[![FOSSA License Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fneonexus%2Fsails-react-bootstrap-webpack.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fneonexus%2Fsails-react-bootstrap-webpack?ref=badge_shield)
Expand All @@ -21,9 +21,13 @@ A virtual start-up in a box!

NOTE: You will need access to a MySQL / MariaDB database for the quick setup. If you want to use a different datastore, you'll need to configure it manually.

[Aiven.io](https://aiven.io) has FREE (no CC required) secure MySQL (5 GB), and Redis (1 GB). Both require use of SSL, and can be restricted to specified IPs. (If you are having trouble finding the
FREE instances, you need to select Digital Ocean as the cloud provider.) Use my [referral link](https://console.aiven.io/signup?referral_code=mk36ekt3wo1dvij7joon) to signup, and you'll get $100
<details>
<summary>For a free / quick option, check out...</summary>

[Aiven.io](https://aiven.io) has a no credit card required, secure MySQL (5 GB), and Redis (1 GB). Both require use of SSL, and can be restricted to specified IPs. (If you are having trouble finding
the FREE instances, you need to select Digital Ocean as the cloud provider.) Use my [referral link](https://console.aiven.io/signup?referral_code=mk36ekt3wo1dvij7joon) to signup, and you'll get $100
extra when you start a trial (trial is NOT needed for the free servers).
</details>

```shell
npx drfg neonexus/sails-react-bootstrap-webpack my-new-site
Expand Down Expand Up @@ -100,8 +104,8 @@ The `master` branch is experimental, and the [release branch](https://github.com
## Current Dependencies

* [Sails](https://sailsjs.com) **v1**
* [React](https://react.dev) **v18**
* [React Router](https://reactrouter.com) **v6**
* [React](https://react.dev) **v19**
* [React Router](https://reactrouter.com) **v7**
* [Bootstrap](https://getbootstrap.com) **v5**
* [React-Bootstrap](https://react-bootstrap.github.io) **v2**
* [Webpack](https://webpack.js.org) **v5**
Expand Down Expand Up @@ -697,7 +701,16 @@ Start said service:
sudo systemctl start crond.service
```

Edit the `crontab` to run the script at `@reboot`:
Edit the `crontab` to run the script at `@reboot` (you don't have to use `nano`, I just find it easier):

Also, notice to lack of `sudo` here. This is because we DON'T want our backend to have super-user powers! That just, would not be good... We want it running under a general user.
For better security, it should be run via a user with limited permissions.

```shell
EDITOR=nano crontab -e
```

Add this:

```shell
@reboot cd myapp; ./tmux.sh
Expand Down
2 changes: 1 addition & 1 deletion api/controllers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

Here is where all of our API actions live. A controller in this context is a folder, and an action of the controller is an individual file. Each action is using the new "actions2" style, as opposed to the classic.

See: https://sailsjs.com/documentation/concepts/actions-and-controllers
See: https://sailsjs.com/documentation/concepts/actions-and-controllers#?actions-2
2 changes: 1 addition & 1 deletion api/controllers/admin/create-user.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ module.exports = {
generatePassword: {
type: 'boolean',
defaultsTo: false,
description: 'Used to auto-generate a password for the user'
description: 'Used to auto-generate a password for the user. Negates the `password` input.'
}
},

Expand Down
6 changes: 0 additions & 6 deletions assets/src/Admin/AdminRouter.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {Component, StrictMode, Suspense, lazy} from 'react';
import PropTypes from 'prop-types';
import '../../styles/admin/admin.scss';
import {
Routes,
Expand Down Expand Up @@ -37,11 +36,6 @@ function RenderOrLogin(props) {
return null; // not ready yet
}

RenderOrLogin.propTypes = {
api: PropTypes.object.isRequired,
userContext: PropTypes.object.isRequired
};

const theApi = new api();

class AdminRouter extends Component {
Expand Down
9 changes: 4 additions & 5 deletions assets/src/Admin/Login.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Component } from 'react';
import PropTypes from 'prop-types';

import {UserConsumer} from '../data/UserContext';
import defaultAPIErrorHandler from '../data/defaultAPIErrorHandler';
Expand All @@ -10,6 +9,10 @@ class Login extends Component {
constructor(props) {
super(props);

if (!props.api) {
throw new Error('No API passed to Login.jsx.');
}

this.state = {
email: localStorage.getItem('email') || '',
password: '',
Expand Down Expand Up @@ -155,8 +158,4 @@ class Login extends Component {
}
}

Login.propTypes = {
api: PropTypes.object.isRequired
};

export default Login;
16 changes: 8 additions & 8 deletions assets/src/Admin/NavBar.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import {useState, useContext, useEffect} from 'react';
import PropTypes from 'prop-types';

import {Button, Nav, Navbar, NavDropdown} from 'react-bootstrap';
import {NavLink as ReactNavLink} from 'react-router-dom';

Expand All @@ -23,7 +21,13 @@ function scrollListener() {
}
}

function NavBar(props) {
function NavBar({handleLogout = null}) {
if (typeof handleLogout !== 'function') {
console.error('`handleLogout` is a required function for NavBar.');

return 'ERROR!';
}

const [isExpanded, setIsExpanded] = useState(false);
const user = useContext(UserContext);

Expand Down Expand Up @@ -110,7 +114,7 @@ function NavBar(props) {
user.isLoggedIn ?
<>
<Navbar.Text>Welcome, {user.info.firstName} &nbsp; &nbsp; </Navbar.Text>
<Button variant="danger" onClick={() => props.handleLogout()} size="sm" className="mt-2 mt-sm-0 mb-2 mb-sm-0">Logout</Button>
<Button variant="danger" onClick={() => handleLogout()} size="sm" className="mt-2 mt-sm-0 mb-2 mb-sm-0">Logout</Button>
</>
: null
}
Expand All @@ -125,8 +129,4 @@ function NavBar(props) {
);
}

NavBar.propTypes = {
handleLogout: PropTypes.func.isRequired
};

export default NavBar;
57 changes: 36 additions & 21 deletions assets/src/Admin/Settings/ChangePasswordModal.jsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,55 @@
import { useState } from 'react';
import PropTypes from 'prop-types';

import {useState} from 'react';
import {Button, Form, Modal, Spinner} from 'react-bootstrap';

function ChangePasswordModal(props) {
/**
* Change Password Modal
*
* @param {object} api
* @param {function} onCancel
* @param {function} onUpdate
* @param {boolean} show
*
* @returns {JSX.Element}
*/
function ChangePasswordModal({api, onCancel, onUpdate, show}) {
if (api === undefined || onCancel === undefined || onUpdate === undefined || show === undefined) {
throw new Error('`api`, `onCancel`, `onUpdate`, and `show` are required parameters for ChangePasswordModal.');
}

if (typeof onCancel !== 'function') {
throw new Error('`onCancel` must be a function in ChangePasswordModal.');
}

if (typeof onUpdate !== 'function') {
throw new Error('`onUpdate` must be a function in ChangePasswordModal.');
}

if (typeof show !== 'boolean') {
throw new Error('`show` must be a boolean in ChangePasswordModal.');
}

const [isLoading, setIsLoading] = useState(false);
const [current, setCurrent] = useState('');
const [newPass, setNewPass] = useState('');
const [confPass, setConfPass] = useState('');



function resetState() {
setCurrent('');
setNewPass('');
setConfPass('');
setIsLoading(false);
}

function onCancel() {
function onCancelHandler() {
resetState();

props.onCancel();
onCancel();
}

function onUpdate() {
function onUpdateHandler() {
setIsLoading(true);

props.api.post({
api.post({
url: '/password',
body: {
currentPassword: current,
Expand All @@ -37,7 +59,7 @@ function ChangePasswordModal(props) {
}, (body) => {
setIsLoading(false);
alert('Password updated successfully.');
props.onUpdate();
onUpdate();
resetState();
}, (err, body) => {
console.error(err);
Expand All @@ -47,8 +69,8 @@ function ChangePasswordModal(props) {
}

return (
<Modal show={props.show} backdrop="static" size="md">
<Form onSubmit={(e) => {e.preventDefault(); onUpdate();}}>
<Modal show={show} backdrop="static" size="md">
<Form onSubmit={(e) => {e.preventDefault(); onUpdateHandler();}}>
<Modal.Header>
<Modal.Title>
Change Password
Expand All @@ -68,7 +90,7 @@ function ChangePasswordModal(props) {
</Modal.Body>

<Modal.Footer className="justify-content-between">
<Button variant="secondary" onClick={onCancel} disabled={isLoading}>Cancel</Button>
<Button variant="secondary" onClick={onCancelHandler} disabled={isLoading}>Cancel</Button>
<Button variant="primary" type="submit" disabled={isLoading}>
{
(isLoading)
Expand All @@ -82,11 +104,4 @@ function ChangePasswordModal(props) {
);
}

ChangePasswordModal.propTypes = {
api: PropTypes.object.isRequired,
onCancel: PropTypes.func.isRequired,
onUpdate: PropTypes.func.isRequired,
show: PropTypes.bool.isRequired
};

export default ChangePasswordModal;
35 changes: 20 additions & 15 deletions assets/src/Admin/Settings/Disable2FAModal.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import {Button, Form, Modal} from 'react-bootstrap';
import PropTypes from 'prop-types';
import {useState} from 'react';

function Disable2FAModal(props) {
function Disable2FAModal({api, onCancel, onDisable, show}) {
if (!api || typeof api.post !== 'function') {
throw new Error("Invalid or missing 'api' prop. Expected an object with a 'post' method.");
}
if (!onCancel || typeof onCancel !== 'function') {
throw new Error("Invalid or missing 'onCancel' prop. Expected a function.");
}
if (!onDisable || typeof onDisable !== 'function') {
throw new Error("Invalid or missing 'onDisable' prop. Expected a function.");
}
if (typeof show !== 'boolean') {
throw new Error("Invalid or missing 'show' prop. Expected a boolean.");
}

const [isLoading, setIsLoading] = useState(false);
const [otp, setOTP] = useState('');
const [password, setPassword] = useState('');
Expand All @@ -13,9 +25,9 @@ function Disable2FAModal(props) {
setIsLoading(false);
}

function onCancel() {
function handleCancel() {
resetState();
props.onCancel();
onCancel();
}

function disable() {
Expand All @@ -25,7 +37,7 @@ function Disable2FAModal(props) {

setIsLoading(true);

props.api.post({
api.post({
url: '/2fa/disable',
body: {
password,
Expand All @@ -35,7 +47,7 @@ function Disable2FAModal(props) {
if (body.success) {
alert('2-factor authentication has been disabled.');
resetState();
props.onDisable();
onDisable();
} else {
alert('Unknown error.');
console.error(body);
Expand All @@ -48,7 +60,7 @@ function Disable2FAModal(props) {
}

return (
<Modal show={props.show} backdrop="static">
<Modal show={show} backdrop="static">
<Modal.Header>
<Modal.Title>
Disable 2-Factor Authentication
Expand Down Expand Up @@ -86,18 +98,11 @@ function Disable2FAModal(props) {
</Modal.Body>

<Modal.Footer className="justify-content-between">
<Button variant="secondary" onClick={onCancel} disabled={isLoading}>Cancel</Button>
<Button variant="secondary" onClick={handleCancel} disabled={isLoading}>Cancel</Button>
<Button variant="danger" onClick={disable} disabled={isLoading}>Disable</Button>
</Modal.Footer>
</Modal>
);
}

Disable2FAModal.propTypes = {
api: PropTypes.object.isRequired,
onCancel: PropTypes.func.isRequired,
onDisable: PropTypes.func.isRequired,
show: PropTypes.bool.isRequired
};

export default Disable2FAModal;
Loading

0 comments on commit 95c09d0

Please sign in to comment.