Auth - Login/Signup/Forgot/Rest
This commit is contained in:
+32
-29
@@ -1,6 +1,6 @@
|
||||
<script context="module">
|
||||
export function preload(page, { config }) {
|
||||
return { config, isMvpUser: !!page.query.mvp };
|
||||
return { config };
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -21,16 +21,15 @@
|
||||
import { user, profile } from '../utils/store/user';
|
||||
import { getSupabase } from '../utils/functions/supabase';
|
||||
import { isMobile } from '../utils/store/useMobile';
|
||||
import { ROUTES_TO_HIDE_NAV, ROUTE } from '../utils/constants/routes';
|
||||
|
||||
export let segment;
|
||||
export let config;
|
||||
// export let isMvpUser;
|
||||
|
||||
let { page, preloading } = stores();
|
||||
|
||||
let supabase = getSupabase(config);
|
||||
let path = $page.path.replace('/', '');
|
||||
let allowUser = false;
|
||||
|
||||
const delayedPreloading = derived(preloading, (currentPreloading, set) => {
|
||||
setTimeout(() => set(currentPreloading), 250);
|
||||
@@ -60,7 +59,7 @@
|
||||
|
||||
const { data, error } = await supabase.from('profile').insert({
|
||||
id: authUser.id,
|
||||
username: regexUsernameMatch[1],
|
||||
username: regexUsernameMatch[1] + '1669991321770',
|
||||
fullname: regexUsernameMatch[1],
|
||||
email: authUser.email,
|
||||
});
|
||||
@@ -75,12 +74,8 @@
|
||||
|
||||
// Set user in sentry
|
||||
Sentry.setUser($profile);
|
||||
// If user coming to login page, then
|
||||
if (path.includes('login') || path.includes('signup')) {
|
||||
goto('/profile/' + $profile.id);
|
||||
} else if (!path.length) {
|
||||
goto('/dashboard');
|
||||
}
|
||||
|
||||
goto(ROUTE.ONBOARDING);
|
||||
}
|
||||
$user.fetchingUser = false;
|
||||
} else if (profileData) {
|
||||
@@ -94,9 +89,9 @@
|
||||
Sentry.setUser($profile);
|
||||
|
||||
// If user coming to login page, then
|
||||
if (path.includes('login') || path.includes('signup')) {
|
||||
goto('/profile/' + $profile.id);
|
||||
} else if (!path.length) {
|
||||
if (!$profile.role) {
|
||||
goto(ROUTE.ONBOARDING);
|
||||
} else {
|
||||
goto('/dashboard');
|
||||
}
|
||||
}
|
||||
@@ -120,31 +115,27 @@
|
||||
|
||||
handleResize();
|
||||
|
||||
const _isMvpUser = localStorage.getItem('mvp');
|
||||
|
||||
if (_isMvpUser || window.location.search.includes('mvp')) {
|
||||
localStorage.setItem('mvp', 'true');
|
||||
allowUser = true;
|
||||
} else if (!_isMvpUser) {
|
||||
// if (!!path) {
|
||||
// goto('/courses');
|
||||
// }
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { data: authListener } = supabase.auth.onAuthStateChange(
|
||||
(event, session) => {
|
||||
if (config.isProd) {
|
||||
handleAuthChange(event, session);
|
||||
}
|
||||
console.log(`event`, event);
|
||||
if (event == 'PASSWORD_RECOVERY') {
|
||||
console.log('PASSWORD RESET');
|
||||
}
|
||||
|
||||
if (path.includes('reset')) {
|
||||
console.log('Dont change auth when on reset page');
|
||||
return;
|
||||
}
|
||||
|
||||
if (event === 'SIGNED_IN') {
|
||||
$user.fetchingUser = true;
|
||||
getProfile();
|
||||
} else {
|
||||
console.log('not logged in, go to login');
|
||||
return goto('/courses');
|
||||
return goto('/login');
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -154,6 +145,18 @@
|
||||
};
|
||||
});
|
||||
|
||||
// if (typeof window !== 'undefined' && typeof document !== 'undefined') {
|
||||
// page.subscribe(() => {
|
||||
// const from = window.location.pathname;
|
||||
// const redirect = (href) => {
|
||||
// goto(href);
|
||||
// };
|
||||
// if (!$user.isLoggedIn && !PUBLIC_ROUTES.includes(from)) {
|
||||
// redirect('/login');
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
$: path = $page.path.replace('/', '');
|
||||
</script>
|
||||
|
||||
@@ -169,8 +172,8 @@
|
||||
<Firework size="60" color="#1d4ed8" unit="px" duration="1s" />
|
||||
</Backdrop>
|
||||
{/if}
|
||||
{#if !['login', 'signup'].includes(path) && !isCoursePage(path)}
|
||||
<Navigation {segment} disableLogin={!allowUser} />
|
||||
{#if !ROUTES_TO_HIDE_NAV.includes($page.path) && !isCoursePage(path)}
|
||||
<Navigation {segment} />
|
||||
{/if}
|
||||
|
||||
<div class="flex justify-between">
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
<section class="root">
|
||||
<PageNav title="All courses">
|
||||
<div slot="widget">
|
||||
{#if $profile.can_add_course}
|
||||
{#if $profile.role === 'educator'}
|
||||
<PrimaryButton
|
||||
label="New"
|
||||
onClick={() => ($createCourseModal.open = true)}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
<script context="module">
|
||||
export function preload(page, { config }) {
|
||||
return { config };
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { goto } from '@sapper/app';
|
||||
import TextField from '../components/Form/TextField.svelte';
|
||||
import PrimaryButton from '../components/PrimaryButton/index.svelte';
|
||||
import { VARIANTS } from '../components/PrimaryButton/constants';
|
||||
import { getSupabase } from '../utils/functions/supabase';
|
||||
import { forgotValidation } from '../utils/functions/validator';
|
||||
import { ROUTE } from '../utils/constants/routes';
|
||||
import { FORGOT_PASSWORD_FIELDS } from '../utils/constants/authentication';
|
||||
import AuthUI from '../components/AuthUI/index.svelte';
|
||||
import EmailSentIcon from '../components/Icons/EmailSentIcon.svelte';
|
||||
|
||||
export let config;
|
||||
|
||||
let supabase = getSupabase(config);
|
||||
let fields = Object.assign({}, FORGOT_PASSWORD_FIELDS);
|
||||
let submitError;
|
||||
let loading = false;
|
||||
let success = false;
|
||||
let errors = {};
|
||||
|
||||
async function handleSubmit() {
|
||||
errors = forgotValidation(fields);
|
||||
console.log('errors', errors);
|
||||
|
||||
if (Object.keys(errors).length) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
loading = true;
|
||||
const { data, error } = await supabase.auth.api.resetPasswordForEmail(
|
||||
fields.email,
|
||||
{
|
||||
redirectTo: window.location.origin + ROUTE.RESET,
|
||||
}
|
||||
);
|
||||
console.log('data', data);
|
||||
if (error) throw error;
|
||||
|
||||
success = true;
|
||||
} catch (error) {
|
||||
submitError = error.error_description || error.message;
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Reset Password - ClassroomIO</title>
|
||||
</svelte:head>
|
||||
|
||||
<AuthUI {supabase} {handleSubmit} showOnlyContent={true} showLogo={!success}>
|
||||
{#if success}
|
||||
<div class="mt-4 w-full flex items-center justify-center flex-col">
|
||||
<EmailSentIcon />
|
||||
<h3 class="text-xl font-semibold my-3">Email Sent!</h3>
|
||||
<p class="text-md mb-6 text-center">
|
||||
We have sent a confirmation email to <span class="text-blue-700"
|
||||
>{fields.email}</span
|
||||
>. Kindly check your inbox to reset password.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="my-4 w-full flex justify-end items-center flex-col">
|
||||
<PrimaryButton
|
||||
label="Okay"
|
||||
type="button"
|
||||
className="sm:w-full w-full mb-4"
|
||||
onClick={() => goto(ROUTE.LOGIN)}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="mt-4 w-full">
|
||||
<h3 class="text-xl font-semibold my-3">Forgot Password</h3>
|
||||
<p class="text-sm mb-6">We will send you a reset link to your email</p>
|
||||
<TextField
|
||||
label="Your email"
|
||||
bind:value={fields.email}
|
||||
type="email"
|
||||
autoFocus={true}
|
||||
placeholder="you@domain.com"
|
||||
className="mb-6"
|
||||
inputClassName="w-full"
|
||||
isDisabled={loading}
|
||||
errorMessage={errors.email}
|
||||
/>
|
||||
{#if submitError}
|
||||
<p class="text-sm text-red-500">{submitError}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="my-4 w-full flex justify-end items-center flex-col">
|
||||
<PrimaryButton
|
||||
label="Reset Password"
|
||||
type="submit"
|
||||
className="sm:w-full w-full mb-4"
|
||||
isDisabled={loading}
|
||||
isLoading={loading}
|
||||
/>
|
||||
<PrimaryButton
|
||||
label="Cancel"
|
||||
type="button"
|
||||
className="sm:w-full w-full text-blue-700"
|
||||
variant={VARIANTS.NONE}
|
||||
onClick={() => goto(ROUTE.LOGIN)}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</AuthUI>
|
||||
+64
-73
@@ -5,47 +5,47 @@
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { Chasing } from 'svelte-loading-spinners';
|
||||
import { goto } from '@sapper/app';
|
||||
import TextField from '../components/Form/TextField.svelte';
|
||||
import PrimaryButton from '../components/PrimaryButton/index.svelte';
|
||||
import { getSupabase } from '../utils/functions/supabase';
|
||||
import { user } from '../utils/store/user';
|
||||
import { authValidation } from '../utils/functions/validator';
|
||||
import { ROUTE } from '../utils/constants/routes';
|
||||
import { LOGIN_FIELDS } from '../utils/constants/authentication';
|
||||
import AuthUI from '../components/AuthUI/index.svelte';
|
||||
|
||||
export let config;
|
||||
|
||||
let formRef;
|
||||
let supabase = getSupabase(config);
|
||||
let email;
|
||||
let password;
|
||||
let fields = Object.assign({}, LOGIN_FIELDS);
|
||||
let submitError;
|
||||
let loading = false;
|
||||
let success = false;
|
||||
let errors = {};
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!email) {
|
||||
errors = {
|
||||
email: 'Email is required',
|
||||
password: 'Password is required',
|
||||
};
|
||||
const validationRes = authValidation(fields);
|
||||
console.log('validationRes', validationRes);
|
||||
|
||||
if (Object.keys(validationRes).length) {
|
||||
errors = Object.assign(errors, validationRes);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
loading = true;
|
||||
const { data, error } = await supabase.auth.signIn(
|
||||
{ email },
|
||||
{
|
||||
redirectTo: window.location.origin + '/login',
|
||||
}
|
||||
);
|
||||
const { data, error } = await supabase.auth.signIn({
|
||||
email: fields.email,
|
||||
password: fields.password,
|
||||
});
|
||||
console.log('data', data);
|
||||
if (error) throw error;
|
||||
|
||||
success = true;
|
||||
email = {};
|
||||
return goto(ROUTE.DASHBOARD);
|
||||
} catch (error) {
|
||||
errors = {
|
||||
email: error.error_description || error.message,
|
||||
};
|
||||
submitError = error.error_description || error.message;
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
@@ -53,64 +53,55 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Login - ClassroomIO</title>
|
||||
<title>Welcome back to ClassroomIO</title>
|
||||
</svelte:head>
|
||||
|
||||
<AuthUI {supabase} isLogin={true} {handleSubmit}>
|
||||
<AuthUI
|
||||
{supabase}
|
||||
isLogin={true}
|
||||
{handleSubmit}
|
||||
showOnlyContent={loading}
|
||||
bind:formRef
|
||||
>
|
||||
<div class="mt-4 w-full">
|
||||
{#if $user.fetchingUser}
|
||||
<div class="h-40 text-md flex items-center justify-center">
|
||||
<Chasing size="60" color="#ff3e00" unit="px" duration="1s" />
|
||||
</div>
|
||||
{:else if success}
|
||||
<div class="h-40 text-md flex items-center justify-center flex-col">
|
||||
<p class="">
|
||||
A link has been sent to your email. Please check both your
|
||||
<strong>spam </strong>and your<strong> inbox</strong>.
|
||||
</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>
|
||||
{:else}
|
||||
<p class="text-lg font-semibold mb-6">Welcome back</p>
|
||||
<TextField
|
||||
label="Your email"
|
||||
bind:value={email}
|
||||
type="email"
|
||||
autoFocus={true}
|
||||
placeholder="you@domain.com"
|
||||
className="mb-6"
|
||||
inputClassName="w-full"
|
||||
isDisabled={loading}
|
||||
errorMessage={errors.email}
|
||||
/>
|
||||
<TextField
|
||||
label="Your password"
|
||||
bind:value={password}
|
||||
type="password"
|
||||
placeholder="************"
|
||||
className="mb-6"
|
||||
inputClassName="w-full"
|
||||
isDisabled={loading}
|
||||
errorMessage={errors.password}
|
||||
/>
|
||||
<div class="w-full text-right">
|
||||
<a class="text-md text-blue-700" href="/forgot"> Forgot password? </a>
|
||||
</div>
|
||||
<p class="text-lg font-semibold mb-6">Welcome back</p>
|
||||
<TextField
|
||||
label="Your email"
|
||||
bind:value={fields.email}
|
||||
type="email"
|
||||
autoFocus={true}
|
||||
placeholder="you@domain.com"
|
||||
className="mb-6"
|
||||
inputClassName="w-full"
|
||||
isDisabled={loading}
|
||||
errorMessage={errors.email}
|
||||
/>
|
||||
<TextField
|
||||
label="Your password"
|
||||
bind:value={fields.password}
|
||||
type="password"
|
||||
placeholder="************"
|
||||
className="mb-6"
|
||||
inputClassName="w-full"
|
||||
isDisabled={loading}
|
||||
errorMessage={errors.password}
|
||||
/>
|
||||
{#if submitError}
|
||||
<p class="text-sm text-red-500">{submitError}</p>
|
||||
{/if}
|
||||
<div class="w-full text-right">
|
||||
<a class="text-md text-blue-700" href="/forgot"> Forgot password? </a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if !success}
|
||||
<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
|
||||
label={loading ? 'Creating...' : 'Log In'}
|
||||
type="submit"
|
||||
className="py-3 sm:w-full w-full"
|
||||
isDisabled={loading}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<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
|
||||
label="Log In"
|
||||
type="submit"
|
||||
className="sm:w-full w-full"
|
||||
isDisabled={loading}
|
||||
isLoading={loading}
|
||||
/>
|
||||
</div>
|
||||
</AuthUI>
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '@sapper/app';
|
||||
import CheckmarkFilled20 from 'carbon-icons-svelte/lib/CheckmarkFilled20';
|
||||
import TextField from '../components/Form/TextField.svelte';
|
||||
import UserProfileIcon from '../components/Icons/UserProfileIcon.svelte';
|
||||
import PrimaryButton from '../components/PrimaryButton/index.svelte';
|
||||
import { VARIANTS } from '../components/PrimaryButton/constants';
|
||||
import { profile } from '../utils/store/user';
|
||||
import { ROUTE } from '../utils/constants/routes';
|
||||
import { onboardingValidation } from '../utils/functions/validator';
|
||||
import { supabase } from '../utils/functions/supabase';
|
||||
|
||||
enum OnboardingRoles {
|
||||
Educator = 'educator',
|
||||
Student = 'student',
|
||||
}
|
||||
|
||||
interface OnboardingField {
|
||||
fullname?: string;
|
||||
role?: OnboardingRoles;
|
||||
goal?: string;
|
||||
source?: string;
|
||||
}
|
||||
|
||||
interface Goal {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const maxSteps = 2;
|
||||
|
||||
let fields: OnboardingField = {
|
||||
fullname: '',
|
||||
};
|
||||
let errors: OnboardingField = {};
|
||||
let progress = 50;
|
||||
let step = 1;
|
||||
let goals: Goal[] = [];
|
||||
let loading = false;
|
||||
|
||||
const roles = [OnboardingRoles.Educator, OnboardingRoles.Student];
|
||||
|
||||
const educatorGoals: Goal[] = [
|
||||
{
|
||||
label: 'Sell courses online',
|
||||
value: 'sell-online',
|
||||
},
|
||||
{
|
||||
label: 'Teach my current students online',
|
||||
value: 'teach-online',
|
||||
},
|
||||
{
|
||||
label: 'On another platform, expanding here',
|
||||
value: 'expanding-platform',
|
||||
},
|
||||
{
|
||||
label: 'Just here to explore',
|
||||
value: 'explore',
|
||||
},
|
||||
];
|
||||
|
||||
const studentGoals: Goal[] = [
|
||||
{
|
||||
label: 'Find new courses',
|
||||
value: 'find-course',
|
||||
},
|
||||
{
|
||||
label: 'Join my class online',
|
||||
value: 'join-class',
|
||||
},
|
||||
{
|
||||
label: 'Just here to explore',
|
||||
value: 'explore',
|
||||
},
|
||||
];
|
||||
|
||||
const sources = [
|
||||
{
|
||||
label: 'Articles',
|
||||
value: 'articles',
|
||||
},
|
||||
{
|
||||
label: 'Search engine',
|
||||
value: 'search-engine',
|
||||
},
|
||||
{
|
||||
label: 'Social media',
|
||||
value: 'social-media',
|
||||
},
|
||||
{
|
||||
label: 'Friends and family',
|
||||
value: 'friends-family',
|
||||
},
|
||||
];
|
||||
|
||||
const onGoalInputChange = (event: KeyboardEvent) => {
|
||||
console.log('event', event);
|
||||
fields.goal = (event.target as HTMLInputElement).value;
|
||||
};
|
||||
|
||||
const onSourceInputChange = (event: KeyboardEvent) => {
|
||||
console.log('event', event);
|
||||
fields.source = (event.target as HTMLInputElement).value;
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
// Run validation
|
||||
console.log(fields);
|
||||
errors = onboardingValidation(fields, step);
|
||||
console.log('errors', errors);
|
||||
|
||||
if (Object.keys(errors).length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (step === maxSteps) {
|
||||
// Submit filed
|
||||
loading = true;
|
||||
let { data, error } = await supabase
|
||||
.from('profile')
|
||||
.update(fields)
|
||||
.match({ id: $profile.id });
|
||||
loading = false;
|
||||
|
||||
console.log('data', data);
|
||||
console.log('error', error);
|
||||
return goto(ROUTE.DASHBOARD);
|
||||
}
|
||||
|
||||
// Move to next step
|
||||
step = step + 1;
|
||||
};
|
||||
|
||||
$: {
|
||||
progress = Math.round((step / maxSteps) * 100);
|
||||
goals =
|
||||
fields.role === OnboardingRoles.Educator ? educatorGoals : studentGoals;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full min-h-screen flex justify-center">
|
||||
<div class="flex justify-center items-center flex-col w-9/12 lg:w-2/5">
|
||||
<!-- Header With Logo -->
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="flex items-center w-full justify-center mb-4">
|
||||
<img
|
||||
src="/logo-192.png"
|
||||
alt="ClassroomIO logo"
|
||||
height="50"
|
||||
width="50"
|
||||
data-atf="1"
|
||||
/>
|
||||
<h4 class="text-xl">ClassroomIO</h4>
|
||||
</div>
|
||||
|
||||
<!-- Loggedin Email -->
|
||||
<div
|
||||
class="w-64 flex items-center justify-center mb-6 py-6 bg-gray-100 rounded-2xl border border-gray-300"
|
||||
>
|
||||
<UserProfileIcon />
|
||||
<p class="text-sm ml-2">{$profile.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-container overflow-y-auto w-full pl-2">
|
||||
{#if step === 1}
|
||||
<!-- Role Question -->
|
||||
<div id="role-question" class="flex items-start flex-col mb-6">
|
||||
<!-- Full name questionn -->
|
||||
<TextField
|
||||
label="Full Name"
|
||||
bind:value={fields.fullname}
|
||||
type="text"
|
||||
placeholder="e.g Joke Silva"
|
||||
className="mb-5 w-7/12"
|
||||
labelClassName="text-lg font-normal"
|
||||
errorMessage={errors.fullname}
|
||||
/>
|
||||
|
||||
<!-- Roal question -->
|
||||
<div>
|
||||
<p class="text-lg font-normal mb-2">What's your main goal today?</p>
|
||||
<p class="text-sm mb-6">
|
||||
This will help us personalize your experience on Classroomio
|
||||
</p>
|
||||
|
||||
<!-- Roles -->
|
||||
<div class="flex items-center">
|
||||
{#each roles as role, index}
|
||||
<button
|
||||
class="w-52 h-44 p-4 {index === 0 &&
|
||||
'mr-4'} flex flex-col justify-between bg-gray-200 rounded-md {fields.role ===
|
||||
role &&
|
||||
'border-2 border-blue-700'} hover:shadow-xl hover:border-blue-700 transition delay-150 duration-300 ease-in-out"
|
||||
type="button"
|
||||
on:click={() => (fields.role = role)}
|
||||
>
|
||||
<div class="flex flex-col items-end w-full">
|
||||
{#if fields.role === role}
|
||||
<CheckmarkFilled20 class="text-blue-700" />
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-lg text-left capitalize">{role}</h4>
|
||||
<p class="text-sm text-black-200">
|
||||
{role === OnboardingRoles.Educator ? 'Teach' : 'Learn'} on
|
||||
ClassroomIO
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{#if errors.role}
|
||||
<p class="text-sm text-red-500">{errors.role}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Goal/Source Question -->
|
||||
<div id="goal-question" class="flex items-center flex-col mb-6">
|
||||
<div class="w-10/12">
|
||||
<!-- Goal Question -->
|
||||
<div class="w-full flex items-start flex-col justify-between mb-10">
|
||||
<label for="text-field" class="m-0 text-lg font-normal mb-3">
|
||||
What brings you to ClassroomIO?
|
||||
</label>
|
||||
|
||||
<!-- Loop through Goals -->
|
||||
{#each goals as goal}
|
||||
<label class="w-full inline-flex items-center mb-1 font-light">
|
||||
<input
|
||||
type="radio"
|
||||
bind:group={fields.goal}
|
||||
name="goal"
|
||||
value={goal.value}
|
||||
class="mr-2"
|
||||
/>
|
||||
{goal.label}
|
||||
</label>
|
||||
{/each}
|
||||
<!-- Goal: Error message -->
|
||||
{#if errors.goal}
|
||||
<p class="text-sm text-red-500">{errors.goal}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Source Question -->
|
||||
<div class="w-full flex items-start flex-col justify-between">
|
||||
<label for="text-field" class="m-0 text-lg font-normal mb-3">
|
||||
How did you hear about us?
|
||||
</label>
|
||||
|
||||
<!-- Loop through Goals -->
|
||||
{#each sources as source}
|
||||
<label class="w-full inline-flex items-center mb-1 font-light">
|
||||
<input
|
||||
type="radio"
|
||||
bind:group={fields.source}
|
||||
name="source"
|
||||
value={source.value}
|
||||
class="mr-2"
|
||||
/>
|
||||
{source.label}
|
||||
</label>
|
||||
{/each}
|
||||
<!-- Goal: Error message -->
|
||||
{#if errors.source}
|
||||
<p class="text-sm text-red-500">{errors.source}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="flex justify-between items-center mt-8 w-full">
|
||||
<div class="w-24 h-2 bg-gray-300 relative">
|
||||
<span
|
||||
class="progress absolute top-0 left-0 bg-blue-700 h-full"
|
||||
style="width: {progress}%;"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
{#if step > 1}
|
||||
<PrimaryButton
|
||||
label="Back"
|
||||
variant={VARIANTS.NONE}
|
||||
className="py-2 px-6 mr-2 text-blue-700"
|
||||
onClick={() => (step = step - 1)}
|
||||
/>
|
||||
{/if}
|
||||
<PrimaryButton
|
||||
label="Continue"
|
||||
type="button"
|
||||
className="px-5 py-3"
|
||||
isLoading={loading}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.form-container {
|
||||
max-height: 66%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,113 @@
|
||||
<script context="module">
|
||||
export function preload(page, { config }) {
|
||||
return { config };
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { goto } from '@sapper/app';
|
||||
import TextField from '../components/Form/TextField.svelte';
|
||||
import PrimaryButton from '../components/PrimaryButton/index.svelte';
|
||||
import { getSupabase } from '../utils/functions/supabase';
|
||||
import {
|
||||
resetValidation,
|
||||
getConfirmPasswordError,
|
||||
getDisableSubmit,
|
||||
} from '../utils/functions/validator';
|
||||
import { RESET_FIELDS } from '../utils/constants/authentication';
|
||||
import AuthUI from '../components/AuthUI/index.svelte';
|
||||
|
||||
export let config;
|
||||
|
||||
let supabase = getSupabase(config);
|
||||
let fields = Object.assign({}, RESET_FIELDS);
|
||||
let loading = false;
|
||||
let success = false;
|
||||
let errors = {};
|
||||
let submitError;
|
||||
let disableSubmit = false;
|
||||
let formRef;
|
||||
|
||||
async function handleSubmit(e) {
|
||||
errors = resetValidation(fields);
|
||||
console.log('errors', errors);
|
||||
|
||||
if (Object.keys(errors).length) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
loading = true;
|
||||
const { data, error } = await supabase.auth.update({
|
||||
password: fields.password,
|
||||
});
|
||||
console.log('data', data);
|
||||
if (error) throw error;
|
||||
|
||||
formRef?.reset();
|
||||
|
||||
return goto('/login');
|
||||
} catch (error) {
|
||||
submitError = error.error_description || error.message;
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
$: errors.confirmPassword = getConfirmPasswordError(fields);
|
||||
$: disableSubmit = getDisableSubmit(fields);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Join ClassroomIO</title>
|
||||
</svelte:head>
|
||||
|
||||
<AuthUI
|
||||
{supabase}
|
||||
isLogin={false}
|
||||
{handleSubmit}
|
||||
showOnlyContent={true}
|
||||
showLogo={true}
|
||||
bind:formRef
|
||||
>
|
||||
<div class="mt-4 w-full">
|
||||
<h3 class="text-xl font-semibold my-3">New Password</h3>
|
||||
<p class="text-sm mb-6">Enter your new password details</p>
|
||||
<TextField
|
||||
label="Your Password"
|
||||
bind:value={fields.password}
|
||||
type="password"
|
||||
placeholder="************"
|
||||
className="mb-6"
|
||||
inputClassName="w-full"
|
||||
isDisabled={loading}
|
||||
errorMessage={errors.password}
|
||||
helperMessage="Password must be more than 6 characters"
|
||||
isRequired
|
||||
/>
|
||||
<TextField
|
||||
label="Confirm Password"
|
||||
bind:value={fields.confirmPassword}
|
||||
type="password"
|
||||
placeholder="************"
|
||||
className="mb-6"
|
||||
inputClassName="w-full"
|
||||
isDisabled={loading}
|
||||
errorMessage={errors.confirmPassword}
|
||||
isRequired
|
||||
/>
|
||||
{#if submitError}
|
||||
<p class="text-sm text-red-500">{submitError}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="my-4 w-full flex justify-end items-center">
|
||||
<PrimaryButton
|
||||
label="Reset Password"
|
||||
type="submit"
|
||||
className="sm:w-full w-full"
|
||||
isDisabled={disableSubmit || loading}
|
||||
isLoading={loading}
|
||||
/>
|
||||
</div>
|
||||
</AuthUI>
|
||||
+73
-91
@@ -6,19 +6,16 @@
|
||||
|
||||
<script>
|
||||
import { goto } from '@sapper/app';
|
||||
import { Chasing } from 'svelte-loading-spinners';
|
||||
import TextField from '../components/Form/TextField.svelte';
|
||||
import PrimaryButton from '../components/PrimaryButton/index.svelte';
|
||||
import { getSupabase } from '../utils/functions/supabase';
|
||||
import { user } from '../utils/store/user';
|
||||
import {
|
||||
signUpValidation,
|
||||
authValidation,
|
||||
getConfirmPasswordError,
|
||||
getDisableSubmit,
|
||||
} from '../utils/functions/validator';
|
||||
import { SIGNUP_FIELDS } from '../utils/constants/authentication';
|
||||
import AuthUI from '../components/AuthUI/index.svelte';
|
||||
import EmailSentIcon from '../components/Icons/EmailSentIcon.svelte';
|
||||
|
||||
export let config;
|
||||
|
||||
@@ -29,9 +26,10 @@
|
||||
let errors = {};
|
||||
let submitError;
|
||||
let disableSubmit = false;
|
||||
let formRef;
|
||||
|
||||
async function handleSubmit() {
|
||||
const validationRes = signUpValidation(fields);
|
||||
async function handleSubmit(e) {
|
||||
const validationRes = authValidation(fields);
|
||||
console.log('validationRes', validationRes);
|
||||
|
||||
if (Object.keys(validationRes).length) {
|
||||
@@ -44,14 +42,12 @@
|
||||
const { data, error } = await supabase.auth.signUp({
|
||||
email: fields.email,
|
||||
password: fields.password,
|
||||
options: {
|
||||
data: {
|
||||
fullname: fields.name,
|
||||
},
|
||||
},
|
||||
});
|
||||
console.log('data', data);
|
||||
if (error) throw error;
|
||||
|
||||
formRef?.reset();
|
||||
|
||||
return goto('/login');
|
||||
// success = true;
|
||||
// fields = Object.assign({}, SIGNUP_FIELDS);
|
||||
@@ -67,90 +63,76 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Sign Up - ClassroomIO</title>
|
||||
<title>Join ClassroomIO</title>
|
||||
</svelte:head>
|
||||
|
||||
<AuthUI {supabase} isLogin={false} {handleSubmit} showOnlyContent={success}>
|
||||
<AuthUI
|
||||
{supabase}
|
||||
isLogin={false}
|
||||
{handleSubmit}
|
||||
showOnlyContent={success}
|
||||
bind:formRef
|
||||
>
|
||||
<div class="mt-4 w-full">
|
||||
{#if $user.fetchingUser}
|
||||
<div class="h-40 text-md flex items-center justify-center">
|
||||
<Chasing size="60" color="#ff3e00" unit="px" duration="1s" />
|
||||
</div>
|
||||
{:else if success}
|
||||
<div class="text-md flex items-center justify-center flex-col">
|
||||
<EmailSentIcon />
|
||||
<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>.
|
||||
</p>
|
||||
</div>
|
||||
{:else}
|
||||
<p class="text-lg font-semibold mb-6">Create a free account</p>
|
||||
<TextField
|
||||
label="Full Name"
|
||||
bind:value={fields.name}
|
||||
type="text"
|
||||
autoFocus={true}
|
||||
placeholder="e.g Joke Silva"
|
||||
className="mb-6"
|
||||
inputClassName="w-full"
|
||||
isDisabled={loading}
|
||||
errorMessage={errors.name}
|
||||
isRequired
|
||||
/>
|
||||
<TextField
|
||||
label="Your Email"
|
||||
bind:value={fields.email}
|
||||
type="email"
|
||||
placeholder="you@domain.com"
|
||||
className="mb-6"
|
||||
inputClassName="w-full"
|
||||
isDisabled={loading}
|
||||
errorMessage={errors.email}
|
||||
isRequired
|
||||
/>
|
||||
<TextField
|
||||
label="Your Password"
|
||||
bind:value={fields.password}
|
||||
type="password"
|
||||
placeholder="************"
|
||||
className="mb-6"
|
||||
inputClassName="w-full"
|
||||
isDisabled={loading}
|
||||
errorMessage={errors.password}
|
||||
helperMessage="Password must be more than 6 characters"
|
||||
isRequired
|
||||
/>
|
||||
<TextField
|
||||
label="Confirm Password"
|
||||
bind:value={fields.confirmPassword}
|
||||
type="password"
|
||||
placeholder="************"
|
||||
className="mb-6"
|
||||
inputClassName="w-full"
|
||||
isDisabled={loading}
|
||||
errorMessage={errors.confirmPassword}
|
||||
isRequired
|
||||
/>
|
||||
{#if submitError}
|
||||
<p class="text-sm text-red-500">{submitError}</p>
|
||||
{/if}
|
||||
<div class="w-full text-right">
|
||||
<a class="text-md text-blue-700" href="/forgot"> Forgot password? </a>
|
||||
</div>
|
||||
<p class="text-lg font-semibold mb-6">Create a free account</p>
|
||||
<!-- <TextField
|
||||
label="Full Name"
|
||||
bind:value={fields.name}
|
||||
type="text"
|
||||
autoFocus={true}
|
||||
placeholder="e.g Joke Silva"
|
||||
className="mb-6"
|
||||
inputClassName="w-full"
|
||||
isDisabled={loading}
|
||||
errorMessage={errors.name}
|
||||
isRequired
|
||||
/> -->
|
||||
<TextField
|
||||
label="Your Email"
|
||||
bind:value={fields.email}
|
||||
type="email"
|
||||
placeholder="you@domain.com"
|
||||
className="mb-6"
|
||||
inputClassName="w-full"
|
||||
isDisabled={loading}
|
||||
errorMessage={errors.email}
|
||||
isRequired
|
||||
/>
|
||||
<TextField
|
||||
label="Your Password"
|
||||
bind:value={fields.password}
|
||||
type="password"
|
||||
placeholder="************"
|
||||
className="mb-6"
|
||||
inputClassName="w-full"
|
||||
isDisabled={loading}
|
||||
errorMessage={errors.password}
|
||||
helperMessage="Password must be more than 6 characters"
|
||||
isRequired
|
||||
/>
|
||||
<TextField
|
||||
label="Confirm Password"
|
||||
bind:value={fields.confirmPassword}
|
||||
type="password"
|
||||
placeholder="************"
|
||||
className="mb-6"
|
||||
inputClassName="w-full"
|
||||
isDisabled={loading}
|
||||
errorMessage={errors.confirmPassword}
|
||||
isRequired
|
||||
/>
|
||||
{#if submitError}
|
||||
<p class="text-sm text-red-500">{submitError}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if !success}
|
||||
<div class="my-4 w-full flex justify-end items-center">
|
||||
<PrimaryButton
|
||||
label={loading ? 'Creating...' : 'Create Account'}
|
||||
type="submit"
|
||||
className="py-3 sm:w-full w-full"
|
||||
isDisabled={disableSubmit || loading}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="my-4 w-full flex justify-end items-center">
|
||||
<PrimaryButton
|
||||
label="Create Account"
|
||||
type="submit"
|
||||
className="sm:w-full w-full"
|
||||
isDisabled={disableSubmit || loading}
|
||||
isLoading={loading}
|
||||
/>
|
||||
</div>
|
||||
</AuthUI>
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
<script>
|
||||
import TimeTable from "../components/TimeTable/index.svelte";
|
||||
import { stores } from "@sapper/app";
|
||||
|
||||
const { page } = stores();
|
||||
let tab = "";
|
||||
|
||||
$: tab = $page.query.tab || "mine";
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Расписание</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="root">
|
||||
<div class="header flex justify-between mb-2">
|
||||
<h4 class="title">Расписание</h4>
|
||||
<div class="flex justify-evenly items-center">
|
||||
<a
|
||||
class="mr-5 text-sm {tab === 'mine' && 'active'}"
|
||||
href="/timetable?tab=mine"
|
||||
>
|
||||
Мои
|
||||
</a>
|
||||
<a
|
||||
class="mr-5 text-sm {tab === 'saved' && 'active'}"
|
||||
href="/timetable?tab=saved"
|
||||
>
|
||||
Сохранение
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<TimeTable />
|
||||
<TimeTable />
|
||||
<TimeTable />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.root {
|
||||
margin: 0 auto;
|
||||
border-left: 1px solid var(--border-color);
|
||||
border-right: 1px solid var(--border-color);
|
||||
}
|
||||
.root .header {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.active {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.active::after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background-color: var(--main-primary-color);
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user