Skip to content

Commit 7a84c72

Browse files
Working on register
1 parent c8df272 commit 7a84c72

8 files changed

Lines changed: 251 additions & 201 deletions

File tree

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<template>
2+
<v-card width="100%" height="100%" class="h-100 d-flex align-center">
3+
<v-card-text class="px-10">
4+
<h3 class="text-h4 text-center mb-4">Create Account</h3>
5+
6+
<p class="mb-10 text-center">Join Mycelium Cloud and start your journey</p>
7+
8+
<v-form v-model="valid" @submit.prevent="register()">
9+
<v-row>
10+
<v-col cols="6">
11+
<v-text-field
12+
v-model.trim="username"
13+
variant="outlined"
14+
prepend-inner-icon="mdi-account"
15+
label="Username"
16+
placeholder="Enter your username"
17+
autofocus
18+
:rules="[
19+
(v) => !!v || 'Username is required',
20+
(v) => v.length >= 3 || 'Username must be at least 3 characters',
21+
(v) => v.length <= 12 || 'Username must be less than 12 characters',
22+
]"
23+
/>
24+
</v-col>
25+
26+
<v-col cols="6">
27+
<v-text-field
28+
v-model.trim="email"
29+
prepend-inner-icon="mdi-email"
30+
variant="outlined"
31+
label="Email Address"
32+
placeholder="Enter your email address"
33+
:rules="[
34+
(v) => !!v || 'Email address is required',
35+
(v) => (v.includes('@') && v.includes('.')) || 'Email address must be valid',
36+
]"
37+
/>
38+
</v-col>
39+
40+
<v-col cols="12">
41+
<PasswordInput v-model="password" />
42+
</v-col>
43+
</v-row>
44+
45+
<v-btn
46+
type="submit"
47+
block
48+
size="large"
49+
text="Create Account"
50+
prepend-icon="mdi-account-plus"
51+
variant="outlined"
52+
class="mt-6"
53+
:disabled="!valid"
54+
:loading="isLoading"
55+
/>
56+
</v-form>
57+
<div class="text-caption d-flex justify-center align-center my-4">
58+
By creating an account you agree to our
59+
<v-btn text="Terms & Conditions" variant="text" size="x-small" color="primary" /> and
60+
<v-btn text="Privacy Policy" variant="text" size="x-small" color="primary" />.
61+
</div>
62+
</v-card-text>
63+
</v-card>
64+
</template>
65+
66+
<script lang="ts" setup>
67+
import type { HandlersRegisterInput } from "../generated/api"
68+
69+
const props = defineProps<{ modelValue: HandlersRegisterInput | null }>()
70+
const emit = defineEmits<{ (e: "update:model-value", value: HandlersRegisterInput | null): void }>()
71+
72+
const api = useApi()
73+
74+
const valid = ref(false)
75+
76+
const username = ref("")
77+
const email = ref("")
78+
const password = ref("")
79+
80+
const { execute: register, isLoading } = useAsyncState(
81+
async () => {
82+
if (props.modelValue !== null) {
83+
emit("update:model-value", null)
84+
}
85+
86+
const registerBody = {
87+
name: username.value,
88+
email: email.value,
89+
password: password.value,
90+
confirm_password: password.value,
91+
}
92+
93+
const { data: d1 } = await api.users.registerUser(registerBody, {
94+
unauthenticated: true,
95+
})
96+
const completed = await api.helpers.awaitWorkflowCompletion(d1.data?.workflow_id ?? "")
97+
98+
if (completed) {
99+
emit("update:model-value", registerBody)
100+
}
101+
},
102+
null,
103+
{ immediate: false }
104+
)
105+
</script>
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<template>
2+
<v-container max-width="400" class="h-100 d-flex justify-center align-center">
3+
<v-card>
4+
<v-card-text>
5+
<h3 class="text-h5 text-center mb-4">Please check your email</h3>
6+
<p class="text-body-2 text-center mb-4">
7+
We've sent a code to
8+
<span class="font-weight-bold">{{ modelValue?.email }}</span>
9+
</p>
10+
11+
<v-form @submit.prevent>
12+
<v-otp-input
13+
v-model="otp"
14+
length="4"
15+
variant="outlined"
16+
autofocus
17+
:disabled="isLoading"
18+
/>
19+
20+
<v-btn
21+
type="submit"
22+
block
23+
size="large"
24+
text="Verify"
25+
variant="outlined"
26+
class="mt-4"
27+
prepend-icon="mdi-check-circle"
28+
:disabled="otp.length !== 4"
29+
:loading="isLoading"
30+
@click="verifyCode()"
31+
/>
32+
</v-form>
33+
34+
<div class="d-flex justify-center align-center mt-4">
35+
<p>
36+
Didn't receive the code?
37+
<v-btn text="Resend Code" variant="outlined" size="small" />
38+
</p>
39+
</div>
40+
41+
<v-btn text="Another Account?" variant="text" @click="$emit('update:model-value', null)" />
42+
</v-card-text>
43+
</v-card>
44+
</v-container>
45+
</template>
46+
47+
<script setup lang="ts">
48+
import type { HandlersRegisterInput } from "../generated/api"
49+
50+
const props = defineProps<{ modelValue: HandlersRegisterInput | null }>()
51+
defineEmits<{ (e: "update:model-value", value: HandlersRegisterInput | null): void }>()
52+
53+
const router = useRouter()
54+
const api = useApi()
55+
const otp = ref("")
56+
57+
const accessToken = useLocalStorage<string>("accessToken", "", { writeDefaults: false })
58+
const refreshToken = useLocalStorage<string>("refreshToken", "", { writeDefaults: false })
59+
60+
const { execute: verifyCode, isLoading } = useAsyncState(
61+
async () => {
62+
const { data } = await api.users.verifyRegisterCode(
63+
{
64+
code: +otp.value,
65+
email: props.modelValue?.email ?? "",
66+
},
67+
{ unauthenticated: true }
68+
)
69+
70+
const completed = await api.helpers.awaitWorkflowCompletion(data.data?.workflow_id ?? "")
71+
if (!completed) {
72+
return console.log("failed")
73+
}
74+
75+
const { data: loginData } = await api.users.loginUser(
76+
{
77+
email: props.modelValue?.email ?? "",
78+
password: props.modelValue?.password ?? "",
79+
},
80+
{ unauthenticated: true }
81+
)
82+
83+
accessToken.value = loginData.data?.access_token ?? ""
84+
refreshToken.value = loginData.data?.refresh_token ?? ""
85+
86+
router.push("/dashboard")
87+
},
88+
null,
89+
{ immediate: false }
90+
)
91+
</script>

frontend/kubecloud-v2/composables/api.ts

Lines changed: 33 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,54 +10,19 @@ import {
1010
WorkflowApi,
1111
} from "../generated/api"
1212

13-
class ApiHelpers {
14-
constructor(
15-
private readonly admin: AdminApi,
16-
private readonly deployments: DeploymentsApi,
17-
private readonly invoices: InvoicesApi,
18-
private readonly nodes: NodesApi,
19-
private readonly notifications: NotificationsApi,
20-
private readonly twins: TwinsApi,
21-
private readonly users: UsersApi,
22-
private readonly workflow: WorkflowApi
23-
) {}
24-
25-
public async awaitWorkflowCompletion(
26-
workflowId: string,
27-
deplay: number = 5_000
28-
): Promise<boolean> {
29-
const { data } = await this.workflow.getWorkflowStatus(workflowId, {
30-
_flags: { unauthenticated: true },
31-
})
32-
33-
console.log("state", data.data)
34-
35-
if (data.data === "failed") {
36-
return false
37-
}
38-
39-
if (data.data === "completed") {
40-
return true
41-
}
42-
43-
await new Promise((res) => setTimeout(res, deplay))
44-
return this.awaitWorkflowCompletion(workflowId, deplay)
45-
}
46-
}
47-
4813
export const useApi = createGlobalState(() => {
4914
const config = useRuntimeConfig()
5015
const { apiBasePath } = config.public
5116

52-
const accessToken = useLocalStorage<string>("accessToken", "")
53-
// const refreshToken = useLocalStorage<string>("refreshToken", "")
17+
const accessToken = useLocalStorage<string>("accessToken", "", { writeDefaults: false })
18+
// const refreshToken = useLocalStorage<string>("refreshToken", "", { writeDefaults: false })
5419

5520
const instance = axios.create({
5621
baseURL: apiBasePath,
5722
})
5823

5924
instance.interceptors.request.use((config) => {
60-
if (config._flags?.unauthenticated) {
25+
if (config.unauthenticated) {
6126
return config
6227
}
6328

@@ -67,6 +32,36 @@ export const useApi = createGlobalState(() => {
6732
return config
6833
})
6934

35+
class ApiHelpers {
36+
constructor(
37+
private readonly admin: AdminApi,
38+
private readonly deployments: DeploymentsApi,
39+
private readonly invoices: InvoicesApi,
40+
private readonly nodes: NodesApi,
41+
private readonly notifications: NotificationsApi,
42+
private readonly twins: TwinsApi,
43+
private readonly users: UsersApi,
44+
private readonly workflow: WorkflowApi
45+
) {}
46+
47+
public async awaitWorkflowCompletion(workflowId: string): Promise<boolean> {
48+
const { data } = await this.workflow.getWorkflowStatus(workflowId, {
49+
unauthenticated: true,
50+
})
51+
52+
if (data.data === "failed") {
53+
return false
54+
}
55+
56+
if (data.data === "completed") {
57+
return true
58+
}
59+
60+
await new Promise((res) => setTimeout(res, 2_000))
61+
return this.awaitWorkflowCompletion(workflowId)
62+
}
63+
}
64+
7065
/*
7166
instance.interceptors.response.use(
7267
(response) => response,

frontend/kubecloud-v2/composables/forms.ts

Lines changed: 0 additions & 12 deletions
This file was deleted.

frontend/kubecloud-v2/global.d.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import "axios"
22

33
declare module "axios" {
44
interface AxiosRequestConfig {
5-
_flags?: {
6-
unauthenticated?: boolean
7-
}
5+
unauthenticated?: boolean
86
}
97
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
11
<template>
22
<div>Dashboard Overview</div>
33
</template>
4+
5+
<script setup lang="ts">
6+
const api = useApi()
7+
onMounted(async () => {
8+
const { data } = await api.users.getUser()
9+
console.log(data)
10+
})
11+
</script>

0 commit comments

Comments
 (0)