fix(gitea-bot): add write:organization scope so bot can create repos
Without this the bot PAT 403s on POST /orgs/{org}/repos, which is
the single most important operation — creating new project repos
inside the workspace's Gitea org.
Made-with: Cursor
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { ReactNode, CSSProperties } from "react";
|
||||
import { JM } from "./modal-theme";
|
||||
|
||||
export interface SetupProps {
|
||||
workspace: string;
|
||||
@@ -8,7 +9,68 @@ export interface SetupProps {
|
||||
onBack: () => void;
|
||||
}
|
||||
|
||||
// Shared modal header
|
||||
export function FieldLabel({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<label style={{
|
||||
display: "block", fontSize: 12, fontWeight: 600, color: JM.mid,
|
||||
marginBottom: 6, fontFamily: JM.fontSans,
|
||||
}}>
|
||||
{children}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
export function ForWhomSelector({
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
value: "personal" | "client";
|
||||
onChange: (v: "personal" | "client") => void;
|
||||
}) {
|
||||
const cardBase: CSSProperties = {
|
||||
flex: 1,
|
||||
border: `1px solid ${JM.border}`,
|
||||
borderRadius: 9,
|
||||
padding: 14,
|
||||
cursor: "pointer",
|
||||
textAlign: "center" as const,
|
||||
background: JM.inputBg,
|
||||
transition: "all 0.15s",
|
||||
fontFamily: JM.fontSans,
|
||||
};
|
||||
|
||||
const row = (key: "personal" | "client", emoji: string, title: string, sub: string) => {
|
||||
const sel = value === key;
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
key={key}
|
||||
onClick={() => onChange(key)}
|
||||
style={{
|
||||
...cardBase,
|
||||
borderColor: sel ? JM.indigo : JM.border,
|
||||
background: sel ? JM.cream : JM.inputBg,
|
||||
boxShadow: sel ? "0 0 0 1px rgba(99,102,241,0.2)" : undefined,
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: 20, marginBottom: 5 }}>{emoji}</div>
|
||||
<div style={{ fontSize: 12.5, fontWeight: 600, color: JM.ink }}>{title}</div>
|
||||
<div style={{ fontSize: 11, color: JM.muted, marginTop: 2 }}>{sub}</div>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: 22 }}>
|
||||
<FieldLabel>This project is for…</FieldLabel>
|
||||
<div style={{ display: "flex", gap: 8 }}>
|
||||
{row("personal", "🧑💻", "Myself", "My own product")}
|
||||
{row("client", "🤝", "A client", "Client project")}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function SetupHeader({
|
||||
icon,
|
||||
label,
|
||||
@@ -25,41 +87,47 @@ export function SetupHeader({
|
||||
onClose: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", marginBottom: 28 }}>
|
||||
<div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", marginBottom: 22 }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onBack}
|
||||
style={{
|
||||
background: "none", border: "none", cursor: "pointer",
|
||||
color: "#b5b0a6", fontSize: "1rem", padding: "3px 5px",
|
||||
color: JM.muted, fontSize: "1rem", padding: "3px 5px",
|
||||
borderRadius: 4, lineHeight: 1, flexShrink: 0,
|
||||
fontFamily: JM.fontSans,
|
||||
}}
|
||||
onMouseEnter={e => (e.currentTarget.style.color = "#1a1a1a")}
|
||||
onMouseLeave={e => (e.currentTarget.style.color = "#b5b0a6")}
|
||||
onMouseEnter={e => (e.currentTarget.style.color = JM.ink)}
|
||||
onMouseLeave={e => (e.currentTarget.style.color = JM.muted)}
|
||||
>
|
||||
←
|
||||
</button>
|
||||
<div>
|
||||
<h2 style={{
|
||||
fontFamily: "var(--font-lora), ui-serif, serif", fontSize: "1.3rem", fontWeight: 400,
|
||||
color: "#1a1a1a", margin: 0, marginBottom: 3,
|
||||
fontFamily: JM.fontDisplay, fontSize: 18, fontWeight: 700,
|
||||
color: JM.ink, margin: 0, marginBottom: 3, letterSpacing: "-0.02em",
|
||||
}}>
|
||||
{label}
|
||||
</h2>
|
||||
<p style={{ fontSize: "0.72rem", fontWeight: 600, color: accent, textTransform: "uppercase", letterSpacing: "0.04em", margin: 0 }}>
|
||||
<p style={{
|
||||
fontSize: 10.5, fontWeight: 600, color: accent, textTransform: "uppercase",
|
||||
letterSpacing: "0.07em", margin: 0, fontFamily: JM.fontSans,
|
||||
}}>
|
||||
{tagline}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
style={{
|
||||
background: "none", border: "none", cursor: "pointer",
|
||||
color: "#b5b0a6", fontSize: "1.2rem", lineHeight: 1,
|
||||
padding: "2px 5px", borderRadius: 4, flexShrink: 0,
|
||||
color: JM.muted, fontSize: 20, lineHeight: 1,
|
||||
padding: 4, flexShrink: 0, fontFamily: JM.fontSans,
|
||||
}}
|
||||
onMouseEnter={e => (e.currentTarget.style.color = "#6b6560")}
|
||||
onMouseLeave={e => (e.currentTarget.style.color = "#b5b0a6")}
|
||||
onMouseEnter={e => (e.currentTarget.style.color = JM.mid)}
|
||||
onMouseLeave={e => (e.currentTarget.style.color = JM.muted)}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
@@ -67,14 +135,6 @@ export function SetupHeader({
|
||||
);
|
||||
}
|
||||
|
||||
export function FieldLabel({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<label style={{ display: "block", fontSize: "0.72rem", fontWeight: 600, color: "#6b6560", marginBottom: 6, letterSpacing: "0.02em" }}>
|
||||
{children}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
export function TextInput({
|
||||
value,
|
||||
onChange,
|
||||
@@ -88,13 +148,13 @@ export function TextInput({
|
||||
placeholder?: string;
|
||||
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||
autoFocus?: boolean;
|
||||
inputRef?: React.RefObject<HTMLInputElement>;
|
||||
inputRef?: React.RefObject<HTMLInputElement | null> | React.RefObject<HTMLInputElement>;
|
||||
}) {
|
||||
const base: CSSProperties = {
|
||||
width: "100%", padding: "11px 14px", marginBottom: 16,
|
||||
borderRadius: 8, border: "1px solid #e0dcd4",
|
||||
background: "#faf8f5", fontSize: "0.9rem",
|
||||
fontFamily: "var(--font-inter), ui-sans-serif, sans-serif", color: "#1a1a1a",
|
||||
width: "100%", padding: "10px 13px", marginBottom: 16,
|
||||
borderRadius: 8, border: `1px solid ${JM.border}`,
|
||||
background: JM.inputBg, fontSize: 14,
|
||||
fontFamily: JM.fontSans, color: JM.ink,
|
||||
outline: "none", boxSizing: "border-box",
|
||||
};
|
||||
return (
|
||||
@@ -107,8 +167,8 @@ export function TextInput({
|
||||
placeholder={placeholder}
|
||||
autoFocus={autoFocus}
|
||||
style={base}
|
||||
onFocus={e => (e.currentTarget.style.borderColor = "#1a1a1a")}
|
||||
onBlur={e => (e.currentTarget.style.borderColor = "#e0dcd4")}
|
||||
onFocus={e => (e.currentTarget.style.borderColor = JM.indigo)}
|
||||
onBlur={e => (e.currentTarget.style.borderColor = JM.border)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -127,27 +187,44 @@ export function PrimaryButton({
|
||||
const active = !disabled && !loading;
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
disabled={!active}
|
||||
style={{
|
||||
width: "100%", padding: "12px",
|
||||
borderRadius: 8, border: "none",
|
||||
background: active ? "#1a1a1a" : "#e0dcd4",
|
||||
color: active ? "#fff" : "#b5b0a6",
|
||||
fontSize: "0.88rem", fontWeight: 600,
|
||||
fontFamily: "var(--font-inter), ui-sans-serif, sans-serif",
|
||||
background: active ? JM.primaryGradient : "#E5E7EB",
|
||||
color: active ? "#fff" : JM.muted,
|
||||
fontSize: 14, fontWeight: 600,
|
||||
fontFamily: JM.fontSans,
|
||||
cursor: active ? "pointer" : "not-allowed",
|
||||
display: "flex", alignItems: "center", justifyContent: "center", gap: 8,
|
||||
display: "flex", alignItems: "center", justifyContent: "center", gap: 6,
|
||||
boxShadow: active ? JM.primaryShadow : "none",
|
||||
transition: "box-shadow 0.2s, transform 0.15s, opacity 0.15s",
|
||||
}}
|
||||
onMouseEnter={e => {
|
||||
if (active) {
|
||||
e.currentTarget.style.boxShadow = JM.primaryShadowHover;
|
||||
e.currentTarget.style.transform = "translateY(-1px)";
|
||||
}
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
e.currentTarget.style.boxShadow = active ? JM.primaryShadow : "none";
|
||||
e.currentTarget.style.transform = "none";
|
||||
}}
|
||||
onMouseEnter={e => { if (active) e.currentTarget.style.opacity = "0.85"; }}
|
||||
onMouseLeave={e => { e.currentTarget.style.opacity = "1"; }}
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<span style={{ width: 14, height: 14, borderRadius: "50%", border: "2px solid #fff4", borderTopColor: "#fff", animation: "vibn-spin 0.7s linear infinite", display: "inline-block" }} />
|
||||
<span style={{
|
||||
width: 14, height: 14, borderRadius: "50%",
|
||||
border: "2px solid rgba(255,255,255,0.35)", borderTopColor: "#fff",
|
||||
animation: "vibn-spin 0.7s linear infinite", display: "inline-block",
|
||||
}} />
|
||||
Creating…
|
||||
</>
|
||||
) : children}
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user