feat: render CNAME and Cloudflare SSL TXT verification instructions with copy buttons in mobile app domain settings

This commit is contained in:
AyrisAI
2026-08-20 17:33:14 +03:00
parent 5cbd80326f
commit 8adec100af
+156 -17
View File
@@ -30,6 +30,24 @@ interface RestaurantDetail {
address: string | null;
}
export interface TxtRecord {
name: string;
value: string;
}
export interface DomainInstructions {
cname?: { type: string; host: string; target: string };
ownershipTxt?: TxtRecord | null;
sslTxt?: TxtRecord[];
}
export interface CloudflareStatus {
hostnameStatus?: string;
sslStatus?: string;
ownershipTxt?: TxtRecord | null;
sslTxt?: TxtRecord[];
}
interface DomainRow {
id: string;
hostname: string;
@@ -37,6 +55,8 @@ interface DomainRow {
status: "pending" | "verified" | "failed";
verified_at: string | null;
created_at: string;
instructions?: DomainInstructions;
cloudflareStatus?: CloudflareStatus | null;
}
const THEME_OPTIONS = [
@@ -192,15 +212,20 @@ export default function AccountScreen() {
setAddingDomain(true);
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium).catch(() => {});
try {
const res = await api.post<{ domain: DomainRow }>(`/restaurants/${active.restaurantId}/domains`, {
hostname: newDomainInput.trim(),
});
setDomains((prev) => [res.domain, ...prev]);
const res = await api.post<{ domain: DomainRow; instructions?: DomainInstructions; cnameTarget?: string }>(
`/restaurants/${active.restaurantId}/domains`,
{ hostname: newDomainInput.trim() },
);
const updatedDomain: DomainRow = {
...res.domain,
instructions: res.instructions,
};
setDomains((prev) => [updatedDomain, ...prev]);
setNewDomainInput("");
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {});
Alert.alert(
"Alan Adı Eklendi 🎉",
`Lütfen DNS sağlayıcınızda (GoDaddy, Cloudflare vb.) ${res.domain.hostname} için CNAME kaydını ${cnameTarget} adresine yönlendirin.`,
`Lütfen DNS sağlayıcınızda (GoDaddy, Cloudflare vb.) yönlendirme (CNAME) ve varsa SSL TXT doğrulama kayıtlarını ekleyin.`,
);
} catch (err) {
Alert.alert("Hata", err instanceof Error ? err.message : "Alan adı eklenemedi.");
@@ -214,15 +239,19 @@ export default function AccountScreen() {
setVerifyingDomainId(domainId);
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {});
try {
const res = await api.post<{ verified: boolean; domain: DomainRow; message: string }>(
const res = await api.post<{ verified: boolean; domain: DomainRow; cloudflareStatus?: CloudflareStatus; message: string }>(
`/restaurants/${active.restaurantId}/domains/${domainId}/verify`,
);
const updatedDomain: DomainRow = {
...res.domain,
cloudflareStatus: res.cloudflareStatus,
};
if (res.verified) {
setDomains((prev) => prev.map((d) => (d.id === domainId ? res.domain : d)));
setDomains((prev) => prev.map((d) => (d.id === domainId ? updatedDomain : d)));
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {});
Alert.alert("Tebrikler! 🎉", res.message);
} else {
setDomains((prev) => prev.map((d) => (d.id === domainId ? res.domain : d)));
setDomains((prev) => prev.map((d) => (d.id === domainId ? updatedDomain : d)));
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning).catch(() => {});
Alert.alert("Doğrulanamadı", res.message);
}
@@ -673,25 +702,135 @@ export default function AccountScreen() {
</View>
</View>
{/* CNAME instructions if pending */}
{/* DNS & SSL Instructions if Pending */}
{!isVerified ? (
<View
style={{
marginTop: 10,
padding: 10,
marginTop: 12,
padding: 12,
backgroundColor: "#FFFFFF",
borderRadius: 8,
borderRadius: 12,
borderWidth: 1,
borderColor: "#E7E5E4",
gap: 10,
}}
>
<Text style={{ fontSize: 11, fontWeight: "700", color: "#44403C" }}>
DNS Kaydı Talimatı:
<Text style={{ fontSize: 12, fontWeight: "800", color: "#1C1917" }}>
📌 DNS & SSL Doğrulama Talimatları
</Text>
<Text style={{ fontSize: 11, color: "#78716C", marginTop: 2 }}>
Tür: <Text style={{ fontWeight: "700", color: "#1C1917" }}>CNAME</Text> | Hedef:{" "}
<Text style={{ fontWeight: "700", color: "#C8A96B" }}>{cnameTarget}</Text>
<Text style={{ fontSize: 11, color: "#78716C", lineHeight: 16 }}>
Domain sağlayıcınızın (GoDaddy, Natro, Cloudflare vb.) DNS paneline aşağıdaki kayıtları ekleyin:
</Text>
{/* 1. CNAME Record */}
<View
style={{
backgroundColor: "#FAF8F5",
padding: 10,
borderRadius: 8,
borderWidth: 1,
borderColor: "#E7E5E4",
gap: 4,
}}
>
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
<Text style={{ fontSize: 11, fontWeight: "800", color: "#C8A96B" }}>
1. CNAME Yönlendirme Kaydı (Zorunlu)
</Text>
<Pressable
onPress={() => {
Clipboard.setStringAsync(cnameTarget);
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {});
Alert.alert("Kopyalandı! 📋", `CNAME Hedefi (${cnameTarget}) panoya kopyalandı.`);
}}
style={{ backgroundColor: "#F59E0B20", paddingHorizontal: 8, paddingVertical: 2, borderRadius: 6 }}
>
<Text style={{ fontSize: 10, fontWeight: "700", color: "#D97706" }}>Kopyala</Text>
</Pressable>
</View>
<Text style={{ fontSize: 11, color: "#57534E" }}>
Tür: <Text style={{ fontWeight: "700", color: "#1C1917" }}>CNAME</Text>
</Text>
<Text style={{ fontSize: 11, color: "#57534E" }}>
Host: <Text style={{ fontWeight: "700", color: "#1C1917" }}>{dom.hostname}</Text>
</Text>
<Text style={{ fontSize: 11, color: "#57534E" }}>
Hedef: <Text style={{ fontWeight: "700", color: "#C8A96B" }}>{cnameTarget}</Text>
</Text>
</View>
{/* 2. TXT Verification Records (If returned by Cloudflare for SSL) */}
{(() => {
const txts: TxtRecord[] = [];
if (dom.instructions?.ownershipTxt) txts.push(dom.instructions.ownershipTxt);
if (dom.instructions?.sslTxt) txts.push(...dom.instructions.sslTxt);
if (dom.cloudflareStatus?.ownershipTxt) txts.push(dom.cloudflareStatus.ownershipTxt);
if (dom.cloudflareStatus?.sslTxt) txts.push(...dom.cloudflareStatus.sslTxt);
const uniqueTxts = txts.filter(
(txt, index, self) =>
index === self.findIndex((t) => t.name === txt.name && t.value === txt.value),
);
if (uniqueTxts.length === 0) return null;
return (
<View style={{ gap: 8 }}>
<Text style={{ fontSize: 11, fontWeight: "800", color: "#0284C7", marginTop: 2 }}>
2. SSL Sertifikası TXT Doğrulama Kaydı (Gerekliyse):
</Text>
{uniqueTxts.map((txt, idx) => (
<View
key={idx}
style={{
backgroundColor: "#F0F9FF",
padding: 10,
borderRadius: 8,
borderWidth: 1,
borderColor: "#BAE6FD",
gap: 6,
}}
>
<Text style={{ fontSize: 10, fontWeight: "800", color: "#0369A1" }}>
TXT Kaydı #{idx + 1} (SSL Doğrulama)
</Text>
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
<Text style={{ fontSize: 11, color: "#334155", flex: 1, marginRight: 6 }}>
İsim (Name): <Text style={{ fontWeight: "700", color: "#0F172A" }}>{txt.name}</Text>
</Text>
<Pressable
onPress={() => {
Clipboard.setStringAsync(txt.name);
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {});
Alert.alert("Kopyalandı! 📋", `TXT İsmi (${txt.name}) panoya kopyalandı.`);
}}
style={{ backgroundColor: "#0284C720", paddingHorizontal: 6, paddingVertical: 3, borderRadius: 4 }}
>
<Text style={{ fontSize: 10, fontWeight: "700", color: "#0284C7" }}>İsim Kopyala</Text>
</Pressable>
</View>
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
<Text numberOfLines={1} style={{ fontSize: 11, color: "#334155", flex: 1, marginRight: 6 }}>
Değer (Value): <Text style={{ fontWeight: "700", color: "#0F172A" }}>{txt.value}</Text>
</Text>
<Pressable
onPress={() => {
Clipboard.setStringAsync(txt.value);
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {});
Alert.alert("Kopyalandı! 📋", `TXT Değeri panoya kopyalandı.`);
}}
style={{ backgroundColor: "#0284C720", paddingHorizontal: 6, paddingVertical: 3, borderRadius: 4 }}
>
<Text style={{ fontSize: 10, fontWeight: "700", color: "#0284C7" }}>Değer Kopyala</Text>
</Pressable>
</View>
</View>
))}
</View>
);
})()}
</View>
) : null}
</View>