Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 61 additions & 27 deletions v0/src/components/Navbar/User/UserMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,8 @@

<v-list-item
class="list-item-avatar"
:prepend-avatar="authStore.getUserAvatar"
:prepend-icon="
authStore.getUserAvatar === 'default' ? 'mdi-account-circle-outline' : undefined
"
:prepend-avatar="avatarUrl"
:prepend-icon="avatarIcon"
:title="authStore.getUsername"
lines="two"
color="white"
Expand All @@ -48,19 +46,18 @@

<v-divider class="my-2 bg-white"></v-divider>

<!-- Authentication Section -->
<template v-if="!authStore.getIsLoggedIn">
<v-list density="compact" nav>
<v-list-item
@click.stop="showAuthModal(true)"
@click="showAuthModal(true)"
prepend-icon="mdi-login"
title="Sign In"
value="sign_in"
variant="text"
color="white"
></v-list-item>
<v-list-item
@click.stop="showAuthModal(false)"
@click="showAuthModal(false)"
prepend-icon="mdi-account-plus"
title="Register"
value="register"
Expand All @@ -70,27 +67,26 @@
</v-list>
</template>

<!-- User Menu Section -->
<template v-else>
<v-list density="compact" nav>
<v-list-item
@click.stop="dashboard"
@click="dashboard"
prepend-icon="mdi-view-dashboard-outline"
title="Dashboard"
value="dashboard"
variant="text"
color="white"
></v-list-item>
<v-list-item
@click.stop="my_groups"
@click="my_groups"
prepend-icon="mdi-account-group-outline"
title="My Groups"
value="my_groups"
variant="text"
color="white"
></v-list-item>
<v-list-item
@click.stop="notifications"
@click="notifications"
prepend-icon="mdi-bell-outline"
title="Notifications"
value="notifications"
Expand All @@ -106,7 +102,7 @@
<v-divider class="my-2 bg-white"></v-divider>

<v-list-item
@click.stop="signout"
@click="signout"
prepend-icon="mdi-logout"
title="Logout"
value="logout"
Expand All @@ -120,7 +116,7 @@
<v-btn
class="avatar-btn"
variant="text"
@click.stop="drawer = !drawer"
@click="drawer = !drawer"
rounded="xl"
color="white"
>
Expand All @@ -141,7 +137,6 @@
</v-main>
</v-layout>

<!-- Authentication Dialog -->
<v-dialog v-model="authModal" max-width="500" persistent>
<v-card class="auth-modal" style="background-color: white">
<v-toolbar color="#43b984">
Expand Down Expand Up @@ -214,7 +209,6 @@
</v-card>
</v-dialog>

<!-- Snackbar Notification -->
<v-snackbar
v-model="snackbar.visible"
:color="snackbar.color"
Expand All @@ -227,21 +221,20 @@
<v-btn
variant="text"
@click="snackbar.visible = false"
:icon="mdiClose"
color="white"
></v-btn>
>
<v-icon>mdi-close</v-icon>
</v-btn>
</template>
</v-snackbar>
</v-card>
</template>

<script lang="ts" setup>
import { ref } from 'vue'
import { ref, watch, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { availableLocale } from '#/locales/i18n'
import { useAuthStore } from '#/store/authStore'
import { mdiClose } from '@mdi/js'
// import { fetch } from '@tauri-apps/plugin-http' // Uncomment if using Tauri's HTTP plugin
import './User.scss'

const authStore = useAuthStore()
Expand All @@ -256,7 +249,42 @@ const isLoading = ref(false)
const authForm = ref()
const unreadCount = ref(0)

// Form validation rules
const avatarUrl = computed(() => {
const avatar = authStore.getUserAvatar
if (!avatar || avatar === 'default') {
return undefined
}
return avatar
})


const avatarIcon = computed(() => {
const avatar = authStore.getUserAvatar
if (!avatar || avatar === 'default') {
return 'mdi-account-circle-outline'
}
return undefined
})
// Watch for locale changes to update localStorage and document attributes
watch(locale, (newLocale) => {
try {
localStorage.setItem('locale', newLocale)
document.documentElement.lang = newLocale

// RTL support: Set text direction to right-to-left for Arabic
if (newLocale === 'ar') {
document.documentElement.dir = 'rtl'
console.log('RTL mode activated for Arabic')
} else {
document.documentElement.dir = 'ltr'
console.log('LTR mode activated for', newLocale)
}
} catch (e) {
console.error('Error saving locale:', e)
}
})
Comment on lines +268 to +285
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Locale watcher: apply on first render + avoid console logging + handle RTL more robustly.
As written, dir/lang may not be set on page load (e.g., refresh while already on Arabic) unless something else does it globally; and console.log/console.error will be noisy in production. Also consider ar-* locales.

Proposed fix
-watch(locale, (newLocale) => {
-  try {
-    localStorage.setItem('locale', newLocale)
-    document.documentElement.lang = newLocale
-    
-    // RTL support: Set text direction to right-to-left for Arabic
-    if (newLocale === 'ar') {
-      document.documentElement.dir = 'rtl'
-      console.log('RTL mode activated for Arabic')
-    } else {
-      document.documentElement.dir = 'ltr'
-      console.log('LTR mode activated for', newLocale)
-    }
-  } catch (e) {
-    console.error('Error saving locale:', e)
-  }
-})
+watch(
+  locale,
+  (newLocale) => {
+    // SSR / non-browser guard
+    if (typeof window === 'undefined') return
+    try {
+      window.localStorage.setItem('locale', newLocale)
+      document.documentElement.lang = newLocale
+
+      // RTL support (handle ar-* too)
+      document.documentElement.dir = newLocale === 'ar' || newLocale.startsWith('ar-') ? 'rtl' : 'ltr'
+    } catch {
+      // Intentionally swallow (private mode / storage blocked, etc.)
+    }
+  },
+  { immediate: true }
+)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Watch for locale changes to update localStorage and document attributes
watch(locale, (newLocale) => {
try {
localStorage.setItem('locale', newLocale)
document.documentElement.lang = newLocale
// RTL support: Set text direction to right-to-left for Arabic
if (newLocale === 'ar') {
document.documentElement.dir = 'rtl'
console.log('RTL mode activated for Arabic')
} else {
document.documentElement.dir = 'ltr'
console.log('LTR mode activated for', newLocale)
}
} catch (e) {
console.error('Error saving locale:', e)
}
})
// Watch for locale changes to update localStorage and document attributes
watch(
locale,
(newLocale) => {
// SSR / non-browser guard
if (typeof window === 'undefined') return
try {
window.localStorage.setItem('locale', newLocale)
document.documentElement.lang = newLocale
// RTL support (handle ar-* too)
document.documentElement.dir = newLocale === 'ar' || newLocale.startsWith('ar-') ? 'rtl' : 'ltr'
} catch {
// Intentionally swallow (private mode / storage blocked, etc.)
}
},
{ immediate: true }
)
🤖 Prompt for AI Agents
In @v0/src/components/Navbar/User/UserMenu.vue around lines 268 - 285, The
watcher on locale (watch(locale, ...)) only runs on changes so
dir/lang/localStorage may not be applied on initial render and it uses
console.log/console.error; update it to apply immediately and silently: extract
the logic into a helper (e.g., setLocaleAttributes) and call it once on
component mount/initialization and from the watch callback, set
document.documentElement.lang and document.documentElement.dir, treat RTL for
any locale starting with "ar" (e.g., newLocale.startsWith('ar')), persist locale
to localStorage inside a try/catch but avoid console logging (optionally use a
component logger or swallow the error), and remove console.log/console.error
calls to prevent noisy production output.


// Form validation rules - using arrow functions for better TypeScript support
const requiredRule = (v: string) => !!v || 'This field is required'
const emailRule = (v: string) => /.+@.+\..+/.test(v) || 'E-mail must be valid'
const passwordRule = (v: string) => v.length >= 6 || 'Password must be at least 6 characters'
Expand All @@ -268,6 +296,14 @@ const snackbar = ref({
color: '#43b984'
})

// Color mapping for different snackbar types
const snackbarColors: Record<'success' | 'error' | 'warning' | 'info', string> = {
success: '#43b984',
error: '#dc5656',
warning: '#f39c12',
info: '#3498db'
}

function showAuthModal(login: boolean) {
isLoginMode.value = login
authModal.value = true
Expand Down Expand Up @@ -312,13 +348,11 @@ async function handleAuthSubmit() {
} catch (e) {
errorData = { message: 'An error occurred' }
}
console.error('Authentication failed:', response.status, errorData)
handleLoginError(response.status, errorData)
return
}

const data = await response.json()
console.log('Auth successful:', data)

if (!data.token) {
throw new Error('No token received from server')
Expand All @@ -331,8 +365,8 @@ async function handleAuthSubmit() {
)
authModal.value = false
} catch (error) {
console.error('Authentication error:', error)
showSnackbar(`Authentication failed: ${error.message}`, 'error')
const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred'
showSnackbar(`Authentication failed: ${errorMessage}`, 'error')
} finally {
isLoading.value = false
}
Expand Down Expand Up @@ -361,7 +395,7 @@ function showSnackbar(message: string, type: 'success' | 'error' | 'warning' | '
snackbar.value = {
visible: true,
message,
color: type
color: snackbarColors[type]
}
}

Expand All @@ -381,4 +415,4 @@ function signout() {
authStore.signOut()
showSnackbar('You have been logged out', 'info')
}
</script>
</script>
167 changes: 167 additions & 0 deletions v0/src/locales/ar.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
{
"simulator": {
"save_online": "حفظ عبر الإنترنت",
"save_offline": "حفظ بدون اتصال",
"preview_circuit": "معاينة الدائرة",
"export_verilog": "تصدير Verilog",
"insert_subcircuit": "إدراج دائرة فرعية",
"undo": "تراجع",
"report_issue": "إبلاغ عن مشكلة",
"restricted_elements_used": "العناصر المقيدة المستخدمة:",
"made_with_circuitverse": "صُنع بواسطة CircuitVerse",
"layout": {
"layout_elements": "عناصر التخطيط",
"no_elements_available": "لا توجد عناصر تخطيط متاحة"
},
"unlabeled": "بدون تسمية",
"nav": {
"untitled_project": "مشروع بدون عنوان",
"sign_out": "تسجيل الخروج",
"sign_in": "تسجيل الدخول",
"user_dropdown": {
"dashboard": "لوحة القيادة",
"my_groups": "مجموعاتي",
"notifications": "التنبيهات"
},
"project": {
"heading": "المشروع",
"project_page": "صفحة المشروع",
"new_project": "مشروع جديد",
"open_offline": "فتح بدون اتصال",
"export_as_file": "تصدير كملف",
"import_project": "استيراد مشروع",
"save_online": "حفظ عبر الإنترنت",
"save_offline": "حفظ بدون اتصال",
"preview_circuit": "معاينة الدائرة",
"clear_project": "مسح المشروع",
"recover_project": "استعادة المشروع",
"view_previous_ui": "عرض الواجهة السابقة"
},
"circuit": {
"heading": "الدائرة",
"new_circuit": "دائرة جديدة +",
"insert_subcircuit": "إدراج دائرة فرعية",
"new_verilog_module_html": "وحدة Verilog جديدة"
},
"tools": {
"heading": "الأدوات",
"combinational_analysis_html": "التحليل التوافيقي",
"hex_bin_dec_converter_html": "محول الأنظمة العددية",
"download_image": "تحميل الصورة",
"themes": "السمات",
"export_verilog": "تصدير Verilog",
"custom_shortcut": "اختصارات مخصصة"
},
"help": {
"heading": "مساعدة",
"tutorial_guide": "دليل تعليمي",
"user_manual": "دليل المستخدم",
"learn_digital_logic": "تعلم المنطق الرقمي",
"discussion_forum": "منتدى النقاش"
}
},
"panel_header": {
"circuit_elements": "عناصر الدائرة",
"layout_elements": "عناصر التخطيط",
"timing_diagram": "المخطط الزمني",
"verilog_module": "وحدة Verilog",
"properties": "الخصائص",
"layout": "التخطيط",
"keybinding_preference": "تفضيلات المفاتيح",
"render_image": "رسم الصورة",
"select_theme": "اختر سمة",
"boolean_logic_table": "جدول المنطق البولياني",
"open_project": "فتح المشروع",
"bit_converter": "محول البت"
},
"panel_body": {
"circuit_elements": {
"search": "بحث...",
"search_result": "لم يتم العثور على عناصر...",
"expansion_panel_title": {
"Input": "الإدخال",
"Output": "الإخراج",
"Gates": "البوابات",
"Decoders & Plexers": "المشفرات والمجمعات",
"Sequential Elements": "العناصر التتابعية",
"Annotation": "ملاحظات",
"Misc": "متنوعات"
}
},
"timing_diagram": {
"one_cycle": "دورة واحدة =",
"units": "وحدات"
},
"verilog_module": {
"reset_code": "إعادة تعيين الكود",
"save_code": "حفظ الكود",
"module_in_experiment_notice": "هذه وحدة تجريبية. لن يتم حفظ الكود إلا عند النقر على زر حفظ الكود.",
"apply_themes": "تطبيق السمات",
"select_theme": "اختر سمة:"
},
"layout": {
"width": "العرض",
"height": "الارتفاع",
"reset_all_nodes": "إعادة تعيين كافة العقد:",
"title": "العنوان",
"title_enabled": "تمكين العنوان:",
"save": "حفظ",
"cancel": "إلغاء"
},
"render_image": {
"full_circuit_view": "عرض الدائرة كاملة",
"current_view": "العرض الحالي",
"transparent_background": "خلفية شفافة",
"resolution": "الدقة:"
},
"context_menu": {
"paste": "لصق",
"copy": "نسخ",
"cut": "قص",
"delete": "حذف",
"new_circuit": "دائرة جديدة",
"center_focus": "تركيز في المنتصف"
},
"bit_converter": {
"decimal_value": "قيمة عشرية",
"binary_value": "قيمة ثنائية",
"octal_value": "قيمة ثمانية",
"hexadecimal_value": "قيمة سداسية عشرية"
},
"custom_shortcut": {
"esc_cancel": "اضغط على مجموعة المفاتيح المطلوبة ثم اضغط Enter",
"command": "أمر",
"keymapping": "تعيين المفاتيح",
"reset_to_default": "إعادة للوضع الافتراضي",
"save": "حفظ"
},
"report_issue": {
"describe_issue": "صف مشكلتك:",
"email": "البريد الإلكتروني",
"optional": " [اختياري]",
"report_btn": "إبلاغ",
"cancel_btn": "إلغاء",
"close_btn": "إغلاق"
}
},
"tooltip": {
"delete_selected": "حذف المحدد",
"download_as_image": "تحميل كصورة",
"fit_to_screen": "ملاءمة الشاشة",
"redo": "إعادة",
"decrease_size": "تصغير الحجم",
"increase_size": "تكبير الحجم",
"decrease_height": "تقليل الارتفاع",
"increase_height": "زيادة الارتفاع",
"reset_timing_diagram": "إعادة تعيين المخطط الزمني",
"autocalibrate_cycle_units": "معايرة تلقائية لوحدات الدورة",
"zoom_in": "تكبير",
"zoom_out": "تصغير",
"resume_timing_diagram": "استئناف المخطط الزمني",
"pause_timing_diagram": "إيقاف المخطط الزمني مؤقتاً",
"decrease_width": "تقليل العرض",
"increase_width": "زيادة العرض",
"reset": "إعادة تعيين"
}
}
}
Loading