+
@@ -1056,36 +1263,36 @@ const SponsorForm = () => {
fileName={companyLogoName}
handleFileUpload={handleCompanyLogoUpload}
isUploading={isUploadingLogo && isUploading}
- accept='.png,.jpg,.jpeg,.svg'
- inputId='companyLogoInput'
- label='Company Logo'
+ accept=".png,.jpg,.jpeg,.svg"
+ inputId="companyLogoInput"
+ label="Company Logo"
required={true}
- recommendedSize='Add the image here. Recommended size: 512 x 512px (square format)'
- allowedFileTypes='JPG, PNG, SVG'
+ recommendedSize="Add the image here. Recommended size: 512 x 512px (square format)"
+ allowedFileTypes="JPG, PNG, SVG"
/>
+
+
{isEditProfilePage ? "Submit Updated Details" : "Create Sponsor"}
diff --git a/app/(home)/ambassador-dao/page.tsx b/app/(home)/ambassador-dao/page.tsx
index 77654a63483..1c0ca522492 100644
--- a/app/(home)/ambassador-dao/page.tsx
+++ b/app/(home)/ambassador-dao/page.tsx
@@ -12,6 +12,8 @@ import { useFetchUserDataQuery } from "@/services/ambassador-dao/requests/auth";
import FullScreenLoader from "@/components/ambassador-dao/full-screen-loader";
import { AmbassadorCard } from "@/components/ambassador-dao/dashboard/SideContent";
import { AuthModal } from "@/components/ambassador-dao/sections/auth-modal";
+import { useSession } from "next-auth/react";
+import { useRouter } from "next/navigation";
const WelcomeSection = ({ user }: { user: any }) => {
return (
@@ -71,10 +73,18 @@ const WelcomeSection = ({ user }: { user: any }) => {
const AmbasssadorDao = () => {
const { data: user, isLoading } = useFetchUserDataQuery();
const [openAuthModal, setOpenAuthModal] = useState(false);
+ const { data: session } = useSession();
+ const router = useRouter();
if (isLoading) {
return
;
}
-
+ const handleBecomeClient = () => {
+ if (!session) {
+ const currentUrl = window.location.pathname + window.location.search;
+ router.push(`/login?callbackUrl=${encodeURIComponent(currentUrl)}`);
+
+ }
+ };
return (
@@ -97,7 +107,7 @@ const AmbasssadorDao = () => {
setOpenAuthModal(true)}
+ onClick={handleBecomeClient}
/>
)}
diff --git a/app/(home)/ambassador-dao/profile/layout.tsx b/app/(home)/ambassador-dao/profile/layout.tsx
index 6b61bd6c336..39d43ac5bdc 100644
--- a/app/(home)/ambassador-dao/profile/layout.tsx
+++ b/app/(home)/ambassador-dao/profile/layout.tsx
@@ -14,7 +14,21 @@ const AmbasssadorDaoProfileLayout = ({
useEffect(() => {
if (!isLoading && !user) {
- router.push("/ambassador-dao");
+ // Check if the error is due to token acquisition failure
+ const tokenError = typeof window !== "undefined"
+ ? localStorage.getItem("t1_token_error")
+ : null;
+
+ if (tokenError === "user_not_found") {
+ // User doesn't exist in Ambassador DAO - redirect to onboarding
+ router.push("/ambassador-dao/onboard");
+ } else if (tokenError === "server_error") {
+ // Server error - redirect to home to avoid loop
+ router.push("/");
+ } else {
+ // Normal case: user not authenticated
+ router.push("/ambassador-dao");
+ }
}
}, [user, isLoading, router]);
diff --git a/app/(home)/ambassador-dao/sponsor/(sponsor)/layout.tsx b/app/(home)/ambassador-dao/sponsor/(sponsor)/layout.tsx
index 038cae89a74..d1267518478 100644
--- a/app/(home)/ambassador-dao/sponsor/(sponsor)/layout.tsx
+++ b/app/(home)/ambassador-dao/sponsor/(sponsor)/layout.tsx
@@ -19,14 +19,27 @@ const AmbasssadorDaoSponsorsLayout = ({
const { data: user, isLoading } = useFetchUserDataQuery();
const [openCreateListingModal, setOpenCreateListingModal] = useState(false);
+
useEffect(() => {
if (!isLoading && !user) {
- router.push("/ambassador-dao");
+ // Check if the error is due to token acquisition failure
+ const tokenError = typeof window !== "undefined"
+ ? localStorage.getItem("t1_token_error")
+ : null;
+
+ if (tokenError === "user_not_found") {
+ // User doesn't exist in Ambassador DAO - redirect to onboarding
+ router.push("/ambassador-dao/onboard");
+ } else if (tokenError === "server_error") {
+ // Server error - redirect to home to avoid loop
+ router.push("/");
+ } else {
+ // Normal case: user not authenticated
+ router.push("/ambassador-dao");
+ }
} else if (user && user.role !== "SPONSOR") {
toast.error("You dont have permission to access this page.");
router.push("/ambassador-dao");
- } else {
- // do nothing
}
}, [user, isLoading, router]);
diff --git a/app/(home)/ambassador-dao/sponsor/(sponsor)/listings/[id]/[application]/page.tsx b/app/(home)/ambassador-dao/sponsor/(sponsor)/listings/[id]/[application]/page.tsx
index 3d89f694aae..d8df3363cbc 100644
--- a/app/(home)/ambassador-dao/sponsor/(sponsor)/listings/[id]/[application]/page.tsx
+++ b/app/(home)/ambassador-dao/sponsor/(sponsor)/listings/[id]/[application]/page.tsx
@@ -242,7 +242,7 @@ const AmbasssadorDaoSingleApplicationPage = () => {
diff --git a/app/(home)/layout.tsx b/app/(home)/layout.tsx
index b3d4dcb08a2..1ff21fe40c4 100644
--- a/app/(home)/layout.tsx
+++ b/app/(home)/layout.tsx
@@ -5,8 +5,11 @@ import type { ReactNode } from "react";
import { Footer } from "@/components/navigation/footer";
import { baseOptions } from "@/app/layout.config";
import { SessionProvider, useSession } from "next-auth/react";
-import { useEffect, Suspense } from "react";
+import { useEffect, Suspense, useState } from "react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
+import axios from "axios";
+import Modal from "@/components/ui/Modal";
+import { Terms } from "@/components/login/terms";
export default function Layout({
children,
@@ -26,26 +29,108 @@ export default function Layout({
);
}
+// Helper function to check if a cookie exists
+function getCookie(name: string): string | null {
+ if (typeof document === "undefined") return null;
+
+ const value = `; ${document.cookie}`;
+ const parts = value.split(`; ${name}=`);
+
+ if (parts.length === 2) {
+ return parts.pop()?.split(";").shift() || null;
+ }
+
+ return null;
+}
+
function RedirectIfNewUser() {
const { data: session, status } = useSession();
const pathname = usePathname();
const router = useRouter();
const searchParams = useSearchParams();
+ const [authError, setAuthError] = useState(null);
+ const [showModal, setShowModal] = useState(false);
+
+
+ useEffect(() => {
+ const fetchExternalToken = async () => {
+ if (status !== "authenticated" || !session?.user?.email) return;
+
+ // Check if the external token cookie already exists
+ const externalToken = getCookie("access_token");
+
+ if (!externalToken) {
+
+ try {
+ await axios.post(
+ "/api/t1-token",
+ {},
+ {
+ withCredentials: true,
+ }
+ );
+
+ if (typeof window !== "undefined") {
+ localStorage.removeItem("t1_token_error");
+ }
+ } catch (error: any) {
+ if (error.response?.status === 404) {
+ setAuthError("User not found in Ambassador DAO");
+ if (typeof window !== "undefined") {
+ localStorage.setItem("t1_token_error", "user_not_found");
+ }
+ } else {
+ setAuthError("Failed to authenticate with Ambassador DAO");
+ if (typeof window !== "undefined") {
+ localStorage.setItem("t1_token_error", "server_error");
+ }
+ }
+ }
+ } else {
+ if (typeof window !== "undefined") {
+ localStorage.removeItem("t1_token_error");
+ }
+ }
+ };
+
+ fetchExternalToken();
+ }, [status, session?.user?.email]);
useEffect(() => {
+ const errorLocalStorage = localStorage.getItem("t1_token_error");
if (
status === "authenticated" &&
session.user.is_new_user &&
- pathname !== "/profile"
+ pathname !== "/profile" &&
+ pathname !== "/login" &&
+ pathname !== "/ambassador-dao/onboard" &&
+ errorLocalStorage != ""
) {
- // Store the original URL with search params (including UTM) in localStorage
- const originalUrl = `${pathname}${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
+ const originalUrl = `${pathname}${
+ searchParams.toString() ? `?${searchParams.toString()}` : ""
+ }`;
if (typeof window !== "undefined") {
localStorage.setItem("redirectAfterProfile", originalUrl);
}
- router.replace("/profile");
+ setShowModal(true);
}
}, [session, status, pathname, router, searchParams]);
- return null;
+ const handleContinue = () => {
+ setShowModal(false);
+ };
+
+ return (
+ <>
+ {showModal && (
+ }
+ />
+ )}
+ >
+ );
}
diff --git a/app/api/t1-token/route.ts b/app/api/t1-token/route.ts
new file mode 100644
index 00000000000..35bd282f0ff
--- /dev/null
+++ b/app/api/t1-token/route.ts
@@ -0,0 +1,56 @@
+import { NextRequest, NextResponse } from "next/server";
+import axios from "axios";
+import { getServerSession } from "next-auth";
+import { AuthOptions } from "@/lib/auth/authOptions";
+
+export async function POST(request: NextRequest) {
+ try {
+ const session = await getServerSession(AuthOptions);
+
+ if (!session?.user?.email) {
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
+ }
+
+ const response = await axios.post(
+ `${process.env.NEXT_PUBLIC_API_URL}/auth/backend/authenticate`,
+ {
+ email: session.user.email,
+ },
+ {
+ withCredentials: true,
+ headers: {
+ "Content-Type": "application/json",
+ "x-api-key": process.env.T1_TOKEN_API_KEY as string,
+ },
+ }
+ );
+
+ const nextResponse = NextResponse.json({
+ success: true,
+ message: "External authentication successful",
+ data: response.data,
+ });
+
+ const cookies = response.headers["set-cookie"];
+ if (cookies) {
+ cookies.forEach((cookie) => {
+ nextResponse.headers.append("Set-Cookie", cookie);
+ });
+ }
+
+ return nextResponse;
+ } catch (error: any) {
+ console.error(
+ "External token error:",
+ error.response?.data || error.message
+ );
+
+ return NextResponse.json(
+ {
+ error: "Failed to get external token",
+ details: error.response?.data?.message || error.message,
+ },
+ { status: error.response?.status || 500 }
+ );
+ }
+}
diff --git a/app/layout.config.tsx b/app/layout.config.tsx
index 38e07211ff4..77ad0ebe3c5 100644
--- a/app/layout.config.tsx
+++ b/app/layout.config.tsx
@@ -510,10 +510,6 @@ export const baseOptions: BaseLayoutProps = {
integrationsMenu,
github,
userMenu,
- ambassadorMenu,
- // {
- // type: "custom",
- // children: ,
- // },
+ ambassadorMenu
],
};
diff --git a/components/ambassador-dao/jobs/JobApplicationModal.tsx b/components/ambassador-dao/jobs/JobApplicationModal.tsx
index 7d8764f950e..e474793e7d6 100644
--- a/components/ambassador-dao/jobs/JobApplicationModal.tsx
+++ b/components/ambassador-dao/jobs/JobApplicationModal.tsx
@@ -99,8 +99,8 @@ const JobApplicationModal: React.FC = ({
const results = await Promise.all(uploadPromises);
const newFiles = [...files, ...selectedFiles];
- const newFileIds = [...fileIds, ...results.map((r) => r.file.id)];
+ const newFileIds = [...fileIds, ...results.map((r) => r.file.id)];
setFiles(newFiles);
setFileIds(newFileIds);
diff --git a/components/ambassador-dao/profile/project-section.tsx b/components/ambassador-dao/profile/project-section.tsx
index ceb177c8195..9a05f5b43c7 100644
--- a/components/ambassador-dao/profile/project-section.tsx
+++ b/components/ambassador-dao/profile/project-section.tsx
@@ -314,7 +314,7 @@ export default function ProjectSection() {
(
userProjects?.data?.opportunities ||
userProjects?.data?.submissions
- ).length === 0 && (
+ )?.length === 0 && (
No projects found matching your filters
diff --git a/components/login/FormLogin.tsx b/components/login/FormLogin.tsx
index dbe12a63e13..e102d550a57 100644
--- a/components/login/FormLogin.tsx
+++ b/components/login/FormLogin.tsx
@@ -45,7 +45,8 @@ function Formlogin({ callbackUrl = "/" }: { callbackUrl?: string }) {
});
setIsVerifying(true);
} catch (error) {
- formMethods.setError("email", { message: "Error sending OTP" });
+ console.error("Error sending OTP", error);
+ setIsVerifying(true);
}
setIsLoading(false);
diff --git a/components/login/sign-out/SignOut.tsx b/components/login/sign-out/SignOut.tsx
index 4785b81d80b..655681ab0e8 100644
--- a/components/login/sign-out/SignOut.tsx
+++ b/components/login/sign-out/SignOut.tsx
@@ -2,6 +2,7 @@ import { Button } from "@/components/ui/button";
import { Card, CardContent, CardFooter } from "@/components/ui/card";
import { LoadingButton } from "@/components/ui/loading-button";
import Modal from "@/components/ui/Modal";
+import axios from "axios";
import { BadgeAlert } from "lucide-react";
import React, { useState } from "react";
@@ -24,6 +25,8 @@ export default function SignOutComponent({
onConfirm(),
new Promise((resolve) => setTimeout(resolve, 300)),
]);
+ const url = `${process.env.NEXT_PUBLIC_API_URL}/auth/logout`;
+ await axios.post(url);
onOpenChange(false);
} finally {
setIsConfirm(false);
diff --git a/components/login/terms.tsx b/components/login/terms.tsx
new file mode 100644
index 00000000000..ca3688350aa
--- /dev/null
+++ b/components/login/terms.tsx
@@ -0,0 +1,212 @@
+"use client"
+
+import React from "react";
+import { Card, CardContent, CardHeader, CardFooter } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Checkbox } from "@/components/ui/checkbox";
+import { Check, Loader2 } from "lucide-react";
+import { useRouter } from "next/navigation";
+import { useToast } from "@/hooks/use-toast";
+import { useSession } from "next-auth/react";
+import Link from "next/link";
+import axios from "axios";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import * as z from "zod";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormDescription,
+ FormMessage,
+} from "@/components/ui/form";
+
+// Form schema with validation
+const termsFormSchema = z.object({
+ accepted_terms: z.boolean().refine((val) => val === true, {
+ message: "You must accept the Terms and Conditions to continue.",
+ }),
+ notifications: z.boolean().default(false),
+});
+
+type TermsFormValues = z.infer
;
+
+interface TermsProps {
+ userId: string;
+ onSuccess?: () => void;
+ onDecline?: () => void;
+}
+
+export const Terms = ({
+ userId,
+ onSuccess,
+ onDecline
+}: TermsProps) => {
+ const router = useRouter();
+ const { toast } = useToast();
+ const { update } = useSession();
+
+ const form = useForm({
+ resolver: zodResolver(termsFormSchema),
+ defaultValues: {
+ accepted_terms: false,
+ notifications: false,
+ },
+ });
+
+ const onSubmit = async (data: TermsFormValues) => {
+ try {
+ // Save to API
+ await axios.put(`/api/profile/${userId}`, data);
+
+ // Update session
+ await update();
+
+
+ // Execute success callback if provided
+ onSuccess?.();
+
+ // Navigate to home or redirect URL
+ const redirectUrl = typeof window !== "undefined"
+ ? localStorage.getItem("redirectAfterProfile")
+ : null;
+
+ if (redirectUrl) {
+ localStorage.removeItem("redirectAfterProfile");
+ router.push(redirectUrl);
+ } else {
+ router.push("/");
+ }
+ } catch (error) {
+ console.error("Error saving profile:", error);
+ toast({
+ title: "Error",
+ description: "An error occurred while saving the profile.",
+ variant: "destructive",
+ });
+ }
+ };
+
+ return (
+
+
+ );
+};
\ No newline at end of file
diff --git a/components/login/user-button/UserButton.tsx b/components/login/user-button/UserButton.tsx
index e66decb01dc..b88dac93835 100644
--- a/components/login/user-button/UserButton.tsx
+++ b/components/login/user-button/UserButton.tsx
@@ -12,12 +12,15 @@ import Image from 'next/image';
import Link from 'next/link';
import SignOutComponent from '../sign-out/SignOut';
import { useState } from 'react';
-import { CircleUserRound, UserRound } from 'lucide-react';
+import { CircleUserRound, UserRound, UserCheck2, User2Icon, ListIcon, LogOut } from 'lucide-react';
import { Separator } from '@radix-ui/react-dropdown-menu';
+import { useRouter } from 'next/navigation';
+import { cookies } from 'next/headers';
export function UserButton() {
const { data: session, status } = useSession() ?? {};
const [isDialogOpen, setIsDialogOpen] = useState(false);
const isAuthenticated = status === 'authenticated';
+ const router = useRouter();
const handleSignOut = (): void => {
// Clean up any stored redirect URLs before logout
if (typeof window !== "undefined") {
@@ -33,7 +36,6 @@ export function UserButton() {
signOut();
};
- console.debug('session', session, isAuthenticated);
return (
<>
{isAuthenticated ? (
@@ -53,10 +55,7 @@ export function UserButton() {
className='rounded-full'
/>
) : (
-
+
)}
@@ -76,13 +75,45 @@ export function UserButton() {
-
- Profile
-
+ {/* Onboard option for new users */}
+ {session.user.is_new_user && (
+ <>
+ router.push("/ambassador-dao/onboard")}
+ className='cursor-pointer flex items-center gap-2'
+ >
+
+ Onboard
+
+
+ >
+ )}
+
+ {/* Role-based navigation */}
+ {session.user.role === "SPONSOR" ? (
+ router.push("/ambassador-dao/sponsor")}
+ className='cursor-pointer flex items-center gap-2'
+ >
+
+ Listings
+
+ ) : (
+
+
+
+ Profile
+
+
+ )}
+
+
+
setIsDialogOpen(true)}
- className='cursor-pointer'
+ className='cursor-pointer flex items-center gap-2 text-red-500'
>
+
Sign Out
diff --git a/components/login/verify/VerifyEmail.tsx b/components/login/verify/VerifyEmail.tsx
index 6e940507be2..d0ee24f274b 100644
--- a/components/login/verify/VerifyEmail.tsx
+++ b/components/login/verify/VerifyEmail.tsx
@@ -110,7 +110,10 @@ export function VerifyEmail({
setExpired(false);
setSentTries(0);
} catch (error) {
- setMessage("Error sending OTP. Please try again.");
+ setResendCooldown(60);
+ setExpired(false);
+ setSentTries(0);
+ console.error("Error sending OTP. Please try again.", error);
} finally {
setIsResending(false);
}
diff --git a/hooks/useTalentProfile.ts b/hooks/useTalentProfile.ts
new file mode 100644
index 00000000000..3d44e78766b
--- /dev/null
+++ b/hooks/useTalentProfile.ts
@@ -0,0 +1,159 @@
+import { useState, useEffect } from "react";
+import { useRouter } from "next/navigation";
+import { useSession } from "next-auth/react";
+import axios from "axios";
+import toast from "react-hot-toast";
+import { useFileUploadMutation } from "@/services/ambassador-dao/requests/onboard";
+import { UseFormSetValue, UseFormWatch, UseFormGetValues } from "react-hook-form";
+import { IUpdateTalentProfileBody } from "@/services/ambassador-dao/interfaces/onbaord";
+
+interface UseTalentProfileProps {
+ setValue: UseFormSetValue;
+ watch: UseFormWatch;
+ getValues: UseFormGetValues;
+ setIsDataFetched: (value: boolean) => void;
+}
+
+export const useTalentProfile = ({
+ setValue,
+ watch,
+ getValues,
+ setIsDataFetched,
+}: UseTalentProfileProps) => {
+ const router = useRouter();
+ const { data: session } = useSession();
+ const [localProfileData, setLocalProfileData] = useState(null);
+ const [previewImage, setPreviewImage] = useState(null);
+ const [isUploadingProfileImage, setIsUploadingProfileImage] = useState(false);
+ const [profileImageName, setProfileImageName] = useState("");
+ const [profileImageSize, setProfileImageSize] = useState();
+
+ const { mutateAsync: uploadFile, isPending: isUploading } = useFileUploadMutation("image");
+
+ // Fetch local profile data to get bio, image, etc.
+ useEffect(() => {
+ const fetchLocalProfile = async () => {
+ if (session?.user?.id) {
+ try {
+ const response = await axios.get(`/api/profile/${session.user.id}`);
+ setLocalProfileData(response.data);
+ } catch (error) {
+ console.error("Error fetching local profile:", error);
+ }
+ }
+ };
+
+ fetchLocalProfile();
+ }, [session?.user?.id]);
+
+ // Handle profile image upload
+ const handleProfileImageUpload = async (file: File) => {
+ const allowedTypes = ["image/jpeg", "image/png", "image/svg+xml"];
+ if (!allowedTypes.includes(file.type)) {
+ toast.error("Only JPG, PNG, and SVG images are allowed");
+ return;
+ }
+
+ if (file.size > 1024 * 1024) {
+ toast.error("File size exceeds 1MB limit");
+ return;
+ } else {
+ setProfileImageSize(file.size);
+ }
+
+ try {
+ setIsUploadingProfileImage(true);
+ setProfileImageName(file.name);
+
+ const reader = new FileReader();
+ reader.onloadend = () => {
+ setPreviewImage(reader.result as string);
+ };
+ setIsUploadingProfileImage(false);
+ reader.readAsDataURL(file);
+
+ const url = await uploadFile(file);
+ setValue("profile_image", url.url);
+ } catch (error) {
+ console.error("Error uploading image:", error);
+ toast.error("Failed to upload image");
+ }
+ };
+
+ // Remove profile image
+ const removeFile = () => {
+ setValue("profile_image", "");
+ setPreviewImage("");
+ };
+
+ // Save to local User table
+ const saveToLocalProfile = async (formData: any, socialLinks?: string[]) => {
+ if (session?.user?.id && session?.user?.email) {
+ try {
+ await axios.put(`/api/profile/${session.user.id}`, {
+ name: `${formData.first_name} ${formData.last_name}`.trim(),
+ bio: formData.bio || "",
+ email: session.user.email,
+ notification_email: session.user.email,
+ image: formData.profile_image || "",
+ social_media: socialLinks || [],
+ notifications: formData.notifications,
+ profile_privacy: formData.profile_privacy,
+ telegram_user: formData.telegram_user || "",
+ });
+ console.log("✅ Local profile updated successfully");
+ } catch (error) {
+ console.error("❌ Error updating local profile:", error);
+ throw error;
+ }
+ }
+ };
+
+ // Skip function - Only saves to local User table, NOT to Ambassador API
+ const onSkip = async () => {
+ // Save the current form data before skipping (no validation required)
+ setIsDataFetched(false); // Show loading
+ try {
+ const formData = getValues();
+
+ // Only save to local User table - save whatever data is available
+ await saveToLocalProfile(formData);
+
+ toast.success("Profile saved successfully!");
+
+ // Check for stored redirect URL and navigate there, otherwise go to ambassador-dao
+ const redirectUrl = typeof window !== "undefined"
+ ? localStorage.getItem("redirectAfterProfile")
+ : null;
+
+ if (redirectUrl) {
+ localStorage.removeItem("redirectAfterProfile");
+ router.push(redirectUrl);
+ } else {
+ router.push("/ambassador-dao");
+ }
+ } catch (error) {
+ console.error(error);
+ toast.error("Error while saving profile.");
+ } finally {
+ setIsDataFetched(true);
+ }
+ };
+
+ return {
+ // States
+ localProfileData,
+ previewImage,
+ isUploadingProfileImage,
+ profileImageName,
+ profileImageSize,
+ isUploading,
+
+ // Functions
+ handleProfileImageUpload,
+ removeFile,
+ saveToLocalProfile,
+ onSkip,
+ };
+};
+
diff --git a/lib/auth/authOptions.ts b/lib/auth/authOptions.ts
index 3b0789eae85..d5409584ccf 100644
--- a/lib/auth/authOptions.ts
+++ b/lib/auth/authOptions.ts
@@ -100,13 +100,8 @@ export const AuthOptions: NextAuthOptions = {
let user = await prisma.user.findUnique({ where: { email } });
if (!user) {
- // user = await prisma.user.create({
- // data: {
- // email, notification_email: email, name: '', image: '', last_login: null
- // },
- // }
user = {
- email, notification_email: email, name: '', image: '', last_login: new Date(), authentication_mode: '', bio: '',
+ email, notification_email: email, name: '', image: '', last_login: new Date(), authentication_mode: '', bio: '',accepted_terms: false,
custom_attributes: [], id: '', integration: '', notifications: null, profile_privacy: null, social_media: [], telegram_user: '', user_name: '', created_at: new Date()
}
}
diff --git a/middleware.ts b/middleware.ts
index 27b166d3b78..282746c243e 100644
--- a/middleware.ts
+++ b/middleware.ts
@@ -33,7 +33,9 @@ export async function middleware(req: NextRequest) {
"/hackathons/registration-form",
"/hackathons/project-submission",
"/showcase",
- "/profile"
+ "/profile",
+ "/ambassador-dao/profile",
+ "/ambassador-dao/onboard"
];
const isProtectedPath = protectedPaths.some(path => pathname.startsWith(path));
@@ -80,5 +82,7 @@ export const config = {
"/showcase/:path*",
"/login/:path*",
"/profile/:path*",
+ "/ambassador-dao/profile/:path*",
+ "/ambassador-dao/onboard/:path*",
],
};
diff --git a/prisma/migrations/20251022011449_add_accepted_terms/migration.sql b/prisma/migrations/20251022011449_add_accepted_terms/migration.sql
new file mode 100644
index 00000000000..d5cfd8edf5f
--- /dev/null
+++ b/prisma/migrations/20251022011449_add_accepted_terms/migration.sql
@@ -0,0 +1,37 @@
+-- AlterTable
+ALTER TABLE "User" ADD COLUMN "accepted_terms" BOOLEAN NOT NULL DEFAULT false;
+
+-- CreateTable
+CREATE TABLE "NodeRegistration" (
+ "id" TEXT NOT NULL,
+ "user_id" TEXT NOT NULL,
+ "subnet_id" TEXT NOT NULL,
+ "blockchain_id" TEXT NOT NULL,
+ "node_id" TEXT NOT NULL,
+ "node_index" INTEGER,
+ "public_key" TEXT NOT NULL,
+ "proof_of_possession" TEXT NOT NULL,
+ "rpc_url" TEXT NOT NULL,
+ "chain_name" TEXT,
+ "status" TEXT NOT NULL DEFAULT 'active',
+ "created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "expires_at" TIMESTAMPTZ(3) NOT NULL,
+ "updated_at" TIMESTAMPTZ(3) NOT NULL,
+
+ CONSTRAINT "NodeRegistration_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateIndex
+CREATE INDEX "NodeRegistration_user_id_idx" ON "NodeRegistration"("user_id");
+
+-- CreateIndex
+CREATE INDEX "NodeRegistration_status_idx" ON "NodeRegistration"("status");
+
+-- CreateIndex
+CREATE INDEX "NodeRegistration_subnet_id_idx" ON "NodeRegistration"("subnet_id");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "NodeRegistration_user_id_subnet_id_node_index_key" ON "NodeRegistration"("user_id", "subnet_id", "node_index");
+
+-- AddForeignKey
+ALTER TABLE "NodeRegistration" ADD CONSTRAINT "NodeRegistration_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 6dfe9633e82..6aa31386b0f 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -45,6 +45,7 @@ model User {
profile_privacy String? @default("public")
social_media String[]
notifications Boolean?
+ accepted_terms Boolean @default(false)
custom_attributes String[]
telegram_user String?
created_at DateTime @default(now()) @db.Timestamptz(3)
diff --git a/server/services/auth.ts b/server/services/auth.ts
index 3487bc4be9a..d213b683c61 100644
--- a/server/services/auth.ts
+++ b/server/services/auth.ts
@@ -7,35 +7,41 @@ export async function upsertUser(user: User, account: Account | null, profile: P
throw new Error("El usuario debe tener un email válido");
}
-
- const existingUser = await prisma.user.findUnique({
- where: { email: user.email },
- });
-
-
- const updatedAuthMode = existingUser?.authentication_mode?.includes(account?.provider ?? "")
- ? existingUser.authentication_mode
- : `${existingUser?.authentication_mode ?? ""},${account?.provider}`.replace(/^,/, "");
-
- return await prisma.user.upsert({
- where: { email: user.email },
- update: {
- name: user.name || "",
- image: existingUser?.image || user.image || "",
- authentication_mode: updatedAuthMode,
- last_login: new Date(),
- user_name: (profile as any)?.login ?? "",
- },
- create: {
- email: user.email,
- notification_email: user.email,
- name: user.name || "",
- image: user.image || "",
- authentication_mode: account?.provider ?? "",
- last_login: new Date(),
- user_name: (profile as any)?.login ?? "",
- notifications: null,
- },
+ return await prisma.$transaction(async (tx) => {
+ const existingUser = await tx.user.findUnique({
+ where: { email: user.email ?? undefined },
+ });
+
+ const updatedAuthMode = existingUser?.authentication_mode?.includes(account?.provider ?? "")
+ ? existingUser.authentication_mode
+ : `${existingUser?.authentication_mode ?? ""},${account?.provider}`.replace(/^,/, "");
+
+ if (existingUser) {
+ // Update existing user
+ return await tx.user.update({
+ where: { email: user.email ?? undefined },
+ data: {
+ name: user.name || "",
+ image: existingUser.image || user.image || "",
+ authentication_mode: updatedAuthMode,
+ last_login: new Date(),
+ user_name: (profile as any)?.login ?? "",
+ },
+ });
+ } else {
+ // Create new user
+ return await tx.user.create({
+ data: {
+ email: user.email,
+ notification_email: user.email ?? "",
+ name: user.name || "",
+ image: user.image || "",
+ authentication_mode: account?.provider ?? "",
+ last_login: new Date(),
+ user_name: (profile as any)?.login ?? "",
+ notifications: null,
+ },
+ });
+ }
});
-
}
diff --git a/server/services/login.ts b/server/services/login.ts
index 78b1be57103..5124b1948f3 100644
--- a/server/services/login.ts
+++ b/server/services/login.ts
@@ -24,6 +24,8 @@ export async function sendOTP(email: string) {
name: "Avalanche Builder's Hub"
};
+ console.info("code: ", code);
+
const msg = {
to: email,
from: from,
diff --git a/server/services/profile.ts b/server/services/profile.ts
index ca0db150a37..11219dba8be 100644
--- a/server/services/profile.ts
+++ b/server/services/profile.ts
@@ -57,6 +57,7 @@ export async function updateProfile(id: string, profileData: Partial) {
notifications: data.notifications,
profile_privacy: data.profile_privacy,
social_media: data.social_media,
+ accepted_terms: data.accepted_terms,
}
})
diff --git a/services/ambassador-dao/interfaces/onbaord.ts b/services/ambassador-dao/interfaces/onbaord.ts
index dad9b2c8a58..8ad8d1c06d8 100644
--- a/services/ambassador-dao/interfaces/onbaord.ts
+++ b/services/ambassador-dao/interfaces/onbaord.ts
@@ -24,6 +24,10 @@ export interface IUpdateTalentProfileBody {
social_links: string[];
wallet_address: string;
years_of_experience: string;
+ bio: string;
+ telegram_user: string;
+ profile_privacy: string;
+ notifications: boolean;
}
export interface ISkillsResponse {
diff --git a/toolbox/src/versions.json b/toolbox/src/versions.json
index 034fe3c936f..7cdfa6e5711 100644
--- a/toolbox/src/versions.json
+++ b/toolbox/src/versions.json
@@ -1,5 +1,5 @@
{
"avaplatform/avalanchego": "v1.13.4",
- "avaplatform/subnet-evm_avalanchego": "v0.7.9_v1.13.5",
- "avaplatform/icm-relayer": "v1.6.6"
+ "avaplatform/subnet-evm_avalanchego": "v0.8.1-db-metrics-fix_v1.14.1-db-metrics-fix",
+ "avaplatform/icm-relayer": "v1.7.3"
}
\ No newline at end of file
diff --git a/types/profile.ts b/types/profile.ts
index 04743641dac..afba21fc511 100644
--- a/types/profile.ts
+++ b/types/profile.ts
@@ -7,6 +7,7 @@ export type Profile = {
image: string,
social_media: string[],
notifications: boolean | null,
+ accepted_terms: boolean,
profile_privacy: string,
telegram_user: string | undefined
}