Skip to content

feat: first pages#398

Merged
hmbanan666 merged 2 commits into
mainfrom
pages
May 7, 2025
Merged

feat: first pages#398
hmbanan666 merged 2 commits into
mainfrom
pages

Conversation

@hmbanan666
Copy link
Copy Markdown
Contributor

@hmbanan666 hmbanan666 commented May 7, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a sidebar navigation, user menu, and responsive layout with slide-over panel for improved navigation.
    • Added onboarding forms for initializing options and master user accounts, with validation and toast notifications.
    • Implemented channel creation via modal and form interfaces.
    • Added sign-in and welcome pages with localized content and confetti animation.
    • Added support for English and Russian full localization across the app.
    • Introduced a robots.txt file and app metadata schema.
  • Enhancements

    • Expanded UI theme configuration with new color, icon, and size options.
    • Improved button, modal, input, select, and tab component styling and variants.
    • Refined error page and loading indicator appearance.
  • Bug Fixes

    • Corrected schema validation references for user sign-in and creation.
  • Chores

    • Added new dependencies and utility functions for localization and selection options.
    • Added new API endpoints for fetching options and channels.
    • Introduced a Pinia store for managing global app state.
  • Documentation

    • Significantly expanded English and Russian locale files for comprehensive UI translation coverage.

@hmbanan666 hmbanan666 self-assigned this May 7, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2025

"""

Walkthrough

This update introduces a comprehensive suite of features and foundational infrastructure for the Essence application. Key changes include the addition of navigation and layout components, onboarding and sign-in flows, a Pinia store for essence data, utility helpers, expanded localization, new API endpoints, and schema/type refinements. UI themes and configuration are enhanced, and toast notification logic is added for user feedback.

Changes

File(s) / Path(s) Summary
app.config.ts Expanded UI theme configuration: new color/icon definitions, modal/button/input/tabs variants, and styling refinements.
app.vue Changed loading indicator class; added startup logic to redirect to /welcome if the essence store is not initialized.
components/Navigation.vue, components/NavigationHeader.vue, components/UserMenu.vue Added navigation sidebar, header, and user menu components with i18n and store integration.
components/form/CreateChannel.vue, components/form/InitMaster.vue, components/form/InitOption.vue Added onboarding form components for channel creation, master user, and option initialization with validation and API calls.
components/modal/CreateChannel.vue Added modal for channel creation, closing overlays on submit/success.
composables/useApp.ts New composable for shared navbar open/close state, resets on route change.
error.vue Updated error button color and translation key.
layouts/default.vue, layouts/empty.vue Added default and empty layout components for responsive app structure.
pages/index.vue Added placeholder index page.
pages/sign-in/index.vue Added sign-in page with form, validation, and API integration.
pages/welcome/index.vue Added welcome page with confetti, onboarding flow, and conditional forms based on store state.
utils/helpers.ts New utility module for locale/currency/unit/timezone helpers and select option generators.
i18n/locales/en-US.json, i18n/locales/ru-RU.json Major expansion of English and Russian locale files: UI, onboarding, menu, toast, and error messages.
package.json Added @neoconfetti/vue dependency.
public/robots.txt Added robots.txt to allow all except /welcome.
public/schema.json Added package metadata schema file.
server/api/auth/sign-in.post.ts, server/api/user/master.put.ts Refactored to use renamed schema constants for validation.
server/api/option/index.get.ts New API endpoint: returns options, master account existence, and channel list.
server/services/db/repository/channel.ts Added static list method to fetch all channels from DB.
stores/essence.ts New Pinia store for essence state, with update method, initialization logic, and error handling.
packages/schema/src/services/channel.ts, packages/schema/src/services/option.ts, packages/schema/src/services/user.ts Added type aliases inferred from schemas; renamed schema constants for clarity.
packages/ui/app/assets/css/main.css Simplified Tailwind CSS import.
packages/ui/app/composables/useActionToast.ts New composable for standardized action toast notifications (start, success, error).
packages/ui/i18n/locales/en-US.json, packages/ui/i18n/locales/ru-RU.json Added "to-home", "toast", and "error" namespaces/keys for UI feedback.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant App
    participant EssenceStore
    participant API

    User->>App: Loads app
    App->>EssenceStore: essence.update()
    EssenceStore->>API: GET /api/option
    API-->>EssenceStore: { id, masterAccountExists, channels }
    App->>App: If not initialized and not on /welcome
    App->>User: Redirect to /welcome

    User->>App: Fills onboarding form (option/master/channel)
    App->>API: PUT /api/option or /api/user/master or POST /api/channel
    API-->>App: Success/Error
    App->>EssenceStore: essence.update()
    App->>User: Show toast (success/error)
    App->>User: Redirect or update UI
Loading

Possibly related PRs

  • feat: channel routes #389: Adds CRUD API routes for channel entities, related to channel data management and API layer used in this PR.

Suggested labels

enhancement

Poem

A rabbit hopped through code so wide,
Bringing layouts, forms, and flows with pride.
With toasts and tabs and channels anew,
And confetti to welcome both me and you!
Now Essence shines with colors bright—
Hop along, enjoy the site!
🐇✨
"""

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 17

🧹 Nitpick comments (28)
apps/essence/public/schema.json (1)

1-5: Basic package metadata provided.

The schema.json file provides essential metadata for the package. Consider expanding it with additional fields like author, license, repository, and keywords to improve discoverability and provide more context about the package.

{
  "version": "0.1.0",
  "title": "@nextorders/essence",
  "description": "Business data collection and data management software."
+  "author": "NextOrders",
+  "license": "MIT",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/nextorders/essence"
+  },
+  "keywords": ["business", "data", "management"]
}
apps/essence/app/utils/helpers.ts (3)

15-22: Consider simplifying the locale value retrieval logic.

The ternary expression in the return statement could be clearer with a more explicit structure.

-  return data.values.find((v) => hasCurrentLocale ? v.locale === data.locale : v.locale === data.defaultLocale)?.value ?? ''
+  const localeToUse = hasCurrentLocale ? data.locale : data.defaultLocale
+  return data.values.find((v) => v.locale === localeToUse)?.value ?? ''

24-43: Simplify type parameter in getWeightLocalizedUnit function.

The type parameter <WeightUnitLiteral = string & object> seems unnecessarily complex.

-export function getWeightLocalizedUnit<WeightUnitLiteral = string & object>(unit?: WeightUnit | WeightUnitLiteral): string {
+export function getWeightLocalizedUnit(unit?: WeightUnit | string): string {

162-217: Consider moving icon lists to a separate file.

The category icons list is quite extensive. Consider moving it to a separate file to improve maintainability.

You could create a new file like iconSelectors.ts and import the functions from there to keep this file more focused on core helpers.

apps/essence/server/services/db/repository/channel.ts (1)

13-15: Implement pagination for channel listing

The new list() method retrieves all records from the channels table without any pagination. While this works for small datasets, it could lead to performance issues as the number of channels grows.

Consider implementing pagination to make the method more scalable:

-  static async list() {
-    return useDatabase().query.channels.findMany()
+  static async list(options?: { limit?: number; offset?: number }) {
+    return useDatabase().query.channels.findMany({
+      limit: options?.limit,
+      offset: options?.offset,
+    })

This would allow API consumers to request a specific page of results, improving performance for large datasets.

apps/essence/app/components/modal/CreateChannel.vue (2)

9-17: Missing explicit import for useOverlay composable

The useOverlay composable is used but not explicitly imported. While auto-imports might handle this, explicit imports improve code readability and maintainability.

<script setup lang="ts">
+import { useOverlay } from '~/composables/useOverlay' // adjust path as needed
const overlay = useOverlay()

function closeAll() {
  for (const o of overlay.overlays) {
    overlay.close(o.id)
  }
}
</script>

12-16: Consider more targeted overlay closing

The closeAll function closes all active overlays in the system. Consider whether it's more appropriate to close just this modal rather than all overlays, which could affect unrelated UI elements.

function closeAll() {
-  for (const o of overlay.overlays) {
-    overlay.close(o.id)
-  }
+  // Close only the current modal
+  overlay.close()
}
apps/essence/server/api/option/index.get.ts (1)

5-13: Improve error message specificity

The error message "Not found" is generic. Consider providing more context about what specifically wasn't found to help with debugging.

if (!options?.length || !options[0]) {
  throw createError({
    statusCode: 404,
-    message: 'Not found',
+    message: 'No application options found',
  })
}
apps/essence/app/layouts/default.vue (2)

36-38: Missing explicit import for useApp composable

The useApp composable is used but not explicitly imported. While auto-imports might handle this, explicit imports improve code readability and maintainability.

<script setup lang="ts">
+import { useApp } from '~/composables/useApp' // adjust path as needed
const { isNavbarOpened } = useApp()
</script>

13-33: Enhance accessibility for the slideover navigation

The mobile navigation slideover lacks accessibility attributes. Consider adding ARIA attributes to improve the experience for users with assistive technologies.

<USlideover
  v-model:open="isNavbarOpened"
  side="left"
+ aria-label="Navigation menu"
+ :aria-hidden="!isNavbarOpened"
>
  <template #header>
    <div class="flex flex-row items-center">
      <UButton
        icon="i-lucide-x"
        color="neutral"
        variant="ghost"
+       aria-label="Close navigation"
        @click="isNavbarOpened = false"
      />

      <NavigationHeader />
    </div>
  </template>

  <template #body>
    <Navigation />
  </template>
</USlideover>
apps/essence/app/pages/welcome/index.vue (3)

3-3: Consider setting a minimum particle count for smaller screens.

The confetti animation uses a fixed particle count of 200 regardless of screen size. On smaller devices, this might affect performance.

- <div v-confetti="{ stageHeight: height, stageWidth: width, particleCount: 200, duration: 5000 }" />
+ <div v-confetti="{ stageHeight: height, stageWidth: width, particleCount: width < 640 ? 100 : 200, duration: 5000 }" />

14-32: Add accessibility attributes to the form container.

The form container lacks accessibility attributes like role and aria-labelledby which would improve screen reader navigation.

- <div class="mt-12 px-6 py-6 max-w-sm mx-auto bg-elevated/50 ring ring-default rounded-xl">
+ <div 
+   class="mt-12 px-6 py-6 max-w-sm mx-auto bg-elevated/50 ring ring-default rounded-xl"
+   role="region" 
+   aria-labelledby="welcome-title"
+ >

And update the h1 element:

- <h1 class="mb-2 text-4xl font-semibold text-center">
+ <h1 id="welcome-title" class="mb-2 text-4xl font-semibold text-center">

49-54: Add error handling for the navigation operation.

The navigation logic lacks error handling, which could silently fail if there are routing issues.

if (essence.isInitialized) {
-  await navigateTo('/')
+  try {
+    await navigateTo('/')
+  } catch (error) {
+    console.error('Navigation failed:', error)
+    // Consider implementing a fallback or showing a toast notification
+  }
}
apps/essence/app/app.config.ts (1)

26-26: Consider breaking down the gradient button styles for better readability.

The gradient button variant contains a long string of utility classes, which could be difficult to maintain.

- gradient: 'text-neutral-950 bg-linear-to-br from-lime-300 to-green-400 hover:opacity-90 disabled:bg-none disabled:bg-(--ui-bg-accented) disabled:text-neutral-400 aria-disabled:bg-(--ui-bg-accented) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ui-primary)',
+ gradient: [
+   'text-neutral-950',
+   'bg-linear-to-br from-lime-300 to-green-400',
+   'hover:opacity-90',
+   'disabled:bg-none disabled:bg-(--ui-bg-accented) disabled:text-neutral-400',
+   'aria-disabled:bg-(--ui-bg-accented)',
+   'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ui-primary)',
+ ].join(' '),
apps/essence/app/components/form/InitMaster.vue (2)

9-10: Password visibility toggle is well implemented but could use aria improvements.

The password visibility toggle button has good accessibility features with aria-label and aria-pressed attributes. However, the aria-controls attribute refers to "password" but there is no matching id on the password input field.

To fix this, add an id to the password input:

-        v-model="state.password"
+        v-model="state.password"
+        id="password"
         :type="isPasswordVisible ? 'text' : 'password'"

91-91: Validation logic could be improved.

The form validation only checks if login and password exist but doesn't verify they meet any quality requirements.

Consider using the validation from UserCreateSchema instead of a custom isFormValid computed property. This would ensure consistency with server-side validation:

-const isFormValid = computed<boolean>(() => !!state.value.login && !!state.value.password)
+const isFormValid = computed<boolean>(() => {
+  try {
+    UserCreateSchema.parse({
+      login: state.value.login,
+      password: state.value.password
+    });
+    return true;
+  } catch (error) {
+    return false;
+  }
+})
apps/essence/i18n/locales/en-US.json (1)

17-27: Missing theme switch confirmation messages.

The locale file has labels for theme switching but lacks confirmation messages that would be useful for toast notifications after switching themes.

Consider adding confirmation messages for theme switching:

      "switch": {
        "system": "Switch to System Theme",
        "light": "Switch to Light Mode",
        "dark": "Switch to Dark Mode"
-     }
+     },
+     "switched": {
+       "system": "Switched to System Theme",
+       "light": "Switched to Light Mode",
+       "dark": "Switched to Dark Mode"
+     }
    },
apps/essence/app/pages/sign-in/index.vue (2)

87-92: Error toast uses hardcoded title instead of localized string.

The error toast uses a hardcoded "Error" title instead of using a localized string.

Use localized string for the error toast title:

  onResponseError: async ({ response }) => {
    toast.add({
-     title: 'Error',
+     title: t('common.error'),
      description: response.statusText,
    })
  },

This requires adding a new entry to the localization file:

"common": {
  ...
  "error": "Error",
  ...
}

76-93: Missing loading state indication during form submission.

While the button shows a loading state, there's no visual indication if the form is submitted via Enter key.

Consider adding a loading state indicator for the entire form:

+const isSubmitting = ref(false)

const { status, execute: signIn } = await useFetch('/api/auth/sign-in', {
  method: 'POST',
  body: state,
  immediate: false,
  watch: false,
+  onRequest() {
+    isSubmitting.value = true
+  },
  onResponse: async ({ response }) => {
+    isSubmitting.value = false
    if (response.ok) {
      await refreshSession()
      await navigateTo('/')
    }
  },
  onResponseError: async ({ response }) => {
+    isSubmitting.value = false
    toast.add({
      title: 'Error',
      description: response.statusText,
    })
  },
})

Then add a loading overlay to the form:

<form class="space-y-4" @submit.prevent="signIn">
+ <div v-if="isSubmitting" class="absolute inset-0 bg-gray-100/50 dark:bg-gray-800/50 flex items-center justify-center z-10">
+   <UIcon name="i-lucide-loader-2" class="animate-spin text-primary h-8 w-8" />
+ </div>
  <div class="w-full flex flex-col gap-3">
apps/essence/app/components/Navigation.vue (1)

29-34: Enhance channel items with additional information.

The channel list items only display the name without any additional information or visual cues that might help distinguish between channels.

Consider enhancing channel items with more information, like an icon or active status:

const channelItems = computed(() => essence.channels.map((c) => {
  return {
    label: c.name,
    to: `/channel/${c.id}`,
+   icon: 'i-lucide-layout',
+   trailing: c.active 
+     ? { icon: 'i-lucide-check', color: 'success' } 
+     : { icon: 'i-lucide-x', color: 'danger' },
+   description: c.description ? c.description.substring(0, 20) + '...' : '',
  }
}))
apps/essence/app/components/UserMenu.vue (2)

34-43: Extract type definition for locale items

The as const type assertion is repeated throughout the code. Consider extracting the checkbox type to improve code maintainability.

+const CheckboxType = 'checkbox' as const
+
 const localeItems = ref(locales.value.map((l) => ({
   label: l.name,
-  type: 'checkbox' as const,
+  type: CheckboxType,
   icon: `circle-flags:${l.code}`,
   onUpdateChecked(checked: boolean) {
     if (checked) {
       setLocale(l.code)
     }
   },
 })))

59-69: Consider refactoring duplicate onUpdateChecked handlers

The pattern for handling checkbox updates is very similar across different checkboxes. Consider extracting a factory function to reduce duplication.

+// Add at the beginning of the script
+function createPreferenceHandler(value: string) {
+  return function onUpdateChecked(checked: boolean) {
+    if (checked) {
+      colorMode.preference = value
+    }
+  }
+}

// Then use it in the items definition
{
  label: t('common.color-mode.light'),
  type: 'checkbox' as const,
  icon: 'i-lucide-sun-dim',
-  onUpdateChecked(checked: boolean) {
-    if (checked) {
-      colorMode.preference = 'light'
-    }
-  },
+  onUpdateChecked: createPreferenceHandler('light'),
},
packages/ui/app/composables/useActionToast.ts (3)

7-18: Consider configurable toast duration

The loading toast has a fixed duration of 2 minutes (120000ms), which might be too long for some operations and too short for others. Consider making the duration configurable.

-function start() {
+function start(duration = 120000) {
   toast.add({
     id: id.value,
     title: t('toast.in-process'),
     description: t('toast.updating-data'),
     icon: 'food:loader',
-    duration: 120000,
+    duration,
     ui: {
       icon: 'animate-spin',
     },
   })
 }

20-31: Allow customizing success toast duration

The success toast has a fixed duration of 3 seconds, which might not be appropriate for all messages. Consider making this configurable.

-function success(title: string) {
+function success(title: string, duration = 3000) {
   toast.update(id.value, {
     title,
     description: undefined,
     icon: 'food:check',
     color: 'success',
-    duration: 3000,
+    duration,
     ui: {
       icon: '',
     },
   })
 }

33-44: Allow customizing error toast title

The error toast always uses a fixed title from i18n. Consider making this customizable to support different error scenarios.

-function error(description: string = t('error.default')) {
+function error(description: string = t('error.default'), customTitle?: string) {
   toast.update(id.value, {
-    title: t('error.title'),
+    title: customTitle || t('error.title'),
     icon: 'food:close',
     color: 'error',
     description,
     duration: 5000,
     ui: {
       icon: '',
     },
   })
 }
apps/essence/app/components/form/CreateChannel.vue (3)

99-116: Enhance error handling and user feedback.

The current error handling only logs errors to the console and shows a generic error toast. Consider providing more specific error messages to the user based on the error type.

async function onSubmit(event: FormSubmitEvent<ChannelCreate>) {
  actionToast.start()
  emit('submitted')

  try {
    await $fetch('/api/channel', {
      method: 'POST',
      body: event.data,
    })

    await essence.update()
    actionToast.success(t('toast.channel-created'))
    emit('success')
  } catch (error) {
    console.error(error)
-   actionToast.error()
+   // Get specific error message if available
+   const errorMessage = error.response?.data?.message || t('common.error.default')
+   actionToast.error(errorMessage)
  }
}

90-97: Consider default values for form fields.

The initial state values are all undefined, which may lead to validation issues or inconsistent UX. Consider providing default values for fields where it makes sense (e.g., defaultLocale based on user's browser).

const state = ref<Partial<ChannelCreate>>({
  name: undefined,
  description: undefined,
-  defaultLocale: undefined,
+  defaultLocale: useI18n().locale.value || 'en-US',
  currencyCode: undefined,
  countryCode: undefined,
  timeZone: undefined,
})

78-98: Reset form after successful submission.

The form doesn't reset after successful submission. If the user wants to create multiple channels in succession, they would need to manually clear all fields.

Add a function to reset the form and call it after successful submission:

const state = ref<Partial<ChannelCreate>>({
  name: undefined,
  description: undefined,
  defaultLocale: undefined,
  currencyCode: undefined,
  countryCode: undefined,
  timeZone: undefined,
})

+function resetForm() {
+  state.value = {
+    name: undefined,
+    description: undefined,
+    defaultLocale: undefined,
+    currencyCode: undefined,
+    countryCode: undefined,
+    timeZone: undefined,
+  }
+}

async function onSubmit(event: FormSubmitEvent<ChannelCreate>) {
  // ...existing code
  try {
    // ...existing code
    actionToast.success(t('toast.channel-created'))
+   resetForm()
    emit('success')
  } catch (error) {
    // ...existing code
  }
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea7afe1 and 1298c7f.

⛔ Files ignored due to path filters (4)
  • apps/essence/public/favicon.ico is excluded by !**/*.ico
  • apps/essence/public/img/admin-avatar.svg is excluded by !**/*.svg
  • apps/essence/public/img/bg-pattern-1.jpg is excluded by !**/*.jpg
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (34)
  • apps/essence/app/app.config.ts (1 hunks)
  • apps/essence/app/app.vue (2 hunks)
  • apps/essence/app/components/Navigation.vue (1 hunks)
  • apps/essence/app/components/NavigationHeader.vue (1 hunks)
  • apps/essence/app/components/UserMenu.vue (1 hunks)
  • apps/essence/app/components/form/CreateChannel.vue (1 hunks)
  • apps/essence/app/components/form/InitMaster.vue (1 hunks)
  • apps/essence/app/components/form/InitOption.vue (1 hunks)
  • apps/essence/app/components/modal/CreateChannel.vue (1 hunks)
  • apps/essence/app/composables/useApp.ts (1 hunks)
  • apps/essence/app/error.vue (1 hunks)
  • apps/essence/app/layouts/default.vue (1 hunks)
  • apps/essence/app/layouts/empty.vue (1 hunks)
  • apps/essence/app/pages/index.vue (1 hunks)
  • apps/essence/app/pages/sign-in/index.vue (1 hunks)
  • apps/essence/app/pages/welcome/index.vue (1 hunks)
  • apps/essence/app/utils/helpers.ts (1 hunks)
  • apps/essence/i18n/locales/en-US.json (1 hunks)
  • apps/essence/i18n/locales/ru-RU.json (1 hunks)
  • apps/essence/package.json (1 hunks)
  • apps/essence/public/robots.txt (1 hunks)
  • apps/essence/public/schema.json (1 hunks)
  • apps/essence/server/api/auth/sign-in.post.ts (1 hunks)
  • apps/essence/server/api/option/index.get.ts (1 hunks)
  • apps/essence/server/api/user/master.put.ts (2 hunks)
  • apps/essence/server/services/db/repository/channel.ts (1 hunks)
  • apps/essence/stores/essence.ts (1 hunks)
  • packages/schema/src/services/channel.ts (2 hunks)
  • packages/schema/src/services/option.ts (1 hunks)
  • packages/schema/src/services/user.ts (1 hunks)
  • packages/ui/app/assets/css/main.css (1 hunks)
  • packages/ui/app/composables/useActionToast.ts (1 hunks)
  • packages/ui/i18n/locales/en-US.json (2 hunks)
  • packages/ui/i18n/locales/ru-RU.json (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (6)
apps/essence/server/services/db/repository/channel.ts (1)
apps/essence/server/services/db/database.ts (1)
  • useDatabase (22-28)
apps/essence/server/api/user/master.put.ts (1)
packages/schema/src/services/user.ts (1)
  • UserCreateSchema (8-12)
apps/essence/server/api/auth/sign-in.post.ts (1)
packages/schema/src/services/user.ts (1)
  • UserSignInSchema (3-6)
apps/essence/stores/essence.ts (3)
packages/schema/src/types/food.ts (1)
  • LocaleValue (10-10)
apps/essence/server/services/db/tables.ts (1)
  • channels (51-68)
apps/essence/server/services/db/repository/channel.ts (2)
  • Channel (6-39)
  • update (26-34)
apps/essence/server/api/option/index.get.ts (2)
apps/essence/server/services/db/tables.ts (2)
  • options (6-16)
  • channels (51-68)
apps/essence/server/services/db/repository/index.ts (1)
  • repository (23-23)
apps/essence/app/utils/helpers.ts (2)
packages/schema/src/types/channel.ts (1)
  • CurrencyCode (6-6)
packages/schema/src/types/food.ts (3)
  • LocaleValue (10-10)
  • Locale (4-4)
  • WeightUnit (13-13)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (39)
packages/ui/app/assets/css/main.css (1)

1-1: Tailwind CSS import simplified appropriately.

The simplified import syntax @import "tailwindcss"; aligns with modern Tailwind usage patterns and maintains compatibility with the rest of the styling configuration.

apps/essence/public/robots.txt (1)

1-4: Robots.txt configuration follows standard protocol.

The robots.txt file correctly follows the standard format with proper directives. Disallowing the /welcome path is appropriate if this is an onboarding or intro page that shouldn't be indexed by search engines.

apps/essence/app/composables/useApp.ts (1)

1-18: Well-structured composable for navigation state management.

The useApp composable provides a clean approach to managing navigation state across the application. Using createSharedComposable ensures consistent state between components, and the route watcher automatically closes the navbar on navigation, which improves user experience.

packages/ui/i18n/locales/en-US.json (2)

3-3: Translation key added appropriately.

The new "to-home" key has been correctly added to the "app" namespace, which is referenced in the error.vue file.


15-22: New namespaces added for toast notifications and error handling.

The additions of "toast" and "error" namespaces provide necessary translation support for app-wide notifications and error handling. These namespaces will help maintain consistency across the application.

apps/essence/app/utils/helpers.ts (1)

1-13: Currency sign mapping looks good.

The CURRENCY_SIGNS constant provides a clean mapping between currency codes and their respective symbols, which will be helpful for formatting currency values throughout the app.

apps/essence/app/error.vue (1)

10-10: Button styling and translation key updated consistently.

The button has been styled with a neutral color and now uses the "app.to-home" translation key that was added to the localization file.

Also applies to: 15-15

apps/essence/app/pages/index.vue (1)

1-3: Homepage contains only placeholder content.

The current implementation only displays "123" and lacks proper content structure. This appears to be a placeholder that needs to be replaced with actual homepage content.

Is this intended to be a placeholder for future implementation, or should it be expanded to include actual content for the initial release?

apps/essence/app/layouts/empty.vue (1)

1-3: LGTM! Clean minimal layout implementation

This empty layout provides a clean container for pages that don't need navigation or other UI elements, perfect for onboarding and welcome screens.

packages/schema/src/services/option.ts (1)

14-14: LGTM! Good type definition approach

Exporting a type derived from the schema's inferred type ensures consistency between validation and usage throughout the application.

apps/essence/server/api/auth/sign-in.post.ts (1)

2-2: LGTM! Improved schema naming consistency

The update from UserSignIn to UserSignInSchema improves naming consistency across the codebase, making it clearer that this is a validation schema.

Also applies to: 9-9

apps/essence/server/api/user/master.put.ts (1)

1-1: Schema naming convention improvement

The update from UserCreate to UserCreateSchema improves naming consistency by clearly indicating this is a validation schema. This aligns well with the schema definition in packages/schema/src/services/user.ts.

Also applies to: 18-18

apps/essence/package.json (1)

20-20: Good addition of UI enhancement package

Adding the @neoconfetti/vue package is appropriate for enhancing the user experience with confetti animations, likely for celebratory moments such as on the welcome page or after completing important actions.

packages/schema/src/services/channel.ts (2)

14-14: Improved type safety with inferred types

Good addition of the ChannelCreate type that leverages TypeScript's type inference. This provides strong typing for channel creation operations throughout the application, improving developer experience with IDE autocompletion and catching type errors at compile time.


28-28: Improved type safety with inferred types

Excellent addition of the ChannelUpdate type that maintains consistency between runtime validation and compile-time typing. This follows the DRY principle by deriving types from validation schemas rather than creating separate type definitions.

apps/essence/app/components/NavigationHeader.vue (1)

1-7: Well-structured navigation header component

This is a clean, simple component that properly uses internationalization for the app title. The component follows good practices with semantic HTML and appropriate spacing/styling classes.

apps/essence/app/app.vue (2)

7-7: UI consistency improvement

Changed from CSS variable syntax bg-(--ui-primary) to the direct utility class bg-primary, which is more consistent with standard Tailwind/UI framework conventions.


29-39: Well-implemented app initialization flow

This addition creates a robust initialization flow that:

  1. Updates the essence store on app startup
  2. Redirects users to the welcome page if the app isn't initialized
  3. Ensures users complete the onboarding process before accessing other app features

The async/await handling is also implemented correctly.

packages/schema/src/services/user.ts (1)

3-3: Schema naming convention standardization - Good improvement!

Excellent job renaming the schema constants to include the "Schema" suffix and adding the type inference. This makes the schema naming consistent across files and improves type safety by separating the schema definition from the inferred TypeScript type.

Also applies to: 8-8, 13-13

apps/essence/server/api/option/index.get.ts (1)

18-22: Well-structured response object

The response object neatly combines the option data with additional properties. This approach provides a comprehensive dataset for frontend components in a single request.

apps/essence/app/layouts/default.vue (1)

2-11: Well-structured responsive layout

The layout effectively handles both desktop and mobile views with a clean separation of concerns. The use of Tailwind's responsive classes and logical component hierarchy creates a maintainable structure.

packages/ui/i18n/locales/ru-RU.json (3)

3-3: LGTM! Appropriate translation added for the home navigation link.

The Russian translation "На главную" for the "to-home" key is accurate and aligns with the application's navigation functionality.


15-18: LGTM! Toast notification translations look good.

The Russian translations for toast notifications are properly structured and provide clear messaging for the user.


19-22: LGTM! Error message translations are appropriate.

The error message translations are concise and user-friendly, following good practices for error handling in the UI.

apps/essence/app/app.config.ts (5)

3-8: LGTM! Clean color schema implementation.

The color scheme is well-defined and follows a consistent pattern, using Tailwind's color naming conventions for easy integration with the UI framework.


9-11: LGTM! Custom loader icon defined.

Setting "food:loader" as the loading icon aligns well with the application's domain.


21-24: LGTM! Extra-large button size variant added.

The XL size variant with appropriate padding will provide better touch targets for important actions.


30-63: LGTM! Consistent input component sizing across variants.

The XL size variants for input, inputNumber, select, and selectMenu components maintain consistent padding (px-4 py-3), ensuring a uniform UI experience.


68-70: LGTM! Simplified tab styling with utility classes.

The gradient variant for tabs now uses utility classes, making the styling more maintainable and consistent with the rest of the UI.

apps/essence/i18n/locales/ru-RU.json (5)

3-3: LGTM! Short name added for the Russian language.

The short name "Рус" is appropriate for UI elements where space is limited.


4-56: LGTM! Comprehensive common UI translations.

The common namespace contains all necessary translations for general UI elements, including language settings, color modes, and geographic/currency information.


57-63: LGTM! Well-structured onboarding translations.

The initialization translations provide clear guidance through the onboarding process with appropriate Russian phrasing.


64-98: LGTM! Complete application UI translations.

The app namespace provides comprehensive translations for all navigation elements, menus, and application-specific UI components.


99-136: LGTM! Extensive toast notification messages.

The toast namespace contains a complete set of success messages for all CRUD operations in the application, ensuring users receive appropriate feedback in Russian.

apps/essence/app/components/Navigation.vue (1)

63-70:

Details

❓ Verification inconclusive

Technical support link could use verification.

The technical support link points to a GitHub repository, but there's no verification that this is the correct support channel.

Verify that this is the intended support channel:


🏁 Script executed:

#!/bin/bash
# Check if there's a SUPPORT.md file in the repository that might contain official support information
fd -t f -e md "SUPPORT" -x cat {}

# Check for contact information in the README
fd -t f "README.md" -x grep -i -A 5 "support|contact|help" {}

Length of output: 99


Support link needs confirmation

  • Location: apps/essence/app/components/Navigation.vue (lines 63–70)
  • I didn’t find a SUPPORT.md or any “support/contact” section in the repo.
  • Please confirm whether https://github.com/nextorders/food is the official technical support channel, or update this to: URL to the correct support endpoint.
apps/essence/stores/essence.ts (1)

3-10: Good Pinia store structure with computed state

The store is well-structured with clear separation of state properties and a computed property to determine initialization status.

apps/essence/app/components/UserMenu.vue (1)

1-19: Well-structured dropdown menu UI

The dropdown menu is well-structured with clear organization and good UI customization.

apps/essence/app/components/form/InitOption.vue (1)

1-85: Well-structured form with good validation

The form structure is clear and well-organized, with appropriate field validation and a good use of UI components.

packages/ui/app/composables/useActionToast.ts (1)

54-54: Good use of createSharedComposable pattern

Using createSharedComposable is a great approach for this utility, ensuring consistent toast behavior across the application.

Comment on lines +1 to +217
import type { CurrencyCode, Locale, LocaleValue, WeightUnit } from '@nextorders/schema'

export const CURRENCY_SIGNS: Record<CurrencyCode, string> = {
RUB: '₽',
USD: '$',
EUR: '€',
GEL: '₾',
BYN: 'Br',
UAH: '₴',
KZT: '₸',
PLN: 'zł',
TRY: '₺',
}

export function getLocaleValue(data: { values?: LocaleValue[], locale: Locale, defaultLocale?: Locale }): string {
if (!data.values || !Array.isArray(data.values)) {
return ''
}

const hasCurrentLocale = data.values.some((n) => n.locale === data.locale)
return data.values.find((v) => hasCurrentLocale ? v.locale === data.locale : v.locale === data.defaultLocale)?.value ?? ''
}

export function getWeightLocalizedUnit<WeightUnitLiteral = string & object>(unit?: WeightUnit | WeightUnitLiteral): string {
const { t } = useI18n()

switch (unit as WeightUnit) {
case 'G':
return t('common.abbreviation.g')
case 'KG':
return t('common.abbreviation.kg')
case 'ML':
return t('common.abbreviation.ml')
case 'L':
return t('common.abbreviation.l')
case 'LB':
return t('common.abbreviation.lb')
case 'OZ':
return t('common.abbreviation.oz')
default:
return ''
}
}

export function getLocalizedLanguageCodesForSelect(): { value: string, label: string }[] {
return [
{ value: 'en', label: 'EN - English' },
{ value: 'ru', label: 'RU - Русский' },
]
}

export function getLocalizedCountryCodesForSelect(): { value: string, label: string }[] {
const { t } = useI18n()

return [
{ value: 'BY', label: `BY - ${t('common.country.by')}` },
{ value: 'DE', label: `DE - ${t('common.country.de')}` },
{ value: 'ES', label: `ES - ${t('common.country.es')}` },
{ value: 'FR', label: `FR - ${t('common.country.fr')}` },
{ value: 'GB', label: `GB - ${t('common.country.gb')}` },
{ value: 'GE', label: `GE - ${t('common.country.ge')}` },
{ value: 'GR', label: `GR - ${t('common.country.gr')}` },
{ value: 'IT', label: `IT - ${t('common.country.it')}` },
{ value: 'KZ', label: `KZ - ${t('common.country.kz')}` },
{ value: 'PL', label: `PL - ${t('common.country.pl')}` },
{ value: 'RU', label: `RU - ${t('common.country.ru')}` },
{ value: 'TR', label: `TR - ${t('common.country.tr')}` },
{ value: 'UA', label: `UA - ${t('common.country.ua')}` },
{ value: 'US', label: `US - ${t('common.country.us')}` },
]
}

export function getLocalizedCurrencyCodesForSelect(): { value: string, label: string }[] {
const { t } = useI18n()

return [
{ value: 'EUR', label: `EUR - ${t('common.currency.eur')}` },
{ value: 'BYN', label: `BYN - ${t('common.currency.byn')}` },
{ value: 'GEL', label: `GEL - ${t('common.currency.gel')}` },
{ value: 'KZT', label: `KZT - ${t('common.currency.kzt')}` },
{ value: 'PLN', label: `PLN - ${t('common.currency.pln')}` },
{ value: 'RUB', label: `RUB - ${t('common.currency.rub')}` },
{ value: 'UAH', label: `UAH - ${t('common.currency.uah')}` },
{ value: 'USD', label: `USD - ${t('common.currency.usd')}` },
]
}

export function getLocalizedWeightUnitsForSelect(): { value: WeightUnit, label: string }[] {
const { t } = useI18n()

return [
{ value: 'KG', label: t('common.weight-unit.kg') },
{ value: 'G', label: t('common.weight-unit.g') },
{ value: 'L', label: t('common.weight-unit.l') },
{ value: 'ML', label: t('common.weight-unit.ml') },
{ value: 'OZ', label: t('common.weight-unit.oz') },
{ value: 'LB', label: t('common.weight-unit.lb') },
]
}

export function getLocalizedTimezonesForSelect(): { value: string, label: string }[] {
return [
{ value: '-12:00', label: '-12:00' },
{ value: '-11:00', label: '-11:00' },
{ value: '-10:00', label: '-10:00' },
{ value: '-09:00', label: '-09:00' },
{ value: '-08:00', label: '-08:00' },
{ value: '-07:00', label: '-07:00' },
{ value: '-06:00', label: '-06:00' },
{ value: '-05:00', label: '-05:00' },
{ value: '-04:00', label: '-04:00' },
{ value: '-03:00', label: '-03:00' },
{ value: '-02:00', label: '-02:00' },
{ value: '-01:00', label: '-01:00' },
{ value: '+00:00', label: '+00:00' },
{ value: '+01:00', label: '+01:00' },
{ value: '+02:00', label: '+02:00' },
{ value: '+03:00', label: '+03:00' },
{ value: '+04:00', label: '+04:00' },
{ value: '+05:00', label: '+05:00' },
{ value: '+06:00', label: '+06:00' },
{ value: '+07:00', label: '+07:00' },
{ value: '+08:00', label: '+08:00' },
{ value: '+09:00', label: '+09:00' },
{ value: '+10:00', label: '+10:00' },
{ value: '+11:00', label: '+11:00' },
{ value: '+12:00', label: '+12:00' },
{ value: '+13:00', label: '+13:00' },
{ value: '+14:00', label: '+14:00' },
]
}

export function getLinkIconsForSelect(): { value: string, label: string, icon?: string, type?: 'separator' }[] {
return [
{ value: 'i-lucide-smartphone', icon: 'i-lucide-smartphone', label: 'Phone' },
{ value: 'i-lucide-gift', icon: 'i-lucide-gift', label: 'Gift' },
{ value: 'i-lucide-info', icon: 'i-lucide-info', label: 'Info' },
{ type: 'separator', label: '', value: '' },
{ value: 'simple-icons:bluesky', icon: 'simple-icons:bluesky', label: 'Bluesky' },
{ value: 'simple-icons:facebook', icon: 'simple-icons:facebook', label: 'Facebook' },
{ value: 'simple-icons:github', icon: 'simple-icons:github', label: 'GitHub' },
{ value: 'simple-icons:instagram', icon: 'simple-icons:instagram', label: 'Instagram' },
{ value: 'simple-icons:odnoklassniki', icon: 'simple-icons:odnoklassniki', label: 'Odnoklassniki' },
{ value: 'simple-icons:telegram', icon: 'simple-icons:telegram', label: 'Telegram' },
{ value: 'simple-icons:viber', icon: 'simple-icons:viber', label: 'Viber' },
{ value: 'simple-icons:vk', icon: 'simple-icons:vk', label: 'VK' },
{ value: 'simple-icons:whatsapp', icon: 'simple-icons:whatsapp', label: 'WhatsApp' },
{ value: 'simple-icons:x', icon: 'simple-icons:x', label: 'X' },
{ value: 'simple-icons:youtube', icon: 'simple-icons:youtube', label: 'YouTube' },
]
}

export function getTargetVariantsForSelect(): { value: string, label: string }[] {
const { t } = useI18n()

return [
{ value: '_self', label: t('common.no') },
{ value: '_blank', label: t('common.yes') },
]
}

export function getCategoryIconsForSelect(): { value: string, label: string, icon?: string }[] {
return [
{ value: '', icon: '', label: 'No icon' },
{ value: 'fluent-emoji-flat:avocado', icon: 'fluent-emoji-flat:avocado', label: 'Avocado' },
{ value: 'fluent-emoji-flat:bacon', icon: 'fluent-emoji-flat:bacon', label: 'Bacon' },
{ value: 'fluent-emoji-flat:bagel', icon: 'fluent-emoji-flat:bagel', label: 'Bagel' },
{ value: 'fluent-emoji-flat:banana', icon: 'fluent-emoji-flat:banana', label: 'Banana' },
{ value: 'fluent-emoji-flat:baguette-bread', icon: 'fluent-emoji-flat:baguette-bread', label: 'Baguette' },
{ value: 'fluent-emoji-flat:beer-mug', icon: 'fluent-emoji-flat:beer-mug', label: 'Beer' },
{ value: 'fluent-emoji-flat:bento-box', icon: 'fluent-emoji-flat:bento-box', label: 'Bento' },
{ value: 'fluent-emoji-flat:beverage-box', icon: 'fluent-emoji-flat:beverage-box', label: 'Beverage' },
{ value: 'fluent-emoji-flat:birthday-cake', icon: 'fluent-emoji-flat:birthday-cake', label: 'Birthday Cake' },
{ value: 'fluent-emoji-flat:bread', icon: 'fluent-emoji-flat:bread', label: 'Bread' },
{ value: 'fluent-emoji-flat:broccoli', icon: 'fluent-emoji-flat:broccoli', label: 'Broccoli' },
{ value: 'fluent-emoji-flat:brown-mushroom', icon: 'fluent-emoji-flat:brown-mushroom', label: 'Brown Mushroom' },
{ value: 'fluent-emoji-flat:bubble-tea', icon: 'fluent-emoji-flat:bubble-tea', label: 'Bubble Tea' },
{ value: 'fluent-emoji-flat:burrito', icon: 'fluent-emoji-flat:burrito', label: 'Burrito' },
{ value: 'fluent-emoji-flat:candy', icon: 'fluent-emoji-flat:candy', label: 'Candy' },
{ value: 'fluent-emoji-flat:carrot', icon: 'fluent-emoji-flat:carrot', label: 'Carrot' },
{ value: 'fluent-emoji-flat:cheese-wedge', icon: 'fluent-emoji-flat:cheese-wedge', label: 'Cheese' },
{ value: 'fluent-emoji-flat:chocolate-bar', icon: 'fluent-emoji-flat:chocolate-bar', label: 'Chocolate' },
{ value: 'fluent-emoji-flat:shortcake', icon: 'fluent-emoji-flat:shortcake', label: 'Cake' },
{ value: 'fluent-emoji-flat:cocktail-glass', icon: 'fluent-emoji-flat:cocktail-glass', label: 'Cocktail' },
{ value: 'fluent-emoji-flat:cooked-rice', icon: 'fluent-emoji-flat:cooked-rice', label: 'Cooked Rice' },
{ value: 'fluent-emoji-flat:cookie', icon: 'fluent-emoji-flat:cookie', label: 'Cookie' },
{ value: 'fluent-emoji-flat:cooking', icon: 'fluent-emoji-flat:cooking', label: 'Cooking' },
{ value: 'fluent-emoji-flat:crab', icon: 'fluent-emoji-flat:crab', label: 'Crab' },
{ value: 'fluent-emoji-flat:croissant', icon: 'fluent-emoji-flat:croissant', label: 'Croissant' },
{ value: 'fluent-emoji-flat:cupcake', icon: 'fluent-emoji-flat:cupcake', label: 'Cupcake' },
{ value: 'fluent-emoji-flat:curry-rice', icon: 'fluent-emoji-flat:curry-rice', label: 'Curry' },
{ value: 'fluent-emoji-flat:doughnut', icon: 'fluent-emoji-flat:doughnut', label: 'Doughnut' },
{ value: 'fluent-emoji-flat:fork-and-knife', icon: 'fluent-emoji-flat:fork-and-knife', label: 'Fork' },
{ value: 'fluent-emoji-flat:french-fries', icon: 'fluent-emoji-flat:french-fries', label: 'French Fries' },
{ value: 'fluent-emoji-flat:fried-shrimp', icon: 'fluent-emoji-flat:fried-shrimp', label: 'Fried Shrimp' },
{ value: 'fluent-emoji-flat:hamburger', icon: 'fluent-emoji-flat:hamburger', label: 'Hamburger' },
{ value: 'fluent-emoji-flat:hot-beverage', icon: 'fluent-emoji-flat:hot-beverage', label: 'Hot Beverage' },
{ value: 'fluent-emoji-flat:hot-dog', icon: 'fluent-emoji-flat:hot-dog', label: 'Hot Dog' },
{ value: 'fluent-emoji-flat:hot-pepper', icon: 'fluent-emoji-flat:hot-pepper', label: 'Hot Pepper' },
{ value: 'fluent-emoji-flat:ice-cream', icon: 'fluent-emoji-flat:ice-cream', label: 'Ice Cream' },
{ value: 'fluent-emoji-flat:pancakes', icon: 'fluent-emoji-flat:pancakes', label: 'Pancake' },
{ value: 'fluent-emoji-flat:pizza', icon: 'fluent-emoji-flat:pizza', label: 'Pizza' },
{ value: 'fluent-emoji-flat:popcorn', icon: 'fluent-emoji-flat:popcorn', label: 'Popcorn' },
{ value: 'fluent-emoji-flat:pretzel', icon: 'fluent-emoji-flat:pretzel', label: 'Pretzel' },
{ value: 'fluent-emoji-flat:green-salad', icon: 'fluent-emoji-flat:green-salad', label: 'Salad' },
{ value: 'fluent-emoji-flat:sandwich', icon: 'fluent-emoji-flat:sandwich', label: 'Sandwich' },
{ value: 'fluent-emoji-flat:pot-of-food', icon: 'fluent-emoji-flat:pot-of-food', label: 'Soup' },
{ value: 'fluent-emoji-flat:spaghetti', icon: 'fluent-emoji-flat:spaghetti', label: 'Spaghetti' },
{ value: 'fluent-emoji-flat:cut-of-meat', icon: 'fluent-emoji-flat:cut-of-meat', label: 'Steak' },
{ value: 'fluent-emoji-flat:steaming-bowl', icon: 'fluent-emoji-flat:steaming-bowl', label: 'Stew' },
{ value: 'fluent-emoji-flat:stuffed-flatbread', icon: 'fluent-emoji-flat:stuffed-flatbread', label: 'Stuffed Flatbread' },
{ value: 'fluent-emoji-flat:sushi', icon: 'fluent-emoji-flat:sushi', label: 'Sushi' },
{ value: 'fluent-emoji-flat:taco', icon: 'fluent-emoji-flat:taco', label: 'Taco' },
{ value: 'fluent-emoji-flat:tamale', icon: 'fluent-emoji-flat:tamale', label: 'Tamale' },
{ value: 'fluent-emoji-flat:teacup-without-handle', icon: 'fluent-emoji-flat:teacup-without-handle', label: 'Tea' },
]
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing import for useI18n.

The useI18n function is used throughout the file but not imported.

Add the following import at the top of the file:

+import { useI18n } from 'vue-i18n'
 import type { CurrencyCode, Locale, LocaleValue, WeightUnit } from '@nextorders/schema'
📝 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
import type { CurrencyCode, Locale, LocaleValue, WeightUnit } from '@nextorders/schema'
export const CURRENCY_SIGNS: Record<CurrencyCode, string> = {
RUB: '₽',
USD: '$',
EUR: '€',
GEL: '₾',
BYN: 'Br',
UAH: '₴',
KZT: '₸',
PLN: 'zł',
TRY: '₺',
}
export function getLocaleValue(data: { values?: LocaleValue[], locale: Locale, defaultLocale?: Locale }): string {
if (!data.values || !Array.isArray(data.values)) {
return ''
}
const hasCurrentLocale = data.values.some((n) => n.locale === data.locale)
return data.values.find((v) => hasCurrentLocale ? v.locale === data.locale : v.locale === data.defaultLocale)?.value ?? ''
}
export function getWeightLocalizedUnit<WeightUnitLiteral = string & object>(unit?: WeightUnit | WeightUnitLiteral): string {
const { t } = useI18n()
switch (unit as WeightUnit) {
case 'G':
return t('common.abbreviation.g')
case 'KG':
return t('common.abbreviation.kg')
case 'ML':
return t('common.abbreviation.ml')
case 'L':
return t('common.abbreviation.l')
case 'LB':
return t('common.abbreviation.lb')
case 'OZ':
return t('common.abbreviation.oz')
default:
return ''
}
}
export function getLocalizedLanguageCodesForSelect(): { value: string, label: string }[] {
return [
{ value: 'en', label: 'EN - English' },
{ value: 'ru', label: 'RU - Русский' },
]
}
export function getLocalizedCountryCodesForSelect(): { value: string, label: string }[] {
const { t } = useI18n()
return [
{ value: 'BY', label: `BY - ${t('common.country.by')}` },
{ value: 'DE', label: `DE - ${t('common.country.de')}` },
{ value: 'ES', label: `ES - ${t('common.country.es')}` },
{ value: 'FR', label: `FR - ${t('common.country.fr')}` },
{ value: 'GB', label: `GB - ${t('common.country.gb')}` },
{ value: 'GE', label: `GE - ${t('common.country.ge')}` },
{ value: 'GR', label: `GR - ${t('common.country.gr')}` },
{ value: 'IT', label: `IT - ${t('common.country.it')}` },
{ value: 'KZ', label: `KZ - ${t('common.country.kz')}` },
{ value: 'PL', label: `PL - ${t('common.country.pl')}` },
{ value: 'RU', label: `RU - ${t('common.country.ru')}` },
{ value: 'TR', label: `TR - ${t('common.country.tr')}` },
{ value: 'UA', label: `UA - ${t('common.country.ua')}` },
{ value: 'US', label: `US - ${t('common.country.us')}` },
]
}
export function getLocalizedCurrencyCodesForSelect(): { value: string, label: string }[] {
const { t } = useI18n()
return [
{ value: 'EUR', label: `EUR - ${t('common.currency.eur')}` },
{ value: 'BYN', label: `BYN - ${t('common.currency.byn')}` },
{ value: 'GEL', label: `GEL - ${t('common.currency.gel')}` },
{ value: 'KZT', label: `KZT - ${t('common.currency.kzt')}` },
{ value: 'PLN', label: `PLN - ${t('common.currency.pln')}` },
{ value: 'RUB', label: `RUB - ${t('common.currency.rub')}` },
{ value: 'UAH', label: `UAH - ${t('common.currency.uah')}` },
{ value: 'USD', label: `USD - ${t('common.currency.usd')}` },
]
}
export function getLocalizedWeightUnitsForSelect(): { value: WeightUnit, label: string }[] {
const { t } = useI18n()
return [
{ value: 'KG', label: t('common.weight-unit.kg') },
{ value: 'G', label: t('common.weight-unit.g') },
{ value: 'L', label: t('common.weight-unit.l') },
{ value: 'ML', label: t('common.weight-unit.ml') },
{ value: 'OZ', label: t('common.weight-unit.oz') },
{ value: 'LB', label: t('common.weight-unit.lb') },
]
}
export function getLocalizedTimezonesForSelect(): { value: string, label: string }[] {
return [
{ value: '-12:00', label: '-12:00' },
{ value: '-11:00', label: '-11:00' },
{ value: '-10:00', label: '-10:00' },
{ value: '-09:00', label: '-09:00' },
{ value: '-08:00', label: '-08:00' },
{ value: '-07:00', label: '-07:00' },
{ value: '-06:00', label: '-06:00' },
{ value: '-05:00', label: '-05:00' },
{ value: '-04:00', label: '-04:00' },
{ value: '-03:00', label: '-03:00' },
{ value: '-02:00', label: '-02:00' },
{ value: '-01:00', label: '-01:00' },
{ value: '+00:00', label: '+00:00' },
{ value: '+01:00', label: '+01:00' },
{ value: '+02:00', label: '+02:00' },
{ value: '+03:00', label: '+03:00' },
{ value: '+04:00', label: '+04:00' },
{ value: '+05:00', label: '+05:00' },
{ value: '+06:00', label: '+06:00' },
{ value: '+07:00', label: '+07:00' },
{ value: '+08:00', label: '+08:00' },
{ value: '+09:00', label: '+09:00' },
{ value: '+10:00', label: '+10:00' },
{ value: '+11:00', label: '+11:00' },
{ value: '+12:00', label: '+12:00' },
{ value: '+13:00', label: '+13:00' },
{ value: '+14:00', label: '+14:00' },
]
}
export function getLinkIconsForSelect(): { value: string, label: string, icon?: string, type?: 'separator' }[] {
return [
{ value: 'i-lucide-smartphone', icon: 'i-lucide-smartphone', label: 'Phone' },
{ value: 'i-lucide-gift', icon: 'i-lucide-gift', label: 'Gift' },
{ value: 'i-lucide-info', icon: 'i-lucide-info', label: 'Info' },
{ type: 'separator', label: '', value: '' },
{ value: 'simple-icons:bluesky', icon: 'simple-icons:bluesky', label: 'Bluesky' },
{ value: 'simple-icons:facebook', icon: 'simple-icons:facebook', label: 'Facebook' },
{ value: 'simple-icons:github', icon: 'simple-icons:github', label: 'GitHub' },
{ value: 'simple-icons:instagram', icon: 'simple-icons:instagram', label: 'Instagram' },
{ value: 'simple-icons:odnoklassniki', icon: 'simple-icons:odnoklassniki', label: 'Odnoklassniki' },
{ value: 'simple-icons:telegram', icon: 'simple-icons:telegram', label: 'Telegram' },
{ value: 'simple-icons:viber', icon: 'simple-icons:viber', label: 'Viber' },
{ value: 'simple-icons:vk', icon: 'simple-icons:vk', label: 'VK' },
{ value: 'simple-icons:whatsapp', icon: 'simple-icons:whatsapp', label: 'WhatsApp' },
{ value: 'simple-icons:x', icon: 'simple-icons:x', label: 'X' },
{ value: 'simple-icons:youtube', icon: 'simple-icons:youtube', label: 'YouTube' },
]
}
export function getTargetVariantsForSelect(): { value: string, label: string }[] {
const { t } = useI18n()
return [
{ value: '_self', label: t('common.no') },
{ value: '_blank', label: t('common.yes') },
]
}
export function getCategoryIconsForSelect(): { value: string, label: string, icon?: string }[] {
return [
{ value: '', icon: '', label: 'No icon' },
{ value: 'fluent-emoji-flat:avocado', icon: 'fluent-emoji-flat:avocado', label: 'Avocado' },
{ value: 'fluent-emoji-flat:bacon', icon: 'fluent-emoji-flat:bacon', label: 'Bacon' },
{ value: 'fluent-emoji-flat:bagel', icon: 'fluent-emoji-flat:bagel', label: 'Bagel' },
{ value: 'fluent-emoji-flat:banana', icon: 'fluent-emoji-flat:banana', label: 'Banana' },
{ value: 'fluent-emoji-flat:baguette-bread', icon: 'fluent-emoji-flat:baguette-bread', label: 'Baguette' },
{ value: 'fluent-emoji-flat:beer-mug', icon: 'fluent-emoji-flat:beer-mug', label: 'Beer' },
{ value: 'fluent-emoji-flat:bento-box', icon: 'fluent-emoji-flat:bento-box', label: 'Bento' },
{ value: 'fluent-emoji-flat:beverage-box', icon: 'fluent-emoji-flat:beverage-box', label: 'Beverage' },
{ value: 'fluent-emoji-flat:birthday-cake', icon: 'fluent-emoji-flat:birthday-cake', label: 'Birthday Cake' },
{ value: 'fluent-emoji-flat:bread', icon: 'fluent-emoji-flat:bread', label: 'Bread' },
{ value: 'fluent-emoji-flat:broccoli', icon: 'fluent-emoji-flat:broccoli', label: 'Broccoli' },
{ value: 'fluent-emoji-flat:brown-mushroom', icon: 'fluent-emoji-flat:brown-mushroom', label: 'Brown Mushroom' },
{ value: 'fluent-emoji-flat:bubble-tea', icon: 'fluent-emoji-flat:bubble-tea', label: 'Bubble Tea' },
{ value: 'fluent-emoji-flat:burrito', icon: 'fluent-emoji-flat:burrito', label: 'Burrito' },
{ value: 'fluent-emoji-flat:candy', icon: 'fluent-emoji-flat:candy', label: 'Candy' },
{ value: 'fluent-emoji-flat:carrot', icon: 'fluent-emoji-flat:carrot', label: 'Carrot' },
{ value: 'fluent-emoji-flat:cheese-wedge', icon: 'fluent-emoji-flat:cheese-wedge', label: 'Cheese' },
{ value: 'fluent-emoji-flat:chocolate-bar', icon: 'fluent-emoji-flat:chocolate-bar', label: 'Chocolate' },
{ value: 'fluent-emoji-flat:shortcake', icon: 'fluent-emoji-flat:shortcake', label: 'Cake' },
{ value: 'fluent-emoji-flat:cocktail-glass', icon: 'fluent-emoji-flat:cocktail-glass', label: 'Cocktail' },
{ value: 'fluent-emoji-flat:cooked-rice', icon: 'fluent-emoji-flat:cooked-rice', label: 'Cooked Rice' },
{ value: 'fluent-emoji-flat:cookie', icon: 'fluent-emoji-flat:cookie', label: 'Cookie' },
{ value: 'fluent-emoji-flat:cooking', icon: 'fluent-emoji-flat:cooking', label: 'Cooking' },
{ value: 'fluent-emoji-flat:crab', icon: 'fluent-emoji-flat:crab', label: 'Crab' },
{ value: 'fluent-emoji-flat:croissant', icon: 'fluent-emoji-flat:croissant', label: 'Croissant' },
{ value: 'fluent-emoji-flat:cupcake', icon: 'fluent-emoji-flat:cupcake', label: 'Cupcake' },
{ value: 'fluent-emoji-flat:curry-rice', icon: 'fluent-emoji-flat:curry-rice', label: 'Curry' },
{ value: 'fluent-emoji-flat:doughnut', icon: 'fluent-emoji-flat:doughnut', label: 'Doughnut' },
{ value: 'fluent-emoji-flat:fork-and-knife', icon: 'fluent-emoji-flat:fork-and-knife', label: 'Fork' },
{ value: 'fluent-emoji-flat:french-fries', icon: 'fluent-emoji-flat:french-fries', label: 'French Fries' },
{ value: 'fluent-emoji-flat:fried-shrimp', icon: 'fluent-emoji-flat:fried-shrimp', label: 'Fried Shrimp' },
{ value: 'fluent-emoji-flat:hamburger', icon: 'fluent-emoji-flat:hamburger', label: 'Hamburger' },
{ value: 'fluent-emoji-flat:hot-beverage', icon: 'fluent-emoji-flat:hot-beverage', label: 'Hot Beverage' },
{ value: 'fluent-emoji-flat:hot-dog', icon: 'fluent-emoji-flat:hot-dog', label: 'Hot Dog' },
{ value: 'fluent-emoji-flat:hot-pepper', icon: 'fluent-emoji-flat:hot-pepper', label: 'Hot Pepper' },
{ value: 'fluent-emoji-flat:ice-cream', icon: 'fluent-emoji-flat:ice-cream', label: 'Ice Cream' },
{ value: 'fluent-emoji-flat:pancakes', icon: 'fluent-emoji-flat:pancakes', label: 'Pancake' },
{ value: 'fluent-emoji-flat:pizza', icon: 'fluent-emoji-flat:pizza', label: 'Pizza' },
{ value: 'fluent-emoji-flat:popcorn', icon: 'fluent-emoji-flat:popcorn', label: 'Popcorn' },
{ value: 'fluent-emoji-flat:pretzel', icon: 'fluent-emoji-flat:pretzel', label: 'Pretzel' },
{ value: 'fluent-emoji-flat:green-salad', icon: 'fluent-emoji-flat:green-salad', label: 'Salad' },
{ value: 'fluent-emoji-flat:sandwich', icon: 'fluent-emoji-flat:sandwich', label: 'Sandwich' },
{ value: 'fluent-emoji-flat:pot-of-food', icon: 'fluent-emoji-flat:pot-of-food', label: 'Soup' },
{ value: 'fluent-emoji-flat:spaghetti', icon: 'fluent-emoji-flat:spaghetti', label: 'Spaghetti' },
{ value: 'fluent-emoji-flat:cut-of-meat', icon: 'fluent-emoji-flat:cut-of-meat', label: 'Steak' },
{ value: 'fluent-emoji-flat:steaming-bowl', icon: 'fluent-emoji-flat:steaming-bowl', label: 'Stew' },
{ value: 'fluent-emoji-flat:stuffed-flatbread', icon: 'fluent-emoji-flat:stuffed-flatbread', label: 'Stuffed Flatbread' },
{ value: 'fluent-emoji-flat:sushi', icon: 'fluent-emoji-flat:sushi', label: 'Sushi' },
{ value: 'fluent-emoji-flat:taco', icon: 'fluent-emoji-flat:taco', label: 'Taco' },
{ value: 'fluent-emoji-flat:tamale', icon: 'fluent-emoji-flat:tamale', label: 'Tamale' },
{ value: 'fluent-emoji-flat:teacup-without-handle', icon: 'fluent-emoji-flat:teacup-without-handle', label: 'Tea' },
]
}
import { useI18n } from 'vue-i18n'
import type { CurrencyCode, Locale, LocaleValue, WeightUnit } from '@nextorders/schema'
export const CURRENCY_SIGNS: Record<CurrencyCode, string> = {
RUB: '₽',
USD: '$',
EUR: '€',
GEL: '₾',
BYN: 'Br',
UAH: '₴',
KZT: '₸',
PLN: 'zł',
TRY: '₺',
}
...

Comment on lines +1 to +2
import { repository } from '~~/server/services/db/repository'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing import for errorResolver

The code uses errorResolver on line 24 but doesn't import it. Add the missing import statement.

import { repository } from '~~/server/services/db/repository'
+import { errorResolver } from '~~/server/utils/errorResolver' // adjust path as needed
📝 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
import { repository } from '~~/server/services/db/repository'
import { repository } from '~~/server/services/db/repository'
import { errorResolver } from '~~/server/utils/errorResolver' // adjust path as needed


const master = await repository.user.findMaster()

const channels = await repository.channel.list()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Consider pagination for channels list

As the channels list grows, fetching all channels at once could impact performance. Consider implementing pagination or limiting the number of channels returned.

- const channels = await repository.channel.list()
+ // Consider pagination or limiting channels
+ const channels = await repository.channel.list({ limit: 50 })

Let's check if the list method supports pagination or limiting:


🏁 Script executed:

#!/bin/bash
# Check if channel.list supports pagination or limiting options
rg -A 5 "channel.list" --glob "*.ts"

Length of output: 437


Let’s locate where repository.channel comes from and inspect its list signature:


🏁 Script executed:

#!/bin/bash
# Show imports in the API handler to find where `repository` is coming from
echo "=== apps/essence/server/api/option/index.get.ts (lines 1–50) ==="
sed -n '1,50p' apps/essence/server/api/option/index.get.ts

# Find any channel-related repository implementation files
echo "=== channel*.ts files ==="
find . -type f -name "channel*.ts" | sed 's/^/  /'

# Grep for a `list` method inside those files (with context)
echo "=== Inspecting `list` in channel repository implementations ==="
rg -A5 "list\(" --glob "*/channel*.ts" || true

Length of output: 1604


Let’s inspect the channel repository implementation to see if list accepts any pagination options:


🏁 Script executed:

#!/bin/bash
# Display the channel repository to check `list` signature
echo "=== apps/essence/server/services/db/repository/channel.ts ==="
sed -n '1,200p' apps/essence/server/services/db/repository/channel.ts

Length of output: 1127


Add pagination support to Channel repository and API handler

The current implementation of repository.channel.list() fetches all channels without limits:

  • In apps/essence/server/api/option/index.get.ts:

    - const channels = await repository.channel.list()
    + // TODO: pass pagination params (e.g. limit/offset) once supported
    + const channels = await repository.channel.list({ limit: 50, offset: 0 })
  • In apps/essence/server/services/db/repository/channel.ts, update list() to accept and forward pagination options:

    - static async list() {
    -   return useDatabase().query.channels.findMany()
    - }
    + static async list(params: { limit?: number; offset?: number } = {}) {
    +   const { limit, offset } = params
    +   return useDatabase().query.channels.findMany({
    +     limit,
    +     offset,
    +   })
    + }

Steps to complete:

  • Confirm that findMany supports limit and offset (Drizzle-ORM).
  • Add type definitions for the pagination parameters.
  • Wire up client-driven pagination (e.g. query params) as needed.
📝 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
const channels = await repository.channel.list()
// TODO: pass pagination params (e.g. limit/offset) once supported
const channels = await repository.channel.list({ limit: 50, offset: 0 })

Comment on lines +106 to +108
} catch (error) {
console.error(error)
actionToast.error()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Error handling could provide more specific feedback.

The current error handling logs to console and shows a generic error toast without providing specific information to the user.

Improve error handling to provide more specific feedback:

  } catch (error) {
    console.error(error)
-   actionToast.error()
+   if (error.response?.data?.message) {
+     actionToast.error(error.response.data.message)
+   } else {
+     actionToast.error(t('toast.general-error'))
+   }
  }
📝 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
} catch (error) {
console.error(error)
actionToast.error()
} catch (error) {
console.error(error)
if (error.response?.data?.message) {
actionToast.error(error.response.data.message)
} else {
actionToast.error(t('toast.general-error'))
}
}

Comment on lines +99 to +136
"toast": {
"payment-method-created": "Payment Method created!",
"payment-method-updated": "Payment Method updated!",
"payment-method-deleted": "Payment Method deleted!",
"receiving-method-updated": "Method updated!",
"receiver-created": "Receiver created!",
"receiver-updated": "Receiver updated!",
"receiver-deleted": "Receiver deleted!",
"menu-created": "Menu created!",
"menu-updated": "Menu updated!",
"channel-created": "Channel created!",
"channel-updated": "Channel updated!",
"channel-deleted": "Channel deleted!",
"category-created": "Category created!",
"category-updated": "Category updated!",
"category-deleted": "Category deleted!",
"product-created": "Product created!",
"product-updated": "Product updated!",
"product-deleted": "Product deleted!",
"variant-created": "Product variant created!",
"variant-updated": "Product variant updated!",
"variant-deleted": "Product variant deleted!",
"link-created": "Link created!",
"link-updated": "Link updated!",
"link-deleted": "Link deleted!",
"page-created": "Page created!",
"page-updated": "Page updated!",
"page-deleted": "Page deleted!",
"warehouse-created": "Warehouse created!",
"warehouse-updated": "Warehouse updated!",
"website-configured": "Website configured!",
"website-updated": "Website updated!",
"account-configured": "Account configured!",
"working-day-updated": "Working day updated!",
"opening-hours-updated": "Opening hours updated!",
"photo-loaded": "Photo loaded!",
"options-updated": "Options updated!"
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Missing general error message in the toast section.

There's no general error message that could be used for unspecified errors or fallbacks.

Add a general error message to the toast section:

  "toast": {
+   "general-error": "An error occurred. Please try again.",
    "payment-method-created": "Payment Method created!",
📝 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
"toast": {
"payment-method-created": "Payment Method created!",
"payment-method-updated": "Payment Method updated!",
"payment-method-deleted": "Payment Method deleted!",
"receiving-method-updated": "Method updated!",
"receiver-created": "Receiver created!",
"receiver-updated": "Receiver updated!",
"receiver-deleted": "Receiver deleted!",
"menu-created": "Menu created!",
"menu-updated": "Menu updated!",
"channel-created": "Channel created!",
"channel-updated": "Channel updated!",
"channel-deleted": "Channel deleted!",
"category-created": "Category created!",
"category-updated": "Category updated!",
"category-deleted": "Category deleted!",
"product-created": "Product created!",
"product-updated": "Product updated!",
"product-deleted": "Product deleted!",
"variant-created": "Product variant created!",
"variant-updated": "Product variant updated!",
"variant-deleted": "Product variant deleted!",
"link-created": "Link created!",
"link-updated": "Link updated!",
"link-deleted": "Link deleted!",
"page-created": "Page created!",
"page-updated": "Page updated!",
"page-deleted": "Page deleted!",
"warehouse-created": "Warehouse created!",
"warehouse-updated": "Warehouse updated!",
"website-configured": "Website configured!",
"website-updated": "Website updated!",
"account-configured": "Account configured!",
"working-day-updated": "Working day updated!",
"opening-hours-updated": "Opening hours updated!",
"photo-loaded": "Photo loaded!",
"options-updated": "Options updated!"
}
"toast": {
"general-error": "An error occurred. Please try again.",
"payment-method-created": "Payment Method created!",
"payment-method-updated": "Payment Method updated!",
"payment-method-deleted": "Payment Method deleted!",
"receiving-method-updated": "Method updated!",
"receiver-created": "Receiver created!",
"receiver-updated": "Receiver updated!",
"receiver-deleted": "Receiver deleted!",
"menu-created": "Menu created!",
"menu-updated": "Menu updated!",
"channel-created": "Channel created!",
"channel-updated": "Channel updated!",
"channel-deleted": "Channel deleted!",
"category-created": "Category created!",
"category-updated": "Category updated!",
"category-deleted": "Category deleted!",
"product-created": "Product created!",
"product-updated": "Product updated!",
"product-deleted": "Product deleted!",
"variant-created": "Product variant created!",
"variant-updated": "Product variant updated!",
"variant-deleted": "Product variant deleted!",
"link-created": "Link created!",
"link-updated": "Link updated!",
"link-deleted": "Link deleted!",
"page-created": "Page created!",
"page-updated": "Page updated!",
"page-deleted": "Page deleted!",
"warehouse-created": "Warehouse created!",
"warehouse-updated": "Warehouse updated!",
"website-configured": "Website configured!",
"website-updated": "Website updated!",
"account-configured": "Account configured!",
"working-day-updated": "Working day updated!",
"opening-hours-updated": "Opening hours updated!",
"photo-loaded": "Photo loaded!",
"options-updated": "Options updated!"
}

Comment on lines +148 to +151
} catch (error) {
console.error(error)
actionToast.error()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance error handling in submit function

The error handling in the submit function only logs to console and shows a generic error toast. Consider providing more specific error information to the user.

   } catch (error) {
     console.error(error)
-    actionToast.error()
+    let errorMessage = t('error.default')
+    
+    if (error instanceof Error) {
+      console.error('Form submission error:', error.message)
+      
+      // Provide more specific error messages based on the error
+      if (error.message.includes('network')) {
+        errorMessage = t('error.network')
+      } else if (error.message.includes('validation')) {
+        errorMessage = t('error.validation')
+      }
+    }
+    
+    actionToast.error(errorMessage)
   }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +1 to +6
function _useActionToast() {
const toast = useToast()
const { t } = useI18n()

const id = ref(useId())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Potential issue with toast ID reuse

The toast ID is generated once and reused for all toast notifications. This could cause issues if multiple toasts need to be displayed simultaneously or in quick succession.

 function _useActionToast() {
   const toast = useToast()
   const { t } = useI18n()
 
-  const id = ref(useId())
+  // Generate a new ID for each toast operation to support multiple simultaneous toasts
+  function generateId() {
+    return useId()
+  }
+  
+  let currentId = ref('')

Then update all the functions to use this new approach:

 function start() {
+  currentId.value = generateId()
   toast.add({
-    id: id.value,
+    id: currentId.value,
     title: t('toast.in-process'),
     // ...

And similarly update the other methods. Also update the return value:

 return {
-  id: id.value,
+  id: currentId.value,
   start,
   // ...

Comment on lines +1 to +76
<template>
<UForm
:schema="ChannelCreateSchema"
:state="state"
class="flex flex-col gap-3"
@submit="onSubmit"
>
<UFormField :label="$t('common.title')" name="name">
<UInput
v-model="state.name"
size="xl"
class="w-full items-center justify-center"
/>
</UFormField>

<UFormField :label="$t('common.description')" name="description">
<UInput
v-model="state.description"
:placeholder="$t('common.optional')"
size="xl"
class="w-full items-center justify-center"
/>
</UFormField>

<UFormField :label="$t('common.language')" name="defaultLocale">
<USelect
v-model="state.defaultLocale"
:items="getLocalizedLanguageCodesForSelect()"
:placeholder="$t('common.select')"
size="xl"
class="w-full"
/>
</UFormField>

<UFormField :label="$t('app.country')" name="countryCode">
<USelect
v-model="state.countryCode"
:items="getLocalizedCountryCodesForSelect()"
:placeholder="$t('common.select')"
size="xl"
class="w-full"
/>
</UFormField>

<UFormField :label="$t('app.currency')" name="currencyCode">
<USelect
v-model="state.currencyCode"
:items="getLocalizedCurrencyCodesForSelect()"
:placeholder="$t('common.select')"
size="xl"
class="w-full"
/>
</UFormField>

<UFormField :label="$t('app.timezone')" name="timeZone">
<USelect
v-model="state.timeZone"
:items="getLocalizedTimezonesForSelect()"
:placeholder="$t('common.select')"
size="xl"
class="w-full"
/>
</UFormField>

<UButton
type="submit"
variant="solid"
color="primary"
size="xl"
block
class="mt-3"
>
{{ $t('app.create.title') }}
</UButton>
</UForm>
</template>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add form validation feedback.

The form uses a schema for validation but there's no visual feedback to help users understand validation errors before submitting.

Consider adding validation feedback by using the built-in validation features of UForm. For example:

<UForm
  :schema="ChannelCreateSchema"
  :state="state"
  class="flex flex-col gap-3"
+  validate-on="submit"
  @submit="onSubmit"
>

This will show validation errors when users attempt to submit an invalid form, helping them correct issues before sending data to the server.

📝 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
<template>
<UForm
:schema="ChannelCreateSchema"
:state="state"
class="flex flex-col gap-3"
@submit="onSubmit"
>
<UFormField :label="$t('common.title')" name="name">
<UInput
v-model="state.name"
size="xl"
class="w-full items-center justify-center"
/>
</UFormField>
<UFormField :label="$t('common.description')" name="description">
<UInput
v-model="state.description"
:placeholder="$t('common.optional')"
size="xl"
class="w-full items-center justify-center"
/>
</UFormField>
<UFormField :label="$t('common.language')" name="defaultLocale">
<USelect
v-model="state.defaultLocale"
:items="getLocalizedLanguageCodesForSelect()"
:placeholder="$t('common.select')"
size="xl"
class="w-full"
/>
</UFormField>
<UFormField :label="$t('app.country')" name="countryCode">
<USelect
v-model="state.countryCode"
:items="getLocalizedCountryCodesForSelect()"
:placeholder="$t('common.select')"
size="xl"
class="w-full"
/>
</UFormField>
<UFormField :label="$t('app.currency')" name="currencyCode">
<USelect
v-model="state.currencyCode"
:items="getLocalizedCurrencyCodesForSelect()"
:placeholder="$t('common.select')"
size="xl"
class="w-full"
/>
</UFormField>
<UFormField :label="$t('app.timezone')" name="timeZone">
<USelect
v-model="state.timeZone"
:items="getLocalizedTimezonesForSelect()"
:placeholder="$t('common.select')"
size="xl"
class="w-full"
/>
</UFormField>
<UButton
type="submit"
variant="solid"
color="primary"
size="xl"
block
class="mt-3"
>
{{ $t('app.create.title') }}
</UButton>
</UForm>
</template>
<template>
<UForm
:schema="ChannelCreateSchema"
:state="state"
class="flex flex-col gap-3"
validate-on="submit"
@submit="onSubmit"
>

Comment on lines +28 to +32
:items="getLocalizedLanguageCodesForSelect()"
:placeholder="$t('common.select')"
size="xl"
class="w-full"
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Import missing helper functions.

The component is using getLocalizedLanguageCodesForSelect() but only imports getLocalizedCountryCodesForSelect. You need to import the other helper functions used in the component.

-import { getLocalizedCountryCodesForSelect } from '../../utils/helpers'
+import { 
+  getLocalizedCountryCodesForSelect,
+  getLocalizedLanguageCodesForSelect,
+  getLocalizedCurrencyCodesForSelect,
+  getLocalizedTimezonesForSelect
+} from '../../utils/helpers'

Comment on lines +65 to +74
<UButton
type="submit"
variant="solid"
color="primary"
size="xl"
block
class="mt-3"
>
{{ $t('app.create.title') }}
</UButton>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add loading state to submit button.

The form submission process is asynchronous, but there's no visual indication to users that the submission is in progress. Add a loading state to prevent multiple submissions and improve user experience.

<UButton
  type="submit"
  variant="solid"
  color="primary"
  size="xl"
  block
+  :loading="isSubmitting"
  class="mt-3"
>
  {{ $t('app.create.title') }}
</UButton>

And add a reactive state to track submission:

const state = ref<Partial<ChannelCreate>>({
  name: undefined,
  description: undefined,
  defaultLocale: undefined,
  currencyCode: undefined,
  countryCode: undefined,
  timeZone: undefined,
})
+const isSubmitting = ref(false)

Update the submission handler:

async function onSubmit(event: FormSubmitEvent<ChannelCreate>) {
+  if (isSubmitting.value) return
+  isSubmitting.value = true
  actionToast.start()
  emit('submitted')

  try {
    // ... existing code
  } catch (error) {
    // ... existing code
  } finally {
+    isSubmitting.value = false
  }
}

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 7, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
25.5% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/essence/app/utils/ui.ts (1)

4-19: Consider improving type safety for createValidator.

The implementation looks good overall, but I would recommend adding more specific types instead of using any for the schema and state parameters. This would provide better compile-time safety and developer experience.

- export function createValidator(schema: any) {
+ export function createValidator<T>(schema: ReturnType<typeof type>) {

  const { t } = useI18n()

-  return function (state: any) {
+  return function (state: T) {
    const errors: FormError[] = []
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1298c7f and 62e6f73.

📒 Files selected for processing (9)
  • apps/essence/app/components/form/InitOption.vue (1 hunks)
  • apps/essence/app/pages/settings/index.vue (1 hunks)
  • apps/essence/app/utils/ui.ts (1 hunks)
  • apps/essence/i18n/locales/en-US.json (1 hunks)
  • apps/essence/i18n/locales/ru-RU.json (1 hunks)
  • packages/schema/src/services/option.ts (1 hunks)
  • packages/schema/src/types/channel.ts (1 hunks)
  • packages/schema/src/types/date.ts (1 hunks)
  • packages/schema/src/types/food.ts (2 hunks)
✅ Files skipped from review due to trivial changes (3)
  • apps/essence/app/pages/settings/index.vue
  • packages/schema/src/types/food.ts
  • packages/schema/src/types/channel.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/schema/src/services/option.ts
  • apps/essence/i18n/locales/ru-RU.json
  • apps/essence/app/components/form/InitOption.vue
  • apps/essence/i18n/locales/en-US.json
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (3)
packages/schema/src/types/date.ts (1)

12-12: The schema enhancement looks good.

Adding .describe('error.not-selected') to the TimeZoneSchema improves error handling consistency with other schemas in the project. This will enable proper error message localization for timezone validation.

apps/essence/app/utils/ui.ts (2)

5-5: Verify Composition API usage.

Using useI18n() outside of a Vue component's setup function might cause issues since composables typically rely on a component context. Ensure that this utility works correctly in your application's context.

Consider wrapping this in a function that's called within the component context or use a non-composable alternative if needed.


13-13: Robust error message fallback.

Good approach using the schema's description for error messages with a fallback to 'error.common'. This ensures that validation errors are always communicated to the user, even if a schema doesn't have a description.

@hmbanan666 hmbanan666 merged commit d689f17 into main May 7, 2025
5 of 6 checks passed
@hmbanan666 hmbanan666 deleted the pages branch May 7, 2025 15:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant