├── .dockerignore
├── .github
└── workflows
│ ├── build-dev-image.yml
│ ├── build-image.yml
│ └── run-checks.yml
├── .gitignore
├── CHANGELOG.md
├── Dockerfile
├── LICENSE
├── README.md
├── art
├── db.png
├── hero.png
├── herov4.png
├── import.png
├── landing2.png
├── landing3.png
├── logo.png
├── logo_dark.png
├── organize.png
├── v3-landing.png
└── your-shelf.png
├── crowdin.yml
├── docker
├── config.js
├── entrypoint.sh
└── nginx.conf
├── env.d.ts
├── eslint.config.js
├── index.html
├── package-lock.json
├── package.json
├── public
├── 404.png
├── favicon.png
├── fonts
│ ├── inter400.woff2
│ ├── inter700.woff2
│ ├── mono400.woff2
│ ├── noticia400.woff2
│ └── noticia700.woff2
├── icons
│ ├── maskable_icon.png
│ ├── maskable_icon_x128.png
│ ├── maskable_icon_x48.png
│ ├── maskable_icon_x512.png
│ ├── pwa256.png
│ └── pwa512.png
├── logo-black.png
├── no-cocktail.jpg
├── no-ingredient.png
└── robots.txt
├── src
├── App.vue
├── AppState.ts
├── AuthLayout.vue
├── PrintLayout.vue
├── api
│ ├── BarAssistantClient.ts
│ ├── SearchResults.d.ts
│ └── api.d.ts
├── assets
│ ├── alerts.css
│ ├── base.css
│ ├── blocks.css
│ ├── buttons.css
│ ├── chips.css
│ ├── dialog.css
│ ├── dropdown.css
│ ├── fonts.css
│ ├── forms.css
│ ├── grid.css
│ ├── main.css
│ ├── search.css
│ ├── table.css
│ └── tags.css
├── components
│ ├── AmountInput.vue
│ ├── Auth
│ │ ├── AuthConfirmation.vue
│ │ ├── AuthForgotPassword.vue
│ │ ├── AuthLogin.vue
│ │ ├── AuthRegister.vue
│ │ ├── AuthResetPassword.vue
│ │ ├── OauthCallback.vue
│ │ └── ProviderList.vue
│ ├── Bar
│ │ ├── BarForm.vue
│ │ ├── BarIndex.vue
│ │ └── BarJoinDialog.vue
│ ├── Calculator
│ │ ├── CalculatorForm.vue
│ │ ├── CalculatorImportDialog.vue
│ │ ├── CalculatorRender.vue
│ │ ├── CalculatorRenderBlock.vue
│ │ ├── CalculatorsIndex.vue
│ │ ├── CocktailPriceCalculator.vue
│ │ └── CocktailQuantityCalculator.vue
│ ├── CloseButton.vue
│ ├── Cocktail
│ │ ├── CocktailDetails.vue
│ │ ├── CocktailForm.vue
│ │ ├── CocktailGridContainer.vue
│ │ ├── CocktailGridItem.vue
│ │ ├── CocktailImport.vue
│ │ ├── CocktailIndex.vue
│ │ ├── CocktailIngredient.vue
│ │ ├── CocktailIngredientModal.vue
│ │ ├── CocktailIngredientShare.vue
│ │ ├── CocktailPrice.vue
│ │ ├── CocktailPublicDetails.vue
│ │ ├── CocktailRating.vue
│ │ ├── CocktailThumb.vue
│ │ ├── GenerateImageDialog.vue
│ │ ├── PublicLinkDialog.vue
│ │ ├── PublicRecipe.vue
│ │ ├── SimilarCocktails.vue
│ │ └── SubstituteModal.vue
│ ├── CocktailFinder.vue
│ ├── Collections
│ │ ├── CollectionDetailsWidget.vue
│ │ ├── CollectionDialog.vue
│ │ ├── CollectionForm.vue
│ │ ├── CollectionIndex.vue
│ │ └── CollectionWidget.vue
│ ├── DateFormatter.vue
│ ├── Dialog
│ │ ├── ConfirmDialog.vue
│ │ ├── SaltRimDialog.vue
│ │ └── plugin.js
│ ├── EmptyState.vue
│ ├── Feeds
│ │ ├── FeedsIndex.vue
│ │ └── FeedsRecipe.vue
│ ├── GlassSelector.vue
│ ├── Icons
│ │ ├── IconBarShelf.vue
│ │ ├── IconCalculator.vue
│ │ ├── IconCheck.vue
│ │ ├── IconClose.vue
│ │ ├── IconCocktail.vue
│ │ ├── IconExternal.vue
│ │ ├── IconFavorite.vue
│ │ ├── IconJigger.vue
│ │ ├── IconMedal.vue
│ │ ├── IconMore.vue
│ │ ├── IconPublicLink.vue
│ │ ├── IconRecommender.vue
│ │ ├── IconShoppingCart.vue
│ │ ├── IconStar.vue
│ │ └── IconUserShelf.vue
│ ├── ImageEditor.vue
│ ├── ImageThumb.vue
│ ├── ImageUpload.vue
│ ├── Ingredient
│ │ ├── Hierarchy
│ │ │ ├── IngredientTreeNode.vue
│ │ │ └── IngredientTreeNodeItem.vue
│ │ ├── IngredientDetails.vue
│ │ ├── IngredientForm.vue
│ │ ├── IngredientGridContainer.vue
│ │ ├── IngredientGridItem.vue
│ │ ├── IngredientHierarchy.vue
│ │ ├── IngredientImage.vue
│ │ ├── IngredientImport.vue
│ │ ├── IngredientIndex.vue
│ │ ├── IngredientListContainer.vue
│ │ ├── IngredientSpotlight.vue
│ │ └── RecommendedIngredients.vue
│ ├── IngredientFinder.vue
│ ├── IngredientFinderBasic.vue
│ ├── Layout
│ │ ├── SiteFooter.vue
│ │ ├── SiteHeader.vue
│ │ └── SiteLogo.vue
│ ├── ListItemContainer.vue
│ ├── Menu
│ │ ├── MenuIndex.vue
│ │ └── MenuPublicIndex.vue
│ ├── MiniRating.vue
│ ├── Note
│ │ ├── NoteDetails.vue
│ │ └── NoteDialog.vue
│ ├── OverlayLoader.vue
│ ├── PageHeader.vue
│ ├── Print
│ │ ├── PrintCocktail.vue
│ │ └── PrintShoppingList.vue
│ ├── RatingActions.vue
│ ├── SaltRimCheckbox.vue
│ ├── SaltRimDropdown.vue
│ ├── SaltRimRadio.vue
│ ├── SaltRimSpinner.vue
│ ├── Search
│ │ ├── FilterIngredientsModal.vue
│ │ ├── OffCanvas.vue
│ │ ├── SearchPagination.vue
│ │ └── SearchRefinement.vue
│ ├── Settings
│ │ ├── APIForm.vue
│ │ ├── APIList.vue
│ │ ├── BillingInfo.vue
│ │ ├── CocktailMethodsForm.vue
│ │ ├── CocktailMethodsList.vue
│ │ ├── ExportForm.vue
│ │ ├── ExportsList.vue
│ │ ├── GlassForm.vue
│ │ ├── GlassesList.vue
│ │ ├── PriceCategoriesList.vue
│ │ ├── PriceCategoryForm.vue
│ │ ├── ProfileForm.vue
│ │ ├── SettingsNavigation.vue
│ │ ├── TagForm.vue
│ │ ├── TagsList.vue
│ │ ├── UserForm.vue
│ │ ├── UsersList.vue
│ │ ├── UtensilForm.vue
│ │ └── UtensilsList.vue
│ ├── Shelf
│ │ └── ShelfIndex.vue
│ ├── ShoppingList
│ │ └── ShoppingListIndex.vue
│ ├── SiteAutocomplete.vue
│ ├── SourcePresenter.vue
│ ├── StatusCheck.vue
│ ├── SubscriptionCheck.vue
│ ├── TagSelector.vue
│ ├── ThemeToggle.vue
│ ├── TimeStamps.vue
│ ├── ToggleIngredientBarShelf.vue
│ ├── ToggleIngredientShelf.vue
│ ├── ToggleIngredientShoppingCart.vue
│ ├── Units
│ │ ├── UnitConverter.vue
│ │ └── UnitPicker.vue
│ └── WakeLockToggle.vue
├── composables
│ ├── __tests__
│ │ ├── useHtmlDecode.test.ts
│ │ ├── useRecommendedAmount.test.ts
│ │ └── useUnits.test.ts
│ ├── confirm.ts
│ ├── eventBus.ts
│ ├── ingredientBg.ts
│ ├── title.ts
│ ├── toast.ts
│ ├── useAuth.ts
│ ├── useGetRoleName.ts
│ ├── useHtmlDecode.ts
│ ├── useRecommendedAmounts.ts
│ ├── useTheme.ts
│ └── useUnits.ts
├── locales
│ ├── de-DE.js
│ ├── en-US.js
│ ├── fr-FR.js
│ ├── hr-HR.js
│ ├── it-IT.js
│ ├── messages
│ │ ├── cs-CZ.json
│ │ ├── da-DK.json
│ │ ├── de-DE.json
│ │ ├── el-GR.json
│ │ ├── en-US.json
│ │ ├── es-ES.json
│ │ ├── fi-FI.json
│ │ ├── fr-FR.json
│ │ ├── hr-HR.json
│ │ ├── hu-HU.json
│ │ ├── it-IT.json
│ │ ├── nl-NL.json
│ │ ├── no-NO.json
│ │ ├── pl-PL.json
│ │ ├── pt-PT.json
│ │ ├── ro-RO.json
│ │ ├── sv-SE.json
│ │ ├── zh-CN.json
│ │ └── zh-TW.json
│ ├── pl-PL.js
│ ├── sv-SE.js
│ └── zh-CN.js
├── main.ts
├── router
│ └── index.js
└── views
│ ├── BarFormView.vue
│ ├── BarJoinView.vue
│ ├── BarsView.vue
│ ├── CalculatorFormView.vue
│ ├── CalculatorsIndex.vue
│ ├── CocktailCollections.vue
│ ├── CocktailPrintView.vue
│ ├── CocktailView.vue
│ ├── CocktailsFormView.vue
│ ├── CocktailsScrapeView.vue
│ ├── CocktailsView.vue
│ ├── ConfirmationView.vue
│ ├── FeedsView.vue
│ ├── ForgotPasswordView.vue
│ ├── HomeView.vue
│ ├── IngredientFormView.vue
│ ├── IngredientImport.vue
│ ├── IngredientView.vue
│ ├── IngredientsView.vue
│ ├── LoginView.vue
│ ├── MenuPublicView.vue
│ ├── MenuView.vue
│ ├── OauthCallbackView.vue
│ ├── PageNotFound.vue
│ ├── ProfileView.vue
│ ├── PublicCocktailView.vue
│ ├── QuantityCalcView.vue
│ ├── RegisterView.vue
│ ├── ResetPasswordView.vue
│ ├── ServiceDownView.vue
│ ├── SettingsAPIView.vue
│ ├── SettingsBillingView.vue
│ ├── SettingsCocktailMethodsView.vue
│ ├── SettingsExportsView.vue
│ ├── SettingsGlassesView.vue
│ ├── SettingsPriceCategoriesView.vue
│ ├── SettingsProfileView.vue
│ ├── SettingsTagsView.vue
│ ├── SettingsUsersView.vue
│ ├── SettingsUtensilsView.vue
│ ├── ShelfShoppingListPrintView.vue
│ └── ShoppingListView.vue
├── tsconfig.json
└── vite.config.js
/.dockerignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | .env
3 | .env.dist
4 | README.md
5 | /art
6 | public/config.js
7 | /dist
8 | /.github
9 | /.vscode
10 | /.git
11 |
12 | Dockerfile
13 | .dockerignore
14 | crowdin.yml
--------------------------------------------------------------------------------
/.github/workflows/build-dev-image.yml:
--------------------------------------------------------------------------------
1 | name: Build unstable docker image
2 |
3 | on:
4 | push:
5 | branches:
6 | - 'develop'
7 |
8 | jobs:
9 | docker:
10 | runs-on: ubuntu-latest
11 | permissions:
12 | contents: read
13 | packages: write
14 | steps:
15 | -
16 | name: Checkout
17 | uses: actions/checkout@v3
18 | -
19 | name: Set up QEMU
20 | uses: docker/setup-qemu-action@v2
21 | -
22 | name: Set up Docker Buildx
23 | uses: docker/setup-buildx-action@v2
24 | -
25 | name: Login to Docker Hub
26 | uses: docker/login-action@v2
27 | with:
28 | username: ${{ secrets.HUB_BASS_USERNAME }}
29 | password: ${{ secrets.HUB_BASS_TOKEN }}
30 | -
31 | name: Login to GitHub Container Registry
32 | uses: docker/login-action@v2
33 | with:
34 | registry: ghcr.io
35 | username: ${{ github.actor }}
36 | password: ${{ secrets.GITHUB_TOKEN }}
37 | -
38 | name: Build and push
39 | uses: docker/build-push-action@v3
40 | with:
41 | context: .
42 | platforms: linux/amd64,linux/arm64
43 | push: true
44 | build-args: BUILD_VERSION=develop
45 | tags: |
46 | barassistant/salt-rim:dev
47 | ghcr.io/${{ github.repository_owner }}/salt-rim:dev
48 | cache-from: type=gha
49 | cache-to: type=gha,mode=max
50 |
--------------------------------------------------------------------------------
/.github/workflows/build-image.yml:
--------------------------------------------------------------------------------
1 | name: Build stable docker image
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 |
8 | jobs:
9 | docker:
10 | runs-on: ubuntu-latest
11 | permissions:
12 | contents: read
13 | packages: write
14 | steps:
15 | -
16 | name: Checkout
17 | uses: actions/checkout@v3
18 | -
19 | name: Docker meta
20 | id: meta
21 | uses: docker/metadata-action@v5
22 | with:
23 | images: |
24 | barassistant/salt-rim
25 | ghcr.io/${{ github.repository_owner }}/salt-rim
26 | flavor: |
27 | latest=false
28 | tags: |
29 | type=semver,pattern={{version}}
30 | type=semver,pattern={{major}}.{{minor}}
31 | type=semver,pattern=v{{major}}
32 | -
33 | name: Set up QEMU
34 | uses: docker/setup-qemu-action@v2
35 | -
36 | name: Set up Docker Buildx
37 | uses: docker/setup-buildx-action@v2
38 | -
39 | name: Login to Docker Hub
40 | uses: docker/login-action@v2
41 | with:
42 | username: ${{ secrets.HUB_BASS_USERNAME }}
43 | password: ${{ secrets.HUB_BASS_TOKEN }}
44 | -
45 | name: Login to GitHub Container Registry
46 | uses: docker/login-action@v2
47 | with:
48 | registry: ghcr.io
49 | username: ${{ github.actor }}
50 | password: ${{ secrets.GITHUB_TOKEN }}
51 | -
52 | name: Build and push
53 | uses: docker/build-push-action@v3
54 | with:
55 | context: .
56 | platforms: linux/amd64,linux/arm64
57 | build-args: BUILD_VERSION=${{github.ref_name}}
58 | push: true
59 | tags: ${{ steps.meta.outputs.tags }}
60 |
--------------------------------------------------------------------------------
/.github/workflows/run-checks.yml:
--------------------------------------------------------------------------------
1 | name: Run Vue checks
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | permissions:
13 | contents: read
14 |
15 | steps:
16 | - name: Checkout repository
17 | uses: actions/checkout@v3
18 |
19 | - name: Set up Node.js
20 | uses: actions/setup-node@v3
21 | with:
22 | node-version: '22'
23 |
24 | - name: Install dependencies
25 | run: npm install
26 |
27 | - name: Run type checks
28 | run: npm run type-check
29 |
30 | - name: Run ESLint
31 | run: npm run lint
32 |
33 | - name: Run tests
34 | run: npm run test
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .env
3 | dist/
4 | public/config.js
5 | .vscode
6 | spec.yml
7 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:current-alpine3.20 AS build
2 |
3 | ARG BUILD_VERSION
4 | ENV BUILD_VERSION=${BUILD_VERSION:-develop}
5 |
6 | WORKDIR /app
7 | COPY package*.json .
8 | RUN npm install
9 | COPY . .
10 |
11 | RUN sed -i "s/{{VERSION}}/$BUILD_VERSION/g" ./docker/config.js
12 |
13 | RUN npm run build
14 |
15 | FROM nginx AS prod
16 |
17 | LABEL org.opencontainers.image.source="https://github.com/karlomikus/vue-salt-rim"
18 |
19 | COPY --from=build /app/dist /var/www/html
20 |
21 | COPY --from=build /app/docker/config.js /var/www/config.js
22 | COPY ./docker/nginx.conf /etc/nginx/nginx.conf
23 | COPY ./docker/entrypoint.sh /usr/local/bin/entrypoint
24 |
25 | RUN chmod +x /usr/local/bin/entrypoint
26 |
27 | EXPOSE 8080
28 |
29 | CMD [ "entrypoint" ]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Karlo Mikuš
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/art/db.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/art/db.png
--------------------------------------------------------------------------------
/art/hero.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/art/hero.png
--------------------------------------------------------------------------------
/art/herov4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/art/herov4.png
--------------------------------------------------------------------------------
/art/import.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/art/import.png
--------------------------------------------------------------------------------
/art/landing2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/art/landing2.png
--------------------------------------------------------------------------------
/art/landing3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/art/landing3.png
--------------------------------------------------------------------------------
/art/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/art/logo.png
--------------------------------------------------------------------------------
/art/logo_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/art/logo_dark.png
--------------------------------------------------------------------------------
/art/organize.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/art/organize.png
--------------------------------------------------------------------------------
/art/v3-landing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/art/v3-landing.png
--------------------------------------------------------------------------------
/art/your-shelf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/art/your-shelf.png
--------------------------------------------------------------------------------
/crowdin.yml:
--------------------------------------------------------------------------------
1 | files:
2 | - source: /src/locales/messages/en-US.json
3 | translation: /src/locales/messages/%locale%.json
4 |
--------------------------------------------------------------------------------
/docker/config.js:
--------------------------------------------------------------------------------
1 | window.srConfig = {}
2 | window.srConfig.VERSION = "{{VERSION}}"
3 | window.srConfig.API_URL = "$API_URL"
4 | window.srConfig.MEILISEARCH_URL = "$MEILISEARCH_URL";
5 | window.srConfig.DEFAULT_LOCALE = "$DEFAULT_LOCALE";
6 | window.srConfig.ENV = "$SALT_RIM_ENV";
7 | window.srConfig.MAILS_ENABLED = "$MAILS_ENABLED" === 'true';
8 | window.srConfig.BILLING_TOKEN = "$BILLING_TOKEN";
9 | window.srConfig.BILLING_ENABLED = "$BILLING_ENABLED" === 'true';
10 | window.srConfig.BILLING_ENV = "$BILLING_ENV";
11 | window.srConfig.ANALYTICS_HOST = "$ANALYTICS_HOST";
12 | window.srConfig.ALLOW_REGISTRATION = "$ALLOW_REGISTRATION";
13 | window.srConfig.SENTRY_DSN = "$SENTRY_DSN";
14 | window.srConfig.REDIRECT_TO_SSO = "$REDIRECT_TO_SSO" === 'true';
15 |
--------------------------------------------------------------------------------
/docker/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | envsubst < /var/www/config.js > /var/www/html/config.js
4 |
5 | exec nginx -g "daemon off;"
6 |
--------------------------------------------------------------------------------
/docker/nginx.conf:
--------------------------------------------------------------------------------
1 | worker_processes 1;
2 |
3 | events { worker_connections 1024; }
4 |
5 | http {
6 | include mime.types;
7 |
8 | sendfile on;
9 | tcp_nopush on;
10 | tcp_nodelay on;
11 |
12 | access_log off;
13 | error_log stderr error;
14 |
15 | client_max_body_size 100M;
16 |
17 | server {
18 | listen 8080 default_server;
19 | listen [::]:8080 default_server;
20 | server_name _;
21 | root /var/www/html/;
22 |
23 | location / {
24 | try_files $uri $uri/ /index.html;
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | declare module 'sortablejs'
5 | declare module '@types/slug'
6 |
7 | interface Window {
8 | srConfig: {
9 | VERSION: string,
10 | API_URL: string,
11 | MEILISEARCH_URL: string,
12 | DEFAULT_LOCALE: string,
13 | ENV: string,
14 | MAILS_ENABLED: boolean,
15 | BILLING_TOKEN: string,
16 | BILLING_ENABLED: boolean,
17 | BILLING_ENV: string,
18 | ANALYTICS_HOST: string,
19 | ALLOW_REGISTRATION: string,
20 | SENTRY_DSN: string,
21 | REDIRECT_TO_SSO: boolean,
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import pluginVue from 'eslint-plugin-vue'
2 | import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
3 | import pluginVitest from '@vitest/eslint-plugin'
4 |
5 | export default defineConfigWithVueTs(
6 | {
7 | name: 'app/files-to-lint',
8 | files: ['**/*.{ts,mts,tsx,vue}'],
9 | },
10 |
11 | {
12 | name: 'app/files-to-ignore',
13 | ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'],
14 | },
15 |
16 | pluginVue.configs['flat/base'],
17 | vueTsConfigs.recommended,
18 |
19 | {
20 | rules: {
21 | "vue/html-indent": ["warn", 4],
22 | "vue/max-attributes-per-line": ['off'],
23 | "vue/block-lang": ['off'],
24 | "vue/multiline-html-element-content-newline": ['off'],
25 | "vue/html-self-closing": ['off'],
26 | "vue/no-v-html": ['off'],
27 | "vue/singleline-html-element-content-newline": ['off'],
28 | "vue/v-on-event-hyphenation": ['warn', 'always', {
29 | autofix: true
30 | }],
31 | "@typescript-eslint/no-explicit-any": ['off'],
32 | "@typescript-eslint/no-unused-vars": ['off'],
33 | "no-unused-expressions": "off",
34 | "@typescript-eslint/no-unused-expressions": "off",
35 | "@typescript-eslint/ban-ts-comment": "off",
36 | "@typescript-eslint/no-this-alias": "off",
37 | }
38 | },
39 |
40 | {
41 | ...pluginVitest.configs.recommended,
42 | files: ['src/**/__tests__/*'],
43 | },
44 | )
45 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Bar Assistant
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-salt-rim",
3 | "type": "module",
4 | "scripts": {
5 | "dev": "vite",
6 | "build": "vite build",
7 | "preview": "vite preview --port 4173",
8 | "lint": "eslint src/",
9 | "lint-fix": "eslint --fix src",
10 | "test": "vitest",
11 | "type-check": "vue-tsc --build",
12 | "gen": "npx openapi-typescript ./spec.yml -o ./src/api/api.d.ts"
13 | },
14 | "dependencies": {
15 | "@floating-ui/vue": "^1.1.5",
16 | "@meilisearch/instant-meilisearch": "^0.25.0",
17 | "@paddle/paddle-js": "^1.2.1",
18 | "@sentry/vue": "^8.47.0",
19 | "@types/sortablejs": "^1.15.8",
20 | "@vueuse/core": "^11.0.3",
21 | "cropperjs": "^1.6.1",
22 | "dayjs": "^1.11.7",
23 | "entities": "^6.0.0",
24 | "format-quantity": "^3.0.0",
25 | "html-to-image": "^1.11.11",
26 | "mathjs": "^14.0.1",
27 | "micromark": "^4.0.0",
28 | "numeric-quantity": "^2.0.1",
29 | "openapi-fetch": "^0.11.1",
30 | "plausible-tracker": "^0.3.8",
31 | "qrcode-vue3": "^1.6.8",
32 | "remove-markdown": "^0.5.0",
33 | "slug": "^8.2.3",
34 | "sortablejs": "^1.15.0",
35 | "swiper": "^11.0.0",
36 | "thumbhash": "^0.1.1",
37 | "treeflex": "^2.0.1",
38 | "vue": "^3.2.40",
39 | "vue-i18n": "^10.0.0",
40 | "vue-instantsearch": "^4.6.0",
41 | "vue-router": "^4.1.5",
42 | "vue-toast-notification": "^3.0.4"
43 | },
44 | "devDependencies": {
45 | "@types/slug": "^5.0.9",
46 | "@vitejs/plugin-vue": "^5.2.1",
47 | "@vitest/eslint-plugin": "1.1.31",
48 | "@vue/eslint-config-typescript": "^14.4.0",
49 | "@vue/tsconfig": "^0.7.0",
50 | "eslint": "^9.22.0",
51 | "eslint-plugin-vue": "^10.0",
52 | "openapi-typescript": "^7.3.0",
53 | "typescript": "^5.5.4",
54 | "vite": "^6.2.3",
55 | "vite-plugin-pwa": "^0.21.1",
56 | "vite-plugin-vue-devtools": "^7.7.2",
57 | "vitest": "^3.0.8",
58 | "vue-tsc": "^2.2.2"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/public/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/public/404.png
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/public/favicon.png
--------------------------------------------------------------------------------
/public/fonts/inter400.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/public/fonts/inter400.woff2
--------------------------------------------------------------------------------
/public/fonts/inter700.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/public/fonts/inter700.woff2
--------------------------------------------------------------------------------
/public/fonts/mono400.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/public/fonts/mono400.woff2
--------------------------------------------------------------------------------
/public/fonts/noticia400.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/public/fonts/noticia400.woff2
--------------------------------------------------------------------------------
/public/fonts/noticia700.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/public/fonts/noticia700.woff2
--------------------------------------------------------------------------------
/public/icons/maskable_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/public/icons/maskable_icon.png
--------------------------------------------------------------------------------
/public/icons/maskable_icon_x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/public/icons/maskable_icon_x128.png
--------------------------------------------------------------------------------
/public/icons/maskable_icon_x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/public/icons/maskable_icon_x48.png
--------------------------------------------------------------------------------
/public/icons/maskable_icon_x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/public/icons/maskable_icon_x512.png
--------------------------------------------------------------------------------
/public/icons/pwa256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/public/icons/pwa256.png
--------------------------------------------------------------------------------
/public/icons/pwa512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/public/icons/pwa512.png
--------------------------------------------------------------------------------
/public/logo-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/public/logo-black.png
--------------------------------------------------------------------------------
/public/no-cocktail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/public/no-cocktail.jpg
--------------------------------------------------------------------------------
/public/no-ingredient.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlomikus/vue-salt-rim/6da49322f568357f4b9142be1de356620d6d2db3/public/no-ingredient.png
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow: /
3 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/AuthLayout.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/PrintLayout.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 | {{ $t('print') }}
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/api/SearchResults.d.ts:
--------------------------------------------------------------------------------
1 | export interface SearchResults {
2 | ingredient: {
3 | id: number;
4 | slug: string;
5 | name: string;
6 | image_url: string|null;
7 | description: string|null;
8 | category: string|null;
9 | bar_id: number;
10 | units?: string|null;
11 | },
12 | cocktail: {
13 | id: number;
14 | name: string;
15 | slug: string;
16 | description: string|null;
17 | image_url: string|null;
18 | short_ingredients: string[];
19 | tags: string[];
20 | bar_id: number;
21 | }
22 | }
--------------------------------------------------------------------------------
/src/assets/alerts.css:
--------------------------------------------------------------------------------
1 | .alert {
2 | --_clr-text: #000;
3 | --_clr-border: #000;
4 | --_clr-bg: #fff;
5 | background: var(--_clr-bg);
6 | border: 2px solid var(--_clr-border);
7 | padding: 1rem;
8 | border-radius: var(--radius-1);
9 | color: var(--_clr-text);
10 | }
11 |
12 | .alert h3 {
13 | font-weight: var(--fw-bold);
14 | font-size: 0.7rem;
15 | letter-spacing: 1px;
16 | text-transform: uppercase;
17 | margin-bottom: 0.3rem;
18 | }
19 |
20 | .alert.alert--info {
21 | --_clr-text: rgb(64, 75, 112);
22 | --_clr-border: rgb(64, 75, 112);
23 | --_clr-bg: rgb(247, 249, 255);
24 | border-color: var(--_clr-border);
25 | }
26 |
27 | .dark-theme .alert.alert--info {
28 | --_clr-text: #a8c6f3;
29 | --_clr-border: #2c4b79;
30 | --_clr-bg: #0e213d;
31 | }
32 |
33 | .alert.alert--warning {
34 | --_clr-text: rgb(173, 68, 7);
35 | --_clr-border: rgb(173, 68, 7);
36 | --_clr-bg: rgb(255, 250, 247);
37 | border-color: var(--_clr-border);
38 | }
--------------------------------------------------------------------------------
/src/assets/blocks.css:
--------------------------------------------------------------------------------
1 | .block-container {
2 | --_bc-padding: 1.5rem;
3 | background: #fcf9fb;
4 | border-bottom: 1px solid var(--clr-accent-200);
5 | border-top: 2px solid #fff;
6 | border-radius: var(--radius-2);
7 | box-shadow: var(--shadow-elevation-medium);
8 | }
9 |
10 | @media (max-width: 450px) {
11 | .block-container {
12 | --_bc-padding: 1rem;
13 | }
14 | }
15 |
16 | .dark-theme .block-container {
17 | background: var(--clr-dark-main-800);
18 | border-top: 1px solid var(--clr-dark-main-650);
19 | border-bottom: 1px solid var(--clr-dark-main-900);
20 | box-shadow: var(--shadow-elevation-medium-dark);
21 | }
22 |
23 | .block-container.block-container--padded {
24 | padding: var(--_bc-padding);
25 | }
26 |
27 | .block-container.block-container--hover:hover {
28 | background: rgba(255, 255, 255, .8);
29 | /* border-bottom-color: var(--clr-link-color-hover); */
30 | }
31 |
32 | .dark-theme .block-container.block-container--hover:hover {
33 | background: var(--clr-dark-main-700);
34 | border-top-color: var(--clr-dark-main-600);
35 | /* border-bottom-color: var(--clr-dark-main-800); */
36 | }
37 |
38 | .details-block-container__title {
39 | font-family: var(--font-heading);
40 | font-size: 1.5rem;
41 | font-weight: var(--fw-bold);
42 | margin-bottom: 1rem;
43 | line-height: 1;
44 | color: var(--clr-accent-800);
45 | }
46 |
47 | .dark-theme .details-block-container__title {
48 | color: var(--clr-accent-200);
49 | }
50 |
51 | .block-container.block-container--inset {
52 | border: 0;
53 | background-color: #f4edf2;
54 | border-bottom: 1px solid #fff;
55 | border-radius: var(--radius-2);
56 | box-shadow:
57 | inset 0px 0.4px 0.5px hsl(var(--shadow-color) / 0.25),
58 | inset 0px 1.1px 1.2px -0.8px hsl(var(--shadow-color) / 0.25),
59 | inset 0px 2.6px 2.9px -1.7px hsl(var(--shadow-color) / 0.25),
60 | inset 0px 6.3px 7.1px -2.5px hsl(var(--shadow-color) / 0.25);
61 | }
62 |
63 | .block-container.block-container--inset.block-container--padded {
64 | padding: var(--gap-size-3);
65 | }
66 |
67 | .dark-theme .block-container.block-container--inset {
68 | background-color: rgba(0, 0, 0, .15);
69 | border-bottom: 1px solid rgba(255, 255, 255, .1);
70 | box-shadow:
71 | inset 0px 0.4px 0.5px hsl(var(--shadow-color-dark) / 0.25),
72 | inset 0px 1.1px 1.2px -0.8px hsl(var(--shadow-color-dark) / 0.25),
73 | inset 0px 2.6px 2.9px -1.7px hsl(var(--shadow-color-dark) / 0.25),
74 | inset 0px 6.3px 7.1px -2.5px hsl(var(--shadow-color-dark) / 0.25);
75 | }
76 |
77 | .block-container.block-container--placeholder {
78 | background: #d9e3f6;
79 | border-top-color: #f5f9ff;
80 | border-bottom-color: #a7badd;
81 | }
82 |
83 | .dark-theme .block-container.block-container--placeholder {
84 | background: #ec8703;
85 | border-top-color: #ffcc8a;
86 | border-bottom-color: #4b2a00;
87 | }
--------------------------------------------------------------------------------
/src/assets/chips.css:
--------------------------------------------------------------------------------
1 | .item-details__chips {
2 | display: flex;
3 | flex-wrap: wrap;
4 | gap: var(--gap-size-2);
5 | margin-bottom: 1rem;
6 | }
7 |
8 | .item-details__chips__group__title {
9 | font-size: 0.65rem;
10 | margin-bottom: -2px;
11 | color: var(--clr-gray-600);
12 | }
13 |
14 | .dark-theme .item-details__chips__group__title {
15 | color: rgba(255, 255, 255, .4);
16 | }
17 |
18 | .item-details__chips .rating {
19 | line-height: 1;
20 | }
21 |
22 | .chips-list {
23 | list-style: none;
24 | padding: 0;
25 | margin: 0;
26 | display: flex;
27 | flex-wrap: wrap;
28 | gap: var(--gap-size-2);
29 | }
30 |
31 | .chip {
32 | display: block;
33 | padding: 1px 9px;
34 | font-size: 0.75rem;
35 | background-color: #E6DBF0;
36 | color: #3a304d;
37 | border-radius: var(--radius-1);
38 | text-decoration: none;
39 | }
40 |
41 | a.chip:hover,
42 | a.chip:active,
43 | a.chip:focus {
44 | background-color: #c1cef0;
45 | color: #1e273b;
46 | }
47 |
48 | .dark-theme .chip {
49 | background-color: #572d2c;
50 | color: #f3dada;
51 | }
52 |
53 | .dark-theme a.chip:hover,
54 | .dark-theme a.chip:active,
55 | .dark-theme a.chip:focus {
56 | background-color: #743f3e;
57 | color: #fff0f0;
58 | }
59 |
--------------------------------------------------------------------------------
/src/assets/dialog.css:
--------------------------------------------------------------------------------
1 | .dialog {
2 | --dialog-margin: 2rem;
3 | --dialog-padding: 1.5rem;
4 | inset: 0;
5 | position: fixed;
6 | transition: opacity 250ms ease, backdrop-filter 250ms ease;
7 | z-index: var(--z-dialog);
8 | }
9 |
10 | @media (max-width: 450px) {
11 | .dialog {
12 | --dialog-margin: 0.5rem;
13 | --dialog-padding: 1rem;
14 | }
15 | }
16 |
17 | .dialog__container {
18 | height: 100%;
19 | outline: 0;
20 | overflow-x: hidden;
21 | overflow-y: auto;
22 | padding: var(--dialog-margin);
23 | display: flex;
24 | align-items: center;
25 | justify-content: center;
26 | }
27 |
28 | .dialog-animation-enter-from,
29 | .dialog-animation-leave-to {
30 | opacity: 0;
31 | backdrop-filter: blur(0)
32 | }
33 |
34 | .dialog__overlay {
35 | background-color: rgba(180, 175, 189, 0.8);
36 | position: fixed;
37 | inset: 0;
38 | pointer-events: auto;
39 | backdrop-filter: blur(5px);
40 | }
41 |
42 | .dark-theme .dialog__overlay {
43 | background-color: rgba(11, 5, 21, 0.8);
44 | }
45 |
46 | .dialog__content {
47 | outline: none;
48 | pointer-events: auto;
49 | contain: layout;
50 | background-color: #fcf9fb;
51 | border-top: 2px solid #fff;
52 | padding: var(--dialog-padding);
53 | margin: auto;
54 | border-radius: var(--radius-3);
55 | transition: all 200ms cubic-bezier(0.16, 1, 0.3, 1);
56 | max-width: 600px;
57 | width: 100%;
58 | box-shadow: var(--shadow-elevation-high-dark);
59 | }
60 |
61 | .dark-theme .dialog__content {
62 | background-color: var(--clr-dark-main-800);
63 | border-bottom: 1px solid var(--clr-dark-main-900);
64 | border-top: 1px solid var(--clr-dark-main-650);
65 | }
66 |
67 | .dialog-animation-enter-from .dialog__content,
68 | .dialog-animation-leave-to .dialog__content {
69 | transform: translate(0, 2rem);
70 | opacity: 0;
71 | }
72 |
73 | .dialog-title {
74 | font-size: 1.5em;
75 | margin-bottom: 1.5rem;
76 | line-height: 1;
77 | }
78 |
79 | @media (max-width: 450px) {
80 | .dialog-title {
81 | margin-bottom: 1rem;
82 | margin-right: 1rem;
83 | }
84 | }
85 |
86 | .dialog-actions {
87 | display: flex;
88 | justify-content: end;
89 | flex-wrap: wrap;
90 | gap: var(--gap-size-2);
91 | margin-top: 1.5rem;
92 | }
93 |
--------------------------------------------------------------------------------
/src/assets/dropdown.css:
--------------------------------------------------------------------------------
1 | .floating-element {
2 | width: max-content;
3 | position: absolute;
4 | top: 0;
5 | left: 0;
6 | z-index: var(--z-dropdown);
7 | }
8 |
9 | .dropdown-menu {
10 | background-color: #fff;
11 | padding: 0.5rem;
12 | border-radius: var(--radius-3);
13 | display: flex;
14 | flex-direction: column;
15 | max-width: 270px;
16 | animation: slide-in-top 400ms cubic-bezier(0.23, 1, 0.32, 1) both;
17 | box-shadow: var(--shadow-elevation-low);
18 | }
19 |
20 | .dropdown-menu .dropdown-menu__item {
21 | padding: 6px 16px 6px 12px;
22 | display: flex;
23 | align-items: center;
24 | color: black;
25 | font-size: 0.9rem;
26 | border-radius: var(--radius-2);
27 | text-decoration: none;
28 | white-space: nowrap;
29 | }
30 |
31 | .dropdown-menu .dropdown-menu__item svg {
32 | margin-right: 12px;
33 | flex-shrink: 0;
34 | /* width: 20px; */
35 | }
36 |
37 | .dropdown-menu .dropdown-menu__item:hover {
38 | color: #fff;
39 | background-color: var(--clr-gray-800);
40 | }
41 |
42 | .dropdown-menu .dropdown-menu__item:hover svg {
43 | fill: #fff;
44 | }
45 |
46 | .dropdown-menu .dropdown-menu__title {
47 | font-size: 0.8rem;
48 | padding: 3px 16px 3px 12px;
49 | font-weight: bold;
50 | color: var(--clr-gray-400);
51 | }
52 |
53 | .dropdown-menu__separator {
54 | border: 0;
55 | height: 1px;
56 | background-color: var(--clr-gray-200);
57 | margin: 0.5rem;
58 | }
59 |
60 | @keyframes slide-in-top {
61 | 0% {
62 | top: -10px;
63 | opacity: 0;
64 | }
65 |
66 | 100% {
67 | top: 0;
68 | opacity: 1;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/assets/grid.css:
--------------------------------------------------------------------------------
1 | .sr-grid {
2 | display: grid;
3 | gap: var(--gap-size-2);
4 | }
5 |
6 | .sr-grid.sr-grid--2-col {
7 | grid-template-columns: 1fr 1fr;
8 | }
9 |
10 | .sr-grid.sr-grid--3-col {
11 | grid-template-columns: 1fr 1fr 1fr;
12 | }
13 |
14 | @media (max-width: 350px) {
15 | .sr-grid.sr-grid--2-col {
16 | grid-template-columns: 1fr;
17 | }
18 | }
19 |
20 | @media (max-width: 450px) {
21 | .sr-grid.sr-grid--3-col {
22 | grid-template-columns: 1fr;
23 | }
24 | }
--------------------------------------------------------------------------------
/src/assets/table.css:
--------------------------------------------------------------------------------
1 | .table {
2 | --table-border-width: 1px;
3 | --table-border-color: var(--clr-accent-100);
4 | --table-row-hover-clr: var(--clr-accent-50);
5 | --table-small-clr: var(--clr-gray-500);
6 | width: 100%;
7 | caption-side: bottom;
8 | border-collapse: collapse;
9 | border-spacing: 0;
10 | text-align: left;
11 | }
12 |
13 | .dark-theme .table {
14 | --table-row-hover-clr: var(--clr-dark-main-900);
15 | --table-border-color: var(--clr-dark-main-700);
16 | --table-small-clr: var(--clr-dark-main-400);
17 | }
18 |
19 | .table td {
20 | padding: .5rem;
21 | box-shadow: inset var(--table-border-width) calc(var(--table-border-width) * -1) 0 0 var(--table-border-color);
22 | }
23 |
24 | .table tr:last-child td {
25 | box-shadow: inset var(--table-border-width) 0 0 0 var(--table-border-color);
26 | }
27 |
28 | .table td:first-child {
29 | box-shadow: inset 0 calc(var(--table-border-width) * -1) 0 0 var(--table-border-color);
30 | }
31 |
32 | .table td:first-child {
33 | box-shadow: inset 0 calc(var(--table-border-width) * -1) 0 0 var(--table-border-color);
34 | }
35 |
36 | .table tr:last-child td:first-child {
37 | box-shadow: none;
38 | }
39 |
40 | .table th {
41 | font-weight: var(--fw-bold);
42 | font-size: 0.85rem;
43 | padding: .5rem;
44 | border-bottom: 2px solid var(--clr-accent-200);
45 | }
46 |
47 | .table tr:hover td {
48 | background-color: var(--table-row-hover-clr);
49 | }
50 |
51 | .table td small {
52 | color: var(--table-small-clr);
53 | line-height: 1.4;
54 | display: block;
55 | }
56 |
--------------------------------------------------------------------------------
/src/assets/tags.css:
--------------------------------------------------------------------------------
1 | .tag-container {
2 | display: flex;
3 | flex-wrap: wrap;
4 | gap: var(--gap-size-1);
5 | }
6 |
7 | .tag {
8 | --color-tag: #DFE7FD;
9 | --color-text: var(--clr-gray-800);
10 |
11 | text-decoration: none;
12 | background-color: none;
13 | padding: 2px 6px;
14 | border-radius: var(--radius-2);
15 | font-size: 0.75rem;
16 | line-height: 1.3;
17 | white-space: nowrap;
18 | }
19 |
20 | .dark-theme .tag {
21 | --color-tag: var(--clr-gray-700);
22 | --color-text: var(--clr-gray-200);
23 | }
24 |
25 | .tag.tag--link {
26 | border: 1px solid var(--clr-accent-300);
27 | }
28 |
29 | .tag.tag--link:hover,
30 | .tag.tag--link:focus,
31 | .tag.tag--link:active {
32 | background-color: #fff;
33 | }
34 |
35 | .tag.tag--link.tag--is-selected {
36 | background-color: var(--clr-gray-800);
37 | border: 1px solid var(--clr-gray-800);
38 | color: var(--color-text);
39 | }
40 |
41 | .tag.tag--background {
42 | background-color: var(--color-tag);
43 | color: var(--color-text);
44 | }
45 |
--------------------------------------------------------------------------------
/src/components/AmountInput.vue:
--------------------------------------------------------------------------------
1 |
56 |
57 |
58 |
59 |
60 | {{ recommendedAmount }}
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/components/Auth/AuthConfirmation.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
24 |
--------------------------------------------------------------------------------
/src/components/Auth/AuthForgotPassword.vue:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
20 |
51 |
--------------------------------------------------------------------------------
/src/components/Auth/AuthRegister.vue:
--------------------------------------------------------------------------------
1 |
2 |
29 |
30 |
31 |
76 |
--------------------------------------------------------------------------------
/src/components/Auth/AuthResetPassword.vue:
--------------------------------------------------------------------------------
1 |
2 |
25 |
26 |
27 |
66 |
--------------------------------------------------------------------------------
/src/components/Auth/OauthCallback.vue:
--------------------------------------------------------------------------------
1 |
44 |
45 |
46 |
56 |
--------------------------------------------------------------------------------
/src/components/Bar/BarJoinDialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
52 |
--------------------------------------------------------------------------------
/src/components/Calculator/CalculatorImportDialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
55 |
--------------------------------------------------------------------------------
/src/components/Calculator/CalculatorRender.vue:
--------------------------------------------------------------------------------
1 |
58 |
59 |
60 |
61 |
{{ calculator.name }}
62 |
63 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/src/components/Calculator/CalculatorRenderBlock.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
{{ evaluatedBlock.block.label }}:
19 |
{{ evaluatedBlock.block.settings.prefix }} {{ evaluatedBlock.result }} {{ evaluatedBlock.block.settings.suffix }}
20 |
{{ evaluatedBlock.block.description }}
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/components/CloseButton.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/components/Cocktail/CocktailGridContainer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
27 |
28 |
44 |
--------------------------------------------------------------------------------
/src/components/Cocktail/CocktailIngredientShare.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ name }} ({{ $t('optional') }})
5 |
6 | {{ $t('substitutes') }}: {{ substitutes }}
7 |
8 |
9 |
{{ amount }}
10 |
11 |
12 |
13 |
53 |
54 |
79 |
--------------------------------------------------------------------------------
/src/components/Cocktail/CocktailPrice.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ unitHandler.formatPrice(cocktailPrice.total_price.price, cocktailPrice.total_price.currency) }}
6 | {{ cocktailPrice.price_category.name }}
7 |
8 |
9 |
10 |
11 | {{ ingredient.ingredient.name }} · {{ unitHandler.formatPrice(ingredient.price_per_use.price, ingredient.price_per_use.currency) }} /per use ({{ ingredient.units }})
12 |
13 | Some ingredients are missing prices
14 |
15 |
16 |
17 |
18 |
28 |
29 |
--------------------------------------------------------------------------------
/src/components/Cocktail/CocktailRating.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
{{ userRating }}
16 |
17 |
18 |
32 |
68 |
--------------------------------------------------------------------------------
/src/components/Cocktail/CocktailThumb.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
37 |
52 |
--------------------------------------------------------------------------------
/src/components/Cocktail/GenerateImageDialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{ $t('generate-image-dialog.preview') }}
5 |
6 |
7 |
10 |
11 |
16 |
17 |
18 |
19 |
95 |
96 |
106 |
--------------------------------------------------------------------------------
/src/components/Cocktail/SimilarCocktails.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {{ cocktail.name }}
11 | {{ cocktail.ingredients.map(i => i.ingredient.name).join(', ') }}
12 |
13 |
14 |
15 |
16 | {{ $t('no-cocktails') }}
17 |
18 |
19 |
20 |
21 |
54 |
--------------------------------------------------------------------------------
/src/components/Collections/CollectionDetailsWidget.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
{{ props.collection.name }}
14 |
{{ props.collection.description ?? 'n/a' }}
15 |
16 |
--------------------------------------------------------------------------------
/src/components/Collections/CollectionForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ dialogTitle }}
5 |
6 | {{ $t('name') }}:
7 |
8 |
9 |
10 | {{ $t('description') }}:
11 |
12 |
13 |
14 |
15 |
16 | {{ $t('collections.share-in-bar') }}
17 |
18 |
19 |
20 | {{ $t('cancel') }}
21 | {{ $t('save') }}
22 |
23 |
24 |
25 |
26 |
86 |
--------------------------------------------------------------------------------
/src/components/DateFormatter.vue:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 | {{ formattedDate }}
24 |
25 |
--------------------------------------------------------------------------------
/src/components/Dialog/ConfirmDialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
{{ $t('confirm-dialog.title') }}
10 |
{{ body }}
11 |
12 | {{ $t('cancel') }}
13 | {{ $t('confirm') }}
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
67 |
68 |
95 |
--------------------------------------------------------------------------------
/src/components/Dialog/SaltRimDialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Trigger dialog
5 |
6 |
7 |
8 |
17 |
18 |
19 |
20 |
21 |
22 |
68 |
--------------------------------------------------------------------------------
/src/components/Dialog/plugin.js:
--------------------------------------------------------------------------------
1 | import { dialogBus } from '@/composables/eventBus'
2 |
3 | export default {
4 | install: (app) => {
5 | app.config.globalProperties.$confirm = (message, dialogOptions) => {
6 | dialogBus.emit('requestConfirm', { body: message, ...dialogOptions })
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/EmptyState.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
43 |
--------------------------------------------------------------------------------
/src/components/Feeds/FeedsIndex.vue:
--------------------------------------------------------------------------------
1 |
32 |
33 |
34 |
35 | {{ t('feeds.title') }}
36 |
37 |
38 |
39 |
40 |
41 |
[Beta] From the web
42 |
Here you can find the latest interesting cocktail news and recipes from the web. You can also quickly import recipe if feed entry supports it. Use github issues to give feedback or to recommend sites that you would like to see included.
43 |
44 |
45 |
46 |
47 |
48 |
49 |
62 |
--------------------------------------------------------------------------------
/src/components/Feeds/FeedsRecipe.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
{{ recipe.title }}
8 |
{{ recipe.source }} · {{ d(recipe.date, 'short') }}
9 |
10 |
11 | Import recipe
12 |
13 |
14 |
15 |
16 |
17 |
32 |
33 |
--------------------------------------------------------------------------------
/src/components/GlassSelector.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
56 |
57 |
--------------------------------------------------------------------------------
/src/components/Icons/IconBarShelf.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/components/Icons/IconCalculator.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/components/Icons/IconCheck.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/components/Icons/IconClose.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/components/Icons/IconCocktail.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/components/Icons/IconExternal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/components/Icons/IconFavorite.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/components/Icons/IconJigger.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/components/Icons/IconMedal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/components/Icons/IconMore.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/components/Icons/IconPublicLink.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/components/Icons/IconRecommender.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/components/Icons/IconShoppingCart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/components/Icons/IconStar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/components/Icons/IconUserShelf.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/components/ImageEditor.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Subscribe to "Mixologist" plan to enable image editing!
5 |
{{ $t('image-editor.title') }}
6 |
7 |
8 |
9 |
22 |
23 | {{ $t('close') }}
24 | {{ $t('save') }}
25 |
26 |
27 |
28 |
94 |
101 |
--------------------------------------------------------------------------------
/src/components/ImageThumb.vue:
--------------------------------------------------------------------------------
1 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
59 |
--------------------------------------------------------------------------------
/src/components/Ingredient/Hierarchy/IngredientTreeNode.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/components/Ingredient/Hierarchy/IngredientTreeNodeItem.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 | {{ ingredient.name }}
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/components/Ingredient/IngredientGridContainer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
16 |
17 |
30 |
--------------------------------------------------------------------------------
/src/components/Ingredient/IngredientHierarchy.vue:
--------------------------------------------------------------------------------
1 |
49 |
50 |
51 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/components/Ingredient/IngredientImage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
35 |
80 |
--------------------------------------------------------------------------------
/src/components/Ingredient/IngredientListContainer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
20 |
--------------------------------------------------------------------------------
/src/components/Ingredient/IngredientSpotlight.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
{{ ingredient.hierarchy.path_to_self }}
9 |
{{ ingredient.name }}
10 |
{{ truncatedDescription }}
11 |
{{ $t('show-more') }}
12 |
13 |
14 |
15 |
79 |
110 |
--------------------------------------------------------------------------------
/src/components/Ingredient/RecommendedIngredients.vue:
--------------------------------------------------------------------------------
1 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | {{ shelfPercent }}
41 |
42 |
43 |
44 | {{ ing.name }}
45 | ·
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/src/components/Layout/SiteFooter.vue:
--------------------------------------------------------------------------------
1 |
2 |
37 |
38 |
39 |
59 |
60 |
102 |
--------------------------------------------------------------------------------
/src/components/Layout/SiteLogo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{ name }}
9 | {{ subtitle }}
10 |
11 |
12 |
13 |
14 |
51 |
--------------------------------------------------------------------------------
/src/components/ListItemContainer.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/components/MiniRating.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 | {{ rating }} ★
15 |
16 |
17 |
18 |
34 |
--------------------------------------------------------------------------------
/src/components/Note/NoteDetails.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
{{ note.note }}
7 |
8 |
9 |
10 |
44 |
45 |
70 |
--------------------------------------------------------------------------------
/src/components/Note/NoteDialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ $t('note-dialog.title') }}
5 |
6 | {{ $t('content') }}:
7 |
8 |
9 |
10 | {{ $t('cancel') }}
11 | {{ $t('save') }}
12 |
13 |
14 |
15 |
16 |
63 |
--------------------------------------------------------------------------------
/src/components/OverlayLoader.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
51 |
--------------------------------------------------------------------------------
/src/components/PageHeader.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
69 |
--------------------------------------------------------------------------------
/src/components/Print/PrintShoppingList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('your-shopping-list') }}
4 |
12 |
15 |
16 |
17 |
49 |
50 |
98 |
--------------------------------------------------------------------------------
/src/components/RatingActions.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
55 |
56 |
97 |
--------------------------------------------------------------------------------
/src/components/SaltRimCheckbox.vue:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
{{ label }}
27 |
{{ description }}
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/components/SaltRimDropdown.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/components/SaltRimRadio.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
{{ title }}
9 |
{{ description }}
10 |
11 |
12 |
13 |
46 |
111 |
--------------------------------------------------------------------------------
/src/components/SaltRimSpinner.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
22 |
23 |
42 |
--------------------------------------------------------------------------------
/src/components/Search/OffCanvas.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/Search/SearchPagination.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
62 |
--------------------------------------------------------------------------------
/src/components/Settings/CocktailMethodsForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ dialogTitle }}
5 |
6 | {{ $t('name') }}:
7 |
8 |
9 |
10 | {{ $t('cocktail-methods.dilution-percentage') }} (%):
11 |
12 |
13 |
14 | {{ $t('cancel') }}
15 | {{ $t('save') }}
16 |
17 |
18 |
19 |
20 |
75 |
--------------------------------------------------------------------------------
/src/components/Settings/PriceCategoryForm.vue:
--------------------------------------------------------------------------------
1 |
63 |
64 |
65 |
66 |
67 | {{ dialogTitle }}
68 |
69 | {{ t('name') }}:
70 |
71 |
72 |
77 |
78 | {{ t('description') }}:
79 |
80 |
81 |
82 | {{ t('cancel') }}
83 | {{ t('save') }}
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/src/components/Settings/SettingsNavigation.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ $t('profile') }}
4 | {{ $t('api.tokens') }}
5 | {{ $t('exports.title') }}
6 |
7 | {{ $t('billing.title') }}
8 |
9 |
10 | {{ $t('users.title') }}
11 | {{ $t('glass-type.types') }}
12 | {{ $t('tag.tags') }}
13 | {{ $t('utensils.title') }}
14 | {{ $t('prices.price-categories') }}
15 | {{ $t('cocktail-methods.cocktail-methods') }}
16 |
17 |
18 |
19 |
31 |
54 |
--------------------------------------------------------------------------------
/src/components/Settings/TagForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ dialogTitle }}
5 |
6 | {{ $t('name') }}:
7 |
8 |
9 |
10 | {{ $t('cancel') }}
11 | {{ $t('save') }}
12 |
13 |
14 |
15 |
16 |
74 |
--------------------------------------------------------------------------------
/src/components/Settings/UtensilForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ dialogTitle }}
5 |
6 | {{ $t('name') }}:
7 |
8 |
9 |
10 | {{ $t('description') }}:
11 |
12 |
13 |
14 | {{ $t('cancel') }}
15 | {{ $t('save') }}
16 |
17 |
18 |
19 |
20 |
79 |
--------------------------------------------------------------------------------
/src/components/SourcePresenter.vue:
--------------------------------------------------------------------------------
1 |
2 | {{ $t('source') }}
3 | {{ $t('source') }}: {{ source }}
4 |
5 |
6 |
25 |
--------------------------------------------------------------------------------
/src/components/StatusCheck.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Unable to connect to the Meilisearch server at "{{ appState.bar.search_host }}". Some features will be missing.
4 |
5 |
6 |
7 |
29 |
30 |
43 |
--------------------------------------------------------------------------------
/src/components/SubscriptionCheck.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 📢 · Find out more!
4 |
5 |
6 |
7 |
12 |
13 |
26 |
--------------------------------------------------------------------------------
/src/components/ThemeToggle.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
52 |
53 |
79 |
--------------------------------------------------------------------------------
/src/components/TimeStamps.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ $t('created') }}: · {{ resource.created_user.name }}
4 |
5 |
6 | {{ $t('updated') }}: · {{ resource.updated_user.name }}
7 |
8 |
9 |
10 |
11 |
31 |
39 |
--------------------------------------------------------------------------------
/src/components/ToggleIngredientBarShelf.vue:
--------------------------------------------------------------------------------
1 |
47 |
48 |
49 |
50 | {{ t('ingredient.add-to-bar-shelf') }}
51 | {{ t('ingredient.remove-from-bar-shelf') }}
52 |
53 | {{ t('loading') }}...
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/components/ToggleIngredientShelf.vue:
--------------------------------------------------------------------------------
1 |
47 |
48 |
49 |
50 | {{ t('ingredient.add-to-shelf') }}
51 | {{ t('ingredient.remove-from-shelf') }}
52 |
53 | {{ t('loading') }}...
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/components/ToggleIngredientShoppingCart.vue:
--------------------------------------------------------------------------------
1 |
50 |
51 |
52 |
53 | {{ t('ingredient.add-to-list') }}
54 | {{ t('ingredient.remove-from-list') }}
55 |
56 | {{ t('loading') }}...
57 |
58 |
59 |
--------------------------------------------------------------------------------
/src/components/Units/UnitConverter.vue:
--------------------------------------------------------------------------------
1 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/components/Units/UnitPicker.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
{{ $t('units') }}:
9 | ml
10 | oz
11 | cl
12 |
13 |
--------------------------------------------------------------------------------
/src/components/WakeLockToggle.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ $t('wakelock-toggle') }}
6 |
7 |
8 |
9 |
10 |
50 |
51 |
--------------------------------------------------------------------------------
/src/composables/__tests__/useHtmlDecode.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect } from 'vitest';
2 | import { useHtmlDecode } from '../useHtmlDecode';
3 |
4 | describe('useHtmlDecode', () => {
5 | it('decodes a valid HTML-encoded string', () => {
6 | const input = '&';
7 | const result = useHtmlDecode(input);
8 | expect(result).toBe('&');
9 | });
10 |
11 | it('returns an empty string when input is empty', () => {
12 | const input = '';
13 | const result = useHtmlDecode(input);
14 | expect(result).toBe('');
15 | });
16 |
17 | it('returns the same string if there are no HTML entities', () => {
18 | const input = 'Hello, world!';
19 | const result = useHtmlDecode(input);
20 | expect(result).toBe('Hello, world!');
21 | });
22 |
23 | it('decodes a string with multiple HTML entities', () => {
24 | const input = '<div>Hello & welcome!</div>';
25 | const result = useHtmlDecode(input);
26 | expect(result).toBe('Hello & welcome!
');
27 | });
28 |
29 | it('handles unsupported or invalid HTML entities gracefully', () => {
30 | const input = '&unknown;';
31 | const result = useHtmlDecode(input);
32 | expect(result).toBe('&unknown;');
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/src/composables/__tests__/useRecommendedAmount.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect } from 'vitest';
2 | import { useRecommendedAmounts } from './../useRecommendedAmounts';
3 |
4 | describe('useRecommendedAmounts', () => {
5 | it('returns default amounts in ml when default unit is ml', () => {
6 | const { defaultAmounts } = useRecommendedAmounts('ml');
7 | expect(defaultAmounts.value).toEqual(['7.5', '15', '22.5', '30', '37.5', '45', '52.5', '60']);
8 | });
9 |
10 | it('converts default amounts to cl when default unit is cl', () => {
11 | const { defaultAmounts } = useRecommendedAmounts('cl');
12 | expect(defaultAmounts.value).toEqual(['0.75', '1.5', '2.25', '3', '3.75', '4.5', '5.25', '6']);
13 | });
14 |
15 | it('converts default amounts to oz as fractions when default unit is oz', () => {
16 | const { defaultAmounts } = useRecommendedAmounts('oz');
17 | const expectedAmounts = ['1/4', '1/2', '3/4', '1', '1 1/4', '1 1/2', '1 3/4', '2'];
18 | expect(defaultAmounts.value).toEqual(expectedAmounts);
19 | });
20 |
21 | it('handles an empty default unit gracefully', () => {
22 | const { defaultAmounts } = useRecommendedAmounts('');
23 | expect(defaultAmounts.value).toEqual(['7.5', '15', '22.5', '30', '37.5', '45', '52.5', '60']);
24 | });
25 |
26 | it('handles an unsupported default unit gracefully', () => {
27 | const { defaultAmounts } = useRecommendedAmounts('unsupported');
28 | expect(defaultAmounts.value).toEqual(['7.5', '15', '22.5', '30', '37.5', '45', '52.5', '60']);
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/src/composables/__tests__/useUnits.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import { unitHandler } from './../useUnits'
3 |
4 | test('format numbers as fraction', () => {
5 | expect(unitHandler.asFraction(1.5)).toBe('1 1/2')
6 | expect(unitHandler.asFraction(1)).toBe('1')
7 | expect(unitHandler.asFraction(0.5)).toBe('1/2')
8 | })
9 |
10 | test('format numbers as decimal', () => {
11 | expect(unitHandler.asDecimal('1 1/2')).toBe(1.5)
12 | expect(unitHandler.asDecimal('1')).toBe(1)
13 | expect(unitHandler.asDecimal('1/2')).toBe(0.5)
14 | })
15 |
16 | test('ingredients are pretty printed', () => {
17 | expect(unitHandler.print({ amount: 30, units: 'ml' })).toBe('30 ml')
18 | expect(unitHandler.print({ amount: 30, amount_max: 60, units: 'ml' })).toBe('30-60 ml')
19 | expect(unitHandler.print({ amount: 30, units: 'ml' }, 'oz')).toBe('1 oz')
20 | expect(unitHandler.print({ amount: 1, amount_max: '1 1/2', units: 'oz' }, 'ml')).toBe('30-45 ml')
21 | expect(unitHandler.print({ amount: '1/8', units: 'teaspoon' })).toBe('0.125 teaspoon')
22 | expect(unitHandler.print({ amount: '500', units: 'unconvertable' }, 'cl')).toBe('500 unconvertable')
23 | expect(unitHandler.print({ amount: 60, units: 'ml' }, 'ml', 3)).toBe('180 ml')
24 | expect(unitHandler.print({ amount: null, units: 'ml' }, 'ml')).toBe('0 ml')
25 | expect(unitHandler.print({ amount: null, amount_max: null, units: 'ml' }, 'ml')).toBe('0 ml')
26 | expect(unitHandler.print({ amount: '0.125', units: 'oz' }, 'oz')).toBe('1/8 oz')
27 | })
28 |
29 | test('numbers are fixed and truncated', () => {
30 | expect(unitHandler.toFixedWithTruncate(1.2333333333339, 2)).toBe(1.23)
31 | expect(unitHandler.toFixedWithTruncate(9.00, 3)).toBe(9)
32 | expect(unitHandler.toFixedWithTruncate(9, 3)).toBe(9)
33 | })
34 |
35 | test('converts units', () => {
36 | expect(unitHandler.convertFromTo('oz', 1, 'ml')).toBe(30)
37 | expect(unitHandler.convertFromTo('oz', 1, 'cl')).toBe(3)
38 | expect(unitHandler.convertFromTo('ml', 30, 'oz')).toBe(1)
39 | expect(unitHandler.convertFromTo('ml', 30, 'cl')).toBe(3)
40 | expect(unitHandler.convertFromTo('cl', 3, 'oz')).toBe(1)
41 | expect(unitHandler.convertFromTo('cl', 3, 'ml')).toBe(30)
42 | })
43 |
44 | test('formats prices', () => {
45 | expect(unitHandler.formatPrice(30, 'USD')).toBe('$30.00')
46 | expect(unitHandler.formatPrice(25.99, 'EUR')).toBe('€25.99')
47 | })
--------------------------------------------------------------------------------
/src/composables/confirm.ts:
--------------------------------------------------------------------------------
1 | import { dialogBus } from './eventBus'
2 |
3 | export function useConfirm() {
4 | return {
5 | show(message: string, dialogOptions: object): void {
6 | dialogBus.emit('requestConfirm', { body: message, ...dialogOptions })
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/src/composables/eventBus.ts:
--------------------------------------------------------------------------------
1 | import { useEventBus } from '@vueuse/core'
2 |
3 | const dialogBus = useEventBus('dialogs')
4 | const barBus = useEventBus('bars')
5 |
6 | export { dialogBus, barBus }
--------------------------------------------------------------------------------
/src/composables/ingredientBg.ts:
--------------------------------------------------------------------------------
1 | const useIngredientBg = (hex: string) => {
2 | let c: number | string[] | null = null;
3 |
4 | if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) {
5 | c = hex.substring(1).split('');
6 |
7 | if (c.length === 3) {
8 | c = [c[0], c[0], c[1], c[1], c[2], c[2]];
9 | }
10 |
11 | const hexValue = Number('0x' + c.join(''));
12 | const red = (hexValue >> 16) & 255;
13 | const green = (hexValue >> 8) & 255;
14 | const blue = hexValue & 255;
15 |
16 | return `rgba(${red}, ${green}, ${blue}, 0.3)`;
17 | }
18 |
19 | return hex;
20 | }
21 |
22 | export { useIngredientBg }
--------------------------------------------------------------------------------
/src/composables/title.ts:
--------------------------------------------------------------------------------
1 | import { useTitle as useVueTitle } from '@vueuse/core'
2 | import AppState from '@/AppState'
3 |
4 | const useTitle = (title: string) => {
5 | const appState = new AppState()
6 |
7 | useVueTitle(`${title} \u22C5 ${appState.bar.name ?? 'Bar Assistant'}`)
8 | }
9 |
10 | export { useTitle }
--------------------------------------------------------------------------------
/src/composables/toast.ts:
--------------------------------------------------------------------------------
1 | import { useToast } from 'vue-toast-notification'
2 |
3 | export function useSaltRimToast() {
4 | return useToast({
5 | position: 'top',
6 | type: 'default',
7 | duration: 1500,
8 | pauseOnHover: true,
9 | })
10 | }
--------------------------------------------------------------------------------
/src/composables/useAuth.ts:
--------------------------------------------------------------------------------
1 | import BarAssistantClient from '@/api/BarAssistantClient'
2 | import AppState from '@/AppState'
3 |
4 | const useAuth = async (token: string): Promise => {
5 | const appState = new AppState()
6 | appState.setToken(token)
7 |
8 | try {
9 | const profile = (await BarAssistantClient.getProfile())?.data
10 | if (!profile) {
11 | appState.forgetUser()
12 | return '/login'
13 | }
14 |
15 | appState.setUser(profile)
16 |
17 | if (profile.settings?.language) {
18 | appState.setLanguage(profile.settings.language)
19 | }
20 |
21 | if (profile.settings?.theme) {
22 | appState.setTheme(profile.settings.theme)
23 | }
24 | } catch (e: any) {
25 | appState.forgetUser()
26 |
27 | return '/login'
28 | }
29 |
30 | const bars = (await BarAssistantClient.getBars())?.data
31 |
32 | if (bars?.length == 1) {
33 | appState.setBar(bars[0])
34 |
35 | return '/'
36 | } else {
37 | return '/bars'
38 | }
39 | }
40 |
41 | export { useAuth }
--------------------------------------------------------------------------------
/src/composables/useGetRoleName.ts:
--------------------------------------------------------------------------------
1 | export function getRoleName(roleId: number): string|null {
2 | switch (roleId) {
3 | case 1:
4 | return 'admin'
5 | case 2:
6 | return 'moderator'
7 | case 3:
8 | return 'general'
9 | case 4:
10 | return 'guest'
11 | default:
12 | return null
13 | }
14 | }
--------------------------------------------------------------------------------
/src/composables/useHtmlDecode.ts:
--------------------------------------------------------------------------------
1 | import { decode } from 'entities'
2 |
3 | export function useHtmlDecode(input: string): string {
4 | return decode(input)
5 | }
6 |
--------------------------------------------------------------------------------
/src/composables/useRecommendedAmounts.ts:
--------------------------------------------------------------------------------
1 | import { ref } from 'vue';
2 | import { unitHandler } from './useUnits';
3 |
4 | export function useRecommendedAmounts(defaultUnit: string) {
5 | const defaultAmountsInMl = ['7.5', '15', '22.5', '30', '37.5', '45', '52.5', '60'];
6 | const defaultAmounts = ref(defaultAmountsInMl);
7 |
8 | if (defaultUnit === 'cl') {
9 | defaultAmounts.value = defaultAmountsInMl.map(amount => (parseFloat(amount) / 10).toString());
10 | } else if (defaultUnit === 'oz') {
11 | defaultAmounts.value = defaultAmountsInMl.map(amount => unitHandler.asFraction(unitHandler.ml2oz(parseFloat(amount))));
12 | }
13 |
14 | return { defaultAmounts };
15 | }
16 |
--------------------------------------------------------------------------------
/src/composables/useTheme.ts:
--------------------------------------------------------------------------------
1 | const metaThemeColor: Record = {
2 | 'dark': '#282238',
3 | 'light': '#584b75',
4 | }
5 |
6 | const useTheme = (theme: string): void => {
7 | const darkThemeCssClassName = `dark-theme`
8 |
9 | document.body.classList.remove(darkThemeCssClassName)
10 | if (theme === 'dark') {
11 | document.body.classList.add(darkThemeCssClassName)
12 | } else {
13 | document.body.classList.remove(darkThemeCssClassName)
14 | }
15 |
16 | document.querySelector('meta[name="theme-color"]')?.setAttribute('content', metaThemeColor[theme])
17 | }
18 |
19 | export { useTheme }
--------------------------------------------------------------------------------
/src/locales/de-DE.js:
--------------------------------------------------------------------------------
1 | import messages from './messages/de-DE.json'
2 |
3 | const datetime = {
4 | 'short': {
5 | 'year': 'numeric',
6 | 'month': 'short',
7 | 'day': 'numeric'
8 | },
9 | 'long': {
10 | 'year': 'numeric',
11 | 'month': 'short',
12 | 'day': 'numeric',
13 | 'weekday': 'short',
14 | 'hour': 'numeric',
15 | 'minute': 'numeric'
16 | }
17 | }
18 |
19 | const numbers = {
20 | decimal: {
21 | style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2
22 | }
23 | }
24 |
25 | export default { messages, datetime, numbers }
26 |
--------------------------------------------------------------------------------
/src/locales/en-US.js:
--------------------------------------------------------------------------------
1 | import messages from './messages/en-US.json'
2 |
3 | const datetime = {
4 | 'short': {
5 | 'year': 'numeric',
6 | 'month': 'short',
7 | 'day': 'numeric'
8 | },
9 | 'long': {
10 | 'year': 'numeric',
11 | 'month': 'short',
12 | 'day': 'numeric',
13 | 'weekday': 'short',
14 | 'hour': 'numeric',
15 | 'minute': 'numeric'
16 | }
17 | }
18 |
19 | const numbers = {
20 | decimal: {
21 | style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2
22 | }
23 | }
24 |
25 | export default { messages, datetime, numbers }
26 |
--------------------------------------------------------------------------------
/src/locales/fr-FR.js:
--------------------------------------------------------------------------------
1 | import messages from './messages/fr-FR.json'
2 |
3 | const datetime = {
4 | 'short': {
5 | 'year': 'numeric',
6 | 'month': 'short',
7 | 'day': 'numeric'
8 | },
9 | 'long': {
10 | 'year': 'numeric',
11 | 'month': 'short',
12 | 'day': 'numeric',
13 | 'weekday': 'short',
14 | 'hour': 'numeric',
15 | 'minute': 'numeric'
16 | }
17 | }
18 |
19 | const numbers = {
20 | decimal: {
21 | style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2
22 | }
23 | }
24 |
25 | export default { messages, datetime, numbers }
26 |
--------------------------------------------------------------------------------
/src/locales/hr-HR.js:
--------------------------------------------------------------------------------
1 | import messages from './messages/hr-HR.json'
2 |
3 | const datetime = {
4 | 'short': {
5 | 'year': 'numeric',
6 | 'month': 'short',
7 | 'day': 'numeric'
8 | },
9 | 'long': {
10 | 'year': 'numeric',
11 | 'month': 'short',
12 | 'day': 'numeric',
13 | 'hour': 'numeric',
14 | 'minute': 'numeric'
15 | }
16 | }
17 |
18 | const numbers = {
19 | decimal: {
20 | style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2
21 | }
22 | }
23 |
24 | export default { messages, datetime, numbers }
25 |
--------------------------------------------------------------------------------
/src/locales/it-IT.js:
--------------------------------------------------------------------------------
1 | import messages from './messages/it-IT.json'
2 |
3 | const datetime = {
4 | 'short': {
5 | 'year': 'numeric',
6 | 'month': 'short',
7 | 'day': 'numeric'
8 | },
9 | 'long': {
10 | 'year': 'numeric',
11 | 'month': 'short',
12 | 'day': 'numeric',
13 | 'weekday': 'short',
14 | 'hour': 'numeric',
15 | 'minute': 'numeric'
16 | }
17 | }
18 |
19 | const numbers = {
20 | decimal: {
21 | style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2
22 | }
23 | }
24 |
25 | export default { messages, datetime, numbers }
26 |
--------------------------------------------------------------------------------
/src/locales/pl-PL.js:
--------------------------------------------------------------------------------
1 | import messages from './messages/pl-PL.json'
2 |
3 | const datetime = {
4 | 'short': {
5 | 'year': 'numeric',
6 | 'month': 'short',
7 | 'day': 'numeric'
8 | },
9 | 'long': {
10 | 'year': 'numeric',
11 | 'month': 'short',
12 | 'day': 'numeric',
13 | 'weekday': 'short',
14 | 'hour': 'numeric',
15 | 'minute': 'numeric'
16 | }
17 | }
18 |
19 | const numbers = {
20 | decimal: {
21 | style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2
22 | }
23 | }
24 |
25 | export default { messages, datetime, numbers }
26 |
--------------------------------------------------------------------------------
/src/locales/sv-SE.js:
--------------------------------------------------------------------------------
1 | import messages from './messages/sv-SE.json'
2 |
3 | const datetime = {
4 | 'short': {
5 | 'year': 'numeric',
6 | 'month': 'short',
7 | 'day': 'numeric'
8 | },
9 | 'long': {
10 | 'year': 'numeric',
11 | 'month': 'short',
12 | 'day': 'numeric',
13 | 'weekday': 'short',
14 | 'hour': 'numeric',
15 | 'minute': 'numeric'
16 | }
17 | }
18 |
19 | const numbers = {
20 | decimal: {
21 | style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2
22 | }
23 | }
24 |
25 | export default { messages, datetime, numbers }
26 |
--------------------------------------------------------------------------------
/src/locales/zh-CN.js:
--------------------------------------------------------------------------------
1 | import messages from './messages/zh-CN.json'
2 |
3 | const datetime = {
4 | 'short': {
5 | 'year': 'numeric',
6 | 'month': 'short',
7 | 'day': 'numeric'
8 | },
9 | 'long': {
10 | 'year': 'numeric',
11 | 'month': 'short',
12 | 'day': 'numeric',
13 | 'weekday': 'short',
14 | 'hour': 'numeric',
15 | 'minute': 'numeric'
16 | }
17 | }
18 |
19 | const numbers = {
20 | decimal: {
21 | style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2
22 | }
23 | }
24 |
25 | export default { messages, datetime, numbers }
26 |
--------------------------------------------------------------------------------
/src/views/BarFormView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/BarJoinView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/BarsView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/CalculatorFormView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/CalculatorsIndex.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/CocktailCollections.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/CocktailPrintView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/CocktailView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/CocktailsFormView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/CocktailsScrapeView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/CocktailsView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/ConfirmationView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/views/FeedsView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/ForgotPasswordView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/views/HomeView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/IngredientFormView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/IngredientImport.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/IngredientView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/IngredientsView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/LoginView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/views/MenuPublicView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/views/MenuView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/OauthCallbackView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/views/PageNotFound.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
404
6 |
10 |
11 |
12 | Page not found!
13 |
14 |
15 | Go back home
16 |
17 |
18 |
19 |
20 |
21 |
22 |
54 |
--------------------------------------------------------------------------------
/src/views/ProfileView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/PublicCocktailView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/views/QuantityCalcView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/RegisterView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/views/ResetPasswordView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/views/ServiceDownView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 | Service is currently in maintenance mode. It's unavailable until upgrades are complete. Please try again later.
9 |
10 | Homepage · Try again
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/views/SettingsAPIView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/SettingsBillingView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/SettingsCocktailMethodsView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/SettingsExportsView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/SettingsGlassesView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/SettingsPriceCategoriesView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/SettingsProfileView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/SettingsTagsView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/SettingsUsersView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/SettingsUtensilsView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/ShelfShoppingListPrintView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/ShoppingListView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@vue/tsconfig/tsconfig.dom.json",
3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
4 | "exclude": ["src/**/__tests__/*"],
5 | "compilerOptions": {
6 | "composite": true,
7 | "allowJs": true,
8 | "checkJs": false,
9 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
10 | "paths": {
11 | "@/*": ["./src/*"]
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { fileURLToPath, URL } from 'node:url'
2 | import { defineConfig } from 'vite'
3 | import vue from '@vitejs/plugin-vue'
4 | import { VitePWA } from 'vite-plugin-pwa'
5 |
6 | const manifest = {
7 | name: 'Bar Assistant',
8 | short_name: 'Bar Assistant',
9 | description: 'Bar assistant is a self hosted application for managing your home bar.',
10 | theme_color: '#584b75',
11 | orientation: "portrait",
12 | icons: [
13 | {
14 | "src": 'icons/pwa256.png',
15 | "sizes": '256x256',
16 | "type": 'image/png',
17 | "purpose": "any",
18 | },
19 | {
20 | "src": 'icons/pwa512.png',
21 | "sizes": '512x512',
22 | "type": 'image/png',
23 | "purpose": "any",
24 | },
25 | {
26 | "purpose": "maskable",
27 | "sizes": "858x858",
28 | "src": "icons/maskable_icon.png",
29 | "type": "image/png"
30 | },
31 | {
32 | "purpose": "maskable",
33 | "sizes": "48x48",
34 | "src": "icons/maskable_icon_x48.png",
35 | "type": "image/png"
36 | },
37 | {
38 | "purpose": "maskable",
39 | "sizes": "128x128",
40 | "src": "icons/maskable_icon_x128.png",
41 | "type": "image/png"
42 | },
43 | {
44 | "purpose": "maskable",
45 | "sizes": "512x512",
46 | "src": "icons/maskable_icon_x512.png",
47 | "type": "image/png"
48 | }
49 | ]
50 | };
51 |
52 | // https://vitejs.dev/config/
53 | export default defineConfig({
54 | plugins: [vue({
55 | template: {
56 | compilerOptions: {
57 | isCustomElement: (tag) => ['swiper-container', 'swiper-slide'].includes(tag),
58 | }
59 | }
60 | }), VitePWA({
61 | workbox: {
62 | navigateFallbackDenylist: [/^\/bar/]
63 | },
64 | manifest: manifest,
65 | registerType: 'autoUpdate',
66 | })],
67 | resolve: {
68 | alias: {
69 | '@': fileURLToPath(new URL('./src', import.meta.url))
70 | }
71 | }
72 | })
73 |
--------------------------------------------------------------------------------