CLASSROOMIO-T-1: 1st logic for signup
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
export let supabase;
|
export let supabase;
|
||||||
export let handleSubmit;
|
export let handleSubmit;
|
||||||
export let isLogin = true;
|
export let isLogin = true;
|
||||||
|
export let showOnlyContent = false;
|
||||||
|
|
||||||
async function signInWithGoogle() {
|
async function signInWithGoogle() {
|
||||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||||
@@ -15,49 +16,57 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-full min-h-screen flex items-center justify-center mt-4">
|
<div class="w-full min-h-screen flex items-center justify-center mt-4">
|
||||||
<div class="container border border-gray bg-white">
|
<div
|
||||||
|
class="container border border-gray bg-white {showOnlyContent && 'h-2/4'}"
|
||||||
|
>
|
||||||
<div class="flex items-center flex-col p-2 lg:px-8 lg:py-4">
|
<div class="flex items-center flex-col p-2 lg:px-8 lg:py-4">
|
||||||
<div class="flex items-center w-full justify-center">
|
{#if !showOnlyContent}
|
||||||
<img
|
<div class="flex items-center w-full justify-center">
|
||||||
src="/logo-192.png"
|
<img
|
||||||
alt="ClassroomIO logo"
|
src="/logo-192.png"
|
||||||
height="50"
|
alt="ClassroomIO logo"
|
||||||
width="50"
|
height="50"
|
||||||
data-atf="1"
|
width="50"
|
||||||
/>
|
data-atf="1"
|
||||||
<h4 class="text-xl">ClassroomIO</h4>
|
/>
|
||||||
</div>
|
<h4 class="text-xl">ClassroomIO</h4>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<form
|
<form
|
||||||
on:submit|preventDefault={handleSubmit}
|
on:submit|preventDefault={handleSubmit}
|
||||||
class="flex items-center flex-col w-10/12"
|
class="flex items-center flex-col w-10/12"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</form>
|
</form>
|
||||||
<div class="w-10/12 mb-3">
|
{#if !showOnlyContent}
|
||||||
<p class="text-sm mb-5">or sign up with:</p>
|
<div class="w-10/12 mb-3">
|
||||||
<PrimaryButton
|
<p class="text-sm mb-5">or sign up with:</p>
|
||||||
variant={VARIANTS.OUTLINED}
|
<PrimaryButton
|
||||||
onClick={signInWithGoogle}
|
variant={VARIANTS.OUTLINED}
|
||||||
className="py-3 sm:w-full w-full"
|
onClick={signInWithGoogle}
|
||||||
>
|
className="py-3 sm:w-full w-full"
|
||||||
<GoogleIconColored />
|
>
|
||||||
<span class="ml-2">Sign up with Google</span>
|
<GoogleIconColored />
|
||||||
</PrimaryButton>
|
<span class="ml-2">Sign up with Google</span>
|
||||||
</div>
|
</PrimaryButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full p-6 border-t border-grey text-center">
|
|
||||||
{#if isLogin}
|
|
||||||
Not registered yet? <a
|
|
||||||
class="hover:underline text-blue-700"
|
|
||||||
href="/signup">Sign up</a
|
|
||||||
>
|
|
||||||
{:else}
|
|
||||||
Already have an account? <a
|
|
||||||
class="hover:underline text-blue-700"
|
|
||||||
href="/login">Log In</a
|
|
||||||
>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
{#if !showOnlyContent}
|
||||||
|
<div class="w-full p-6 border-t border-grey text-center">
|
||||||
|
{#if isLogin}
|
||||||
|
Not registered yet? <a
|
||||||
|
class="hover:underline text-blue-700"
|
||||||
|
href="/signup">Sign up</a
|
||||||
|
>
|
||||||
|
{:else}
|
||||||
|
Already have an account? <a
|
||||||
|
class="hover:underline text-blue-700"
|
||||||
|
href="/login">Log In</a
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<svg
|
||||||
|
width="120"
|
||||||
|
height="120"
|
||||||
|
viewBox="0 0 120 120"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M85.3281 31H34.6719C33.4724 31 32.5 31.9724 32.5 33.1719V70.8281C32.5 72.0276 33.4724 73 34.6719 73H85.3281C86.5276 73 87.5 72.0276 87.5 70.8281V33.1719C87.5 31.9724 86.5276 31 85.3281 31ZM34.6719 27C31.2632 27 28.5 29.7632 28.5 33.1719V70.8281C28.5 74.2368 31.2632 77 34.6719 77H85.3281C88.7368 77 91.5 74.2368 91.5 70.8281V33.1719C91.5 29.7632 88.7368 27 85.3281 27H34.6719Z"
|
||||||
|
fill="#0233BD"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="40.9844"
|
||||||
|
y="37.875"
|
||||||
|
width="36.4141"
|
||||||
|
height="3.08594"
|
||||||
|
rx="1.54297"
|
||||||
|
fill="#658DFE"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="40.9844"
|
||||||
|
y="44.0469"
|
||||||
|
width="36.4141"
|
||||||
|
height="3.08594"
|
||||||
|
rx="1.54297"
|
||||||
|
fill="#658DFE"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="40.9844"
|
||||||
|
y="50.2188"
|
||||||
|
width="36.4141"
|
||||||
|
height="3.08594"
|
||||||
|
rx="1.54297"
|
||||||
|
fill="#658DFE"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M23 94.0391V62.5938H97V94.0391C97 96.067 95.356 97.7109 93.3281 97.7109H26.6719C24.644 97.7109 23 96.067 23 94.0391Z"
|
||||||
|
fill="#658DFE"
|
||||||
|
stroke="#0233BD"
|
||||||
|
stroke-width="5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -37,9 +37,9 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function getProfile() {
|
async function getProfile() {
|
||||||
console.log('Get user profile');
|
|
||||||
// Get user profile
|
// Get user profile
|
||||||
const authUser = supabase.auth.user();
|
const authUser = supabase.auth.user();
|
||||||
|
console.log('Get user profile', authUser);
|
||||||
|
|
||||||
// Check if user has profile
|
// Check if user has profile
|
||||||
let {
|
let {
|
||||||
@@ -82,6 +82,7 @@
|
|||||||
goto('/dashboard');
|
goto('/dashboard');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$user.fetchingUser = false;
|
||||||
} else if (profileData) {
|
} else if (profileData) {
|
||||||
// Profile exists, go to profile page
|
// Profile exists, go to profile page
|
||||||
$user.fetchingUser = false;
|
$user.fetchingUser = false;
|
||||||
|
|||||||
+54
-36
@@ -5,81 +5,92 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { goto } from '@sapper/app';
|
||||||
import { Chasing } from 'svelte-loading-spinners';
|
import { Chasing } from 'svelte-loading-spinners';
|
||||||
import TextField from '../components/Form/TextField.svelte';
|
import TextField from '../components/Form/TextField.svelte';
|
||||||
import PrimaryButton from '../components/PrimaryButton/index.svelte';
|
import PrimaryButton from '../components/PrimaryButton/index.svelte';
|
||||||
import { getSupabase } from '../utils/functions/supabase';
|
import { getSupabase } from '../utils/functions/supabase';
|
||||||
import { user } from '../utils/store/user';
|
import { user } from '../utils/store/user';
|
||||||
|
import {
|
||||||
|
signUpValidation,
|
||||||
|
getConfirmPasswordError,
|
||||||
|
getDisableSubmit,
|
||||||
|
} from '../utils/functions/validator';
|
||||||
|
import { SIGNUP_FIELDS } from '../utils/constants/authentication';
|
||||||
import AuthUI from '../components/AuthUI/index.svelte';
|
import AuthUI from '../components/AuthUI/index.svelte';
|
||||||
|
import EmailSentIcon from '../components/Icons/EmailSentIcon.svelte';
|
||||||
|
|
||||||
export let config;
|
export let config;
|
||||||
|
|
||||||
let supabase = getSupabase(config);
|
let supabase = getSupabase(config);
|
||||||
let email;
|
let fields = Object.assign({}, SIGNUP_FIELDS);
|
||||||
let name;
|
|
||||||
let password;
|
|
||||||
let passwordAgain;
|
|
||||||
let loading = false;
|
let loading = false;
|
||||||
let success = false;
|
let success = false;
|
||||||
let errors = {};
|
let errors = {};
|
||||||
|
let submitError;
|
||||||
|
let disableSubmit = false;
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
if (!email) {
|
const validationRes = signUpValidation(fields);
|
||||||
errors = {
|
console.log('validationRes', validationRes);
|
||||||
email: 'Email is required',
|
|
||||||
password: 'Password is required',
|
if (Object.keys(validationRes).length) {
|
||||||
};
|
errors = Object.assign(errors, validationRes);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
loading = true;
|
loading = true;
|
||||||
const { data, error } = await supabase.auth.signIn(
|
const { data, error } = await supabase.auth.signUp({
|
||||||
{ email },
|
email: fields.email,
|
||||||
{
|
password: fields.password,
|
||||||
redirectTo: window.location.origin + '/login',
|
options: {
|
||||||
}
|
data: {
|
||||||
);
|
fullname: fields.name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
success = true;
|
return goto('/login');
|
||||||
email = {};
|
// success = true;
|
||||||
|
// fields = Object.assign({}, SIGNUP_FIELDS);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errors = {
|
submitError = error.error_description || error.message;
|
||||||
email: error.error_description || error.message,
|
|
||||||
};
|
|
||||||
} finally {
|
} finally {
|
||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: errors.confirmPassword = getConfirmPasswordError(fields);
|
||||||
|
$: disableSubmit = getDisableSubmit(fields);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>Sign Up - ClassroomIO</title>
|
<title>Sign Up - ClassroomIO</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<AuthUI {supabase} isLogin={false} {handleSubmit}>
|
<AuthUI {supabase} isLogin={false} {handleSubmit} showOnlyContent={success}>
|
||||||
<div class="mt-4 w-full">
|
<div class="mt-4 w-full">
|
||||||
{#if $user.fetchingUser}
|
{#if $user.fetchingUser}
|
||||||
<div class="h-40 text-md flex items-center justify-center">
|
<div class="h-40 text-md flex items-center justify-center">
|
||||||
<Chasing size="60" color="#ff3e00" unit="px" duration="1s" />
|
<Chasing size="60" color="#ff3e00" unit="px" duration="1s" />
|
||||||
</div>
|
</div>
|
||||||
{:else if success}
|
{:else if success}
|
||||||
<div class="h-40 text-md flex items-center justify-center flex-col">
|
<div class="text-md flex items-center justify-center flex-col">
|
||||||
<p class="">
|
<EmailSentIcon />
|
||||||
A link has been sent to your email. Please check both your
|
<h2 class="text-xl">Email sent!</h2>
|
||||||
|
<p class="mt-4 text-center">
|
||||||
|
We have sent a confirmation email to {fields.email}. Please check both
|
||||||
|
your
|
||||||
<strong>spam </strong>and your<strong> inbox</strong>.
|
<strong>spam </strong>and your<strong> inbox</strong>.
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-2 underline">
|
|
||||||
If our email is in your spam, do make sure you mark our email address
|
|
||||||
as safe, then move it from your spam to your inbox.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<p class="text-lg font-semibold mb-6">Create a free account</p>
|
<p class="text-lg font-semibold mb-6">Create a free account</p>
|
||||||
<TextField
|
<TextField
|
||||||
label="Full Name"
|
label="Full Name"
|
||||||
bind:value={name}
|
bind:value={fields.name}
|
||||||
type="text"
|
type="text"
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
placeholder="e.g Joke Silva"
|
placeholder="e.g Joke Silva"
|
||||||
@@ -87,37 +98,45 @@
|
|||||||
inputClassName="w-full"
|
inputClassName="w-full"
|
||||||
isDisabled={loading}
|
isDisabled={loading}
|
||||||
errorMessage={errors.name}
|
errorMessage={errors.name}
|
||||||
|
isRequired
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label="Your Email"
|
label="Your Email"
|
||||||
bind:value={email}
|
bind:value={fields.email}
|
||||||
type="email"
|
type="email"
|
||||||
placeholder="[email protected]"
|
placeholder="[email protected]"
|
||||||
className="mb-6"
|
className="mb-6"
|
||||||
inputClassName="w-full"
|
inputClassName="w-full"
|
||||||
isDisabled={loading}
|
isDisabled={loading}
|
||||||
errorMessage={errors.email}
|
errorMessage={errors.email}
|
||||||
|
isRequired
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label="Your Password"
|
label="Your Password"
|
||||||
bind:value={password}
|
bind:value={fields.password}
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="************"
|
placeholder="************"
|
||||||
className="mb-6"
|
className="mb-6"
|
||||||
inputClassName="w-full"
|
inputClassName="w-full"
|
||||||
isDisabled={loading}
|
isDisabled={loading}
|
||||||
errorMessage={errors.password}
|
errorMessage={errors.password}
|
||||||
|
helperMessage="Password must be more than 6 characters"
|
||||||
|
isRequired
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label="Confirm Password"
|
label="Confirm Password"
|
||||||
bind:value={passwordAgain}
|
bind:value={fields.confirmPassword}
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="************"
|
placeholder="************"
|
||||||
className="mb-6"
|
className="mb-6"
|
||||||
inputClassName="w-full"
|
inputClassName="w-full"
|
||||||
isDisabled={loading}
|
isDisabled={loading}
|
||||||
errorMessage={errors.passwordAgain}
|
errorMessage={errors.confirmPassword}
|
||||||
|
isRequired
|
||||||
/>
|
/>
|
||||||
|
{#if submitError}
|
||||||
|
<p class="text-sm text-red-500">{submitError}</p>
|
||||||
|
{/if}
|
||||||
<div class="w-full text-right">
|
<div class="w-full text-right">
|
||||||
<a class="text-md text-blue-700" href="/forgot"> Forgot password? </a>
|
<a class="text-md text-blue-700" href="/forgot"> Forgot password? </a>
|
||||||
</div>
|
</div>
|
||||||
@@ -126,12 +145,11 @@
|
|||||||
|
|
||||||
{#if !success}
|
{#if !success}
|
||||||
<div class="my-4 w-full flex justify-end items-center">
|
<div class="my-4 w-full flex justify-end items-center">
|
||||||
<!-- <a href="/login" class="text-blue-700 text-sm">Create an account</a> -->
|
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
label={loading ? 'Creating...' : 'Log In'}
|
label={loading ? 'Creating...' : 'Create Account'}
|
||||||
type="submit"
|
type="submit"
|
||||||
className="py-3 sm:w-full w-full"
|
className="py-3 sm:w-full w-full"
|
||||||
isDisabled={loading}
|
isDisabled={disableSubmit || loading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export const SIGNUP_FIELDS = {
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
confirmPassword: '',
|
||||||
|
};
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import z from 'zod';
|
||||||
|
|
||||||
|
const validationSchema = z.object({
|
||||||
|
name: z.string().min(3, {
|
||||||
|
message: 'Must be 3 or more characters long',
|
||||||
|
invalid_type_error: 'Must not be empty',
|
||||||
|
}),
|
||||||
|
email: z.string().email({
|
||||||
|
message: 'Invalid email address',
|
||||||
|
invalid_type_error: 'Must not be empty',
|
||||||
|
}),
|
||||||
|
password: z.string().min(6, {
|
||||||
|
message: 'Must be 6 or more characters long',
|
||||||
|
invalid_type_error: 'Must not be empty',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getConfirmPasswordError = ({ password, confirmPassword }) => {
|
||||||
|
return password > 6 && confirmPassword > 6 && password !== confirmPassword
|
||||||
|
? 'Does not match password'
|
||||||
|
: null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const signUpValidation = (fields = {}) => {
|
||||||
|
const { error } = validationSchema.safeParse(fields);
|
||||||
|
const errors = {};
|
||||||
|
|
||||||
|
if (Array.isArray(error?.issues)) {
|
||||||
|
for (const issue of error.issues) {
|
||||||
|
const { message, path } = issue;
|
||||||
|
console.log(issue);
|
||||||
|
if (path.length) {
|
||||||
|
errors[path[0]] = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDisableSubmit = ({ password, confirmPassword }) => {
|
||||||
|
return !!(password && confirmPassword && password !== confirmPassword);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user