Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

新規登録関連画面の実装 #73

Merged
merged 11 commits into from
Dec 16, 2024
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_API_BASE_URL = http://localhost:4010
8 changes: 8 additions & 0 deletions env.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
/// <reference types="vite/client" />

interface ImportMetaEnv {
readonly VITE_API_BASE_URL: string;
}

interface ImportMeta {
readonly env: ImportMetaEnv;
}
18 changes: 18 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"dependencies": {
"@types/eslint__js": "^8.42.3",
"eslint-config-prettier": "^9.1.0",
"jwt-decode": "^4.0.0",
"validator": "^13.12.0",
"vue": "^3.5.6",
"vue-router": "^4.3.3"
Expand All @@ -25,6 +26,7 @@
"@eslint/js": "^9.11.0",
"@rushstack/eslint-patch": "^1.8.0",
"@tsconfig/node20": "^20.1.4",
"@types/jwt-decode": "^2.2.1",
"@types/node": "^20.14.5",
"@types/validator": "^13.12.2",
"@vitejs/plugin-vue": "^5.1.3",
Expand Down
11 changes: 10 additions & 1 deletion src/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,16 @@
line-height: 1rem;
}

.fontstyle-ui-caption {
.fontstyle-ui-caption-link {
@apply font-primary;
font-size: 0.75rem;
font-style: normal;
font-weight: 400;
line-height: 1rem;
text-decoration: underline;
}

.fontstyle-ui-caption-strong {
@apply font-primary;
font-size: 0.75rem;
font-style: normal;
Expand Down
86 changes: 86 additions & 0 deletions src/components/Controls/OAuthButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<script setup lang="ts">
import { useRouter } from 'vue-router'

const {
disabled = false,
app,
mode
} = defineProps<{
disabled?: boolean
app: string
mode: 'signup' | 'login'
}>()

const router = useRouter()

async function onOAuthClick() {
try {
if (mode === 'signup') {
if (app === 'Github') {
const response = await fetch(`${import.meta.env.VITE_API_BASE_URL}/github-oauth2/params`)
if (response.status === 200) {
const responseJson = await response.json()
alert(responseJson.url)
router.push(responseJson.url)
} else if (response.status === 500) {
const responseJson = await response.json()
alert('Internal Server Error: ' + responseJson.message)
}
else {
alert(response.status)
}
}
if (app === 'Google') {
const response = await fetch(`${import.meta.env.VITE_API_BASE_URL}/google-oauth2/params`)
if (response.status === 200) {
const responseJson = await response.json()
router.push(responseJson.url)
} else if (response.status === 500) {
const responseJson = await response.json()
alert('Internal Server Error: ' + responseJson.message)
}
else {
alert(response.status)
}
}
if (app === 'traQ') {
const response = await fetch(`${import.meta.env.VITE_API_BASE_URL}/traq-oauth2/params`)
if (response.status === 200) {
const responseJson = await response.json()
router.push(responseJson.url)
} else if (response.status === 500) {
const responseJson = await response.json()
alert('Internal Server Error: ' + responseJson.message)
}
else {
alert(response.status)
}
}
}
} catch (error) {
console.error('OAuth Error:', error)
alert('OAuth Error:' + error)
}
}
</script>

<template>
<button
:disabled="disabled"
class="fontstyle-ui-control-strong inline-block space-x-2.5 rounded-lg border border-border-secondary px-3 py-2 text-text-primary enabled:hover:bg-background-secondary disabled:opacity-50"
@click="onOAuthClick"
>
<span v-if="app === 'Github'" class="inline-block align-middle"
><img src="" class="size-5"
/></span>
<span v-if="app === 'Google'" class="inline-block align-middle"
><img src="" class="size-5"
/></span>
<span v-if="app === 'traQ'" class="inline-block align-middle"
><img src="" class="size-5"
/></span>
<span class="inline-block align-middle">{{ app }} で新規登録</span>
</button>
</template>

<style scoped></style>
35 changes: 32 additions & 3 deletions src/views/SignupAfterMailView.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,37 @@
<script setup lang="ts"></script>
<script setup lang="ts">
import { useRouter } from 'vue-router';
import BorderedButton from '@/components/Controls/BorderedButton.vue'
import PrimaryButton from '@/components/Controls/PrimaryButton.vue'

const router = useRouter()

function onClose() {
router.push('/')
}
function onResendEmail() {
router.push('/signup')
}
</script>

<template>
<div>
<h1>Signup After Mail</h1>
<div
class="flex items-center justify-center bg-background-tertiary px-8 py-6"
style="height: calc(100vh - 56px)"
>
<div class="max-w-xl space-y-5 rounded-2xl bg-white px-14 py-10">
<div class="fontstyle-ui-title text-left">確認メールを送信しました</div>
<div class="fontstyle-ui-body text-left text-text-secondary">
60分以内に、メールに記載されたリンクから登録フォームにアクセスしてください。
</div>
<div class="flex gap-3">
<div class="flex-1">
<PrimaryButton text="この画面を閉じる" class="w-full" @click="onClose" />
</div>
<div class="flex-1">
<BorderedButton text="メールを再送信する" class="w-full" @click="onResendEmail" />
</div>
</div>
</div>
</div>
</template>

Expand Down
126 changes: 123 additions & 3 deletions src/views/SignupRegisterView.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,128 @@
<script setup lang="ts"></script>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { jwtDecode } from 'jwt-decode'
import PasswordTextbox from '@/components/Controls/Textbox/PasswordTextbox.vue'
import PlainTextbox from '@/components/Controls/Textbox/PlainTextbox.vue'
import PrimaryButton from '@/components/Controls/PrimaryButton.vue'

const username = ref('')
const emailAddress = ref('')
const password = ref('')
const confirmPassword = ref('')
const confirmPasswordErrorMessage = ref('')

onMounted(() => {
try {
const token = new URLSearchParams(window.location.search).get('token')
if (token) {
const decodedToken = jwtDecode<{ email: string }>(token)
emailAddress.value = decodedToken.email
}
} catch (error) {
console.error('Signup Register Error:', error)
alert('Signup Register Error:' + error)
}
})

const router = useRouter()

async function onSignupRegister() {
try {
if (password.value !== confirmPassword.value) {
confirmPasswordErrorMessage.value = 'パスワードが一致しません'
return
}
const token = new URLSearchParams(window.location.search).get('token')
const response = await fetch(`${import.meta.env.VITE_API_BASE_URL}/signup`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ userName: username.value, password: password.value, token: token })
})
if (response.status === 201) {
router.push('/login')
} else if (response.status === 400) {
alert('不正なリクエストです')
} else if (response.status === 401) {
alert('Unauthorized')
} else {
alert(response.status)
}
} catch (error) {
console.error('Signup Register Error:', error)
alert('Signup Register Error:' + error)
}
}
</script>

<template>
<div>
<h1>Signup Register</h1>
<div
class="flex items-center justify-center bg-background-tertiary px-8 py-6"
style="height: calc(100vh - 56px)"
>
<div class="max-w-3xl space-y-5 rounded-2xl bg-white px-14 py-10">
<div class="fontstyle-ui-title text-left">新規登録</div>
Copy link
Collaborator

Choose a reason for hiding this comment

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

以下の各種ラベルとフォームもgridで整列すると良さそうです。

<div>
Copy link
Collaborator

Choose a reason for hiding this comment

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

このようなformのタイトル?を表すようなものは全てlabel要素で書き換え,それぞれのformとforidにより対応づけをしてください。(このドキュメントを参照してください。 https://developer.mozilla.org/ja/docs/Web/HTML/Element/label)
上記の修正をしても現状では正しくformがフォーカスされないと思いますが,それはTextboxコンポーネントの実装の問題なので別issueで作業しますl。

<span class="fontstyle-ui-body text-status-error">*</span>
<span class="fontstyle-ui-body text-text-primary">がついた項目は必須です。</span>
</div>
<div class="space-y-5 p-2.5">
<div class="flex gap-6">
<div class="w-50 text-right">
<span class="fontstyle-ui-body-strong text-text-primary">ユーザー名</span>
<span class="fontstyle-ui-body-strong text-status-error">*</span>
</div>
<div class="flex-1">
<PlainTextbox v-model="username" />
<div class="fontstyle-ui-caption-strong pt-1 text-text-secondary">
文字数は〇以上〇以下で、半角英数字とアンダースコアのみが使用できます。
</div>
Copy link
Collaborator

Choose a reason for hiding this comment

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

pタグとかで適当に囲ってあげた方が良いと思います。それから,僕の画面だとこの文字列が改行されてしまっているので,常に一行に収まることを保証したいです。

Copy link
Collaborator

Choose a reason for hiding this comment

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

正規表現を使ったvalidateを実装してもらえると助かります!字数制限は仮に5以上10以下などにしてください。
また,validateルールは例えばrules.tsのようなファイルを用意してそこに記述すると再利用できて良さそうなので,可能ならそうしてください。

</div>
</div>
<div class="flex gap-6">
<div class="w-50 text-right">
<span class="fontstyle-ui-body-strong text-text-primary">メールアドレス</span>
</div>
<div class="flex-1">
<span class="fontstyle-ui-body w-full px-1 text-text-primary">
{{ emailAddress }}
</span>
</div>
</div>
<div class="flex gap-6">
<div class="w-50 text-right">
<span class="fontstyle-ui-body-strong text-text-primary">パスワード</span>
<span class="fontstyle-ui-body-strong text-status-error">*</span>
</div>
<div class="flex-1">
<PasswordTextbox v-model="password" />
<div class="fontstyle-ui-caption-strong pt-1 text-text-secondary">
文字数は〇以上〇以下で、半角英数字と記号が使用できます。
</div>
<div class="fontstyle-ui-caption-strong pt-1 text-text-secondary">
英字、数字、記号がそれぞれ1文字以上含まれている必要があります。
</div>
</div>
</div>
<div class="flex gap-6">
<div class="w-50 text-right">
<span class="fontstyle-ui-body-strong text-text-primary">パスワード(確認)</span>
<span class="fontstyle-ui-body-strong text-status-error">*</span>
</div>
<div class="flex-1">
<PasswordTextbox
v-model="confirmPassword"
:error-message="confirmPasswordErrorMessage"
/>
</div>
</div>
<div class="flex justify-center">
<PrimaryButton text="次へ" @click="onSignupRegister" />
</div>
</div>
</div>
</div>
</template>

Expand Down
Loading
Loading