diff --git a/package-lock.json b/package-lock.json index 6ed2e47..312f5e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "AGPL-3.0-only", "dependencies": { + "@hcaptcha/vue3-hcaptcha": "^1.3.0", "@nuxt/content": "^3.4.0", "@nuxt/eslint": "^1.3.0", "@nuxt/fonts": "^0.11.1", @@ -1270,6 +1271,17 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@hcaptcha/vue3-hcaptcha": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@hcaptcha/vue3-hcaptcha/-/vue3-hcaptcha-1.3.0.tgz", + "integrity": "sha512-IEonS6JiYdU7uy6aeib8cYtMO4nj8utwStbA9bWHyYbOvOvhpkV+AW8vfSKh6SntYxqle/TRwhv+kU9p92CfsA==", + "dependencies": { + "vue": "^3.2.19" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", diff --git a/package.json b/package.json index 6f25454..0f5e89a 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "lint:fix": "eslint . --fix" }, "dependencies": { + "@hcaptcha/vue3-hcaptcha": "^1.3.0", "@nuxt/content": "^3.4.0", "@nuxt/eslint": "^1.3.0", "@nuxt/fonts": "^0.11.1", diff --git a/src/assets/css/auth.css b/src/assets/css/auth.css new file mode 100644 index 0000000..36bd61e --- /dev/null +++ b/src/assets/css/auth.css @@ -0,0 +1,137 @@ +header { + margin: 35px 0; +} + +.account-form-wrapper { + height: 80vh; + display: flex; + justify-content: center; + align-content: center; + flex-direction: column; + margin: 15vh auto; + width: fit-content; + overflow: hidden; +} + +form.account { + height: fit-content; + display: block; + padding: 40px 48px; + background-color: var(--bg-shade-2); + color: var(--text-shade-1); + border-radius: 12px; + width: min(480px, 90vw); + box-sizing: border-box; +} + +form.account h2 { + margin: 0; + color: var(--text-shade-3); +} + +form.account p { + margin: 12px 0; +} + +form.account div { + margin-top: 24px; +} + +form.account label { + display: block; + margin-bottom: 6px; + text-transform: uppercase; + font-size: 12px; +} + +form.account button { + width: 100%; + background: var(--accent-shade-0); +} + +form.account a { + text-decoration: none; + display: block; + color: var(--text-shade-1); + text-align: right; + margin: 6px 0; + width: fit-content; +} + +form.account a:hover { + color: var(--text-shade-3); +} + +form.account a.pwdreset { + margin-left: auto; + font-size: 14px; +} + +form.account a.register { + margin: auto; + margin-top: 18px; +} + +@keyframes banner-notice { + 0% { + top: -150px; + } + + 20% { + top: 35px; + } + + 80% { + top: 35px; + } + + 100% { + top: -150px; + } +} + +.banner-notice { + display: flex; + justify-content: center; + position: absolute; + left: 0; + top: -150px; + width: 100%; + animation: banner-notice 5s; +} + +.banner-notice div { + padding: 4px 36px; + border-radius: 5px; + z-index: 3; +} + +.banner-notice.error div { + background: var(--red-shade-1); +} + +form.account.register { + display: grid; + grid-template-columns: repeat(2, 1fr); + width: min(780px, 90vw); + column-gap: 24px; + margin-bottom: 48px; +} + +form.account.register p, +form.account.register div.email, +form.account.register div.buttons { + grid-column: 1 / span 2; +} + +@media screen and (max-width: 720px) { + form.account.register { + grid-template-columns: 1fr; + } + + form.account.register p, + form.account.register div.email, + form.account.register div.buttons { + grid-column: unset; + } +} \ No newline at end of file diff --git a/src/pages/account/login/index.vue b/src/pages/account/login/index.vue index f2a0e88..66da032 100644 --- a/src/pages/account/login/index.vue +++ b/src/pages/account/login/index.vue @@ -85,150 +85,5 @@ async function loginSubmission() { - - diff --git a/src/pages/account/register/index.vue b/src/pages/account/register/index.vue new file mode 100644 index 0000000..be309cf --- /dev/null +++ b/src/pages/account/register/index.vue @@ -0,0 +1,140 @@ + + + + + + + {{ $t("account.loginForm.register") }} + {{ $t("account.loginForm.detailsPrompt") }} + + {{ $t("account.loginForm.email") }} + + + + {{ $t("account.loginForm.username") }} + + + + {{ $t("account.loginForm.miiName") }} + + + + {{ $t("account.loginForm.password") }} + + + + {{ $t("account.loginForm.confirmPassword") }} + + + + + {{ $t("account.loginForm.register") }} + + {{ $t("account.loginForm.loginPrompt") }} + + + + + + + {{ errorMessage }} + + + + + + diff --git a/src/server/api/account/register/index.ts b/src/server/api/account/register/index.ts index 7a1488f..5536ca5 100644 --- a/src/server/api/account/register/index.ts +++ b/src/server/api/account/register/index.ts @@ -1 +1,36 @@ -// TODO: registration +import { FetchError } from 'ofetch'; + +// TODO: Drop to interface from type, type is unnecessary for this +type RegisterCCResponse = { // TODO: this is the same as the login response. figure out where we should put types and merge into AuthCCReponse! + refresh_token: string; + access_token: string; + token_type: string; + expires_in: number; +}; + +export default defineEventHandler(async (event) => { + const body = await readBody(event); + + try { + const apiResponse = await $fetch(`/v1/register`, { + method: 'POST', + baseURL: useRuntimeConfig(event).apiBase, + body: body + }); + + setCookie(event, 'refresh_token', apiResponse.refresh_token); + setCookie(event, 'access_token', apiResponse.access_token); + setCookie(event, 'token_type', apiResponse.token_type); + + setResponseStatus(event, 200); + event.node.res.end(); + } catch (error: unknown) { + if (error instanceof FetchError) { + setResponseStatus(event, error.status, error.data.error); + } else { + setResponseStatus(event, 500); + } + + event.node.res.end(); + } +});
{{ $t("account.loginForm.detailsPrompt") }}
{{ errorMessage }}