Skip to content
Closed
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
107 changes: 66 additions & 41 deletions v0/src/components/Navbar/User/UserMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
size="x-small"
icon
class="dialogClose"
@click="drawer = false"
v-on:click="drawer = false"
variant="text"
color="white"
>
Expand All @@ -23,18 +23,18 @@

<v-list-item
class="list-item-avatar"
:prepend-avatar="authStore.getUserAvatar"
:prepend-icon="
v-bind:prepend-avatar="authStore.getUserAvatar"
v-bind:prepend-icon="
authStore.getUserAvatar === 'default' ? 'mdi-account-circle-outline' : undefined
"
:title="authStore.getUsername"
v-bind:title="authStore.getUsername"
lines="two"
color="white"
></v-list-item>

<v-list-item>
<v-select
:items="availableLocale"
v-bind:items="availableLocale"
label="Locale"
v-model="locale"
density="compact"
Expand All @@ -48,19 +48,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)"
v-on: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)"
v-on:click="showAuthModal(false)"
prepend-icon="mdi-account-plus"
title="Register"
value="register"
Expand All @@ -70,43 +69,42 @@
</v-list>
</template>

<!-- User Menu Section -->
<template v-else>
<v-list density="compact" nav>
<v-list-item
@click.stop="dashboard"
v-on: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"
v-on: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"
v-on:click="notifications"
prepend-icon="mdi-bell-outline"
title="Notifications"
value="notifications"
variant="text"
color="white"
>
<template v-if="unreadCount > 0" v-slot:append>
<v-badge :content="unreadCount" color="error"></v-badge>
<v-badge v-bind:content="unreadCount" color="error"></v-badge>
</template>
</v-list-item>
</v-list>

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

<v-list-item
@click.stop="signout"
v-on:click="signout"
prepend-icon="mdi-logout"
title="Logout"
value="logout"
Expand All @@ -120,14 +118,14 @@
<v-btn
class="avatar-btn"
variant="text"
@click.stop="drawer = !drawer"
v-on:click="drawer = !drawer"
rounded="xl"
color="white"
>
<v-avatar
v-if="authStore.getUserAvatar !== 'default'"
size="32"
:image="authStore.getUserAvatar"
v-bind:image="authStore.getUserAvatar"
color="white"
></v-avatar>
<v-icon
Expand All @@ -141,27 +139,26 @@
</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">
<v-toolbar-title class="text-white">
{{ isLoginMode ? 'Sign In' : 'Register' }}
</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn icon @click="authModal = false" color="white">
<v-btn icon v-on:click="authModal = false" color="white">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-toolbar>

<v-card-text class="pa-6">
<v-form @submit.prevent="handleAuthSubmit" ref="authForm">
<v-form v-on:submit.prevent="handleAuthSubmit" ref="authForm">
<v-text-field
v-if="!isLoginMode"
v-model="name"
label="Name"
type="text"
:rules="[requiredRule]"
v-bind:rules="[requiredRule]"
variant="outlined"
class="mb-0"
bg-color="#f0eee6"
Expand All @@ -171,7 +168,7 @@
v-model="email"
label="Email"
type="email"
:rules="[requiredRule, emailRule]"
v-bind:rules="[requiredRule, emailRule]"
variant="outlined"
class="mb-0"
bg-color="#f0eee6"
Expand All @@ -181,7 +178,7 @@
v-model="password"
label="Password"
type="password"
:rules="[requiredRule, passwordRule]"
v-bind:rules="[requiredRule, passwordRule]"
variant="outlined"
class="mb-0"
bg-color="#f0eee6"
Expand All @@ -191,8 +188,8 @@
<v-btn
color="#43b984"
type="submit"
:loading="isLoading"
:disabled="isLoading"
v-bind:loading="isLoading"
v-bind:disabled="isLoading"
size="large"
block
class="mb-2"
Expand All @@ -202,7 +199,7 @@

<v-btn
variant="text"
@click="toggleAuthMode"
v-on:click="toggleAuthMode"
size="small"
color="#43b984"
>
Expand All @@ -214,20 +211,19 @@
</v-card>
</v-dialog>

<!-- Snackbar Notification -->
<v-snackbar
v-model="snackbar.visible"
:color="snackbar.color"
:timeout="3000"
v-bind:color="snackbar.color"
v-bind:timeout="3000"
location="bottom right"
>
{{ snackbar.message }}

<template v-slot:actions>
<v-btn
variant="text"
@click="snackbar.visible = false"
:icon="mdiClose"
v-on:click="snackbar.visible = false"
v-bind:icon="mdiClose"
color="white"
></v-btn>
</template>
Expand All @@ -236,12 +232,11 @@
</template>

<script lang="ts" setup>
import { ref } from 'vue'
import { ref, watch, onMounted } 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,10 +251,43 @@ const isLoading = ref(false)
const authForm = ref()
const unreadCount = ref(0)

/**
* Watcher to handle language changes:
* 1. Saves preference to localStorage for refresh persistence.
* 2. Updates the document lang and direction (RTL support).
*/
watch(locale, function(newLocale) {
localStorage.setItem('locale', newLocale)
document.documentElement.lang = newLocale

if (newLocale === 'ar') {
document.documentElement.dir = 'rtl'
} else {
document.documentElement.dir = 'ltr'
}
Comment on lines +263 to +267
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Incomplete RTL language support.

Only Arabic ('ar') is handled for RTL directionality. Other RTL languages like Hebrew ('he'), Farsi/Persian ('fa'), and Urdu ('ur') should also set dir='rtl'.

🌍 Proposed fix for comprehensive RTL support
-  if (newLocale === 'ar') {
+  const rtlLanguages = ['ar', 'he', 'fa', 'ur']
+  if (rtlLanguages.includes(newLocale)) {
     document.documentElement.dir = 'rtl'
   } else {
     document.documentElement.dir = 'ltr'
   }
📝 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
if (newLocale === 'ar') {
document.documentElement.dir = 'rtl'
} else {
document.documentElement.dir = 'ltr'
}
const rtlLanguages = ['ar', 'he', 'fa', 'ur']
if (rtlLanguages.includes(newLocale)) {
document.documentElement.dir = 'rtl'
} else {
document.documentElement.dir = 'ltr'
}
🤖 Prompt for AI Agents
In @v0/src/components/Navbar/User/UserMenu.vue around lines 263 - 267, The
current locale-to-direction logic only treats 'ar' as RTL; update it to treat
all RTL languages (e.g., 'ar', 'he', 'fa', 'ur') as RTL by checking newLocale
against a set/array of RTL codes (e.g., const RTL_LOCALES =
['ar','he','fa','ur']) and then setting document.documentElement.dir =
RTL_LOCALES.includes(newLocale) ? 'rtl' : 'ltr'; apply this change where
newLocale is used to set document.documentElement.dir in UserMenu.vue.

})
Comment on lines +259 to +268
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add error handling for localStorage access.

The localStorage.setItem() call can throw exceptions in private/incognito mode, when storage is full, or when disabled. This would crash the locale switcher.

🛡️ Proposed fix with try-catch
 watch(locale, function(newLocale) {
-  localStorage.setItem('locale', newLocale)
+  try {
+    localStorage.setItem('locale', newLocale)
+  } catch (error) {
+    console.warn('Failed to save locale preference:', error)
+  }
   document.documentElement.lang = newLocale
   
   if (newLocale === 'ar') {
📝 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(locale, function(newLocale) {
localStorage.setItem('locale', newLocale)
document.documentElement.lang = newLocale
if (newLocale === 'ar') {
document.documentElement.dir = 'rtl'
} else {
document.documentElement.dir = 'ltr'
}
})
watch(locale, function(newLocale) {
try {
localStorage.setItem('locale', newLocale)
} catch (error) {
console.warn('Failed to save locale preference:', error)
}
document.documentElement.lang = newLocale
if (newLocale === 'ar') {
document.documentElement.dir = 'rtl'
} else {
document.documentElement.dir = 'ltr'
}
})
🤖 Prompt for AI Agents
In @v0/src/components/Navbar/User/UserMenu.vue around lines 259 - 268, The
watcher on locale calls localStorage.setItem directly which can throw in
restricted environments; wrap the localStorage.setItem('locale', newLocale) call
in a try-catch inside the watch(locale, ...) callback, swallow or log the error
(e.g., console.warn or an existing logger) and continue to set
document.documentElement.lang and dir so the locale switcher won’t crash; keep
document.documentElement.lang = newLocale and the dir logic unchanged and only
guard the storage write.


/**
* Lifecycle hook to restore user's language preference on load.
*/
onMounted(function() {
const savedLocale = localStorage.getItem('locale')
if (savedLocale) {
locale.value = savedLocale
}

document.documentElement.lang = locale.value
if (locale.value === 'ar') {
document.documentElement.dir = 'rtl'
} else {
document.documentElement.dir = 'ltr'
}
})

// Form validation rules
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'
const requiredRule = function(v: string) { return !!v || 'This field is required' }
const emailRule = function(v: string) { return /.+@.+\..+/.test(v) || 'E-mail must be valid' }
const passwordRule = function(v: string) { return v.length >= 6 || 'Password must be at least 6 characters' }

// Snackbar state
const snackbar = ref({
Expand Down Expand Up @@ -312,13 +340,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 @@ -330,8 +356,7 @@ async function handleAuthSubmit() {
'success'
)
authModal.value = false
} catch (error) {
console.error('Authentication error:', error)
} catch (error: any) {
showSnackbar(`Authentication failed: ${error.message}`, 'error')
} finally {
isLoading.value = false
Expand Down Expand Up @@ -361,7 +386,7 @@ function showSnackbar(message: string, type: 'success' | 'error' | 'warning' | '
snackbar.value = {
visible: true,
message,
color: type
color: type === 'error' ? '#dc5656' : '#43b984'
}
}

Expand All @@ -381,4 +406,4 @@ function signout() {
authStore.signOut()
showSnackbar('You have been logged out', 'info')
}
</script>
</script>
2 changes: 1 addition & 1 deletion v0/src/locales/bn.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"simulator": {
"save_online": "অনলাইন সংরক্ষণ করুন",
"save_online": "",
"save_offline": "অফলাইন সংরক্ষণ করুন",
"preview_circuit": "সার্কিট পূর্বরূপ",
"export_verilog": "ভেরিলগ রপ্তানি করুন",
Expand Down
Loading