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:
Adeyemi Adetunji
2024-09-30 01:36:12 +01:00
committed by GitHub
co-authored by Rotimi Best Em_Dev Iniubong Obonguko Iniubong Obonguko Aditya Deshlahre
parent 91bd9d4414
commit 9c066c79be
106 changed files with 3778 additions and 478 deletions
@@ -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,5 +1,5 @@
<script lang="ts">
import Section from './Section.svelte';
import Section from './CoreFeatureSection.svelte';
import { env } from '$env/dynamic/public';
</script>
@@ -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
}
];