CLASSROOMIO-T-36: Student authentication flow

Co-authored-by: Rotimi Best <rotimiibitoyeemma@gmail.com>

Merge-request: CLASSROOMIO-MR-12
Merged-by: Digdippa Team <digdippa@gmail.com>
This commit is contained in:
Rotimi Best
2023-05-16 04:31:45 +00:00
committed by Space Cloud
co-authored by Rotimi Best
parent 8d0f1e4de1
commit 68e1dbb1b5
12 changed files with 387 additions and 38 deletions
+44
View File
@@ -0,0 +1,44 @@
<svg
width="25"
height="24"
viewBox="0 0 25 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="dark"
>
<path
d="M16.9999 2H8.99991C8.46948 2 7.96077 2.21071 7.58569 2.58579C7.21062 2.96086 6.99991 3.46957 6.99991 4V20C6.99991 20.5304 7.21062 21.0391 7.58569 21.4142C7.96077 21.7893 8.46948 22 8.99991 22H20.9999C21.5303 22 22.039 21.7893 22.4141 21.4142C22.7892 21.0391 22.9999 20.5304 22.9999 20V8L16.9999 2Z"
stroke="#7A797D"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M16.9999 2V8H22.9999"
stroke="#7A797D"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M18.9999 13H10.9999"
stroke="#7A797D"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M18.9999 17H10.9999"
stroke="#7A797D"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M12.9999 9H11.9999H10.9999"
stroke="#7A797D"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+119
View File
@@ -0,0 +1,119 @@
<script>
import { stores } from '@sapper/app';
import Help from 'carbon-icons-svelte/lib/Help20';
import TextChip from '../../components/Chip/Text.svelte';
import OrgSelector from '../../components/OrgSelector/OrgSelector.svelte';
import HomeIcon from '../../components/Icons/HomeIcon.svelte';
import CourseIcon from '../../components/Icons/CourseIcon.svelte';
import ExerciseIcon from '../../components/Icons/ExerciseIcon.svelte';
import Avatar from '../../components/Avatar/index.svelte';
import { currentOrg } from '../../utils/store/org';
import { profile } from '../../utils/store/user';
let activeClass = 'bg-gray-200 dark:bg-gray-500';
let { page } = stores();
function isActive(pagePath, itemPath) {
const pageLinkItems = pagePath.split('/');
const itemLinkItems = itemPath.split('/');
if (itemLinkItems.length !== pageLinkItems.length) {
return false;
}
return pagePath.includes(itemPath);
}
</script>
<aside class="root bg-gray-100 dark:bg-gray-700 h-full">
<div class="h-full flex flex-col">
<div class="border-b border-gray-200 pt-5 px-4">
{#if $currentOrg.avatar_url}
<Avatar
src={$currentOrg.avatar_url}
name={$currentOrg.name}
shape="rounded-md"
width="w-7"
height="h-7"
/>
{:else}
<TextChip
value={$currentOrg.shortName}
className="bg-blue-200 font-bold"
/>
{/if}
<OrgSelector />
<ul class="my-5">
<a href="/lms" class="text-black">
<li
class="flex items-center py-3 px-4 mb-2 rounded hover:bg-gray-200 dark:hover:bg-gray-500 {isActive(
$page.path,
`/lms`
) && activeClass}"
>
<HomeIcon />
<p class="dark:text-white ml-2">Home</p>
</li>
</a>
<a href="/lms/mylearning" class="text-black">
<li
class="flex items-center py-3 px-4 mb-2 rounded hover:bg-gray-200 dark:hover:bg-gray-500 {isActive(
$page.path,
`/lms/mylearning`
) && activeClass}"
>
<CourseIcon />
<p class="dark:text-white ml-2">My Learning</p>
</li>
</a>
<a href="/lms/exercises" class="text-black">
<li
class="flex items-center py-3 px-4 mb-2 rounded hover:bg-gray-200 dark:hover:bg-gray-500 {isActive(
$page.path,
`/lms/exercises`
) && activeClass}"
>
<ExerciseIcon />
<p class="dark:text-white ml-2">Exercises</p>
</li>
</a>
</ul>
</div>
<span class="flex-grow" />
<ul class="my-5 pb-5 px-4">
<a href="/lms" class="text-black">
<li
class="flex items-center py-3 px-4 mb-2 rounded hover:bg-gray-200 dark:hover:bg-gray-500"
>
<Help class="carbon-icon" />
<p class="dark:text-white ml-2">Help</p>
</li>
</a>
<a href="/lms/settings" class="text-black">
<li
class="flex items-center py-3 px-4 mb-2 rounded hover:bg-gray-200 dark:hover:bg-gray-500 {isActive(
$page.path,
`/lms/settings`
) && activeClass}"
>
<Avatar
src={$profile.avatar_url}
name={$profile.username}
width="w-7"
height="h-7"
/>
<p class="dark:text-white ml-2">Settings</p>
</li>
</a>
</ul>
</div>
</aside>
<style>
.root {
width: 250px;
}
</style>
+28 -20
View File
@@ -1,6 +1,7 @@
<script>
import { goto } from '@sapper/app';
import { goto, stores } from '@sapper/app';
import { user } from '../../utils/store/user';
import { isCoursePage } from '../../utils/functions/app';
import PrimaryButton from '../PrimaryButton/index.svelte';
import { VARIANTS } from '../PrimaryButton/constants';
@@ -9,13 +10,16 @@
export let logo = '/logo-192.png';
export let orgName = 'ClassroomIO';
let { page } = stores();
let navClass = '';
const redirect = isCoursePage($page.path) ? `?redirect=${$page.path}` : '';
$: navClass = ['discussion'].includes(segment) ? 'hide' : '';
</script>
<nav
class="{navClass} flex w-full p-2 border-t-0 border-r-0 border-b border-l-0 border-gray-300"
class="{navClass} bg-white sticky top-0 z-50 flex w-full p-2 border-t-0 border-r-0 border-b border-l-0 border-gray-300"
>
<ul class="flex w-full items-center">
<div class="logo">
@@ -76,24 +80,28 @@
<span class="flex-grow" />
<li>
{#if !disableLogin}
<div class="flex">
<PrimaryButton
label="Login"
variant={VARIANTS.NONE}
className="py-3 px-6 mx-2 app-background-color"
onClick={() => goto('/login')}
/>
<PrimaryButton
label="Sign Up"
variant={VARIANTS.CONTAINED}
className="py-3 px-6 mx-2"
onClick={() => goto('/signup')}
/>
</div>
{/if}
</li>
{#if $user.isLoggedIn}
<li><a class="block" href="/lms"> Go to LMS </a></li>
{:else}
<li>
{#if !disableLogin}
<div class="flex">
<PrimaryButton
label="Login"
variant={VARIANTS.NONE}
className="py-3 px-6 mx-2 app-background-color"
onClick={() => goto('/login' + redirect)}
/>
<PrimaryButton
label="Sign Up"
variant={VARIANTS.CONTAINED}
className="py-3 px-6 mx-2"
onClick={() => goto('/signup' + redirect)}
/>
</div>
{/if}
</li>
{/if}
<!-- <li class="hidden">
<a
class="flex items-center"
+102
View File
@@ -0,0 +1,102 @@
<script>
import { stores } from '@sapper/app';
import NotificationIcon from 'carbon-icons-svelte/lib/Notification20';
import Moon from 'carbon-icons-svelte/lib/Moon16';
import Sun from 'carbon-icons-svelte/lib/Sun16';
import IconButton from '../IconButton/index.svelte';
import { appStore } from '../../utils/store/app';
import { toggleBodyByTheme } from '../../utils/functions/app';
export let navClass = '';
const { page } = stores();
function toggleTheme() {
$appStore.isDark = !$appStore.isDark;
toggleBodyByTheme($appStore.isDark);
localStorage.setItem('theme', $appStore.isDark ? 'dark' : '');
}
</script>
<nav class="{navClass} flex w-full p-2 md:px-6 bg-blue-600">
<ul class="flex w-full items-center">
<div class="">
<a
href={$page.path}
title="Go to ClassroomIO Home"
id="logo"
class="text-lg"
>
ClassroomIO
</a>
</div>
<span class="flex-grow" />
<li>
<NotificationIcon class="text-white mr-2" />
</li>
<li>
<IconButton size="small" onClick={toggleTheme}>
{#if $appStore.isDark}
<Sun class="text-white" />
{:else}
<Moon class="text-white" />
{/if}
</IconButton>
</li>
<li />
</ul>
</nav>
<style>
ul {
padding: 0;
}
/* clearfix */
ul::after {
content: '';
display: block;
clear: both;
}
/* li.new-question {
margin-top: 10px;
} */
a {
text-decoration: none;
color: #fff;
font-weight: 700;
}
/* [aria-current] {
color: var(--main-primary-color);
position: relative;
display: inline-block;
}
[aria-current]::after {
position: absolute;
content: '';
width: calc(100% - 3em);
height: 2px;
background-color: var(--main-primary-color);
display: block;
bottom: -17px;
} */
@media only screen and (max-width: 1002px) {
nav.hide {
display: none;
}
ul {
align-items: center;
}
a {
padding: 0 0.5em;
}
}
</style>
+1 -1
View File
@@ -99,7 +99,7 @@
</div>
<span class="flex-grow" />
<ul class="my-5 pb-5 px-4">
<a href="/help" class="text-black">
<a href="/org" class="text-black">
<li
class="flex items-center py-3 px-4 mb-2 rounded hover:bg-gray-200 dark:hover:bg-gray-500"
>
+56 -16
View File
@@ -15,22 +15,26 @@
import Tailwindcss from '../components/Tailwindcss.svelte';
import LandingNavigation from '../components/Navigation/index.svelte';
import OrgNavigation from '../components/Navigation/app.svelte';
import LMSNavigation from '../components/Navigation/lms.svelte';
import OrgLandingPage from '../components/Org/LandingPage/index.svelte';
import Snackbar from '../components/Snackbar/index.svelte';
import Backdrop from '../components/Backdrop/index.svelte';
import OrgSideBar from '../components/Org/SideBar.svelte';
import LMSSideBar from '../components/LMS/SideBar.svelte';
// import SideBar from "../components/SideBar/index.svelte";
// import Footer from '../components/Footer/index.svelte';
import Apps from '../components/Apps/index.svelte';
import { handleAuthChange } from '../utils/functions/api';
import { isCoursesPage, isOrgPage, isLMSPage } from '../utils/functions/app';
import showAppsSideBar from '../utils/functions/showAppsSideBar';
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';
import { getOrganizations } from '../utils/services/org';
import { getOrganizations, getCurrentOrg } from '../utils/services/org';
import { toggleBodyByTheme } from '../utils/functions/app';
import { appStore } from '../utils/store/app';
import { currentOrg } from '../utils/store/org';
export let segment;
export let config;
@@ -46,6 +50,7 @@
});
async function getProfile() {
const params = new URLSearchParams(window.location.search);
// Get user profile
const authUser = supabase.auth.user();
console.log('Get user profile', authUser);
@@ -85,6 +90,28 @@
// Set user in sentry
Sentry.setUser($profile);
if ($appStore.isStudentDomain) {
const { data, error } = await supabase
.from('organizationmember')
.insert({
organization_id: $currentOrg.id,
profile_id: $profile.id,
role_id: 3,
});
if (error) {
console.error('Error adding user to organisation', error);
} else {
console.log('Success adding user to organisation', data);
}
if (params.has('redirect')) {
goto(params.get('redirect'));
} else {
goto('/lms');
}
return;
}
goto(ROUTE.ONBOARDING);
}
$user.fetchingUser = false;
@@ -97,12 +124,16 @@
// Set user in sentry
Sentry.setUser($profile);
const orgRes = await getOrganizations($profile.id);
// student redirect
if ($appStore.isStudentDomain) {
// student redirect
if (params.has('redirect')) {
goto(params.get('redirect'));
} else {
goto('/lms');
}
} else {
const orgRes = await getOrganizations($profile.id);
// If user coming to login page, then
if (isEmpty(orgRes.orgs)) {
goto(ROUTE.ONBOARDING);
} else if (
@@ -123,18 +154,10 @@
const answer = Array.isArray(matches) ? !!matches[1] : false;
$appStore.isStudentDomain = debug || answer;
$appStore.siteNameFromDomain = debug ? 'startupscoo' : matches?.[1] ?? '';
$appStore.siteNameFromDomain = debug ? 'codingdojo' : matches?.[1] ?? '';
}
}
function isCoursePage(path) {
return /course[s]*\/[a-z 0-9 -]/.test(path);
}
function isOrgPage(path) {
return /org\/[a-z 0-9 -]/.test(path);
}
function handleResize() {
isMobile.update(() => window.innerWidth <= 760);
}
@@ -182,6 +205,10 @@
$appStore.isDark = localStorage.getItem('theme') === 'dark';
toggleBodyByTheme($appStore.isDark);
if ($appStore.isStudentDomain) {
getCurrentOrg($appStore.siteNameFromDomain);
}
return () => {
authListener.unsubscribe();
};
@@ -212,11 +239,17 @@
<Moon size="60" color="#1d4ed8" unit="px" duration="1s" />
</Backdrop>
{/if}
{#if !ROUTES_TO_HIDE_NAV.includes($page.path) && !isCoursePage(path)}
{#if $page.path.includes('org') || $page.path.includes('profile')}
{#if !ROUTES_TO_HIDE_NAV.includes($page.path) && !isCoursesPage(path)}
{#if isOrgPage($page.path) || $page.path.includes('profile')}
<OrgNavigation />
{:else if isLMSPage($page.path)}
<LMSNavigation />
{:else}
<LandingNavigation {segment} />
<LandingNavigation
{segment}
logo={$appStore.isStudentDomain ? $currentOrg.avatar_url : undefined}
orgName={$appStore.isStudentDomain ? $currentOrg.name : undefined}
/>
{/if}
{/if}
@@ -228,6 +261,13 @@
<slot />
</div>
</div>
{:else if isLMSPage($page.path)}
<div class="org-root w-full flex items-center justify-between">
<LMSSideBar />
<div class="org-slot bg-white dark:bg-gray-800">
<slot />
</div>
</div>
{:else}
<slot />
{/if}
+1
View File
@@ -0,0 +1 @@
Exercises
+1
View File
@@ -0,0 +1 @@
<h1>Your Journey into progress begins today. Welcome !!!</h1>
+1
View File
@@ -0,0 +1 @@
My Learning
+1
View File
@@ -0,0 +1 @@
Settings Page
+16
View File
@@ -5,3 +5,19 @@ export function toggleBodyByTheme(isDark) {
document.body.className = document.body.className.replace(' dark', '');
}
}
export function isCoursesPage(path) {
return /courses\/[a-z 0-9 -]/.test(path);
}
export function isCoursePage(path) {
return /course\/[a-z 0-9 -]/.test(path);
}
export function isOrgPage(path) {
return /org\/[a-z 0-9 -]/.test(path);
}
export function isLMSPage(path) {
return /lms[\/a-z 0-9 -]*/.test(path);
}
+17 -1
View File
@@ -1,4 +1,5 @@
import get from 'lodash/get';
import { goto } from '@sapper/app';
import { supabase } from '../../functions/supabase';
import { orgs, currentOrg, orgAudience, orgTeam } from '../../store/org';
import { ROLE, ROLE_LABEL } from '../../constants/roles';
@@ -20,6 +21,7 @@ export async function getOrgTeam(orgId) {
`
)
.eq('organization_id', orgId)
.neq('role_id', ROLE.STUDENT)
.order('id', { ascending: false });
const team = [];
@@ -49,7 +51,6 @@ export async function getOrganizations(userId) {
.from('organizationmember')
.select(
`
id,
profile_id,
organization (
id,
@@ -152,3 +153,18 @@ export async function getCourseBySiteName(siteName) {
return data;
}
export async function getCurrentOrg(siteName) {
const { data, error } = await supabase
.from('organization')
.select(`*`)
.eq('siteName', siteName)
.single();
if (error || !data) {
console.error('Error getOrganization', error);
return goto('/404');
}
currentOrg.set(data);
}