feat: [lw] free tools (#297)
* checkpoint * checkpoint * feat: Create free tools page * responsiveness fix * rename free-tools to tools * add slug * feat: Random name picker * quick fix * PR Fixes * PR Fixes * - adjustments * update env example * remove base url * remove base url in .env.example * fix type errors * add og images * add signup cta to /tools layout * update cta * add socials avatar * add meta tags * feat: tic tac toe (#307) * Feat: tic tac toe * PR Adjustments * feat: Implement Timed Activity Tool (#316) * chore: update verification email content * chore: landing page improvements * feat: grade exercise with AI (#296) * working on the UI of the grading * working on the grading function of the radio and checkboxes * ai grading fully implemented with dummy functions for generating scores * working on the openai response * working on the openai response * finished with the ai_grading * finished with the ai_grading * creating new migration file * working on reviews * updated the translations * improvements --------- Co-authored-by: Rotimi Best <[email protected]> * feat: selfhost tinymce * fix: logo resolution error in manifest.json * feat: move from gpt3.5 to gpt-4o * feat: customize the LMS from dashboard (#304) * Done with the customization of the LMS task * generated custom_lms migration file * updated the migration file * copy improvements * update wording * made updates based on reviews,i hope i didnt miss anything * updated the translations and migration files * refactoring * disabled improvements for apps * more improvements * more custom improvements * pr refactoring --------- Co-authored-by: Rotimi Best <[email protected]> * fix: CustomizeLMS not found * fix: CustomizeLms wrong path * setup * docs: update short hero tagline * debug: courses not showing on vercel * more debugging * i think this fixes it * fix: phew, clean up debug cause it works * fix: move RBS into coursecontainer on marks page * chore: cleaner way to implement redirect if role permission denied * docs: update tagline * fix: delete /org/# route * checkpoint * add reusable header component * fix: empty lesson title bug (#314) * finished the lesson title bug * made chnages based on review * Update +page.svelte --------- Co-authored-by: Rotimi Best <[email protected]> * feat: add sound cdn * remove console.log * feat: course types - self paced and live class (#309) * working on the course type * trying to mark it persist * almost done here * fixed sendig emtpy options bug * created migration file * created migration file * made updates based on PR review * merge * update course type card * add redirect logic with classroomio.com/new * make new course modal only open/close with url * feat: page active state (#315) * finished the active link state * updated the active link state based on review * simplify code --------- Co-authored-by: Rotimi Best <[email protected]> * add type to course card --------- Co-authored-by: Rotimi Best <[email protected]> * general improvements to free tools * fix timer and styling issues --------- Co-authored-by: Rotimi Best <[email protected]> Co-authored-by: Em_Dev <[email protected]> * update tagline for tools page * feat: Add Free Section to Landing Page (#332) * feat: Add Free Section to Landing Page * PR Fixes * refactor * PR Fixes * restructure * improve cursor UX --------- Co-authored-by: Rotimi Best <[email protected]> * feat: Progress Report Tool (#329) * checkpoint * fix: corrections * finalization * improve ux of progress tool * more styling improvements * PR Fixes * PR Fixes * fix: mood bg on report * fix: generate exercise with AI bug (#331) * fix: enable lesson cascade delete (#335) * styling improvements * chore: hide course grading toggle in settings (#338) * feat: reset select in community page (#339) * Feat: Add Reset for comminity dropdown * - add translation * refactor --------- Co-authored-by: Rotimi Best <[email protected]> * fix: prevent hypen at start or end of organzation or domain name (#340) * fix: adding hypen to start or end of org or domain name * fix: add org name validation to settings page * chore: remove unnecessary variable use * chore: remove error reset function * Update OrgSettings.svelte --------- Co-authored-by: Rotimi Best <[email protected]> * fix: full screen * fix: pnpm version error & add top level .nvmrc (#343) * update name of tool * chore: add translations for validation messages (#345) * chore: add validation texts to locale file * feat: switch all validations from plain text to use i18n locale * feat: switch review form validations from plain text to use locale file * run translation script * chore: add translations for validation messages (#345) (#347) * chore: add translations for validation messages (#345) * chore: add validation texts to locale file * feat: switch all validations from plain text to use i18n locale * feat: switch review form validations from plain text to use locale file * run translation script --------- Co-authored-by: Iniubong Obonguko <[email protected]> * PR Fixes * Bug fixes on mobile * Final bug fix * code improvements * PR Fixes * Merge Fix * PR Fixes * fix: width * code cleanup * feat: Promodo Tool (tracks tim spent) * feat: Pomodoro timer (without tracking feature) * fix: - change pause functionality to next - correct spelling of pomodoro * fix: pomodoro logo rename * styling improvements --------- Co-authored-by: Rotimi Best <[email protected]> Co-authored-by: Em_Dev <[email protected]> Co-authored-by: Iniubong Obonguko <[email protected]> Co-authored-by: Iniubong Obonguko <[email protected]> Co-authored-by: Aditya Deshlahre <[email protected]> * feat: Implement Bottom menu (#341) * popup logic done * popup UI first phase done * finished the menupop up for both lms and admin * finished the menupop up for both lms and admin * done * cleanup * fixed popover position * still working on the routing * i think that is it for the day * updated the menupopup currentorgdomain * rename popup to bottomMenu * refactoring --------- Co-authored-by: Rotimi Best <[email protected]> * remove logout * change tagline * feat: Store todolist to localStorage (#375) * feat: Save todo to localStorage * remove fly animation to todos * feat: Progress Report avatar update (#376) * feat: Reorder Avatar list * quick fix * change avatar url * more improvements * quick background fix * fix: white background * body background fix * update lock --------- Co-authored-by: Rotimi Best <[email protected]> Co-authored-by: Em_Dev <[email protected]> Co-authored-by: Iniubong Obonguko <[email protected]> Co-authored-by: Iniubong Obonguko <[email protected]> Co-authored-by: Aditya Deshlahre <[email protected]>
This commit is contained in:
co-authored by
Rotimi Best
Em_Dev
Iniubong Obonguko
Iniubong Obonguko
Aditya Deshlahre
parent
91bd9d4414
commit
9c066c79be
@@ -17,7 +17,7 @@
|
||||
<p class="lg:text-base mb-5 mr-2 text-sm leading-7 tracking-wide text-gray-600 lg:leading-7">
|
||||
Launch Your Online Bootcamp In Minutes
|
||||
<br />
|
||||
The easiest way to launch and scale your online bootcamp
|
||||
ClassroomIO is the easiest way to grow your teaching business.
|
||||
</p>
|
||||
<!-- <a
|
||||
href="https://www.producthunt.com/posts/classroomio?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-classroomio"
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export { before, after, offset, overlay, contain, hideOnSlide };
|
||||
|
||||
let hideOnSlide = true,
|
||||
imgOffset = null,
|
||||
sliding = false,
|
||||
contain = false,
|
||||
overlay = true,
|
||||
offset = 0.5,
|
||||
before = '',
|
||||
after = '',
|
||||
img;
|
||||
|
||||
function resize(e) {
|
||||
imgOffset = (e.type === 'load' ? e.target : img).getBoundingClientRect();
|
||||
w = imgOffset && imgOffset.width;
|
||||
h = imgOffset && imgOffset.height;
|
||||
style = contain ? `width:100%;height:100%;` : `width:${w}px;height:${h}px;`;
|
||||
}
|
||||
|
||||
function move(e) {
|
||||
if (sliding && imgOffset) {
|
||||
let x = (e.touches ? e.touches[0].pageX : e.pageX) - imgOffset.left;
|
||||
x = x < 0 ? 0 : x > w ? w : x;
|
||||
offset = x / w;
|
||||
}
|
||||
}
|
||||
|
||||
function start(e) {
|
||||
sliding = true;
|
||||
move(e);
|
||||
}
|
||||
|
||||
function end() {
|
||||
sliding = false;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
resize({});
|
||||
});
|
||||
|
||||
$: w = imgOffset && imgOffset.width;
|
||||
$: h = imgOffset && imgOffset.height;
|
||||
$: x = w * offset;
|
||||
$: opacity = hideOnSlide && sliding ? 0 : 1;
|
||||
$: style = contain ? `width:100%;height:100%;` : `width:${w}px;height:${h}px;`;
|
||||
</script>
|
||||
|
||||
<svelte:window on:resize={resize} />
|
||||
|
||||
<button
|
||||
class="container floating w-[85%] lg:w-[70%] h-full mx-auto rounded-md"
|
||||
{style}
|
||||
on:touchstart={start}
|
||||
on:mousedown={start}
|
||||
on:touchmove={move}
|
||||
on:touchend={end}
|
||||
on:mousemove={move}
|
||||
on:mouseup={end}
|
||||
>
|
||||
<img
|
||||
loading="lazy"
|
||||
bind:this={img}
|
||||
src={after}
|
||||
alt="after"
|
||||
on:mousedown|preventDefault
|
||||
on:load={resize}
|
||||
{style}
|
||||
/>
|
||||
<img
|
||||
loading="lazy"
|
||||
src={before}
|
||||
alt="before"
|
||||
on:mousedown|preventDefault
|
||||
style="{style}clip:rect(0, {x}px, {h}px, 0);"
|
||||
/>
|
||||
{#if overlay}
|
||||
<div class="overlay" style="opacity:{opacity}" />
|
||||
{/if}
|
||||
<div class="before-label" style="opacity:{opacity}">
|
||||
<slot name="before" />
|
||||
</div>
|
||||
<div class="after-label" style="opacity:{opacity}">
|
||||
<slot name="after" />
|
||||
</div>
|
||||
<div class="handle" style="left: calc({offset * 100}% - 20px)">
|
||||
<div class="arrow-left" />
|
||||
<div class="arrow-right" />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
box-sizing: content-box;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
.container img {
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 20;
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
user-select: none;
|
||||
object-fit: cover;
|
||||
position: absolute;
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
}
|
||||
.overlay {
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
z-index: 25;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
transition: opacity 0.5s;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.before-label,
|
||||
.after-label {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 25;
|
||||
user-select: none;
|
||||
position: absolute;
|
||||
}
|
||||
.before-label {
|
||||
left: 0;
|
||||
}
|
||||
.after-label {
|
||||
right: 0;
|
||||
}
|
||||
.container:hover > .overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
.handle {
|
||||
z-index: 30;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
cursor: move;
|
||||
background: none;
|
||||
margin-top: -4px;
|
||||
margin-left: -4px;
|
||||
user-select: none;
|
||||
position: absolute;
|
||||
border-radius: 50px;
|
||||
top: calc(50% - 20px);
|
||||
border: 4px solid white;
|
||||
}
|
||||
.handle:before,
|
||||
.handle:after {
|
||||
content: '';
|
||||
height: 9999px;
|
||||
position: absolute;
|
||||
left: calc(50% - 2px);
|
||||
border: 2px solid white;
|
||||
}
|
||||
.handle:before {
|
||||
top: 40px;
|
||||
}
|
||||
.handle:after {
|
||||
bottom: 40px;
|
||||
}
|
||||
.arrow-right,
|
||||
.arrow-left {
|
||||
width: 0;
|
||||
height: 0;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid transparent;
|
||||
}
|
||||
.arrow-right {
|
||||
left: 17px;
|
||||
bottom: 13px;
|
||||
border-left: 10px solid white;
|
||||
}
|
||||
.arrow-left {
|
||||
left: 5px;
|
||||
top: 7px;
|
||||
border-right: 10px solid white;
|
||||
}
|
||||
|
||||
img,
|
||||
.overlay,
|
||||
.container {
|
||||
max-height: 500px;
|
||||
}
|
||||
</style>
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import Section from './Section.svelte';
|
||||
import Section from './CoreFeatureSection.svelte';
|
||||
import { env } from '$env/dynamic/public';
|
||||
</script>
|
||||
|
||||
+42
@@ -2,6 +2,7 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { fly } from 'svelte/transition';
|
||||
import { folder, globe, team, transcription } from '$lib/emojis';
|
||||
import { tools } from '$lib/utils/constants/tools';
|
||||
|
||||
let selected = 0;
|
||||
let interval: NodeJS.Timer;
|
||||
@@ -36,6 +37,8 @@
|
||||
}
|
||||
];
|
||||
|
||||
const freeTools = tools.filter((item) => item.showFeature);
|
||||
|
||||
onMount(() => {
|
||||
if (window.innerWidth > 768) {
|
||||
interval = setInterval(() => {
|
||||
@@ -104,6 +107,45 @@
|
||||
{/key}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- free tools section -->
|
||||
<div>
|
||||
<div class="flex justify-evenly gap-y-8 flex-wrap w-full mt-10 mx-auto">
|
||||
<div class="text-center">
|
||||
<h1 class="text-3xl md:text-5xl font-bold">
|
||||
Play With Our <span class="text-[#0233BD]">Free Tools</span>
|
||||
</h1>
|
||||
<p class="text-sm md:text-base text-[#334155] md:w-[65%] mt-5 md:mt-10 mx-auto">
|
||||
Innovative tools to help you aid you example preparation, as well as making learning
|
||||
more fun for students and educator looking for engaging resources.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{#each freeTools as tool}
|
||||
<a
|
||||
class="w-full flex flex-col gap-3 max-w-[400px] md:max-w-[300px] mt-5 p-5 border rounded-md bg-white shadow-sm hover:scale-95 transition-all ease-in-out cursor-pointer"
|
||||
href="/tools/{tool.slug}"
|
||||
>
|
||||
<img class="w-[30%] rounded-full border border-[#EAEAEA]" src={tool.src} alt="" />
|
||||
<div class="rounded-r-md p-3">
|
||||
<h1 class="font-bold text-sm md:text-base">{tool.title}</h1>
|
||||
<p class="text-xs text-gray-600 mt-1.5">
|
||||
{tool.subText}
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex justify-center items-center mt-20">
|
||||
<a
|
||||
class="md:text-base w-full md:max-w-[300px] max-w-[400px] rounded-md bg-blue-700 px-6 py-3 lg:px-6 lg:py-3 text-sm font-medium text-white text-center transition-all delay-100 hover:bg-blue-800 hover:shadow-xl hover:scale-95 lg:font-semibold cursor-pointer"
|
||||
href="/tools"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
More Tools
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<script>
|
||||
export let className = '';
|
||||
export let open = false;
|
||||
</script>
|
||||
|
||||
{#if open}
|
||||
<div class="relative flex items-center justify-center">
|
||||
<!-- black background -->
|
||||
<div class="fixed z-[3000] top-0 left-0 w-full h-full bg-black opacity-[0.7]"></div>
|
||||
<div
|
||||
class="fixed z-[3001] {className} w-full md:w-[30%] md:min-w-[500px] bg-white rounded-md mx-auto py-6 px-7"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -52,7 +52,7 @@
|
||||
<div
|
||||
class="flex w-full justify-between items-center py-6 border-b-[1px] md:px-12 px-5 fixed top-0 z-[3000] filter backdrop-blur-xl shadow-sm bg-white"
|
||||
>
|
||||
<a href="/" class="w-[20%]">
|
||||
<a href="/" class="w-[10%]">
|
||||
<div class="flex items-center w-full">
|
||||
<img
|
||||
loading="lazy"
|
||||
@@ -66,7 +66,7 @@
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<nav class="w-[40%] hidden md:hidden lg:block">
|
||||
<nav class="w-[50%] hidden md:hidden lg:block">
|
||||
<ul class="flex items-center justify-between w-full gap-2">
|
||||
<li class="text-gray-800 font-semibold text-sm cursor-pointer relative">
|
||||
<button
|
||||
@@ -111,20 +111,20 @@
|
||||
{/if}
|
||||
</li>
|
||||
<a
|
||||
href="/pricing"
|
||||
href="/tools"
|
||||
class="text-gray-800 font-semibold text-sm cursor-pointer"
|
||||
class:active={activeLink.startsWith('/pricing')}
|
||||
class:active={activeLink.startsWith('/tools')}
|
||||
>
|
||||
<li class="hover:bg-gray-100 px-4 py-2 rounded-md">Pricing</li>
|
||||
<li class="hover:bg-gray-100 px-4 py-2 rounded-md">Free Tools</li>
|
||||
</a>
|
||||
<a
|
||||
<!-- <a
|
||||
href="/docs"
|
||||
target="_blank"
|
||||
class="text-gray-800 font-semibold text-sm cursor-pointer"
|
||||
class:active={activeLink.startsWith('/docs')}
|
||||
>
|
||||
<li class="hover:bg-gray-100 px-4 py-2 rounded-md">Docs</li>
|
||||
</a>
|
||||
</a> -->
|
||||
|
||||
<a
|
||||
href="/blog"
|
||||
@@ -133,6 +133,13 @@
|
||||
>
|
||||
<li class="hover:bg-gray-100 px-4 py-2 rounded-md">Blog</li>
|
||||
</a>
|
||||
<a
|
||||
href="/pricing"
|
||||
class="text-gray-800 font-semibold text-sm cursor-pointer"
|
||||
class:active={activeLink.startsWith('/pricing')}
|
||||
>
|
||||
<li class="hover:bg-gray-100 px-4 py-2 rounded-md">Pricing</li>
|
||||
</a>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -214,17 +221,15 @@
|
||||
</div>
|
||||
{/if}
|
||||
</li>
|
||||
<!-- Free Tools -->
|
||||
<a
|
||||
class="text-gray-800 font-semibold text-sm md:text-lg cursor-pointer hover:bg-gray-100 py-3 px-4 rounded-xl w-full"
|
||||
on:click={() => {
|
||||
handleShowNav();
|
||||
}}
|
||||
href="/pricing"
|
||||
class:active={activeLink.startsWith('/pricing')}
|
||||
on:click={handleShowNav}
|
||||
href="/tools"
|
||||
>
|
||||
<li>Pricing</li>
|
||||
<li>Free Tools</li>
|
||||
</a>
|
||||
<a
|
||||
<!-- <a
|
||||
class="text-gray-800 font-semibold text-sm md:text-lg cursor-pointer hover:bg-gray-100 py-3 px-4 rounded-md w-full"
|
||||
on:click={() => {
|
||||
handleShowNav();
|
||||
@@ -234,7 +239,7 @@
|
||||
target="_blank"
|
||||
>
|
||||
<li>Docs</li>
|
||||
</a>
|
||||
</a> -->
|
||||
<a
|
||||
class="text-gray-800 font-semibold text-sm md:text-lg cursor-pointer hover:bg-gray-100 py-3 px-4 rounded-md w-full"
|
||||
on:click={() => {
|
||||
@@ -245,6 +250,17 @@
|
||||
>
|
||||
<li>Blog</li>
|
||||
</a>
|
||||
<!-- Pricing -->
|
||||
<a
|
||||
class="text-gray-800 font-semibold text-sm md:text-lg cursor-pointer hover:bg-gray-100 py-3 px-4 rounded-xl w-full"
|
||||
on:click={() => {
|
||||
handleShowNav();
|
||||
}}
|
||||
href="/pricing"
|
||||
class:active={activeLink.startsWith('/pricing')}
|
||||
>
|
||||
<li>Pricing</li>
|
||||
</a>
|
||||
<a
|
||||
class="text-gray-800 font-semibold text-sm md:text-lg cursor-pointer hover:bg-gray-100 py-3 px-4 rounded-md w-full"
|
||||
on:click={() => {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<script>
|
||||
export let className = '';
|
||||
</script>
|
||||
|
||||
<header class="tools-hero-bg text-center py-14 {className} border-b border-slate-100 bg-white">
|
||||
<slot />
|
||||
</header>
|
||||
@@ -0,0 +1,71 @@
|
||||
interface Tool {
|
||||
src: string;
|
||||
title: string;
|
||||
subText: string;
|
||||
slug: string;
|
||||
show: boolean;
|
||||
showFeature: boolean;
|
||||
}
|
||||
|
||||
export const tools: Tool[] = [
|
||||
{
|
||||
src: '/free-tools/progress-report.svg',
|
||||
title: 'Progress Report',
|
||||
subText: 'Monitor your progress with a system to check and share your progress with others',
|
||||
slug: 'progress',
|
||||
show: true,
|
||||
showFeature: true
|
||||
},
|
||||
{
|
||||
src: '/free-tools/pomodoro.svg',
|
||||
title: 'Pomodoro Timer',
|
||||
subText:
|
||||
'Make your workday more engaging and effective with the Pomodoro timer, break work into 25-minute focused intervals called "pomodoros".',
|
||||
slug: 'pomodoro',
|
||||
show: true,
|
||||
showFeature: false
|
||||
},
|
||||
{
|
||||
src: '/free-tools/name-picker.svg',
|
||||
title: 'Random Name Picker',
|
||||
subText: 'Effortlessly choose names for class participation or group activities',
|
||||
slug: 'name-picker',
|
||||
show: true,
|
||||
showFeature: false
|
||||
},
|
||||
{
|
||||
src: '/free-tools/activity-stopwatch.svg',
|
||||
title: 'Activity Stopwatch',
|
||||
subText:
|
||||
'Stay on track and enhance productivity with our customizable stopwatch for timed tasks, quizzes, and study sessions',
|
||||
slug: 'stopwatch',
|
||||
show: true,
|
||||
showFeature: true
|
||||
},
|
||||
{
|
||||
src: '/free-tools/tic-tac.svg',
|
||||
title: 'Tic tac toe game',
|
||||
subText:
|
||||
"More than just a game; it's an educational tool that teaches pattern recognition, and decision-making.",
|
||||
slug: 'tic-tac-toe',
|
||||
show: true,
|
||||
showFeature: true
|
||||
},
|
||||
{
|
||||
src: '/free-tools/waec.svg',
|
||||
title: 'WAEC Practice tool',
|
||||
subText: 'Ace your WAEC exams with comprehensive study materials, past questions',
|
||||
slug: 'waec',
|
||||
show: false,
|
||||
showFeature: false
|
||||
},
|
||||
{
|
||||
src: '/free-tools/jamb.svg',
|
||||
title: 'JAMB Practice tool',
|
||||
subText:
|
||||
'Prepare for JAMB with past questions and instant feedback for effective exam readiness',
|
||||
slug: 'jamb',
|
||||
show: false,
|
||||
showFeature: false
|
||||
}
|
||||
];
|
||||
Reference in New Issue
Block a user