@fils/sanity-components 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,14 +4,14 @@
4
4
  * Recommended to use inside the dashboard
5
5
  * Uses studio secrets to store Github token
6
6
  */
7
- interface DeployButtonConfig {
7
+ export interface DeployButtonConfig {
8
8
  owner: string;
9
9
  repo: string;
10
10
  token?: string;
11
11
  branch?: string;
12
12
  eventType?: string;
13
13
  }
14
- interface DeployButtonProps {
14
+ export interface DeployButtonProps {
15
15
  config: DeployButtonConfig;
16
16
  title?: string;
17
17
  deploymentUrl?: string;
@@ -19,5 +19,4 @@ interface DeployButtonProps {
19
19
  secretsNamespace?: string;
20
20
  secretKey?: string;
21
21
  }
22
- declare const DeployButton: ({ config, title, deploymentUrl, environment, secretsNamespace, secretKey }: DeployButtonProps) => import("react/jsx-runtime").JSX.Element;
23
- export default DeployButton;
22
+ export declare const DeployButton: ({ config, title, deploymentUrl, environment, secretsNamespace, secretKey }: DeployButtonProps) => import("react/jsx-runtime").JSX.Element;
@@ -3,7 +3,7 @@ import { useState, useEffect, useRef } from 'react';
3
3
  import { Button, Card, Stack, Text, Spinner, Flex } from '@sanity/ui';
4
4
  import { PlayIcon, CheckmarkIcon, CloseIcon } from '@sanity/icons';
5
5
  import { useSecrets, SettingsView } from '@sanity/studio-secrets';
6
- const DeployButton = ({ config, title = "🚀 Site Deployment", deploymentUrl, environment = "production", secretsNamespace = "deployButton", secretKey = "github_token" }) => {
6
+ export const DeployButton = ({ config, title = "🚀 Site Deployment", deploymentUrl, environment = "production", secretsNamespace = "deployButton", secretKey = "github_token" }) => {
7
7
  // ... rest of code
8
8
  // ALL HOOKS MUST BE CALLED FIRST - NEVER CONDITIONALLY
9
9
  const [deployState, setDeployState] = useState('idle');
@@ -243,4 +243,3 @@ const DeployButton = ({ config, title = "🚀 Site Deployment", deploymentUrl, e
243
243
  lineHeight: '1'
244
244
  }, children: _jsx(buttonProps.icon, {}) })), _jsx(Text, { size: 2, weight: "medium", style: { lineHeight: '1' }, children: buttonProps.text })] }) }) }), message && (_jsx(Card, { padding: 3, radius: 1, tone: deployState === 'error' ? 'critical' : deployState === 'success' ? 'positive' : 'primary', children: _jsx(Text, { size: 1, children: message }) })), deploymentUrl && deployState === 'success' && (_jsx(Button, { as: "a", href: deploymentUrl, target: "_blank", rel: "noopener noreferrer", tone: "primary", mode: "ghost", size: 3, children: "\uD83C\uDF10 View Live Site" })), _jsxs(Text, { size: 1, muted: true, children: ["Deploys from ", config.branch || 'main', " branch to ", environment] })] }) }));
245
245
  };
246
- export default DeployButton;
@@ -10,5 +10,5 @@ interface TokenConfigProps {
10
10
  secretKey?: string;
11
11
  environment?: string;
12
12
  }
13
- declare const GitHubTokenConfig: ({ title, description, secretsNamespace, secretKey, environment }: TokenConfigProps) => import("react/jsx-runtime").JSX.Element;
14
- export default GitHubTokenConfig;
13
+ export declare const GitHubTokenConfig: ({ title, description, secretsNamespace, secretKey, environment }: TokenConfigProps) => import("react/jsx-runtime").JSX.Element;
14
+ export {};
@@ -3,7 +3,7 @@ import { useState } from 'react';
3
3
  import { Card, Stack, Text, Button } from '@sanity/ui';
4
4
  import { useSecrets, SettingsView } from '@sanity/studio-secrets';
5
5
  import { CogIcon } from '@sanity/icons';
6
- const GitHubTokenConfig = ({ title = "🔐 GitHub Token Configuration", description = "Configure your GitHub personal access token for deployment automation.", secretsNamespace = "deployButton", secretKey = "github_token", environment = "production" }) => {
6
+ export const GitHubTokenConfig = ({ title = "🔐 GitHub Token Configuration", description = "Configure your GitHub personal access token for deployment automation.", secretsNamespace = "deployButton", secretKey = "github_token", environment = "production" }) => {
7
7
  const [showSettings, setShowSettings] = useState(false);
8
8
  // Use Sanity secrets to check if token exists
9
9
  const { secrets } = useSecrets(secretsNamespace);
@@ -26,4 +26,3 @@ const GitHubTokenConfig = ({ title = "🔐 GitHub Token Configuration", descript
26
26
  }
27
27
  return (_jsx(Card, { padding: 4, radius: 2, shadow: 1, children: _jsxs(Stack, { space: 3, children: [_jsx(Text, { size: 2, weight: "semibold", children: title }), _jsx(Text, { size: 1, muted: true, children: description }), _jsxs(Stack, { space: 2, children: [_jsxs(Text, { size: 1, children: ["Status: ", hasToken ? '✅ Token configured' : '⚠️ No token configured'] }), _jsx(Button, { tone: hasToken ? 'default' : 'primary', icon: CogIcon, onClick: () => setShowSettings(true), text: hasToken ? 'Update Token' : 'Configure Token' })] })] }) }));
28
28
  };
29
- export default GitHubTokenConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fils/sanity-components",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Fil's Components for Sanity Back-Ends",
5
5
  "repository": "git@github.com:fil-studio/fils.git",
6
6
  "author": "Fil Studio <hello@fil.studio>",
@@ -11,7 +11,7 @@ import { Button, Card, Stack, Text, Spinner, Flex } from '@sanity/ui';
11
11
  import { PlayIcon, CheckmarkIcon, CloseIcon } from '@sanity/icons';
12
12
  import { useSecrets, SettingsView } from '@sanity/studio-secrets';
13
13
 
14
- interface DeployButtonConfig {
14
+ export interface DeployButtonConfig {
15
15
  owner: string;
16
16
  repo: string;
17
17
  token?: string;
@@ -19,7 +19,7 @@ interface DeployButtonConfig {
19
19
  eventType?: string;
20
20
  }
21
21
 
22
- interface DeployButtonProps {
22
+ export interface DeployButtonProps {
23
23
  config: DeployButtonConfig;
24
24
  title?: string;
25
25
  deploymentUrl?: string;
@@ -43,7 +43,7 @@ interface WorkflowRunsResponse {
43
43
  workflow_runs: WorkflowRun[];
44
44
  }
45
45
 
46
- const DeployButton = ({
46
+ export const DeployButton = ({
47
47
  config,
48
48
  title = "🚀 Site Deployment",
49
49
  deploymentUrl,
@@ -442,6 +442,4 @@ const DeployButton = ({
442
442
  </Stack>
443
443
  </Card>
444
444
  );
445
- };
446
-
447
- export default DeployButton;
445
+ };
@@ -18,7 +18,7 @@ interface TokenConfigProps {
18
18
  environment?: string;
19
19
  }
20
20
 
21
- const GitHubTokenConfig = ({
21
+ export const GitHubTokenConfig = ({
22
22
  title = "🔐 GitHub Token Configuration",
23
23
  description = "Configure your GitHub personal access token for deployment automation.",
24
24
  secretsNamespace = "deployButton",
@@ -90,4 +90,4 @@ const GitHubTokenConfig = ({
90
90
  );
91
91
  };
92
92
 
93
- export default GitHubTokenConfig;
93
+ // export default GitHubTokenConfig;
@@ -1,23 +0,0 @@
1
- /**
2
- * Fil's Hosted Deploy Button
3
- * Dependencies: React, @sanity/ui, @sanity/icons, @sanity/studio-secrets
4
- * Recommended to use inside the dashboard
5
- * Uses studio secrets to store Github token
6
- */
7
- interface DeployButtonConfig {
8
- owner: string;
9
- repo: string;
10
- token?: string;
11
- branch?: string;
12
- eventType?: string;
13
- }
14
- interface DeployButtonProps {
15
- config: DeployButtonConfig;
16
- title?: string;
17
- deploymentUrl?: string;
18
- environment?: string;
19
- secretsNamespace?: string;
20
- secretKey?: string;
21
- }
22
- declare const DeployButton: ({ config, title, deploymentUrl, environment, secretsNamespace, secretKey }: DeployButtonProps) => import("react/jsx-runtime").JSX.Element;
23
- export default DeployButton;
@@ -1,225 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useEffect, useRef } from 'react';
3
- import { Button, Card, Stack, Text, Spinner, Flex } from '@sanity/ui';
4
- import { PlayIcon, CheckmarkIcon, CloseIcon } from '@sanity/icons';
5
- import { useSecrets, SettingsView } from '@sanity/studio-secrets';
6
- const DeployButton = ({ config, title = "🚀 Site Deployment", deploymentUrl, environment = "production", secretsNamespace = "deployButton", secretKey = "github_token" }) => {
7
- // ... rest of code
8
- // ALL HOOKS MUST BE CALLED FIRST - NEVER CONDITIONALLY
9
- const [deployState, setDeployState] = useState('idle');
10
- const [message, setMessage] = useState('');
11
- const [showSettings, setShowSettings] = useState(false);
12
- const intervalRef = useRef(null);
13
- const monitoringRef = useRef(false);
14
- // Use Sanity secrets - ALWAYS call this hook
15
- const { secrets } = useSecrets(secretsNamespace);
16
- const token = secrets?.[secretKey];
17
- // Get the effective token (secrets take priority over config)
18
- const effectiveToken = token || config?.token;
19
- // Only show settings if we don't have a token AND we have valid config
20
- useEffect(() => {
21
- if (!effectiveToken && config?.owner && config?.repo && !showSettings) {
22
- setShowSettings(true);
23
- }
24
- else if (effectiveToken && showSettings) {
25
- // Close settings if we now have a token
26
- setShowSettings(false);
27
- }
28
- }, [effectiveToken, config?.owner, config?.repo, showSettings]);
29
- // Clean up on unmount
30
- useEffect(() => {
31
- return () => {
32
- if (intervalRef.current) {
33
- clearInterval(intervalRef.current);
34
- intervalRef.current = null;
35
- }
36
- };
37
- }, []);
38
- // Plugin config for secrets
39
- const pluginConfigKeys = [
40
- {
41
- key: secretKey,
42
- title: `GitHub Token for ${environment}`,
43
- description: `Personal access token for ${config?.owner}/${config?.repo} deployment`,
44
- type: 'string',
45
- inputType: 'password'
46
- }
47
- ];
48
- // Poll workflow status
49
- const pollWorkflowStatus = async (runId) => {
50
- try {
51
- console.log('Polling workflow:', runId);
52
- const response = await fetch(`https://api.github.com/repos/${config.owner}/${config.repo}/actions/runs/${runId}`, {
53
- headers: {
54
- 'Authorization': `token ${effectiveToken}`,
55
- 'Accept': 'application/vnd.github.v3+json'
56
- }
57
- });
58
- if (!response.ok) {
59
- throw new Error(`HTTP ${response.status}`);
60
- }
61
- const run = await response.json();
62
- console.log('Run status:', run.status, 'conclusion:', run.conclusion);
63
- if (run.status === 'completed') {
64
- // Stop polling
65
- if (intervalRef.current) {
66
- clearInterval(intervalRef.current);
67
- intervalRef.current = null;
68
- }
69
- monitoringRef.current = false;
70
- if (run.conclusion === 'success') {
71
- setDeployState('success');
72
- setMessage('🎉 Deployment completed successfully!');
73
- }
74
- else {
75
- setDeployState('error');
76
- setMessage(`❌ Deployment failed: ${run.conclusion}`);
77
- }
78
- }
79
- else if (run.status === 'in_progress' || run.status === 'queued') {
80
- const startTime = new Date(run.run_started_at || run.created_at);
81
- const elapsed = Math.floor((Date.now() - startTime.getTime()) / 1000);
82
- setDeployState('deploying');
83
- setMessage(`🔄 Deployment running... (${elapsed}s)`);
84
- }
85
- }
86
- catch (error) {
87
- console.error('Polling error:', error);
88
- }
89
- };
90
- // Find the workflow run
91
- const findWorkflowRun = async () => {
92
- try {
93
- console.log('Looking for workflow run...');
94
- const response = await fetch(`https://api.github.com/repos/${config.owner}/${config.repo}/actions/runs?per_page=10`, {
95
- headers: {
96
- 'Authorization': `token ${effectiveToken}`,
97
- 'Accept': 'application/vnd.github.v3+json'
98
- }
99
- });
100
- if (!response.ok) {
101
- throw new Error(`HTTP ${response.status}`);
102
- }
103
- const data = await response.json();
104
- console.log('Found runs:', data.workflow_runs.length);
105
- // Find the most recent run (within last 90 seconds)
106
- const recentRun = data.workflow_runs.find(run => {
107
- const ageInSeconds = (Date.now() - new Date(run.created_at).getTime()) / 1000;
108
- console.log(`Run ${run.id}: age ${ageInSeconds}s, event: ${run.event}, status: ${run.status}`);
109
- return ageInSeconds < 90;
110
- });
111
- if (recentRun) {
112
- console.log('Found recent run:', recentRun.id, 'Event:', recentRun.event);
113
- setDeployState('deploying');
114
- setMessage('🚀 Deployment started...');
115
- // Start polling every 5 seconds
116
- intervalRef.current = setInterval(() => {
117
- pollWorkflowStatus(recentRun.id);
118
- }, 5000);
119
- // Initial poll
120
- pollWorkflowStatus(recentRun.id);
121
- }
122
- else {
123
- console.log('No recent workflow found');
124
- setDeployState('error');
125
- setMessage('❌ Could not find workflow run - check if workflow exists');
126
- }
127
- }
128
- catch (error) {
129
- console.error('Error finding workflow:', error);
130
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
131
- setDeployState('error');
132
- setMessage(`❌ Error finding workflow: ${errorMessage}`);
133
- }
134
- };
135
- const triggerDeploy = async () => {
136
- // Prevent multiple deployments
137
- if (deployState !== 'idle' && deployState !== 'success' && deployState !== 'error')
138
- return;
139
- setDeployState('triggering');
140
- setMessage('⚡ Triggering deployment...');
141
- try {
142
- console.log('Triggering deployment...');
143
- const response = await fetch(`https://api.github.com/repos/${config.owner}/${config.repo}/dispatches`, {
144
- method: 'POST',
145
- headers: {
146
- 'Authorization': `token ${effectiveToken}`,
147
- 'Accept': 'application/vnd.github.v3+json',
148
- 'Content-Type': 'application/json'
149
- },
150
- body: JSON.stringify({
151
- event_type: config.eventType || 'deploy-site',
152
- client_payload: {
153
- environment: environment,
154
- triggered_by: 'sanity_studio',
155
- timestamp: new Date().toISOString()
156
- }
157
- })
158
- });
159
- if (response.ok) {
160
- console.log('Deployment triggered successfully');
161
- setMessage('✅ Deployment triggered! Looking for workflow...');
162
- // Wait 5 seconds then look for the workflow
163
- setTimeout(() => {
164
- if (!monitoringRef.current) {
165
- monitoringRef.current = true;
166
- findWorkflowRun();
167
- }
168
- }, 5000);
169
- }
170
- else {
171
- const error = await response.json();
172
- throw new Error(error.message || `HTTP ${response.status}`);
173
- }
174
- }
175
- catch (error) {
176
- console.error('Deploy trigger failed:', error);
177
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
178
- setDeployState('error');
179
- setMessage(`❌ Failed to trigger: ${errorMessage}`);
180
- }
181
- };
182
- const getButtonProps = () => {
183
- switch (deployState) {
184
- case 'triggering':
185
- return { tone: 'primary', disabled: true, icon: Spinner, text: 'Triggering...' };
186
- case 'deploying':
187
- return { tone: 'primary', disabled: true, icon: Spinner, text: 'Deploying...' };
188
- case 'success':
189
- return { tone: 'positive', disabled: false, icon: CheckmarkIcon, text: 'Deploy Again' };
190
- case 'error':
191
- return { tone: 'critical', disabled: false, icon: CloseIcon, text: 'Try Again' };
192
- default:
193
- return { tone: 'primary', disabled: false, icon: PlayIcon, text: 'Deploy Site' };
194
- }
195
- };
196
- // NOW WE CAN HANDLE CONDITIONAL RENDERING AFTER ALL HOOKS
197
- // Check for missing config
198
- if (!config || !config.owner || !config.repo) {
199
- return (_jsx(Card, { padding: 4, radius: 2, shadow: 1, tone: "critical", children: _jsxs(Stack, { space: 3, children: [_jsx(Text, { size: 2, weight: "semibold", children: "\u274C Deploy Button Configuration Error" }), _jsx(Text, { size: 1, children: "Missing required config: owner and repo are required" })] }) }));
200
- }
201
- // If showing settings, render the settings view
202
- if (showSettings && !effectiveToken) {
203
- return (_jsx(Card, { padding: 4, radius: 2, shadow: 1, children: _jsxs(Stack, { space: 3, children: [_jsxs(Text, { size: 2, weight: "semibold", children: ["\uD83D\uDD10 ", title, " - Setup Required"] }), _jsx(Text, { size: 1, muted: true, children: "Please configure your GitHub token to enable deployments." }), _jsx(SettingsView, { title: `${title} Configuration`, namespace: secretsNamespace, keys: pluginConfigKeys, onClose: () => {
204
- setShowSettings(false);
205
- } })] }) }));
206
- }
207
- // Check for missing token after settings
208
- if (!effectiveToken) {
209
- return (_jsx(Card, { padding: 4, radius: 2, shadow: 1, tone: "critical", children: _jsxs(Stack, { space: 3, children: [_jsx(Text, { size: 2, weight: "semibold", children: "\u274C Authentication Required" }), _jsx(Text, { size: 1, children: "No GitHub token configured." }), _jsx(Button, { tone: "primary", mode: "ghost", size: 1, onClick: () => setShowSettings(true), children: "Configure Token" })] }) }));
210
- }
211
- const buttonProps = getButtonProps();
212
- return (_jsx(Card, { padding: 4, radius: 2, shadow: 1, children: _jsxs(Stack, { space: 3, children: [_jsx(Text, { size: 2, weight: "semibold", children: title }), _jsx(Flex, { justify: "flex-start", children: _jsx(Button, { tone: buttonProps.tone, disabled: buttonProps.disabled, onClick: triggerDeploy, size: 3, style: {
213
- width: '100%',
214
- maxWidth: '360px',
215
- }, children: _jsxs(Flex, { align: "center", justify: "center", style: {
216
- gap: '6px',
217
- padding: '16px 0px 16px 0px'
218
- }, children: [buttonProps.icon && (_jsx("span", { style: {
219
- display: 'flex',
220
- alignItems: 'center',
221
- fontSize: '16px',
222
- lineHeight: '1'
223
- }, children: _jsx(buttonProps.icon, {}) })), _jsx(Text, { size: 2, weight: "medium", style: { lineHeight: '1' }, children: buttonProps.text })] }) }) }), message && (_jsx(Card, { padding: 3, radius: 1, tone: deployState === 'error' ? 'critical' : deployState === 'success' ? 'positive' : 'primary', children: _jsx(Text, { size: 1, children: message }) })), deploymentUrl && deployState === 'success' && (_jsx(Button, { as: "a", href: deploymentUrl, target: "_blank", rel: "noopener noreferrer", tone: "primary", mode: "ghost", size: 3, children: "\uD83C\uDF10 View Live Site" })), _jsxs(Text, { size: 1, muted: true, children: ["Deploys from ", config.branch || 'main', " branch to ", environment] })] }) }));
224
- };
225
- export default DeployButton;
@@ -1,15 +0,0 @@
1
- /**
2
- * Fil's Hosted Deploy Button
3
- * Dependencies: React, @sanity/ui, @sanity/icons, @sanity/studio-secrets
4
- * Recommended to use inside the dashboard
5
- * Uses studio secrets to store Github token
6
- */
7
- declare const DeployButton: ({ config, title, deploymentUrl, environment, secretsNamespace, secretKey }: {
8
- config: any;
9
- title?: string | undefined;
10
- deploymentUrl: any;
11
- environment?: string | undefined;
12
- secretsNamespace?: string | undefined;
13
- secretKey?: string | undefined;
14
- }) => import("react/jsx-runtime").JSX.Element;
15
- export default DeployButton;
@@ -1,249 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- /**
3
- * Fil's Hosted Deploy Button
4
- * Dependencies: React, @sanity/ui, @sanity/icons, @sanity/studio-secrets
5
- * Recommended to use inside the dashboard
6
- * Uses studio secrets to store Github token
7
- */
8
- //@ts-ignore
9
- import React, { useState, useEffect, useRef } from 'react';
10
- import { Button, Card, Stack, Text, Spinner, Flex } from '@sanity/ui';
11
- import { PlayIcon, CheckmarkIcon, CloseIcon } from '@sanity/icons';
12
- import { useSecrets, SettingsView } from '@sanity/studio-secrets';
13
- const DeployButton = ({
14
- //@ts-ignore
15
- config, title = "🚀 Site Deployment",
16
- //@ts-ignore
17
- deploymentUrl, environment = "production", secretsNamespace = "deployButton", secretKey = "github_token" }) => {
18
- // ALL HOOKS MUST BE CALLED FIRST - NEVER CONDITIONALLY
19
- const [deployState, setDeployState] = useState('idle');
20
- const [message, setMessage] = useState('');
21
- const [showSettings, setShowSettings] = useState(false);
22
- const intervalRef = useRef(null);
23
- const monitoringRef = useRef(false);
24
- // Use Sanity secrets - ALWAYS call this hook
25
- const { secrets } = useSecrets(secretsNamespace);
26
- //@ts-ignore
27
- const token = secrets?.[secretKey];
28
- // Get the effective token (secrets take priority over config)
29
- const effectiveToken = token || config?.token;
30
- // Only show settings if we don't have a token AND we have valid config
31
- useEffect(() => {
32
- if (!effectiveToken && config?.owner && config?.repo && !showSettings) {
33
- setShowSettings(true);
34
- }
35
- else if (effectiveToken && showSettings) {
36
- // Close settings if we now have a token
37
- setShowSettings(false);
38
- }
39
- }, [effectiveToken, config?.owner, config?.repo, showSettings]);
40
- // Clean up on unmount
41
- useEffect(() => {
42
- return () => {
43
- if (intervalRef.current) {
44
- clearInterval(intervalRef.current);
45
- intervalRef.current = null;
46
- }
47
- };
48
- }, []);
49
- // Plugin config for secrets
50
- const pluginConfigKeys = [
51
- {
52
- key: secretKey,
53
- title: `GitHub Token for ${environment}`,
54
- description: `Personal access token for ${config?.owner}/${config?.repo} deployment`,
55
- type: 'string',
56
- inputType: 'password' // This should hide the token
57
- }
58
- ];
59
- // Poll workflow status
60
- //@ts-ignore
61
- const pollWorkflowStatus = async (runId) => {
62
- try {
63
- console.log('Polling workflow:', runId);
64
- const response = await fetch(`https://api.github.com/repos/${config.owner}/${config.repo}/actions/runs/${runId}`, {
65
- headers: {
66
- 'Authorization': `token ${effectiveToken}`,
67
- 'Accept': 'application/vnd.github.v3+json'
68
- }
69
- });
70
- if (!response.ok) {
71
- throw new Error(`HTTP ${response.status}`);
72
- }
73
- const run = await response.json();
74
- console.log('Run status:', run.status, 'conclusion:', run.conclusion);
75
- if (run.status === 'completed') {
76
- // Stop polling
77
- if (intervalRef.current) {
78
- clearInterval(intervalRef.current);
79
- intervalRef.current = null;
80
- }
81
- monitoringRef.current = false;
82
- if (run.conclusion === 'success') {
83
- setDeployState('success');
84
- setMessage('🎉 Deployment completed successfully!');
85
- }
86
- else {
87
- setDeployState('error');
88
- setMessage(`❌ Deployment failed: ${run.conclusion}`);
89
- }
90
- }
91
- else if (run.status === 'in_progress' || run.status === 'queued') {
92
- const startTime = new Date(run.run_started_at || run.created_at);
93
- const elapsed = Math.floor((Date.now() - startTime.getTime()) / 1000);
94
- setDeployState('deploying');
95
- setMessage(`🔄 Deployment running... (${elapsed}s)`);
96
- }
97
- }
98
- catch (error) {
99
- console.error('Polling error:', error);
100
- }
101
- };
102
- // Find the workflow run
103
- const findWorkflowRun = async () => {
104
- try {
105
- console.log('Looking for workflow run...');
106
- const response = await fetch(`https://api.github.com/repos/${config.owner}/${config.repo}/actions/runs?per_page=10`, {
107
- headers: {
108
- 'Authorization': `token ${effectiveToken}`,
109
- 'Accept': 'application/vnd.github.v3+json'
110
- }
111
- });
112
- if (!response.ok) {
113
- throw new Error(`HTTP ${response.status}`);
114
- }
115
- const data = await response.json();
116
- console.log('Found runs:', data.workflow_runs.length);
117
- // Find the most recent run (within last 90 seconds)
118
- //@ts-ignore
119
- const recentRun = data.workflow_runs.find(run => {
120
- const ageInSeconds = (Date.now() - new Date(run.created_at).getTime()) / 1000;
121
- console.log(`Run ${run.id}: age ${ageInSeconds}s, event: ${run.event}, status: ${run.status}`);
122
- return ageInSeconds < 90;
123
- });
124
- if (recentRun) {
125
- console.log('Found recent run:', recentRun.id, 'Event:', recentRun.event);
126
- setDeployState('deploying');
127
- setMessage('🚀 Deployment started...');
128
- // Start polling every 5 seconds
129
- //@ts-ignore
130
- intervalRef.current = setInterval(() => {
131
- pollWorkflowStatus(recentRun.id);
132
- }, 5000);
133
- // Initial poll
134
- pollWorkflowStatus(recentRun.id);
135
- }
136
- else {
137
- console.log('No recent workflow found');
138
- setDeployState('error');
139
- setMessage('❌ Could not find workflow run - check if workflow exists');
140
- }
141
- }
142
- catch (error) {
143
- console.error('Error finding workflow:', error);
144
- setDeployState('error');
145
- //@ts-ignore
146
- setMessage(`❌ Error finding workflow: ${error.message}`);
147
- }
148
- };
149
- const triggerDeploy = async () => {
150
- // Prevent multiple deployments
151
- if (deployState !== 'idle' && deployState !== 'success' && deployState !== 'error')
152
- return;
153
- setDeployState('triggering');
154
- setMessage('⚡ Triggering deployment...');
155
- try {
156
- console.log('Triggering deployment...');
157
- const response = await fetch(`https://api.github.com/repos/${config.owner}/${config.repo}/dispatches`, {
158
- method: 'POST',
159
- headers: {
160
- 'Authorization': `token ${effectiveToken}`,
161
- 'Accept': 'application/vnd.github.v3+json',
162
- 'Content-Type': 'application/json'
163
- },
164
- body: JSON.stringify({
165
- event_type: config.eventType || 'deploy-site',
166
- client_payload: {
167
- environment: environment,
168
- triggered_by: 'sanity_studio',
169
- timestamp: new Date().toISOString()
170
- }
171
- })
172
- });
173
- if (response.ok) {
174
- console.log('Deployment triggered successfully');
175
- setMessage('✅ Deployment triggered! Looking for workflow...');
176
- // Wait 5 seconds then look for the workflow
177
- setTimeout(() => {
178
- if (!monitoringRef.current) {
179
- monitoringRef.current = true;
180
- findWorkflowRun();
181
- }
182
- }, 5000);
183
- }
184
- else {
185
- const error = await response.json();
186
- throw new Error(error.message || `HTTP ${response.status}`);
187
- }
188
- }
189
- catch (error) {
190
- console.error('Deploy trigger failed:', error);
191
- setDeployState('error');
192
- //@ts-ignore
193
- setMessage(`❌ Failed to trigger: ${error.message}`);
194
- }
195
- };
196
- const getButtonProps = () => {
197
- switch (deployState) {
198
- case 'triggering':
199
- return { tone: 'primary', disabled: true, icon: Spinner, text: 'Triggering...' };
200
- case 'deploying':
201
- return { tone: 'primary', disabled: true, icon: Spinner, text: 'Deploying...' };
202
- case 'success':
203
- return { tone: 'positive', disabled: false, icon: CheckmarkIcon, text: 'Deploy Again' };
204
- case 'error':
205
- return { tone: 'critical', disabled: false, icon: CloseIcon, text: 'Try Again' };
206
- default:
207
- return { tone: 'primary', disabled: false, icon: PlayIcon, text: 'Deploy Site' };
208
- }
209
- };
210
- // NOW WE CAN HANDLE CONDITIONAL RENDERING AFTER ALL HOOKS
211
- // Check for missing config
212
- if (!config || !config.owner || !config.repo) {
213
- return (_jsx(Card, { padding: 4, radius: 2, shadow: 1, tone: "critical", children: _jsxs(Stack, { space: 3, children: [_jsx(Text, { size: 2, weight: "semibold", children: "\u274C Deploy Button Configuration Error" }), _jsx(Text, { size: 1, children: "Missing required config: owner and repo are required" })] }) }));
214
- }
215
- // If showing settings, render the settings view
216
- if (showSettings && !effectiveToken) {
217
- return (_jsx(Card, { padding: 4, radius: 2, shadow: 1, children: _jsxs(Stack, { space: 3, children: [_jsxs(Text, { size: 2, weight: "semibold", children: ["\uD83D\uDD10 ", title, " - Setup Required"] }), _jsx(Text, { size: 1, muted: true, children: "Please configure your GitHub token to enable deployments." }), _jsx(SettingsView, { title: `${title} Configuration`, namespace: secretsNamespace, keys: pluginConfigKeys, onClose: () => {
218
- setShowSettings(false);
219
- } })] }) }));
220
- }
221
- // Check for missing token after settings
222
- if (!effectiveToken) {
223
- return (_jsx(Card, { padding: 4, radius: 2, shadow: 1, tone: "critical", children: _jsxs(Stack, { space: 3, children: [_jsx(Text, { size: 2, weight: "semibold", children: "\u274C Authentication Required" }), _jsx(Text, { size: 1, children: "No GitHub token configured." }), _jsx(Button, { tone: "primary", mode: "ghost",
224
- //@ts-ignore
225
- size: "small", onClick: () => setShowSettings(true), children: "Configure Token" })] }) }));
226
- }
227
- const buttonProps = getButtonProps();
228
- return (_jsx(Card, { padding: 4, radius: 2, shadow: 1, children: _jsxs(Stack, { space: 3, children: [_jsx(Text, { size: 2, weight: "semibold", children: title }), _jsx(Flex, { justify: "flex-start", children: _jsx(Button
229
- //@ts-ignore
230
- , {
231
- //@ts-ignore
232
- tone: buttonProps.tone, disabled: buttonProps.disabled, onClick: triggerDeploy,
233
- //@ts-ignore
234
- size: "large", style: {
235
- width: '100%',
236
- maxWidth: '360px',
237
- }, children: _jsxs(Flex, { align: "center", justify: "center", style: {
238
- gap: '6px',
239
- padding: '16px 0px 16px 0px'
240
- }, children: [buttonProps.icon && (_jsx("span", { style: {
241
- display: 'flex',
242
- alignItems: 'center',
243
- fontSize: '16px',
244
- lineHeight: '1'
245
- }, children: React.createElement(buttonProps.icon) })), _jsx(Text, { size: 2, weight: "medium", style: { lineHeight: '1' }, children: buttonProps.text })] }) }) }), message && (_jsx(Card, { padding: 3, radius: 1, tone: deployState === 'error' ? 'critical' : deployState === 'success' ? 'positive' : 'primary', children: _jsx(Text, { size: 1, children: message }) })), deploymentUrl && deployState === 'success' && (_jsx(Button, { as: "a", href: deploymentUrl, target: "_blank", rel: "noopener noreferrer", tone: "primary", mode: "ghost",
246
- //@ts-ignore
247
- size: "small", children: "\uD83C\uDF10 View Live Site" })), _jsxs(Text, { size: 1, muted: true, children: ["Deploys from ", config.branch || 'main', " branch to ", environment] })] }) }));
248
- };
249
- export default DeployButton;