17 | */
18 | public function definition()
19 | {
20 | return [
21 | 'name' => fake()->name(),
22 | 'email' => fake()->unique()->safeEmail(),
23 | 'email_verified_at' => now(),
24 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
25 | 'remember_token' => Str::random(10),
26 | ];
27 | }
28 |
29 | /**
30 | * Indicate that the model's email address should be unverified.
31 | *
32 | * @return static
33 | */
34 | public function unverified()
35 | {
36 | return $this->state(fn (array $attributes) => [
37 | 'email_verified_at' => null,
38 | ]);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/database/migrations/0000_00_00_000000_create_websockets_statistics_entries_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->string('app_id');
19 | $table->integer('peak_connection_count');
20 | $table->integer('websocket_message_count');
21 | $table->integer('api_message_count');
22 | $table->nullableTimestamps();
23 | });
24 | }
25 |
26 | /**
27 | * Reverse the migrations.
28 | *
29 | * @return void
30 | */
31 | public function down()
32 | {
33 | Schema::dropIfExists('websockets_statistics_entries');
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/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 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | *
30 | * @return void
31 | */
32 | public function down()
33 | {
34 | Schema::dropIfExists('users');
35 | }
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 | 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->timestamp('expires_at')->nullable();
24 | $table->timestamps();
25 | });
26 | }
27 |
28 | /**
29 | * Reverse the migrations.
30 | *
31 | * @return void
32 | */
33 | public function down()
34 | {
35 | Schema::dropIfExists('personal_access_tokens');
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/database/migrations/2023_01_25_044415_create_messages_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->text('message');
19 | $table->unsignedBigInteger('sender_id');
20 | $table->unsignedBigInteger('receiver_id');
21 | $table->timestamps();
22 | });
23 | }
24 |
25 | /**
26 | * Reverse the migrations.
27 | *
28 | * @return void
29 | */
30 | public function down()
31 | {
32 | Schema::dropIfExists('messages');
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/database/seeders/DatabaseSeeder.php:
--------------------------------------------------------------------------------
1 | create();
18 |
19 | // \App\Models\User::factory()->create([
20 | // 'name' => 'Test User',
21 | // 'email' => 'test@example.com',
22 | // ]);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@/*": ["resources/js/*"]
6 | }
7 | },
8 | "exclude": ["node_modules", "public"]
9 | }
10 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lang/en/pagination.php:
--------------------------------------------------------------------------------
1 | '« Previous',
17 | 'next' => 'Next »',
18 |
19 | ];
20 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "vite",
5 | "build": "vite build"
6 | },
7 | "devDependencies": {
8 | "@headlessui/react": "^1.4.2",
9 | "@inertiajs/inertia": "^0.11.0",
10 | "@inertiajs/inertia-react": "^0.8.1",
11 | "@inertiajs/progress": "^0.2.6",
12 | "@tailwindcss/forms": "^0.5.3",
13 | "@vitejs/plugin-react": "^3.0.0",
14 | "autoprefixer": "^10.4.12",
15 | "axios": "^1.1.2",
16 | "laravel-echo": "^1.14.2",
17 | "laravel-vite-plugin": "^0.7.2",
18 | "lodash": "^4.17.19",
19 | "postcss": "^8.4.18",
20 | "pusher-js": "^7.6.0",
21 | "react": "^18.2.0",
22 | "react-dom": "^18.2.0",
23 | "tailwindcss": "^3.2.1",
24 | "vite": "^4.0.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/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/ManiruzzamanAkash/laravel-chat-app/a1ca56286d2a463ebdf8628009b73675efe8ddfa/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.php:
--------------------------------------------------------------------------------
1 | make(Kernel::class);
50 |
51 | $response = $kernel->handle(
52 | $request = Request::capture()
53 | )->send();
54 |
55 | $kernel->terminate($request, $response);
56 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
3 |
--------------------------------------------------------------------------------
/resources/css/app.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/resources/js/Components/ApplicationLogo.jsx:
--------------------------------------------------------------------------------
1 | export default function ApplicationLogo({ className }) {
2 | return (
3 |
4 |
5 |
6 | );
7 | }
8 |
--------------------------------------------------------------------------------
/resources/js/Components/Chat/ChatInput.jsx:
--------------------------------------------------------------------------------
1 | import { useForm } from "@inertiajs/inertia-react";
2 | import TextInput from "../TextInput";
3 |
4 | export default function ChatInput({ receiver }) {
5 | const { data, setData, post, processing, errors, reset } = useForm({
6 | message: "",
7 | });
8 |
9 | const onHandleChange = (event) => {
10 | setData(event.target.name, event.target.value);
11 | };
12 |
13 | const submit = (e) => {
14 | e.preventDefault();
15 |
16 | post(route("chat.store", receiver?.id));
17 | reset("message");
18 | };
19 |
20 | return (
21 |
22 |
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/resources/js/Components/Chat/ChatMessages.jsx:
--------------------------------------------------------------------------------
1 | import { Fragment } from "react";
2 |
3 | export default function ChatMessages({ messages, auth_id }) {
4 | const isReceivedMessage = (message) => {
5 | return message.receiver_id === auth_id;
6 | };
7 |
8 | return (
9 | <>
10 | {(messages || []).map((message, index) => (
11 |
12 |
19 |
30 |
{message?.message}
31 |
32 |
33 |
34 | ))}
35 | >
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/resources/js/Components/Chat/ChatSidebar.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from "@inertiajs/inertia-react";
2 |
3 | export default function ChatSidebar({ recentMessages }) {
4 | return (
5 | <>
6 |
7 |
8 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | {recentMessages.map((user, index) => (
27 |
32 |
33 | {user?.avatar !== undefined ? (
34 |
38 | ) : (
39 |
40 | )}
41 |
42 |
43 |
44 |
45 | {user.name.length > 0 ? user.name : "N/A"}
46 |
47 |
48 | {user.message}
49 |
50 |
51 |
52 | ))}
53 |
54 | >
55 | );
56 | }
57 |
--------------------------------------------------------------------------------
/resources/js/Components/Chat/ChatUserInfoHeader.jsx:
--------------------------------------------------------------------------------
1 | export default function ChatUserInfoHeader({ receiver }) {
2 | return (
3 |
4 |
5 |
6 | {receiver?.avatar !== undefined ? (
7 |
11 | ) : (
12 |
13 | )}
14 |
15 |
16 | {receiver?.name}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/resources/js/Components/Checkbox.jsx:
--------------------------------------------------------------------------------
1 | export default function Checkbox({ name, value, handleChange }) {
2 | return (
3 | handleChange(e)}
9 | />
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/resources/js/Components/DangerButton.jsx:
--------------------------------------------------------------------------------
1 | export default function DangerButton({ type = 'submit', className = '', processing, children, onClick }) {
2 | return (
3 |
13 | {children}
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/resources/js/Components/Dropdown.jsx:
--------------------------------------------------------------------------------
1 | import { useState, createContext, useContext, Fragment } from 'react';
2 | import { Link } from '@inertiajs/inertia-react';
3 | import { Transition } from '@headlessui/react';
4 |
5 | const DropDownContext = createContext();
6 |
7 | const Dropdown = ({ children }) => {
8 | const [open, setOpen] = useState(false);
9 |
10 | const toggleOpen = () => {
11 | setOpen((previousState) => !previousState);
12 | };
13 |
14 | return (
15 |
16 | {children}
17 |
18 | );
19 | };
20 |
21 | const Trigger = ({ children }) => {
22 | const { open, setOpen, toggleOpen } = useContext(DropDownContext);
23 |
24 | return (
25 | <>
26 | {children}
27 |
28 | {open && setOpen(false)}>
}
29 | >
30 | );
31 | };
32 |
33 | const Content = ({ align = 'right', width = '48', contentClasses = 'py-1 bg-white', children }) => {
34 | const { open, setOpen } = useContext(DropDownContext);
35 |
36 | let alignmentClasses = 'origin-top';
37 |
38 | if (align === 'left') {
39 | alignmentClasses = 'origin-top-left left-0';
40 | } else if (align === 'right') {
41 | alignmentClasses = 'origin-top-right right-0';
42 | }
43 |
44 | let widthClasses = '';
45 |
46 | if (width === '48') {
47 | widthClasses = 'w-48';
48 | }
49 |
50 | return (
51 | <>
52 |
62 | setOpen(false)}
65 | >
66 |
{children}
67 |
68 |
69 | >
70 | );
71 | };
72 |
73 | const DropdownLink = ({ href, method, as, children }) => {
74 | return (
75 |
81 | {children}
82 |
83 | );
84 | };
85 |
86 | Dropdown.Trigger = Trigger;
87 | Dropdown.Content = Content;
88 | Dropdown.Link = DropdownLink;
89 |
90 | export default Dropdown;
91 |
--------------------------------------------------------------------------------
/resources/js/Components/InputError.jsx:
--------------------------------------------------------------------------------
1 | export default function InputError({ message, className = '' }) {
2 | return message ? {message}
: null;
3 | }
4 |
--------------------------------------------------------------------------------
/resources/js/Components/InputLabel.jsx:
--------------------------------------------------------------------------------
1 | export default function InputLabel({ forInput, value, className, children }) {
2 | return (
3 |
4 | {value ? value : children}
5 |
6 | );
7 | }
8 |
--------------------------------------------------------------------------------
/resources/js/Components/Modal.jsx:
--------------------------------------------------------------------------------
1 | import { Fragment } from 'react';
2 | import { Dialog, Transition } from '@headlessui/react';
3 |
4 | export default function Modal({ children, show = false, maxWidth = '2xl', closeable = true, onClose = () => {} }) {
5 | const close = () => {
6 | if (closeable) {
7 | onClose();
8 | }
9 | };
10 |
11 | const maxWidthClass = {
12 | sm: 'sm:max-w-sm',
13 | md: 'sm:max-w-md',
14 | lg: 'sm:max-w-lg',
15 | xl: 'sm:max-w-xl',
16 | '2xl': 'sm:max-w-2xl',
17 | }[maxWidth];
18 |
19 | return (
20 |
21 |
27 |
36 |
37 |
38 |
39 |
48 |
51 | {children}
52 |
53 |
54 |
55 |
56 | );
57 | }
58 |
--------------------------------------------------------------------------------
/resources/js/Components/NavLink.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from '@inertiajs/inertia-react';
2 |
3 | export default function NavLink({ href, active, children }) {
4 | return (
5 |
13 | {children}
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/resources/js/Components/PrimaryButton.jsx:
--------------------------------------------------------------------------------
1 | export default function PrimaryButton({ type = 'submit', className = '', processing, children, onClick }) {
2 | return (
3 |
13 | {children}
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/resources/js/Components/ResponsiveNavLink.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from '@inertiajs/inertia-react';
2 |
3 | export default function ResponsiveNavLink({ method = 'get', as = 'a', href, active = false, children }) {
4 | return (
5 |
15 | {children}
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/resources/js/Components/SecondaryButton.jsx:
--------------------------------------------------------------------------------
1 | export default function SecondaryButton({ type = 'button', className = '', processing, children, onClick }) {
2 | return (
3 |
13 | {children}
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/resources/js/Components/TextInput.jsx:
--------------------------------------------------------------------------------
1 | import { forwardRef, useEffect, useRef } from 'react';
2 |
3 | export default forwardRef(function TextInput(
4 | { type = 'text', name, id, value, className, autoComplete, required, isFocused, handleChange },
5 | ref
6 | ) {
7 | const input = ref ? ref : useRef();
8 |
9 | useEffect(() => {
10 | if (isFocused) {
11 | input.current.focus();
12 | }
13 | }, []);
14 |
15 | return (
16 |
17 | handleChange(e)}
30 | />
31 |
32 | );
33 | });
34 |
--------------------------------------------------------------------------------
/resources/js/Layouts/AuthenticatedLayout.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import ApplicationLogo from '@/Components/ApplicationLogo';
3 | import Dropdown from '@/Components/Dropdown';
4 | import NavLink from '@/Components/NavLink';
5 | import ResponsiveNavLink from '@/Components/ResponsiveNavLink';
6 | import { Link } from '@inertiajs/inertia-react';
7 |
8 | export default function Authenticated({ auth, header, children }) {
9 | const [showingNavigationDropdown, setShowingNavigationDropdown] = useState(false);
10 |
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
22 |
23 |
24 |
25 | Dashboard
26 |
27 |
28 |
29 |
30 | Chat
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
44 | {auth.user.name}
45 |
46 |
52 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | Profile
64 |
65 | Log Out
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
setShowingNavigationDropdown((previousState) => !previousState)}
75 | className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out"
76 | >
77 |
78 |
85 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | Dashboard
102 |
103 |
104 |
105 |
106 |
107 |
108 | {auth.user.name}
109 |
110 |
{auth.user.email}
111 |
112 |
113 |
114 | Profile
115 |
116 | Log Out
117 |
118 |
119 |
120 |
121 |
122 |
123 | {header && (
124 |
127 | )}
128 |
129 |
{children}
130 |
131 | );
132 | }
133 |
--------------------------------------------------------------------------------
/resources/js/Layouts/GuestLayout.jsx:
--------------------------------------------------------------------------------
1 | import ApplicationLogo from '@/Components/ApplicationLogo';
2 | import { Link } from '@inertiajs/inertia-react';
3 |
4 | export default function Guest({ children }) {
5 | return (
6 |
7 |
12 |
13 |
14 | {children}
15 |
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/ConfirmPassword.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import GuestLayout from '@/Layouts/GuestLayout';
3 | import InputError from '@/Components/InputError';
4 | import InputLabel from '@/Components/InputLabel';
5 | import PrimaryButton from '@/Components/PrimaryButton';
6 | import TextInput from '@/Components/TextInput';
7 | import { Head, useForm } from '@inertiajs/inertia-react';
8 |
9 | export default function ConfirmPassword() {
10 | const { data, setData, post, processing, errors, reset } = useForm({
11 | password: '',
12 | });
13 |
14 | useEffect(() => {
15 | return () => {
16 | reset('password');
17 | };
18 | }, []);
19 |
20 | const onHandleChange = (event) => {
21 | setData(event.target.name, event.target.value);
22 | };
23 |
24 | const submit = (e) => {
25 | e.preventDefault();
26 |
27 | post(route('password.confirm'));
28 | };
29 |
30 | return (
31 |
32 |
33 |
34 |
35 | This is a secure area of the application. Please confirm your password before continuing.
36 |
37 |
38 |
61 |
62 | );
63 | }
64 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/ForgotPassword.jsx:
--------------------------------------------------------------------------------
1 | import GuestLayout from '@/Layouts/GuestLayout';
2 | import InputError from '@/Components/InputError';
3 | import PrimaryButton from '@/Components/PrimaryButton';
4 | import TextInput from '@/Components/TextInput';
5 | import { Head, useForm } from '@inertiajs/inertia-react';
6 |
7 | export default function ForgotPassword({ status }) {
8 | const { data, setData, post, processing, errors } = useForm({
9 | email: '',
10 | });
11 |
12 | const onHandleChange = (event) => {
13 | setData(event.target.name, event.target.value);
14 | };
15 |
16 | const submit = (e) => {
17 | e.preventDefault();
18 |
19 | post(route('password.email'));
20 | };
21 |
22 | return (
23 |
24 |
25 |
26 |
27 | Forgot your password? No problem. Just let us know your email address and we will email you a password
28 | reset link that will allow you to choose a new one.
29 |
30 |
31 | {status && {status}
}
32 |
33 |
52 |
53 | );
54 | }
55 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/Login.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import Checkbox from '@/Components/Checkbox';
3 | import GuestLayout from '@/Layouts/GuestLayout';
4 | import InputError from '@/Components/InputError';
5 | import InputLabel from '@/Components/InputLabel';
6 | import PrimaryButton from '@/Components/PrimaryButton';
7 | import TextInput from '@/Components/TextInput';
8 | import { Head, Link, useForm } from '@inertiajs/inertia-react';
9 |
10 | export default function Login({ status, canResetPassword }) {
11 | const { data, setData, post, processing, errors, reset } = useForm({
12 | email: '',
13 | password: '',
14 | remember: '',
15 | });
16 |
17 | useEffect(() => {
18 | return () => {
19 | reset('password');
20 | };
21 | }, []);
22 |
23 | const onHandleChange = (event) => {
24 | setData(event.target.name, event.target.type === 'checkbox' ? event.target.checked : event.target.value);
25 | };
26 |
27 | const submit = (e) => {
28 | e.preventDefault();
29 |
30 | post(route('login'));
31 | };
32 |
33 | return (
34 |
35 |
36 |
37 | {status && {status}
}
38 |
39 |
95 |
96 | );
97 | }
98 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/Register.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import GuestLayout from '@/Layouts/GuestLayout';
3 | import InputError from '@/Components/InputError';
4 | import InputLabel from '@/Components/InputLabel';
5 | import PrimaryButton from '@/Components/PrimaryButton';
6 | import TextInput from '@/Components/TextInput';
7 | import { Head, Link, useForm } from '@inertiajs/inertia-react';
8 |
9 | export default function Register() {
10 | const { data, setData, post, processing, errors, reset } = useForm({
11 | name: '',
12 | email: '',
13 | password: '',
14 | password_confirmation: '',
15 | });
16 |
17 | useEffect(() => {
18 | return () => {
19 | reset('password', 'password_confirmation');
20 | };
21 | }, []);
22 |
23 | const onHandleChange = (event) => {
24 | setData(event.target.name, event.target.type === 'checkbox' ? event.target.checked : event.target.value);
25 | };
26 |
27 | const submit = (e) => {
28 | e.preventDefault();
29 |
30 | post(route('register'));
31 | };
32 |
33 | return (
34 |
35 |
36 |
37 |
118 |
119 | );
120 | }
121 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/ResetPassword.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import GuestLayout from '@/Layouts/GuestLayout';
3 | import InputError from '@/Components/InputError';
4 | import InputLabel from '@/Components/InputLabel';
5 | import PrimaryButton from '@/Components/PrimaryButton';
6 | import TextInput from '@/Components/TextInput';
7 | import { Head, useForm } from '@inertiajs/inertia-react';
8 |
9 | export default function ResetPassword({ token, email }) {
10 | const { data, setData, post, processing, errors, reset } = useForm({
11 | token: token,
12 | email: email,
13 | password: '',
14 | password_confirmation: '',
15 | });
16 |
17 | useEffect(() => {
18 | return () => {
19 | reset('password', 'password_confirmation');
20 | };
21 | }, []);
22 |
23 | const onHandleChange = (event) => {
24 | setData(event.target.name, event.target.value);
25 | };
26 |
27 | const submit = (e) => {
28 | e.preventDefault();
29 |
30 | post(route('password.store'));
31 | };
32 |
33 | return (
34 |
35 |
36 |
37 |
92 |
93 | );
94 | }
95 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/VerifyEmail.jsx:
--------------------------------------------------------------------------------
1 | import GuestLayout from '@/Layouts/GuestLayout';
2 | import PrimaryButton from '@/Components/PrimaryButton';
3 | import { Head, Link, useForm } from '@inertiajs/inertia-react';
4 |
5 | export default function VerifyEmail({ status }) {
6 | const { post, processing } = useForm();
7 |
8 | const submit = (e) => {
9 | e.preventDefault();
10 |
11 | post(route('verification.send'));
12 | };
13 |
14 | return (
15 |
16 |
17 |
18 |
19 | Thanks for signing up! Before getting started, could you verify your email address by clicking on the
20 | link we just emailed to you? If you didn't receive the email, we will gladly send you another.
21 |
22 |
23 | {status === 'verification-link-sent' && (
24 |
25 | A new verification link has been sent to the email address you provided during registration.
26 |
27 | )}
28 |
29 |
43 |
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/resources/js/Pages/Chat/Chat.jsx:
--------------------------------------------------------------------------------
1 | import ChatInput from "@/Components/Chat/ChatInput";
2 | import ChatMessages from "@/Components/Chat/ChatMessages";
3 | import ChatSidebar from "@/Components/Chat/ChatSidebar";
4 | import ChatUserInfoHeader from "@/Components/Chat/ChatUserInfoHeader";
5 | import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
6 |
7 | export default function Chat(props) {
8 | const { auth, errors, recentMessages, receiver, messages } = props;
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | {receiver?.id ? (
20 | <>
21 |
22 |
23 |
24 |
28 |
29 |
30 |
31 |
32 | >
33 | ) : (
34 |
35 |
36 | Please select a User to start
37 | chatting...
38 |
39 |
40 | )}
41 |
42 |
43 |
44 |
45 |
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/resources/js/Pages/Dashboard.jsx:
--------------------------------------------------------------------------------
1 | import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
2 | import { Head } from '@inertiajs/inertia-react';
3 |
4 | export default function Dashboard(props) {
5 | return (
6 | Dashboard}
10 | >
11 |
12 |
13 |
14 |
15 |
16 |
You're logged in!
17 |
18 |
19 |
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/resources/js/Pages/Profile/Edit.jsx:
--------------------------------------------------------------------------------
1 | import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
2 | import DeleteUserForm from './Partials/DeleteUserForm';
3 | import UpdatePasswordForm from './Partials/UpdatePasswordForm';
4 | import UpdateProfileInformationForm from './Partials/UpdateProfileInformationForm';
5 | import { Head } from '@inertiajs/inertia-react';
6 |
7 | export default function Edit({ auth, mustVerifyEmail, status }) {
8 | return (
9 | Profile}
12 | >
13 |
14 |
15 |
16 |
17 |
18 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/resources/js/Pages/Profile/Partials/DeleteUserForm.jsx:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from 'react';
2 | import DangerButton from '@/Components/DangerButton';
3 | import InputError from '@/Components/InputError';
4 | import InputLabel from '@/Components/InputLabel';
5 | import Modal from '@/Components/Modal';
6 | import SecondaryButton from '@/Components/SecondaryButton';
7 | import TextInput from '@/Components/TextInput';
8 | import { useForm } from '@inertiajs/inertia-react';
9 |
10 | export default function DeleteUserForm({ className }) {
11 | const [confirmingUserDeletion, setConfirmingUserDeletion] = useState(false);
12 | const passwordInput = useRef();
13 |
14 | const {
15 | data,
16 | setData,
17 | delete: destroy,
18 | processing,
19 | reset,
20 | errors,
21 | } = useForm({
22 | password: '',
23 | });
24 |
25 | const confirmUserDeletion = () => {
26 | setConfirmingUserDeletion(true);
27 | };
28 |
29 | const deleteUser = (e) => {
30 | e.preventDefault();
31 |
32 | destroy(route('profile.destroy'), {
33 | preserveScroll: true,
34 | onSuccess: () => closeModal(),
35 | onError: () => passwordInput.current.focus(),
36 | onFinish: () => reset(),
37 | });
38 | };
39 |
40 | const closeModal = () => {
41 | setConfirmingUserDeletion(false);
42 |
43 | reset();
44 | };
45 |
46 | return (
47 |
48 |
49 | Delete Account
50 |
51 |
52 | Once your account is deleted, all of its resources and data will be permanently deleted. Before
53 | deleting your account, please download any data or information that you wish to retain.
54 |
55 |
56 |
57 | Delete Account
58 |
59 |
60 |
96 |
97 |
98 | );
99 | }
100 |
--------------------------------------------------------------------------------
/resources/js/Pages/Profile/Partials/UpdatePasswordForm.jsx:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 | import InputError from '@/Components/InputError';
3 | import InputLabel from '@/Components/InputLabel';
4 | import PrimaryButton from '@/Components/PrimaryButton';
5 | import TextInput from '@/Components/TextInput';
6 | import { useForm } from '@inertiajs/inertia-react';
7 | import { Transition } from '@headlessui/react';
8 |
9 | export default function UpdatePasswordForm({ className }) {
10 | const passwordInput = useRef();
11 | const currentPasswordInput = useRef();
12 |
13 | const { data, setData, errors, put, reset, processing, recentlySuccessful } = useForm({
14 | current_password: '',
15 | password: '',
16 | password_confirmation: '',
17 | });
18 |
19 | const updatePassword = (e) => {
20 | e.preventDefault();
21 |
22 | put(route('password.update'), {
23 | preserveScroll: true,
24 | onSuccess: () => reset(),
25 | onError: () => {
26 | if (errors.password) {
27 | reset('password', 'password_confirmation');
28 | passwordInput.current.focus();
29 | }
30 |
31 | if (errors.current_password) {
32 | reset('current_password');
33 | currentPasswordInput.current.focus();
34 | }
35 | },
36 | });
37 | };
38 |
39 | return (
40 |
111 | );
112 | }
113 |
--------------------------------------------------------------------------------
/resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.jsx:
--------------------------------------------------------------------------------
1 | import InputError from '@/Components/InputError';
2 | import InputLabel from '@/Components/InputLabel';
3 | import PrimaryButton from '@/Components/PrimaryButton';
4 | import TextInput from '@/Components/TextInput';
5 | import { Link, useForm, usePage } from '@inertiajs/inertia-react';
6 | import { Transition } from '@headlessui/react';
7 |
8 | export default function UpdateProfileInformation({ mustVerifyEmail, status, className }) {
9 | const user = usePage().props.auth.user;
10 |
11 | const { data, setData, patch, errors, processing, recentlySuccessful } = useForm({
12 | name: user.name,
13 | email: user.email,
14 | });
15 |
16 | const submit = (e) => {
17 | e.preventDefault();
18 |
19 | patch(route('profile.update'));
20 | };
21 |
22 | return (
23 |
101 | );
102 | }
103 |
--------------------------------------------------------------------------------
/resources/js/app.jsx:
--------------------------------------------------------------------------------
1 | import './bootstrap';
2 | import '../css/app.css';
3 |
4 | import { createRoot } from 'react-dom/client';
5 | import { createInertiaApp } from '@inertiajs/inertia-react';
6 | import { InertiaProgress } from '@inertiajs/progress';
7 | import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
8 |
9 | const appName = window.document.getElementsByTagName('title')[0]?.innerText || 'Laravel';
10 |
11 | createInertiaApp({
12 | title: (title) => `${title} - ${appName}`,
13 | resolve: (name) => resolvePageComponent(`./Pages/${name}.jsx`, import.meta.glob('./Pages/**/*.jsx')),
14 | setup({ el, App, props }) {
15 | const root = createRoot(el);
16 |
17 | root.render( );
18 | },
19 | });
20 |
21 | InertiaProgress.init({ color: '#4B5563' });
22 |
23 | Echo.private(`messenger.1.2`)
24 | .listen('MessageSent', (e) => {
25 | console.log(e);
26 | console.log(e.message);
27 | });
28 |
29 |
30 | // Echo.join(`group_chat.1`)
31 | // .here((users) => {
32 | // console.log(users);
33 | // })
34 | // .joining((user) => {
35 | // console.log(user.name);
36 | // })
37 | // .leaving((user) => {
38 | // console.log(user.name);
39 | // })
40 | // .listen('GroupChatMessage', (e) => {
41 | // console.log(e);
42 | // })
43 | // .error((error) => {
44 | // console.error(error);
45 | // });
46 |
--------------------------------------------------------------------------------
/resources/js/bootstrap.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | window._ = _;
3 |
4 | /**
5 | * We'll load the axios HTTP library which allows us to easily issue requests
6 | * to our Laravel back-end. This library automatically handles sending the
7 | * CSRF token as a header based on the value of the "XSRF" token cookie.
8 | */
9 |
10 | import axios from 'axios';
11 | window.axios = axios;
12 |
13 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
14 |
15 | /**
16 | * Echo exposes an expressive API for subscribing to channels and listening
17 | * for events that are broadcast by Laravel. Echo and event broadcasting
18 | * allows your team to easily build robust real-time web applications.
19 | */
20 |
21 | import Echo from 'laravel-echo';
22 |
23 | import Pusher from 'pusher-js';
24 | window.Pusher = Pusher;
25 |
26 | window.Echo = new Echo({
27 | broadcaster: 'pusher',
28 | key: import.meta.env.VITE_PUSHER_APP_KEY,
29 | wsHost: import.meta.env.VITE_PUSHER_HOST ? import.meta.env.VITE_PUSHER_HOST : `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
30 | wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
31 | wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
32 | forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
33 | enabledTransports: ['ws', 'wss'],
34 | });
35 |
--------------------------------------------------------------------------------
/resources/views/app.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{ config('app.name', 'Laravel') }}
9 |
10 |
11 |
12 |
13 |
14 |
15 | @routes
16 | @viteReactRefresh
17 | @vite(['resources/js/app.jsx', "resources/js/Pages/{$page['component']}.jsx"])
18 | @inertiaHead
19 |
20 |
21 | @inertia
22 |
23 |
24 |
--------------------------------------------------------------------------------
/routes/api.php:
--------------------------------------------------------------------------------
1 | get('/user', function (Request $request) {
19 | return $request->user();
20 | });
21 |
22 | Broadcast::routes(['prefix' => 'api', 'middleware' => ['auth:api']]);
23 |
--------------------------------------------------------------------------------
/routes/auth.php:
--------------------------------------------------------------------------------
1 | group(function () {
16 | Route::get('register', [RegisteredUserController::class, 'create'])
17 | ->name('register');
18 |
19 | Route::post('register', [RegisteredUserController::class, 'store']);
20 |
21 | Route::get('login', [AuthenticatedSessionController::class, 'create'])
22 | ->name('login');
23 |
24 | Route::post('login', [AuthenticatedSessionController::class, 'store']);
25 |
26 | Route::get('forgot-password', [PasswordResetLinkController::class, 'create'])
27 | ->name('password.request');
28 |
29 | Route::post('forgot-password', [PasswordResetLinkController::class, 'store'])
30 | ->name('password.email');
31 |
32 | Route::get('reset-password/{token}', [NewPasswordController::class, 'create'])
33 | ->name('password.reset');
34 |
35 | Route::post('reset-password', [NewPasswordController::class, 'store'])
36 | ->name('password.store');
37 | });
38 |
39 | Route::middleware('auth')->group(function () {
40 | Route::get('verify-email', [EmailVerificationPromptController::class, '__invoke'])
41 | ->name('verification.notice');
42 |
43 | Route::get('verify-email/{id}/{hash}', [VerifyEmailController::class, '__invoke'])
44 | ->middleware(['signed', 'throttle:6,1'])
45 | ->name('verification.verify');
46 |
47 | Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store'])
48 | ->middleware('throttle:6,1')
49 | ->name('verification.send');
50 |
51 | Route::get('confirm-password', [ConfirmablePasswordController::class, 'show'])
52 | ->name('password.confirm');
53 |
54 | Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']);
55 |
56 | Route::put('password', [PasswordController::class, 'update'])->name('password.update');
57 |
58 | Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])
59 | ->name('logout');
60 | });
61 |
--------------------------------------------------------------------------------
/routes/channels.php:
--------------------------------------------------------------------------------
1 | id === (int) $id;
18 | // });
19 |
20 | Broadcast::channel('messenger.{sender}.{receiver}', function ($user) {
21 | return !is_null($user);
22 | });
23 |
24 | Broadcast::channel('group_chat.{roomId}', function ($user, $roomId) {
25 | if (true) {
26 | return ['id' => $user->id, 'name' => $user->name];
27 | }
28 | });
29 |
--------------------------------------------------------------------------------
/routes/console.php:
--------------------------------------------------------------------------------
1 | comment(Inspiring::quote());
19 | })->purpose('Display an inspiring quote');
20 |
--------------------------------------------------------------------------------
/routes/web.php:
--------------------------------------------------------------------------------
1 | Route::has('login'),
23 | 'canRegister' => Route::has('register'),
24 | 'laravelVersion' => Application::VERSION,
25 | 'phpVersion' => PHP_VERSION,
26 | ]);
27 | });
28 |
29 | Route::get('/dashboard', function () {
30 | return Inertia::render('Dashboard');
31 | })->middleware(['auth', 'verified'])->name('dashboard');
32 |
33 | Route::middleware('auth')->group(function () {
34 | Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
35 | Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
36 | Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
37 |
38 | Route::group(['prefix' => 'chat', 'as' => 'chat.'], function() {
39 | Route::get('/{receiverId?}', [ChatController::class, 'index'])->name('index');
40 | Route::post('/{receiverId?}', [ChatController::class, 'store'])->name('store');
41 | });
42 | });
43 |
44 | require __DIR__.'/auth.php';
45 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | const defaultTheme = require('tailwindcss/defaultTheme');
2 |
3 | /** @type {import('tailwindcss').Config} */
4 | module.exports = {
5 | content: [
6 | './vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',
7 | './storage/framework/views/*.php',
8 | './resources/views/**/*.blade.php',
9 | './resources/js/**/*.jsx',
10 | ],
11 |
12 | theme: {
13 | extend: {
14 | fontFamily: {
15 | sans: ['Nunito', ...defaultTheme.fontFamily.sans],
16 | },
17 | },
18 | },
19 |
20 | plugins: [require('@tailwindcss/forms')],
21 | };
22 |
--------------------------------------------------------------------------------
/tests/CreatesApplication.php:
--------------------------------------------------------------------------------
1 | make(Kernel::class)->bootstrap();
19 |
20 | return $app;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/AuthenticationTest.php:
--------------------------------------------------------------------------------
1 | get('/login');
17 |
18 | $response->assertStatus(200);
19 | }
20 |
21 | public function test_users_can_authenticate_using_the_login_screen()
22 | {
23 | $user = User::factory()->create();
24 |
25 | $response = $this->post('/login', [
26 | 'email' => $user->email,
27 | 'password' => 'password',
28 | ]);
29 |
30 | $this->assertAuthenticated();
31 | $response->assertRedirect(RouteServiceProvider::HOME);
32 | }
33 |
34 | public function test_users_can_not_authenticate_with_invalid_password()
35 | {
36 | $user = User::factory()->create();
37 |
38 | $this->post('/login', [
39 | 'email' => $user->email,
40 | 'password' => 'wrong-password',
41 | ]);
42 |
43 | $this->assertGuest();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/EmailVerificationTest.php:
--------------------------------------------------------------------------------
1 | create([
20 | 'email_verified_at' => null,
21 | ]);
22 |
23 | $response = $this->actingAs($user)->get('/verify-email');
24 |
25 | $response->assertStatus(200);
26 | }
27 |
28 | public function test_email_can_be_verified()
29 | {
30 | $user = User::factory()->create([
31 | 'email_verified_at' => null,
32 | ]);
33 |
34 | Event::fake();
35 |
36 | $verificationUrl = URL::temporarySignedRoute(
37 | 'verification.verify',
38 | now()->addMinutes(60),
39 | ['id' => $user->id, 'hash' => sha1($user->email)]
40 | );
41 |
42 | $response = $this->actingAs($user)->get($verificationUrl);
43 |
44 | Event::assertDispatched(Verified::class);
45 | $this->assertTrue($user->fresh()->hasVerifiedEmail());
46 | $response->assertRedirect(RouteServiceProvider::HOME.'?verified=1');
47 | }
48 |
49 | public function test_email_is_not_verified_with_invalid_hash()
50 | {
51 | $user = User::factory()->create([
52 | 'email_verified_at' => null,
53 | ]);
54 |
55 | $verificationUrl = URL::temporarySignedRoute(
56 | 'verification.verify',
57 | now()->addMinutes(60),
58 | ['id' => $user->id, 'hash' => sha1('wrong-email')]
59 | );
60 |
61 | $this->actingAs($user)->get($verificationUrl);
62 |
63 | $this->assertFalse($user->fresh()->hasVerifiedEmail());
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/PasswordConfirmationTest.php:
--------------------------------------------------------------------------------
1 | create();
16 |
17 | $response = $this->actingAs($user)->get('/confirm-password');
18 |
19 | $response->assertStatus(200);
20 | }
21 |
22 | public function test_password_can_be_confirmed()
23 | {
24 | $user = User::factory()->create();
25 |
26 | $response = $this->actingAs($user)->post('/confirm-password', [
27 | 'password' => 'password',
28 | ]);
29 |
30 | $response->assertRedirect();
31 | $response->assertSessionHasNoErrors();
32 | }
33 |
34 | public function test_password_is_not_confirmed_with_invalid_password()
35 | {
36 | $user = User::factory()->create();
37 |
38 | $response = $this->actingAs($user)->post('/confirm-password', [
39 | 'password' => 'wrong-password',
40 | ]);
41 |
42 | $response->assertSessionHasErrors();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/PasswordResetTest.php:
--------------------------------------------------------------------------------
1 | get('/forgot-password');
18 |
19 | $response->assertStatus(200);
20 | }
21 |
22 | public function test_reset_password_link_can_be_requested()
23 | {
24 | Notification::fake();
25 |
26 | $user = User::factory()->create();
27 |
28 | $this->post('/forgot-password', ['email' => $user->email]);
29 |
30 | Notification::assertSentTo($user, ResetPassword::class);
31 | }
32 |
33 | public function test_reset_password_screen_can_be_rendered()
34 | {
35 | Notification::fake();
36 |
37 | $user = User::factory()->create();
38 |
39 | $this->post('/forgot-password', ['email' => $user->email]);
40 |
41 | Notification::assertSentTo($user, ResetPassword::class, function ($notification) {
42 | $response = $this->get('/reset-password/'.$notification->token);
43 |
44 | $response->assertStatus(200);
45 |
46 | return true;
47 | });
48 | }
49 |
50 | public function test_password_can_be_reset_with_valid_token()
51 | {
52 | Notification::fake();
53 |
54 | $user = User::factory()->create();
55 |
56 | $this->post('/forgot-password', ['email' => $user->email]);
57 |
58 | Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) {
59 | $response = $this->post('/reset-password', [
60 | 'token' => $notification->token,
61 | 'email' => $user->email,
62 | 'password' => 'password',
63 | 'password_confirmation' => 'password',
64 | ]);
65 |
66 | $response->assertSessionHasNoErrors();
67 |
68 | return true;
69 | });
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/PasswordUpdateTest.php:
--------------------------------------------------------------------------------
1 | create();
17 |
18 | $response = $this
19 | ->actingAs($user)
20 | ->from('/profile')
21 | ->put('/password', [
22 | 'current_password' => 'password',
23 | 'password' => 'new-password',
24 | 'password_confirmation' => 'new-password',
25 | ]);
26 |
27 | $response
28 | ->assertSessionHasNoErrors()
29 | ->assertRedirect('/profile');
30 |
31 | $this->assertTrue(Hash::check('new-password', $user->refresh()->password));
32 | }
33 |
34 | public function test_correct_password_must_be_provided_to_update_password()
35 | {
36 | $user = User::factory()->create();
37 |
38 | $response = $this
39 | ->actingAs($user)
40 | ->from('/profile')
41 | ->put('/password', [
42 | 'current_password' => 'wrong-password',
43 | 'password' => 'new-password',
44 | 'password_confirmation' => 'new-password',
45 | ]);
46 |
47 | $response
48 | ->assertSessionHasErrors('current_password')
49 | ->assertRedirect('/profile');
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/RegistrationTest.php:
--------------------------------------------------------------------------------
1 | get('/register');
16 |
17 | $response->assertStatus(200);
18 | }
19 |
20 | public function test_new_users_can_register()
21 | {
22 | $response = $this->post('/register', [
23 | 'name' => 'Test User',
24 | 'email' => 'test@example.com',
25 | 'password' => 'password',
26 | 'password_confirmation' => 'password',
27 | ]);
28 |
29 | $this->assertAuthenticated();
30 | $response->assertRedirect(RouteServiceProvider::HOME);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/Feature/ExampleTest.php:
--------------------------------------------------------------------------------
1 | get('/');
18 |
19 | $response->assertStatus(200);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/Feature/ProfileTest.php:
--------------------------------------------------------------------------------
1 | create();
16 |
17 | $response = $this
18 | ->actingAs($user)
19 | ->get('/profile');
20 |
21 | $response->assertOk();
22 | }
23 |
24 | public function test_profile_information_can_be_updated()
25 | {
26 | $user = User::factory()->create();
27 |
28 | $response = $this
29 | ->actingAs($user)
30 | ->patch('/profile', [
31 | 'name' => 'Test User',
32 | 'email' => 'test@example.com',
33 | ]);
34 |
35 | $response
36 | ->assertSessionHasNoErrors()
37 | ->assertRedirect('/profile');
38 |
39 | $user->refresh();
40 |
41 | $this->assertSame('Test User', $user->name);
42 | $this->assertSame('test@example.com', $user->email);
43 | $this->assertNull($user->email_verified_at);
44 | }
45 |
46 | public function test_email_verification_status_is_unchanged_when_the_email_address_is_unchanged()
47 | {
48 | $user = User::factory()->create();
49 |
50 | $response = $this
51 | ->actingAs($user)
52 | ->patch('/profile', [
53 | 'name' => 'Test User',
54 | 'email' => $user->email,
55 | ]);
56 |
57 | $response
58 | ->assertSessionHasNoErrors()
59 | ->assertRedirect('/profile');
60 |
61 | $this->assertNotNull($user->refresh()->email_verified_at);
62 | }
63 |
64 | public function test_user_can_delete_their_account()
65 | {
66 | $user = User::factory()->create();
67 |
68 | $response = $this
69 | ->actingAs($user)
70 | ->delete('/profile', [
71 | 'password' => 'password',
72 | ]);
73 |
74 | $response
75 | ->assertSessionHasNoErrors()
76 | ->assertRedirect('/');
77 |
78 | $this->assertGuest();
79 | $this->assertNull($user->fresh());
80 | }
81 |
82 | public function test_correct_password_must_be_provided_to_delete_account()
83 | {
84 | $user = User::factory()->create();
85 |
86 | $response = $this
87 | ->actingAs($user)
88 | ->from('/profile')
89 | ->delete('/profile', [
90 | 'password' => 'wrong-password',
91 | ]);
92 |
93 | $response
94 | ->assertSessionHasErrors('password')
95 | ->assertRedirect('/profile');
96 |
97 | $this->assertNotNull($user->fresh());
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | assertTrue(true);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import laravel from 'laravel-vite-plugin';
3 | import react from '@vitejs/plugin-react';
4 |
5 | export default defineConfig({
6 | plugins: [
7 | laravel({
8 | input: 'resources/js/app.jsx',
9 | refresh: true,
10 | }),
11 | react(),
12 | ],
13 | });
14 |
--------------------------------------------------------------------------------