Upload avatar
This commit is contained in:
@@ -1,14 +1,17 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
import { fly } from 'svelte/transition';
|
||||||
import { stores, goto } from '@sapper/app';
|
import { stores, goto } from '@sapper/app';
|
||||||
import hotkeys from 'hotkeys-js';
|
import hotkeys from 'hotkeys-js';
|
||||||
|
import Home32 from 'carbon-icons-svelte/lib/Home32';
|
||||||
import CheckmarkFilled20 from 'carbon-icons-svelte/lib/CheckmarkFilled20';
|
import CheckmarkFilled20 from 'carbon-icons-svelte/lib/CheckmarkFilled20';
|
||||||
import Settings20 from 'carbon-icons-svelte/lib/Settings20';
|
import Settings20 from 'carbon-icons-svelte/lib/Settings20';
|
||||||
import ChevronLeft32 from 'carbon-icons-svelte/lib/ChevronLeft32';
|
import ChevronLeft16 from 'carbon-icons-svelte/lib/ChevronLeft16';
|
||||||
import ChevronRight32 from 'carbon-icons-svelte/lib/ChevronRight32';
|
import ChevronRight16 from 'carbon-icons-svelte/lib/ChevronRight16';
|
||||||
import Expandable from '../../../Expandable/index.svelte';
|
import Expandable from '../../../Expandable/index.svelte';
|
||||||
import PageNav from '../../../PageNav/index.svelte';
|
import PageNav from '../../../PageNav/index.svelte';
|
||||||
import IconButton from '../../../IconButton/index.svelte';
|
import IconButton from '../../../IconButton/index.svelte';
|
||||||
|
import Avatar from '../../../Avatar/index.svelte';
|
||||||
import {
|
import {
|
||||||
getNavItemRoute,
|
getNavItemRoute,
|
||||||
getLessonsRoute,
|
getLessonsRoute,
|
||||||
@@ -20,10 +23,11 @@
|
|||||||
import { lessons } from '../Lesson/store/lessons';
|
import { lessons } from '../Lesson/store/lessons';
|
||||||
import { course } from '../../store';
|
import { course } from '../../store';
|
||||||
import { updateCourse } from '../../../../utils/services/courses';
|
import { updateCourse } from '../../../../utils/services/courses';
|
||||||
|
import { profile } from '../../../../utils/store/user';
|
||||||
|
|
||||||
// export let lessonId;
|
// export let lessonId;
|
||||||
export let path;
|
export let path;
|
||||||
let show = null;
|
let show = true;
|
||||||
|
|
||||||
const { page } = stores();
|
const { page } = stores();
|
||||||
|
|
||||||
@@ -45,7 +49,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
show = localStorage.getItem('hideCourseNav') === 'false';
|
if (localStorage.getItem('hideCourseNav')) {
|
||||||
|
show = localStorage.getItem('hideCourseNav') === 'false';
|
||||||
|
} else {
|
||||||
|
show = true;
|
||||||
|
}
|
||||||
|
|
||||||
hotkeys('b', function (event, handler) {
|
hotkeys('b', function (event, handler) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -101,23 +109,22 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Settings />
|
<Settings />
|
||||||
|
|
||||||
<div class="root z-10 {!show && 'hide'}">
|
<div class="root z-10 {!show && 'hide'}">
|
||||||
{#if show}
|
{#if show}
|
||||||
<PageNav
|
<div class="relative h-full">
|
||||||
bind:title={$course.title}
|
<PageNav bind:title={$course.title} paddingClass="pl-2">
|
||||||
onEditComplete={handleSaveTitle}
|
<slot:fragment slot="image">
|
||||||
isTitleEditable={true}
|
<Avatar src={$course.logo} />
|
||||||
paddingClass="pl-2"
|
</slot:fragment>
|
||||||
>
|
<slot:fragment slot="widget">
|
||||||
<slot:fragment slot="widget">
|
<IconButton
|
||||||
<IconButton
|
onClick={() => ($settingsDialog.open = !$settingsDialog.open)}
|
||||||
onClick={() => ($settingsDialog.open = !$settingsDialog.open)}
|
>
|
||||||
>
|
<Settings20 class="carbon-icon" />
|
||||||
<Settings20 class="carbon-icon" />
|
</IconButton>
|
||||||
</IconButton>
|
</slot:fragment>
|
||||||
</slot:fragment>
|
</PageNav>
|
||||||
</PageNav>
|
|
||||||
<div>
|
|
||||||
{#each navItems as navItem}
|
{#each navItems as navItem}
|
||||||
<Expandable
|
<Expandable
|
||||||
label={navItem.label}
|
label={navItem.label}
|
||||||
@@ -134,7 +141,9 @@
|
|||||||
'cursor-not-allowed'}"
|
'cursor-not-allowed'}"
|
||||||
href={getLessonsRoute($course.id, item.id)}
|
href={getLessonsRoute($course.id, item.id)}
|
||||||
>
|
>
|
||||||
<span class="course-counter"> {getLectureNo(index + 1)} </span>
|
<span class="course-counter">
|
||||||
|
{getLectureNo(index + 1)}
|
||||||
|
</span>
|
||||||
<span>{item.title}</span>
|
<span>{item.title}</span>
|
||||||
{#if item.is_complete}
|
{#if item.is_complete}
|
||||||
<span class="ml-2 success">
|
<span class="ml-2 success">
|
||||||
@@ -148,12 +157,28 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</Expandable>
|
</Expandable>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="w-full footer p-3 absolute bottom-2 flex items-center justify-between"
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
value="toggle"
|
||||||
|
onClick={() => goto('/courses')}
|
||||||
|
buttonClassName="mx-3"
|
||||||
|
>
|
||||||
|
<Home32 class="carbon-icon" />
|
||||||
|
</IconButton>
|
||||||
|
<a class="block mx-3" href={`/profile/${$profile.id}`}>
|
||||||
|
<Avatar src={$profile.avatar_url} name={$profile.username} />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="toggler absolute bottom-0 -right-0.5">
|
<div class="toggler rounded-full shadow-lg absolute bottom-0">
|
||||||
<IconButton
|
<IconButton
|
||||||
value="toggle"
|
value="toggle"
|
||||||
onClick={() => (show = !show)}
|
onClick={() => (show = !show)}
|
||||||
|
size="small"
|
||||||
toolTipProps={{
|
toolTipProps={{
|
||||||
title: 'Toggle sidebar',
|
title: 'Toggle sidebar',
|
||||||
hotkeys: ['B'],
|
hotkeys: ['B'],
|
||||||
@@ -161,9 +186,9 @@
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{#if show}
|
{#if show}
|
||||||
<ChevronLeft32 class="carbon-icon" />
|
<ChevronLeft16 class="carbon-icon" />
|
||||||
{:else}
|
{:else}
|
||||||
<ChevronRight32 class="carbon-icon" />
|
<ChevronRight16 class="carbon-icon" />
|
||||||
{/if}
|
{/if}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -171,7 +196,7 @@
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.root {
|
.root {
|
||||||
height: 93vh;
|
height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-width: 360px;
|
min-width: 360px;
|
||||||
@@ -182,11 +207,16 @@
|
|||||||
background-color: rgb(250, 251, 252);
|
background-color: rgb(250, 251, 252);
|
||||||
|
|
||||||
&.hide {
|
&.hide {
|
||||||
min-width: 0px;
|
min-width: 15px;
|
||||||
max-width: 0px;
|
max-width: 15px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
.active {
|
.active {
|
||||||
background-color: #cae2f9;
|
background-color: #cae2f9;
|
||||||
border-left: 3px solid var(--main-primary-color);
|
border-left: 3px solid var(--main-primary-color);
|
||||||
@@ -209,10 +239,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.toggler {
|
.toggler {
|
||||||
right: -58px;
|
right: -15px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
border-top-right-radius: 20px;
|
top: 50px;
|
||||||
border-bottom-right-radius: 20px;
|
height: fit-content;
|
||||||
|
background: var(--border-color);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -65,6 +65,7 @@
|
|||||||
onClose={resetForm}
|
onClose={resetForm}
|
||||||
bind:open={$invitationModal.open}
|
bind:open={$invitationModal.open}
|
||||||
width="w-2/5"
|
width="w-2/5"
|
||||||
|
maxWidth="max-w-lg"
|
||||||
modalHeading="Invite people"
|
modalHeading="Invite people"
|
||||||
>
|
>
|
||||||
<form on:submit|preventDefault={addPerson}>
|
<form on:submit|preventDefault={addPerson}>
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import Modal from '../../../Modal/index.svelte';
|
import Modal from '../../../Modal/index.svelte';
|
||||||
|
import TextField from '../../../Form/TextField.svelte';
|
||||||
import TextArea from '../../../Form/TextArea.svelte';
|
import TextArea from '../../../Form/TextArea.svelte';
|
||||||
import PrimaryButton from '../../../PrimaryButton/index.svelte';
|
import PrimaryButton from '../../../PrimaryButton/index.svelte';
|
||||||
|
import UploadImage from '../../../UploadImage/index.svelte';
|
||||||
|
|
||||||
import { VARIANTS } from '../../../PrimaryButton/constants';
|
import { VARIANTS } from '../../../PrimaryButton/constants';
|
||||||
import { settingsDialog } from './store';
|
import { settingsDialog } from './store';
|
||||||
@@ -12,22 +14,43 @@
|
|||||||
} from '../../../../utils/services/courses';
|
} from '../../../../utils/services/courses';
|
||||||
|
|
||||||
let shouldDelete = false;
|
let shouldDelete = false;
|
||||||
|
let avatar;
|
||||||
|
let errors = {};
|
||||||
|
|
||||||
async function updateDescription() {
|
async function updateDescription() {
|
||||||
console.log('update');
|
if (!$course.title) {
|
||||||
|
errors.title = 'Title cannot be empty';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!$course.description) {
|
||||||
|
errors.description = 'Description cannot be empty';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$settingsDialog.open = false;
|
$settingsDialog.open = false;
|
||||||
await updateCourse($course.id, {
|
const logo = await updateCourse($course.id, {
|
||||||
|
avatar,
|
||||||
|
title: $course.title,
|
||||||
description: $course.description,
|
description: $course.description,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (logo) {
|
||||||
|
$course.logo = logo;
|
||||||
|
}
|
||||||
|
|
||||||
|
avatar = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDelete() {
|
async function handleDelete() {
|
||||||
console.log('delete');
|
|
||||||
|
|
||||||
$settingsDialog.open = false;
|
$settingsDialog.open = false;
|
||||||
await deleteCourse($course.id);
|
await deleteCourse($course.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setAvatar(logo) {
|
||||||
|
avatar = logo;
|
||||||
|
}
|
||||||
|
|
||||||
|
$: setAvatar($course.logo);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
@@ -57,6 +80,14 @@
|
|||||||
</form>
|
</form>
|
||||||
{:else}
|
{:else}
|
||||||
<form on:submit|preventDefault={updateDescription}>
|
<form on:submit|preventDefault={updateDescription}>
|
||||||
|
<UploadImage bind:avatar />
|
||||||
|
<TextField
|
||||||
|
bind:value={$course.title}
|
||||||
|
placeholder="Course title"
|
||||||
|
className="mb-4"
|
||||||
|
isRequired={true}
|
||||||
|
errorMessage={errors.title}
|
||||||
|
/>
|
||||||
<TextArea
|
<TextArea
|
||||||
label="Short description"
|
label="Short description"
|
||||||
bind:value={$course.description}
|
bind:value={$course.description}
|
||||||
@@ -65,6 +96,7 @@
|
|||||||
placeholder="A little description about this course"
|
placeholder="A little description about this course"
|
||||||
className="mb-4"
|
className="mb-4"
|
||||||
isRequired={true}
|
isRequired={true}
|
||||||
|
errorMessage={errors.description}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="mt-5 flex items-center justify-between">
|
<div class="mt-5 flex items-center justify-between">
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</PageNav>
|
</PageNav>
|
||||||
|
|
||||||
<PageBody>
|
<PageBody width="max-w-3xl px-3">
|
||||||
{#if mode === MODES.edit}
|
{#if mode === MODES.edit}
|
||||||
<EditContent
|
<EditContent
|
||||||
writeLabel="Note"
|
writeLabel="Note"
|
||||||
@@ -50,6 +50,8 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
{@html marked($course.overview || '', { breaks: true, gfm: true })}
|
<article class="preview prose prose-sm sm:prose p-2">
|
||||||
|
{@html marked($course.overview || '')}
|
||||||
|
</article>
|
||||||
{/if}
|
{/if}
|
||||||
</PageBody>
|
</PageBody>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
export let buttonClassName = '';
|
export let buttonClassName = '';
|
||||||
export let contained = false;
|
export let contained = false;
|
||||||
export let value = '';
|
export let value = '';
|
||||||
|
export let size = 'large';
|
||||||
export let toolTipProps = {
|
export let toolTipProps = {
|
||||||
title: '',
|
title: '',
|
||||||
hotkeys: [],
|
hotkeys: [],
|
||||||
@@ -23,7 +24,7 @@
|
|||||||
direction={toolTipProps.direction}
|
direction={toolTipProps.direction}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="root {selected && 'active'} {contained &&
|
class="root {selected && 'active'} {size} {contained &&
|
||||||
'contained'} {buttonClassName}"
|
'contained'} {buttonClassName}"
|
||||||
{disabled}
|
{disabled}
|
||||||
on:click={handleClick}
|
on:click={handleClick}
|
||||||
@@ -58,6 +59,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.root.small {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.contained {
|
.contained {
|
||||||
background-color: rgba(0, 0, 0, 0.04);
|
background-color: rgba(0, 0, 0, 0.04);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
export let onClose = () => {};
|
export let onClose = () => {};
|
||||||
export let modalHeading = '';
|
export let modalHeading = '';
|
||||||
export let width = '';
|
export let width = '';
|
||||||
|
export let maxWidth = '';
|
||||||
export let containerClass = '';
|
export let containerClass = '';
|
||||||
|
|
||||||
afterUpdate(() => {
|
afterUpdate(() => {
|
||||||
@@ -23,7 +24,8 @@
|
|||||||
>
|
>
|
||||||
<div aria-hidden="true" class="backdrop" />
|
<div aria-hidden="true" class="backdrop" />
|
||||||
<div
|
<div
|
||||||
class="container bg-white mx-auto {width} shadow-lg p-5 pt-2 rounded-md"
|
class="{maxWidth ||
|
||||||
|
'container'} bg-white mx-auto {width} shadow-lg p-5 pt-2 rounded-md"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h3 class="text-xl m-0">
|
<h3 class="text-xl m-0">
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
.root {
|
.root {
|
||||||
overflow-y: scroll;
|
overflow-y: auto;
|
||||||
height: 85vh;
|
height: 85vh;
|
||||||
}
|
}
|
||||||
.head {
|
.head {
|
||||||
|
|||||||
@@ -46,7 +46,10 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<h4 class="title">{title}</h4>
|
<div class="flex items-center" {title}>
|
||||||
|
<slot name="image" />
|
||||||
|
<h4 class="title">{title}</h4>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{#if Array.isArray(navItems) && navItems.length}
|
{#if Array.isArray(navItems) && navItems.length}
|
||||||
@@ -78,7 +81,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 20px;
|
font-size: 18px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 250px;
|
||||||
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
@@ -121,11 +129,4 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 1002px) {
|
|
||||||
.header {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<script>
|
||||||
|
export let avatar;
|
||||||
|
export let src;
|
||||||
|
export let widthHeight;
|
||||||
|
|
||||||
|
let fileinput;
|
||||||
|
|
||||||
|
const onFileSelected = (e) => {
|
||||||
|
const image = e.target.files[0];
|
||||||
|
let reader = new FileReader();
|
||||||
|
reader.readAsDataURL(image);
|
||||||
|
reader.onload = (e) => {
|
||||||
|
avatar = image;
|
||||||
|
src = e.target.result;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section class="flex justify-center">
|
||||||
|
<div
|
||||||
|
class="avatar-container {widthHeight ||
|
||||||
|
'setwidthheight'} pointer border-2 border-gray-200 relative hover:bg-gray-400 hover:bg-opacity-75 cursor-pointer rounded-full"
|
||||||
|
on:click={() => {
|
||||||
|
fileinput.click();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{#if src}
|
||||||
|
<img class="w-full h-full rounded-full" {src} alt="d" />
|
||||||
|
{:else if avatar}
|
||||||
|
<img class="w-full h-full rounded-full" src={avatar} alt="d" />
|
||||||
|
{:else}
|
||||||
|
<img
|
||||||
|
class="w-full h-full rounded-full"
|
||||||
|
src="https://cdn4.iconfinder.com/data/icons/small-n-flat/24/user-alt-512.png"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
<div
|
||||||
|
class="upload-icon w-full h-full flex items-center justify-center top-0"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="text-white"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
role="presentation"
|
||||||
|
><g fill="currentColor" fill-rule="evenodd"
|
||||||
|
><path
|
||||||
|
d="M2 6.994C2 5.893 2.898 5 3.99 5h16.02C21.108 5 22 5.895 22 6.994v12.012A1.997 1.997 0 0120.01 21H3.99A1.994 1.994 0 012 19.006V6.994zM12 17a4 4 0 100-8 4 4 0 000 8zm5-8c0 .556.448 1 1 1 .556 0 1-.448 1-1 0-.556-.448-1-1-1-.556 0-1 .448-1 1zM8 4c0-.552.453-1 .997-1h6.006c.55 0 .997.444.997 1v1H8V4z"
|
||||||
|
/><circle cx="12" cy="13" r="2" /></g
|
||||||
|
></svg
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input
|
||||||
|
style="display:none"
|
||||||
|
type="file"
|
||||||
|
accept=".jpg, .jpeg, .png"
|
||||||
|
on:change={(e) => onFileSelected(e)}
|
||||||
|
bind:this={fileinput}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.avatar-container.setwidthheight {
|
||||||
|
height: 128px;
|
||||||
|
width: 128px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-container:hover .upload-icon {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
// isProd: process.env.NODE_ENV === 'development',
|
// isProd: process.env.NODE_ENV === 'development',
|
||||||
supabaseConfig: {
|
supabaseConfig: {
|
||||||
url: process.env.SVELTE_APP_SUPABASE_URL,
|
bucketPath:
|
||||||
anonKey: process.env.SVELTE_APP_SUPABASE_ANON_KEY,
|
'https://koxqonvbkeakwvmdegcf.supabase.in/storage/v1/object/sign/',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
$user.currentSession = authUser;
|
$user.currentSession = authUser;
|
||||||
|
|
||||||
profile.set(data[0]);
|
profile.set(data[0]);
|
||||||
await setProfileIdOfGroupMember(authUser.email, data[0].id);
|
// await setProfileIdOfGroupMember(authUser.email, data[0].id);
|
||||||
|
|
||||||
// If user coming to login page, then
|
// If user coming to login page, then
|
||||||
if (path.includes('login') || path.includes('signup')) {
|
if (path.includes('login') || path.includes('signup')) {
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
console.log(`profileData`, profileData);
|
console.log(`profileData`, profileData);
|
||||||
profile.set(profileData);
|
profile.set(profileData);
|
||||||
|
|
||||||
setProfileIdOfGroupMember(authUser.email, profileData.id);
|
// setProfileIdOfGroupMember(authUser.email, profileData.id);
|
||||||
|
|
||||||
// If user coming to login page, then
|
// If user coming to login page, then
|
||||||
if (path.includes('login') || path.includes('signup')) {
|
if (path.includes('login') || path.includes('signup')) {
|
||||||
@@ -86,6 +86,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isCoursePage(path) {
|
||||||
|
return /courses\/[a-z 0-9 -]/.test(path);
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const _isMvpUser = localStorage.getItem('mvp');
|
const _isMvpUser = localStorage.getItem('mvp');
|
||||||
|
|
||||||
@@ -130,7 +134,7 @@
|
|||||||
<!-- <Nav {segment} /> -->
|
<!-- <Nav {segment} /> -->
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
{#if !['login', 'signup'].includes(path)}
|
{#if !['login', 'signup'].includes(path) && !isCoursePage(path)}
|
||||||
<Navigation {segment} disableLogin={!allowUser} />
|
<Navigation {segment} disableLogin={!allowUser} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -12,13 +12,15 @@
|
|||||||
|
|
||||||
export let courseId;
|
export let courseId;
|
||||||
|
|
||||||
|
let prevCourseId;
|
||||||
|
|
||||||
async function onCourseIdChange(courseId) {
|
async function onCourseIdChange(courseId) {
|
||||||
if (!courseId) return;
|
if (!courseId || prevCourseId === courseId) return;
|
||||||
console.log(`overview courseId`, courseId);
|
|
||||||
|
|
||||||
const { data } = await fetchCourse(courseId);
|
const { data } = await fetchCourse(courseId);
|
||||||
|
|
||||||
setCourseData(data);
|
setCourseData(data);
|
||||||
|
prevCourseId = courseId;
|
||||||
}
|
}
|
||||||
|
|
||||||
$: onCourseIdChange(courseId);
|
$: onCourseIdChange(courseId);
|
||||||
|
|||||||
@@ -248,5 +248,6 @@
|
|||||||
<style>
|
<style>
|
||||||
.group {
|
.group {
|
||||||
min-height: 170px;
|
min-height: 170px;
|
||||||
|
background-color: #fafafa;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -14,17 +14,17 @@
|
|||||||
import Book16 from 'carbon-icons-svelte/lib/Book16';
|
import Book16 from 'carbon-icons-svelte/lib/Book16';
|
||||||
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 UploadImage from '../../components/UploadImage/index.svelte';
|
||||||
import { supabase } from '../../utils/functions/supabase';
|
import { supabase } from '../../utils/functions/supabase';
|
||||||
import { user, profile } from '../../utils/store/user';
|
import { user, profile } from '../../utils/store/user';
|
||||||
|
|
||||||
export let profileId;
|
export let profileId;
|
||||||
|
|
||||||
|
let avatar;
|
||||||
let loading = false;
|
let loading = false;
|
||||||
let currentProfile = {};
|
let currentProfile = {};
|
||||||
let isOwner = false;
|
let isOwner = false;
|
||||||
let docs = [];
|
let initialValueOfUserName, initialValueOfFullName, initialValueOfAvatar;
|
||||||
let initialValueOfUserName;
|
|
||||||
let initialValueOfFullName;
|
|
||||||
|
|
||||||
async function getProfile(profileId) {
|
async function getProfile(profileId) {
|
||||||
loading = true;
|
loading = true;
|
||||||
@@ -56,9 +56,28 @@
|
|||||||
const updates = {
|
const updates = {
|
||||||
fullname: currentProfile.fullname,
|
fullname: currentProfile.fullname,
|
||||||
username: currentProfile.username,
|
username: currentProfile.username,
|
||||||
avatar_url: currentProfile.avatar_url,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (avatar) {
|
||||||
|
const filename = `user/${currentProfile.username + Date.now()}.webp`;
|
||||||
|
|
||||||
|
const { data } = await supabase.storage
|
||||||
|
.from('avatars')
|
||||||
|
.upload(filename, avatar, {
|
||||||
|
cacheControl: '3600',
|
||||||
|
upsert: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data && data.Key) {
|
||||||
|
const { publicURL } = supabase.storage
|
||||||
|
.from('avatars')
|
||||||
|
.getPublicUrl(filename);
|
||||||
|
|
||||||
|
updates.avatar_url = publicURL;
|
||||||
|
}
|
||||||
|
avatar = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
let { error } = await supabase
|
let { error } = await supabase
|
||||||
.from('profile')
|
.from('profile')
|
||||||
.update(updates, {
|
.update(updates, {
|
||||||
@@ -66,6 +85,13 @@
|
|||||||
})
|
})
|
||||||
.match({ id: profileId });
|
.match({ id: profileId });
|
||||||
|
|
||||||
|
if (isOwner) {
|
||||||
|
profile.update((_profile) => ({
|
||||||
|
..._profile,
|
||||||
|
...updates,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert(error.message);
|
alert(error.message);
|
||||||
@@ -78,12 +104,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {});
|
|
||||||
|
|
||||||
$: isOwner = $profile.id === profileId;
|
$: isOwner = $profile.id === profileId;
|
||||||
$: {
|
$: getProfile(profileId);
|
||||||
getProfile(profileId);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -93,12 +115,17 @@
|
|||||||
<section class="root w-11/12 mt-3 m-auto flex items-start justify-between">
|
<section class="root w-11/12 mt-3 m-auto flex items-start justify-between">
|
||||||
{#if currentProfile.id}
|
{#if currentProfile.id}
|
||||||
<div class="w-1/4 flex items-center flex-col">
|
<div class="w-1/4 flex items-center flex-col">
|
||||||
<img
|
<!-- <img
|
||||||
alt={currentProfile.username}
|
alt={currentProfile.username}
|
||||||
src={currentProfile.avatar_url}
|
src={currentProfile.avatar_url}
|
||||||
height="260"
|
height="260"
|
||||||
width="260"
|
width="260"
|
||||||
class="rounded-full bg-light-blue-100 mb-4"
|
class="rounded-full bg-light-blue-100 mb-4"
|
||||||
|
/> -->
|
||||||
|
<UploadImage
|
||||||
|
bind:avatar
|
||||||
|
src={currentProfile.avatar_url}
|
||||||
|
widthHeight="w-60 h-60"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{#if isOwner}
|
{#if isOwner}
|
||||||
@@ -115,7 +142,7 @@
|
|||||||
inputClassName="rounded-md"
|
inputClassName="rounded-md"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{#if initialValueOfUserName !== currentProfile.username || initialValueOfFullName !== currentProfile.fullname}
|
{#if avatar || initialValueOfUserName !== currentProfile.username || initialValueOfFullName !== currentProfile.fullname}
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
label={loading ? 'Updating...' : 'Update profile'}
|
label={loading ? 'Updating...' : 'Update profile'}
|
||||||
onClick={updateProfile}
|
onClick={updateProfile}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ const config = {
|
|||||||
isDev: process.env.NODE_ENV === 'development',
|
isDev: process.env.NODE_ENV === 'development',
|
||||||
telegramToken: process.env.SVELTE_APP_TELEGRAM_API_TOKEN,
|
telegramToken: process.env.SVELTE_APP_TELEGRAM_API_TOKEN,
|
||||||
supabaseConfig: {
|
supabaseConfig: {
|
||||||
|
bucketPath:
|
||||||
|
'https://koxqonvbkeakwvmdegcf.supabase.in/storage/v1/object/sign',
|
||||||
url: process.env.SVELTE_APP_SUPABASE_URL,
|
url: process.env.SVELTE_APP_SUPABASE_URL,
|
||||||
anonKey: process.env.SVELTE_APP_SUPABASE_ANON_KEY,
|
anonKey: process.env.SVELTE_APP_SUPABASE_ANON_KEY,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,16 +7,16 @@ export async function fetchCourse(courseId, session) {
|
|||||||
let jsonUser;
|
let jsonUser;
|
||||||
|
|
||||||
if (!process.browser) {
|
if (!process.browser) {
|
||||||
try {
|
// try {
|
||||||
jsonUser = JSON.parse(session.user);
|
// jsonUser = JSON.parse(session.user);
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.log('Token is expired probably cause user not found');
|
// console.log('Token is expired probably cause user not found');
|
||||||
return {
|
// return {
|
||||||
data: [],
|
// data: [],
|
||||||
cantFetch: true,
|
// cantFetch: true,
|
||||||
};
|
// };
|
||||||
// return this.redirect(301, '/login');
|
// // return this.redirect(301, '/login');
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
@@ -27,6 +27,7 @@ export async function fetchCourse(courseId, session) {
|
|||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
overview,
|
overview,
|
||||||
|
logo,
|
||||||
group(*,
|
group(*,
|
||||||
members:groupmember(*,
|
members:groupmember(*,
|
||||||
profile(*)
|
profile(*)
|
||||||
@@ -40,7 +41,7 @@ export async function fetchCourse(courseId, session) {
|
|||||||
)
|
)
|
||||||
.match({ id: courseId })
|
.match({ id: courseId })
|
||||||
.single();
|
.single();
|
||||||
|
console.log(`error`, error);
|
||||||
if (!data || error) {
|
if (!data || error) {
|
||||||
console.log(`data`, data);
|
console.log(`data`, data);
|
||||||
console.log(`fetchCourse => error`, error);
|
console.log(`fetchCourse => error`, error);
|
||||||
@@ -82,10 +83,33 @@ export async function setProfileIdOfGroupMember(email, profileId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function updateCourse(courseId, course) {
|
export async function updateCourse(courseId, course) {
|
||||||
return await supabase
|
if (course.avatar) {
|
||||||
|
const filename = `course/${courseId + Date.now()}.webp`;
|
||||||
|
|
||||||
|
const { data } = await supabase.storage
|
||||||
|
.from('avatars')
|
||||||
|
.upload(filename, course.avatar, {
|
||||||
|
cacheControl: '3600',
|
||||||
|
upsert: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data && data.Key) {
|
||||||
|
const { publicURL } = supabase.storage
|
||||||
|
.from('avatars')
|
||||||
|
.getPublicUrl(filename);
|
||||||
|
|
||||||
|
course.logo = publicURL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete course.avatar;
|
||||||
|
|
||||||
|
await supabase
|
||||||
.from('course')
|
.from('course')
|
||||||
.update(course, { returning: 'minimal' })
|
.update(course, { returning: 'minimal' })
|
||||||
.match({ id: courseId });
|
.match({ id: courseId });
|
||||||
|
|
||||||
|
return course.logo;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteCourse(courseId, course) {
|
export async function deleteCourse(courseId, course) {
|
||||||
|
|||||||
Reference in New Issue
Block a user