├── .browserslistrc ├── .editorconfig ├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── README.md ├── eslint.config.js ├── index.html ├── jsconfig.json ├── package.json ├── public └── favicon.ico ├── src ├── App.vue ├── assets │ ├── logo.png │ ├── logo.svg │ └── theme.css ├── components │ ├── AppFooter.vue │ ├── README.md │ └── cart │ │ ├── ActiveCartDisplay.vue │ │ ├── AvailableCarts.vue │ │ ├── CartHeader.vue │ │ ├── CartItem.vue │ │ ├── CartItemList.vue │ │ ├── CartSection.vue │ │ ├── ConfigurationDetails.vue │ │ ├── ItemPricing.vue │ │ └── OSConfiguration.vue ├── config │ └── sites.js ├── main.js ├── pages │ ├── Me.vue │ ├── README.md │ ├── Servers.vue │ ├── Settings.vue │ ├── index.vue │ └── plan │ │ └── [plan].vue ├── plugins │ ├── README.md │ ├── index.js │ └── vuetify.js ├── router │ └── index.js └── styles │ ├── README.md │ └── settings.scss ├── vite.config.mjs └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | not ie 11 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [18.x, 20.x, 22.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v4 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'npm' 29 | - run: yarn 30 | - run: yarn build 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OVH Cart 2 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import pluginVue from 'eslint-plugin-vue' 3 | 4 | export default [ 5 | { 6 | name: 'app/files-to-lint', 7 | files: ['**/*.{js,mjs,jsx,vue}'], 8 | }, 9 | 10 | { 11 | name: 'app/files-to-ignore', 12 | ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'], 13 | }, 14 | 15 | js.configs.recommended, 16 | ...pluginVue.configs['flat/recommended'], 17 | 18 | { 19 | rules: { 20 | 'vue/multi-word-component-names': 'off', 21 | }, 22 | } 23 | ] 24 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Ovh Cart 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "target": "es5", 5 | "module": "esnext", 6 | "baseUrl": "./", 7 | "moduleResolution": "bundler", 8 | "paths": { 9 | "@/*": [ 10 | "src/*" 11 | ] 12 | }, 13 | "lib": [ 14 | "esnext", 15 | "dom", 16 | "dom.iterable", 17 | "scripthost" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ovhcart", 3 | "private": true, 4 | "type": "module", 5 | "version": "0.0.0", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "lint": "eslint . --fix" 11 | }, 12 | "dependencies": { 13 | "@mdi/font": "7.4.47", 14 | "axios": "^1.8.4", 15 | "roboto-fontface": "*", 16 | "vue": "^3.4.31", 17 | "vuetify": "^3.6.14" 18 | }, 19 | "devDependencies": { 20 | "@eslint/js": "^9.14.0", 21 | "@vitejs/plugin-vue": "^5.0.5", 22 | "eslint": "^9.14.0", 23 | "eslint-plugin-import": "^2.29.1", 24 | "eslint-plugin-n": "^16.6.2", 25 | "eslint-plugin-node": "^11.1.0", 26 | "eslint-plugin-promise": "^6.4.0", 27 | "eslint-plugin-vue": "^9.30.0", 28 | "sass": "1.77.8", 29 | "sass-embedded": "^1.77.8", 30 | "unplugin-fonts": "^1.1.1", 31 | "unplugin-vue-components": "^28.4.1", 32 | "unplugin-vue-router": "^0.10.0", 33 | "vite": "^5.4.0", 34 | "vite-plugin-vuetify": "^2.0.3", 35 | "vue-router": "^4.4.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orvice/ovhcart/d82ddbb321ae402677eb9f35ecc1a4731287f04e/public/favicon.ico -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 44 | 45 | 51 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orvice/ovhcart/d82ddbb321ae402677eb9f35ecc1a4731287f04e/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/theme.css: -------------------------------------------------------------------------------- 1 | /* Theme variables */ 2 | :root[data-theme="light"] { 3 | --background-color: #f5f5f5; 4 | --text-color: #212121; 5 | --primary-color: #1976d2; 6 | --secondary-color: #424242; 7 | --accent-color: #82b1ff; 8 | --error-color: #ff5252; 9 | --success-color: #4caf50; 10 | --warning-color: #fb8c00; 11 | --info-color: #2196f3; 12 | --surface-color: #ffffff; 13 | --card-border-color: #e0e0e0; 14 | } 15 | 16 | :root[data-theme="dark"] { 17 | --background-color: #121212; 18 | --text-color: #e0e0e0; 19 | --primary-color: #2196f3; 20 | --secondary-color: #757575; 21 | --accent-color: #536dfe; 22 | --error-color: #ff5252; 23 | --success-color: #4caf50; 24 | --warning-color: #fb8c00; 25 | --info-color: #2196f3; 26 | --surface-color: #1e1e1e; 27 | --card-border-color: #424242; 28 | } 29 | 30 | /* Apply theme variables to Vuetify */ 31 | .v-application { 32 | background-color: var(--background-color) !important; 33 | color: var(--text-color) !important; 34 | } 35 | 36 | .v-main { 37 | background-color: var(--background-color) !important; 38 | } 39 | 40 | .v-card { 41 | background-color: var(--surface-color) !important; 42 | color: var(--text-color) !important; 43 | border-color: var(--card-border-color) !important; 44 | } 45 | 46 | /* Additional style overrides for dark/light themes can be added here */ 47 | 48 | /* List items */ 49 | .v-list-item { 50 | color: var(--text-color) !important; 51 | } 52 | 53 | .v-list { 54 | background-color: var(--surface-color) !important; 55 | color: var(--text-color) !important; 56 | } 57 | 58 | /* Buttons */ 59 | .v-btn.v-btn--variant-elevated { 60 | background-color: var(--primary-color) !important; 61 | color: var(--surface-color) !important; 62 | } 63 | 64 | /* Tables */ 65 | .v-table { 66 | background-color: var(--surface-color) !important; 67 | color: var(--text-color) !important; 68 | } 69 | 70 | /* Dialog */ 71 | .v-dialog .v-card { 72 | background-color: var(--surface-color) !important; 73 | color: var(--text-color) !important; 74 | } 75 | 76 | /* Inputs */ 77 | .v-field__field { 78 | color: var(--text-color) !important; 79 | } 80 | 81 | /* System preference dark mode listener */ 82 | @media (prefers-color-scheme: dark) { 83 | :root[data-theme="auto"] { 84 | --background-color: #121212; 85 | --text-color: #e0e0e0; 86 | --primary-color: #2196f3; 87 | --secondary-color: #757575; 88 | --accent-color: #536dfe; 89 | --error-color: #ff5252; 90 | --success-color: #4caf50; 91 | --warning-color: #fb8c00; 92 | --info-color: #2196f3; 93 | --surface-color: #1e1e1e; 94 | --card-border-color: #424242; 95 | } 96 | } 97 | 98 | @media (prefers-color-scheme: light) { 99 | :root[data-theme="auto"] { 100 | --background-color: #f5f5f5; 101 | --text-color: #212121; 102 | --primary-color: #1976d2; 103 | --secondary-color: #424242; 104 | --accent-color: #82b1ff; 105 | --error-color: #ff5252; 106 | --success-color: #4caf50; 107 | --warning-color: #fb8c00; 108 | --info-color: #2196f3; 109 | --surface-color: #ffffff; 110 | --card-border-color: #e0e0e0; 111 | } 112 | } -------------------------------------------------------------------------------- /src/components/AppFooter.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 70 | 71 | 80 | -------------------------------------------------------------------------------- /src/components/README.md: -------------------------------------------------------------------------------- 1 | # Components 2 | 3 | Vue template files in this folder are automatically imported. 4 | 5 | ## 🚀 Usage 6 | 7 | Importing is handled by [unplugin-vue-components](https://github.com/unplugin/unplugin-vue-components). This plugin automatically imports `.vue` files created in the `src/components` directory, and registers them as global components. This means that you can use any component in your application without having to manually import it. 8 | 9 | The following example assumes a component located at `src/components/MyComponent.vue`: 10 | 11 | ```vue 12 | 17 | 18 | 21 | ``` 22 | 23 | When your template is rendered, the component's import will automatically be inlined, which renders to this: 24 | 25 | ```vue 26 | 31 | 32 | 35 | ``` 36 | -------------------------------------------------------------------------------- /src/components/cart/ActiveCartDisplay.vue: -------------------------------------------------------------------------------- 1 | 109 | 110 | 152 | -------------------------------------------------------------------------------- /src/components/cart/AvailableCarts.vue: -------------------------------------------------------------------------------- 1 | 66 | 67 | 96 | -------------------------------------------------------------------------------- /src/components/cart/CartHeader.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 32 | -------------------------------------------------------------------------------- /src/components/cart/CartItem.vue: -------------------------------------------------------------------------------- 1 | 113 | 114 | 197 | -------------------------------------------------------------------------------- /src/components/cart/CartItemList.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 48 | -------------------------------------------------------------------------------- /src/components/cart/CartSection.vue: -------------------------------------------------------------------------------- 1 | 64 | 65 | 145 | -------------------------------------------------------------------------------- /src/components/cart/ConfigurationDetails.vue: -------------------------------------------------------------------------------- 1 | 139 | 140 | 226 | -------------------------------------------------------------------------------- /src/components/cart/ItemPricing.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 58 | -------------------------------------------------------------------------------- /src/components/cart/OSConfiguration.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 160 | 161 | 166 | -------------------------------------------------------------------------------- /src/config/sites.js: -------------------------------------------------------------------------------- 1 | export const availableSites = [ 2 | { code: 'CZ', name: 'Czech Republic' }, 3 | { code: 'DE', name: 'Germany' }, 4 | { code: 'ES', name: 'Spain' }, 5 | { code: 'EU', name: 'Europe' }, 6 | { code: 'FI', name: 'Finland' }, 7 | { code: 'FR', name: 'France' }, 8 | { code: 'GB', name: 'United Kingdom' }, 9 | { code: 'IE', name: 'Internationnal' }, 10 | { code: 'IT', name: 'Italy' }, 11 | { code: 'LT', name: 'Lithuania' }, 12 | { code: 'MA', name: 'Morocco' }, 13 | { code: 'NL', name: 'Netherlands' }, 14 | { code: 'PL', name: 'Poland' }, 15 | { code: 'PT', name: 'Portugal' }, 16 | { code: 'SN', name: 'Senegal' }, 17 | { code: 'TN', name: 'Tunisia' } 18 | ] 19 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * main.js 3 | * 4 | * Bootstraps Vuetify and other plugins then mounts the App` 5 | */ 6 | 7 | // Plugins 8 | import { registerPlugins } from '@/plugins' 9 | 10 | // Components 11 | import App from './App.vue' 12 | 13 | // Composables 14 | import { createApp } from 'vue' 15 | 16 | const app = createApp(App) 17 | 18 | registerPlugins(app) 19 | 20 | app.mount('#app') 21 | -------------------------------------------------------------------------------- /src/pages/Me.vue: -------------------------------------------------------------------------------- 1 | 195 | 196 | 289 | -------------------------------------------------------------------------------- /src/pages/README.md: -------------------------------------------------------------------------------- 1 | # Pages 2 | 3 | Vue components created in this folder will automatically be converted to navigatable routes. 4 | 5 | Full documentation for this feature can be found in the Official [unplugin-vue-router](https://github.com/posva/unplugin-vue-router) repository. 6 | -------------------------------------------------------------------------------- /src/pages/Servers.vue: -------------------------------------------------------------------------------- 1 | 395 | 396 | 869 | 870 | 875 | -------------------------------------------------------------------------------- /src/pages/Settings.vue: -------------------------------------------------------------------------------- 1 | 149 | 150 | 248 | -------------------------------------------------------------------------------- /src/pages/index.vue: -------------------------------------------------------------------------------- 1 | 882 | 883 | 2306 | -------------------------------------------------------------------------------- /src/pages/plan/[plan].vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 107 | 108 | -------------------------------------------------------------------------------- /src/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Plugins 2 | 3 | Plugins are a way to extend the functionality of your Vue application. Use this folder for registering plugins that you want to use globally. 4 | -------------------------------------------------------------------------------- /src/plugins/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * plugins/index.js 3 | * 4 | * Automatically included in `./src/main.js` 5 | */ 6 | 7 | // Plugins 8 | import vuetify from './vuetify' 9 | import router from '@/router' 10 | 11 | export function registerPlugins (app) { 12 | app 13 | .use(vuetify) 14 | .use(router) 15 | } 16 | -------------------------------------------------------------------------------- /src/plugins/vuetify.js: -------------------------------------------------------------------------------- 1 | /** 2 | * plugins/vuetify.js 3 | * 4 | * Framework documentation: https://vuetifyjs.com` 5 | */ 6 | 7 | // Styles 8 | import '@mdi/font/css/materialdesignicons.css' 9 | import 'vuetify/styles' 10 | 11 | // Composables 12 | import { createVuetify } from 'vuetify' 13 | 14 | // https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides 15 | export default createVuetify({ 16 | theme: { 17 | defaultTheme: 'dark', 18 | }, 19 | }) 20 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * router/index.ts 4 | * 5 | * Automatic routes for `./src/pages/*.vue` 6 | */ 7 | 8 | // Composables 9 | import { createRouter, createWebHistory } from 'vue-router/auto' 10 | import { routes } from 'vue-router/auto-routes' 11 | 12 | const router = createRouter({ 13 | history: createWebHistory(import.meta.env.BASE_URL), 14 | routes, 15 | }) 16 | 17 | // Workaround for https://github.com/vitejs/vite/issues/11804 18 | router.onError((err, to) => { 19 | if (err?.message?.includes?.('Failed to fetch dynamically imported module')) { 20 | if (!localStorage.getItem('vuetify:dynamic-reload')) { 21 | console.log('Reloading page to fix dynamic import error') 22 | localStorage.setItem('vuetify:dynamic-reload', 'true') 23 | location.assign(to.fullPath) 24 | } else { 25 | console.error('Dynamic import error, reloading page did not fix it', err) 26 | } 27 | } else { 28 | console.error(err) 29 | } 30 | }) 31 | 32 | router.isReady().then(() => { 33 | localStorage.removeItem('vuetify:dynamic-reload') 34 | }) 35 | 36 | export default router 37 | -------------------------------------------------------------------------------- /src/styles/README.md: -------------------------------------------------------------------------------- 1 | # Styles 2 | 3 | This directory is for configuring the styles of the application. 4 | -------------------------------------------------------------------------------- /src/styles/settings.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * src/styles/settings.scss 3 | * 4 | * Configures SASS variables and Vuetify overwrites 5 | */ 6 | 7 | // https://vuetifyjs.com/features/sass-variables/` 8 | // @use 'vuetify/settings' with ( 9 | // $color-pack: false 10 | // ); 11 | -------------------------------------------------------------------------------- /vite.config.mjs: -------------------------------------------------------------------------------- 1 | // Plugins 2 | import Components from 'unplugin-vue-components/vite' 3 | import Vue from '@vitejs/plugin-vue' 4 | import Vuetify, { transformAssetUrls } from 'vite-plugin-vuetify' 5 | import ViteFonts from 'unplugin-fonts/vite' 6 | import VueRouter from 'unplugin-vue-router/vite' 7 | 8 | // Utilities 9 | import { defineConfig } from 'vite' 10 | import { fileURLToPath, URL } from 'node:url' 11 | 12 | // https://vitejs.dev/config/ 13 | export default defineConfig({ 14 | plugins: [ 15 | VueRouter(), 16 | Vue({ 17 | template: { transformAssetUrls } 18 | }), 19 | // https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin#readme 20 | Vuetify({ 21 | autoImport: true, 22 | styles: { 23 | configFile: 'src/styles/settings.scss', 24 | }, 25 | }), 26 | Components(), 27 | ViteFonts({ 28 | google: { 29 | families: [{ 30 | name: 'Roboto', 31 | styles: 'wght@100;300;400;500;700;900', 32 | }], 33 | }, 34 | }), 35 | ], 36 | define: { 'process.env': {} }, 37 | resolve: { 38 | alias: { 39 | '@': fileURLToPath(new URL('./src', import.meta.url)) 40 | }, 41 | extensions: [ 42 | '.js', 43 | '.json', 44 | '.jsx', 45 | '.mjs', 46 | '.ts', 47 | '.tsx', 48 | '.vue', 49 | ], 50 | }, 51 | server: { 52 | port: 3000, 53 | }, 54 | css: { 55 | preprocessorOptions: { 56 | sass: { 57 | api: 'modern-compiler', 58 | }, 59 | }, 60 | }, 61 | }) 62 | --------------------------------------------------------------------------------