Download webui/src/features/vercel/VercelSyncForm.jsx from a3216/ds2api: direct link, hf CLI and curl.
- Browser
- Download file 11.8 kB
-
https://huggingface.co/spaces/a3216/ds2api/resolve/ce5a019598af8a3a8fa70d3880098df3e850ecd7/webui/src/features/vercel/VercelSyncForm.jsx
- Command line
-
hf download hf://spaces/a3216/ds2api@ce5a019598af8a3a8fa70d3880098df3e850ecd7/webui/src/features/vercel/VercelSyncForm.jsx
-
curl -L -o VercelSyncForm.jsx https://huggingface.co/spaces/a3216/ds2api/resolve/ce5a019598af8a3a8fa70d3880098df3e850ecd7/webui/src/features/vercel/VercelSyncForm.jsx
11.8 kB
| import { ArrowRight, CheckCircle2, Cloud, ExternalLink, RefreshCw } from 'lucide-react' | |
| import clsx from 'clsx' | |
| export default function VercelSyncForm({ | |
| t, | |
| syncStatus, | |
| pollPaused, | |
| pollFailures, | |
| onManualRefresh, | |
| preconfig, | |
| vercelToken, | |
| setVercelToken, | |
| projectId, | |
| setProjectId, | |
| teamId, | |
| setTeamId, | |
| saveCredentials, | |
| setSaveCredentials, | |
| configSource, | |
| onConfigSourceChange, | |
| localConfig, | |
| localConfigName, | |
| localConfigError, | |
| onLocalConfigSelect, | |
| onLocalConfigClear, | |
| loading, | |
| onSync, | |
| }) { | |
| return ( | |
| <div className="bg-card border border-border rounded-xl shadow-sm p-6 space-y-6"> | |
| <div className="border-b border-border pb-6"> | |
| <div className="flex items-center justify-between"> | |
| <h2 className="text-xl font-semibold flex items-center gap-2"> | |
| <Cloud className="w-6 h-6 text-primary" /> | |
| {t('vercel.title')} | |
| </h2> | |
| {syncStatus && ( | |
| <div className={clsx( | |
| "flex items-center gap-1.5 text-xs font-semibold px-2.5 py-1 rounded-full border transition-colors", | |
| syncStatus.synced | |
| ? "text-emerald-500 bg-emerald-500/10 border-emerald-500/20" | |
| : syncStatus.has_synced_before | |
| ? "text-amber-500 bg-amber-500/10 border-amber-500/20" | |
| : "text-muted-foreground bg-muted/50 border-border", | |
| )}> | |
| <span className={clsx( | |
| "w-1.5 h-1.5 rounded-full", | |
| syncStatus.synced ? "bg-emerald-500" : syncStatus.has_synced_before ? "bg-amber-500 animate-pulse" : "bg-muted-foreground", | |
| )} /> | |
| {syncStatus.synced | |
| ? t('vercel.statusSynced') | |
| : syncStatus.has_synced_before | |
| ? t('vercel.statusNotSynced') | |
| : t('vercel.statusNeverSynced')} | |
| </div> | |
| )} | |
| </div> | |
| <p className="text-muted-foreground text-sm mt-1"> | |
| {t('vercel.description')} | |
| </p> | |
| {pollPaused && ( | |
| <div className="mt-2 flex flex-wrap items-center gap-2"> | |
| <p className="text-xs text-destructive"> | |
| {t('vercel.pollPaused', { count: pollFailures })} | |
| </p> | |
| <button | |
| type="button" | |
| onClick={onManualRefresh} | |
| className="px-2 py-1 text-xs rounded border border-border hover:bg-secondary/50" | |
| > | |
| {t('vercel.manualRefresh')} | |
| </button> | |
| </div> | |
| )} | |
| {syncStatus?.last_sync_time && ( | |
| <p className="text-xs text-muted-foreground/60 mt-1.5 flex items-center gap-1"> | |
| <RefreshCw className="w-3 h-3" /> | |
| {t('vercel.lastSyncTime', { time: new Date(syncStatus.last_sync_time * 1000).toLocaleString() })} | |
| </p> | |
| )} | |
| {syncStatus?.draft_differs && ( | |
| <p className="text-xs text-amber-500 mt-2"> | |
| {t('vercel.draftDiffers')} | |
| </p> | |
| )} | |
| </div> | |
| <div className="space-y-4"> | |
| <div className="space-y-2"> | |
| <label className="text-sm font-medium">{t('vercel.configSourceLabel')}</label> | |
| <div className="grid grid-cols-1 sm:grid-cols-2 gap-2"> | |
| <button | |
| type="button" | |
| onClick={() => onConfigSourceChange('memory')} | |
| className={clsx( | |
| "px-3 py-2 rounded-lg border text-sm transition-colors", | |
| configSource === 'memory' | |
| ? "border-primary bg-primary/10 text-primary" | |
| : "border-border hover:bg-secondary/50" | |
| )} | |
| > | |
| {t('vercel.configSourceMemory')} | |
| </button> | |
| <button | |
| type="button" | |
| onClick={() => onConfigSourceChange('local')} | |
| className={clsx( | |
| "px-3 py-2 rounded-lg border text-sm transition-colors", | |
| configSource === 'local' | |
| ? "border-primary bg-primary/10 text-primary" | |
| : "border-border hover:bg-secondary/50" | |
| )} | |
| > | |
| {t('vercel.configSourceLocal')} | |
| </button> | |
| </div> | |
| {configSource === 'local' && ( | |
| <div className="mt-2 space-y-2"> | |
| <input | |
| type="file" | |
| accept="application/json,.json" | |
| onChange={(e) => onLocalConfigSelect(e.target.files?.[0])} | |
| className="block w-full text-sm file:mr-3 file:py-2 file:px-3 file:rounded-lg file:border-0 file:bg-secondary file:text-secondary-foreground hover:file:bg-secondary/80" | |
| /> | |
| {localConfigName && ( | |
| <div className="flex items-center justify-between text-xs text-muted-foreground"> | |
| <span>{t('vercel.localConfigLoaded', { name: localConfigName })}</span> | |
| <button type="button" onClick={onLocalConfigClear} className="text-primary hover:underline"> | |
| {t('vercel.localConfigClear')} | |
| </button> | |
| </div> | |
| )} | |
| {!localConfig && !localConfigError && ( | |
| <p className="text-xs text-muted-foreground">{t('vercel.localConfigHint')}</p> | |
| )} | |
| {localConfigError && ( | |
| <p className="text-xs text-destructive">{localConfigError}</p> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| <div className="space-y-2"> | |
| <label className="text-sm font-medium flex items-center justify-between"> | |
| {t('vercel.tokenLabel')} | |
| <a href="https://vercel.com/account/tokens" target="_blank" rel="noopener noreferrer" className="text-xs text-primary hover:underline flex items-center gap-1"> | |
| {t('vercel.getToken')} <ExternalLink className="w-3 h-3" /> | |
| </a> | |
| </label> | |
| <div className="relative"> | |
| <input | |
| type="password" | |
| className="w-full h-10 px-3 bg-background border border-border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring transition-all pr-10" | |
| placeholder={preconfig?.has_token ? t('vercel.tokenPlaceholderPreconfig') : t('vercel.tokenPlaceholder')} | |
| value={vercelToken} | |
| onChange={e => setVercelToken(e.target.value)} | |
| /> | |
| {preconfig?.has_token && !vercelToken && ( | |
| <div className="absolute right-3 top-2.5 text-emerald-500"> | |
| <CheckCircle2 className="w-5 h-5" /> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| <div className="space-y-2"> | |
| <label className="text-sm font-medium">{t('vercel.projectIdLabel')}</label> | |
| <input | |
| type="text" | |
| className="w-full h-10 px-3 bg-background border border-border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring transition-all" | |
| placeholder="prj_xxxxxxxxxxxx or Project Name" | |
| value={projectId} | |
| onChange={e => setProjectId(e.target.value)} | |
| /> | |
| <p className="text-xs text-muted-foreground">{t('vercel.projectIdHint')}</p> | |
| </div> | |
| <div className="space-y-2"> | |
| <label className="text-sm font-medium flex items-center gap-2"> | |
| {t('vercel.teamIdLabel')} <span className="text-xs text-muted-foreground font-normal">({t('vercel.optional')})</span> | |
| </label> | |
| <input | |
| type="text" | |
| className="w-full h-10 px-3 bg-background border border-border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring transition-all" | |
| placeholder="team_xxxxxxxxxxxx" | |
| value={teamId} | |
| onChange={e => setTeamId(e.target.value)} | |
| /> | |
| </div> | |
| <label className="flex items-start gap-3 text-sm"> | |
| <input | |
| type="checkbox" | |
| className="mt-1 h-4 w-4 rounded border-border text-primary focus:ring-ring" | |
| checked={saveCredentials} | |
| onChange={e => setSaveCredentials(e.target.checked)} | |
| /> | |
| <span className="space-y-1"> | |
| <span className="block font-medium">{t('vercel.saveCredentials')}</span> | |
| <span className="block text-xs text-muted-foreground">{t('vercel.saveCredentialsHint')}</span> | |
| </span> | |
| </label> | |
| </div> | |
| <div className="pt-4"> | |
| <button | |
| onClick={onSync} | |
| disabled={loading} | |
| className="w-full flex items-center justify-center gap-2 py-3 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-all font-medium text-sm shadow-sm hover:shadow-md disabled:opacity-50 disabled:shadow-none" | |
| > | |
| {loading ? ( | |
| <span className="flex items-center gap-2"> | |
| <span className="w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin" /> | |
| {t('vercel.syncing')} | |
| </span> | |
| ) : ( | |
| <span className="flex items-center gap-2"> | |
| {t('vercel.syncRedeploy')} <ArrowRight className="w-4 h-4" /> | |
| </span> | |
| )} | |
| </button> | |
| <p className="text-xs text-center text-muted-foreground mt-4"> | |
| {t('vercel.redeployHint')} | |
| </p> | |
| </div> | |
| </div> | |
| ) | |
| } | |