bugfix: site renamed. Name component created.

This commit is contained in:
2026-08-19 02:41:05 +03:30
parent 80167c622c
commit e5c39c6b7e
12 changed files with 76 additions and 31 deletions

View File

@@ -1,6 +1,6 @@
{ {
"common": { "common": {
"appName": "DyoLink", "appName": "Nudentic",
"loading": "Loading...", "loading": "Loading...",
"loadingApp": "Loading app...", "loadingApp": "Loading app...",
"loadingWorkspace": "Loading workspace...", "loadingWorkspace": "Loading workspace...",
@@ -168,7 +168,7 @@
"featureRealtimeDescription": "Get instant notifications on case status changes.", "featureRealtimeDescription": "Get instant notifications on case status changes.",
"featureSecurityTitle": "Secure & Compliant", "featureSecurityTitle": "Secure & Compliant",
"featureSecurityDescription": "HIPAA-compliant with enterprise-grade security.", "featureSecurityDescription": "HIPAA-compliant with enterprise-grade security.",
"footerCopyright": "© 2026 DyoLink. All rights reserved.", "footerCopyright": "© 2026 Nudentic. All rights reserved.",
"termsAndConditions": "Terms & Conditions" "termsAndConditions": "Terms & Conditions"
}, },
"accountMenu": { "accountMenu": {
@@ -854,7 +854,7 @@
}, },
"organizations": { "organizations": {
"loadingOrganization": "Loading organization...", "loadingOrganization": "Loading organization...",
"subtitle": "Search organizations, send connection requests to existing accounts, or invitation links when they are not on DyoLink yet.", "subtitle": "Search organizations, send connection requests to existing accounts, or invitation links when they are not on Nudentic yet.",
"invitationHistory": "Invitation History", "invitationHistory": "Invitation History",
"searchPlaceholder": "Search {counterpart} by name, email, or phone...", "searchPlaceholder": "Search {counterpart} by name, email, or phone...",
"backToList": "Back to list", "backToList": "Back to list",
@@ -948,7 +948,7 @@
"passwordChanged": "Password updated. Please sign in again.", "passwordChanged": "Password updated. Please sign in again.",
"passwordChangeFailed": "Could not update password. Please try again.", "passwordChangeFailed": "Could not update password. Please try again.",
"subscriptionsTitle": "Subscriptions", "subscriptionsTitle": "Subscriptions",
"subscriptionsSubtitle": "Your DyoLink workspace plan and seats for {orgName}. Clinic and lab income tracking stays under the sidebar Billing tab.", "subscriptionsSubtitle": "Your Nudentic workspace plan and seats for {orgName}. Clinic and lab income tracking stays under the sidebar Billing tab.",
"noSubscriptionNotice": "This organization has no active subscription. Select a plan below to start the purchase process.", "noSubscriptionNotice": "This organization has no active subscription. Select a plan below to start the purchase process.",
"currentPlan": "Current plan", "currentPlan": "Current plan",
"planPrice": "Plan price", "planPrice": "Plan price",

View File

@@ -1,6 +1,6 @@
{ {
"common": { "common": {
"appName": "DyoLink", "appName": "Nudentic",
"loading": "در حال بارگذاری...", "loading": "در حال بارگذاری...",
"loadingApp": "در حال بارگذاری برنامه...", "loadingApp": "در حال بارگذاری برنامه...",
"loadingWorkspace": "در حال بارگذاری فضای کاری...", "loadingWorkspace": "در حال بارگذاری فضای کاری...",
@@ -168,7 +168,7 @@
"featureRealtimeDescription": "اعلان‌های فوری درباره تغییر وضعیت پرونده‌ها دریافت کنید.", "featureRealtimeDescription": "اعلان‌های فوری درباره تغییر وضعیت پرونده‌ها دریافت کنید.",
"featureSecurityTitle": "امن و منطبق با استانداردها", "featureSecurityTitle": "امن و منطبق با استانداردها",
"featureSecurityDescription": "منطبق با HIPAA و دارای امنیت در سطح سازمانی.", "featureSecurityDescription": "منطبق با HIPAA و دارای امنیت در سطح سازمانی.",
"footerCopyright": "© ۲۰۲۶ DyoLink. تمامی حقوق محفوظ است.", "footerCopyright": "© ۲۰۲۶ Nudentic. تمامی حقوق محفوظ است.",
"termsAndConditions": "شرایط و ضوابط" "termsAndConditions": "شرایط و ضوابط"
}, },
"accountMenu": { "accountMenu": {
@@ -855,7 +855,7 @@
}, },
"organizations": { "organizations": {
"loadingOrganization": "در حال بارگذاری سازمان...", "loadingOrganization": "در حال بارگذاری سازمان...",
"subtitle": "سازمان‌ها را جستجو کنید، درخواست اتصال به حساب‌های موجود ارسال کنید، یا اگر هنوز در DyoLink نیستند، لینک دعوت ارسال کنید.", "subtitle": "سازمان‌ها را جستجو کنید، درخواست اتصال به حساب‌های موجود ارسال کنید، یا اگر هنوز در Nudentic نیستند، لینک دعوت ارسال کنید.",
"invitationHistory": "تاریخچه دعوت‌نامه‌ها", "invitationHistory": "تاریخچه دعوت‌نامه‌ها",
"searchPlaceholder": "جستجوی {counterpart} بر اساس نام، ایمیل یا تلفن...", "searchPlaceholder": "جستجوی {counterpart} بر اساس نام، ایمیل یا تلفن...",
"backToList": "بازگشت به لیست", "backToList": "بازگشت به لیست",
@@ -949,7 +949,7 @@
"passwordChanged": "رمز عبور به‌روزرسانی شد. لطفاً دوباره وارد شوید.", "passwordChanged": "رمز عبور به‌روزرسانی شد. لطفاً دوباره وارد شوید.",
"passwordChangeFailed": "به‌روزرسانی رمز عبور انجام نشد. دوباره تلاش کنید.", "passwordChangeFailed": "به‌روزرسانی رمز عبور انجام نشد. دوباره تلاش کنید.",
"subscriptionsTitle": "اشتراک‌ها", "subscriptionsTitle": "اشتراک‌ها",
"subscriptionsSubtitle": "طرح و مجوزهای فضای کاری DyoLink شما برای {orgName}. پیگیری درآمد کلینیک و لابراتوار در برگه صورتحساب در نوار کناری قرار دارد.", "subscriptionsSubtitle": "طرح و مجوزهای فضای کاری Nudentic شما برای {orgName}. پیگیری درآمد کلینیک و لابراتوار در برگه صورتحساب در نوار کناری قرار دارد.",
"noSubscriptionNotice": "این سازمان اشتراک فعالی ندارد. برای شروع فرآیند خرید، یک طرح زیر را انتخاب کنید.", "noSubscriptionNotice": "این سازمان اشتراک فعالی ندارد. برای شروع فرآیند خرید، یک طرح زیر را انتخاب کنید.",
"currentPlan": "طرح فعلی", "currentPlan": "طرح فعلی",
"planPrice": "قیمت طرح", "planPrice": "قیمت طرح",

View File

@@ -1,6 +1,6 @@
{ {
"common": { "common": {
"appName": "DyoLink", "appName": "Nudentic",
"loading": "Laden...", "loading": "Laden...",
"loadingApp": "Applicatie laden...", "loadingApp": "Applicatie laden...",
"loadingWorkspace": "Werkruimte laden...", "loadingWorkspace": "Werkruimte laden...",
@@ -168,7 +168,7 @@
"featureRealtimeDescription": "Ontvang direct meldingen bij statuswijzigingen van casussen.", "featureRealtimeDescription": "Ontvang direct meldingen bij statuswijzigingen van casussen.",
"featureSecurityTitle": "Veilig & Conform", "featureSecurityTitle": "Veilig & Conform",
"featureSecurityDescription": "HIPAA-conform met beveiliging op ondernemingsniveau.", "featureSecurityDescription": "HIPAA-conform met beveiliging op ondernemingsniveau.",
"footerCopyright": "© 2026 DyoLink. Alle rechten voorbehouden.", "footerCopyright": "© 2026 Nudentic. Alle rechten voorbehouden.",
"termsAndConditions": "Algemene Voorwaarden" "termsAndConditions": "Algemene Voorwaarden"
}, },
"accountMenu": { "accountMenu": {
@@ -854,7 +854,7 @@
}, },
"organizations": { "organizations": {
"loadingOrganization": "Organisatie laden...", "loadingOrganization": "Organisatie laden...",
"subtitle": "Zoek organisaties, stuur verbindingsverzoeken naar bestaande accounts, of uitnodigingslinks wanneer ze nog niet op DyoLink staan.", "subtitle": "Zoek organisaties, stuur verbindingsverzoeken naar bestaande accounts, of uitnodigingslinks wanneer ze nog niet op Nudentic staan.",
"invitationHistory": "Uitnodigingsgeschiedenis", "invitationHistory": "Uitnodigingsgeschiedenis",
"searchPlaceholder": "Zoek {counterpart} op naam, e-mail of telefoon...", "searchPlaceholder": "Zoek {counterpart} op naam, e-mail of telefoon...",
"backToList": "Terug naar lijst", "backToList": "Terug naar lijst",
@@ -948,7 +948,7 @@
"passwordChanged": "Wachtwoord bijgewerkt. Log opnieuw in.", "passwordChanged": "Wachtwoord bijgewerkt. Log opnieuw in.",
"passwordChangeFailed": "Wachtwoord kon niet worden bijgewerkt. Probeer het opnieuw.", "passwordChangeFailed": "Wachtwoord kon niet worden bijgewerkt. Probeer het opnieuw.",
"subscriptionsTitle": "Abonnementen", "subscriptionsTitle": "Abonnementen",
"subscriptionsSubtitle": "Uw DyoLink-werkruimteplan en plaatsen voor {orgName}. Kliniek- en laboratoriuminkomsten blijven onder het tabblad Facturatie in de zijbalk.", "subscriptionsSubtitle": "Uw Nudentic-werkruimteplan en plaatsen voor {orgName}. Kliniek- en laboratoriuminkomsten blijven onder het tabblad Facturatie in de zijbalk.",
"noSubscriptionNotice": "Deze organisatie heeft geen actief abonnement. Selecteer hieronder een abonnement om het aankoopproces te starten.", "noSubscriptionNotice": "Deze organisatie heeft geen actief abonnement. Selecteer hieronder een abonnement om het aankoopproces te starten.",
"currentPlan": "Huidig abonnement", "currentPlan": "Huidig abonnement",
"planPrice": "Abonnementsprijs", "planPrice": "Abonnementsprijs",

View File

@@ -10,6 +10,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod'; import * as z from 'zod';
import { Lock, Mail, User } from 'lucide-react'; import { Lock, Mail, User } from 'lucide-react';
import { AuthPageShell } from '@/components/ui/auth/AuthPageShell'; import { AuthPageShell } from '@/components/ui/auth/AuthPageShell';
import { BrandWordmark } from '@/components/ui/shared/BrandWordmark';
import { Button } from '@/components/ui/shared/Button'; import { Button } from '@/components/ui/shared/Button';
import { Input } from '@/components/ui/shared/Input'; import { Input } from '@/components/ui/shared/Input';
import { OrganizationDetailsFields } from '@/components/ui/auth/OrganizationDetailsFields'; import { OrganizationDetailsFields } from '@/components/ui/auth/OrganizationDetailsFields';
@@ -179,9 +180,7 @@ function AcceptOrganizationInviteContent() {
header={ header={
<> <>
<Link href="/" className="flex justify-center"> <Link href="/" className="flex justify-center">
<span className="text-2xl sm:text-3xl font-semibold text-text-primary"> <BrandWordmark className="text-2xl sm:text-3xl font-semibold text-text-primary" />
{tCommon('appName')}
</span>
</Link> </Link>
<h2 className="mt-4 sm:mt-6 text-center text-2xl sm:text-3xl font-semibold text-text-primary"> <h2 className="mt-4 sm:mt-6 text-center text-2xl sm:text-3xl font-semibold text-text-primary">
{t('acceptOrganizationTitle')} {t('acceptOrganizationTitle')}

View File

@@ -11,6 +11,7 @@ import { authApi } from '@/lib/api/auth';
import { getUserFacingError } from '@/components/shared/formatApiError'; import { getUserFacingError } from '@/components/shared/formatApiError';
import { useAuth } from '@/lib/hooks/useAuth'; import { useAuth } from '@/lib/hooks/useAuth';
import { AuthPageShell } from '@/components/ui/auth/AuthPageShell'; import { AuthPageShell } from '@/components/ui/auth/AuthPageShell';
import { BrandWordmark } from '@/components/ui/shared/BrandWordmark';
import { Button } from '@/components/ui/shared/Button'; import { Button } from '@/components/ui/shared/Button';
import { Input } from '@/components/ui/shared/Input'; import { Input } from '@/components/ui/shared/Input';
@@ -28,7 +29,6 @@ function normalizeIranMobile(input: string): string {
export default function ForgotPasswordPage() { export default function ForgotPasswordPage() {
const t = useTranslations('auth'); const t = useTranslations('auth');
const tCommon = useTranslations('common');
const tValidation = useTranslations('validation'); const tValidation = useTranslations('validation');
const tErrors = useTranslations('errors'); const tErrors = useTranslations('errors');
const router = useRouter(); const router = useRouter();
@@ -128,9 +128,7 @@ export default function ForgotPasswordPage() {
header={ header={
<> <>
<Link href="/" className="flex justify-center"> <Link href="/" className="flex justify-center">
<span className="text-2xl sm:text-3xl font-semibold text-text-primary"> <BrandWordmark className="text-2xl sm:text-3xl font-semibold text-text-primary" />
{tCommon('appName')}
</span>
</Link> </Link>
<h2 className="mt-4 sm:mt-6 text-center text-2xl sm:text-3xl font-semibold text-text-primary"> <h2 className="mt-4 sm:mt-6 text-center text-2xl sm:text-3xl font-semibold text-text-primary">
{t('forgotPasswordTitle')} {t('forgotPasswordTitle')}

View File

@@ -12,6 +12,7 @@ import { getUserFacingError } from '@/components/shared/formatApiError';
import { useAuth } from '@/lib/hooks/useAuth'; import { useAuth } from '@/lib/hooks/useAuth';
import { getRememberedEmail } from '@/lib/auth/rememberMe'; import { getRememberedEmail } from '@/lib/auth/rememberMe';
import { AuthPageShell } from '@/components/ui/auth/AuthPageShell'; import { AuthPageShell } from '@/components/ui/auth/AuthPageShell';
import { BrandWordmark } from '@/components/ui/shared/BrandWordmark';
import { Button } from '@/components/ui/shared/Button'; import { Button } from '@/components/ui/shared/Button';
import { Checkbox } from '@/components/ui/shared/Checkbox'; import { Checkbox } from '@/components/ui/shared/Checkbox';
import { Input } from '@/components/ui/shared/Input'; import { Input } from '@/components/ui/shared/Input';
@@ -91,9 +92,7 @@ function LoginPageContent() {
header={ header={
<> <>
<Link href="/" className="flex justify-center"> <Link href="/" className="flex justify-center">
<span className="text-2xl sm:text-3xl font-semibold text-text-primary"> <BrandWordmark className="text-2xl sm:text-3xl font-semibold text-text-primary" />
{tCommon('appName')}
</span>
</Link> </Link>
<h2 className="mt-4 sm:mt-6 text-center text-2xl sm:text-3xl font-semibold text-text-primary"> <h2 className="mt-4 sm:mt-6 text-center text-2xl sm:text-3xl font-semibold text-text-primary">
{t('signInTitle')} {t('signInTitle')}

View File

@@ -5,20 +5,20 @@ import { Link } from '@/i18n/navigation';
import { useAuth } from '@/lib/hooks/useAuth'; import { useAuth } from '@/lib/hooks/useAuth';
import { Button } from '@/components/ui/shared/Button'; import { Button } from '@/components/ui/shared/Button';
import { TopBarControls } from '@/components/ui/shared/TopBarControls'; import { TopBarControls } from '@/components/ui/shared/TopBarControls';
import { BrandWordmark } from '@/components/ui/shared/BrandWordmark';
import { Building2, Beaker, Calendar, Shield, Clock, Users } from 'lucide-react'; import { Building2, Beaker, Calendar, Shield, Clock, Users } from 'lucide-react';
export default function HomePage() { export default function HomePage() {
const t = useTranslations('landing'); const t = useTranslations('landing');
const tAuth = useTranslations('auth'); const tAuth = useTranslations('auth');
const tCommon = useTranslations('common');
const { user, isAuthReady } = useAuth(); const { user, isAuthReady } = useAuth();
return ( return (
<div className="min-h-[100dvh] flex flex-col app-web-bg text-text-primary"> <div className="min-h-[100dvh] flex flex-col app-web-bg text-text-primary">
<header className="border-b border-border/70 bg-background-secondary/65 backdrop-blur-sm fixed top-0 w-full z-10"> <header className="border-b border-border/70 bg-background-secondary/65 backdrop-blur-sm fixed top-0 w-full z-10">
<div className="container mx-auto px-4 py-3 sm:py-4 flex flex-wrap items-center justify-between gap-x-4 gap-y-3"> <div className="container mx-auto px-4 py-3 sm:py-4 flex flex-wrap items-center justify-between gap-x-4 gap-y-3">
<Link href="/" className="text-xl sm:text-2xl font-semibold text-text-primary shrink-0"> <Link href="/" className="shrink-0">
{tCommon('appName')} <BrandWordmark className="text-xl sm:text-2xl font-semibold text-text-primary" />
</Link> </Link>
<div className="flex items-center gap-1.5 sm:gap-3 ml-auto shrink-0"> <div className="flex items-center gap-1.5 sm:gap-3 ml-auto shrink-0">

View File

@@ -11,6 +11,7 @@ import { getUserFacingError } from '@/components/shared/formatApiError';
import { useAuth } from '@/lib/hooks/useAuth'; import { useAuth } from '@/lib/hooks/useAuth';
import { useEnterAppWhenAuthenticated } from '@/lib/hooks/useEnterAppWhenAuthenticated'; import { useEnterAppWhenAuthenticated } from '@/lib/hooks/useEnterAppWhenAuthenticated';
import { AuthPageShell } from '@/components/ui/auth/AuthPageShell'; import { AuthPageShell } from '@/components/ui/auth/AuthPageShell';
import { BrandWordmark } from '@/components/ui/shared/BrandWordmark';
import { OrganizationDetailsFields } from '@/components/ui/auth/OrganizationDetailsFields'; import { OrganizationDetailsFields } from '@/components/ui/auth/OrganizationDetailsFields';
import { RegistrationProgressSteps } from '@/components/ui/auth/RegistrationProgressSteps'; import { RegistrationProgressSteps } from '@/components/ui/auth/RegistrationProgressSteps';
import { Button } from '@/components/ui/shared/Button'; import { Button } from '@/components/ui/shared/Button';
@@ -136,9 +137,7 @@ export default function RegisterPage() {
header={ header={
<> <>
<Link href="/" className="flex justify-center"> <Link href="/" className="flex justify-center">
<span className="text-2xl sm:text-3xl font-semibold text-text-primary"> <BrandWordmark className="text-2xl sm:text-3xl font-semibold text-text-primary" />
{tCommon('appName')}
</span>
</Link> </Link>
<h2 className="mt-4 sm:mt-6 text-center text-2xl sm:text-3xl font-semibold text-text-primary"> <h2 className="mt-4 sm:mt-6 text-center text-2xl sm:text-3xl font-semibold text-text-primary">
{t('registerTitle')} {t('registerTitle')}

View File

@@ -13,7 +13,7 @@ import { routing, isRtlLocale, localeHtmlLang } from '@/i18n/routing';
import { LocaleSync } from '@/components/i18n/LocaleSync'; import { LocaleSync } from '@/components/i18n/LocaleSync';
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'DyoLink - Dental Clinic & Lab Communication Hub', title: 'Nudentic - Dental Clinic & Lab Communication Hub',
description: 'Connect dental clinics and laboratories seamlessly', description: 'Connect dental clinics and laboratories seamlessly',
}; };

View File

@@ -12,6 +12,7 @@ import { formatToothList } from '@/components/treatment/prosthesisTypeDisplay';
import type { LabCaseDetail } from '@/types/cases'; import type { LabCaseDetail } from '@/types/cases';
import type { ProsthesisCatalogEntry } from '@/types/treatment-catalog'; import type { ProsthesisCatalogEntry } from '@/types/treatment-catalog';
import type { FdiToothId } from '@/types/treatment'; import type { FdiToothId } from '@/types/treatment';
import { BrandWordmark, BRAND_PRIMARY_HEX } from '@/components/ui/shared/BrandWordmark';
export type CaseSheetLabels = { export type CaseSheetLabels = {
title: string; title: string;
@@ -89,7 +90,9 @@ export function CaseSheetPrintLayout({
}} }}
> >
<div style={{ minWidth: 0, flex: 1 }}> <div style={{ minWidth: 0, flex: 1 }}>
<p style={{ ...muted, margin: 0 }}>Dyolink</p> <p style={{ ...muted, margin: 0, textTransform: 'none', letterSpacing: '0.02em' }}>
<BrandWordmark dotColor={BRAND_PRIMARY_HEX} />
</p>
<h1 style={{ margin: '4px 0 0', fontSize: 24, fontWeight: 600 }}>{labels.title}</h1> <h1 style={{ margin: '4px 0 0', fontSize: 24, fontWeight: 600 }}>{labels.title}</h1>
</div> </div>
{labCase.shareUrl ? ( {labCase.shareUrl ? (

View File

@@ -0,0 +1,44 @@
import type { CSSProperties } from 'react';
export const BRAND_NAME = 'Nudentic';
/** Light-theme primary — use in print/PDF where theme tokens are unsafe. */
export const BRAND_PRIMARY_HEX = '#009cae';
type BrandWordmarkProps = {
className?: string;
style?: CSSProperties;
/**
* i-dot color. Omit to use the theme primary token.
* Pass a hex (e.g. `BRAND_PRIMARY_HEX`) for print/PDF capture.
*/
dotColor?: string;
};
function BrandI({ fill }: { fill: string }) {
return (
<svg
aria-hidden
viewBox="0 0 24 80"
className="inline-block h-[0.78em] w-[0.26em] overflow-visible align-baseline ms-[0.05em] me-[0.04em]"
>
<circle cx="12" cy="10" r="8" fill={fill} />
<rect x="6" y="28" width="12" height="52" rx="1.2" fill="currentColor" />
</svg>
);
}
export function BrandWordmark({ className, style, dotColor }: BrandWordmarkProps) {
const tittleFill = dotColor ?? 'var(--color-primary)';
return (
<span dir="ltr" className={className} style={style}>
<span className="sr-only">{BRAND_NAME}</span>
<span aria-hidden>
Nudent
<BrandI fill={tittleFill} />
c
</span>
</span>
);
}

View File

@@ -21,6 +21,7 @@ import { usePendingConnectionsCount } from '@/lib/hooks/usePendingConnectionsCou
import { useTabBadgeCounts } from '@/lib/hooks/useTabBadgeCounts'; import { useTabBadgeCounts } from '@/lib/hooks/useTabBadgeCounts';
import { badgeCountForPath } from '@/lib/tabBadgeUtils'; import { badgeCountForPath } from '@/lib/tabBadgeUtils';
import { NavBadgePill } from '@/components/ui/shared/NavBadgePill'; import { NavBadgePill } from '@/components/ui/shared/NavBadgePill';
import { BrandWordmark } from '@/components/ui/shared/BrandWordmark';
import { import {
canViewAppointmentsTab, canViewAppointmentsTab,
canViewCases, canViewCases,
@@ -110,7 +111,9 @@ function Sidebar({ mobileOpen = false, onClose }: SidebarProps) {
}`} }`}
> >
<div className="h-[71px] px-4 flex items-center justify-between gap-2"> <div className="h-[71px] px-4 flex items-center justify-between gap-2">
<h1 className="text-lg font-medium tracking-tight truncate">{tCommon('appName')}</h1> <h1 className="min-w-0 truncate">
<BrandWordmark className="text-lg font-medium tracking-tight" />
</h1>
{onClose ? ( {onClose ? (
<button <button
type="button" type="button"