File size: 5,736 Bytes
bf8b26e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import { useStore } from '@nanostores/react';
import { vercelConnection } from '~/lib/stores/vercel';
import { chatId } from '~/lib/persistence/useChatHistory';
import * as Tooltip from '@radix-ui/react-tooltip';
import { useEffect, useState } from 'react';

export function VercelDeploymentLink() {
  const connection = useStore(vercelConnection);
  const currentChatId = useStore(chatId);
  const [deploymentUrl, setDeploymentUrl] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    async function fetchProjectData() {
      if (!connection.token || !currentChatId) {
        return;
      }

      // Check if we have a stored project ID for this chat
      const projectId = localStorage.getItem(`vercel-project-${currentChatId}`);

      if (!projectId) {
        return;
      }

      setIsLoading(true);

      try {
        // Fetch projects directly from the API
        const projectsResponse = await fetch('https://api.vercel.com/v9/projects', {
          headers: {
            Authorization: `Bearer ${connection.token}`,
            'Content-Type': 'application/json',
          },
          cache: 'no-store',
        });

        if (!projectsResponse.ok) {
          throw new Error(`Failed to fetch projects: ${projectsResponse.status}`);
        }

        const projectsData = (await projectsResponse.json()) as any;
        const projects = projectsData.projects || [];

        // Extract the chat number from currentChatId
        const chatNumber = currentChatId.split('-')[0];

        // Find project by matching the chat number in the name
        const project = projects.find((p: { name: string | string[] }) => p.name.includes(`bolt-diy-${chatNumber}`));

        if (project) {
          // Fetch project details including deployments
          const projectDetailsResponse = await fetch(`https://api.vercel.com/v9/projects/${project.id}`, {
            headers: {
              Authorization: `Bearer ${connection.token}`,
              'Content-Type': 'application/json',
            },
            cache: 'no-store',
          });

          if (projectDetailsResponse.ok) {
            const projectDetails = (await projectDetailsResponse.json()) as any;

            // Try to get URL from production aliases first
            if (projectDetails.targets?.production?.alias && projectDetails.targets.production.alias.length > 0) {
              // Find the clean URL (without -projects.vercel.app)
              const cleanUrl = projectDetails.targets.production.alias.find(
                (a: string) => a.endsWith('.vercel.app') && !a.includes('-projects.vercel.app'),
              );

              if (cleanUrl) {
                setDeploymentUrl(`https://${cleanUrl}`);
                return;
              } else {
                // If no clean URL found, use the first alias
                setDeploymentUrl(`https://${projectDetails.targets.production.alias[0]}`);
                return;
              }
            }
          }

          // If no aliases or project details failed, try fetching deployments
          const deploymentsResponse = await fetch(
            `https://api.vercel.com/v6/deployments?projectId=${project.id}&limit=1`,
            {
              headers: {
                Authorization: `Bearer ${connection.token}`,
                'Content-Type': 'application/json',
              },
              cache: 'no-store',
            },
          );

          if (deploymentsResponse.ok) {
            const deploymentsData = (await deploymentsResponse.json()) as any;

            if (deploymentsData.deployments && deploymentsData.deployments.length > 0) {
              setDeploymentUrl(`https://${deploymentsData.deployments[0].url}`);
              return;
            }
          }
        }

        // Fallback to API call if not found in fetched projects
        const fallbackResponse = await fetch(`/api/vercel-deploy?projectId=${projectId}&token=${connection.token}`, {
          method: 'GET',
        });

        const data = await fallbackResponse.json();

        if ((data as { deploy?: { url?: string } }).deploy?.url) {
          setDeploymentUrl((data as { deploy: { url: string } }).deploy.url);
        } else if ((data as { project?: { url?: string } }).project?.url) {
          setDeploymentUrl((data as { project: { url: string } }).project.url);
        }
      } catch (err) {
        console.error('Error fetching Vercel deployment:', err);
      } finally {
        setIsLoading(false);
      }
    }

    fetchProjectData();
  }, [connection.token, currentChatId]);

  if (!deploymentUrl) {
    return null;
  }

  return (
    <Tooltip.Provider>
      <Tooltip.Root>
        <Tooltip.Trigger asChild>
          <a
            href={deploymentUrl}
            target="_blank"
            rel="noopener noreferrer"
            className="inline-flex items-center justify-center w-8 h-8 rounded hover:bg-bolt-elements-item-backgroundActive text-bolt-elements-textSecondary hover:text-[#000000] z-50"
            onClick={(e) => {
              e.stopPropagation();
            }}
          >
            <div className={`i-ph:link w-4 h-4 hover:text-blue-400 ${isLoading ? 'animate-pulse' : ''}`} />
          </a>
        </Tooltip.Trigger>
        <Tooltip.Portal>
          <Tooltip.Content
            className="px-3 py-2 rounded bg-bolt-elements-background-depth-3 text-bolt-elements-textPrimary text-xs z-50"
            sideOffset={5}
          >
            {deploymentUrl}
            <Tooltip.Arrow className="fill-bolt-elements-background-depth-3" />
          </Tooltip.Content>
        </Tooltip.Portal>
      </Tooltip.Root>
    </Tooltip.Provider>
  );
}