diff --git a/app/components/app/auth/SignIn.vue b/app/components/app/auth/SignIn.vue index 129f75a8..bf556a2a 100644 --- a/app/components/app/auth/SignIn.vue +++ b/app/components/app/auth/SignIn.vue @@ -1,22 +1,41 @@ diff --git a/eslint.config.js b/eslint.config.js index c268af73..7bb4f304 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,5 +1,5 @@ -import antfu from "@antfu/eslint-config"; -import withNuxt from "./.nuxt/eslint.config.mjs"; +import antfu from '@antfu/eslint-config' +import withNuxt from './.nuxt/eslint.config.mjs' export default withNuxt( antfu( @@ -9,7 +9,7 @@ export default withNuxt( // Relaxed stylistic options stylistic: { semi: false, - quotes: "single", + quotes: 'single', // Less strict formatting jsx: false, }, @@ -17,34 +17,36 @@ export default withNuxt( { rules: { // Basic rules - quotes: ["error", "single", { avoidEscape: true }], - "style/no-trailing-spaces": ["error", { ignoreComments: true }], + quotes: ['error', 'single', { avoidEscape: true }], + 'style/no-trailing-spaces': ['error', { ignoreComments: true }], + + 'no-console': 'off', // Relax strict formatting rules - "style/brace-style": "off", // Allow inline if - curly: ["error", "multi-line"], // Only require braces for multi-line - "style/arrow-parens": "off", + 'style/brace-style': 'off', // Allow inline if + curly: ['error', 'multi-line'], // Only require braces for multi-line + 'style/arrow-parens': 'off', // UnoCSS - make it warning instead of error, or disable completely - "unocss/order": "off", // atau 'off' untuk disable + 'unocss/order': 'off', // atau 'off' untuk disable // Vue specific - relax template formatting - "vue/html-indent": "off", - "vue/max-attributes-per-line": "off", - "vue/singleline-html-element-content-newline": "off", - "vue/multiline-html-element-content-newline": "off", - "vue/html-self-closing": "off", + 'vue/html-indent': 'off', + 'vue/max-attributes-per-line': 'off', + 'vue/singleline-html-element-content-newline': 'off', + 'vue/multiline-html-element-content-newline': 'off', + 'vue/html-self-closing': 'off', // Allow more flexible code style - "style/max-statements-per-line": ["error", { max: 3 }], - "antfu/if-newline": "off", // Disable newline after if requirement + 'style/max-statements-per-line': ['error', { max: 3 }], + 'antfu/if-newline': 'off', // Disable newline after if requirement }, }, { - files: ["**/*.md"], + files: ['**/*.md'], rules: { - "style/no-trailing-spaces": "off", + 'style/no-trailing-spaces': 'off', }, - }, - ), -); + } + ) +) diff --git a/nuxt.config.ts b/nuxt.config.ts index 524b9cb1..d234719a 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -1,6 +1,9 @@ // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({ devtools: { enabled: true }, + runtimeConfig: { + API_ORIGIN: process.env.API_ORIGIN || 'https://main-api.dev-hopis.sabbi.id', + }, modules: [ '@unocss/nuxt', 'shadcn-nuxt', diff --git a/package.json b/package.json index 33adcb56..50a71509 100644 --- a/package.json +++ b/package.json @@ -19,14 +19,12 @@ "@unovis/ts": "^1.5.1", "@unovis/vue": "^1.5.1", "embla-carousel": "^8.5.2", - "embla-carousel-vue": "^8.5.2" + "embla-carousel-vue": "^8.5.2", + "h3": "^1.15.4" }, "devDependencies": { "@antfu/eslint-config": "^4.10.1", "@nuxt/eslint": "^1.2.0", - "prettier": "^3.6.2", - "prettier-plugin-tailwindcss": "^0.5.14", - "eslint-config-prettier": "^9.1.0", "@nuxt/icon": "^1.11.0", "@nuxtjs/color-mode": "^3.5.2", "@pinia/nuxt": "^0.5.1", @@ -39,9 +37,12 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "eslint": "^9.22.0", + "eslint-config-prettier": "^9.1.0", "eslint-plugin-format": "^1.0.1", "lucide-vue-next": "^0.482.0", "nuxt": "^4.0.1", + "prettier": "^3.6.2", + "prettier-plugin-tailwindcss": "^0.5.14", "radix-vue": "^1.9.17", "shadcn-nuxt": "^0.10.4", "tailwind-merge": "^2.6.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7683beff..445d9060 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ dependencies: embla-carousel-vue: specifier: ^8.5.2 version: 8.6.0(vue@3.5.18) + h3: + specifier: ^1.15.4 + version: 1.15.4 devDependencies: '@antfu/eslint-config': @@ -5279,7 +5282,6 @@ packages: /cookie-es@1.2.2: resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} - dev: true /cookie-es@2.0.0: resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==} @@ -5365,7 +5367,6 @@ packages: resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} dependencies: uncrypto: 0.1.3 - dev: true /css-declaration-sorter@7.2.0(postcss@8.5.6): resolution: {integrity: sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==} @@ -5865,7 +5866,6 @@ packages: /defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} - dev: true /delaunator@5.0.1: resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} @@ -5890,7 +5890,6 @@ packages: /destr@2.0.5: resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} - dev: true /detect-libc@1.0.3: resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} @@ -7320,7 +7319,6 @@ packages: radix3: 1.1.2 ufo: 1.6.1 uncrypto: 0.1.3 - dev: true /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} @@ -7486,7 +7484,6 @@ packages: /iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} - dev: true /is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} @@ -8817,7 +8814,6 @@ packages: /node-mock-http@1.0.2: resolution: {integrity: sha512-zWaamgDUdo9SSLw47we78+zYw/bDr5gH8pH7oRRs8V3KmBtu8GLgGIbV2p/gRPd3LWpEOpjQj7X1FOU3VFMJ8g==} - dev: true /node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} @@ -9945,7 +9941,6 @@ packages: /radix3@1.1.2: resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} - dev: true /randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -10883,7 +10878,6 @@ packages: /ufo@1.6.1: resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} - dev: true /ultrahtml@1.6.0: resolution: {integrity: sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==} @@ -10900,7 +10894,6 @@ packages: /uncrypto@0.1.3: resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} - dev: true /unctx@2.4.1: resolution: {integrity: sha512-AbaYw0Nm4mK4qjhns67C+kgxR2YWiwlDBPzxrN8h8C6VtAdCgditAY5Dezu3IJy4XVqAnbrXt9oQJvsn3fyozg==} diff --git a/server/api/v1/authentication/login.post.ts b/server/api/v1/authentication/login.post.ts new file mode 100644 index 00000000..8a096ab5 --- /dev/null +++ b/server/api/v1/authentication/login.post.ts @@ -0,0 +1,43 @@ +import { useRuntimeConfig } from '#imports' +import { getRequestURL, readBody, setCookie } from 'h3' + +export default defineEventHandler(async (event) => { + const body = await readBody(event) + const url = getRequestURL(event) + const config = useRuntimeConfig() + + const apiOrigin = config.API_ORIGIN + + const externalUrl = apiOrigin + url.pathname.replace(/^\/api/, '') + url.search + + const resp = await fetch(externalUrl, { + method: 'POST', + body: JSON.stringify(body), + headers: { + 'Content-Type': 'application/json', + }, + }) + + if (resp.status === 200) { + const data = await resp.json() + + if (data?.data?.accessToken) { + setCookie(event, 'authentication', data.data.accessToken, { + path: '/', + httpOnly: true, + sameSite: 'strict', + maxAge: 60 * 60 * 24, + }) + + delete data.data.accessToken + return data + } + } + + return new Response(await resp.text(), { + status: resp.status, + headers: { + 'Content-Type': resp.headers.get('content-type') || 'text/plain', + }, + }) +})