Skip to content

Commit

Permalink
feat: google auth
Browse files Browse the repository at this point in the history
  • Loading branch information
Shchepotin committed Sep 25, 2023
1 parent e0f6c2f commit f6ef737
Show file tree
Hide file tree
Showing 16 changed files with 191 additions and 10 deletions.
3 changes: 3 additions & 0 deletions example.env.local
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
NEXT_PUBLIC_API_URL=https://nestjs-boilerplate-test.herokuapp.com/api

NEXT_PUBLIC_IS_GOOGLE_AUTH_ENABLED=true
NEXT_PUBLIC_GOOGLE_CLIENT_ID=778828327699-3hu034kj1u76k4o5qla8221ovtbgl2g0.apps.googleusercontent.com
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@hookform/resolvers": "^3.2.0",
"@mui/icons-material": "5.14.9",
"@mui/material": "5.14.10",
"@react-oauth/google": "^0.11.1",
"@tanstack/react-query": "^4.35.3",
"@tanstack/react-query-devtools": "^4.35.3",
"@types/node": "20.6.5",
Expand Down
11 changes: 7 additions & 4 deletions src/app/[language]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import LeavePageProvider from "@/services/leave-page/leave-page-provider";
import QueryClientProvider from "@/services/react-query/query-client-provider";
import queryClient from "@/services/react-query/query-client";
import ReactQueryDevtools from "@/services/react-query/react-query-devtools";
import GoogleOAuthProvider from "@/services/social-auth/google/google-auth-provider";

type Props = {
params: { language: string };
Expand Down Expand Up @@ -52,10 +53,12 @@ export default function RootLayout({
<SnackbarProvider maxSnack={3}>
<StoreLanguageProvider>
<AuthProvider>
<LeavePageProvider>
<ResponsiveAppBar />
{children}
</LeavePageProvider>
<GoogleOAuthProvider>
<LeavePageProvider>
<ResponsiveAppBar />
{children}
</LeavePageProvider>
</GoogleOAuthProvider>
</AuthProvider>
</StoreLanguageProvider>
</SnackbarProvider>
Expand Down
17 changes: 13 additions & 4 deletions src/app/[language]/profile/edit/page-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import useLeavePage from "@/services/leave-page/use-leave-page";
import Box from "@mui/material/Box";
import HTTP_CODES_ENUM from "@/services/api/types/http-codes";
import { useTranslation } from "@/services/i18n/client";
import { UserProviderEnum } from "@/services/api/types/user";

type EditProfileBasicInfoFormData = {
firstName: string;
Expand Down Expand Up @@ -160,8 +161,8 @@ function FormBasicInfo() {
<FormProvider {...methods}>
<Container maxWidth="xs">
<form onSubmit={handleSubmit(onSubmit)}>
<Grid container spacing={2}>
<Grid item xs={12} mt={3}>
<Grid container spacing={2} mb={3} mt={3}>
<Grid item xs={12}>
<Typography variant="h6">{t("profile:title1")}</Typography>
</Grid>
<Grid item xs={12}>
Expand Down Expand Up @@ -260,7 +261,7 @@ function FormChangePassword() {
<Container maxWidth="xs">
<form onSubmit={handleSubmit(onSubmit)}>
<Grid container spacing={2} mb={2}>
<Grid item xs={12} mt={3}>
<Grid item xs={12}>
<Typography variant="h6">{t("profile:title2")}</Typography>
</Grid>
<Grid item xs={12}>
Expand Down Expand Up @@ -307,11 +308,19 @@ function FormChangePassword() {
);
}

function FormChangePasswordWrapper() {
const { user } = useAuth();

return user?.provider === UserProviderEnum.EMAIL ? (
<FormChangePassword />
) : null;
}

function EditProfile() {
return (
<>
<FormBasicInfo />
<FormChangePassword />
<FormChangePasswordWrapper />
</>
);
}
Expand Down
14 changes: 14 additions & 0 deletions src/app/[language]/sign-in/page-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import Link from "@/components/link";
import Box from "@mui/material/Box";
import HTTP_CODES_ENUM from "@/services/api/types/http-codes";
import { useTranslation } from "@/services/i18n/client";
import SocialAuth from "@/services/social-auth/social-auth";
import Divider from "@mui/material/Divider";
import Chip from "@mui/material/Chip";
import { isGoogleAuthEnabled } from "@/services/social-auth/google/google-config";

type SignInFormData = {
email: string;
Expand Down Expand Up @@ -145,6 +149,16 @@ function Form() {
</Button>
</Box>
</Grid>

{[isGoogleAuthEnabled].some(Boolean) && (
<Grid item xs={12}>
<Divider sx={{ mb: 2 }}>
<Chip label={t("sign-in:or")} />
</Divider>

<SocialAuth />
</Grid>
)}
</Grid>
</form>
</Container>
Expand Down
14 changes: 14 additions & 0 deletions src/app/[language]/sign-up/page-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import Link from "@/components/link";
import Box from "@mui/material/Box";
import HTTP_CODES_ENUM from "@/services/api/types/http-codes";
import { useTranslation } from "@/services/i18n/client";
import Divider from "@mui/material/Divider";
import Chip from "@mui/material/Chip";
import SocialAuth from "@/services/social-auth/social-auth";
import { isGoogleAuthEnabled } from "@/services/social-auth/google/google-config";

type SignUpFormData = {
firstName: string;
Expand Down Expand Up @@ -177,6 +181,16 @@ function Form() {
</Button>
</Box>
</Grid>

{[isGoogleAuthEnabled].some(Boolean) && (
<Grid item xs={12}>
<Divider sx={{ mb: 2 }}>
<Chip label={t("sign-up:or")} />
</Divider>

<SocialAuth />
</Grid>
)}
</Grid>
</form>
</Container>
Expand Down
25 changes: 25 additions & 0 deletions src/components/full-page-loader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Box from "@mui/material/Box";
import CircularProgress from "@mui/material/CircularProgress";
import Modal from "@mui/material/Modal";

type FullPageLoaderType = {
isLoading: boolean;
};

export function FullPageLoader({ isLoading }: FullPageLoaderType) {
return (
<Modal open={isLoading}>
<Box
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
outline: "none",
}}
>
<CircularProgress />
</Box>
</Modal>
);
}
23 changes: 23 additions & 0 deletions src/services/api/services/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,29 @@ export function useAuthLoginService() {
[fetchBase]
);
}

export type AuthGoogleLoginRequest = {
idToken: string;
};

export type AuthGoogleLoginResponse = Tokens & {
user: User;
};

export function useAuthGoogleLoginService() {
const fetchBase = useFetchBase();

return useCallback(
(data: AuthGoogleLoginRequest) => {
return fetchBase(`${API_URL}/v1/auth/google/login`, {
method: "POST",
body: JSON.stringify(data),
}).then(wrapperFetchJsonResponse<AuthGoogleLoginResponse>);
},
[fetchBase]
);
}

export type AuthSignUpRequest = {
email: string;
password: string;
Expand Down
7 changes: 7 additions & 0 deletions src/services/api/types/user.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { FileEntity } from "./file-entity";

export enum UserProviderEnum {
EMAIL = "email",
GOOGLE = "google",
}

export type User = {
id: number;
email: string;
firstName?: string;
lastName?: string;
photo?: FileEntity;
provider?: UserProviderEnum;
socialId?: string;
};
3 changes: 2 additions & 1 deletion src/services/i18n/locales/en/sign-in.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@
}
}
}
}
},
"or": "OR"
}
3 changes: 2 additions & 1 deletion src/services/i18n/locales/en/sign-up.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,6 @@
}
}
}
}
},
"or": "OR"
}
14 changes: 14 additions & 0 deletions src/services/social-auth/google/google-auth-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"use client";

import { GoogleOAuthProvider as GoogleProvider } from "@react-oauth/google";
import { isGoogleAuthEnabled, googleClientId } from "./google-config";

function GoogleOAuthProvider({ children }: { children: React.ReactNode }) {
return isGoogleAuthEnabled && googleClientId ? (
<GoogleProvider clientId={googleClientId}>{children}</GoogleProvider>
) : (
children
);
}

export default GoogleOAuthProvider;
45 changes: 45 additions & 0 deletions src/services/social-auth/google/google-auth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"use client";

import { useAuthGoogleLoginService } from "@/services/api/services/auth";
import HTTP_CODES_ENUM from "@/services/api/types/http-codes";
import useAuthActions from "@/services/auth/use-auth-actions";
import useAuthTokens from "@/services/auth/use-auth-tokens";
import { CredentialResponse, GoogleLogin } from "@react-oauth/google";
import { useState } from "react";
import { FullPageLoader } from "@/components/full-page-loader";
import useLanguage from "@/services/i18n/use-language";

export default function GoogleSignIn() {
const { setUser } = useAuthActions();
const { setTokensInfo } = useAuthTokens();
const authGoogleLoginService = useAuthGoogleLoginService();
const language = useLanguage();
const [isLoading, setIsLoading] = useState(false);

const onSuccess = async (tokenResponse: CredentialResponse) => {
if (!tokenResponse.credential) return;

setIsLoading(true);

const { status, data } = await authGoogleLoginService({
idToken: tokenResponse.credential,
});

if (status === HTTP_CODES_ENUM.OK) {
setTokensInfo({
token: data.token,
refreshToken: data.refreshToken,
tokenExpires: data.tokenExpires,
});
setUser(data.user);
}
setIsLoading(false);
};

return (
<>
<GoogleLogin onSuccess={onSuccess} locale={language} />
<FullPageLoader isLoading={isLoading} />
</>
);
}
3 changes: 3 additions & 0 deletions src/services/social-auth/google/google-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const isGoogleAuthEnabled =
process.env.NEXT_PUBLIC_IS_GOOGLE_AUTH_ENABLED === "true";
export const googleClientId = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID;
8 changes: 8 additions & 0 deletions src/services/social-auth/social-auth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"use client";

import GoogleSignIn from "./google/google-auth";
import { isGoogleAuthEnabled } from "./google/google-config";

export default function SocialAuth() {
return <>{isGoogleAuthEnabled && <GoogleSignIn />}</>;
}

0 comments on commit f6ef737

Please sign in to comment.