Creating two log in flows for Creators and Fans

How can I create two separate log in flows for two types of users, "creators" and "fans" and how can I get this to also work with social auth like Google?
13 Replies
kapa.ai
kapa.ai2w ago
Hi @Vinny (@Wasp). I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- To create separate login flows for "creators" and "fans" in Wasp, including support for social authentication like Google, you'll need to customize the authentication process. While Wasp doesn't have built-in support for multiple user types, you can achieve this with some customization. Here's a high-level approach: 1. User Entity: First, modify your User entity to include a field for user type:
entity User {=psl
id Int @id @default(autoincrement())
userType String // 'creator' or 'fan'
// ... other fields
psl=}
entity User {=psl
id Int @id @default(autoincrement())
userType String // 'creator' or 'fan'
// ... other fields
psl=}
2. Custom Signup Action: Create a custom signup action to handle the user type:
import {
createProviderId,
createUser,
// ... other imports
} from 'wasp/server/auth'

export const customSignup = async (args, context) => {
// ... validation logic

const providerId = createProviderId('username', args.username)
const providerData = await sanitizeAndSerializeProviderData({
hashedPassword: args.password,
})

await createUser(
providerId,
providerData,
{ userType: args.userType } // Add user type to additional data
)

// ... rest of the signup logic
}
import {
createProviderId,
createUser,
// ... other imports
} from 'wasp/server/auth'

export const customSignup = async (args, context) => {
// ... validation logic

const providerId = createProviderId('username', args.username)
const providerData = await sanitizeAndSerializeProviderData({
hashedPassword: args.password,
})

await createUser(
providerId,
providerData,
{ userType: args.userType } // Add user type to additional data
)

// ... rest of the signup logic
}
Customizing the Auth Flow (https://wasp-lang.dev/docs/auth/username-and-pass#2-creating-your-custom-sign-up-action) 3. Social Authentication: For social authentication like Google, you'll need to customize the userSignupFields function:
export function userSignupFields(profile) {
return {
userType: profile.userType || 'fan', // Default to 'fan' if not specified
// ... other fields
}
}
export function userSignupFields(profile) {
return {
userType: profile.userType || 'fan', // Default to 'fan' if not specified
// ... other fields
}
}
Google Auth API Reference (https://wasp-lang.dev/docs/auth/social-auth/google#api-reference) 4. Client-side Routing: On the client-side, use the useAuth() hook to check the user type and redirect accordingly:
import { useAuth } from 'wasp/client/auth'
import { Redirect } from 'react-router-dom'

export function HomePage() {
const { data: user } = useAuth()

if (user.userType === 'creator') {
return <Redirect to="/creator-dashboard" />
} else if (user.userType === 'fan') {
return <Redirect to="/fan-dashboard" />
}

// ... rest of the component
}
import { useAuth } from 'wasp/client/auth'
import { Redirect } from 'react-router-dom'

export function HomePage() {
const { data: user } = useAuth()

if (user.userType === 'creator') {
return <Redirect to="/creator-dashboard" />
} else if (user.userType === 'fan') {
return <Redirect to="/fan-dashboard" />
}

// ... rest of the component
}
Showing the Correct State on the Client (https://wasp-lang.dev/docs/auth/social-auth/overview#3-showing-the-correct-state-on-the-client) 5. Auth UI: You'll need to create separate login/signup pages for creators and fans. You can use Wasp's Auth UI components as a starting point:
import { LoginForm } from 'wasp/client/auth'

export function CreatorLoginPage() {
return <LoginForm extraFields={{ userType: 'creator' }} />
}

export function FanLoginPage() {
return <LoginForm extraFields={{ userType: 'fan' }} />
}
import { LoginForm } from 'wasp/client/auth'

export function CreatorLoginPage() {
return <LoginForm extraFields={{ userType: 'creator' }} />
}

export function FanLoginPage() {
return <LoginForm extraFields={{ userType: 'fan' }} />
}
Auth UI Components (https://wasp-lang.dev/docs/auth/ui#auth-components) Remember to update your routes in main.wasp to include these new pages. This approach provides a basic framework for creating separate login flows for creators and fans, including social authentication. However, please note that the exact implementation details may vary depending on your specific requirements and the current version of Wasp you're using. You might need to further customize the authentication process to fully meet your needs. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @Vinny (@Wasp), I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
Vinny (@Wasp)
Vinny (@Wasp)OP2w ago
ok @santi_matero the bot gave an ok suggestion but I see it didn't get everything right. Here are some options for you to consider: 1. Add some extra properties to the User:
model User {
id String @id @default(uuid())
createdAt DateTime @default(now())
email String? @unique
username String? @unique
userType String @default('fan') // 'creator' | 'fan'
isSignupComplete Boolean @default(false)
model User {
id String @id @default(uuid())
createdAt DateTime @default(now())
email String? @unique
username String? @unique
userType String @default('fan') // 'creator' | 'fan'
isSignupComplete Boolean @default(false)
After they go through the sign up process, isSignupComplete will be false. Check for that on the homepage and redirect them to a CompleteSignupPage with a form that allows the choose if they want to continue as a "creator" or "fan".
import { useAuth } from 'wasp/client/auth'
import { Redirect } from 'react-router-dom'

export function HomePage() {
const { data: user } = useAuth()

if (user.userType === 'creator') {
return <Redirect to="/creator-dashboard" />
} else if (user.userType === 'fan') {
return <Redirect to="/fan-dashboard" />
}

// ... rest of the component
}
import { useAuth } from 'wasp/client/auth'
import { Redirect } from 'react-router-dom'

export function HomePage() {
const { data: user } = useAuth()

if (user.userType === 'creator') {
return <Redirect to="/creator-dashboard" />
} else if (user.userType === 'fan') {
return <Redirect to="/fan-dashboard" />
}

// ... rest of the component
}
from there on, you redirect users to either the creator or fan dashboard pages 2. You create two urls, one for the creators and one for the fans, e.g. - https://yourapp.com/login?isCreator=1 for creators - https://yourapp.com/login?isCreator=0 for fans Then in your SignupPage, you do the following:
import { SignupForm, googleSignInUrl } from 'wasp/client/auth'; // Import the googleSignInUrl
import { AuthPageLayout } from './AuthPageLayout';
import { useSearchParams } from 'react-router-dom';
import { useState, useEffect } from 'react';

type UserType = 'creator' | 'fan';

export function Signup() {
const [searchParams] = useSearchParams();
const [userType, setUserType] = useState<UserType>();

useEffect(() => {
const userTypeParam = searchParams.get('userType');
if (userTypeParam === 'creator' || userTypeParam === 'fan') {
setUserType(userTypeParam);
} else {
setUserType('fan');
}
}, [searchParams]);

return (
<AuthPageLayout>
<a href={googleSignInUrl.concat(`?userType=${userType}`)}>
<button>Google Login</button>
</a>
//..
import { SignupForm, googleSignInUrl } from 'wasp/client/auth'; // Import the googleSignInUrl
import { AuthPageLayout } from './AuthPageLayout';
import { useSearchParams } from 'react-router-dom';
import { useState, useEffect } from 'react';

type UserType = 'creator' | 'fan';

export function Signup() {
const [searchParams] = useSearchParams();
const [userType, setUserType] = useState<UserType>();

useEffect(() => {
const userTypeParam = searchParams.get('userType');
if (userTypeParam === 'creator' || userTypeParam === 'fan') {
setUserType(userTypeParam);
} else {
setUserType('fan');
}
}, [searchParams]);

return (
<AuthPageLayout>
<a href={googleSignInUrl.concat(`?userType=${userType}`)}>
<button>Google Login</button>
</a>
//..
Then you use the Auth Hooks to deal with it: https://wasp-lang.dev/docs/auth/auth-hooks I can help you out more with this later if you're interested
santi_matero
santi_matero2w ago
Dude you're the best! Thanks a lot! Will try it out after work 👍
Vinny (@Wasp)
Vinny (@Wasp)OP2w ago
Any luck?
santi_matero
santi_matero5d ago
Hi @Vinny (@Wasp) , sorry I was sick the entire week and just now getting back on my feet. Will give you an update once I've implemented it. Thanks again I'm more interested in the second option, since I don't need an extra form for the user to choose. Do I need to implement the Google Login myself in that case? I assume I need the auth hook to save the usertype? which hook, should I use?
Vinny (@Wasp)
Vinny (@Wasp)OP4d ago
here is an example. It's quite a lot of work to get it set up using both email and google becasue you have to customize both processes. Honestly, I would go with option 1. above because it could save you a lot of headached in the future, but here is option 2. anyway: https://github.com/vincanger/custom-auth-open-saas/tree/master/src/auth You'll notice: - a custom signup action - custom signup form (for email and password login) - a custom google signup button These custom components will assign the userType you set in your url query param http://localhost:3000/signup?userType=creator or it defaults to userType=fan You still have to: - implement the email verification step in customSignup.ts - fix the signup page styles - redirect the users to the proper dashboards based on user.userType.type (you might have to create a custom query to get this type -- see schema.prisma)
GitHub
custom-auth-open-saas/src/auth at master · vincanger/custom-auth-op...
Contribute to vincanger/custom-auth-open-saas development by creating an account on GitHub.
santi_matero
santi_matero4d ago
You even made a repo? Thanks! Ok, I see your point. That is a lot of added complexity, which makes option one tempting, but adding another form to the funnel will surely increase the bounce rate
MEE6
MEE64d ago
Wohooo @santi_matero, you just became a Waspeteer level 1!
santi_matero
santi_matero4d ago
Is this a feature that will be supported by wasp in the future? without having to use custom auth
Vinny (@Wasp)
Vinny (@Wasp)OP4d ago
there are a lot of ways you could get around this. For example, it's most likely that users will be of "fan" userType so you could just default it to that, and put something in account settings that allows them to change their userType to creator. So its only creators that need to do the extra step. or, don't use social auth and only use email & password and just use a custom signup field, which is a lot easier than creating all these custom sign up actions and hooks: https://wasp-lang.dev/docs/auth/overview#signup-fields-customization FYI @miho -- trying to add signup field customization for both email & password and google auth methods is a pain. For email and password I could have just used a custom signup field in defineUserSignupFields and customized the signupForm But with google auth, I had to use the beforeOauthRedirect hook, create a new userType record, then use the onAfterSignup hook to assign that userType to the newly created user. It would be cool if we could just pass in one property to the SignupForm and have that property be passed to ALL auth methods, e.g.
export const SignupPage = () => {
return (
<SignupForm
additionalFields={[
{
name: 'userType',
label: 'User Type',
type: 'input',
},
/>
//...

export const getUsernameUserFields = defineUserSignupFields({
userType: (data) => data.userType,
});

export const getGoogleUserFields = defineUserSignupFields({
userType: (data) => data.userType,
email: (data) => {
const googleData = googleDataSchema.parse(data);
return googleData.profile.email;
},
export const SignupPage = () => {
return (
<SignupForm
additionalFields={[
{
name: 'userType',
label: 'User Type',
type: 'input',
},
/>
//...

export const getUsernameUserFields = defineUserSignupFields({
userType: (data) => data.userType,
});

export const getGoogleUserFields = defineUserSignupFields({
userType: (data) => data.userType,
email: (data) => {
const googleData = googleDataSchema.parse(data);
return googleData.profile.email;
},
miho
miho3d ago
It's a thing we could support better I agree. The complexity arises from the fact that we have to do all this redirects. App -> OAuth provider -> App and then we don't get the same form data at the end unless we store it somewhere in between the redirects. Maybe we could offer an easier way to temporarily store data related to some ID?
Vinny (@Wasp)
Vinny (@Wasp)OP3d ago
yeah thats what I did manually in the example repo above. it would be cool to be able to save a property straight to the user entity, but if not, then a related entity with the oauth uniqueRequestId did the trick. I was able to relate the two entities in the onAfterSignup hook using that ID.
santi_matero
santi_matero3d ago
Yes, that would be great 👍 So that it's not necessary to go the custom route I would remove the social auth only as a last option, since I know from other apps that it is widely used. It would be also great if we could support other socials, like FB for example
Want results from more Discord servers?
Add your server