From b2c0f4502ff693c93b30d89443b7c8315fb7941f Mon Sep 17 00:00:00 2001 From: itsmeakhil Date: Fri, 15 May 2026 00:19:07 +0530 Subject: [PATCH 1/4] UI --- apps/backend/app/api/routes/auth/api.py | 19 + apps/backend/app/api/routes/auth/schema.py | 1 + apps/backend/app/api/routes/auth/services.py | 1 + .../backend/app/api/routes/auth/users_repo.py | 14 +- .../app/dashboard/dashboard-client-layout.tsx | 2 + apps/web/src/components/onboarding-gate.tsx | 39 + apps/web/src/components/onboarding-modal.tsx | 693 ++++++++++++++++++ apps/web/src/lib/onboarding-api.ts | 19 + 8 files changed, 787 insertions(+), 1 deletion(-) create mode 100644 apps/web/src/components/onboarding-gate.tsx create mode 100644 apps/web/src/components/onboarding-modal.tsx create mode 100644 apps/web/src/lib/onboarding-api.ts diff --git a/apps/backend/app/api/routes/auth/api.py b/apps/backend/app/api/routes/auth/api.py index c3e859d4..36833375 100644 --- a/apps/backend/app/api/routes/auth/api.py +++ b/apps/backend/app/api/routes/auth/api.py @@ -28,6 +28,7 @@ ) from app.api.routes.auth.users_repo import ( clear_refresh_token_hash, + complete_onboarding, find_uid_by_refresh_hash, get_backup_code_by_id, get_master_vault, @@ -79,6 +80,7 @@ async def create_session(request: Request, payload: SessionRequest, response: Re photo_url=doc.get("photo_url"), email_verified=bool(doc.get("email_verified")), disabled=bool(doc.get("disabled")), + onboarding_completed=bool(doc.get("onboarding_completed", False)), ) @@ -359,3 +361,20 @@ async def use_backup_code_endpoint( ) -> OkResponse: await mark_backup_code_used(uid, payload.codeId) return OkResponse(ok=True) + + +# ── Onboarding ──────────────────────────────────────────────────────────────── + + +@router.post( + "/onboarding/complete", + response_model=OkResponse, + summary="Mark onboarding as completed for the current user", +) +@limiter.limit("10/minute") +async def complete_onboarding_endpoint( + request: Request, + uid: Annotated[str, Depends(get_current_uid)], +) -> OkResponse: + await complete_onboarding(uid) + return OkResponse(ok=True) diff --git a/apps/backend/app/api/routes/auth/schema.py b/apps/backend/app/api/routes/auth/schema.py index 8a1acb44..4aaad465 100644 --- a/apps/backend/app/api/routes/auth/schema.py +++ b/apps/backend/app/api/routes/auth/schema.py @@ -108,6 +108,7 @@ class UserProfileResponse(BaseModel): certifications: list[Certification] = Field(default_factory=list) portfolio_settings: PortfolioSettings | None = None personal_info: PersonalInfo | None = None + onboarding_completed: bool = False class UpdateProfileRequest(BaseModel): diff --git a/apps/backend/app/api/routes/auth/services.py b/apps/backend/app/api/routes/auth/services.py index 7f4e9850..2417735d 100644 --- a/apps/backend/app/api/routes/auth/services.py +++ b/apps/backend/app/api/routes/auth/services.py @@ -90,4 +90,5 @@ async def get_current_user(uid: str = Depends(get_current_uid)) -> UserProfileRe certifications=[Certification(**c) for c in doc.get("certifications") or []], portfolio_settings=doc.get("portfolio_settings"), personal_info=PersonalInfo(**doc["personal_info"]) if doc.get("personal_info") else None, + onboarding_completed=bool(doc.get("onboarding_completed", False)), ) diff --git a/apps/backend/app/api/routes/auth/users_repo.py b/apps/backend/app/api/routes/auth/users_repo.py index 0dc574ae..cd2ce955 100644 --- a/apps/backend/app/api/routes/auth/users_repo.py +++ b/apps/backend/app/api/routes/auth/users_repo.py @@ -22,7 +22,12 @@ async def upsert_user_from_firebase_claims(decoded: dict[str, Any]) -> None: "disabled": False, "updated_at": now, } - await db_manager.update_one(USERS, {"_id": uid}, {"$set": doc, "$setOnInsert": {"created_at": now}}, upsert=True) + await db_manager.update_one( + USERS, + {"_id": uid}, + {"$set": doc, "$setOnInsert": {"created_at": now, "onboarding_completed": False}}, + upsert=True, + ) async def get_user_doc(uid: str) -> dict[str, Any] | None: @@ -98,6 +103,13 @@ async def get_backup_code_by_id(uid: str, code_id: str) -> dict[str, Any] | None return None +async def complete_onboarding(uid: str) -> None: + now = create_timestamp() + await db_manager.update_one( + USERS, {"_id": uid}, {"$set": {"onboarding_completed": True, "updated_at": now}} + ) + + async def mark_backup_code_used(uid: str, code_id: str) -> None: now = create_timestamp() await db_manager.update_one( diff --git a/apps/web/src/app/dashboard/dashboard-client-layout.tsx b/apps/web/src/app/dashboard/dashboard-client-layout.tsx index 2d7df3a8..0f7708a2 100644 --- a/apps/web/src/app/dashboard/dashboard-client-layout.tsx +++ b/apps/web/src/app/dashboard/dashboard-client-layout.tsx @@ -3,10 +3,12 @@ import React from 'react'; import { ClientLayout } from '../../components/sidebar/client-layout'; import { RequireAuth } from '@/components/require-auth'; import { MasterPasswordGate } from '@/components/master-password-gate'; +import { OnboardingGate } from '@/components/onboarding-gate'; export default function DashboardClientLayout({ children }: { children: React.ReactNode }) { return ( + {children} diff --git a/apps/web/src/components/onboarding-gate.tsx b/apps/web/src/components/onboarding-gate.tsx new file mode 100644 index 00000000..dbaf6af8 --- /dev/null +++ b/apps/web/src/components/onboarding-gate.tsx @@ -0,0 +1,39 @@ +"use client" + +import { useEffect, useState } from "react" +import useAuth from "@/utils/useAuth" +import { getMe } from "@/lib/onboarding-api" +import { OnboardingModal } from "@/components/onboarding-modal" + +type State = "loading" | "show" | "done" + +export function OnboardingGate() { + const { user, loading: authLoading } = useAuth(false) + const [state, setState] = useState("loading") + + useEffect(() => { + if (authLoading || !user) return + let cancelled = false + + const check = async () => { + try { + const profile = await getMe() + if (!cancelled) { + setState(profile.onboarding_completed ? "done" : "show") + } + } catch { + // If we can't fetch, don't block the user — skip onboarding + if (!cancelled) setState("done") + } + } + + void check() + return () => { + cancelled = true + } + }, [user, authLoading]) + + if (state !== "show") return null + + return setState("done")} /> +} diff --git a/apps/web/src/components/onboarding-modal.tsx b/apps/web/src/components/onboarding-modal.tsx new file mode 100644 index 00000000..26d8c1ff --- /dev/null +++ b/apps/web/src/components/onboarding-modal.tsx @@ -0,0 +1,693 @@ +"use client" + +import React, { useState, useCallback } from "react" +import { motion, AnimatePresence } from "framer-motion" +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { Badge } from "@/components/ui/badge" +import { Progress } from "@/components/ui/progress" +import { ScrollArea } from "@/components/ui/scroll-area" +import { completeOnboarding } from "@/lib/onboarding-api" +import { useToolVisibilityStore, DEFAULT_ENABLED_TOOLS } from "@/store/tool-visibility-store" +import { patchUserPreferences } from "@/lib/user-preferences-api" +import { + IconRocket, + IconShield, + IconBraces, + IconArrowsExchange, + IconWand, + IconNetwork, + IconServer, + IconPalette, + IconDeviceGamepad2, + IconCheck, + IconArrowRight, + IconLayout, + IconCommand, + IconPin, + IconX, + IconChevronDown as IconChevron, +} from "@tabler/icons-react" +import { sidebarData } from "@/components/sidebar/data/sidebar-data" +import type { NavLink, NavCollapsible } from "@/components/sidebar/types" + +// ── Tool category icons by title ────────────────────────────────────────────── + +const CATEGORY_ICONS: Record = { + Productivity: IconRocket, + Security: IconShield, + Formatters: IconBraces, + Converters: IconArrowsExchange, + Generators: IconWand, + "Network & API": IconNetwork, + Database: IconServer, + "Media & Design": IconPalette, + "Break Room": IconDeviceGamepad2, +} + +const CATEGORY_COLORS: Record = { + Productivity: "from-violet-500/20 to-violet-600/10 border-violet-500/30", + Security: "from-green-500/20 to-green-600/10 border-green-500/30", + Formatters: "from-blue-500/20 to-blue-600/10 border-blue-500/30", + Converters: "from-cyan-500/20 to-cyan-600/10 border-cyan-500/30", + Generators: "from-amber-500/20 to-amber-600/10 border-amber-500/30", + "Network & API": "from-orange-500/20 to-orange-600/10 border-orange-500/30", + Database: "from-red-500/20 to-red-600/10 border-red-500/30", + "Media & Design": "from-pink-500/20 to-pink-600/10 border-pink-500/30", + "Break Room": "from-emerald-500/20 to-emerald-600/10 border-emerald-500/30", +} + +const CATEGORY_ICON_COLORS: Record = { + Productivity: "text-violet-400", + Security: "text-green-400", + Formatters: "text-blue-400", + Converters: "text-cyan-400", + Generators: "text-amber-400", + "Network & API": "text-orange-400", + Database: "text-red-400", + "Media & Design": "text-pink-400", + "Break Room": "text-emerald-400", +} + +// ── Tour slides ─────────────────────────────────────────────────────────────── + +const TOUR_SLIDES = [ + { + icon: IconLayout, + iconColor: "text-violet-400", + bg: "from-violet-500/10 to-transparent", + title: "Your workspace, your way", + description: + "Every tool lives in the sidebar. Open multiple tools in tabs — they stay open until you close them. Your layout is restored on every visit.", + }, + { + icon: IconCommand, + iconColor: "text-blue-400", + bg: "from-blue-500/10 to-transparent", + title: "Command palette", + description: + "Press ⌘K (or Ctrl+K) to instantly search and launch any tool, jump to settings, or switch tabs — without touching the mouse.", + }, + { + icon: IconPin, + iconColor: "text-amber-400", + bg: "from-amber-500/10 to-transparent", + title: "Pin your favourites", + description: + "Right-click any tool in the sidebar to pin it to the top. Pinned tools are synced across all your devices.", + }, +] + +// ── Step indicators ─────────────────────────────────────────────────────────── + +function StepDot({ active, done }: { active: boolean; done: boolean }) { + return ( +
+ ) +} + +// ── Step 0: Welcome ─────────────────────────────────────────────────────────── + +function WelcomeStep() { + return ( +
+ +
+ +
+
+ +
+
+ + +

Welcome to mydevtools

+

+ Your all-in-one developer toolkit. Let's take 30 seconds to personalise your sidebar and + show you the highlights. +

+
+ + + {["60+ tools", "Synced across devices", "Works offline", "Privacy-first"].map((tag) => ( + + {tag} + + ))} + +
+ ) +} + +// ── Step 1: Tool selection ──────────────────────────────────────────────────── + +/** Flip this to switch between selection UIs without touching anything else. */ +const TOOL_SELECTION_MODE: "tags" | "checkboxes" = "tags" + +interface ToolEntry { + title: string + url: string + icon?: React.ElementType +} + +interface Category { + title: string + tools: ToolEntry[] +} + +function buildCategories(): Category[] { + return sidebarData.navGroups.map((g) => ({ + title: g.title, + tools: g.items.flatMap((item) => { + if (!("items" in item)) { + const link = item as NavLink + return [{ title: link.title, url: String(link.url), icon: link.icon }] + } + return (item as NavCollapsible).items.map((s) => ({ + title: s.title, + url: String(s.url), + icon: s.icon, + })) + }), + })) +} + +interface SelectionProps { + selectedUrls: Set + onToggleTool: (url: string) => void + onToggleCategory: (urls: string[], selected: boolean) => void +} + +// ── Tags UI ─────────────────────────────────────────────────────────────────── + +function TagsToolSelection({ selectedUrls, onToggleTool, onToggleCategory }: SelectionProps) { + const categories = React.useMemo(() => buildCategories(), []) + const allUrls = React.useMemo(() => categories.flatMap((c) => c.tools.map((t) => t.url)), [categories]) + + return ( +
+
+

Pick your tools

+

+ Tap any tool to toggle it. Change anytime in Settings. +

+
+ + +
+ {categories.map((cat) => { + const urls = cat.tools.map((t) => t.url) + const selectedCount = urls.filter((u) => selectedUrls.has(u)).length + const allSelected = selectedCount === urls.length + const Icon = CATEGORY_ICONS[cat.title] ?? IconWand + const iconColor = CATEGORY_ICON_COLORS[cat.title] ?? "text-primary" + + return ( +
+ {/* Category header */} +
+ + + {cat.title} + +
+ +
+ + {/* Tool tags */} +
+ {cat.tools.map((tool) => { + const selected = selectedUrls.has(tool.url) + const ToolIcon = tool.icon + return ( + onToggleTool(tool.url)} + whileTap={{ scale: 0.94 }} + className={cn( + "inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full border text-xs font-medium transition-all duration-150", + selected + ? "bg-primary text-primary-foreground border-primary shadow-sm shadow-primary/20" + : "bg-muted/40 text-muted-foreground border-border hover:border-primary/40 hover:text-foreground hover:bg-muted/70" + )} + > + {ToolIcon && } + {tool.title} + + ) + })} +
+
+ ) + })} +
+ + +
+

+ {selectedUrls.size} tools enabled +

+
+ + · + +
+
+
+ ) +} + +// ── Checkboxes UI ───────────────────────────────────────────────────────────── + +function CheckboxCategoryRow({ + cat, + selectedUrls, + onToggleTool, + onToggleCategory, +}: SelectionProps & { cat: Category }) { + const [expanded, setExpanded] = useState(false) + const urls = cat.tools.map((t) => t.url) + const selectedCount = urls.filter((u) => selectedUrls.has(u)).length + const allSelected = selectedCount === urls.length + const someSelected = selectedCount > 0 && selectedCount < urls.length + const Icon = CATEGORY_ICONS[cat.title] ?? IconWand + const iconColor = CATEGORY_ICON_COLORS[cat.title] ?? "text-primary" + const color = CATEGORY_COLORS[cat.title] ?? "from-primary/20 to-primary/10 border-primary/30" + + return ( +
+
+ + + +
+ + + {expanded && ( + +
+ {cat.tools.map((tool) => { + const ToolIcon = tool.icon + const checked = selectedUrls.has(tool.url) + return ( + + ) + })} +
+
+ )} +
+
+ ) +} + +function CheckboxesToolSelection({ selectedUrls, onToggleTool, onToggleCategory }: SelectionProps) { + const categories = React.useMemo(() => buildCategories(), []) + const allUrls = React.useMemo(() => categories.flatMap((c) => c.tools.map((t) => t.url)), [categories]) + + return ( +
+
+

Pick your tools

+

+ Toggle categories or expand to select individual tools. Change anytime in Settings. +

+
+ + +
+ {categories.map((cat) => ( + + ))} +
+
+ +
+

+ {selectedUrls.size} tools enabled +

+
+ + · + +
+
+
+ ) +} + +// ── Dispatcher ──────────────────────────────────────────────────────────────── + +function ToolSelectionStep(props: SelectionProps) { + if (TOOL_SELECTION_MODE === "checkboxes") return + return +} + +// ── Step 2: Tour ────────────────────────────────────────────────────────────── + +function TourStep({ slideIndex, onSlide }: { slideIndex: number; onSlide: (i: number) => void }) { + const slide = TOUR_SLIDES[slideIndex]! + + return ( +
+
+

Quick tour

+

A few things that will save you time.

+
+ +
+ + +
+ +
+
+

{slide.title}

+

{slide.description}

+
+
+
+
+ + {/* slide dots */} +
+ {TOUR_SLIDES.map((_, i) => ( +
+
+ ) +} + +// ── Root modal ──────────────────────────────────────────────────────────────── + +const STEPS = ["Welcome", "Choose tools", "Tour"] + +interface OnboardingModalProps { + onComplete: () => void +} + +export function OnboardingModal({ onComplete }: OnboardingModalProps) { + const [step, setStep] = useState(0) + const [tourSlide, setTourSlide] = useState(0) + const [completing, setCompleting] = useState(false) + + const setEnabledTools = useToolVisibilityStore((s) => s.setEnabledTools) + + // Pre-select DEFAULT_ENABLED_TOOLS. New tools added to sidebarData always appear + // in the picker (via buildCategories) but are unselected until added to DEFAULT_ENABLED_TOOLS. + const [selectedUrls, setSelectedUrls] = useState>( + () => new Set(DEFAULT_ENABLED_TOOLS) + ) + + const handleToggleTool = useCallback((url: string) => { + setSelectedUrls((prev) => { + const next = new Set(prev) + if (next.has(url)) next.delete(url) + else next.add(url) + return next + }) + }, []) + + const handleToggleCategory = useCallback((urls: string[], add: boolean) => { + setSelectedUrls((prev) => { + const next = new Set(prev) + if (add) { + urls.forEach((u) => next.add(u)) + } else { + if (urls.length === 0) { + next.clear() + } else { + urls.forEach((u) => next.delete(u)) + } + } + return next + }) + }, []) + + const handleNext = async () => { + if (step < STEPS.length - 1) { + if (step === 1) { + // tour step — advance slide first + if (tourSlide < TOUR_SLIDES.length - 1) { + setTourSlide((s) => s + 1) + return + } + } + setStep((s) => s + 1) + setTourSlide(0) + return + } + + // Final step complete + setCompleting(true) + try { + const tools = Array.from(selectedUrls) + setEnabledTools(tools) + await Promise.all([ + completeOnboarding(), + patchUserPreferences({ enabledTools: tools }), + ]) + } catch { + // non-blocking — user can still proceed + } finally { + setCompleting(false) + onComplete() + } + } + + const handleBack = () => { + if (step === 2 && tourSlide > 0) { + setTourSlide((s) => s - 1) + return + } + if (step > 0) setStep((s) => s - 1) + } + + const progress = ((step + (step === 2 ? tourSlide / TOUR_SLIDES.length : 0)) / (STEPS.length - 1)) * 100 + + const isLastAction = + step === STEPS.length - 1 && tourSlide === TOUR_SLIDES.length - 1 + + const nextLabel = isLastAction + ? completing + ? "Saving…" + : "Get started" + : step === 2 + ? "Next tip" + : "Continue" + + return ( +
+ + {/* Header bar */} +
+
+ {STEPS.map((label, i) => ( + + + {label} + + {i < STEPS.length - 1 && ( + + )} + + ))} +
+ + {step + 1} / {STEPS.length} + +
+ + {/* Progress bar */} + + + {/* Step content */} +
+ + {step === 0 && ( + + + + )} + {step === 1 && ( + + + + )} + {step === 2 && ( + + + + )} + +
+ + {/* Footer actions */} +
+ + + +
+
+
+ ) +} diff --git a/apps/web/src/lib/onboarding-api.ts b/apps/web/src/lib/onboarding-api.ts new file mode 100644 index 00000000..d90ee5b6 --- /dev/null +++ b/apps/web/src/lib/onboarding-api.ts @@ -0,0 +1,19 @@ +import { backendFetch } from "@/lib/backend-auth" + +export interface UserProfileOut { + uid: string + onboarding_completed: boolean +} + +export async function getMe(): Promise { + const res = await backendFetch("/api/backend/auth/me") + if (!res.ok) throw new Error("Failed to fetch user profile") + return res.json() as Promise +} + +export async function completeOnboarding(): Promise { + const res = await backendFetch("/api/backend/auth/onboarding/complete", { + method: "POST", + }) + if (!res.ok) throw new Error("Failed to complete onboarding") +} From 4e0fb9de34483b49838b20e988f2e8b3c5ae119d Mon Sep 17 00:00:00 2001 From: itsmeakhil Date: Fri, 15 May 2026 00:47:15 +0530 Subject: [PATCH 2/4] ui --- apps/web/messages/af.json | 15 +++++++++++++++ apps/web/messages/ar.json | 15 +++++++++++++++ apps/web/messages/ca.json | 15 +++++++++++++++ apps/web/messages/cs.json | 15 +++++++++++++++ apps/web/messages/da.json | 15 +++++++++++++++ apps/web/messages/de.json | 15 +++++++++++++++ apps/web/messages/el.json | 15 +++++++++++++++ apps/web/messages/en.json | 1 + apps/web/messages/es.json | 15 +++++++++++++++ apps/web/messages/fa.json | 15 +++++++++++++++ apps/web/messages/fr.json | 15 +++++++++++++++ apps/web/messages/id.json | 15 +++++++++++++++ apps/web/messages/it.json | 15 +++++++++++++++ apps/web/messages/ja.json | 15 +++++++++++++++ apps/web/messages/ko.json | 15 +++++++++++++++ apps/web/messages/ms.json | 15 +++++++++++++++ apps/web/messages/nb.json | 15 +++++++++++++++ apps/web/messages/nl.json | 15 +++++++++++++++ apps/web/messages/pl.json | 15 +++++++++++++++ apps/web/messages/pt-BR.json | 15 +++++++++++++++ apps/web/messages/pt.json | 15 +++++++++++++++ apps/web/messages/ru.json | 15 +++++++++++++++ apps/web/messages/sv.json | 15 +++++++++++++++ apps/web/messages/tr.json | 15 +++++++++++++++ apps/web/messages/uk.json | 15 +++++++++++++++ apps/web/messages/vi.json | 15 +++++++++++++++ apps/web/messages/zh.json | 15 +++++++++++++++ 27 files changed, 391 insertions(+) diff --git a/apps/web/messages/af.json b/apps/web/messages/af.json index 6323c61c..69edabd4 100644 --- a/apps/web/messages/af.json +++ b/apps/web/messages/af.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "Kodesnippette", "qrCodeGenerator": "QR-kodegenerator", + "dnsLookup": "DNS-soektog", "ipSubnetCalculator": "IP-/subnetrekenaar", "hashGenerator": "Hash-generator", "hmacGenerator": "HMAC-generator", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "Sleutelgenerering het misluk" } + }, + "DnsLookup": { + "title": "DNS-soektog", + "subtitle": "Navraag DNS-rekords vir enige domein — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Voer domein in (bv. github.com)", + "lookupBtn": "Soek", + "looking": "Soek tans…", + "refreshBtn": "Verfris", + "recordTypesLabel": "Rekordtipes", + "allBtn": "Alles", + "tryLabel": "Probeer:", + "errors": { + "lookupFailed": "DNS-soektog het misluk" + } } } diff --git a/apps/web/messages/ar.json b/apps/web/messages/ar.json index 4e39a39c..5294cb16 100644 --- a/apps/web/messages/ar.json +++ b/apps/web/messages/ar.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "مقاطع الشيفرة", "qrCodeGenerator": "مُولّد رمز QR", + "dnsLookup": "بحث DNS", "ipSubnetCalculator": "حاسبة IP / الشبكة الفرعية", "hashGenerator": "مُولّد الهاش", "hmacGenerator": "مُولّد HMAC", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "فشل إنشاء المفتاح" } + }, + "DnsLookup": { + "title": "بحث DNS", + "subtitle": "استعلام عن سجلات DNS لأي نطاق — A، AAAA، MX، TXT، NS، CNAME.", + "placeholder": "أدخل النطاق (مثل github.com)", + "lookupBtn": "بحث", + "looking": "جارٍ البحث…", + "refreshBtn": "تحديث", + "recordTypesLabel": "أنواع السجلات", + "allBtn": "الكل", + "tryLabel": "جرّب:", + "errors": { + "lookupFailed": "فشل بحث DNS" + } } } diff --git a/apps/web/messages/ca.json b/apps/web/messages/ca.json index 4b0feb86..a6864153 100644 --- a/apps/web/messages/ca.json +++ b/apps/web/messages/ca.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "Fragments de codi", "qrCodeGenerator": "Generador de codis QR", + "dnsLookup": "Cerca DNS", "ipSubnetCalculator": "Calculadora IP / subxarxa", "hashGenerator": "Generador de hash", "hmacGenerator": "Generador HMAC", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "La generació de la clau ha fallat" } + }, + "DnsLookup": { + "title": "Cerca DNS", + "subtitle": "Consulta els registres DNS de qualsevol domini — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Introdueix un domini (p. ex. github.com)", + "lookupBtn": "Cerca", + "looking": "Cercant…", + "refreshBtn": "Actualitza", + "recordTypesLabel": "Tipus de registres", + "allBtn": "Tots", + "tryLabel": "Prova:", + "errors": { + "lookupFailed": "Ha fallat la consulta DNS" + } } } diff --git a/apps/web/messages/cs.json b/apps/web/messages/cs.json index d7a6b0dd..be6385eb 100644 --- a/apps/web/messages/cs.json +++ b/apps/web/messages/cs.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "Úryvky kódu", "qrCodeGenerator": "Generátor QR kódů", + "dnsLookup": "Vyhledávání DNS", "ipSubnetCalculator": "Kalkulačka IP / podsítě", "hashGenerator": "Generátor hashů", "hmacGenerator": "Generátor HMAC", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "Generování klíče selhalo" } + }, + "DnsLookup": { + "title": "Vyhledávání DNS", + "subtitle": "Dotazujte DNS záznamy pro libovolnou doménu — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Zadejte doménu (např. github.com)", + "lookupBtn": "Vyhledat", + "looking": "Vyhledávám…", + "refreshBtn": "Obnovit", + "recordTypesLabel": "Typy záznamů", + "allBtn": "Vše", + "tryLabel": "Zkusit:", + "errors": { + "lookupFailed": "Vyhledávání DNS selhalo" + } } } diff --git a/apps/web/messages/da.json b/apps/web/messages/da.json index c2fc4f3e..1de13405 100644 --- a/apps/web/messages/da.json +++ b/apps/web/messages/da.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "Kodestykker", "qrCodeGenerator": "QR-kodegenerator", + "dnsLookup": "DNS-opslag", "ipSubnetCalculator": "IP-/subnetberegner", "hashGenerator": "Hash-generator", "hmacGenerator": "HMAC-generator", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "Nøglegenerering mislykkedes" } + }, + "DnsLookup": { + "title": "DNS-opslag", + "subtitle": "Forespørg DNS-poster for ethvert domæne — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Indtast domæne (f.eks. github.com)", + "lookupBtn": "Slå op", + "looking": "Slår op…", + "refreshBtn": "Opdater", + "recordTypesLabel": "Posttyper", + "allBtn": "Alle", + "tryLabel": "Prøv:", + "errors": { + "lookupFailed": "DNS-opslag mislykkedes" + } } } diff --git a/apps/web/messages/de.json b/apps/web/messages/de.json index a14b5b30..29198c38 100644 --- a/apps/web/messages/de.json +++ b/apps/web/messages/de.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "Code-Snippets", "qrCodeGenerator": "QR-Code-Generator", + "dnsLookup": "DNS-Abfrage", "ipSubnetCalculator": "IP-/Subnetz-Rechner", "hashGenerator": "Hash-Generator", "hmacGenerator": "HMAC-Generator", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "Schlüsselgenerierung fehlgeschlagen" } + }, + "DnsLookup": { + "title": "DNS-Abfrage", + "subtitle": "DNS-Einträge für jede Domain abfragen — A, AAAA, MX, TXT, NS, CNAME, SOA, CAA.", + "placeholder": "Domain eingeben (z. B. github.com)", + "lookupBtn": "Abfragen", + "looking": "Abfrage läuft…", + "refreshBtn": "Aktualisieren", + "recordTypesLabel": "Eintragstypen", + "allBtn": "Alle", + "tryLabel": "Beispiele:", + "errors": { + "lookupFailed": "DNS-Abfrage fehlgeschlagen" + } } } diff --git a/apps/web/messages/el.json b/apps/web/messages/el.json index a3df6afb..672ba20f 100644 --- a/apps/web/messages/el.json +++ b/apps/web/messages/el.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "Αποσπάσματα κώδικα", "qrCodeGenerator": "Δημιουργός κωδικών QR", + "dnsLookup": "Αναζήτηση DNS", "ipSubnetCalculator": "Υπολογιστής IP / υποδικτύου", "hashGenerator": "Γεννήτρια hash", "hmacGenerator": "Γεννήτρια HMAC", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "Η δημιουργία κλειδιού απέτυχε" } + }, + "DnsLookup": { + "title": "Αναζήτηση DNS", + "subtitle": "Αναζητήστε εγγραφές DNS για οποιοδήποτε τομέα — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Εισαγάγετε τομέα (π.χ. github.com)", + "lookupBtn": "Αναζήτηση", + "looking": "Γίνεται αναζήτηση…", + "refreshBtn": "Ανανέωση", + "recordTypesLabel": "Τύποι εγγραφών", + "allBtn": "Όλα", + "tryLabel": "Δοκιμάστε:", + "errors": { + "lookupFailed": "Η αναζήτηση DNS απέτυχε" + } } } diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 6d020192..a20da9cd 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -27,6 +27,7 @@ "uuidGenerator": "UUID / ULID", "secretApiKeyGenerator": "Secret / API key", "qrCodeGenerator": "QR Code Generator", + "dnsLookup": "DNS Lookup", "ipSubnetCalculator": "IP / Subnet Calculator", "hashGenerator": "Hash Generator", "hmacGenerator": "HMAC Generator", diff --git a/apps/web/messages/es.json b/apps/web/messages/es.json index 3b99bfda..23264195 100644 --- a/apps/web/messages/es.json +++ b/apps/web/messages/es.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "Fragmentos de código", "qrCodeGenerator": "Generador de códigos QR", + "dnsLookup": "Búsqueda DNS", "ipSubnetCalculator": "Calculadora IP / subred", "hashGenerator": "Generador de hash", "hmacGenerator": "Generador HMAC", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "Error al generar la clave" } + }, + "DnsLookup": { + "title": "Búsqueda DNS", + "subtitle": "Consulta registros DNS de cualquier dominio — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Introduce un dominio (p. ej. github.com)", + "lookupBtn": "Consultar", + "looking": "Consultando…", + "refreshBtn": "Actualizar", + "recordTypesLabel": "Tipos de registro", + "allBtn": "Todos", + "tryLabel": "Probar:", + "errors": { + "lookupFailed": "Error en la consulta DNS" + } } } diff --git a/apps/web/messages/fa.json b/apps/web/messages/fa.json index 4e28ce9f..9f7e0c76 100644 --- a/apps/web/messages/fa.json +++ b/apps/web/messages/fa.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "قطعه‌کدها", "qrCodeGenerator": "تولیدکنندهٔ QR", + "dnsLookup": "جستجوی DNS", "ipSubnetCalculator": "ماشین‌حساب IP / زیرشبکه", "hashGenerator": "تولیدکنندهٔ هش", "hmacGenerator": "تولیدکنندهٔ HMAC", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "تولید کلید ناموفق بود" } + }, + "DnsLookup": { + "title": "جستجوی DNS", + "subtitle": "رکوردهای DNS هر دامنه را جستجو کنید — A، AAAA، MX، TXT، NS، CNAME.", + "placeholder": "دامنه را وارد کنید (مثلاً github.com)", + "lookupBtn": "جستجو", + "looking": "در حال جستجو…", + "refreshBtn": "تازه‌سازی", + "recordTypesLabel": "انواع رکورد", + "allBtn": "همه", + "tryLabel": "امتحان کنید:", + "errors": { + "lookupFailed": "جستجوی DNS ناموفق بود" + } } } diff --git a/apps/web/messages/fr.json b/apps/web/messages/fr.json index bb4e736b..e50852c8 100644 --- a/apps/web/messages/fr.json +++ b/apps/web/messages/fr.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "Extraits de code", "qrCodeGenerator": "Générateur de codes QR", + "dnsLookup": "Recherche DNS", "ipSubnetCalculator": "Calculateur IP / sous-réseau", "hashGenerator": "Générateur de hash", "hmacGenerator": "Générateur HMAC", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "Échec de la génération de la clé" } + }, + "DnsLookup": { + "title": "Recherche DNS", + "subtitle": "Interrogez les enregistrements DNS d'un domaine — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Saisissez un domaine (p. ex. github.com)", + "lookupBtn": "Rechercher", + "looking": "Recherche en cours…", + "refreshBtn": "Actualiser", + "recordTypesLabel": "Types d'enregistrement", + "allBtn": "Tous", + "tryLabel": "Essayer :", + "errors": { + "lookupFailed": "Échec de la recherche DNS" + } } } diff --git a/apps/web/messages/id.json b/apps/web/messages/id.json index 84fe18fb..a9202ec7 100644 --- a/apps/web/messages/id.json +++ b/apps/web/messages/id.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "Cuplikan kode", "qrCodeGenerator": "Generator kode QR", + "dnsLookup": "Pencarian DNS", "ipSubnetCalculator": "Kalkulator IP / subnet", "hashGenerator": "Generator hash", "hmacGenerator": "Generator HMAC", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "Pembuatan kunci gagal" } + }, + "DnsLookup": { + "title": "Pencarian DNS", + "subtitle": "Kueri rekaman DNS untuk domain apa pun — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Masukkan domain (mis. github.com)", + "lookupBtn": "Cari", + "looking": "Mencari…", + "refreshBtn": "Segarkan", + "recordTypesLabel": "Jenis rekaman", + "allBtn": "Semua", + "tryLabel": "Coba:", + "errors": { + "lookupFailed": "Pencarian DNS gagal" + } } } diff --git a/apps/web/messages/it.json b/apps/web/messages/it.json index 65110e24..7adb2b0c 100644 --- a/apps/web/messages/it.json +++ b/apps/web/messages/it.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "Snippet di codice", "qrCodeGenerator": "Generatore di codici QR", + "dnsLookup": "Ricerca DNS", "ipSubnetCalculator": "Calcolatrice IP / subnet", "hashGenerator": "Generatore di hash", "hmacGenerator": "Generatore HMAC", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "Generazione della chiave fallita" } + }, + "DnsLookup": { + "title": "Ricerca DNS", + "subtitle": "Interroga i record DNS per qualsiasi dominio — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Inserisci un dominio (es. github.com)", + "lookupBtn": "Cerca", + "looking": "Ricerca in corso…", + "refreshBtn": "Aggiorna", + "recordTypesLabel": "Tipi di record", + "allBtn": "Tutti", + "tryLabel": "Prova:", + "errors": { + "lookupFailed": "Ricerca DNS non riuscita" + } } } diff --git a/apps/web/messages/ja.json b/apps/web/messages/ja.json index 5ca7b19c..3c88e664 100644 --- a/apps/web/messages/ja.json +++ b/apps/web/messages/ja.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "コードスニペット", "qrCodeGenerator": "QRコード生成", + "dnsLookup": "DNS ルックアップ", "ipSubnetCalculator": "IP/サブネット計算", "hashGenerator": "ハッシュ生成", "hmacGenerator": "HMACジェネレーター", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "鍵の生成に失敗しました" } + }, + "DnsLookup": { + "title": "DNS ルックアップ", + "subtitle": "任意のドメインの DNS レコードを照会 — A、AAAA、MX、TXT、NS、CNAME。", + "placeholder": "ドメインを入力(例: github.com)", + "lookupBtn": "検索", + "looking": "検索中…", + "refreshBtn": "更新", + "recordTypesLabel": "レコード種別", + "allBtn": "すべて", + "tryLabel": "試す:", + "errors": { + "lookupFailed": "DNS ルックアップに失敗しました" + } } } diff --git a/apps/web/messages/ko.json b/apps/web/messages/ko.json index d6aee355..5188a619 100644 --- a/apps/web/messages/ko.json +++ b/apps/web/messages/ko.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "코드 스니펫", "qrCodeGenerator": "QR 코드 생성기", + "dnsLookup": "DNS 조회", "ipSubnetCalculator": "IP / 서브넷 계산기", "hashGenerator": "해시 생성기", "hmacGenerator": "HMAC 생성기", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "키 생성 실패" } + }, + "DnsLookup": { + "title": "DNS 조회", + "subtitle": "도메인의 DNS 레코드를 조회합니다 — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "도메인 입력 (예: github.com)", + "lookupBtn": "조회", + "looking": "조회 중…", + "refreshBtn": "새로고침", + "recordTypesLabel": "레코드 유형", + "allBtn": "전체", + "tryLabel": "시도:", + "errors": { + "lookupFailed": "DNS 조회에 실패했습니다" + } } } diff --git a/apps/web/messages/ms.json b/apps/web/messages/ms.json index 5ad4958b..a85361e6 100644 --- a/apps/web/messages/ms.json +++ b/apps/web/messages/ms.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "Petikan kod", "qrCodeGenerator": "Penjana kod QR", + "dnsLookup": "Carian DNS", "ipSubnetCalculator": "Kalkulator IP / subnet", "hashGenerator": "Penjana hash", "hmacGenerator": "Penjana HMAC", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "Penjanaan kekunci gagal" } + }, + "DnsLookup": { + "title": "Carian DNS", + "subtitle": "Pertanyaan rekod DNS untuk mana-mana domain — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Masukkan domain (cth. github.com)", + "lookupBtn": "Cari", + "looking": "Mencari…", + "refreshBtn": "Muat semula", + "recordTypesLabel": "Jenis rekod", + "allBtn": "Semua", + "tryLabel": "Cuba:", + "errors": { + "lookupFailed": "Carian DNS gagal" + } } } diff --git a/apps/web/messages/nb.json b/apps/web/messages/nb.json index dc479517..339600c7 100644 --- a/apps/web/messages/nb.json +++ b/apps/web/messages/nb.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "Kodeutdrag", "qrCodeGenerator": "QR-kodegenerator", + "dnsLookup": "DNS-oppslag", "ipSubnetCalculator": "IP-/subnettkalkulator", "hashGenerator": "Hash-generator", "hmacGenerator": "HMAC-generator", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "Nøkkelgenerering mislyktes" } + }, + "DnsLookup": { + "title": "DNS-oppslag", + "subtitle": "Spør etter DNS-poster for et hvilket som helst domene — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Skriv inn domene (f.eks. github.com)", + "lookupBtn": "Oppslag", + "looking": "Oppslag pågår…", + "refreshBtn": "Oppdater", + "recordTypesLabel": "Posttyper", + "allBtn": "Alle", + "tryLabel": "Prøv:", + "errors": { + "lookupFailed": "DNS-oppslag mislyktes" + } } } diff --git a/apps/web/messages/nl.json b/apps/web/messages/nl.json index 312e0d53..43aa434c 100644 --- a/apps/web/messages/nl.json +++ b/apps/web/messages/nl.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "Codefragmenten", "qrCodeGenerator": "QR-codegenerator", + "dnsLookup": "DNS-opzoeking", "ipSubnetCalculator": "IP-/subnetcalculator", "hashGenerator": "Hash-generator", "hmacGenerator": "HMAC-generator", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "Sleutelgeneratie mislukt" } + }, + "DnsLookup": { + "title": "DNS-opzoeking", + "subtitle": "Zoek DNS-records op voor elk domein — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Voer een domein in (bijv. github.com)", + "lookupBtn": "Opzoeken", + "looking": "Bezig met opzoeken…", + "refreshBtn": "Vernieuwen", + "recordTypesLabel": "Recordtypen", + "allBtn": "Alles", + "tryLabel": "Probeer:", + "errors": { + "lookupFailed": "DNS-opzoeking mislukt" + } } } diff --git a/apps/web/messages/pl.json b/apps/web/messages/pl.json index 533835c9..f093ca5a 100644 --- a/apps/web/messages/pl.json +++ b/apps/web/messages/pl.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "Fragmenty kodu", "qrCodeGenerator": "Generator kodów QR", + "dnsLookup": "Wyszukiwanie DNS", "ipSubnetCalculator": "Kalkulator IP / podsieci", "hashGenerator": "Generator skrótów", "hmacGenerator": "Generator HMAC", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "Generowanie klucza nie powiodło się" } + }, + "DnsLookup": { + "title": "Wyszukiwanie DNS", + "subtitle": "Zapytaj rekordy DNS dla dowolnej domeny — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Wpisz domenę (np. github.com)", + "lookupBtn": "Szukaj", + "looking": "Wyszukiwanie…", + "refreshBtn": "Odśwież", + "recordTypesLabel": "Typy rekordów", + "allBtn": "Wszystkie", + "tryLabel": "Spróbuj:", + "errors": { + "lookupFailed": "Wyszukiwanie DNS nie powiodło się" + } } } diff --git a/apps/web/messages/pt-BR.json b/apps/web/messages/pt-BR.json index e2a114fb..e5c28ab3 100644 --- a/apps/web/messages/pt-BR.json +++ b/apps/web/messages/pt-BR.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "Trechos de código", "qrCodeGenerator": "Gerador de QR Code", + "dnsLookup": "Pesquisa DNS", "ipSubnetCalculator": "Calculadora IP / sub-rede", "hashGenerator": "Gerador de hash", "hmacGenerator": "Gerador HMAC", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "Falha ao gerar a chave" } + }, + "DnsLookup": { + "title": "Pesquisa DNS", + "subtitle": "Consulte registros DNS de qualquer domínio — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Digite um domínio (ex.: github.com)", + "lookupBtn": "Consultar", + "looking": "Consultando…", + "refreshBtn": "Atualizar", + "recordTypesLabel": "Tipos de registro", + "allBtn": "Todos", + "tryLabel": "Experimente:", + "errors": { + "lookupFailed": "A consulta DNS falhou" + } } } diff --git a/apps/web/messages/pt.json b/apps/web/messages/pt.json index b3ef6f62..d3f4da00 100644 --- a/apps/web/messages/pt.json +++ b/apps/web/messages/pt.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "Excertos de código", "qrCodeGenerator": "Gerador de códigos QR", + "dnsLookup": "Pesquisa DNS", "ipSubnetCalculator": "Calculadora IP / sub-rede", "hashGenerator": "Gerador de hash", "hmacGenerator": "Gerador HMAC", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "Falha ao gerar a chave" } + }, + "DnsLookup": { + "title": "Pesquisa DNS", + "subtitle": "Consulte registros DNS de qualquer domínio — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Introduza um domínio (ex.: github.com)", + "lookupBtn": "Consultar", + "looking": "A consultar…", + "refreshBtn": "Atualizar", + "recordTypesLabel": "Tipos de registo", + "allBtn": "Todos", + "tryLabel": "Experimente:", + "errors": { + "lookupFailed": "A consulta DNS falhou" + } } } diff --git a/apps/web/messages/ru.json b/apps/web/messages/ru.json index a21ddcd1..4b278ada 100644 --- a/apps/web/messages/ru.json +++ b/apps/web/messages/ru.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "Фрагменты кода", "qrCodeGenerator": "Генератор QR-кодов", + "dnsLookup": "DNS-запрос", "ipSubnetCalculator": "Калькулятор IP / подсети", "hashGenerator": "Генератор хешей", "hmacGenerator": "Генератор HMAC", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "Ошибка генерации ключа" } + }, + "DnsLookup": { + "title": "DNS-запрос", + "subtitle": "Запрашивайте DNS-записи для любого домена — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Введите домен (например github.com)", + "lookupBtn": "Запрос", + "looking": "Выполняется запрос…", + "refreshBtn": "Обновить", + "recordTypesLabel": "Типы записей", + "allBtn": "Все", + "tryLabel": "Попробовать:", + "errors": { + "lookupFailed": "Ошибка DNS-запроса" + } } } diff --git a/apps/web/messages/sv.json b/apps/web/messages/sv.json index f9facd7a..1b53bf3d 100644 --- a/apps/web/messages/sv.json +++ b/apps/web/messages/sv.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "Kodutdrag", "qrCodeGenerator": "QR-kodgenerator", + "dnsLookup": "DNS-sökning", "ipSubnetCalculator": "IP-/subnetkalkylator", "hashGenerator": "Hash-generator", "hmacGenerator": "HMAC-generator", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "Nyckelgenerering misslyckades" } + }, + "DnsLookup": { + "title": "DNS-sökning", + "subtitle": "Fråga DNS-poster för valfri domän — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Ange domän (t.ex. github.com)", + "lookupBtn": "Sök", + "looking": "Söker…", + "refreshBtn": "Uppdatera", + "recordTypesLabel": "Posttyper", + "allBtn": "Alla", + "tryLabel": "Prova:", + "errors": { + "lookupFailed": "DNS-sökningen misslyckades" + } } } diff --git a/apps/web/messages/tr.json b/apps/web/messages/tr.json index 6d4198ca..c1f39661 100644 --- a/apps/web/messages/tr.json +++ b/apps/web/messages/tr.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "Kod parçacıkları", "qrCodeGenerator": "QR kod oluşturucu", + "dnsLookup": "DNS Sorgulama", "ipSubnetCalculator": "IP / alt ağ hesaplayıcı", "hashGenerator": "Hash oluşturucu", "hmacGenerator": "HMAC Oluşturucu", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "Anahtar oluşturma başarısız" } + }, + "DnsLookup": { + "title": "DNS Sorgulama", + "subtitle": "Herhangi bir alan adı için DNS kayıtlarını sorgulayın — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Alan adını girin (ör. github.com)", + "lookupBtn": "Sorgula", + "looking": "Sorgulanıyor…", + "refreshBtn": "Yenile", + "recordTypesLabel": "Kayıt türleri", + "allBtn": "Tümü", + "tryLabel": "Dene:", + "errors": { + "lookupFailed": "DNS sorgusu başarısız" + } } } diff --git a/apps/web/messages/uk.json b/apps/web/messages/uk.json index 8b2da03b..6b62afe4 100644 --- a/apps/web/messages/uk.json +++ b/apps/web/messages/uk.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "Фрагменти коду", "qrCodeGenerator": "Генератор QR-кодів", + "dnsLookup": "DNS-запит", "ipSubnetCalculator": "Калькулятор IP / підмережі", "hashGenerator": "Генератор хешів", "hmacGenerator": "Генератор HMAC", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "Помилка генерації ключа" } + }, + "DnsLookup": { + "title": "DNS-запит", + "subtitle": "Запитуйте DNS-записи для будь-якого домену — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Введіть домен (наприклад github.com)", + "lookupBtn": "Запит", + "looking": "Виконується запит…", + "refreshBtn": "Оновити", + "recordTypesLabel": "Типи записів", + "allBtn": "Усі", + "tryLabel": "Спробувати:", + "errors": { + "lookupFailed": "Помилка DNS-запиту" + } } } diff --git a/apps/web/messages/vi.json b/apps/web/messages/vi.json index 0c3251d5..a969524e 100644 --- a/apps/web/messages/vi.json +++ b/apps/web/messages/vi.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "Đoạn mã", "qrCodeGenerator": "Trình tạo mã QR", + "dnsLookup": "Tra cứu DNS", "ipSubnetCalculator": "Máy tính IP / mạng con", "hashGenerator": "Trình tạo hash", "hmacGenerator": "Trình tạo HMAC", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "Tạo khóa thất bại" } + }, + "DnsLookup": { + "title": "Tra cứu DNS", + "subtitle": "Truy vấn bản ghi DNS cho bất kỳ tên miền nào — A, AAAA, MX, TXT, NS, CNAME.", + "placeholder": "Nhập tên miền (vd: github.com)", + "lookupBtn": "Tra cứu", + "looking": "Đang tra cứu…", + "refreshBtn": "Làm mới", + "recordTypesLabel": "Loại bản ghi", + "allBtn": "Tất cả", + "tryLabel": "Thử:", + "errors": { + "lookupFailed": "Tra cứu DNS thất bại" + } } } diff --git a/apps/web/messages/zh.json b/apps/web/messages/zh.json index 385f4833..ddfeed6d 100644 --- a/apps/web/messages/zh.json +++ b/apps/web/messages/zh.json @@ -37,6 +37,7 @@ "csvExcelJson": "CSV / Excel ↔ JSON", "snippetManager": "代码片段", "qrCodeGenerator": "二维码生成器", + "dnsLookup": "DNS 查询", "ipSubnetCalculator": "IP / 子网计算器", "hashGenerator": "哈希生成器", "hmacGenerator": "HMAC 生成器", @@ -3436,5 +3437,19 @@ "errors": { "generationFailed": "密钥生成失败" } + }, + "DnsLookup": { + "title": "DNS 查询", + "subtitle": "查询任意域名的 DNS 记录 — A、AAAA、MX、TXT、NS、CNAME、SOA。", + "placeholder": "输入域名(例如 github.com)", + "lookupBtn": "查询", + "looking": "正在查询…", + "refreshBtn": "刷新", + "recordTypesLabel": "记录类型", + "allBtn": "全部", + "tryLabel": "试试:", + "errors": { + "lookupFailed": "DNS 查询失败" + } } } From f26c1a6641801ac60d71b5c70b444e7c27655f6b Mon Sep 17 00:00:00 2001 From: itsmeakhil Date: Fri, 15 May 2026 00:51:28 +0530 Subject: [PATCH 3/4] Update onboarding-modal.tsx --- apps/web/src/components/onboarding-modal.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/web/src/components/onboarding-modal.tsx b/apps/web/src/components/onboarding-modal.tsx index 26d8c1ff..92989c50 100644 --- a/apps/web/src/components/onboarding-modal.tsx +++ b/apps/web/src/components/onboarding-modal.tsx @@ -8,7 +8,7 @@ import { Badge } from "@/components/ui/badge" import { Progress } from "@/components/ui/progress" import { ScrollArea } from "@/components/ui/scroll-area" import { completeOnboarding } from "@/lib/onboarding-api" -import { useToolVisibilityStore, DEFAULT_ENABLED_TOOLS } from "@/store/tool-visibility-store" +import { useToolVisibilityStore } from "@/store/tool-visibility-store" import { patchUserPreferences } from "@/lib/user-preferences-api" import { IconRocket, @@ -496,11 +496,10 @@ export function OnboardingModal({ onComplete }: OnboardingModalProps) { const setEnabledTools = useToolVisibilityStore((s) => s.setEnabledTools) - // Pre-select DEFAULT_ENABLED_TOOLS. New tools added to sidebarData always appear - // in the picker (via buildCategories) but are unselected until added to DEFAULT_ENABLED_TOOLS. - const [selectedUrls, setSelectedUrls] = useState>( - () => new Set(DEFAULT_ENABLED_TOOLS) - ) + const [selectedUrls, setSelectedUrls] = useState>(() => { + const productivity = buildCategories().find((c) => c.title === "Productivity") + return new Set(productivity?.tools.map((t) => t.url) ?? []) + }) const handleToggleTool = useCallback((url: string) => { setSelectedUrls((prev) => { From ed2d456fc049f495052e4a0acf43ce724229cce0 Mon Sep 17 00:00:00 2001 From: itsmeakhil Date: Fri, 15 May 2026 00:55:36 +0530 Subject: [PATCH 4/4] Update onboarding-modal.tsx --- apps/web/src/components/onboarding-modal.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/web/src/components/onboarding-modal.tsx b/apps/web/src/components/onboarding-modal.tsx index 92989c50..9adffb6b 100644 --- a/apps/web/src/components/onboarding-modal.tsx +++ b/apps/web/src/components/onboarding-modal.tsx @@ -527,14 +527,13 @@ export function OnboardingModal({ onComplete }: OnboardingModalProps) { }, []) const handleNext = async () => { + // Advance tour slides before completing + if (step === 2 && tourSlide < TOUR_SLIDES.length - 1) { + setTourSlide((s) => s + 1) + return + } + if (step < STEPS.length - 1) { - if (step === 1) { - // tour step — advance slide first - if (tourSlide < TOUR_SLIDES.length - 1) { - setTourSlide((s) => s + 1) - return - } - } setStep((s) => s + 1) setTourSlide(0) return