LiveDisplayX / src / app / onboarding / page.tsx
page.tsx
Raw
"use client";
import { useState, useEffect } from "react";
import { useOrganizationList, useUser, useAuth } from "@clerk/nextjs";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { useRouter } from "next/navigation";
import { Sparkles, ArrowRight, ArrowLeft, Check, Mail } from "lucide-react";
import Image from "next/image";
import { completeOnboarding } from './_actions'

export default function Onboarding() {
  const { 
    userMemberships, 
    userInvitations,
    createOrganization, 
    setActive 
  } = useOrganizationList({
    userMemberships: {
      infinite: true,
      keepPreviousData: true,
    },
    userInvitations: {
      infinite: true,
      keepPreviousData: true,
    }
  });
  
  const { user, isLoaded: userLoaded } = useUser();
  const { isSignedIn } = useAuth();
  const router = useRouter();

  const [step, setStep] = useState(1);
  const [orgName, setOrgName] = useState("");
  const [appName, setAppName] = useState("");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState("");
  const [shouldRedirect, setShouldRedirect] = useState(false);
  const [acceptingInvitationId, setAcceptingInvitationId] = useState<string | null>(null);

  useEffect(() => {
    if (!isSignedIn) {
      router.push("/");
    }
    
    if (shouldRedirect) {
      const isAdmin = user?.organizationMemberships.some(
        m => m.organization.id === userMemberships?.data?.[0]?.organization.id && 
        m.role === "org:admin"
      );
      user?.reload()

      router.push("/admin");
    }
  }, [isSignedIn, shouldRedirect, router]);

  const handleCreateOrganization = async () => {
    if (!orgName.trim()) {
      setError("Organization name is required");
      return;
    }
    
    setLoading(true);
    setError("");
    
    try {
      if (createOrganization) {
        const newOrg = await createOrganization({ 
          name: orgName,
          // Optionally set the creating user as admin
        });
        
        // Set the new organization as active
        await setActive?.({ organization: newOrg.id });
        
        // Configure the organization (add logo, etc)
        await configureNewOrganization(newOrg.id);
        
        setShouldRedirect(true);
      }
    } catch (err) {
      console.error("Failed to create organization", err);
      setError("Failed to create organization. Please try again.");
    } finally {
      setLoading(false);
    }
  };

  const configureNewOrganization = async (orgId: string) => {
    try {
      // Here you would make API calls to configure the new organization
      // For example, upload a default logo, set up initial settings, etc.
      // await fetch('/api/configure-org', { method: 'POST', body: JSON.stringify({ orgId }) });
    } catch (err) {
      console.error("Failed to configure organization", err);
    }
  };

  const handleJoinOrganization = async (orgId: string) => {
    setLoading(true);
    try {
      if (setActive) {
        await setActive({ organization: orgId });
        const res = await completeOnboarding();
        if (res?.success) {
            user?.reload()
        }

        setShouldRedirect(true);
      }
    } catch (err) {
      console.error("Failed to join organization", err);
      setError("Failed to join organization. Please try again.");
    } finally {
      setLoading(false);
    }
  };

  const configureUserAfterJoining = async (orgId: string) => {
    try {
      // Here you would make API calls to configure the user in the organization
      // For example, set default permissions, add to groups, etc.
      // await fetch('/api/configure-user', { method: 'POST', body: JSON.stringify({ orgId, userId: user?.id }) });
    } catch (err) {
      console.error("Failed to configure user", err);
    }
  };

  const handleAcceptInvitation = async (invitationId: string) => {
    setAcceptingInvitationId(invitationId);
    try {
      const invitation = userInvitations.data?.find(inv => inv.id === invitationId);
      if (invitation) {
        await invitation.accept();
        
        const res = await completeOnboarding();
        if (res?.success) {
            // Reloads the user's data from the Clerk API
            await user?.reload()
            userInvitations.revalidate?.();
            userMemberships.revalidate?.();
            
            router.push("/admin" );
    
        }
      

      }
    } catch (err) {
      console.error("Failed to accept invitation", err);
      setError("Failed to accept invitation. Please try again.");
    } finally {
      setAcceptingInvitationId(null);
    }
  };

  if (!isSignedIn || !userLoaded) {
    return (
      <div className="flex justify-center items-center min-h-screen">
        <p>Redirecting...</p>
      </div>
    );
  }

  const hasMemberships = userMemberships?.data && userMemberships.data.length > 0;
  const hasInvitations = userInvitations?.data && userInvitations.data.length > 0;

  return (
    <div className="flex justify-center items-center min-h-screen bg-gradient-to-b from-violet-50 via-white to-sky-50">
      <div className="absolute inset-0 overflow-hidden">
        <div className="absolute -top-40 -right-40 w-80 h-80 bg-purple-200 rounded-full blur-3xl opacity-20" />
        <div className="absolute top-20 -left-40 w-80 h-80 bg-blue-200 rounded-full blur-3xl opacity-20" />
        <div className="absolute bottom-0 right-0 w-80 h-80 bg-pink-200 rounded-full blur-3xl opacity-20" />
      </div>

      <Card className="w-full max-w-md border-0 shadow-xl bg-white/80 backdrop-blur-sm">
        <CardHeader>
          <div className="flex items-center gap-2">
            <Sparkles className="h-5 w-5 text-purple-500" />
            <CardTitle className="text-2xl font-bold text-gray-800">
              {step === 1 && "Get Started"}
              {step === 2 && "Create Organization"}
              {step === 3 && "Join Organization"}
            </CardTitle>
          </div>
        </CardHeader>
        <CardContent className="space-y-6">
          {error && (
            <div className="text-red-500 text-sm p-3 bg-red-50 rounded-lg">
              {error}
            </div>
          )}

          {step === 1 && (
            <div className="space-y-4">
              <p className="text-gray-600">
                Welcome, {user?.firstName}! Let's get you set up with an organization.
              </p>
              <Button 
                className="w-full rounded-full text-white h-12 bg-purple-600 hover:bg-purple-700"
                onClick={() => setStep(2)}
              >
                Create a new organization <ArrowRight className="ml-2 h-4 w-4" />
              </Button>
              <Button 
                className="w-full rounded-full h-12"
                variant="outline"
                onClick={() => setStep(3)}
              >
                Join an existing organization <ArrowRight className="ml-2 h-4 w-4" />
              </Button>
            </div>
          )}

          {step === 2 && (
            <div className="space-y-4">
              <div>
                <label className="block text-sm font-medium text-gray-700 mb-1">
                  Organization Name
                </label>
                <Input
                  placeholder="My Awesome Org"
                  value={orgName}
                  onChange={(e) => setOrgName(e.target.value)}
                  className="h-12 rounded-xl"
                />
              </div>
              
              <div>
                <label className="block text-sm font-medium text-gray-700 mb-1">
                  Application Name (optional)
                </label>
                <Input
                  placeholder="My Display App"
                  value={appName}
                  onChange={(e) => setAppName(e.target.value)}
                  className="h-12 rounded-xl"
                />
              </div>
              
              <div className="flex gap-3 pt-2">
                <Button 
                  className="rounded-full h-12 flex-1 bg-purple-600 hover:bg-purple-700"
                  onClick={handleCreateOrganization}
                  disabled={loading}
                >
                  {loading ? "Creating..." : "Create Organization"}
                </Button>
                <Button 
                  className="rounded-full h-12"
                  variant="outline" 
                  onClick={() => setStep(1)}
                  disabled={loading}
                >
                  <ArrowLeft className="h-4 w-4" />
                </Button>
              </div>
            </div>
          )}

          {step === 3 && (
            <div className="space-y-4">
              {hasInvitations && (
                <div className="space-y-3">
                  <h3 className="text-sm font-medium text-gray-700">Pending Invitations</h3>
                  {userInvitations.data?.map((invitation) => (
                    <div 
                      key={invitation.id} 
                      className="flex items-center justify-between p-3 border rounded-lg hover:bg-gray-50 transition-colors"
                    >
                      <div className="flex items-center gap-3">
                        {invitation.publicOrganizationData?.imageUrl ? (
                          <div className="relative h-10 w-10 rounded-md overflow-hidden">
                            <Image
                              src={invitation.publicOrganizationData.imageUrl}
                              alt={invitation.publicOrganizationData.name || 'Organization logo'}
                              fill
                              className="object-cover"
                            />
                          </div>
                        ) : (
                          <div className="h-10 w-10 rounded-md bg-purple-100 flex items-center justify-center">
                            <Mail className="h-5 w-5 text-purple-500" />
                          </div>
                        )}
                        <div>
                          <p className="font-medium">{invitation.publicOrganizationData?.name || 'Unknown Organization'}</p>
                          <p className="text-sm text-gray-500">
                            {invitation.emailAddress} - {invitation.status}
                          </p>
                        </div>
                      </div>
                      <Button 
                        size="sm" 
                        onClick={() => invitation.id && handleAcceptInvitation(invitation.id)}
                        disabled={loading || acceptingInvitationId === invitation.id}
                        className="hover:bg-purple-700 hover:text-white transition-colors"
                      >
                        {acceptingInvitationId === invitation.id ? "Accepting..." : "Accept"}
                      </Button>
                    </div>
                  ))}
                </div>
              )}

              {hasMemberships ? (
                <div className="space-y-3">
                  <h3 className="text-sm font-medium text-gray-700">Your Organizations</h3>
                  {userMemberships.data?.map((membership) => (
                    <Button
                      key={membership.id}
                      className="w-full justify-between h-14 rounded-xl hover:bg-gray-50"
                      variant="outline"
                      onClick={() => handleJoinOrganization(membership.organization.id)}
                      disabled={loading}
                    >
                      <div className="flex items-center gap-3">
                        {membership.organization.imageUrl ? (
                          <div className="relative h-8 w-8 rounded-md overflow-hidden">
                            <Image
                              src={membership.organization.imageUrl}
                              alt={membership.organization.name}
                              fill
                              className="object-cover"
                            />
                          </div>
                        ) : (
                          <div className="h-8 w-8 rounded-md bg-purple-100 flex items-center justify-center">
                            <Mail className="h-4 w-4 text-purple-500" />
                          </div>
                        )}
                        <span>{membership.organization.name}</span>
                      </div>
                      {user?.organizationMemberships?.[0]?.organization.id === membership.organization.id && (
                        <Check className="h-4 w-4 text-green-500" />
                      )}
                    </Button>
                  ))}
                </div>
              ) : (
                <div className="text-center py-8 text-gray-500">
                  {hasInvitations ? null : (
                    <>
                      <p>No organizations available to join.</p>
                      <p className="mt-2">Ask an admin to invite you to their organization.</p>
                    </>
                  )}
                </div>
              )}
              
              <Button 
                className="w-full rounded-full h-12 mt-4 hover:bg-gray-50"
                variant="outline"
                onClick={() => setStep(1)}
                disabled={loading}
              >
                <ArrowLeft className="h-4 w-4 mr-2" /> Back
              </Button>
            </div>
          )}
        </CardContent>
      </Card>
    </div>
  );
}