├── .editorconfig
├── .env.example
├── .gitattributes
├── .gitignore
├── .idea
├── .gitignore
├── inspectionProfiles
│ └── Project_Default.xml
├── modules.xml
├── php.xml
├── phpunit.xml
├── shaird.iml
└── vcs.xml
├── .styleci.yml
├── .vscode
└── launch.json
├── Dockerfile
├── README.md
├── VM
├── php.ini
├── supervisord.conf
└── xdebug.ini
├── app
├── Console
│ └── Kernel.php
├── Exceptions
│ └── Handler.php
├── Http
│ ├── Controllers
│ │ ├── Auth
│ │ │ ├── ConfirmPasswordController.php
│ │ │ ├── ForgotPasswordController.php
│ │ │ ├── LoginController.php
│ │ │ ├── RegisterController.php
│ │ │ ├── ResetPasswordController.php
│ │ │ └── VerificationController.php
│ │ ├── Controller.php
│ │ └── HomeController.php
│ ├── Kernel.php
│ └── Middleware
│ │ ├── Authenticate.php
│ │ ├── EncryptCookies.php
│ │ ├── PreventRequestsDuringMaintenance.php
│ │ ├── RedirectIfAuthenticated.php
│ │ ├── TrimStrings.php
│ │ ├── TrustHosts.php
│ │ ├── TrustProxies.php
│ │ └── VerifyCsrfToken.php
├── Models
│ └── User.php
├── Notifications
│ └── MailResetPasswordMail.php
└── Providers
│ ├── AppServiceProvider.php
│ ├── AuthServiceProvider.php
│ ├── BroadcastServiceProvider.php
│ ├── EventServiceProvider.php
│ └── RouteServiceProvider.php
├── artisan
├── bootstrap
├── app.php
└── cache
│ └── .gitignore
├── client
├── .env.local.example
├── .eslintrc.js
├── .gitignore
├── components
│ ├── Alert
│ │ └── Alert.tsx
│ ├── Button
│ │ └── Button.tsx
│ ├── Card
│ │ └── Card.tsx
│ ├── Form
│ │ └── FormElement.tsx
│ ├── Layout
│ │ └── Sidebar.tsx
│ ├── Navigation
│ │ ├── Footer.tsx
│ │ └── Navbar.tsx
│ ├── SEO
│ │ └── MetaTags.tsx
│ ├── Spinner
│ │ └── Spinner.tsx
│ └── Typography
│ │ └── Headers.tsx
├── config
│ └── config.tsx
├── next-env.d.ts
├── package.json
├── pages
│ ├── 404.tsx
│ ├── _app.tsx
│ ├── _document.tsx
│ ├── dashboard
│ │ └── index.tsx
│ ├── index.tsx
│ └── user
│ │ ├── email
│ │ └── verify
│ │ │ └── [userID]
│ │ │ └── [hash].tsx
│ │ ├── login.tsx
│ │ ├── password
│ │ ├── forgot.tsx
│ │ └── reset
│ │ │ └── [token].tsx
│ │ └── register.tsx
├── postcss.config.js
├── services
│ ├── Auth
│ │ └── AuthGuard.tsx
│ └── UserValidator.tsx
├── store
│ ├── actionTypes.tsx
│ ├── auth
│ │ ├── authActions.tsx
│ │ └── authReducer.tsx
│ └── store.tsx
├── styles
│ └── globals.css
├── tailwind.config.js
├── tsconfig.json
└── yarn.lock
├── composer.json
├── composer.lock
├── config
├── app.php
├── auth.php
├── broadcasting.php
├── cache.php
├── cors.php
├── database.php
├── filesystems.php
├── hashing.php
├── logging.php
├── mail.php
├── queue.php
├── sanctum.php
├── services.php
├── session.php
└── view.php
├── database
├── .gitignore
├── factories
│ └── UserFactory.php
├── migrations
│ ├── 2014_10_12_000000_create_users_table.php
│ ├── 2014_10_12_100000_create_password_resets_table.php
│ ├── 2019_08_19_000000_create_failed_jobs_table.php
│ └── 2019_12_14_000001_create_personal_access_tokens_table.php
└── seeders
│ ├── DatabaseSeeder.php
│ └── UserSeeder.php
├── docker-compose.yml
├── package.json
├── phpunit.xml
├── public
├── .htaccess
├── favicon.ico
├── index.php
├── robots.txt
└── web.config
├── resources
├── css
│ └── app.css
├── js
│ ├── app.js
│ └── bootstrap.js
├── lang
│ └── en
│ │ ├── auth.php
│ │ ├── pagination.php
│ │ ├── passwords.php
│ │ └── validation.php
└── views
│ ├── auth
│ ├── login.blade.php
│ ├── passwords
│ │ ├── confirm.blade.php
│ │ ├── email.blade.php
│ │ └── reset.blade.php
│ ├── register.blade.php
│ └── verify.blade.php
│ ├── home.blade.php
│ ├── layouts
│ └── app.blade.php
│ └── welcome.blade.php
├── routes
├── api.php
├── channels.php
├── console.php
└── web.php
├── server.php
├── storage
├── app
│ ├── .gitignore
│ └── public
│ │ └── .gitignore
├── framework
│ ├── .gitignore
│ ├── cache
│ │ ├── .gitignore
│ │ └── data
│ │ │ └── .gitignore
│ ├── sessions
│ │ └── .gitignore
│ ├── testing
│ │ └── .gitignore
│ └── views
│ │ └── .gitignore
└── logs
│ └── .gitignore
├── tests
├── CreatesApplication.php
├── Feature
│ └── ExampleTest.php
├── TestCase.php
└── Unit
│ └── ExampleTest.php
├── vm-start
├── vm-stop
└── webpack.mix.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | indent_style = space
8 | indent_size = 4
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [*.{yml,yaml}]
15 | indent_size = 2
16 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | APP_NAME=Laravel Next.js Starter Kit
2 | APP_ENV=local
3 | APP_KEY=base64:XXXXXXXXXXXXXXXX
4 | APP_DEBUG=true
5 | APP_URL=http://localhost
6 |
7 | LOG_CHANNEL=stack
8 |
9 | DB_CONNECTION=mysql
10 | DB_HOST=db
11 | DB_PORT=3306
12 | DB_DATABASE=ln_starterkit
13 | DB_USERNAME=lnstarterkit
14 | DB_PASSWORD=123456
15 |
16 | BROADCAST_DRIVER=log
17 | CACHE_DRIVER=file
18 | QUEUE_CONNECTION=sync
19 | SESSION_DRIVER=cookie
20 | SESSION_LIFETIME=120
21 | SESSION_DOMAIN=localhost
22 |
23 | REDIS_HOST=redis
24 | REDIS_PASSWORD=null
25 | REDIS_PORT=6379
26 |
27 | MAIL_MAILER=smtp
28 | MAIL_HOST=XXXXXXXXXXXXXXXX
29 | MAIL_PORT=2525
30 | MAIL_USERNAME=XXXXXXXXXXXXXXXX
31 | MAIL_PASSWORD=XXXXXXXXXXXXXXXX
32 | MAIL_ENCRYPTION=tls
33 | MAIL_FROM_ADDRESS=XXXXXXXXXXXXXXXX
34 | MAIL_FROM_NAME="${APP_NAME}"
35 |
36 | AWS_ACCESS_KEY_ID=
37 | AWS_SECRET_ACCESS_KEY=
38 | AWS_DEFAULT_REGION=
39 | AWS_BUCKET=
40 |
41 | PUSHER_APP_ID=
42 | PUSHER_APP_KEY=
43 | PUSHER_APP_SECRET=
44 | PUSHER_APP_CLUSTER=mt1
45 |
46 | MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
47 | MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
48 |
49 | SANCTUM_STATEFUL_DOMAINS=localhost:3000
50 | ADMIN_EMAIL_ADDRESS=XXXXXXXXXXXXXXXX
51 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 | *.css linguist-vendored
3 | *.scss linguist-vendored
4 | *.js linguist-vendored
5 | CHANGELOG.md export-ignore
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /public/hot
3 | /public/storage
4 | /storage/*.key
5 | /vendor
6 | .env
7 | .env.backup
8 | .phpunit.result.cache
9 | docker-compose.override.yml
10 | Homestead.json
11 | Homestead.yaml
12 | npm-debug.log
13 | yarn-error.log
14 | .env.local
15 | .idea
16 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Datasource local storage ignored files
5 | /dataSources/
6 | /dataSources.local.xml
7 | # Editor-based HTTP Client requests
8 | /httpRequests/
9 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/php.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
--------------------------------------------------------------------------------
/.idea/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.styleci.yml:
--------------------------------------------------------------------------------
1 | php:
2 | preset: laravel
3 | disabled:
4 | - no_unused_imports
5 | finder:
6 | not-name:
7 | - index.php
8 | - server.php
9 | js:
10 | finder:
11 | not-name:
12 | - webpack.mix.js
13 | css: true
14 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Listen for XDebug",
9 | "type": "php",
10 | "request": "launch",
11 | "port": 9000,
12 | "pathMappings": {
13 | "/var/www/html": "${workspaceRoot}"
14 | }
15 | },
16 | {
17 | "name": "Launch currently open script",
18 | "type": "php",
19 | "request": "launch",
20 | "program": "${file}",
21 | "cwd": "${fileDirname}",
22 | "port": 9000
23 | }
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 |
2 | # FROM ubuntu:20.04
3 | # WORKDIR /var/www/html
4 |
5 | # COPY ./ /var/www/html/
6 |
7 | # ENV DEBIAN_FRONTEND noninteractive
8 | # ENV TZ=UTC
9 |
10 | # RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
11 |
12 | # RUN apt-get update \
13 | # && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin vim\
14 | # && mkdir -p ~/.gnupg \
15 | # && echo "disable-ipv6" >> ~/.gnupg/dirmngr.conf \
16 | # && apt-key adv --homedir ~/.gnupg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys E5267A6C \
17 | # && apt-key adv --homedir ~/.gnupg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C300EE8C \
18 | # && echo "deb http://ppa.launchpad.net/ondrej/php/ubuntu focal main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \
19 | # && apt-get update \
20 | # && apt-get install -y php8.0-cli php8.0-dev \
21 | # php8.0-pgsql php8.0-sqlite3 php8.0-gd \
22 | # php8.0-curl php8.0-memcached \
23 | # php8.0-imap php8.0-mysql php8.0-mbstring \
24 | # php8.0-xml php8.0-zip php8.0-bcmath php8.0-soap \
25 | # php8.0-intl php8.0-readline \
26 | # php8.0-msgpack php8.0-igbinary php8.0-ldap \
27 | # php8.0-redis \
28 | # && php -r "readfile('http://getcomposer.org/installer');" | php -- --install-dir=/usr/bin/ --filename=composer \
29 | # && curl -sL https://deb.nodesource.com/setup_16.x | bash - \
30 | # && apt-get install -y nodejs \
31 | # && apt-get -y autoremove \
32 | # && apt-get clean \
33 | # && apt-get install -y zsh \
34 | # && apt-get install -y wget \
35 | # && apt-get install -y php-xdebug \
36 | # && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
37 | # && wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | zsh || true
38 |
39 | # RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.0
40 |
41 | # # Xdebug settings.
42 | # COPY ./VM/xdebug.ini /etc/php/8.0/mods-available/xdebug.ini
43 |
44 | # # Supervisor.
45 | # # COPY ./VM/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
46 |
47 | # # Add admin user
48 | # RUN useradd -ms /bin/bash -u 1337 admin
49 |
50 | # RUN composer update
51 |
52 | # # Make some ZSH configuration.
53 | # ENV TERM xterm
54 | # ENV ZSH_THEME agnoster
55 |
56 | # # Expose ports.
57 | # EXPOSE 8000
58 | # EXPOSE 9000
59 |
60 | # CMD ["php", "artisan", "serve", "--host=0.0.0.0"]
61 | FROM ubuntu:20.04
62 |
63 | WORKDIR /var/www/html
64 |
65 | ENV DEBIAN_FRONTEND noninteractive
66 | ENV TZ=UTC
67 |
68 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
69 |
70 | RUN apt-get update \
71 | && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin \
72 | && mkdir -p ~/.gnupg \
73 | && echo "disable-ipv6" >> ~/.gnupg/dirmngr.conf \
74 | && apt-key adv --homedir ~/.gnupg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys E5267A6C \
75 | && apt-key adv --homedir ~/.gnupg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C300EE8C \
76 | && echo "deb http://ppa.launchpad.net/ondrej/php/ubuntu focal main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \
77 | && apt-get update \
78 | && apt-get install -y php8.0-cli php8.0-dev \
79 | php8.0-pgsql php8.0-sqlite3 php8.0-gd \
80 | php8.0-curl php8.0-memcached \
81 | php8.0-imap php8.0-mysql php8.0-mbstring \
82 | php8.0-xml php8.0-zip php8.0-bcmath php8.0-soap \
83 | php8.0-intl php8.0-readline \
84 | php8.0-msgpack php8.0-igbinary php8.0-ldap \
85 | php8.0-redis \
86 | && php -r "readfile('http://getcomposer.org/installer');" | php -- --install-dir=/usr/bin/ --filename=composer \
87 | && curl -sL https://deb.nodesource.com/setup_16.x | bash - \
88 | && apt-get install -y nodejs \
89 | && apt-get -y autoremove \
90 | && apt-get clean \
91 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
92 |
93 | RUN apt-get update \
94 | && apt-get install -y php-xdebug
95 |
96 | RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.0
97 |
98 | # Xdebug settings.
99 | COPY ./VM/xdebug.ini /etc/php/8.0/mods-available/xdebug.ini
100 |
101 | COPY ./VM/php.ini /etc/php/8.0/cli/conf.d/php.ini
102 | # RUN chmod +x /usr/local/bin/start-container
103 |
104 | EXPOSE 8000
105 | EXPOSE 9000
106 |
107 | CMD ["php", "artisan", "serve", "--host=0.0.0.0"]
108 |
--------------------------------------------------------------------------------
/VM/php.ini:
--------------------------------------------------------------------------------
1 | [PHP]
2 | post_max_size = 100M
3 | upload_max_filesize = 100M
4 | variables_order = EGPCS
--------------------------------------------------------------------------------
/VM/supervisord.conf:
--------------------------------------------------------------------------------
1 | [supervisord]
2 | nodaemon=true
3 |
4 | [program:php]
5 | command=/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0
6 | #stdout_logfile=/var/log/php-fpm/stdout.log
7 | #stdout_logfile_maxbytes=0
8 | #stderr_logfile=/var/log/php-fpm/stderr.log
9 | #stderr_logfile_maxbytes=0
10 | autostart=true
11 | autorestart=unexpected
12 |
13 | [program:node]
14 | directory=/var/www/html/client
15 | autostart=true
16 | command=npm run dev
--------------------------------------------------------------------------------
/VM/xdebug.ini:
--------------------------------------------------------------------------------
1 | zend_extension=/usr/lib/php/20200930/xdebug.so
2 | xdebug.remote_handler=dbgp
3 | xdebug.remote_mode=req
4 | xdebug.mode=debug
5 | xdebug.start_with_request=yes
6 | xdebug.discover_client_host=1
7 | xdebug.client_port=9000
8 | xdebug.remote_port=9000
9 | xdebug.idekey=docker
10 |
--------------------------------------------------------------------------------
/app/Console/Kernel.php:
--------------------------------------------------------------------------------
1 | command('inspire')->hourly();
28 | }
29 |
30 | /**
31 | * Register the commands for the application.
32 | *
33 | * @return void
34 | */
35 | protected function commands()
36 | {
37 | $this->load(__DIR__.'/Commands');
38 |
39 | require base_path('routes/console.php');
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/Exceptions/Handler.php:
--------------------------------------------------------------------------------
1 | reportable(function (Throwable $e) {
37 | //
38 | });
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/ConfirmPasswordController.php:
--------------------------------------------------------------------------------
1 | middleware('auth');
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/ForgotPasswordController.php:
--------------------------------------------------------------------------------
1 | middleware('guest')->except('logout');
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/RegisterController.php:
--------------------------------------------------------------------------------
1 | middleware('guest');
42 | }
43 |
44 | /**
45 | * Get a validator for an incoming registration request.
46 | *
47 | * @param array $data
48 | * @return \Illuminate\Contracts\Validation\Validator
49 | */
50 | protected function validator(array $data)
51 | {
52 | return Validator::make($data, [
53 | 'name' => ['required', 'string', 'max:255'],
54 | 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
55 | 'password' => ['required', 'string', 'min:8'],
56 | ]);
57 | }
58 |
59 | /**
60 | * Create a new user instance after a valid registration.
61 | *
62 | * @param array $data
63 | * @return \App\Models\User
64 | */
65 | protected function create(array $data)
66 | {
67 | return User::create([
68 | 'name' => $data['name'],
69 | 'email' => $data['email'],
70 | 'password' => Hash::make($data['password']),
71 | ]);
72 | }
73 | }
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/ResetPasswordController.php:
--------------------------------------------------------------------------------
1 | middleware('auth');
39 | $this->middleware('signed')->only('verify');
40 | $this->middleware('throttle:6,1')->only('verify', 'resend');
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Controller.php:
--------------------------------------------------------------------------------
1 | middleware('auth');
17 | }
18 |
19 | /**
20 | * Show the application dashboard.
21 | *
22 | * @return \Illuminate\Contracts\Support\Renderable
23 | */
24 | public function index()
25 | {
26 | return view('home');
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/Http/Kernel.php:
--------------------------------------------------------------------------------
1 | [
34 | \App\Http\Middleware\EncryptCookies::class,
35 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
36 | \Illuminate\Session\Middleware\StartSession::class,
37 | // \Illuminate\Session\Middleware\AuthenticateSession::class,
38 | \Illuminate\View\Middleware\ShareErrorsFromSession::class,
39 | \App\Http\Middleware\VerifyCsrfToken::class,
40 | \Illuminate\Routing\Middleware\SubstituteBindings::class,
41 | ],
42 |
43 | 'api' => [
44 | EnsureFrontendRequestsAreStateful::class,
45 | 'throttle:api',
46 | \Illuminate\Routing\Middleware\SubstituteBindings::class,
47 | ],
48 | ];
49 |
50 | /**
51 | * The application's route middleware.
52 | *
53 | * These middleware may be assigned to groups or used individually.
54 | *
55 | * @var array
56 | */
57 | protected $routeMiddleware = [
58 | 'auth' => \App\Http\Middleware\Authenticate::class,
59 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
60 | 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
61 | 'can' => \Illuminate\Auth\Middleware\Authorize::class,
62 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
63 | 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
64 | 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
65 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
66 | 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
67 | ];
68 | }
--------------------------------------------------------------------------------
/app/Http/Middleware/Authenticate.php:
--------------------------------------------------------------------------------
1 | expectsJson()) {
18 | return route('login');
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/Http/Middleware/EncryptCookies.php:
--------------------------------------------------------------------------------
1 | check()) {
26 | return redirect(RouteServiceProvider::HOME);
27 | }
28 | }
29 |
30 | return $next($request);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/Http/Middleware/TrimStrings.php:
--------------------------------------------------------------------------------
1 | allSubdomainsOfApplicationUrl(),
18 | ];
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/Http/Middleware/TrustProxies.php:
--------------------------------------------------------------------------------
1 | 'datetime',
46 | ];
47 |
48 | /**
49 | * Override the default notification mail when the user resets the password.
50 | * This is necessary as by default, the mail the user gets would include
51 | * a reset link that points to the url of the laravel application (e.g. localhost:8000).
52 | * Since we use an SPA for the frontend, we need a different url (e.g., localhost:3000).
53 | * This function achieves the desired behaviour.
54 | */
55 | public function sendPasswordResetNotification($token)
56 | {
57 | $this->notify(new MailResetPasswordMail($token));
58 | }
59 | }
--------------------------------------------------------------------------------
/app/Notifications/MailResetPasswordMail.php:
--------------------------------------------------------------------------------
1 | token = $token;
24 | }
25 |
26 | /**
27 | * Get the notification's delivery channels.
28 | *
29 | * @param mixed $notifiable
30 | * @return array
31 | */
32 | public function via($notifiable)
33 | {
34 | return ['mail'];
35 | }
36 |
37 | /**
38 | * Get the mail representation of the notification.
39 | *
40 | * @param mixed $notifiable
41 | * @return \Illuminate\Notifications\Messages\MailMessage
42 | */
43 | public function toMail($notifiable)
44 | {
45 | /**
46 | * The action link must not include the laravel url as the base url,
47 | * as we use an SPA and different URL on the frontend. We use the sanctum
48 | * stateful domain as the base url, because this is the base url of our frontend.
49 | */
50 | $action_url = 'http://' . env('SANCTUM_STATEFUL_DOMAINS') . '/user/password/reset/' . $this->token;
51 | return (new MailMessage)
52 | ->line('The introduction to the notification.')
53 | ->action('Reset password', $action_url)
54 | ->line('Thank you for using our application!');
55 | }
56 |
57 | /**
58 | * Get the array representation of the notification.
59 | *
60 | * @param mixed $notifiable
61 | * @return array
62 | */
63 | public function toArray($notifiable)
64 | {
65 | return [
66 | //
67 | ];
68 | }
69 | }
--------------------------------------------------------------------------------
/app/Providers/AppServiceProvider.php:
--------------------------------------------------------------------------------
1 | 'App\Policies\ModelPolicy',
19 | ];
20 |
21 | /**
22 | * Register any authentication / authorization services.
23 | *
24 | * @return void
25 | */
26 | public function boot()
27 | {
28 | $this->registerPolicies();
29 |
30 | /**
31 | * The function that sends an email verification mail to the user upon registration.
32 | *
33 | * We're overriding the default toMailUsing function here, as the passed $url parameter
34 | * contains the laravel app url as the base url, which we don't want, as the links that are
35 | * included in the mail should point to our frontend app, which has a different URL.
36 | */
37 | VerifyEmail::toMailUsing(function ($notifiable, $url) {
38 | $slug = substr($url, strpos($url, "/email"));
39 | $new_url = "http://" . env("SANCTUM_STATEFUL_DOMAINS") . "/user" . $slug;
40 | return (new MailMessage)
41 | ->subject('Verify Email Address')
42 | ->line('Click the button below to verify your email address.')
43 | ->action('Verify Email Address', $new_url);
44 | });
45 | }
46 | }
--------------------------------------------------------------------------------
/app/Providers/BroadcastServiceProvider.php:
--------------------------------------------------------------------------------
1 | [
19 | SendEmailVerificationNotification::class,
20 | ],
21 | ];
22 |
23 | /**
24 | * Register any events for your application.
25 | *
26 | * @return void
27 | */
28 | public function boot()
29 | {
30 | //
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/Providers/RouteServiceProvider.php:
--------------------------------------------------------------------------------
1 | configureRateLimiting();
39 |
40 | $this->routes(function () {
41 | Route::prefix('api')
42 | ->middleware('web')
43 | ->namespace($this->namespace)
44 | ->group(base_path('routes/api.php'));
45 |
46 | Route::middleware('web')
47 | ->namespace($this->namespace)
48 | ->group(base_path('routes/web.php'));
49 | });
50 | }
51 |
52 | /**
53 | * Configure the rate limiters for the application.
54 | *
55 | * @return void
56 | */
57 | protected function configureRateLimiting()
58 | {
59 | RateLimiter::for('api', function (Request $request) {
60 | return Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip());
61 | });
62 | }
63 | }
--------------------------------------------------------------------------------
/artisan:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | make(Illuminate\Contracts\Console\Kernel::class);
34 |
35 | $status = $kernel->handle(
36 | $input = new Symfony\Component\Console\Input\ArgvInput,
37 | new Symfony\Component\Console\Output\ConsoleOutput
38 | );
39 |
40 | /*
41 | |--------------------------------------------------------------------------
42 | | Shutdown The Application
43 | |--------------------------------------------------------------------------
44 | |
45 | | Once Artisan has finished running, we will fire off the shutdown events
46 | | so that any final work may be done by the application before we shut
47 | | down the process. This is the last thing to happen to the request.
48 | |
49 | */
50 |
51 | $kernel->terminate($input, $status);
52 |
53 | exit($status);
54 |
--------------------------------------------------------------------------------
/bootstrap/app.php:
--------------------------------------------------------------------------------
1 | singleton(
30 | Illuminate\Contracts\Http\Kernel::class,
31 | App\Http\Kernel::class
32 | );
33 |
34 | $app->singleton(
35 | Illuminate\Contracts\Console\Kernel::class,
36 | App\Console\Kernel::class
37 | );
38 |
39 | $app->singleton(
40 | Illuminate\Contracts\Debug\ExceptionHandler::class,
41 | App\Exceptions\Handler::class
42 | );
43 |
44 | /*
45 | |--------------------------------------------------------------------------
46 | | Return The Application
47 | |--------------------------------------------------------------------------
48 | |
49 | | This script returns the application instance. The instance is given to
50 | | the calling script so we can separate the building of the instances
51 | | from the actual running of the application and sending responses.
52 | |
53 | */
54 |
55 | return $app;
56 |
--------------------------------------------------------------------------------
/bootstrap/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/client/.env.local.example:
--------------------------------------------------------------------------------
1 | NEXT_PUBLIC_API_HOST_URL=http://localhost:8000
2 | NEXT_PUBLIC_USER_HOME_ROUTE=/dashboard
3 | NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID=XXXXXXXXXXXXXXXX
--------------------------------------------------------------------------------
/client/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es6: true,
5 | amd: true,
6 | node: true,
7 | },
8 | extends: [
9 | "eslint:recommended",
10 | "plugin:react/recommended",
11 | "plugin:@typescript-eslint/eslint-recommended",
12 | ],
13 | globals: {
14 | Atomics: "readonly",
15 | SharedArrayBuffer: "readonly",
16 | },
17 | parser: "@typescript-eslint/parser",
18 | parserOptions: {
19 | ecmaFeatures: {
20 | jsx: true,
21 | },
22 | ecmaVersion: 11,
23 | sourceType: "module",
24 | },
25 | plugins: ["react", "@typescript-eslint", "simple-import-sort"],
26 | rules: {
27 | "react/react-in-jsx-scope": "off",
28 | },
29 | settings: {
30 | settings: {
31 | "import/resolver": {
32 | alias: {
33 | map: [
34 | ["@/components", "./components"],
35 | ["@/store", "./store"],
36 | ["@/services", "./services"],
37 | ],
38 | extensions: [".js", ".jsx", ".ts", ".tsx"],
39 | },
40 | },
41 | },
42 | },
43 | };
44 |
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/client/components/Alert/Alert.tsx:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import { ReactElement } from "react";
3 |
4 | export function Alert({ type, children }): ReactElement {
5 | // Determine the classes of the alert depending ong the type given as a prop.
6 | const alertType = (): string => {
7 | switch (type) {
8 | case "danger":
9 | return "bg-red-300 text-red-500";
10 | case "warning":
11 | return "bg-yellow-300 text-yellow-500";
12 | case "success":
13 | return "bg-green-300 text-green-500";
14 | default:
15 | return "bg-red-300 text-red-500";
16 | }
17 | };
18 |
19 | const alertTypeClasses: string = alertType();
20 | const classes = `w-full rounded flex justify-center items-center px-2 py-1 ${alertTypeClasses}`;
21 |
22 | // Returns statement.
23 | return
{children}
;
24 | }
25 |
26 | Alert.propTypes = {
27 | children: PropTypes.any,
28 | type: PropTypes.string.isRequired,
29 | };
30 |
--------------------------------------------------------------------------------
/client/components/Button/Button.tsx:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import { ReactElement } from "react";
3 |
4 | /**
5 | * The Primary Button. Primarily used for CTAs.
6 | *
7 | * @param {object} props
8 | * The props object.
9 | */
10 | export function PrimaryButton(props: any): ReactElement {
11 | const classes: string = `w-full flex items-center justify-center px-8 py-2 border border-transparent text-base font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 md:text-lg md:px-3 ${props.additionalClasses}`;
12 | return (
13 |
14 | {props.children}
15 |
16 | );
17 | }
18 |
19 | PrimaryButton.propTypes = {
20 | onClick: PropTypes.func,
21 | children: PropTypes.any,
22 | additionalClasses: PropTypes.string,
23 | };
24 |
25 | /**
26 | * A Swiper-Like button that can be used to indicate an on and off state.
27 | *
28 | * @param {object} props
29 | */
30 | export function SwiperButton(props: any) {
31 | const outerClasses = `toggle-checkbox flex align-center items-center inline-block rounded-2xl p-1 h-8 w-14 ${props.checked
32 | ? "bg-purple-700 justify-end"
33 | : "bg-gray-300 justify-start"
34 | } cursor-pointer transition-all duration-100 ease-in`;
35 | const innerClasses = `rounded-full h-6 w-6 bg-gray-700 transition-all duration-500 ease-in`;
36 | return (
37 |
40 | );
41 | }
42 |
43 | /**
44 | * A circe round button.
45 | * @param {object} props
46 | */
47 | export function CircleButton({ children, onClick, additionalClasses }) {
48 | const classList = `${additionalClasses} mb-3 mr-3 rounded-full p-2 bg-purple-500 text-pruple-700 focus:outline-none cursour-pointer text-white hover:bg-purple-700`;
49 | return (
50 |
51 | {children}
52 |
53 | );
54 | }
55 |
56 | CircleButton.propTypes = {
57 | children: PropTypes.element.isRequired,
58 | onClick: PropTypes.func,
59 | additionalClasses: PropTypes.string,
60 | };
61 |
62 | /**
63 | * A circle round button with a burger menu inside.
64 | *
65 | * @param {object} props
66 | */
67 | export function BurgerCircleButton({ onClick, additionalClasses }) {
68 | return (
69 |
70 |
77 |
83 |
84 |
85 | );
86 | }
87 |
88 | BurgerCircleButton.propTypes = {
89 | onClick: PropTypes.func,
90 | additionalClasses: PropTypes.string,
91 | };
92 |
--------------------------------------------------------------------------------
/client/components/Card/Card.tsx:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import {ReactElement} from "react";
3 |
4 | /**
5 | * The default card element.
6 | *
7 | * @param {object} props
8 | * The props object.
9 | */
10 | export function Card({
11 | additionalWrapperClasses,
12 | additionalInnerClasses,
13 | children,
14 | }): ReactElement {
15 | const outerClasses: string = `card w-full rounded-md bg-white shadow-md p-3 ${additionalWrapperClasses}`;
16 | const innerClasses: string = `max-w-full h-full mx-auto flex flex-col ${additionalInnerClasses}`;
17 |
18 | return (
19 |
22 | );
23 | }
24 |
25 | Card.propTypes = {
26 | children: PropTypes.element,
27 | additionalInnerClasses: PropTypes.string,
28 | additionalWrapperClasses: PropTypes.string,
29 | };
30 |
--------------------------------------------------------------------------------
/client/components/Form/FormElement.tsx:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import {ReactElement} from "react";
3 |
4 | /**
5 | * Text input field.
6 | *
7 | * @param {object} props
8 | * The props object.
9 | */
10 | export function TextInput(props: any): ReactElement {
11 |
12 | const inputClasses: string = `w-full px-1 py-2 rounded bg-white focus:outline-none focus:ring-2 focus:ring-purple-600 border ${
13 | props.errorMsg ? "border-red-500" : "border-transparent"
14 | } shadow-md focus:border-transparent`;
15 |
16 | // Return statement.
17 | return (
18 |
19 |
27 | {/* Shor error message if given. */}
28 | {props.errorMsg && (
29 |
{props.errorMsg}
30 | )}
31 |
32 | );
33 | }
34 | TextInput.propTypes = {
35 | type: PropTypes.string,
36 | value: PropTypes.any.isRequired,
37 | name: PropTypes.string,
38 | onChange: PropTypes.func.isRequired,
39 | placeholder: PropTypes.string,
40 | errorMsg: PropTypes.string,
41 | };
42 |
43 | /**
44 | * Textarea input field.
45 | *
46 | * @param {object} props
47 | * The props object.
48 | */
49 | export function TextArea(props: any): ReactElement {
50 | return (
51 |
52 |
64 | {props.errorMsg && (
65 |
{props.errorMsg}
66 | )}
67 |
68 | );
69 | }
70 | TextArea.propTypes = {
71 | value: PropTypes.any.isRequired,
72 | name: PropTypes.string,
73 | onChange: PropTypes.func.isRequired,
74 | placeholder: PropTypes.string,
75 | errorMsg: PropTypes.string,
76 | };
77 |
--------------------------------------------------------------------------------
/client/components/Layout/Sidebar.tsx:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import {BurgerCircleButton} from "./../Button/Button";
3 | import tailwindConfig from "./../../tailwind.config";
4 | import {ReactElement} from "react";
5 |
6 | /**
7 | * A page layout that includes a fixed responsive sidebar
8 | * Ob mobile breakpoints, a button on the top right
9 | * enables toggling of the sidebar. On Desktop, the
10 | * sidebar is always visible on the left side.
11 | *
12 | * @param {object} props
13 | */
14 | export function SidebarLayout({
15 | sidebarItems,
16 | children,
17 | showSidebar,
18 | onClickCircleIcon,
19 | }): ReactElement {
20 |
21 | // Set the class list of the sidebar. If the sidebar should not be shown, set the left property to -100%.
22 | const asideClassList: string = `w-10/12 md:w-1/3 lg:w-72 fixed h-screen lg:left-0 shadow-lg bg-white flex-grow z-50 transition-all pb-12 ${
23 | showSidebar ? "" : "-left-full"
24 | }`;
25 |
26 | // Return statement.
27 | return (
28 |
29 | {/* The sidebar menu on the left side. */}
30 |
31 |
32 |
33 | {sidebarItems}
34 |
35 |
36 |
37 |
38 | {/* The content area. */}
39 |
42 |
43 | {/* The toggle button that gets show only on mobile. */}
44 |
48 |
49 | );
50 | }
51 |
52 | SidebarLayout.propTypes = {
53 | sidebarItems: PropTypes.element.isRequired,
54 | children: PropTypes.element.isRequired,
55 | showSidebar: PropTypes.bool.isRequired,
56 | onClickCircleIcon: PropTypes.func.isRequired,
57 | };
58 |
59 | export function SidebarPanel({title, children}): ReactElement {
60 |
61 | return (
62 |
63 |
{title}
64 |
{children}
65 |
66 | );
67 | }
68 |
69 | SidebarPanel.propTypes = {
70 | title: PropTypes.string.isRequired,
71 | children: PropTypes.any.isRequired,
72 | };
73 |
74 | /**
75 | * Helper function that can be used to toggle the sidebar.
76 | *
77 | * @param {boolean} currentSidebarState
78 | * Determines if the sidebar is currently shown or not.
79 | *
80 | * @return {boolean}
81 | */
82 | export const toggleSidebar = (currentSidebarState: boolean): boolean => {
83 | // Get the breakpoints from Tailwind config.
84 | const breakpoints: { sm: number, md: number, lg: number, xl: number } = {
85 | sm: parseInt(tailwindConfig.theme.screens.sm.replace("px", "")),
86 | md: parseInt(tailwindConfig.theme.screens.md.replace("px", "")),
87 | lg: parseInt(tailwindConfig.theme.screens.lg.replace("px", "")),
88 | xl: parseInt(tailwindConfig.theme.screens.xl.replace("px", "")),
89 | };
90 |
91 | /**
92 | * This function should not be called when we're on desktop,
93 | * since the button for toggling is hidden on desktop. But
94 | * if the function gets called anyways that we only toggle the
95 | * sidebar state on mobile breakpoints.
96 | */
97 | const windowWidth: number = window.innerWidth;
98 | if (windowWidth >= breakpoints.lg) {
99 | return true;
100 | }
101 | return !currentSidebarState;
102 | };
103 |
--------------------------------------------------------------------------------
/client/components/Navigation/Footer.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import PropTypes from "prop-types";
3 | import {ReactElement} from "react";
4 |
5 | /**
6 | * Grid layout. Defaults to three colums per row.
7 | */
8 | export function AdvancedFooter(): ReactElement {
9 | // Customize to your needs. The footer ui will be composed of this data.
10 | const footerData: { headline: string, content: { title: string, link: string }[] }[] = [
11 | // First col.
12 | {
13 | headline: "Links",
14 | content: [
15 | {title: "Home", link: "/"},
16 | {title: "Docs", link: "/docs"},
17 | {title: "Blog", link: "/blog"},
18 | {title: "Contact", link: "/contact"},
19 | ],
20 | },
21 | // Second col.
22 | {
23 | headline: "Social Media",
24 | content: [
25 | {title: "Twitter", link: "https://twitter.com/niclas_timm"},
26 | {
27 | title: "GitHub",
28 | link:
29 | "https://github.com/NiclasTimmeDev/laravel-nextjs-starter/",
30 | },
31 | ],
32 | },
33 | // Third col.
34 | {
35 | headline: "Legal",
36 | content: [
37 | {title: "Privacy", link: "/privacy"},
38 | {title: "Imprint", link: "/imprint"},
39 | ],
40 | },
41 | ];
42 |
43 | /**
44 | * Map over the footerData array. For every object in the array,
45 | * return a new div with a headline and as many links as defined
46 | * in the .content of the currently mapped over object.
47 | */
48 | const footerCols: JSX.Element[] = footerData.map((element) => {
49 | return (
50 |
51 |
{element.headline}
52 | {/* Map over every entry in the content and return an footer link component. */}
53 | {element.content.map((link) => {
54 | return (
55 |
60 | );
61 | })}
62 |
63 | );
64 | });
65 |
66 | return (
67 |
68 |
69 | {footerCols}
70 |
71 |
72 | );
73 | }
74 |
75 | /**
76 | * A simple footer component.
77 | *
78 | * @return ReactElement
79 | */
80 | export function SimpleFooter(): ReactElement {
81 | const currentYear: number = new Date().getFullYear();
82 |
83 | /**
84 | * All links that will be displayed in the footer.
85 | * Customize to your requirements.
86 | */
87 | const footerLinks = [
88 | {title: "Imprint", link: "/imprint"},
89 | {title: "Privacy Statement", link: "/privacy"},
90 | ];
91 |
92 | // A FooterLink component for every given link.
93 | const footerItems: ReactElement[] = footerLinks.map((link) => {
94 | return (
95 |
101 | );
102 | });
103 |
104 | // Return statement.
105 | return (
106 |
107 |
108 |
Copyright {currentYear}
109 |
{footerItems}
110 |
111 |
112 | );
113 | }
114 |
115 | /**
116 | * A link that will be displayed in the Footer.
117 | *
118 | * @param {object} props
119 | * The props, including title and link.
120 | */
121 | export function FooterLink({title, link, marginLeft}): ReactElement {
122 | return (
123 |
124 |
129 | {title}
130 |
131 |
132 | );
133 | }
134 |
135 | FooterLink.propTypes = {
136 | title: PropTypes.string.isRequired,
137 | link: PropTypes.string.isRequired,
138 | marginLeft: PropTypes.bool,
139 | };
140 |
--------------------------------------------------------------------------------
/client/components/Navigation/Navbar.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import {ReactElement, useState} from "react";
3 | import PropTypes from "prop-types";
4 |
5 | /**
6 | * The default nabvar.
7 | */
8 | export function Navbar(): ReactElement {
9 | const [showSidebar, toggleSidebar] = useState(false);
10 |
11 | const toggleNavbar = (): void => {
12 | toggleSidebar(!showSidebar);
13 | };
14 |
15 | const sidebarOffset: string = `${
16 | showSidebar
17 | ? "right-0"
18 | : "-right-full md:-right-1/2 lg:-right-1/3 xl:-right-1/4"
19 | }`;
20 |
21 | // Return statement.
22 | return (
23 | <>
24 | {/* The Menu Bar that the horizontal bar at the top of the screen that is shown on all breakpoints. It includes the logo als well as the Burger Menu */}
25 |
26 |
29 |
48 |
49 |
54 |
59 |
64 |
65 | >
66 | );
67 | }
68 |
69 | /**
70 | * A Mega Menu that takes the full witdth and height of the screen.
71 | */
72 | export function MegaMenu(): ReactElement {
73 | const [showSidebar, toggleSidebar] = useState(false);
74 |
75 | const toggleNavbar = (): void => {
76 | toggleSidebar(!showSidebar);
77 | };
78 |
79 | const sidebarOffset: string = `${showSidebar ? "right-0" : "-right-full"}`;
80 |
81 | // Return statement.
82 | return (
83 | <>
84 | {/* The Menu Bar that the horizontal bar at the top of the screen that is shown on all breakpoints. It includes the logo als well as the Burger Menu */}
85 |
86 |
89 |
108 |
109 |
114 |
119 |
124 |
125 | >
126 | );
127 | }
128 |
129 | /**
130 | * A Link that can be displayed in the menu.
131 | * @param {object} props
132 | */
133 | export function NavbarMenuLink({ title, link, onClick }): ReactElement {
134 | return (
135 |
136 |
140 | {title}
141 |
142 |
143 | );
144 | }
145 | NavbarMenuLink.propTypes = {
146 | title: PropTypes.string.isRequired,
147 | link: PropTypes.string.isRequired,
148 | onClick: PropTypes.func
149 | };
150 |
151 | // The horizontal menu bar.
152 | export function MenuBar({ onClick }): ReactElement {
153 | return (
154 |
155 |
156 | Blog
157 |
158 |
174 |
175 | );
176 | }
177 | MenuBar.propTypes = {
178 | onClick: PropTypes.func.isRequired
179 | };
180 |
--------------------------------------------------------------------------------
/client/components/SEO/MetaTags.tsx:
--------------------------------------------------------------------------------
1 | import Head from "next/head";
2 | import PropTypes from "prop-types";
3 | import { ReactElement } from "react";
4 |
5 | // COMPONENT
6 | export function MetaTags({
7 | title,
8 | metaDescription,
9 | canonical,
10 | ogDescription,
11 | ogImage,
12 | ogUrl,
13 | }): ReactElement {
14 | return (
15 | <>
16 |
17 | {/* Title */}
18 | {title}
19 |
20 | {/* Description */}
21 |
22 |
23 | {/* Canocical URL, if given. */}
24 | {canonical && }
25 |
26 | {/* Open Graph. */}
27 |
28 |
29 | {ogImage && }
30 | {ogUrl && }
31 |
32 | >
33 | );
34 | }
35 |
36 | export const OriginalMetaTags = ({
37 | pageName = "Clinic"
38 | }:{
39 | pageName?: string;
40 | }) => {
41 | return (
42 |
43 | {pageName} | Kelompok 4
44 |
45 |
46 |
51 |
52 | )
53 | }
54 |
55 | // PROPS
56 | MetaTags.propTypes = {
57 | title: PropTypes.string.isRequired,
58 | metaDescription: PropTypes.string.isRequired,
59 | canonical: PropTypes.string,
60 | ogDescription: PropTypes.string.isRequired,
61 | ogImage: PropTypes.string,
62 | ogUrl: PropTypes.string,
63 | };
64 |
--------------------------------------------------------------------------------
/client/components/Spinner/Spinner.tsx:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import {ReactElement} from "react";
3 |
4 | export function SmallSpinner(props: any): ReactElement {
5 | const classes: string = `inline-block rounded-full border-4 border-t-4 border-gray-200 border-top-colored animate-spin h-6 w-6`;
6 | return props.show ? : ;
7 | }
8 | SmallSpinner.propTypes = {
9 | show: PropTypes.bool.isRequired,
10 | };
11 |
--------------------------------------------------------------------------------
/client/components/Typography/Headers.tsx:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import {ReactElement} from "react";
3 |
4 | /**
5 | * The Leading headline for every site.
6 | *
7 | * Should only be used once for every view.
8 | *
9 | * @param {object} props
10 | * The props object
11 | */
12 |
13 | export function H1(props: any): ReactElement {
14 | // The Css classes that will be appended to the tag.
15 | const classes: string = `text-5xl ${props.withMargin ? "mb-5" : ""} ${
16 | props.center ? "text-center" : ""
17 | } `;
18 |
19 | return {props.children} ;
20 | }
21 |
22 | H1.propTypes = {
23 | center: PropTypes.bool,
24 | withMargin: PropTypes.bool,
25 | children: PropTypes.string.isRequired,
26 | };
27 |
--------------------------------------------------------------------------------
/client/config/config.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | |--------------------------------------------------------------------------
3 | | Axios defaults
4 | |--------------------------------------------------------------------------
5 | |
6 | | The default config for axios. "withCredentials" is necessary in order to
7 | | get access to the laravel backend. The "baseURL" should match the domain
8 | | and port of your laravel api.
9 | |
10 | */
11 | import axios from "axios";
12 | // Configure axios in order to be able to make api requests to the laravel backend.
13 | axios.defaults.withCredentials = true;
14 | axios.defaults.baseURL = process.env.NEXT_PUBLIC_API_HOST_URL;
15 |
16 | /*
17 | |--------------------------------------------------------------------------
18 | | Protected Routes
19 | |--------------------------------------------------------------------------
20 | |
21 | | A list of routes that is only accessible for authenticated user. If an
22 | | unauthenticated user tries to access on of the listed routes, she will be
23 | | redirected to /user/login. The list also respects sub-routes.
24 | | This means, if you include /dashboard, /dashboard/analytics or /dashboard/1
25 | | will lead to a redirect if the user is not authenticated.
26 | |
27 | */
28 | export const protectedRoutes: string[] = [
29 | process.env.NEXT_PUBLIC_USER_HOME_ROUTE, // -> from .env.local
30 | // "/profile",
31 | // "/acount",
32 | // ...,
33 | ];
34 |
--------------------------------------------------------------------------------
/client/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "eslint --fix ."
10 | },
11 | "dependencies": {
12 | "@chakra-ui/react": "^2.3.6",
13 | "@emotion/react": "^11",
14 | "@emotion/styled": "^11",
15 | "axios": "^0.21.1",
16 | "eslint-plugin-simple-import-sort": "^7.0.0",
17 | "framer-motion": "^6",
18 | "next": "13.0.0",
19 | "prop-types": "^15.7.2",
20 | "react": "^18.2.0",
21 | "react-dom": "^18.2.0",
22 | "react-gtm-module": "^2.0.11",
23 | "react-redux": "^7.2.2",
24 | "redux": "^4.0.5",
25 | "redux-thunk": "^2.3.0",
26 | "validator": "^13.5.2"
27 | },
28 | "devDependencies": {
29 | "@types/node": "^14.14.16",
30 | "@types/react": "^18.0.24",
31 | "@typescript-eslint/eslint-plugin": "^4.11.1",
32 | "@typescript-eslint/parser": "^4.11.1",
33 | "autoprefixer": "^10.1.0",
34 | "babel-plugin-module-resolver": "^4.1.0",
35 | "eslint": "^7.16.0",
36 | "eslint-config-next": "^11.0.1",
37 | "eslint-import-resolver-typescript": "^2.3.0",
38 | "eslint-plugin-react": "^7.22.0",
39 | "postcss": "^8.3.6",
40 | "redux-devtools-extension": "^2.13.8",
41 | "tailwindcss": "^2.0.2",
42 | "typescript": "^4.3.5",
43 | "webpack": "^5.74.0"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/client/pages/404.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | |--------------------------------------------------------------------------
3 | | 404 Page.
4 | |--------------------------------------------------------------------------
5 | |
6 | | The view that gets rendered when a user tries to visit a route that has
7 | | no matching file in your /pages directory.
8 | | If the user is authenticated, a link to the user home route (defined in your
9 | | .env.local) will be displayed. Otherwise, a link to the homepage will be
10 | | displayed.
11 | |
12 | */
13 | import { H1 } from "@/components/Typography/Headers";
14 | import Link from "next/link";
15 | import { connect } from "react-redux";
16 |
17 | function FourOFour(props: any) {
18 | /**
19 | * Determine the link location that the link
20 | * on the page will lead to. If the user is
21 | * authenticated, it will be the user home route.
22 | * Otherwise it will be the homepage.
23 | */
24 | const linkLocation = props.isAuthenticated
25 | ? process.env.NEXT_PUBLIC_USER_HOME_ROUTE
26 | : "/";
27 |
28 | return (
29 |
30 |
31 | 404 | Nothing to see here!
32 |
33 |
34 |
35 | Go Home
36 |
37 |
38 |
39 | );
40 | }
41 |
42 | // Map redux states to local component props.
43 | const mapStateToProps = (state: any) => ({
44 | isAuthenticated: state.auth.isAuthenticated,
45 | });
46 |
47 | export default connect(mapStateToProps)(FourOFour);
48 |
--------------------------------------------------------------------------------
/client/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import "../styles/globals.css";
2 | import store from "@/store/store";
3 | import { Provider } from "react-redux";
4 | import PropTypes from "prop-types";
5 | import { AuthGuard } from "@/services/Auth/AuthGuard";
6 | import { useEffect } from "react";
7 | import * as types from "@/store/actionTypes";
8 | // import TagManager from "react-gtm-module";
9 | import { Navbar } from "@/components/Navigation/Navbar";
10 | import { AdvancedFooter } from "@/components/Navigation/Footer";
11 | import { useRouter } from "next/router";
12 | import { protectedRoutes } from "./../config/config";
13 | import { ChakraProvider } from '@chakra-ui/react'
14 | require("./../config/config.tsx");
15 |
16 | function MyApp(props: any) {
17 | // Initialize Google Tag Manager via react-gtm-module.
18 | // if (process.env.NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID) {
19 | // const tagManagerArgs = {
20 | // gtmId: process.env.NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID,
21 | // };
22 | // if (process.browser) {
23 | // TagManager.initialize(tagManagerArgs);
24 | // }
25 | // }
26 |
27 | const router = useRouter();
28 | // Check if we're on a protected route.
29 | const isNoProtectedRoute = protectedRoutes.every((route) => {
30 | return !router.pathname.startsWith(route);
31 | });
32 |
33 | // Handle current user in redux.
34 | useEffect(() => {
35 | // Store current user if we have one.
36 | if (props.user) {
37 | store.dispatch({
38 | type: types.USER_LOADED,
39 | payload: props.user,
40 | });
41 | return;
42 | }
43 | // Dispatch user loading error if no user is present.
44 | store.dispatch({
45 | type: types.USER_LOADED_ERROR,
46 | });
47 | }, []);
48 |
49 | return (
50 |
51 |
52 |
53 | ;
54 | {isNoProtectedRoute && }
55 |
56 |
57 | );
58 | }
59 |
60 | MyApp.propTypes = {
61 | Component: PropTypes.elementType,
62 | pageProps: PropTypes.object,
63 | };
64 |
65 | /**
66 | * Fetch some data server side before rendering the page client side.
67 | *
68 | * @param {object} context
69 | * The context object.
70 | */
71 | MyApp.getInitialProps = async ({ ctx }) => {
72 | const req = ctx.req;
73 | const pathname = ctx.pathname;
74 | const res = ctx.res;
75 |
76 | /**
77 | * Abort if one var is not present.
78 | * For example, the req obj will be undefined if we don't
79 | * have a page reload but a page switch via the Next Router.
80 | */
81 | if (!req || !pathname || !res) {
82 | return {};
83 | }
84 |
85 | const authenticator = new AuthGuard();
86 | return await authenticator.authenticateUser(req, res, pathname);
87 | };
88 |
89 | export default MyApp;
90 |
--------------------------------------------------------------------------------
/client/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import Document, { Html, Head, Main, NextScript } from "next/document";
2 |
3 | export default class CustomDocument extends Document {
4 | render() {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | );
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/client/pages/dashboard/index.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import { PrimaryButton } from "./../../components/Button/Button";
3 | import { logout } from "./../../store/auth/authActions";
4 | import { connect } from "react-redux";
5 | import {useEffect, ReactElement} from "react";
6 | import {NextRouter, useRouter} from "next/router";
7 | import { OriginalMetaTags } from "@/components/SEO/MetaTags";
8 |
9 | function Dashboard(props: any): ReactElement {
10 | const router: NextRouter = useRouter();
11 |
12 | useEffect(() => {
13 | if (!props.isAuthenticated) {
14 | router.push("/user/login");
15 | }
16 | }, [props.isAuthenticated]);
17 |
18 | // Return statement.
19 | return (
20 | <>
21 |
22 | Dashboard
23 | Register
24 | {
26 | props.logout();
27 | }}
28 | >
29 | Logout
30 |
31 | >
32 | );
33 | }
34 |
35 | const mapStateToProps = (state: any) => ({
36 | isAuthenticated: state.auth.isAuthenticated,
37 | loading: state.auth.registerLoading,
38 | });
39 |
40 | export default connect(mapStateToProps, { logout })(Dashboard);
41 |
--------------------------------------------------------------------------------
/client/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import { H1 } from "./../components/Typography/Headers";
3 |
4 | export default function Home() {
5 | return (
6 | <>
7 |
8 |
9 |
10 | Welcome to the Laravel Next.js Starter Kit
11 |
12 |
13 |
14 | What do you want to do?
15 |
16 |
17 |
18 | Documentation
19 |
20 | Login
21 | Register
22 |
23 |
24 |
25 | >
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/client/pages/user/email/verify/[userID]/[hash].tsx:
--------------------------------------------------------------------------------
1 | import { NextRouter, useRouter } from "next/router";
2 | import { ReactElement, useEffect, useState } from "react";
3 | import { connect } from "react-redux";
4 | import { verifyEmail } from "../../../../../store/auth/authActions";
5 | import { Card } from "./../../../../../components/Card/Card";
6 | import { H1 } from "./../../../../../components/Typography/Headers";
7 | import { SmallSpinner } from "./../../../../../components/Spinner/Spinner";
8 |
9 | function VerifyPassword(props: any): ReactElement {
10 | const router: NextRouter = useRouter();
11 |
12 | const { expires, signature, userID, hash } = router.query;
13 |
14 | // State.
15 | const [state, setState] = useState<{
16 | error: string;
17 | loading: boolean;
18 | }>({
19 | error: "",
20 | loading: true,
21 | });
22 |
23 | // Send api request to api upon mount of the component.
24 | useEffect(() => {
25 | const verify = async () => {
26 | const res = await props.verifyEmail(
27 | userID,
28 | hash,
29 | expires,
30 | signature
31 | );
32 |
33 | // Successfull verification.
34 | if (res.success) {
35 | setState({
36 | ...state,
37 | loading: false,
38 | error: "",
39 | });
40 |
41 | // Redirect to Home route of the user after 3 seconds.
42 | setTimeout(() => {
43 | router.push(process.env.NEXT_PUBLIC_USER_HOME_ROUTE);
44 | }, 3000);
45 | return;
46 | }
47 |
48 | // Set error message if verification failed.
49 | if (res.error) {
50 | setState({
51 | ...state,
52 | loading: false,
53 | error: res.error,
54 | });
55 | }
56 | };
57 | verify();
58 | }, []);
59 |
60 | /**
61 | * Set the text for the H1 header depending on verification status.
62 | */
63 | const headerText = (): string => {
64 | if (state.loading) {
65 | return "We are currently validating your email address...";
66 | } else if (!state.loading && !state.error) {
67 | return "Verification successfull!";
68 | }
69 | return "Verification failed!";
70 | };
71 | const header = headerText();
72 |
73 | /**
74 | * Set the text for the paragraph depending on verification status.
75 | */
76 | const paragraphText = (): string => {
77 | if (state.loading) {
78 | return "";
79 | } else if (!state.loading && !state.error) {
80 | return "Perfect! You will be redirected shortly....";
81 | }
82 | return "Sorry, something went wrong!";
83 | };
84 |
85 | const paragraph: string = paragraphText();
86 |
87 | // Return statement.
88 | return (
89 |
90 |
91 | {/* Card */}
92 |
96 | <>
97 | {/* Header */}
98 |
99 | {header}
100 |
101 |
102 | {/* Paragraph */}
103 |
104 | {" "}
105 | { }{" "}
106 | {paragraph}
107 |
108 | >
109 |
110 |
111 |
112 | );
113 | }
114 |
115 | export default connect(null, { verifyEmail })(VerifyPassword);
116 |
--------------------------------------------------------------------------------
/client/pages/user/login.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement, useEffect, useState } from "react";
2 | import { connect } from "react-redux";
3 | import PropTypes from "prop-types";
4 | import { login } from "@/store/auth/authActions";
5 | import { UserValidator } from "@/services/UserValidator";
6 | import { Card } from "@/components/Card/Card";
7 | import { TextInput } from "@/components/Form/FormElement";
8 | import { H1 } from "@/components/Typography/Headers";
9 | import { PrimaryButton } from "@/components/Button/Button";
10 | import { Alert } from "@/components/Alert/Alert";
11 | import { useRouter } from "next/router";
12 | import Link from "next/link";
13 | import { SmallSpinner } from "@/components/Spinner/Spinner";
14 |
15 | const Login = (props: any): ReactElement => {
16 | /**
17 | * The state.
18 | */
19 | const [formData, setFormData] = useState<{
20 | email: string;
21 | password: string;
22 | emailError: string;
23 | passwordError: string;
24 | }>({
25 | email: "",
26 | password: "",
27 | emailError: "",
28 | passwordError: "",
29 | });
30 |
31 | // The router object used for redirecting after login.
32 | const router = useRouter();
33 |
34 | // Redirect to user home route if user is authenticated.
35 | useEffect(() => {
36 | if (props.isAuthenticated && !props.loading) {
37 | router.push(process.env.NEXT_PUBLIC_USER_HOME_ROUTE);
38 | }
39 | }, [props.isAuthenticated, props.loading]);
40 |
41 | /**
42 | * Handle input change.
43 | *
44 | * @param {object} e
45 | * The event object.
46 | */
47 | const handleInputChange = (e: React.FormEvent): void => {
48 | setFormData({
49 | ...formData,
50 | [e.currentTarget.name]: e.currentTarget.value,
51 | emailError: "",
52 | passwordError: "",
53 | });
54 | };
55 |
56 | /**
57 | * Submit the form.
58 | */
59 | const submit = (): Promise => {
60 | const userValidator: UserValidator = new UserValidator();
61 | const { email, password } = formData;
62 |
63 | // Check for valid email address.
64 | const isEmailValid: boolean = userValidator.validateEmail(email);
65 | if (!isEmailValid) {
66 | setFormData({
67 | ...formData,
68 | emailError: "Please provide a valid email address",
69 | });
70 | return;
71 | }
72 |
73 | // Check for valid password.
74 | if (!password) {
75 | setFormData({
76 | ...formData,
77 | passwordError: "Please provide a valid password",
78 | });
79 | return;
80 | }
81 |
82 | // Make API call if everything is fine.
83 | props.login(email, password);
84 | };
85 |
86 | // Return statement.
87 | return (
88 |
89 |
90 |
94 | <>
95 | {props.loginError && (
96 | {props.loginError}
97 | )}
98 | {/* The main Header */}
99 |
100 | Login
101 |
102 |
103 | {/* Email */}
104 | {
109 | handleInputChange(e);
110 | }}
111 | name="email"
112 | errorMsg={formData.emailError}
113 | />
114 |
115 | {/* Password */}
116 | {
121 | handleInputChange(e);
122 | }}
123 | name="password"
124 | errorMsg={formData.passwordError}
125 | />
126 |
127 | {/* Submit Button */}
128 | {
130 | submit();
131 | }}
132 | >
133 |
134 | Login
135 |
136 |
137 | {/* Additional links. */}
138 |
139 |
140 | No Account yet?
141 |
142 |
143 | Forgot password?
144 |
145 |
146 | >
147 |
148 |
149 |
150 | );
151 | };
152 |
153 | // Map redux states to local component props.
154 | const mapStateToProps = (state: any) => ({
155 | isAuthenticated: state.auth.isAuthenticated,
156 | loginError: state.auth.loginError,
157 | loading: state.auth.loginLoading,
158 | });
159 |
160 | // Define PropTypes.
161 | Login.propTypes = {
162 | props: PropTypes.object,
163 | login: PropTypes.func,
164 | };
165 |
166 | export default connect(mapStateToProps, { login })(Login);
167 |
--------------------------------------------------------------------------------
/client/pages/user/password/forgot.tsx:
--------------------------------------------------------------------------------
1 | import React, {ReactElement, useState} from "react";
2 | import {Card} from "@/components/Card/Card";
3 | import {TextInput} from "@/components/Form/FormElement";
4 | import {H1} from "@/components/Typography/Headers";
5 | import {PrimaryButton} from "@/components/Button/Button";
6 | import {connect} from "react-redux";
7 | import {forgotPassword} from "@/store/auth/authActions";
8 | import {Alert} from "@/components/Alert/Alert";
9 |
10 | function ForgotPassword(props: any): ReactElement {
11 | const [formData, setFormData] = useState<{
12 | email: string;
13 | emailError: string;
14 | notificationAlert: {
15 | type: string;
16 | msg: string;
17 | };
18 | }>({
19 | email: "",
20 | emailError: "",
21 | notificationAlert: {
22 | type: "",
23 | msg: "",
24 | },
25 | });
26 |
27 | const handleInputChange = (e: React.FormEvent): void => {
28 | setFormData({
29 | ...formData,
30 | [e.currentTarget.name]: e.currentTarget.value,
31 | emailError: "",
32 | });
33 | };
34 |
35 | /**
36 | * The submit action.
37 | */
38 | const submit = async (): Promise => {
39 |
40 | const res: any = await props.forgotPassword(formData.email);
41 | if (res.error) {
42 | setFormData({
43 | ...formData,
44 | notificationAlert: {
45 | type: "danger",
46 | msg: res.error,
47 | },
48 | });
49 | } else if (res.success) {
50 | setFormData({
51 | ...formData,
52 | notificationAlert: {
53 | type: "success",
54 | msg: res.success,
55 | },
56 | });
57 | }
58 | };
59 |
60 | // Returns statement.
61 | return (
62 |
63 |
65 |
69 | <>
70 | {/* Alert message. */}
71 | {formData.notificationAlert.type && (
72 |
73 | {formData.notificationAlert.msg}
74 |
75 | )}
76 |
77 | Forgot your password?
78 |
79 |
80 | Enter your email and we will send you a link to
81 | reset your password.
82 |
83 | {/* Email */}
84 | {
89 | handleInputChange(e);
90 | }}
91 | name="email"
92 | errorMsg={formData.emailError}
93 | />
94 |
95 | {/* Submit Button */}
96 | {
98 | submit();
99 | }}
100 | >
101 | Submit
102 |
103 | >
104 |
105 |
106 |
107 | );
108 | }
109 |
110 | export default connect(null, {forgotPassword})(ForgotPassword);
111 |
--------------------------------------------------------------------------------
/client/pages/user/password/reset/[token].tsx:
--------------------------------------------------------------------------------
1 | import {useRouter} from "next/router";
2 | import {Card} from "@/components/Card/Card";
3 | import {PrimaryButton} from "@/components/Button/Button";
4 | import {TextInput} from "@/components/Form/FormElement";
5 | import {H1} from "@/components/Typography/Headers";
6 | import React, {useState} from "react";
7 | import {UserValidator} from "@/services/UserValidator";
8 | import {connect} from "react-redux";
9 | import {resetPassword} from "@/store/auth/authActions";
10 | import {Alert} from "@/components/Alert/Alert";
11 |
12 | function ResetPassword(props: any) {
13 | const router = useRouter();
14 | const {token} = router.query;
15 |
16 | const [formData, setFormData] = useState<{
17 | email: string;
18 | emailError: string;
19 | password: string;
20 | passwordError: string;
21 | password_confirmed: string;
22 | password_confirmedError: string;
23 | error: string;
24 | }>({
25 | email: "",
26 | emailError: "",
27 | password: "",
28 | passwordError: "",
29 | password_confirmed: "",
30 | password_confirmedError: "",
31 | error: "",
32 | });
33 |
34 | /**
35 | * Handle input change.
36 | *
37 | * @param {object} e
38 | * The event object.
39 | */
40 | const handleInputChange = (e: React.FormEvent): void => {
41 | setFormData({
42 | ...formData,
43 | [e.currentTarget.name]: e.currentTarget.value,
44 | emailError: "",
45 | passwordError: "",
46 | });
47 | };
48 |
49 | /**
50 | * Validate the form data and send it to the api by dispatching a redux action.
51 | */
52 | const submit = async (): Promise => {
53 | const validator: UserValidator = new UserValidator();
54 |
55 | // Check if email is valid.
56 | const isEmailValid: boolean = validator.validateEmail(formData.email);
57 |
58 | if (!isEmailValid) {
59 | setFormData({
60 | ...formData,
61 | emailError: "Please enter a valid email address.",
62 | });
63 | return;
64 | }
65 |
66 | // Check if passwords are valid and equal.
67 | const arePasswordsValid: boolean = validator.validatePassword(
68 | formData.password,
69 | formData.password_confirmed,
70 | 8
71 | );
72 |
73 | if (!arePasswordsValid) {
74 | setFormData({
75 | ...formData,
76 | emailError:
77 | "Please enter a valid password and make sure you confirm it correctly.",
78 | });
79 | return;
80 | }
81 |
82 | // Make API request via redux.
83 | const res: any = await props.resetPassword(
84 | formData.email,
85 | formData.password,
86 | token
87 | );
88 |
89 | // Redirect to home route on successful password reset.
90 | if (res.success) {
91 | router.push(process.env.NEXT_PUBLIC_USER_HOME_ROUTE);
92 | }
93 |
94 | // Display danger notification if something went wrong.
95 | if (res.error) {
96 | setFormData({
97 | ...formData,
98 | error: res.error,
99 | });
100 | }
101 | };
102 |
103 | // Return statement.
104 | return (
105 |
106 |
108 |
112 | <>
113 | {/* Display error Message if applicable */}
114 | {formData.error && (
115 | {formData.error}
116 | )}
117 |
118 | {/* Primary Header */}
119 |
120 | Reset your password
121 |
122 |
123 | You now have the possibility to reset your password.
124 | Firstly, please confirm your email address and then
125 | create a new password.
126 |
127 |
128 | {/* Email */}
129 | {
134 | handleInputChange(e);
135 | }}
136 | name="email"
137 | errorMsg={formData.emailError}
138 | />
139 |
140 | {/* Password */}
141 | {
146 | handleInputChange(e);
147 | }}
148 | name="password"
149 | errorMsg={formData.passwordError}
150 | />
151 |
152 | {/* Password confirmed */}
153 | {
158 | handleInputChange(e);
159 | }}
160 | name="password_confirmed"
161 | errorMsg={formData.password_confirmedError}
162 | />
163 |
164 | {/* Submit Button */}
165 | {
167 | submit();
168 | }}
169 | >
170 | Submit
171 |
172 | >
173 |
174 |
175 |
176 | );
177 | }
178 |
179 | export default connect(null, {resetPassword})(ResetPassword);
180 |
--------------------------------------------------------------------------------
/client/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/client/services/Auth/AuthGuard.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | |--------------------------------------------------------------------------
3 | | Authenticator.
4 | |--------------------------------------------------------------------------
5 | |
6 | | A set of functions related to user authentication.
7 | |
8 | */
9 | import axios from "axios";
10 | import { IncomingMessage, ServerResponse } from "http";
11 | import { protectedRoutes } from "./../../config/config";
12 |
13 | export class AuthGuard {
14 | /**
15 | * An array of routes that should not be accessible by unauthenticated users.
16 | */
17 | private protectedRoutes: string[];
18 |
19 | /**
20 | * The constructor function.
21 | */
22 | constructor() {
23 | this.protectedRoutes = protectedRoutes;
24 | }
25 |
26 | /**
27 | * Get the current user from the database and redirect to dashboard if successful.
28 | *
29 | * @param {IncomingMessage} req
30 | * The request object.
31 | * @param {ServerResponse} res
32 | * The response object.
33 | * @param {string} destination
34 | * The destination URL the user will be redirected to if he's authenticated.
35 | *
36 | * @return {object}
37 | * An empty object. It is still necessary to return obj as getServerSideProps() requires it.
38 | */
39 | public async redirectOnAuthentication(
40 | req: IncomingMessage,
41 | res: ServerResponse,
42 | destination: string
43 | ) {
44 | try {
45 | // CSRF.
46 | await axios.get("/sanctum/csrf-cookie");
47 |
48 | /**
49 | * As the API call is executed on the server it by
50 | * default does not have the cookies set in the browser.
51 | * Fortunately, we can extract these cookies from the req object
52 | * and attach them to the api call.
53 | */
54 | const user = await axios.get("/api/user", {
55 | headers: { Cookie: req.headers.cookie },
56 | });
57 |
58 | // Redirect to dashboard if user is logged in.
59 | if (user.status === 200) {
60 | res.writeHead(301, {
61 | Location: destination,
62 | });
63 | res.end();
64 | return { props: {} };
65 | } else {
66 | return {
67 | props: {},
68 | };
69 | }
70 | } catch (error) {
71 | return { props: {} };
72 | }
73 | }
74 |
75 | /**
76 | * Load the currently logged in user from DB.
77 | *
78 | * @param {object} req
79 | * The request object.
80 | */
81 | public async authenticateUser(
82 | req: IncomingMessage,
83 | res: ServerResponse,
84 | pathname: string
85 | ) {
86 | const isNoProtectedRoute = this.isNoProtectedRoute(pathname);
87 | try {
88 | // CSRF.
89 | await axios.get("/sanctum/csrf-cookie");
90 |
91 | // If there are no cookies and the route is protected, redirect to login.
92 | if (!req.headers.cookie && !isNoProtectedRoute) {
93 | /**
94 | * No further redirect if we're already on the login
95 | * path, as we otherwisely would be caught in an
96 | * infinite loop of redirections to /user/login.
97 | */
98 | if (pathname === "/user/login") {
99 | res.end();
100 | return { user: false };
101 | }
102 |
103 | res.writeHead(302, {
104 | Location: "/user/login",
105 | });
106 | res.end();
107 | return { user: false };
108 | }
109 |
110 | /**
111 | * As the API call is executed on the server it by
112 | * default does not have the cookies set in the browser.
113 | * Fortunately, we can extract these cookies from the req object
114 | * and attach them to the api call.
115 | */
116 | const response = await axios.get("/api/user", {
117 | headers: { Cookie: req.headers.cookie },
118 | });
119 |
120 | // Abort if request was not successful.
121 | if (response.status !== 200) {
122 | res.end();
123 | return { user: false };
124 | }
125 |
126 | // New var with the current user data.
127 | const currentUser = response.data;
128 |
129 | // If user is authenticated and he requests login or register, redirect to dashboard.
130 | if (
131 | currentUser &&
132 | (pathname === "/user/register" || pathname === "/user/login")
133 | ) {
134 | res.writeHead(302, {
135 | Location: "/dashboard",
136 | });
137 | res.end();
138 | }
139 | // Redirect to login if user is not authenticated and tries to access protected route.
140 | else if (!currentUser && !isNoProtectedRoute) {
141 | res.writeHead(302, {
142 | Location: "/user/login",
143 | });
144 | res.end();
145 | return { user: false };
146 | }
147 |
148 | // Return the currently authenticated user.
149 | return {
150 | user: currentUser,
151 | };
152 | } catch (error) {
153 | /**
154 | * If the authentication fails (e.g. invalid session)
155 | * the API will send a 401 response. If we're on a
156 | * protected route, redirect to the login page.
157 | */
158 |
159 | if (
160 | error.response &&
161 | error.response.status === 401 &&
162 | !isNoProtectedRoute
163 | ) {
164 | if (pathname === "/user/login") {
165 | return { user: false };
166 | }
167 | res.writeHead(302, {
168 | Location: "/user/login",
169 | });
170 | res.end();
171 | }
172 | return { user: false };
173 | }
174 | }
175 |
176 | /**
177 | * Check if a given path is a protected one.
178 | *
179 | * @param {string} pathname
180 | * The current pathname.
181 | *
182 | * @return {boolean}
183 | * True if it is a protected route.
184 | */
185 | public isNoProtectedRoute(pathname: string): boolean {
186 | return this.protectedRoutes.every((route) => {
187 | return !pathname.startsWith(route);
188 | });
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/client/services/UserValidator.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | |--------------------------------------------------------------------------
3 | | User validation.
4 | |--------------------------------------------------------------------------
5 | |
6 | | Validate the input of a user upon her login or registration.
7 | |
8 | */
9 | import validator from "validator";
10 |
11 | export class UserValidator {
12 | /**
13 | *
14 | * @param {string} name
15 | * The name of the new user.
16 | * @param {string} email
17 | * The email of the new user.
18 | * @param {string} password
19 | * The password of the new user.
20 | * @param {string} passwordConfirmed
21 | * The password confirmation.
22 | * @param {number} minPassChars
23 | * The minimum number of characters the password must have.
24 | *
25 | * @return {boolean}
26 | * True if validation was successful.
27 | */
28 | public validateRegistrationInput(
29 | name: string,
30 | email: string,
31 | password: string,
32 | passwordConfirmed: string,
33 | minPassChars: number
34 | ): { name: string; email: string; password: string } | boolean {
35 | let errorDetected = false;
36 | const errors = {
37 | name: "",
38 | password: "",
39 | email: "",
40 | };
41 | // Check name.
42 | const isNameValid = this.validateName(name);
43 | if (!isNameValid) {
44 | errors.name = "The name may only contain letters.";
45 | errorDetected = true;
46 | }
47 |
48 | // Check password.
49 | const isPasswordValid = this.validatePassword(
50 | password,
51 | passwordConfirmed,
52 | minPassChars
53 | );
54 | if (!isPasswordValid) {
55 | errors.password =
56 | "The password must be at least 8 characters long.";
57 | errorDetected = true;
58 | }
59 |
60 | // Check email.
61 | const isEmailValid = this.validateEmail(email);
62 | if (!isEmailValid) {
63 | errors.email = "Please provide a valid email address.";
64 | errorDetected = true;
65 | }
66 |
67 | // Return true if everythin is valid.
68 | if (!errorDetected) {
69 | return true;
70 | }
71 | return errors;
72 | }
73 |
74 | /**
75 | * Check if the name of the user only contains letters.
76 | *
77 | * @param {string} name
78 | * The name of the user.
79 | */
80 | public validateName(name: string): boolean {
81 | // Removes spaces as validator does not count them as letters.
82 | const tmp = name.replace(" ", "");
83 | return validator.isAlpha(tmp);
84 | }
85 |
86 | /**
87 | * Confirm the users password when registering.
88 | *
89 | * @param {string} password
90 | * The password.
91 | * @param {string} passwordConfirmed
92 | * The password confirmation.
93 | * @param {number} minPasswordLength
94 | * The minimum password length.
95 | *
96 | * @return {boolean}
97 | * True if validation was successful.
98 | */
99 | public validatePassword(
100 | password: string,
101 | passwordConfirmed: string,
102 | minPasswordLength: number
103 | ): boolean {
104 | // Check password length:
105 | if (password.length < minPasswordLength) {
106 | return false;
107 | }
108 |
109 | // Check that password it not too soft:
110 | if (password.includes("passwor") || password.includes("123456")) {
111 | return false;
112 | }
113 |
114 | // Passwords must be equal.
115 | if (password !== passwordConfirmed) {
116 | return false;
117 | }
118 |
119 | // Return true if everything is fine.
120 | return true;
121 | }
122 |
123 | /**
124 | * Check if the user gave a valid email address.
125 | *
126 | * @param {string} email
127 | * The email that is validated.
128 | *
129 | * @return {boolean}
130 | * True if email is valid.
131 | */
132 | public validateEmail(email: string): boolean {
133 | // Let the validator package handle the big lifting.
134 | if (!validator.isEmail(email)) {
135 | return false;
136 | }
137 |
138 | // List of strings within the mail that are suspicious.
139 | const blacklist = ["@example", "@email"];
140 |
141 | // Check that none of the blacklist strings is in the mail address.
142 | const noFraudDetected = blacklist.every((item) => {
143 | return !email.includes(item);
144 | });
145 |
146 | if (!noFraudDetected) {
147 | return false;
148 | }
149 |
150 | return true;
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/client/store/actionTypes.tsx:
--------------------------------------------------------------------------------
1 | //ALERTS:
2 | export const SET_ALERT = "SET_ALERT";
3 | export const REMOVE_ALERT = "REMOVE_ALERT";
4 |
5 | export const AUTH_SUCCESS = "AUTH_SUCCESS";
6 | export const USER_LOADED = "USER_LOADED";
7 | export const AUTHENTICATION_ERROR = "AUTHENTICATION_ERROR";
8 | export const REGISTER_ERROR = "REGISTER_ERROR";
9 | export const REGISTER_SUCCESS = "REGISTER_SUCCESS";
10 | export const LOGIN_ERROR = "LOGIN_ERROR";
11 | export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
12 | export const USER_LOADED_ERROR = "USER_LOADED_ERROR";
13 | export const START_LOGIN_LOADING = "START_LOGIN_LOADING";
14 | export const START_REGISTER_LOADING = "START_REGISTER_LOADING";
15 | export const LOGOUT = "LOGOUT";
16 | export const AUTH_GENERAL_ERROR = "AUTH_GENERAL_ERROR";
17 | export const RESET_PASSWORD_LINK_SENT = "RESET_PASSWORD_LINK_SENT";
18 |
--------------------------------------------------------------------------------
/client/store/auth/authReducer.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | |--------------------------------------------------------------------------
3 | | Auth store.
4 | |--------------------------------------------------------------------------
5 | |
6 | | Here you can find the store for the authentication part of the application
7 | | It manages all authentication data like the current user, auth status and errors.
8 | |
9 | */
10 |
11 | import * as types from "../actionTypes";
12 |
13 | // The initial state.
14 | const initState = {
15 | isAuthenticated: false,
16 | loading: true,
17 | loginLoading: false,
18 | user: {},
19 | loginError: "",
20 | registerError: "",
21 | authError: "",
22 | userLoadedError: "",
23 | };
24 |
25 | /**
26 | * The auth store.
27 | *
28 | * @param {object} state
29 | * The inital state.
30 | * @param {object} action
31 | * The dispatched action.
32 | */
33 | const auth = (state = initState, action: { type: string; payload: any }) => {
34 | switch (action.type) {
35 | case types.AUTH_SUCCESS:
36 | return {
37 | ...state,
38 | loading: false,
39 | isAuthenticated: true,
40 | loginError: "",
41 | registerError: "",
42 | };
43 | case types.USER_LOADED:
44 | return {
45 | ...state,
46 | loading: false,
47 | isAuthenticated: true,
48 | loginError: "",
49 | registerError: "",
50 | user: action.payload,
51 | };
52 | case types.USER_LOADED_ERROR:
53 | return {
54 | ...state,
55 | loading: false,
56 | isAuthenticated: false,
57 | userLoadedError: action.payload,
58 | };
59 | case types.START_LOGIN_LOADING:
60 | return {
61 | ...state,
62 | loginLoading: true,
63 | };
64 | case types.LOGIN_SUCCESS:
65 | return {
66 | ...state,
67 | isAuthenticated: true,
68 | loginLoading: false,
69 | };
70 | case types.LOGIN_ERROR:
71 | return {
72 | ...state,
73 | loginLoading: false,
74 | isAuthenticated: false,
75 | user: {},
76 | loginError: action.payload,
77 | };
78 | case types.START_REGISTER_LOADING:
79 | return {
80 | ...state,
81 | registerLoading: true,
82 | };
83 | case types.REGISTER_SUCCESS:
84 | return {
85 | ...state,
86 | isAuthenticated: true,
87 | registerLoading: false,
88 | };
89 | case types.REGISTER_ERROR:
90 | return {
91 | ...state,
92 | registerLoading: false,
93 | isAuthenticated: false,
94 | user: {},
95 | registerError: action.payload,
96 | };
97 | case types.LOGOUT:
98 | return {
99 | ...state,
100 | isAuthenticated: false,
101 | loading: false,
102 | loginLoading: false,
103 | user: {},
104 | loginError: "",
105 | registerError: "",
106 | authError: "",
107 | userLoadedError: "",
108 | };
109 | case types.AUTH_GENERAL_ERROR:
110 | case types.RESET_PASSWORD_LINK_SENT:
111 | return {
112 | ...state,
113 | isAuthenticated: false,
114 | loading: false,
115 | loginLoading: false,
116 | user: {},
117 | loginError: "",
118 | registerError: "",
119 | authError: "",
120 | userLoadedError: "",
121 | };
122 | }
123 | return state;
124 | };
125 |
126 | export default auth;
127 |
--------------------------------------------------------------------------------
/client/store/store.tsx:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, combineReducers } from "redux";
2 | import { composeWithDevTools } from "redux-devtools-extension";
3 | import thunk from "redux-thunk";
4 |
5 | // Reducers.
6 | import auth from "./auth/authReducer";
7 |
8 | // The inital state. Will be merged with partials states.
9 | const initState = {};
10 |
11 | // Combine all partial reducers.
12 | const rootReducer = combineReducers({
13 | auth,
14 | // Add your stores here.
15 | });
16 |
17 | const middleware = [thunk];
18 |
19 | // Create reduc store of all existing stores. Also init devtools.
20 | const store = createStore(
21 | rootReducer,
22 | initState,
23 | composeWithDevTools(applyMiddleware(...middleware))
24 | );
25 |
26 | export default store;
27 |
--------------------------------------------------------------------------------
/client/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | .border-top-colored {
6 | border-top-color: #4f46e5;
7 | }
8 |
--------------------------------------------------------------------------------
/client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": false,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "baseUrl": ".",
21 | "paths": {
22 | "@/components/*": [
23 | "components/*"
24 | ],
25 | "@/services/*": [
26 | "services/*"
27 | ],
28 | "@/store/*": [
29 | "store/*"
30 | ]
31 | },
32 | "incremental": true
33 | },
34 | "include": [
35 | "next-env.d.ts",
36 | "**/*.ts",
37 | "**/*.tsx"
38 | ],
39 | "exclude": [
40 | "node_modules"
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laravel/laravel",
3 | "type": "project",
4 | "description": "The Laravel Framework.",
5 | "keywords": [
6 | "framework",
7 | "laravel"
8 | ],
9 | "license": "MIT",
10 | "require": {
11 | "php": "^7.3|^8.0",
12 | "fideloper/proxy": "^4.4",
13 | "fruitcake/laravel-cors": "^2.0",
14 | "guzzlehttp/guzzle": "^7.0.1",
15 | "laravel/framework": "^8.12",
16 | "laravel/sanctum": "^2.8",
17 | "laravel/tinker": "^2.5",
18 | "laravel/ui": "^3.1",
19 | "symfony/mailer": "^6.1"
20 | },
21 | "require-dev": {
22 | "facade/ignition": "^2.5",
23 | "fakerphp/faker": "^1.9.1",
24 | "laravel/sail": "^0.0.5",
25 | "mockery/mockery": "^1.4.2",
26 | "nunomaduro/collision": "^5.0",
27 | "phpunit/phpunit": "^9.3.3"
28 | },
29 | "config": {
30 | "optimize-autoloader": true,
31 | "preferred-install": "dist",
32 | "sort-packages": true
33 | },
34 | "extra": {
35 | "laravel": {
36 | "dont-discover": []
37 | }
38 | },
39 | "autoload": {
40 | "psr-4": {
41 | "App\\": "app/",
42 | "Database\\Factories\\": "database/factories/",
43 | "Database\\Seeders\\": "database/seeders/"
44 | }
45 | },
46 | "autoload-dev": {
47 | "psr-4": {
48 | "Tests\\": "tests/"
49 | }
50 | },
51 | "minimum-stability": "dev",
52 | "prefer-stable": true,
53 | "scripts": {
54 | "post-autoload-dump": [
55 | "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
56 | "@php artisan package:discover --ansi"
57 | ],
58 | "post-root-package-install": [
59 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
60 | ],
61 | "post-create-project-cmd": [
62 | "@php artisan key:generate --ansi"
63 | ]
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/config/auth.php:
--------------------------------------------------------------------------------
1 | [
17 | 'guard' => 'web',
18 | 'passwords' => 'users',
19 | ],
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | Authentication Guards
24 | |--------------------------------------------------------------------------
25 | |
26 | | Next, you may define every authentication guard for your application.
27 | | Of course, a great default configuration has been defined for you
28 | | here which uses session storage and the Eloquent user provider.
29 | |
30 | | All authentication drivers have a user provider. This defines how the
31 | | users are actually retrieved out of your database or other storage
32 | | mechanisms used by this application to persist your user's data.
33 | |
34 | | Supported: "session", "token"
35 | |
36 | */
37 |
38 | 'guards' => [
39 | 'web' => [
40 | 'driver' => 'session',
41 | 'provider' => 'users',
42 | ],
43 |
44 | 'api' => [
45 | 'driver' => 'token',
46 | 'provider' => 'users',
47 | 'hash' => false,
48 | ],
49 | ],
50 |
51 | /*
52 | |--------------------------------------------------------------------------
53 | | User Providers
54 | |--------------------------------------------------------------------------
55 | |
56 | | All authentication drivers have a user provider. This defines how the
57 | | users are actually retrieved out of your database or other storage
58 | | mechanisms used by this application to persist your user's data.
59 | |
60 | | If you have multiple user tables or models you may configure multiple
61 | | sources which represent each model / table. These sources may then
62 | | be assigned to any extra authentication guards you have defined.
63 | |
64 | | Supported: "database", "eloquent"
65 | |
66 | */
67 |
68 | 'providers' => [
69 | 'users' => [
70 | 'driver' => 'eloquent',
71 | 'model' => App\Models\User::class,
72 | ],
73 |
74 | // 'users' => [
75 | // 'driver' => 'database',
76 | // 'table' => 'users',
77 | // ],
78 | ],
79 |
80 | /*
81 | |--------------------------------------------------------------------------
82 | | Resetting Passwords
83 | |--------------------------------------------------------------------------
84 | |
85 | | You may specify multiple password reset configurations if you have more
86 | | than one user table or model in the application and you want to have
87 | | separate password reset settings based on the specific user types.
88 | |
89 | | The expire time is the number of minutes that the reset token should be
90 | | considered valid. This security feature keeps tokens short-lived so
91 | | they have less time to be guessed. You may change this as needed.
92 | |
93 | */
94 |
95 | 'passwords' => [
96 | 'users' => [
97 | 'provider' => 'users',
98 | 'table' => 'password_resets',
99 | 'expire' => 60,
100 | 'throttle' => 60,
101 | ],
102 | ],
103 |
104 | /*
105 | |--------------------------------------------------------------------------
106 | | Password Confirmation Timeout
107 | |--------------------------------------------------------------------------
108 | |
109 | | Here you may define the amount of seconds before a password confirmation
110 | | times out and the user is prompted to re-enter their password via the
111 | | confirmation screen. By default, the timeout lasts for three hours.
112 | |
113 | */
114 |
115 | 'password_timeout' => 10800,
116 |
117 | ];
118 |
--------------------------------------------------------------------------------
/config/broadcasting.php:
--------------------------------------------------------------------------------
1 | env('BROADCAST_DRIVER', 'null'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Broadcast Connections
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may define all of the broadcast connections that will be used
26 | | to broadcast events to other systems or over websockets. Samples of
27 | | each available type of connection are provided inside this array.
28 | |
29 | */
30 |
31 | 'connections' => [
32 |
33 | 'pusher' => [
34 | 'driver' => 'pusher',
35 | 'key' => env('PUSHER_APP_KEY'),
36 | 'secret' => env('PUSHER_APP_SECRET'),
37 | 'app_id' => env('PUSHER_APP_ID'),
38 | 'options' => [
39 | 'cluster' => env('PUSHER_APP_CLUSTER'),
40 | 'useTLS' => true,
41 | ],
42 | ],
43 |
44 | 'ably' => [
45 | 'driver' => 'ably',
46 | 'key' => env('ABLY_KEY'),
47 | ],
48 |
49 | 'redis' => [
50 | 'driver' => 'redis',
51 | 'connection' => 'default',
52 | ],
53 |
54 | 'log' => [
55 | 'driver' => 'log',
56 | ],
57 |
58 | 'null' => [
59 | 'driver' => 'null',
60 | ],
61 |
62 | ],
63 |
64 | ];
65 |
--------------------------------------------------------------------------------
/config/cache.php:
--------------------------------------------------------------------------------
1 | env('CACHE_DRIVER', 'file'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Cache Stores
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may define all of the cache "stores" for your application as
26 | | well as their drivers. You may even define multiple stores for the
27 | | same cache driver to group types of items stored in your caches.
28 | |
29 | | Supported drivers: "apc", "array", "database", "file",
30 | | "memcached", "redis", "dynamodb", "null"
31 | |
32 | */
33 |
34 | 'stores' => [
35 |
36 | 'apc' => [
37 | 'driver' => 'apc',
38 | ],
39 |
40 | 'array' => [
41 | 'driver' => 'array',
42 | 'serialize' => false,
43 | ],
44 |
45 | 'database' => [
46 | 'driver' => 'database',
47 | 'table' => 'cache',
48 | 'connection' => null,
49 | 'lock_connection' => null,
50 | ],
51 |
52 | 'file' => [
53 | 'driver' => 'file',
54 | 'path' => storage_path('framework/cache/data'),
55 | ],
56 |
57 | 'memcached' => [
58 | 'driver' => 'memcached',
59 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
60 | 'sasl' => [
61 | env('MEMCACHED_USERNAME'),
62 | env('MEMCACHED_PASSWORD'),
63 | ],
64 | 'options' => [
65 | // Memcached::OPT_CONNECT_TIMEOUT => 2000,
66 | ],
67 | 'servers' => [
68 | [
69 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'),
70 | 'port' => env('MEMCACHED_PORT', 11211),
71 | 'weight' => 100,
72 | ],
73 | ],
74 | ],
75 |
76 | 'redis' => [
77 | 'driver' => 'redis',
78 | 'connection' => 'cache',
79 | 'lock_connection' => 'default',
80 | ],
81 |
82 | 'dynamodb' => [
83 | 'driver' => 'dynamodb',
84 | 'key' => env('AWS_ACCESS_KEY_ID'),
85 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
86 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
87 | 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
88 | 'endpoint' => env('DYNAMODB_ENDPOINT'),
89 | ],
90 |
91 | ],
92 |
93 | /*
94 | |--------------------------------------------------------------------------
95 | | Cache Key Prefix
96 | |--------------------------------------------------------------------------
97 | |
98 | | When utilizing a RAM based store such as APC or Memcached, there might
99 | | be other applications utilizing the same cache. So, we'll specify a
100 | | value to get prefixed to all our keys so we can avoid collisions.
101 | |
102 | */
103 |
104 | 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'),
105 |
106 | ];
107 |
--------------------------------------------------------------------------------
/config/cors.php:
--------------------------------------------------------------------------------
1 | ['api/*', 'sanctum/csrf-cookie', 'login', 'logout', 'register', "password/email", "password/reset", "email/verify/*"],
19 |
20 | 'allowed_methods' => ['*'],
21 |
22 | 'allowed_origins' => ['*'],
23 |
24 | 'allowed_origins_patterns' => [],
25 |
26 | 'allowed_headers' => ['*'],
27 |
28 | 'exposed_headers' => [],
29 |
30 | 'max_age' => 0,
31 |
32 | 'supports_credentials' => true,
33 |
34 | ];
--------------------------------------------------------------------------------
/config/database.php:
--------------------------------------------------------------------------------
1 | env('DB_CONNECTION', 'mysql'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Database Connections
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here are each of the database connections setup for your application.
26 | | Of course, examples of configuring each database platform that is
27 | | supported by Laravel is shown below to make development simple.
28 | |
29 | |
30 | | All database work in Laravel is done through the PHP PDO facilities
31 | | so make sure you have the driver for your particular database of
32 | | choice installed on your machine before you begin development.
33 | |
34 | */
35 |
36 | 'connections' => [
37 |
38 | 'sqlite' => [
39 | 'driver' => 'sqlite',
40 | 'url' => env('DATABASE_URL'),
41 | 'database' => env('DB_DATABASE', database_path('database.sqlite')),
42 | 'prefix' => '',
43 | 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
44 | ],
45 |
46 | 'mysql' => [
47 | 'driver' => 'mysql',
48 | 'url' => env('DATABASE_URL'),
49 | 'host' => env('DB_HOST', '127.0.0.1'),
50 | 'port' => env('DB_PORT', '3306'),
51 | 'database' => env('DB_DATABASE', 'forge'),
52 | 'username' => env('DB_USERNAME', 'forge'),
53 | 'password' => env('DB_PASSWORD', ''),
54 | 'unix_socket' => env('DB_SOCKET', ''),
55 | 'charset' => 'utf8mb4',
56 | 'collation' => 'utf8mb4_unicode_ci',
57 | 'prefix' => '',
58 | 'prefix_indexes' => true,
59 | 'strict' => true,
60 | 'engine' => null,
61 | 'options' => extension_loaded('pdo_mysql') ? array_filter([
62 | PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
63 | ]) : [],
64 | ],
65 |
66 | 'pgsql' => [
67 | 'driver' => 'pgsql',
68 | 'url' => env('DATABASE_URL'),
69 | 'host' => env('DB_HOST', '127.0.0.1'),
70 | 'port' => env('DB_PORT', '5432'),
71 | 'database' => env('DB_DATABASE', 'forge'),
72 | 'username' => env('DB_USERNAME', 'forge'),
73 | 'password' => env('DB_PASSWORD', ''),
74 | 'charset' => 'utf8',
75 | 'prefix' => '',
76 | 'prefix_indexes' => true,
77 | 'schema' => 'public',
78 | 'sslmode' => 'prefer',
79 | ],
80 |
81 | 'sqlsrv' => [
82 | 'driver' => 'sqlsrv',
83 | 'url' => env('DATABASE_URL'),
84 | 'host' => env('DB_HOST', 'localhost'),
85 | 'port' => env('DB_PORT', '1433'),
86 | 'database' => env('DB_DATABASE', 'forge'),
87 | 'username' => env('DB_USERNAME', 'forge'),
88 | 'password' => env('DB_PASSWORD', ''),
89 | 'charset' => 'utf8',
90 | 'prefix' => '',
91 | 'prefix_indexes' => true,
92 | ],
93 |
94 | ],
95 |
96 | /*
97 | |--------------------------------------------------------------------------
98 | | Migration Repository Table
99 | |--------------------------------------------------------------------------
100 | |
101 | | This table keeps track of all the migrations that have already run for
102 | | your application. Using this information, we can determine which of
103 | | the migrations on disk haven't actually been run in the database.
104 | |
105 | */
106 |
107 | 'migrations' => 'migrations',
108 |
109 | /*
110 | |--------------------------------------------------------------------------
111 | | Redis Databases
112 | |--------------------------------------------------------------------------
113 | |
114 | | Redis is an open source, fast, and advanced key-value store that also
115 | | provides a richer body of commands than a typical key-value system
116 | | such as APC or Memcached. Laravel makes it easy to dig right in.
117 | |
118 | */
119 |
120 | 'redis' => [
121 |
122 | 'client' => env('REDIS_CLIENT', 'phpredis'),
123 |
124 | 'options' => [
125 | 'cluster' => env('REDIS_CLUSTER', 'redis'),
126 | 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
127 | ],
128 |
129 | 'default' => [
130 | 'url' => env('REDIS_URL'),
131 | 'host' => env('REDIS_HOST', '127.0.0.1'),
132 | 'password' => env('REDIS_PASSWORD', null),
133 | 'port' => env('REDIS_PORT', '6379'),
134 | 'database' => env('REDIS_DB', '0'),
135 | ],
136 |
137 | 'cache' => [
138 | 'url' => env('REDIS_URL'),
139 | 'host' => env('REDIS_HOST', '127.0.0.1'),
140 | 'password' => env('REDIS_PASSWORD', null),
141 | 'port' => env('REDIS_PORT', '6379'),
142 | 'database' => env('REDIS_CACHE_DB', '1'),
143 | ],
144 |
145 | ],
146 |
147 | ];
148 |
--------------------------------------------------------------------------------
/config/filesystems.php:
--------------------------------------------------------------------------------
1 | env('FILESYSTEM_DRIVER', 'local'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Filesystem Disks
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may configure as many filesystem "disks" as you wish, and you
24 | | may even configure multiple disks of the same driver. Defaults have
25 | | been setup for each driver as an example of the required options.
26 | |
27 | | Supported Drivers: "local", "ftp", "sftp", "s3"
28 | |
29 | */
30 |
31 | 'disks' => [
32 |
33 | 'local' => [
34 | 'driver' => 'local',
35 | 'root' => storage_path('app'),
36 | ],
37 |
38 | 'public' => [
39 | 'driver' => 'local',
40 | 'root' => storage_path('app/public'),
41 | 'url' => env('APP_URL').'/storage',
42 | 'visibility' => 'public',
43 | ],
44 |
45 | 's3' => [
46 | 'driver' => 's3',
47 | 'key' => env('AWS_ACCESS_KEY_ID'),
48 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
49 | 'region' => env('AWS_DEFAULT_REGION'),
50 | 'bucket' => env('AWS_BUCKET'),
51 | 'url' => env('AWS_URL'),
52 | 'endpoint' => env('AWS_ENDPOINT'),
53 | ],
54 |
55 | ],
56 |
57 | /*
58 | |--------------------------------------------------------------------------
59 | | Symbolic Links
60 | |--------------------------------------------------------------------------
61 | |
62 | | Here you may configure the symbolic links that will be created when the
63 | | `storage:link` Artisan command is executed. The array keys should be
64 | | the locations of the links and the values should be their targets.
65 | |
66 | */
67 |
68 | 'links' => [
69 | public_path('storage') => storage_path('app/public'),
70 | ],
71 |
72 | ];
73 |
--------------------------------------------------------------------------------
/config/hashing.php:
--------------------------------------------------------------------------------
1 | 'bcrypt',
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Bcrypt Options
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may specify the configuration options that should be used when
26 | | passwords are hashed using the Bcrypt algorithm. This will allow you
27 | | to control the amount of time it takes to hash the given password.
28 | |
29 | */
30 |
31 | 'bcrypt' => [
32 | 'rounds' => env('BCRYPT_ROUNDS', 10),
33 | ],
34 |
35 | /*
36 | |--------------------------------------------------------------------------
37 | | Argon Options
38 | |--------------------------------------------------------------------------
39 | |
40 | | Here you may specify the configuration options that should be used when
41 | | passwords are hashed using the Argon algorithm. These will allow you
42 | | to control the amount of time it takes to hash the given password.
43 | |
44 | */
45 |
46 | 'argon' => [
47 | 'memory' => 1024,
48 | 'threads' => 2,
49 | 'time' => 2,
50 | ],
51 |
52 | ];
53 |
--------------------------------------------------------------------------------
/config/logging.php:
--------------------------------------------------------------------------------
1 | env('LOG_CHANNEL', 'stack'),
21 |
22 | /*
23 | |--------------------------------------------------------------------------
24 | | Log Channels
25 | |--------------------------------------------------------------------------
26 | |
27 | | Here you may configure the log channels for your application. Out of
28 | | the box, Laravel uses the Monolog PHP logging library. This gives
29 | | you a variety of powerful log handlers / formatters to utilize.
30 | |
31 | | Available Drivers: "single", "daily", "slack", "syslog",
32 | | "errorlog", "monolog",
33 | | "custom", "stack"
34 | |
35 | */
36 |
37 | 'channels' => [
38 | 'stack' => [
39 | 'driver' => 'stack',
40 | 'channels' => ['single'],
41 | 'ignore_exceptions' => false,
42 | ],
43 |
44 | 'single' => [
45 | 'driver' => 'single',
46 | 'path' => storage_path('logs/laravel.log'),
47 | 'level' => env('LOG_LEVEL', 'debug'),
48 | ],
49 |
50 | 'daily' => [
51 | 'driver' => 'daily',
52 | 'path' => storage_path('logs/laravel.log'),
53 | 'level' => env('LOG_LEVEL', 'debug'),
54 | 'days' => 14,
55 | ],
56 |
57 | 'slack' => [
58 | 'driver' => 'slack',
59 | 'url' => env('LOG_SLACK_WEBHOOK_URL'),
60 | 'username' => 'Laravel Log',
61 | 'emoji' => ':boom:',
62 | 'level' => env('LOG_LEVEL', 'critical'),
63 | ],
64 |
65 | 'papertrail' => [
66 | 'driver' => 'monolog',
67 | 'level' => env('LOG_LEVEL', 'debug'),
68 | 'handler' => SyslogUdpHandler::class,
69 | 'handler_with' => [
70 | 'host' => env('PAPERTRAIL_URL'),
71 | 'port' => env('PAPERTRAIL_PORT'),
72 | ],
73 | ],
74 |
75 | 'stderr' => [
76 | 'driver' => 'monolog',
77 | 'handler' => StreamHandler::class,
78 | 'formatter' => env('LOG_STDERR_FORMATTER'),
79 | 'with' => [
80 | 'stream' => 'php://stderr',
81 | ],
82 | ],
83 |
84 | 'syslog' => [
85 | 'driver' => 'syslog',
86 | 'level' => env('LOG_LEVEL', 'debug'),
87 | ],
88 |
89 | 'errorlog' => [
90 | 'driver' => 'errorlog',
91 | 'level' => env('LOG_LEVEL', 'debug'),
92 | ],
93 |
94 | 'null' => [
95 | 'driver' => 'monolog',
96 | 'handler' => NullHandler::class,
97 | ],
98 |
99 | 'emergency' => [
100 | 'path' => storage_path('logs/laravel.log'),
101 | ],
102 | ],
103 |
104 | ];
105 |
--------------------------------------------------------------------------------
/config/mail.php:
--------------------------------------------------------------------------------
1 | env('MAIL_MAILER', 'smtp'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Mailer Configurations
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may configure all of the mailers used by your application plus
24 | | their respective settings. Several examples have been configured for
25 | | you and you are free to add your own as your application requires.
26 | |
27 | | Laravel supports a variety of mail "transport" drivers to be used while
28 | | sending an e-mail. You will specify which one you are using for your
29 | | mailers below. You are free to add additional mailers as required.
30 | |
31 | | Supported: "smtp", "sendmail", "mailgun", "ses",
32 | | "postmark", "log", "array"
33 | |
34 | */
35 |
36 | 'mailers' => [
37 | 'smtp' => [
38 | 'transport' => 'smtp',
39 | 'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
40 | 'port' => env('MAIL_PORT', 587),
41 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'),
42 | 'username' => env('MAIL_USERNAME'),
43 | 'password' => env('MAIL_PASSWORD'),
44 | 'timeout' => null,
45 | 'auth_mode' => null,
46 | ],
47 |
48 | 'ses' => [
49 | 'transport' => 'ses',
50 | ],
51 |
52 | 'mailgun' => [
53 | 'transport' => 'mailgun',
54 | ],
55 |
56 | 'postmark' => [
57 | 'transport' => 'postmark',
58 | ],
59 |
60 | 'sendmail' => [
61 | 'transport' => 'sendmail',
62 | 'path' => '/usr/sbin/sendmail -bs',
63 | ],
64 |
65 | 'log' => [
66 | 'transport' => 'log',
67 | 'channel' => env('MAIL_LOG_CHANNEL'),
68 | ],
69 |
70 | 'array' => [
71 | 'transport' => 'array',
72 | ],
73 | ],
74 |
75 | /*
76 | |--------------------------------------------------------------------------
77 | | Global "From" Address
78 | |--------------------------------------------------------------------------
79 | |
80 | | You may wish for all e-mails sent by your application to be sent from
81 | | the same address. Here, you may specify a name and address that is
82 | | used globally for all e-mails that are sent by your application.
83 | |
84 | */
85 |
86 | 'from' => [
87 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
88 | 'name' => env('MAIL_FROM_NAME', 'Example'),
89 | ],
90 |
91 | /*
92 | |--------------------------------------------------------------------------
93 | | Markdown Mail Settings
94 | |--------------------------------------------------------------------------
95 | |
96 | | If you are using Markdown based email rendering, you may configure your
97 | | theme and component paths here, allowing you to customize the design
98 | | of the emails. Or, you may simply stick with the Laravel defaults!
99 | |
100 | */
101 |
102 | 'markdown' => [
103 | 'theme' => 'default',
104 |
105 | 'paths' => [
106 | resource_path('views/vendor/mail'),
107 | ],
108 | ],
109 |
110 | ];
111 |
--------------------------------------------------------------------------------
/config/queue.php:
--------------------------------------------------------------------------------
1 | env('QUEUE_CONNECTION', 'sync'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Queue Connections
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may configure the connection information for each server that
24 | | is used by your application. A default configuration has been added
25 | | for each back-end shipped with Laravel. You are free to add more.
26 | |
27 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
28 | |
29 | */
30 |
31 | 'connections' => [
32 |
33 | 'sync' => [
34 | 'driver' => 'sync',
35 | ],
36 |
37 | 'database' => [
38 | 'driver' => 'database',
39 | 'table' => 'jobs',
40 | 'queue' => 'default',
41 | 'retry_after' => 90,
42 | ],
43 |
44 | 'beanstalkd' => [
45 | 'driver' => 'beanstalkd',
46 | 'host' => 'localhost',
47 | 'queue' => 'default',
48 | 'retry_after' => 90,
49 | 'block_for' => 0,
50 | ],
51 |
52 | 'sqs' => [
53 | 'driver' => 'sqs',
54 | 'key' => env('AWS_ACCESS_KEY_ID'),
55 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
56 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
57 | 'queue' => env('SQS_QUEUE', 'your-queue-name'),
58 | 'suffix' => env('SQS_SUFFIX'),
59 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
60 | ],
61 |
62 | 'redis' => [
63 | 'driver' => 'redis',
64 | 'connection' => 'default',
65 | 'queue' => env('REDIS_QUEUE', 'default'),
66 | 'retry_after' => 90,
67 | 'block_for' => null,
68 | ],
69 |
70 | ],
71 |
72 | /*
73 | |--------------------------------------------------------------------------
74 | | Failed Queue Jobs
75 | |--------------------------------------------------------------------------
76 | |
77 | | These options configure the behavior of failed queue job logging so you
78 | | can control which database and table are used to store the jobs that
79 | | have failed. You may change them to any database / table you wish.
80 | |
81 | */
82 |
83 | 'failed' => [
84 | 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
85 | 'database' => env('DB_CONNECTION', 'mysql'),
86 | 'table' => 'failed_jobs',
87 | ],
88 |
89 | ];
90 |
--------------------------------------------------------------------------------
/config/sanctum.php:
--------------------------------------------------------------------------------
1 | explode(',', env(
17 | 'SANCTUM_STATEFUL_DOMAINS',
18 | 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1'
19 | )),
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | Expiration Minutes
24 | |--------------------------------------------------------------------------
25 | |
26 | | This value controls the number of minutes until an issued token will be
27 | | considered expired. If this value is null, personal access tokens do
28 | | not expire. This won't tweak the lifetime of first-party sessions.
29 | |
30 | */
31 |
32 | 'expiration' => null,
33 |
34 | /*
35 | |--------------------------------------------------------------------------
36 | | Sanctum Middleware
37 | |--------------------------------------------------------------------------
38 | |
39 | | When authenticating your first-party SPA with Sanctum you may need to
40 | | customize some of the middleware Sanctum uses while processing the
41 | | request. You may change the middleware listed below as required.
42 | |
43 | */
44 |
45 | 'middleware' => [
46 | 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
47 | 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
48 | ],
49 |
50 | ];
51 |
--------------------------------------------------------------------------------
/config/services.php:
--------------------------------------------------------------------------------
1 | [
18 | 'domain' => env('MAILGUN_DOMAIN'),
19 | 'secret' => env('MAILGUN_SECRET'),
20 | 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
21 | ],
22 |
23 | 'postmark' => [
24 | 'token' => env('POSTMARK_TOKEN'),
25 | ],
26 |
27 | 'ses' => [
28 | 'key' => env('AWS_ACCESS_KEY_ID'),
29 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
30 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
31 | ],
32 |
33 | ];
34 |
--------------------------------------------------------------------------------
/config/view.php:
--------------------------------------------------------------------------------
1 | [
17 | resource_path('views'),
18 | ],
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Compiled View Path
23 | |--------------------------------------------------------------------------
24 | |
25 | | This option determines where all the compiled Blade templates will be
26 | | stored for your application. Typically, this is within the storage
27 | | directory. However, as usual, you are free to change this value.
28 | |
29 | */
30 |
31 | 'compiled' => env(
32 | 'VIEW_COMPILED_PATH',
33 | realpath(storage_path('framework/views'))
34 | ),
35 |
36 | ];
37 |
--------------------------------------------------------------------------------
/database/.gitignore:
--------------------------------------------------------------------------------
1 | *.sqlite
2 | *.sqlite-journal
3 |
--------------------------------------------------------------------------------
/database/factories/UserFactory.php:
--------------------------------------------------------------------------------
1 | $this->faker->name,
27 | 'email' => $this->faker->unique()->safeEmail,
28 | 'email_verified_at' => now(),
29 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
30 | 'remember_token' => Str::random(10),
31 | ];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/database/migrations/2014_10_12_000000_create_users_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->string('name');
19 | $table->string('email')->unique();
20 | $table->timestamp('email_verified_at')->nullable();
21 | $table->string('password');
22 | $table->rememberToken();
23 | $table->timestamps();
24 | $table->string('role')->nullable()->default('user');
25 | });
26 | }
27 |
28 | /**
29 | * Reverse the migrations.
30 | *
31 | * @return void
32 | */
33 | public function down()
34 | {
35 | Schema::dropIfExists('users');
36 | }
37 | }
--------------------------------------------------------------------------------
/database/migrations/2014_10_12_100000_create_password_resets_table.php:
--------------------------------------------------------------------------------
1 | string('email')->index();
18 | $table->string('token');
19 | $table->timestamp('created_at')->nullable();
20 | });
21 | }
22 |
23 | /**
24 | * Reverse the migrations.
25 | *
26 | * @return void
27 | */
28 | public function down()
29 | {
30 | Schema::dropIfExists('password_resets');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/migrations/2019_08_19_000000_create_failed_jobs_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->string('uuid')->unique();
19 | $table->text('connection');
20 | $table->text('queue');
21 | $table->longText('payload');
22 | $table->longText('exception');
23 | $table->timestamp('failed_at')->useCurrent();
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | *
30 | * @return void
31 | */
32 | public function down()
33 | {
34 | Schema::dropIfExists('failed_jobs');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->morphs('tokenable');
19 | $table->string('name');
20 | $table->string('token', 64)->unique();
21 | $table->text('abilities')->nullable();
22 | $table->timestamp('last_used_at')->nullable();
23 | $table->timestamps();
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | *
30 | * @return void
31 | */
32 | public function down()
33 | {
34 | Schema::dropIfExists('personal_access_tokens');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/database/seeders/DatabaseSeeder.php:
--------------------------------------------------------------------------------
1 | call([
19 | UserSeeder::class,
20 | ]);
21 | }
22 | }
--------------------------------------------------------------------------------
/database/seeders/UserSeeder.php:
--------------------------------------------------------------------------------
1 | insert([
21 | 'name' => 'Super-Admin',
22 | 'email' => env('ADMIN_EMAIL_ADDRESS') ? env('ADMIN_EMAIL_ADDRESS') : "admin@examplemail.com",
23 | 'email_verified_at' => now(),
24 | 'created_at' => now(),
25 | 'updated_at' => now(),
26 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
27 | 'remember_token' => Str::random(10),
28 | 'role' => 'admin'
29 | ]);
30 | // Create some users with the factory.
31 | User::factory()->times(5)->create();
32 | }
33 | }
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | backend:
4 | build:
5 | context: ./
6 | dockerfile: Dockerfile
7 | ports:
8 | - "${APP_PORT:-80}:80"
9 | - "8000:8000"
10 | volumes:
11 | - ".:/var/www/html"
12 | networks:
13 | - app
14 | environment:
15 | XDEBUG_CONFIG: "client_host=host.docker.internal"
16 | depends_on:
17 | - db
18 | - redis
19 | frontend:
20 | image: "node"
21 | volumes:
22 | - "./client:/var/www/html"
23 | working_dir: "/var/www/html"
24 | ports:
25 | - "3000:3000"
26 | command: bash -c "npm install && npm run dev"
27 |
28 | db:
29 | image: "mysql:8.0"
30 | ports:
31 | - "${DB_PORT}:3306"
32 | environment:
33 | MYSQL_ROOT_PASSWORD: "${DB_PASSWORD}"
34 | MYSQL_DATABASE: "${DB_DATABASE}"
35 | MYSQL_USER: "${DB_USERNAME}"
36 | MYSQL_PASSWORD: "${DB_PASSWORD}"
37 | MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
38 | volumes:
39 | - "appdb:/var/lib/mysql"
40 | networks:
41 | - app
42 |
43 | # Redis.
44 | redis:
45 | image: "redis:alpine"
46 | ports:
47 | - "${FORWARD_REDIS_PORT:-6379}:6379"
48 | volumes:
49 | - "appredis:/data"
50 | networks:
51 | - app
52 |
53 | ## PhpMyAdmin
54 | phpmyadmin:
55 | depends_on:
56 | - db
57 | image: phpmyadmin/phpmyadmin
58 | restart: always
59 | ports:
60 | - "5005:80"
61 | environment:
62 | PMA_HOST: mysql
63 | MYSQL_ROOT_PASSWORD: password
64 | networks:
65 | - app
66 |
67 | # Network.
68 | networks:
69 | app:
70 | driver: bridge
71 |
72 | volumes:
73 | appdb:
74 | driver: local
75 | appredis:
76 | driver: local
77 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "npm run development",
5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --config=node_modules/laravel-mix/setup/webpack.config.js",
6 | "watch": "npm run development -- --watch",
7 | "watch-poll": "npm run watch -- --watch-poll",
8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --disable-host-check --config=node_modules/laravel-mix/setup/webpack.config.js",
9 | "prod": "npm run production",
10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --config=node_modules/laravel-mix/setup/webpack.config.js"
11 | },
12 | "devDependencies": {
13 | "axios": "^0.19",
14 | "cross-env": "^7.0",
15 | "laravel-mix": "^5.0.1",
16 | "lodash": "^4.17.19",
17 | "resolve-url-loader": "^3.1.0"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | ./tests/Unit
10 |
11 |
12 | ./tests/Feature
13 |
14 |
15 |
16 |
17 | ./app
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/public/.htaccess:
--------------------------------------------------------------------------------
1 |
2 |
3 | Options -MultiViews -Indexes
4 |
5 |
6 | RewriteEngine On
7 |
8 | # Handle Authorization Header
9 | RewriteCond %{HTTP:Authorization} .
10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
11 |
12 | # Redirect Trailing Slashes If Not A Folder...
13 | RewriteCond %{REQUEST_FILENAME} !-d
14 | RewriteCond %{REQUEST_URI} (.+)/$
15 | RewriteRule ^ %1 [L,R=301]
16 |
17 | # Send Requests To Front Controller...
18 | RewriteCond %{REQUEST_FILENAME} !-d
19 | RewriteCond %{REQUEST_FILENAME} !-f
20 | RewriteRule ^ index.php [L]
21 |
22 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niclas-timm/laravel-nextjs-starter/582284400ec1c80e9385c91c57cdeeb64ac6aca5/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.php:
--------------------------------------------------------------------------------
1 | make(Kernel::class);
50 |
51 | $response = tap($kernel->handle(
52 | $request = Request::capture()
53 | ))->send();
54 |
55 | $kernel->terminate($request, $response);
56 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
3 |
--------------------------------------------------------------------------------
/public/web.config:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/resources/css/app.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niclas-timm/laravel-nextjs-starter/582284400ec1c80e9385c91c57cdeeb64ac6aca5/resources/css/app.css
--------------------------------------------------------------------------------
/resources/js/app.js:
--------------------------------------------------------------------------------
1 | require('./bootstrap');
2 |
--------------------------------------------------------------------------------
/resources/js/bootstrap.js:
--------------------------------------------------------------------------------
1 | window._ = require('lodash');
2 |
3 | /**
4 | * We'll load the axios HTTP library which allows us to easily issue requests
5 | * to our Laravel back-end. This library automatically handles sending the
6 | * CSRF token as a header based on the value of the "XSRF" token cookie.
7 | */
8 |
9 | window.axios = require('axios');
10 |
11 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
12 |
13 | /**
14 | * Echo exposes an expressive API for subscribing to channels and listening
15 | * for events that are broadcast by Laravel. Echo and event broadcasting
16 | * allows your team to easily build robust real-time web applications.
17 | */
18 |
19 | // import Echo from 'laravel-echo';
20 |
21 | // window.Pusher = require('pusher-js');
22 |
23 | // window.Echo = new Echo({
24 | // broadcaster: 'pusher',
25 | // key: process.env.MIX_PUSHER_APP_KEY,
26 | // cluster: process.env.MIX_PUSHER_APP_CLUSTER,
27 | // forceTLS: true
28 | // });
29 |
--------------------------------------------------------------------------------
/resources/lang/en/auth.php:
--------------------------------------------------------------------------------
1 | 'These credentials do not match our records.',
17 | 'password' => 'The provided password is incorrect.',
18 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
19 |
20 | ];
21 |
--------------------------------------------------------------------------------
/resources/lang/en/pagination.php:
--------------------------------------------------------------------------------
1 | '« Previous',
17 | 'next' => 'Next »',
18 |
19 | ];
20 |
--------------------------------------------------------------------------------
/resources/lang/en/passwords.php:
--------------------------------------------------------------------------------
1 | 'Your password has been reset!',
17 | 'sent' => 'We have emailed your password reset link!',
18 | 'throttled' => 'Please wait before retrying.',
19 | 'token' => 'This password reset token is invalid.',
20 | 'user' => "We can't find a user with that email address.",
21 |
22 | ];
23 |
--------------------------------------------------------------------------------
/resources/views/auth/login.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
73 | @endsection
74 |
--------------------------------------------------------------------------------
/resources/views/auth/passwords/confirm.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | {{ __('Please confirm your password before continuing.') }}
12 |
13 |
44 |
45 |
46 |
47 |
48 |
49 | @endsection
50 |
--------------------------------------------------------------------------------
/resources/views/auth/passwords/email.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | @if (session('status'))
12 |
13 | {{ session('status') }}
14 |
15 | @endif
16 |
17 |
42 |
43 |
44 |
45 |
46 |
47 | @endsection
48 |
--------------------------------------------------------------------------------
/resources/views/auth/passwords/reset.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
65 | @endsection
66 |
--------------------------------------------------------------------------------
/resources/views/auth/register.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
77 | @endsection
78 |
--------------------------------------------------------------------------------
/resources/views/auth/verify.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | @if (session('resent'))
12 |
13 | {{ __('A fresh verification link has been sent to your email address.') }}
14 |
15 | @endif
16 |
17 | {{ __('Before proceeding, please check your email for a verification link.') }}
18 | {{ __('If you did not receive the email') }},
19 |
23 |
24 |
25 |
26 |
27 |
28 | @endsection
29 |
--------------------------------------------------------------------------------
/resources/views/home.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | @if (session('status'))
12 |
13 | {{ session('status') }}
14 |
15 | @endif
16 |
17 | {{ __('You are logged in!') }}
18 |
19 |
20 |
21 |
22 |
23 | @endsection
24 |
--------------------------------------------------------------------------------
/resources/views/layouts/app.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {{ config('app.name', 'Laravel') }}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
76 |
77 |
78 |
79 | @yield('content')
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/routes/api.php:
--------------------------------------------------------------------------------
1 | get('/user', function (Request $request) {
18 | return $request->user();
19 | });
--------------------------------------------------------------------------------
/routes/channels.php:
--------------------------------------------------------------------------------
1 | id === (int) $id;
18 | });
19 |
--------------------------------------------------------------------------------
/routes/console.php:
--------------------------------------------------------------------------------
1 | comment(Inspiring::quote());
19 | })->purpose('Display an inspiring quote');
20 |
--------------------------------------------------------------------------------
/routes/web.php:
--------------------------------------------------------------------------------
1 | get('/user', function (Request $request) {
18 | // return $request->user();
19 | // });
20 |
21 | Auth::routes(['verify' => true]);
--------------------------------------------------------------------------------
/server.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 |
10 | $uri = urldecode(
11 | parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)
12 | );
13 |
14 | // This file allows us to emulate Apache's "mod_rewrite" functionality from the
15 | // built-in PHP web server. This provides a convenient way to test a Laravel
16 | // application without having installed a "real" web server software here.
17 | if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) {
18 | return false;
19 | }
20 |
21 | require_once __DIR__.'/public/index.php';
22 |
--------------------------------------------------------------------------------
/storage/app/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !public/
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/storage/app/public/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/.gitignore:
--------------------------------------------------------------------------------
1 | compiled.php
2 | config.php
3 | down
4 | events.scanned.php
5 | maintenance.php
6 | routes.php
7 | routes.scanned.php
8 | schedule-*
9 | services.json
10 |
--------------------------------------------------------------------------------
/storage/framework/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !data/
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/storage/framework/cache/data/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/sessions/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/testing/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/views/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/logs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/tests/CreatesApplication.php:
--------------------------------------------------------------------------------
1 | make(Kernel::class)->bootstrap();
19 |
20 | return $app;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Feature/ExampleTest.php:
--------------------------------------------------------------------------------
1 | get('/');
18 |
19 | $response->assertStatus(200);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | assertTrue(true);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/vm-start:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | i=1;
4 | j=$#;
5 |
6 | # Make sure only one argument is passed.
7 | if [ $j -gt 1 ]
8 | then
9 | echo "Too many arguments. Only '--build' allowed in order to rebuild the VM.";
10 | exit;
11 | fi
12 |
13 | # If the '--build' argument is passed, rebuild the VM.
14 | if [ "$1" = "--build" ]
15 | then
16 | docker-compose up --build -d;
17 | # If the argument is empty, regularly start the VM.
18 | elif [ -z $1 ]
19 | then
20 | docker-compose up -d;
21 |
22 | # If something else is given as the argument, abort the script.
23 | else
24 | echo "Incorrect argument. Either provide no arguments or '--build' as the single argument in order to rebuild the VM."
25 | fi
26 |
--------------------------------------------------------------------------------
/vm-stop:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | i=1;
4 | j=$#;
5 |
6 | # Make sure only one argument is passed.
7 | if [ $j -gt 0 ]
8 | then
9 | echo "No arguments allowed.";
10 | exit;
11 | fi
12 |
13 | docker-compose down --remove-orphans;
14 |
--------------------------------------------------------------------------------
/webpack.mix.js:
--------------------------------------------------------------------------------
1 | const mix = require('laravel-mix');
2 |
3 | /*
4 | |--------------------------------------------------------------------------
5 | | Mix Asset Management
6 | |--------------------------------------------------------------------------
7 | |
8 | | Mix provides a clean, fluent API for defining some Webpack build steps
9 | | for your Laravel applications. By default, we are compiling the CSS
10 | | file for the application as well as bundling up all the JS files.
11 | |
12 | */
13 |
14 | mix.js('resources/js/app.js', 'public/js')
15 | .postCss('resources/css/app.css', 'public/css', [
16 | //
17 | ]);
18 |
--------------------------------------------------------------------------------