diff --git a/app/bounty/create/page.tsx b/app/bounty/create/page.tsx new file mode 100644 index 0000000..70e0063 --- /dev/null +++ b/app/bounty/create/page.tsx @@ -0,0 +1,53 @@ +"use client"; + +import { useEffect } from "react"; +import { useRouter } from "next/navigation"; +import { authClient } from "@/lib/auth-client"; +import { useUserRole } from "@/hooks/use-user-role"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { AlertCircle } from "lucide-react"; + +export default function CreateBountyPage() { + const router = useRouter(); + const { isPending } = authClient.useSession(); + const userRole = useUserRole(); + + useEffect(() => { + // Redirect to /bounty if the user is not a sponsor + if (!isPending && userRole !== "sponsor") { + router.push("/bounty"); + } + }, [userRole, isPending, router]); + + // Show nothing while checking auth or redirecting + if (isPending || userRole !== "sponsor") { + return null; + } + + return ( +
+

Create a Bounty

+ + +
+ + Coming Soon +
+ + The bounty creation form is under development + +
+ + We're building a powerful form to help you create bounties. Check back + soon! + +
+
+ ); +} diff --git a/components/global-navbar.tsx b/components/global-navbar.tsx index 5a0aeb6..5d819ee 100644 --- a/components/global-navbar.tsx +++ b/components/global-navbar.tsx @@ -21,11 +21,13 @@ import { DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; +import { useUserRole } from "@/hooks/use-user-role"; import { Wallet, LogIn, Fingerprint } from "lucide-react"; export function GlobalNavbar() { const pathname = usePathname(); + const userRole = useUserRole(); const { walletInfo, isConnected, isRegistered, connect, isLoading } = useSmartWallet(); @@ -112,6 +114,18 @@ export function GlobalNavbar() { > Wallet + {userRole === "sponsor" && ( + + Create + + )} ; -type SessionCache = { user?: Partial } & Record< - string, - unknown ->; +type SessionCache = { + user?: Partial; +} & Record; interface ProfileTabProps { defaultValues: ProfileFormValues; @@ -64,6 +65,8 @@ interface ProfileTabProps { export function ProfileTab({ defaultValues }: ProfileTabProps) { const queryClient = useQueryClient(); const { mutateAsync, isPending } = useUpdateUserMutation(); + const currentRole = useUserRole(); + const [isTogglingRole, setIsTogglingRole] = useState(false); const form = useForm({ resolver: zodResolver(profileSchema), @@ -95,6 +98,29 @@ export function ProfileTab({ defaultValues }: ProfileTabProps) { } }; + const handleRoleToggle = async () => { + setIsTogglingRole(true); + const newRole = currentRole === "sponsor" ? "contributor" : "sponsor"; + + const previous = queryClient.getQueryData(authKeys.session()); + + queryClient.setQueryData(authKeys.session(), (old) => { + if (!old || typeof old !== "object") return old; + return { + ...old, + user: { ...old.user, role: newRole }, + }; + }); + + try { + await mutateAsync({ role: newRole as "sponsor" | "contributor" }); + } catch { + queryClient.setQueryData(authKeys.session(), previous); + } finally { + setIsTogglingRole(false); + } + }; + return (
@@ -178,6 +204,38 @@ export function ProfileTab({ defaultValues }: ProfileTabProps) { /> +
+

Account Settings

+
+
+

Sponsor Access

+

+ {currentRole === "sponsor" + ? "You have sponsor privileges. Click to switch to contributor." + : "You are a contributor. Click to switch to sponsor."} +

+
+ +
+
+ {form.formState.errors.root && (

{form.formState.errors.root.message} diff --git a/hooks/use-user-mutations.ts b/hooks/use-user-mutations.ts index 41a3fe6..28e16e2 100644 --- a/hooks/use-user-mutations.ts +++ b/hooks/use-user-mutations.ts @@ -10,6 +10,7 @@ export interface UpdateUserParams { github?: string; twitter?: string; website?: string; + role?: "sponsor" | "contributor"; } export async function updateUser(params: UpdateUserParams) { diff --git a/hooks/use-user-role.ts b/hooks/use-user-role.ts new file mode 100644 index 0000000..0dc62d1 --- /dev/null +++ b/hooks/use-user-role.ts @@ -0,0 +1,13 @@ +import { authClient } from "@/lib/auth-client"; + +/** + * Hook to get the current user's role from the session. + * Returns "sponsor", "contributor", or undefined if not authenticated. + */ +export function useUserRole(): "sponsor" | "contributor" | undefined { + const { data: session } = authClient.useSession(); + return (session?.user as { role?: string } | undefined)?.role as + | "sponsor" + | "contributor" + | undefined; +} diff --git a/lib/auth-client.ts b/lib/auth-client.ts index 4607d70..bae9d7c 100644 --- a/lib/auth-client.ts +++ b/lib/auth-client.ts @@ -18,6 +18,7 @@ export const authClient = createAuthClient({ github: { type: "string", required: false }, twitter: { type: "string", required: false }, website: { type: "string", required: false }, + role: { type: "string", required: false }, }, }), ], diff --git a/lib/server-auth.ts b/lib/server-auth.ts index a47c328..e84c3fd 100644 --- a/lib/server-auth.ts +++ b/lib/server-auth.ts @@ -10,6 +10,7 @@ export interface User { github?: string; twitter?: string; website?: string; + role?: "sponsor" | "contributor"; } interface SessionUser { @@ -105,6 +106,7 @@ export async function getCurrentUser(): Promise { github?: string | null; twitter?: string | null; website?: string | null; + role?: string | null; }; const id = u.id; @@ -119,6 +121,9 @@ export async function getCurrentUser(): Promise { github: u.github ?? undefined, twitter: u.twitter ?? undefined, website: u.website ?? undefined, + role: (u.role === "sponsor" || u.role === "contributor" + ? u.role + : "contributor") as "sponsor" | "contributor", }; } catch (error) { console.error("Failed to resolve current user from session:", error);