feat: render CNAME and Cloudflare SSL TXT verification instructions with copy buttons in mobile app domain settings
This commit is contained in:
@@ -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: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: "#E7E5E4",
|
||||
gap: 10,
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontSize: 12, fontWeight: "800", color: "#1C1917" }}>
|
||||
📌 DNS & SSL Doğrulama Talimatları
|
||||
</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,
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontSize: 11, fontWeight: "700", color: "#44403C" }}>
|
||||
DNS Kaydı Talimatı:
|
||||
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<Text style={{ fontSize: 11, fontWeight: "800", color: "#C8A96B" }}>
|
||||
1. CNAME Yönlendirme Kaydı (Zorunlu)
|
||||
</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>
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user