Skip to content

Commit aa02ed7

Browse files
Merge pull request #1 from designbycode/jules-12839775183683902919-0821cb4f
Implement GitHub and Google Socialite Authentication
2 parents 0a27f42 + c859088 commit aa02ed7

31 files changed

Lines changed: 2432 additions & 38 deletions

File tree

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
/storage/*.key
99
/storage/pail
1010
/resources/js/actions
11-
/resources/js/routes
1211
/resources/js/wayfinder
1312
/vendor
1413
.DS_Store
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Auth;
4+
5+
use App\Http\Controllers\Controller;
6+
use App\Models\Social;
7+
use App\Models\User;
8+
use Illuminate\Http\RedirectResponse;
9+
use Illuminate\Support\Facades\Auth;
10+
use Laravel\Socialite\Facades\Socialite;
11+
12+
class SocialiteController extends Controller
13+
{
14+
/**
15+
* Redirect the user to the provider's authentication page.
16+
*/
17+
public function redirect(string $provider): RedirectResponse
18+
{
19+
return Socialite::driver($provider)->redirect();
20+
}
21+
22+
/**
23+
* Obtain the user information from the provider.
24+
*/
25+
public function callback(string $provider): RedirectResponse
26+
{
27+
$socialUser = Socialite::driver($provider)->user();
28+
29+
$socialAccount = Social::where('provider', $provider)
30+
->where('provider_id', $socialUser->getId())
31+
->first();
32+
33+
if ($socialAccount) {
34+
Auth::login($socialAccount->user);
35+
36+
return redirect()->route('dashboard');
37+
}
38+
39+
$user = User::where('email', $socialUser->getEmail())->first();
40+
41+
if (! $user) {
42+
$user = User::create([
43+
'name' => $socialUser->getName() ?? $socialUser->getNickname(),
44+
'email' => $socialUser->getEmail(),
45+
'email_verified_at' => now(),
46+
]);
47+
}
48+
49+
$user->socials()->create([
50+
'provider' => $provider,
51+
'provider_id' => $socialUser->getId(),
52+
'avatar' => $socialUser->getAvatar(),
53+
]);
54+
55+
Auth::login($user);
56+
57+
return redirect()->route('dashboard');
58+
}
59+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*/
12+
public function up(): void
13+
{
14+
Schema::table('users', function (Blueprint $table) {
15+
$table->string('password')->nullable()->change();
16+
});
17+
}
18+
19+
/**
20+
* Reverse the migrations.
21+
*/
22+
public function down(): void
23+
{
24+
Schema::table('users', function (Blueprint $table) {
25+
$table->string('password')->nullable(false)->change();
26+
});
27+
}
28+
};

resources/js/pages/auth/login.tsx

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ import PasswordInput from '@/components/password-input';
44
import TextLink from '@/components/text-link';
55
import { Button } from '@/components/ui/button';
66
import { Checkbox } from '@/components/ui/checkbox';
7+
import { Icon } from '@/components/ui/icon';
78
import { Input } from '@/components/ui/input';
89
import { Label } from '@/components/ui/label';
10+
import { Separator } from '@/components/ui/separator';
911
import { Spinner } from '@/components/ui/spinner';
12+
import { Github, Chrome } from 'lucide-react';
1013
import { register } from '@/routes';
14+
import { redirect } from '@/routes/socialite';
1115
import { store } from '@/routes/login';
1216
import { request } from '@/routes/password';
1317

@@ -26,14 +30,41 @@ export default function Login({
2630
<>
2731
<Head title="Log in" />
2832

29-
<Form
30-
{...store.form()}
31-
resetOnSuccess={['password']}
32-
className="flex flex-col gap-6"
33-
>
34-
{({ processing, errors }) => (
35-
<>
36-
<div className="grid gap-6">
33+
<div className="flex flex-col gap-6">
34+
<div className="grid grid-cols-2 gap-4">
35+
<Button variant="outline" className="w-full" asChild>
36+
<a href={redirect.url('github')}>
37+
<Icon iconNode={Github} className="mr-2 h-4 w-4" />
38+
Github
39+
</a>
40+
</Button>
41+
<Button variant="outline" className="w-full" asChild>
42+
<a href={redirect.url('google')}>
43+
<Icon iconNode={Chrome} className="mr-2 h-4 w-4" />
44+
Google
45+
</a>
46+
</Button>
47+
</div>
48+
49+
<div className="relative">
50+
<div className="absolute inset-0 flex items-center">
51+
<Separator />
52+
</div>
53+
<div className="relative flex justify-center text-xs uppercase">
54+
<span className="bg-background px-2 text-muted-foreground">
55+
Or continue with
56+
</span>
57+
</div>
58+
</div>
59+
60+
<Form
61+
{...store.form()}
62+
resetOnSuccess={['password']}
63+
className="flex flex-col gap-6"
64+
>
65+
{({ processing, errors }) => (
66+
<>
67+
<div className="grid gap-6">
3768
<div className="grid gap-2">
3869
<Label htmlFor="email">Email address</Label>
3970
<Input
@@ -94,17 +125,18 @@ export default function Login({
94125
</Button>
95126
</div>
96127

97-
{canRegister && (
98-
<div className="text-center text-sm text-muted-foreground">
99-
Don't have an account?{' '}
100-
<TextLink href={register()} tabIndex={5}>
101-
Sign up
102-
</TextLink>
103-
</div>
104-
)}
105-
</>
106-
)}
107-
</Form>
128+
{canRegister && (
129+
<div className="text-center text-sm text-muted-foreground">
130+
Don't have an account?{' '}
131+
<TextLink href={register()} tabIndex={5}>
132+
Sign up
133+
</TextLink>
134+
</div>
135+
)}
136+
</>
137+
)}
138+
</Form>
139+
</div>
108140

109141
{status && (
110142
<div className="mb-4 text-center text-sm font-medium text-green-600">

resources/js/pages/auth/register.tsx

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,56 @@ import InputError from '@/components/input-error';
33
import PasswordInput from '@/components/password-input';
44
import TextLink from '@/components/text-link';
55
import { Button } from '@/components/ui/button';
6+
import { Icon } from '@/components/ui/icon';
67
import { Input } from '@/components/ui/input';
78
import { Label } from '@/components/ui/label';
9+
import { Separator } from '@/components/ui/separator';
810
import { Spinner } from '@/components/ui/spinner';
11+
import { Github, Chrome } from 'lucide-react';
912
import { login } from '@/routes';
13+
import { redirect } from '@/routes/socialite';
1014
import { store } from '@/routes/register';
1115

1216
export default function Register() {
1317
return (
1418
<>
1519
<Head title="Register" />
16-
<Form
17-
{...store.form()}
18-
resetOnSuccess={['password', 'password_confirmation']}
19-
disableWhileProcessing
20-
className="flex flex-col gap-6"
21-
>
22-
{({ processing, errors }) => (
23-
<>
24-
<div className="grid gap-6">
20+
<div className="flex flex-col gap-6">
21+
<div className="grid grid-cols-2 gap-4">
22+
<Button variant="outline" className="w-full" asChild>
23+
<a href={redirect.url('github')}>
24+
<Icon iconNode={Github} className="mr-2 h-4 w-4" />
25+
Github
26+
</a>
27+
</Button>
28+
<Button variant="outline" className="w-full" asChild>
29+
<a href={redirect.url('google')}>
30+
<Icon iconNode={Chrome} className="mr-2 h-4 w-4" />
31+
Google
32+
</a>
33+
</Button>
34+
</div>
35+
36+
<div className="relative">
37+
<div className="absolute inset-0 flex items-center">
38+
<Separator />
39+
</div>
40+
<div className="relative flex justify-center text-xs uppercase">
41+
<span className="bg-background px-2 text-muted-foreground">
42+
Or continue with
43+
</span>
44+
</div>
45+
</div>
46+
47+
<Form
48+
{...store.form()}
49+
resetOnSuccess={['password', 'password_confirmation']}
50+
disableWhileProcessing
51+
className="flex flex-col gap-6"
52+
>
53+
{({ processing, errors }) => (
54+
<>
55+
<div className="grid gap-6">
2556
<div className="grid gap-2">
2657
<Label htmlFor="name">Name</Label>
2758
<Input
@@ -95,15 +126,16 @@ export default function Register() {
95126
</Button>
96127
</div>
97128

98-
<div className="text-center text-sm text-muted-foreground">
99-
Already have an account?{' '}
100-
<TextLink href={login()} tabIndex={6}>
101-
Log in
102-
</TextLink>
103-
</div>
104-
</>
105-
)}
106-
</Form>
129+
<div className="text-center text-sm text-muted-foreground">
130+
Already have an account?{' '}
131+
<TextLink href={login()} tabIndex={6}>
132+
Log in
133+
</TextLink>
134+
</div>
135+
</>
136+
)}
137+
</Form>
138+
</div>
107139
</>
108140
);
109141
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { queryParams, type RouteQueryOptions, type RouteDefinition } from './../../wayfinder'
2+
/**
3+
* @see \App\Http\Controllers\AnimateController::index
4+
* @see Http/Controllers/AnimateController.php:11
5+
* @route '/animate-css'
6+
*/
7+
export const index = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({
8+
url: index.url(options),
9+
method: 'get',
10+
})
11+
12+
index.definition = {
13+
methods: ["get","head"],
14+
url: '/animate-css',
15+
} satisfies RouteDefinition<["get","head"]>
16+
17+
/**
18+
* @see \App\Http\Controllers\AnimateController::index
19+
* @see Http/Controllers/AnimateController.php:11
20+
* @route '/animate-css'
21+
*/
22+
index.url = (options?: RouteQueryOptions) => {
23+
return index.definition.url + queryParams(options)
24+
}
25+
26+
/**
27+
* @see \App\Http\Controllers\AnimateController::index
28+
* @see Http/Controllers/AnimateController.php:11
29+
* @route '/animate-css'
30+
*/
31+
index.get = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({
32+
url: index.url(options),
33+
method: 'get',
34+
})
35+
36+
/**
37+
* @see \App\Http\Controllers\AnimateController::index
38+
* @see Http/Controllers/AnimateController.php:11
39+
* @route '/animate-css'
40+
*/
41+
index.head = (options?: RouteQueryOptions): RouteDefinition<'head'> => ({
42+
url: index.url(options),
43+
method: 'head',
44+
})
45+
46+
const animateCss = {
47+
index: Object.assign(index, index),
48+
}
49+
50+
export default animateCss
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { queryParams, type RouteQueryOptions, type RouteDefinition } from './../../wayfinder'
2+
/**
3+
* @see \Inertia\Controller::__invoke
4+
* @see vendor/inertiajs/inertia-laravel/src/Controller.php:13
5+
* @route '/settings/appearance'
6+
*/
7+
export const edit = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({
8+
url: edit.url(options),
9+
method: 'get',
10+
})
11+
12+
edit.definition = {
13+
methods: ["get","head"],
14+
url: '/settings/appearance',
15+
} satisfies RouteDefinition<["get","head"]>
16+
17+
/**
18+
* @see \Inertia\Controller::__invoke
19+
* @see vendor/inertiajs/inertia-laravel/src/Controller.php:13
20+
* @route '/settings/appearance'
21+
*/
22+
edit.url = (options?: RouteQueryOptions) => {
23+
return edit.definition.url + queryParams(options)
24+
}
25+
26+
/**
27+
* @see \Inertia\Controller::__invoke
28+
* @see vendor/inertiajs/inertia-laravel/src/Controller.php:13
29+
* @route '/settings/appearance'
30+
*/
31+
edit.get = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({
32+
url: edit.url(options),
33+
method: 'get',
34+
})
35+
36+
/**
37+
* @see \Inertia\Controller::__invoke
38+
* @see vendor/inertiajs/inertia-laravel/src/Controller.php:13
39+
* @route '/settings/appearance'
40+
*/
41+
edit.head = (options?: RouteQueryOptions): RouteDefinition<'head'> => ({
42+
url: edit.url(options),
43+
method: 'head',
44+
})
45+
46+
const appearance = {
47+
edit: Object.assign(edit, edit),
48+
}
49+
50+
export default appearance

0 commit comments

Comments
 (0)