Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ BETTER_AUTH_URL=http://localhost:3000
# Generate a secure random string using: openssl rand -base64 32
BETTER_AUTH_SECRET=your-secret-key-here

# Google OAuth
# Google OAuth (Optional)
# Get these from Google Cloud Console: https://console.cloud.google.com
# Required scopes: email, profile
# Leave empty or remove these lines to disable Google OAuth
GOOGLE_CLIENT_ID="your-google-client-id.apps.googleusercontent.com"
GOOGLE_CLIENT_SECRET="your-google-client-secret"

Expand Down
5 changes: 4 additions & 1 deletion app/[locale]/(app)/auth/(auth-layout)/signin/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { CredentialsLoginForm } from "@/features/auth/signin/ui/CredentialsLoginForm";
import { isGoogleOAuthEnabled } from "@/features/auth/lib/is-google-oauth-enabled";

export default async function AuthSignInPage() {
return <CredentialsLoginForm />;
const googleOAuthEnabled = isGoogleOAuthEnabled();

return <CredentialsLoginForm googleOAuthEnabled={googleOAuthEnabled} />;
}
4 changes: 3 additions & 1 deletion app/[locale]/(app)/auth/(auth-layout)/signup/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Link from "next/link";
import { getI18n } from "locales/server";
import { paths } from "@/shared/constants/paths";
import { SignUpForm } from "@/features/auth/signup/ui/signup-form";
import { isGoogleOAuthEnabled } from "@/features/auth/lib/is-google-oauth-enabled";

export const metadata = {
title: "Sign Up - Workout.cool",
Expand All @@ -11,6 +12,7 @@ export const metadata = {

export default async function AuthSignUpPage() {
const t = await getI18n();
const googleOAuthEnabled = isGoogleOAuthEnabled();

return (
<div className="container mx-auto max-w-lg px-4 py-8">
Expand All @@ -19,7 +21,7 @@ export default async function AuthSignUpPage() {
<p className="text-muted-foreground">{t("register_description")}</p>
</div>

<SignUpForm />
<SignUpForm googleOAuthEnabled={googleOAuthEnabled} />

<div className="text-muted-foreground mt-6 text-center text-sm">
<p>
Expand Down
4 changes: 4 additions & 0 deletions docs/SELF-HOSTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ BETTER_AUTH_SECRET=your-secret-key-here

# Optional: Seed sample data on first run
SEED_SAMPLE_DATA=true

# Optional: Google OAuth (can be omitted if not using Google sign-in)
# GOOGLE_CLIENT_ID="your-google-client-id.apps.googleusercontent.com"
# GOOGLE_CLIENT_SECRET="your-google-client-secret"
```

#### 4. Customize Sample Data (Optional)
Expand Down
4 changes: 2 additions & 2 deletions src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export const env = createEnv({
server: {
BETTER_AUTH_URL: z.string().url(),
DATABASE_URL: z.string().url(),
GOOGLE_CLIENT_ID: z.string().min(1),
GOOGLE_CLIENT_SECRET: z.string().min(1),
GOOGLE_CLIENT_ID: z.string().min(1).optional(),
GOOGLE_CLIENT_SECRET: z.string().min(1).optional(),
NODE_ENV: z.enum(["development", "production", "test"]),
BETTER_AUTH_SECRET: z.string().min(1),
OPENPANEL_SECRET_KEY: z.string().optional(),
Expand Down
34 changes: 18 additions & 16 deletions src/features/auth/lib/better-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,20 +123,22 @@ export const auth = betterAuth({
},
enabled: true,
},
socialProviders: {
google: {
enabled: true,
clientId: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET,
mapProfileToUser: async (profile) => {
return {
...profile,
email: profile.email,
firstName: profile.given_name,
lastName: profile.family_name,
role: UserRole.user,
};
},
},
},
socialProviders: env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET
? {
google: {
enabled: true,
clientId: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET,
mapProfileToUser: async (profile) => {
return {
...profile,
email: profile.email,
firstName: profile.given_name,
lastName: profile.family_name,
role: UserRole.user,
};
},
},
}
: {},
});
9 changes: 9 additions & 0 deletions src/features/auth/lib/is-google-oauth-enabled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { env } from "@/env";

/**
* Check if Google OAuth is enabled by verifying both required environment variables are set
* @returns true if Google OAuth is enabled, false otherwise
*/
export function isGoogleOAuthEnabled(): boolean {
return !!(env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET);
}
28 changes: 18 additions & 10 deletions src/features/auth/signin/ui/CredentialsLoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Alert, AlertDescription } from "@/components/ui/alert";

export function CredentialsLoginForm({ className, ...props }: React.ComponentPropsWithoutRef<"form">) {
type CredentialsLoginFormProps = React.ComponentPropsWithoutRef<"form"> & {
googleOAuthEnabled?: boolean;
};

export function CredentialsLoginForm({ className, googleOAuthEnabled = false, ...props }: CredentialsLoginFormProps) {
const t = useI18n();
const searchParams = useSearchParams();
const isResetSuccess = searchParams.get("reset") === "success";
Expand Down Expand Up @@ -66,16 +70,20 @@ export function CredentialsLoginForm({ className, ...props }: React.ComponentPro
</div>
</form>

<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background text-muted-foreground px-2">{t("commons.or")}</span>
</div>
</div>
{googleOAuthEnabled && (
<>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background text-muted-foreground px-2">{t("commons.or")}</span>
</div>
</div>

<ProviderButton action="signin" className="w-full" providerId="google" variant="outline" />
<ProviderButton action="signin" className="w-full" providerId="google" variant="outline" />
</>
)}

<div className="text-center text-sm">
{t("commons.dont_have_account")}{" "}
Expand Down
30 changes: 19 additions & 11 deletions src/features/auth/signup/ui/signup-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import { signUpSchema } from "../schema/signup.schema";

import type { SignUpSchema } from "../schema/signup.schema";

export const SignUpForm = () => {
type SignUpFormProps = {
googleOAuthEnabled?: boolean;
};

export const SignUpForm = ({ googleOAuthEnabled = false }: SignUpFormProps) => {
const t = useI18n();
const searchParams = useSearchParams();
const redirectUrl = searchParams.get("redirect");
Expand Down Expand Up @@ -120,18 +124,22 @@ export const SignUpForm = () => {
{t("commons.submit")}
</Button>

<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background text-muted-foreground px-2">{t("commons.or")}</span>
{googleOAuthEnabled && (
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background text-muted-foreground px-2">{t("commons.or")}</span>
</div>
</div>
</div>
)}
</Form>
<div className="mt-2 flex flex-col gap-2">
<ProviderButton action="signup" providerId="google" variant="default" />
</div>
{googleOAuthEnabled && (
<div className="mt-2 flex flex-col gap-2">
<ProviderButton action="signup" providerId="google" variant="default" />
</div>
)}

<div className="mt-4 text-center text-sm">
{t("commons.already_have_account")}{" "}
Expand Down