diff --git a/app/Http/Controllers/Api/V1/Settings/AccountController.php b/app/Http/Controllers/Api/V1/Settings/AccountController.php new file mode 100644 index 0000000..4ad1d34 --- /dev/null +++ b/app/Http/Controllers/Api/V1/Settings/AccountController.php @@ -0,0 +1,76 @@ +user = auth()->guard('api')->user(); + } + + /** + * Update User's login credentials + * + * @param Illuminate\Http\Request $request + * + * @return Illuminate\Http\JsonResponse + */ + public function updateCredentials(Request $request) : JsonResponse + { + $request->validate([ + 'username' => + "required|string|unique:users,username,{$this->user->id},id,deleted_at,NULL", + 'email' => + "required|email|unique:users,email,{$this->user->id},id,deleted_at,NULL" + ]); + + $this->user->username = $request->input('username'); + $this->user->email = $request->input('email'); + $this->user->update(); + + return response()->json($this->user); + } + + /** + * Update User's password + * + * @param Illuminate\Http\Request $request + * + * @return Illuminate\Http\JsonResponse + */ + public function updatePassword(Request $request) : JsonResponse + { + $request->validate([ + 'old_password' => 'required|string', + 'password' => 'required|string|confirmed|min:8|pwned:100' + ]); + + if (! Hash::check($request->input('old_password'), $this->user->password)) { + throw ValidationException::withMessages([ + 'old_password' => [trans('auth.password_mismatch')] + ]); + + return response()->json('Password was not Changed!', 422); + } + + $this->user->password = bcrypt($request->input('password')); + $this->user->update(); + + return response()->json('Password Changed!'); + } +} diff --git a/app/Http/Controllers/Api/V1/Settings/ProfileController.php b/app/Http/Controllers/Api/V1/Settings/ProfileController.php new file mode 100644 index 0000000..c1c7beb --- /dev/null +++ b/app/Http/Controllers/Api/V1/Settings/ProfileController.php @@ -0,0 +1,40 @@ +guard('api')->user(); + + $request->validate([ + 'firstname' => 'required|string|max:255', + 'lastname' => 'required|string|max:255', + + 'gender' => 'nullable|in:female,male', + 'birthdate' => + 'nullable|date:Y-m-d|before:'.now()->subYear(10)->format('Y-m-d'), + 'address' => 'nullable|string|max:510', + ]); + + $attributes = $request->all(); + unset($attributes['auth_token']); + + $user->fill($attributes); + $user->update(); + + return response()->json($user); + } +} diff --git a/resources/js/config/locale.js b/resources/js/config/locale.js index 7182278..626ab71 100644 --- a/resources/js/config/locale.js +++ b/resources/js/config/locale.js @@ -3,6 +3,7 @@ export default { 'en.actions': require('../../lang/en/actions.php'), 'en.navigation': require('../../lang/en/navigation.php'), 'en.resources': require('../../lang/en/resources.php'), + 'en.settings': require('../../lang/en/settings.php'), 'en.table': require('../../lang/en/table.php'), 'en.validation': require('../../lang/en/validation.php'), @@ -10,6 +11,7 @@ export default { 'fil.actions': require('../../lang/fil/actions.php'), 'fil.navigation': require('../../lang/fil/navigation.php'), 'fil.resources': require('../../lang/fil/resources.php'), + 'fil.settings': require('../../lang/fil/settings.php'), 'fil.table': require('../../lang/fil/table.php'), 'fil.validation': require('../../lang/fil/validation.php'), }; diff --git a/resources/js/routers/backoffice.js b/resources/js/routers/backoffice.js index 9f3290b..74e4ef6 100644 --- a/resources/js/routers/backoffice.js +++ b/resources/js/routers/backoffice.js @@ -1,4 +1,5 @@ import { Home } from '../views/__backoffice'; +import * as Settings from '../views/__backoffice/settings'; import * as Users from '../views/__backoffice/users'; export default [ @@ -8,6 +9,18 @@ export default [ component: Home, }, + { + name: 'settings.profile', + path: '/settings/profile', + component: Settings.Profile, + }, + + { + name: 'settings.account', + path: '/settings/account', + component: Settings.Account, + }, + { name: 'users.index', path: '/users', diff --git a/resources/js/views/__backoffice/Home.js b/resources/js/views/__backoffice/Home.js index 2f9c6ed..85c37a3 100755 --- a/resources/js/views/__backoffice/Home.js +++ b/resources/js/views/__backoffice/Home.js @@ -28,7 +28,6 @@ class Home extends Component { pageTitle={Lang.get('navigation.dashboard')} primaryAction={primaryAction} tabs={tabs} - breadcrumbs={[]} > There is no place like home diff --git a/resources/js/views/__backoffice/layouts/Clean.js b/resources/js/views/__backoffice/layouts/Clean.js new file mode 100644 index 0000000..4ca4947 --- /dev/null +++ b/resources/js/views/__backoffice/layouts/Clean.js @@ -0,0 +1,165 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; + +import { + CircularProgress, + CssBaseline, + Grid, + Hidden, + withStyles, +} from '@material-ui/core'; +import classNames from 'classnames'; + +import { Snackbar } from '../../../ui'; +import { LinearDeterminate } from '../../../ui/Loaders'; +import { Footer, Header, Sidebar } from '../partials'; + +function Clean(props) { + const { classes, ...other } = props; + const { history, loading, message } = props; + + const [drawerOpen, setDrawer] = useState(false); + const [localeMenuOpen, setLocaleMenu] = useState(false); + const [accountMenuOpen, setAccountMenu] = useState(false); + + const sidebarProps = Object.assign(other, { + navigate: path => history.push(path), + PaperProps: { style: { width: drawerWidth } }, + open: drawerOpen, + onClose: () => setDrawer(!drawerOpen), + }); + + const renderLoading = ( + + + + + + ); + + return ( + <> + {loading && } + +
+ + + + +
+
setDrawer(!drawerOpen)} + onLocaleMenuToggle={() => + setLocaleMenu(!localeMenuOpen) + } + onAccountMenuToggle={() => + setAccountMenu(!accountMenuOpen) + } + /> + +
+ {loading ? renderLoading : props.children} +
+
+ +
+
+ + {message && message.hasOwnProperty('type') && ( + + )} + + ); +} + +Clean.propTypes = { + classes: PropTypes.object.isRequired, + history: PropTypes.object.isRequired, + location: PropTypes.object.isRequired, + match: PropTypes.object.isRequired, + pageProps: PropTypes.object.isRequired, + + pageTitle: PropTypes.string, + loading: PropTypes.bool, + message: PropTypes.object, +}; + +Clean.defaultProps = { + pageTitle: '', + loading: false, + message: {}, +}; + +const drawerWidth = 256; + +const styles = theme => ({ + loader: { + zIndex: 9999, + }, + + root: { + display: 'flex', + position: 'relative', + minHeight: '100vh', + maxWidth: '100%', + }, + + drawer: { + drawer: { + width: drawerWidth, + flexShrink: 0, + }, + }, + + contentWrapper: { + flex: 1, + display: 'flex', + flexDirection: 'column', + overflowX: 'scroll', + }, + + content: { + flex: 1, + padding: `0 ${theme.spacing.unit}px`, + marginBottom: 75, + marginLeft: 0, + [theme.breakpoints.up('sm')]: { + marginBottom: 50, + padding: `${theme.spacing.unit}px ${theme.spacing.unit * 3}px`, + }, + }, + + contentShift: { + [theme.breakpoints.up('sm')]: { + transition: theme.transitions.create('margin', { + easing: theme.transitions.easing.easeOut, + duration: theme.transitions.duration.enteringScreen, + }), + marginLeft: drawerWidth, + }, + }, +}); + +export default withStyles(styles)(Clean); diff --git a/resources/js/views/__backoffice/layouts/Master.js b/resources/js/views/__backoffice/layouts/Master.js index aabca7e..9ffe168 100755 --- a/resources/js/views/__backoffice/layouts/Master.js +++ b/resources/js/views/__backoffice/layouts/Master.js @@ -22,18 +22,34 @@ import * as StringUtils from '../../../utils/String'; import * as UrlUtils from '../../../utils/URL'; import { Snackbar, Modal } from '../../../ui'; import { LinearDeterminate } from '../../../ui/Loaders'; -import { Header, Sidebar } from '../partials'; +import { Footer, Header, Sidebar } from '../partials'; class Master extends Component { state = { mobileOpen: false, localeMenuOpen: false, - localeMenuEl: null, accountMenuOpen: false, - accountMenuEl: null, message: {}, }; + /** + * Toggles Locale Menu + * + * @return {undefined} + */ + handleLocaleMenuToggled = () => { + this.handleNavLinkMenuToggled('localeMenuOpen'); + }; + + /** + * Toggles Account Menu + * + * @return {undefined} + */ + handleAccountMenuToggled = () => { + this.handleNavLinkMenuToggled('accountMenuOpen'); + }; + /** * Event listener that is triggered when the a nav link menu is clicked. * @@ -96,7 +112,7 @@ class Master extends Component { } render() { - const { classes, ...childProps } = this.props; + const { classes, showBreadcrumbs, ...other } = this.props; const { children, @@ -130,6 +146,76 @@ class Master extends Component { ); + const renderBreadcrumbs = ( + +
+ + {segments.length > 0 ? ( + ( + + )} + className={classes.breadcrumbItem} + > + + + ) : ( + + )} + + {segments.map((segment, key) => { + if (key + 1 === segments.length) { + return ( + + {StringUtils._uppercaseFirst(segment)} + + ); + } + + return ( + ( + + )} + className={classes.breadcrumbItem} + > + {StringUtils._uppercaseFirst(segment)} + + ); + })} + +
+
+ ); + return ( <> {loading && } @@ -140,7 +226,7 @@ class Master extends Component {