-
Notifications
You must be signed in to change notification settings - Fork 0
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
新規登録関連画面の実装 #73
Changes from 10 commits
60252d0
729fde1
aa8d3a7
824fc4f
c445172
bde51ce
85e673c
eb9b9ed
bc2c2b4
bd036e5
eec2fcd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
VITE_API_BASE_URL = http://localhost:4010 |
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; | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
<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') { | ||
router.push('/_oauth/login?redirect=/') // TODO: Redirect to the correct URL | ||
} | ||
} | ||
} 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> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export const usernameValidator = (username: string): boolean => { | ||
return /^[A-Za-z0-9_]{5,10}$/.test(username) | ||
} | ||
|
||
export const passwordValidator = (password: string): boolean => { | ||
const hasLetter = /[A-Za-z]/.test(password); | ||
const hasNumber = /[0-9]/.test(password); | ||
const hasSpecialChar = /[!@#$%^&*()_+\-={}[\]:;"'<>,.?/]/.test(password); | ||
const isValid = /^[A-Za-z0-9!@#$%^&*()_+\-={}[\]:;"'<>,.?/]{10,64}$/.test(password); | ||
return hasLetter && hasNumber && hasSpecialChar && isValid; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,166 @@ | ||
<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 { usernameValidator, passwordValidator } from '@/utils/validator' | ||
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 usernameErrorMessage = ref('') | ||
const emailAddress = ref('') | ||
const password = ref('') | ||
const passwordErrorMessage = 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 { | ||
let error = false | ||
// check user name | ||
if (!username.value) { | ||
usernameErrorMessage.value = 'ユーザー名を入力してください' | ||
error = true | ||
} else if (!usernameValidator(username.value)) { | ||
usernameErrorMessage.value = '無効なユーザー名' | ||
error = true | ||
} else { | ||
usernameErrorMessage.value = '' | ||
} | ||
// check password | ||
if (!password.value) { | ||
passwordErrorMessage.value = 'パスワードを入力してください' | ||
error = true | ||
} else if (!passwordValidator(password.value)) { | ||
passwordErrorMessage.value = '無効なパスワード' | ||
error = true | ||
} else { | ||
passwordErrorMessage.value = '' | ||
} | ||
// check confirm password | ||
if (!confirmPassword.value) { | ||
confirmPasswordErrorMessage.value = 'パスワード(確認)を入力してください' | ||
error = true | ||
} else if (password.value !== confirmPassword.value) { | ||
confirmPasswordErrorMessage.value = 'パスワードが一致しません' | ||
error = true | ||
} else { | ||
confirmPasswordErrorMessage.value = '' | ||
} | ||
if (error) { | ||
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> | ||
<div> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. このようなformのタイトル?を表すようなものは全てlabel要素で書き換え,それぞれのformと |
||
<span class="fontstyle-ui-body text-status-error">*</span> | ||
<span class="fontstyle-ui-body text-text-primary">がついた項目は必須です。</span> | ||
</div> | ||
<div class="flex flex-col space-y-5 p-2.5"> | ||
<div class="flex gap-6"> | ||
<label for="username" 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> | ||
</label> | ||
<div class="flex-1"> | ||
<PlainTextbox id="username" v-model="username" :error-message="usernameErrorMessage" /> | ||
<div class="fontstyle-ui-caption-strong text-nowrap pt-1 text-text-secondary"> | ||
文字数は5以上10以下で、半角英数字とアンダースコアのみが使用できます。 | ||
</div> | ||
</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"> | ||
<label for="password" 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> | ||
</label> | ||
<div class="flex-1"> | ||
<PasswordTextbox | ||
id="password" | ||
v-model="password" | ||
:error-message="passwordErrorMessage" | ||
/> | ||
<div class="fontstyle-ui-caption-strong pt-1 text-text-secondary"> | ||
文字数は10以上64以下で、半角英数字と記号が使用できます。 | ||
</div> | ||
<div class="fontstyle-ui-caption-strong pt-1 text-text-secondary"> | ||
英字、数字、記号がそれぞれ1文字以上含まれている必要があります。 | ||
</div> | ||
</div> | ||
</div> | ||
<div class="flex gap-6"> | ||
<label for="confirPassword" 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> | ||
</label> | ||
<div class="flex-1"> | ||
<PasswordTextbox | ||
id="confirmPassword" | ||
v-model="confirmPassword" | ||
:error-message="confirmPasswordErrorMessage" | ||
/> | ||
</div> | ||
</div> | ||
<div class="flex justify-center"> | ||
<PrimaryButton text="次へ" @click="onSignupRegister" /> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
以下の各種ラベルとフォームもgridで整列すると良さそうです。