Skip to content

Commit

Permalink
feat: add container to StartProfile
Browse files Browse the repository at this point in the history
  • Loading branch information
dominik-stumpf committed Jul 24, 2024
1 parent a993155 commit d475d01
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const chains: OnboardingChain[] = [
] as const

export const OnboardingDriver = () => {
const [chainIndex, setChainIndex] = useState(0)
const [chainIndex, setChainIndex] = useState(3)
// TODO: remove default chosen subscription, as it is only there for debug
// purposes
const [chainData, setChainData] = useState<Partial<ChainData>>({
Expand Down
113 changes: 69 additions & 44 deletions src/app/(marketing)/create-profile/_components/StartProfile.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client"

import { ConnectFarcasterButton } from "@/components/Account/components/AccountModal/components/FarcasterProfile"
import { Avatar } from "@/components/ui/Avatar"
import { Button } from "@/components/ui/Button"
Expand All @@ -9,63 +11,102 @@ import {
FormLabel,
} from "@/components/ui/Form"
import { Input } from "@/components/ui/Input"
// import useUser from "components/[guild]/hooks/useUser"
import { FarcasterProfile } from "@guildxyz/types"
import { zodResolver } from "@hookform/resolvers/zod"
import { User } from "@phosphor-icons/react"
import { ArrowRight } from "@phosphor-icons/react/dist/ssr"
import { AvatarFallback } from "@radix-ui/react-avatar"
import { useState } from "react"
import { AvatarFallback, AvatarImage } from "@radix-ui/react-avatar"
import { useEffect, useState } from "react"
import { FormProvider, useForm } from "react-hook-form"
import { z } from "zod"
import FarcasterImage from "/src/static/socialIcons/farcaster.svg"
import { useCreateProfile } from "../_hooks/useCreateProfile"
import { profileSchema } from "../schemas"
import { OnboardingChain } from "../types"

const formSchema = z.object({
name: z.string().max(100, { message: "Name cannot exceed 100 characters" }),
username: z
.string()
.min(1, { message: "Handle is required" })
.max(100, { message: "Handle cannot exceed 100 characters" })
.superRefine((value, ctx) => {
const pattern = /^[\w\-.]+$/
const isValid = pattern.test(value)
if (!isValid) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message:
"Handle must only contain either alphanumeric, hyphen, underscore or dot characters",
})
}
}),
})
enum CreateMethod {
FillByFarcaster,
FromBlank,
}

// TODO: use ConnectFarcasterButton
export const StartProfile: OnboardingChain = () => {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
// const farcasterProfiles = useUser().farcasterProfiles || []
// const farcasterProfile = farcasterProfiles.at(0)
const [farcasterProfile, setFarcasterProfile] = useState<FarcasterProfile>()
const [method, setMethod] = useState<CreateMethod>()

useEffect(() => {
if (method !== CreateMethod.FillByFarcaster) return

const mockedFarcasterProfile: FarcasterProfile = {
username: "mocked farcaster username",
userId: 3428402797897,
avatar: "https://picsum.photos/128",
fid: 98798739222,
createdAt: new Date("2008"),
}
setTimeout(() => {
setFarcasterProfile(mockedFarcasterProfile)
}, 1000)
}, [method])
useEffect(() => {
if (!farcasterProfile || method !== CreateMethod.FillByFarcaster) return
form.setValue("name", farcasterProfile.username ?? "", { shouldValidate: true })
form.setValue("username", farcasterProfile.userId.toString(), {
shouldValidate: true,
})
}, [farcasterProfile, method])

const form = useForm<z.infer<typeof profileSchema>>({
resolver: zodResolver(profileSchema),
defaultValues: {
name: "",
username: "",
},
mode: "onTouched",
})

function onSubmit(values: z.infer<typeof formSchema>) {
console.log(values)
const createProfile = useCreateProfile()
async function onSubmit(values: z.infer<typeof profileSchema>) {
// createProfile.onSubmit(values)
console.log("onSubmit", values)
}

const [startMethod, setStartMethod] = useState<"farcaster">()
return (
<div className="flex w-[28rem] flex-col gap-3 p-8">
<h1 className="mb-10 text-pretty text-center font-bold font-display text-2xl leading-none tracking-tight">
Start your Guild Profile!
</h1>

<Avatar className="mb-8 size-36 self-center border bg-card-secondary">
<AvatarImage
src={farcasterProfile?.avatar}
width={144}
height={144}
></AvatarImage>
<AvatarFallback>
<User size={32} />
</AvatarFallback>
</Avatar>

{startMethod ? (
{method === undefined ? (
<>
<ConnectFarcasterButton
className="ml-0 w-full gap-2"
size="md"
onClick={() => setMethod(CreateMethod.FillByFarcaster)}
>
<FarcasterImage />
Connect farcaster
</ConnectFarcasterButton>

<Button variant="ghost">
I don't have a Farcaster profile
<ArrowRight weight="bold" />
</Button>
</>
) : (
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
Expand Down Expand Up @@ -98,29 +139,13 @@ export const StartProfile: OnboardingChain = () => {
className="w-full"
type="submit"
colorScheme="success"
onClick={() => setStartMethod(undefined)}
onClick={() => setMethod(CreateMethod.FromBlank)}
disabled={!form.formState.isValid}
>
Start my profile
</Button>
</form>
</FormProvider>
) : (
<>
<ConnectFarcasterButton
className="ml-0 w-full gap-2"
size="md"
onClick={() => setStartMethod("farcaster")}
>
<FarcasterImage />
Connect farcaster
</ConnectFarcasterButton>

<Button variant="ghost">
I don't have a Farcaster profile
<ArrowRight weight="bold" />
</Button>
</>
)}
</div>
)
Expand Down
41 changes: 41 additions & 0 deletions src/app/(marketing)/create-profile/_hooks/useCreateProfile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useToast } from "@/components/ui/hooks/useToast"
import useJsConfetti from "components/create-guild/hooks/useJsConfetti"
import { SignedValidation, useSubmitWithSign } from "hooks/useSubmit"
import { useRouter } from "next/navigation"
import fetcher from "utils/fetcher"
import { z } from "zod"
import { profileSchema } from "../schemas"

export const useCreateProfile = () => {
const router = useRouter()
const { toast } = useToast()
const triggerConfetti = useJsConfetti()

const createProfile = async (signedValidation: SignedValidation) =>
fetcher(`/v2/profiles`, {
method: "POST",
...signedValidation,
})

const submitWithSign = useSubmitWithSign<unknown>(createProfile, {
onSuccess: (response) => {
triggerConfetti()
toast({
variant: "success",
title: "Successfully created profile",
})
// router.push(`/profile/${response.username}`)
},
onError: (error) =>
toast({
variant: "error",
title: "Failed to create profile",
description: error?.message,
}),
})
return {
...submitWithSign,
onSubmit: (payload: z.infer<typeof profileSchema>) =>
submitWithSign.onSubmit(payload),
}
}
36 changes: 36 additions & 0 deletions src/app/(marketing)/create-profile/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { z } from "zod"
export const profileSchema = z.object({
name: z
.string()
.max(100, { message: "Name cannot exceed 100 characters" })
.optional(),
username: z
.string()
.min(1, { message: "Handle is required" })
.max(100, { message: "Handle cannot exceed 100 characters" })
.superRefine((value, ctx) => {
const pattern = /^[\w\-.]+$/
const isValid = pattern.test(value)
if (!isValid) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message:
"Handle must only contain either alphanumeric, hyphen, underscore or dot characters",
})
}
}),
bio: z
.string()
.max(1000, { message: "Bio cannot exceed 500 characters" })
.optional(),
profileImageUrl: z
.string()
.url({ message: "Profile image must be a valid URL" })
.max(500, { message: "Profile image URL cannot exceed 500 characters" })
.optional(),
backgroundImageUrl: z
.string()
.url({ message: "Background image must be a valid URL" })
.max(500, { message: "Profile image URL cannot exceed 500 characters" })
.optional(),
})

0 comments on commit d475d01

Please sign in to comment.