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:
committed by
Space Cloud
co-authored by
Rotimi Best
parent
8d0f1e4de1
commit
68e1dbb1b5
@@ -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 |
@@ -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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
@@ -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
@@ -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}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Exercises
|
||||
@@ -0,0 +1 @@
|
||||
<h1>Your Journey into progress begins today. Welcome !!!</h1>
|
||||
@@ -0,0 +1 @@
|
||||
My Learning
|
||||
@@ -0,0 +1 @@
|
||||
Settings Page
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user