CLASSROOMIO-T-177: Upload Widget
improve banner image Merge branch 'main' of https://gitlab.com/thenextgreat/classroomio into CLASSROOMIO-T-177 Merge branch 'CLASSROOMIO-T-177' of https://gitlab.com/thenextgreat/classroomio into CLASSROOMIO-T-177 CLASSROOMIO-T-177: Moved API call to backend general ux improvements Merge branch 'CLASSROOMIO-T-177' of https://gitlab.com/thenextgreat/classroomio into CLASSROOMIO-T-177 CLASSROOMIO-T-177: Resolved Feedbacks CLASSROOMIO-T-177: UI improvements CLASSROOMIO-T-177: fix styling or image CLASSROOMIO-T-177: Resolved Feedbacks CLASSROOMIO-T-177: Landing Page Max Width CLASSROOMIO-T-177: Refactor Upload Widget Merge branch 'main' into CLASSROOMIO-T-177 CLASSROOMIO-T-177: Completion of Upload Widget Merge branch 'main' into CLASSROOMIO-T-177 CLASSROOMIO-T-177: Widget Test Co-authored-by: Rotimi Best <[email protected]> Co-authored-by: Tunji Adeyemi <[email protected]> Merge-request: CLASSROOMIO-MR-86 Merged-by: Best Admin <[email protected]>
This commit is contained in:
committed by
Space Cloud
co-authored by
Rotimi Best
parent
589a61fe44
commit
804e5cdeb9
+1
-1
@@ -28,7 +28,6 @@
|
||||
"@types/paystack": "^2.0.1",
|
||||
"@types/pluralize": "0.0.30",
|
||||
"ai": "^2.1.31",
|
||||
"axios": "^1.4.0",
|
||||
"body-parser": "^1.20.2",
|
||||
"canvas-confetti": "^1.6.0",
|
||||
"cookie-parser": "^1.4.6",
|
||||
@@ -43,6 +42,7 @@
|
||||
"pluralize": "^8.0.0",
|
||||
"sirv": "^2.0.3",
|
||||
"svelte-email": "^0.0.4",
|
||||
"unsplash-js": "^7.0.18",
|
||||
"wait-on": "^7.0.1",
|
||||
"zod": "^3.21.4"
|
||||
},
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
type
|
||||
? 'border-primary-400'
|
||||
: `border-gray-200 dark:border-gray-400 ${
|
||||
!option.isDisabled && 'hover:scale-90'
|
||||
!option.isDisabled && 'hover:scale-95'
|
||||
}`} flex flex-col {option.isDisabled &&
|
||||
'cursor-not-allowed opacity-60'} transition-all ease-in-out"
|
||||
type="button"
|
||||
@@ -145,7 +145,7 @@
|
||||
<div class="flex flex-wrap items-start gap-2">
|
||||
{#each TEMPLATES[selectedTag] as template}
|
||||
<button
|
||||
class="w-[161px] h-[140px] p-5 rounded-md dark:bg-neutral-700 border {template.id ===
|
||||
class="w-[161px] h-[140px] hover:scale-95 p-5 rounded-md dark:bg-neutral-700 border-2 {template.id ===
|
||||
selectedTemplateId
|
||||
? 'border-primary-400'
|
||||
: `border-gray-200 dark:border-gray-400`} flex flex-col transition-all ease-in-out"
|
||||
|
||||
@@ -211,16 +211,16 @@
|
||||
hideSortIcon: true
|
||||
},
|
||||
{
|
||||
label: 'Landing Page',
|
||||
to: getNavItemRoute($course.id, 'landingpage'),
|
||||
label: 'Settings',
|
||||
to: getNavItemRoute($course.id, 'settings'),
|
||||
hideSortIcon: true,
|
||||
show() {
|
||||
return !isStudent;
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
to: getNavItemRoute($course.id, 'settings'),
|
||||
label: 'Landing Page',
|
||||
to: getNavItemRoute($course.id, 'landingpage'),
|
||||
hideSortIcon: true,
|
||||
show() {
|
||||
return !isStudent;
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
import { lessons } from '../Lesson/store/lessons';
|
||||
import { currentOrg } from '$lib/utils/store/org';
|
||||
import { snackbar } from '$lib/components/Snackbar/store';
|
||||
import UploadWidget from '$lib/components/UploadWidget/index.svelte';
|
||||
import { handleOpenWidget } from '$lib/components/CourseLandingPage/store';
|
||||
|
||||
let uploadingImage = false;
|
||||
let isSaving = false;
|
||||
@@ -29,37 +31,10 @@
|
||||
let avatar;
|
||||
let imagebuffer;
|
||||
|
||||
// FILE UPLOAD
|
||||
const uploadImage = async (image: File) => {
|
||||
if (image) {
|
||||
const filename = `bannerimage/${Date.now()}` + image.name;
|
||||
const { data } = await supabase.storage.from('avatars').upload(filename, image, {
|
||||
cacheControl: '3600',
|
||||
upsert: false
|
||||
});
|
||||
|
||||
if (data) {
|
||||
const { data: response } = await supabase.storage.from('avatars').getPublicUrl(filename);
|
||||
|
||||
$settings.image = response.publicUrl;
|
||||
|
||||
uploadingImage = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onFileSelected = () => {
|
||||
uploadingImage = true;
|
||||
const image = fileInput.files[0];
|
||||
if (image) {
|
||||
let reader = new FileReader();
|
||||
reader.readAsDataURL(image);
|
||||
reader.onload = (e) => {
|
||||
imagebuffer = image;
|
||||
uploadImage(image);
|
||||
};
|
||||
}
|
||||
};
|
||||
// controls widget open and close
|
||||
function widgetControl() {
|
||||
$handleOpenWidget.open = true;
|
||||
}
|
||||
|
||||
function getLessonOrder(id) {
|
||||
const index = $lessons.findIndex((lesson) => lesson.id === id);
|
||||
@@ -205,17 +180,17 @@
|
||||
<Grid class="border-c rounded border-gray-200">
|
||||
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
|
||||
<Column sm={8} md={8} lg={8}>
|
||||
<SectionTitle>Banner Image</SectionTitle>
|
||||
<SectionTitle>Cover Image</SectionTitle>
|
||||
<p>
|
||||
This optional image will show up on your welcome page. If you include one, it should be at
|
||||
least 280 x 200"
|
||||
</p>
|
||||
<span class="flex items-center justify-start">
|
||||
<PrimaryButton
|
||||
variant={VARIANTS.LINK}
|
||||
label="Replace Banner image"
|
||||
className="-ml-6"
|
||||
onClick={() => fileInput.click()}
|
||||
variant={VARIANTS.OUTLINED}
|
||||
label="Replace"
|
||||
className="mr-2"
|
||||
onClick={widgetControl}
|
||||
/>
|
||||
<PrimaryButton
|
||||
variant={VARIANTS.CONTAINED_DANGER}
|
||||
@@ -223,22 +198,18 @@
|
||||
onClick={deleteBannerImage}
|
||||
/>
|
||||
</span>
|
||||
{#if $handleOpenWidget.open}
|
||||
<UploadWidget />
|
||||
{/if}
|
||||
</Column>
|
||||
|
||||
<Column sm={8} md={8} lg={6}>
|
||||
<div class="w-fit relative">
|
||||
<input
|
||||
type="file"
|
||||
accept=".jpg, .jpeg, .png"
|
||||
style="display: none;"
|
||||
bind:this={fileInput}
|
||||
on:change={onFileSelected}
|
||||
/>
|
||||
<div class="w-fit relative z-[20]">
|
||||
<img
|
||||
style="min-width:280px; min-height:200px"
|
||||
alt="About us"
|
||||
src={$settings.image ? $settings.image : '/images/classroomio-course-img-template.jpg'}
|
||||
class="mt-2 md:mt-0 rounded-md w-full relative"
|
||||
class="mt-2 md:mt-0 w-[280px] h-[200px] rounded-md relative"
|
||||
/>
|
||||
{#if uploadingImage}
|
||||
<Loading withOverlay={true} small class="absolute top-0 w-[280px]" />
|
||||
|
||||
@@ -1,55 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { settings } from '$lib/components/Course/components/Settings/store';
|
||||
import TextArea from '$lib/components/Form/TextArea.svelte';
|
||||
import TextField from '$lib/components/Form/TextField.svelte';
|
||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { supabase } from '$lib/utils/functions/supabase';
|
||||
import { updateCourse } from '$lib/utils/services/courses';
|
||||
import { landingPage } from '../../store';
|
||||
import { handleOpenWidget } from '../../store';
|
||||
|
||||
export let course = {};
|
||||
let fileInput;
|
||||
let imageBuffer;
|
||||
let avatar;
|
||||
|
||||
const uploadImage = async (image: File) => {
|
||||
if (image) {
|
||||
const filename = `landingpage/${Date.now()}` + image.name;
|
||||
const { data } = await supabase.storage.from('avatars').upload(filename, image, {
|
||||
cacheControl: '3600',
|
||||
upsert: false
|
||||
});
|
||||
|
||||
if (data) {
|
||||
const { data: response } = await supabase.storage.from('avatars').getPublicUrl(filename);
|
||||
|
||||
$landingPage.imageUrl = response.publicUrl;
|
||||
$settings.image = $landingPage.imageUrl;
|
||||
|
||||
await updateCourse(course.id, avatar, {
|
||||
logo: $settings.image
|
||||
});
|
||||
|
||||
course.logo = $settings.image;
|
||||
|
||||
$landingPage.uploadingImage = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onFileSelected = () => {
|
||||
$landingPage.uploadingImage = true;
|
||||
const image = fileInput.files[0];
|
||||
if (image) {
|
||||
let reader = new FileReader();
|
||||
reader.readAsDataURL(image);
|
||||
reader.onload = (e) => {
|
||||
imageBuffer = image;
|
||||
uploadImage(image);
|
||||
};
|
||||
}
|
||||
};
|
||||
function widgetControl() {
|
||||
$handleOpenWidget.open = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<TextField className="mt-5" labelClassName="font-bold" label="Title" bind:value={course.title} />
|
||||
@@ -71,18 +31,7 @@
|
||||
type="text"
|
||||
bind:value={course.metadata.videoUrl}
|
||||
/>
|
||||
<div>
|
||||
<PrimaryButton
|
||||
variant={VARIANTS.LINK}
|
||||
label="Replace Banner image"
|
||||
className="-ml-6"
|
||||
onClick={() => fileInput.click()}
|
||||
/>
|
||||
<input
|
||||
type="file"
|
||||
accept=".jpg, .jpeg, .png"
|
||||
style="display: none;"
|
||||
bind:this={fileInput}
|
||||
on:change={onFileSelected}
|
||||
/>
|
||||
<div class="mt-7">
|
||||
<p class="font-bold mb-3">Replace cover image</p>
|
||||
<PrimaryButton label="Replace" variant={VARIANTS.OUTLINED} onClick={widgetControl} />
|
||||
</div>
|
||||
|
||||
@@ -1,29 +1,33 @@
|
||||
<script lang="ts">
|
||||
import UploadWidget from '$lib/components/UploadWidget/index.svelte';
|
||||
import get from 'lodash/get';
|
||||
import { observeIntersection } from './components/IntersectionObserver';
|
||||
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import pluralize from 'pluralize';
|
||||
import { page } from '$app/stores';
|
||||
import { landingPage } from './store';
|
||||
import { Loading } from 'carbon-components-svelte';
|
||||
import Notebook from 'carbon-icons-svelte/lib/Notebook.svelte'; //note
|
||||
import PresentationFile from 'carbon-icons-svelte/lib/PresentationFile.svelte'; // exercise
|
||||
import Video from 'carbon-icons-svelte/lib/Video.svelte'; //video
|
||||
import PageNumber from 'carbon-icons-svelte/lib/PageNumber.svelte';
|
||||
import PlayFilled from 'carbon-icons-svelte/lib/PlayFilled.svelte';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import PricingSection from './components/PricingSection.svelte';
|
||||
import { observeIntersection } from './components/IntersectionObserver';
|
||||
import { getLectureNo } from '../Course/function';
|
||||
import { NAV_ITEMS } from './constants';
|
||||
import Avatar from '$lib/components/Avatar/index.svelte';
|
||||
import Chip from '../Chip/index.svelte';
|
||||
import { getEmbedId } from '$lib/utils/functions/formatYoutubeVideo';
|
||||
import type { Course, Lesson, Review } from '$lib/utils/types';
|
||||
import Modal from '../Modal/index.svelte';
|
||||
import { reviewsModalStore } from './store';
|
||||
import Chip from '../Chip/index.svelte';
|
||||
import Avatar from '$lib/components/Avatar/index.svelte';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { getEmbedId } from '$lib/utils/functions/formatYoutubeVideo';
|
||||
import type { Course, Lesson, Review } from '$lib/utils/types';
|
||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||
import HtmlRender from '$lib/components/HTMLRender/HTMLRender.svelte';
|
||||
import { calDateDiff } from '$lib/utils/functions/date';
|
||||
import { landingPage } from './store';
|
||||
import { Loading } from 'carbon-components-svelte';
|
||||
import { handleOpenWidget } from './store';
|
||||
|
||||
export let editMode = false;
|
||||
export let courseData: Course;
|
||||
const ratingsImg = [
|
||||
@@ -124,6 +128,10 @@
|
||||
startCoursePayment = true;
|
||||
}}
|
||||
/>
|
||||
|
||||
{#if $handleOpenWidget.open}
|
||||
<UploadWidget />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Banner Image getEmbedId(videoUrl) -->
|
||||
@@ -146,12 +154,12 @@
|
||||
/>
|
||||
</div> -->
|
||||
{:else}
|
||||
<div class="w-2/3 relative rounded-md overflow-hidden">
|
||||
<div class="banner-image md:w-2/3 relative rounded-md overflow-hidden">
|
||||
<img
|
||||
style="min-width:280px; min-height:200px"
|
||||
alt="About us"
|
||||
src={bannerImage ? bannerImage : '/images/classroomio-course-img-template.jpg'}
|
||||
class="mt-2 md:mt-0 rounded-md w-full relative"
|
||||
class="mt-2 md:mt-0 rounded-md w-full h-full max-w-[500px] max-h-[400px] relative"
|
||||
/>
|
||||
{#if $landingPage.uploadingImage}
|
||||
<Loading withOverlay={true} small class="absolute top-0 w-full h-full" />
|
||||
|
||||
@@ -64,3 +64,7 @@ export const landingPage = writable({
|
||||
uploadingImage: false,
|
||||
imageUrl: ''
|
||||
});
|
||||
|
||||
export let handleOpenWidget = writable({
|
||||
open: false
|
||||
});
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<a
|
||||
rel="prefetch"
|
||||
href={isOnLandingPage ? `/course/${slug}` : `/courses/${id}`}
|
||||
class="text-black border border-gray rounded w-full max-w-[320px] relative hover:shadow-lg transition-all ease-in-out"
|
||||
class="text-black border border-gray rounded w-full max-w-[320px] relative hover:scale-95 transition-all ease-in-out"
|
||||
>
|
||||
<div class="p-4">
|
||||
<div class=" mb-5">
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
export let onKeyDown = () => {};
|
||||
export let className = '';
|
||||
export let inputClassName = '';
|
||||
export let labelClassName = '';
|
||||
export let labelClassName = 'font-light';
|
||||
export let bgColor = 'bg-gray-100 focus:bg-primary-50 dark:bg-neutral-800 dark:text-white';
|
||||
export let type = 'text';
|
||||
export let isPassword = false;
|
||||
@@ -57,7 +57,7 @@
|
||||
|
||||
<label class="block relative {className}">
|
||||
{#if label}
|
||||
<p for="text-field" class="text-sm dark:text-white text-left m-0 font-light {labelClassName}">
|
||||
<p for="text-field" class="text-sm dark:text-white text-left m-0 {labelClassName}">
|
||||
{label}
|
||||
|
||||
{#if isRequired}
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<div class="">
|
||||
<slot name="content" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import Tabs from '$lib/components/Tabs/index.svelte';
|
||||
import { course } from '$lib/components/Course/store';
|
||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||
import TabContent from '$lib/components/TabContent/index.svelte';
|
||||
import { updateCourse } from '$lib/utils/services/courses';
|
||||
import { snackbar } from '../Snackbar/store';
|
||||
import { isObject } from '$lib/utils/functions/isObject';
|
||||
import Modal from '$lib/components/Modal/index.svelte';
|
||||
import { handleOpenWidget } from '$lib/components/CourseLandingPage/store';
|
||||
import { settings } from '../Course/components/Settings/store';
|
||||
import { queryUnsplash } from './utils';
|
||||
|
||||
export let selectedImgUrl = '';
|
||||
|
||||
let tabs = [
|
||||
{ label: 'Unsplash', value: 'unsplash' },
|
||||
{ label: 'Upload', value: 'upload' }
|
||||
];
|
||||
let currentTab = tabs[0].value;
|
||||
let searchQuery = '';
|
||||
let unsplashImages = [];
|
||||
let avatar;
|
||||
let fileInput;
|
||||
let imagebuffer;
|
||||
|
||||
const supabase = getSupabase();
|
||||
const onChange = (tabValue) => {
|
||||
currentTab = tabValue;
|
||||
};
|
||||
|
||||
async function handleImageClick(imageUrl) {
|
||||
selectedImgUrl = imageUrl;
|
||||
uploadImage(selectedImgUrl);
|
||||
}
|
||||
|
||||
const onFileSelected = () => {
|
||||
const image = fileInput.files[0];
|
||||
if (image) {
|
||||
let reader = new FileReader();
|
||||
reader.readAsDataURL(image);
|
||||
reader.onload = (e) => {
|
||||
imagebuffer = image;
|
||||
uploadImage(image);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const uploadImage = async (image) => {
|
||||
if (image) {
|
||||
if (typeof image === 'string') {
|
||||
$settings.image = image;
|
||||
} else if (image instanceof File) {
|
||||
const filename = `bannerimage/${Date.now()}` + image.name;
|
||||
const { data } = await supabase.storage.from('avatars').upload(filename, image, {
|
||||
cacheControl: '3600',
|
||||
upsert: false
|
||||
});
|
||||
|
||||
if (data) {
|
||||
const { data: response } = await supabase.storage.from('avatars').getPublicUrl(filename);
|
||||
|
||||
$settings.image = response.publicUrl;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const { image } = $settings;
|
||||
await updateCourse($course.id, avatar, {
|
||||
logo: image,
|
||||
|
||||
metadata: {
|
||||
...(isObject($course.metadata) ? $course.metadata : {})
|
||||
}
|
||||
});
|
||||
|
||||
$course.logo = image;
|
||||
$course.metadata = {
|
||||
...(isObject($course.metadata) ? $course.metadata : {})
|
||||
};
|
||||
snackbar.success('Saved successfully');
|
||||
$handleOpenWidget.open = false;
|
||||
} catch (error) {
|
||||
snackbar.error();
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function handleUpload() {
|
||||
fileInput.click();
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
unsplashImages = await queryUnsplash(searchQuery || 'rocks');
|
||||
} catch (error) {
|
||||
console.error('Error fetching images from Unsplash:', error);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(handleSubmit);
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
onClose={() => ($handleOpenWidget.open = false)}
|
||||
bind:open={$handleOpenWidget.open}
|
||||
width="w-3/5"
|
||||
maxWidth=""
|
||||
modalHeading="Banner Widget"
|
||||
>
|
||||
<div class="w-full bg-white dark:bg-inherit p-5">
|
||||
<Tabs {tabs} {currentTab} {onChange}>
|
||||
<slot:fragment slot="content">
|
||||
<TabContent value={tabs[1].value} index={currentTab}>
|
||||
<!-- Your Upload content here -->
|
||||
<div class="w-full mt-2">
|
||||
<input
|
||||
type="file"
|
||||
style="display: none;"
|
||||
bind:this={fileInput}
|
||||
on:change={onFileSelected}
|
||||
/>
|
||||
<button
|
||||
on:click={handleUpload}
|
||||
class="py-2 w-full font-semibold hover:bg-gray-200 border-[1px]">Upload Image</button
|
||||
>
|
||||
<p class="text-center text-sm text-gray-500 my-2">
|
||||
Images wider than 1500 pixels work best.
|
||||
</p>
|
||||
<p class="text-center text-sm text-gray-500">The maximum size per file is 5 MB.</p>
|
||||
</div>
|
||||
</TabContent>
|
||||
<TabContent value={tabs[0].value} index={currentTab}>
|
||||
<!-- Your Images content here -->
|
||||
<div class="h-full py-5 overflow-y-scroll">
|
||||
<form on:submit|preventDefault={handleSubmit} class="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
bind:value={searchQuery}
|
||||
name=""
|
||||
id=""
|
||||
class="rounded-lg w-[87%] ml-2 dark:text-black"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
class="px-3 py-1 bg-white rounded-lg border-[1px] border-gray-500 text-black"
|
||||
>Submit</button
|
||||
>
|
||||
</form>
|
||||
{#if unsplashImages && unsplashImages.length > 0}
|
||||
<div class="flex flex-row items-center flex-wrap gap-2 py-4 px-[10px] max-h-[300px]">
|
||||
{#each unsplashImages as unsplashImages (unsplashImages.id)}
|
||||
<div class="w-[190px] h-[130px] overflow-hidden relative">
|
||||
<img
|
||||
src={unsplashImages.urls.regular}
|
||||
alt={unsplashImages.alt_description}
|
||||
class="w-full h-full object-cover rounded-md hover:opacity-80 cursor-pointer"
|
||||
on:click={() => handleImageClick(unsplashImages.urls.regular)}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<p class="text-center pt-7">No images available.</p>
|
||||
{/if}
|
||||
</div>
|
||||
</TabContent>
|
||||
</slot:fragment>
|
||||
</Tabs>
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -0,0 +1,24 @@
|
||||
import { supabase } from '$lib/utils/functions/supabase';
|
||||
|
||||
export async function queryUnsplash(searchQuery: string) {
|
||||
const { data } = await supabase.auth.getSession();
|
||||
const accessToken = data.session?.access_token || '';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/unsplash', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: accessToken
|
||||
},
|
||||
body: JSON.stringify({ searchQuery })
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text());
|
||||
}
|
||||
const result = await response.json();
|
||||
return result.photos;
|
||||
} catch (error) {
|
||||
console.error('Error sending fetch request', error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { UNSPLASH_API_KEY } from '$env/static/private';
|
||||
|
||||
const UNSPLASH_API_URL = `https://api.unsplash.com/search/photos?page=2&per_page=15&auto=format&fit=crop&w=2970&q=80&client_id=${UNSPLASH_API_KEY}`;
|
||||
|
||||
// API to fetch photos from Unsplash
|
||||
export async function POST({ request }) {
|
||||
const body = await request.json();
|
||||
const { searchQuery } = body;
|
||||
|
||||
const query = searchQuery || 'rocks';
|
||||
|
||||
try {
|
||||
const response = await fetch(`${UNSPLASH_API_URL}&query=${encodeURIComponent(query)}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Unable to fetch photos from Unsplash API');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
return json({
|
||||
success: true,
|
||||
photos: data.results,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
return json({ success: false, message: 'Error fetching photos from Unsplash' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -306,7 +306,7 @@
|
||||
class="px-2 my-2 pr-4 border-t-0 border-b-0 border-l-0 border border-gray-300 flex items-center"
|
||||
on:click={() => goto(path)}
|
||||
>
|
||||
<CourseIcon color="#fff" />
|
||||
<CourseIcon />
|
||||
</button>
|
||||
{/if}
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user