Infrastructure Sync: Initial migration to custom self-hosted Gitea

This commit is contained in:
2026-08-21 15:40:26 +09:00
parent fa15956a90
commit d64b3c72b7
47 changed files with 1228 additions and 1460 deletions
+163 -24
View File
@@ -125,17 +125,20 @@ function PrefSlider({
);
}
export function SettingsModal({ isOpen, onClose, user, isDemo = false }: SettingsModalProps) {
export function SettingsModal({ isOpen, onClose, user, isDemo: _isDemo = false }: SettingsModalProps) {
const { t, lang, setLang } = useI18n();
const { theme, toggleTheme } = useTheme();
const { prefs, updatePrefs, resetToDefaults } = useUserPrefs();
const [activeTab, setActiveTab] = useState<"profile" | "preferences" | "labs" | "sync" | "admin">("profile");
const [displayName, setDisplayName] = useState(user.name || "Demo User");
const [email, setEmail] = useState(user.email || "demo@checkflow.local");
const [displayName, setDisplayName] = useState(() => user.name || (typeof window !== "undefined" ? getUserSettings().displayName : "Demo User"));
const [email, setEmail] = useState(() => user.email || (typeof window !== "undefined" ? getUserSettings().email : "demo@checkflow.local"));
const [password, setPassword] = useState("");
const [trashRetention, setTrashRetention] = useState(30);
const [trashRetention, setTrashRetention] = useState(() => (typeof window !== "undefined" ? (getUserSettings().trashRetentionDays ?? 30) : 30));
const [savedMsg, setSavedMsg] = useState("");
const [syncPlatform, setSyncPlatform] = useState<"android" | "apple" | "thunderbird">("android");
const [copiedCalDav, setCopiedCalDav] = useState(false);
const [testSyncStatus, setTestSyncStatus] = useState<"idle" | "testing" | "success" | "error">("idle");
useEffect(() => {
if (isOpen) {
@@ -146,6 +149,23 @@ export function SettingsModal({ isOpen, onClose, user, isDemo = false }: Setting
}
}, [isOpen, user]);
const handleTestConnection = async () => {
setTestSyncStatus("testing");
try {
const res = await fetch("/api/dav", { method: "OPTIONS" });
if (res.ok || res.status === 401 || res.status === 207) {
setTestSyncStatus("success");
} else {
setTestSyncStatus("error");
}
} catch {
setTestSyncStatus("error");
}
setTimeout(() => {
setTestSyncStatus("idle");
}, 4000);
};
if (!isOpen) return null;
const handleSave = () => {
@@ -267,7 +287,7 @@ export function SettingsModal({ isOpen, onClose, user, isDemo = false }: Setting
</div>
<div className="form-group">
<label className="form-label">{t("language")}</label>
<select className="form-input" value={lang} onChange={(e) => setLang(e.target.value as any)}>
<select className="form-input" value={lang} onChange={(e) => setLang(e.target.value as "en" | "ko" | "ja")}>
<option value="en">English (Default)</option>
<option value="ko"> (Korean)</option>
<option value="ja"> (Japanese)</option>
@@ -428,7 +448,7 @@ export function SettingsModal({ isOpen, onClose, user, isDemo = false }: Setting
min={0}
max={360}
value={prefs.accentHue}
onChange={(e) => updatePrefs({ accentHue: parseInt(e.target.value) })}
onChange={(e) => updatePrefs({ accentHue: parseInt(e.target.value, 10) })}
style={{ width: 100, accentColor: accentPreview }}
/>
<div style={{ width: 18, height: 18, borderRadius: "50%", background: accentPreview, flexShrink: 0 }} />
@@ -490,30 +510,149 @@ export function SettingsModal({ isOpen, onClose, user, isDemo = false }: Setting
</div>
)}
{/* Tab: CalDAV / CardDAV Integrations */}
{/* Tab: CalDAV / External Sync */}
{activeTab === "sync" && (
<div className="settings-tab-content">
<h3 style={{ fontSize: 14, fontWeight: 700, marginBottom: 8 }}>📱 Galaxy & Mobile Sync via DAVx</h3>
<p style={{ fontSize: 13, color: "var(--text-secondary)", lineHeight: 1.5, marginBottom: 12 }}>
CheckFlow supports native two-way synchronization with Samsung Galaxy Reminder, Apple Reminders, and Thunderbird via CalDAV/CardDAV protocols.
<h3 style={{ fontSize: 15, fontWeight: 700, marginBottom: 6 }}>
📱 {t("syncTitle") || "Galaxy & External Sync (CalDAV)"}
</h3>
<p style={{ fontSize: 13, color: "var(--text-secondary)", lineHeight: 1.5, marginBottom: 14 }}>
{t("syncDesc") || "CheckFlow supports native two-way synchronization with Samsung Galaxy Reminder, Apple Reminders, and Thunderbird via CalDAV."}
</p>
<div className="form-group">
<label className="form-label">CalDAV Base Endpoint</label>
<div style={{ display: "flex", gap: 8 }}>
<input className="form-input" readOnly value={calDavUrl} style={{ fontFamily: "monospace", fontSize: 12 }} />
<button type="button" className="btn btn-ghost btn-sm" onClick={() => { navigator.clipboard.writeText(calDavUrl); }}>
📋 Copy
{/* Endpoint bar & Action buttons */}
<div className="form-group" style={{ marginBottom: 14 }}>
<label className="form-label" style={{ fontWeight: 600 }}>{t("syncBaseUrl") || "CalDAV Server Base URL"}</label>
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
<input
className="form-input"
readOnly
value={calDavUrl}
style={{ fontFamily: "monospace", fontSize: 12.5, background: "var(--bg-secondary)", flex: 1 }}
onClick={(e) => (e.target as HTMLInputElement).select()}
/>
<button
type="button"
className="btn btn-ghost btn-sm"
onClick={() => {
navigator.clipboard.writeText(calDavUrl);
setCopiedCalDav(true);
setTimeout(() => setCopiedCalDav(false), 2000);
}}
style={{ minWidth: 80, fontWeight: 600 }}
>
{copiedCalDav ? `${t("syncCopied") || "Copied!"}` : `📋 ${t("syncCopyUrl") || "Copy"}`}
</button>
</div>
</div>
<div style={{ background: "var(--bg-secondary)", padding: 12, borderRadius: "var(--radius-sm)", fontSize: 12, color: "var(--text-secondary)" }}>
<strong>Setup Instructions for DAVx (Android):</strong>
<ol style={{ paddingLeft: 18, marginTop: 6, lineHeight: 1.6 }}>
<li>Install <strong>DAVx</strong> on your Samsung Galaxy from Google Play or F-Droid.</li>
<li>Add account <strong>Login with URL and user name</strong>.</li>
<li>Base URL: <code>{calDavUrl}</code></li>
<li>User / Password: Your CheckFlow email & password.</li>
</ol>
{/* Actions row: Test & Download */}
<div style={{ display: "flex", gap: 10, marginBottom: 16, flexWrap: "wrap", alignItems: "center" }}>
<button
type="button"
className="btn btn-sm btn-ghost"
onClick={handleTestConnection}
disabled={testSyncStatus === "testing"}
style={{ display: "inline-flex", alignItems: "center", gap: 6 }}
>
{testSyncStatus === "testing" ? `${t("syncTesting") || "Testing..."}` : `🔍 ${t("syncTestConnection") || "Test Endpoint"}`}
</button>
<a
href="/api/dav"
target="_blank"
rel="noreferrer"
className="btn btn-sm btn-ghost"
style={{ display: "inline-flex", alignItems: "center", gap: 6, textDecoration: "none" }}
>
📥 {t("syncDownloadIcs") || "Download .ICS Feed"}
</a>
{testSyncStatus === "success" && (
<span style={{ fontSize: 12, color: "var(--success)", fontWeight: 600 }}>
{t("syncTestSuccess") || "✓ CalDAV endpoint responded successfully"}
</span>
)}
{testSyncStatus === "error" && (
<span style={{ fontSize: 12, color: "var(--danger)", fontWeight: 600 }}>
Endpoint test failed
</span>
)}
</div>
{/* Client setup guides segmented selector */}
<div style={{ marginBottom: 10 }}>
<SegmentedControl
value={syncPlatform}
options={[
{ label: `🤖 ${t("syncTabAndroid") || "Galaxy / DAVx⁵"}`, value: "android" },
{ label: `🍎 ${t("syncTabApple") || "Apple Reminders"}`, value: "apple" },
{ label: `💻 ${t("syncTabThunderbird") || "Thunderbird"}`, value: "thunderbird" },
]}
onChange={(v) => setSyncPlatform(v)}
/>
</div>
{/* Step by step cards */}
<div
style={{
background: "var(--bg-secondary)",
padding: "14px 16px",
borderRadius: "var(--radius-md)",
border: "1px solid var(--border)",
fontSize: 12.5,
color: "var(--text-primary)",
lineHeight: 1.6,
}}
>
{syncPlatform === "android" && (
<div>
<div style={{ fontWeight: 700, marginBottom: 6, color: "var(--accent)" }}>
📱 Samsung Galaxy & Android (DAVx + Reminder / OpenTasks)
</div>
<ol style={{ paddingLeft: 20, margin: 0, display: "flex", flexDirection: "column", gap: 4 }}>
<li>{t("syncAndroidStep1")}</li>
<li>{t("syncAndroidStep2")}</li>
<li>{t("syncAndroidStep3")}</li>
<li>{t("syncAndroidStep4")}</li>
</ol>
<div style={{ marginTop: 10, fontSize: 11.5, color: "var(--text-tertiary)", borderTop: "1px dashed var(--border)", paddingTop: 8 }}>
💡 <strong>Tip:</strong> In DAVx account settings, set Sync Interval to <strong>15 minutes</strong> for battery efficiency and near real-time sync.
</div>
</div>
)}
{syncPlatform === "apple" && (
<div>
<div style={{ fontWeight: 700, marginBottom: 6, color: "var(--accent)" }}>
🍎 Apple Reminders & Calendar (iOS / iPadOS / macOS)
</div>
<ol style={{ paddingLeft: 20, margin: 0, display: "flex", flexDirection: "column", gap: 4 }}>
<li>{t("syncAppleStep1")}</li>
<li>{t("syncAppleStep2")}</li>
<li>{t("syncAppleStep3")}</li>
<li>{t("syncAppleStep4")}</li>
</ol>
<div style={{ marginTop: 10, fontSize: 11.5, color: "var(--text-tertiary)", borderTop: "1px dashed var(--border)", paddingTop: 8 }}>
💡 <strong>Tip:</strong> If using HTTPS behind a reverse proxy (Nginx/Caddy), ensure valid SSL certificates are trusted by Apple devices.
</div>
</div>
)}
{syncPlatform === "thunderbird" && (
<div>
<div style={{ fontWeight: 700, marginBottom: 6, color: "var(--accent)" }}>
💻 Mozilla Thunderbird (Windows / Mac / Linux)
</div>
<ol style={{ paddingLeft: 20, margin: 0, display: "flex", flexDirection: "column", gap: 4 }}>
<li>{t("syncThunderbirdStep1")}</li>
<li>{t("syncThunderbirdStep2")}</li>
<li>{t("syncThunderbirdStep3")}</li>
<li>{t("syncThunderbirdStep4")}</li>
</ol>
<div style={{ marginTop: 10, fontSize: 11.5, color: "var(--text-tertiary)", borderTop: "1px dashed var(--border)", paddingTop: 8 }}>
💡 <strong>Tip:</strong> Thunderbird Tasks view will display CheckFlow priority tags, due dates, and completion status.
</div>
</div>
)}
</div>
</div>
)}