refactor: Update content & fix some issues

This commit is contained in:
shiva raj badu 2026-04-07 11:37:21 +05:45
parent 787fe55937
commit bda9d2b99e
No known key found for this signature in database
15 changed files with 40 additions and 186 deletions

View file

@ -64,12 +64,10 @@ const PrivacySettings = () => {
<div className="w-full space-y-6">
<div className="bg-[#F9F8F8] p-7 rounded-[20px]">
<h4 className="text-sm font-semibold text-[#191919] mb-1">
Anonymous Usage Tracking
Usage analytics
</h4>
<p className="text-xs text-[#6B7280] mb-6 leading-relaxed max-w-lg">
When enabled, Presenton collects anonymous usage data to help us
understand how the app is used and improve your experience.<span className="font-bold"> No
personal information or presentation content is ever collected.</span>
Share anonymous usage data to help us improve Presenton. No personal information or presentation content is collected.
</p>
<div className="flex items-center justify-between gap-4 rounded-[10px] bg-white border border-[#EDEEEF] p-4">
@ -78,12 +76,12 @@ const PrivacySettings = () => {
htmlFor="tracking-toggle"
className="text-sm font-medium text-[#191919] cursor-pointer select-none block"
>
{trackingEnabled ? "Tracking Enabled" : "Tracking Disabled"}
Share anonymous usage data
</label>
<p className="text-xs text-[#9CA3AF] mt-0.5">
{trackingEnabled
? "Anonymous usage data is being sent."
: "No usage data is being collected."}
? "Anonymous usage data is being shared."
: "Anonymous usage data is not being shared"}
</p>
</div>
<div className="flex items-center gap-2">

View file

@ -9,31 +9,31 @@ const SettingSideBar = ({ mode, setMode, selectedProvider, setSelectedProvider }
const textProviderIcon = LLM_PROVIDERS[llm_config.LLM as keyof typeof LLM_PROVIDERS]?.icon
const imageProviderIcon = IMAGE_PROVIDERS[llm_config.IMAGE_PROVIDER as keyof typeof IMAGE_PROVIDERS]?.icon || '/providers/pexel.png'
return (
<div className='w-full max-w-[230px] h-screen px-4 pt-[22px] bg-[#F9FAFB] flex flex-col'>
<div className='w-full max-w-[230px] h-screen px-3 pt-[22px] bg-[#F9FAFB] flex flex-col'>
<p className='text-xs text-black font-medium border-b mt-[3.15rem] border-[#E1E1E5] pb-3.5'>FILTER BY:</p>
<div className='mt-6 flex-1'>
<p className='text-[#3A3A3A] text-xs font-medium pb-2.5'>Select Mode</p>
<div className='p-0.5 rounded-[40px] bg-[#ffffff] w-fit border border-[#EDEEEF] flex items-center justify-center mb-[34px] '>
<button className='px-3 font-syne h-[26px] text-xs font-medium text-[#3A3A3A] rounded-[70px]'
<button className='px-3 font-syne h-[26px] text-[10px] font-medium text-[#3A3A3A] rounded-[70px]'
onClick={() => setMode('presenton')}
style={{
background: mode === 'presenton' ? '#F4F3FF' : 'transparent',
color: mode === 'presenton' ? '#5146E5' : '#3A3A3A'
}}
>Presenton
>Template Based
</button>
<svg xmlns="http://www.w3.org/2000/svg" className='mx-1' width="2" height="17" viewBox="0 0 2 17" fill="none">
<path d="M1 0V16.5" stroke="#EDECEC" strokeWidth="2" />
</svg>
<div className='relative'>
<button className='px-3 font-syne h-[26px] text-xs font-medium rounded-[70px] cursor-not-allowed opacity-60'
<button className='px-3 font-syne h-[26px] text-[10px] font-medium rounded-[70px] cursor-not-allowed opacity-60'
disabled
style={{
background: 'transparent',
color: '#9CA3AF'
}}
>
Nanobanana
Image Based
</button>
<span className='absolute -top-2 -right-5 text-[7px] uppercase tracking-wide bg-[#F4F3FF] text-[#5146E5] border border-[#D9D6FE] rounded-full px-1.5 py-0.5 whitespace-nowrap'>
Coming soon

View file

@ -229,7 +229,7 @@ const TextProvider = ({
</div>
<h3 className="text-xl font-normal text-[#191919] py-2.5">Text Generation Settings</h3>
<p className=" text-sm text-gray-500">
Choosing where text contets come from
Choosing where text content comes from
</p>
</div>
<div className='flex flex-col justify-end items-end gap-4'>

View file

@ -432,7 +432,7 @@ const PresentationHeader = ({
trackEvent(MixpanelEvent.Navigation, { from: pathname, to });
router.push(to);
}}
disabled={!presentationData?.slides || presentationData?.slides.length === 0} className="cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed group">
disabled={isStreaming || !presentationData?.slides || presentationData?.slides.length === 0} className="cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed group">
<Play className="w-3.5 h-3.5 text-[#101323] group-hover:text-[#5141e5] duration-300" />
</button>
</ToolTip>
@ -444,7 +444,7 @@ const PresentationHeader = ({
style={{
background: "linear-gradient(270deg, #D5CAFC 2.4%, #E3D2EB 27.88%, #F4DCD3 69.23%, #FDE4C2 100%)",
}}
disabled={isExporting}
disabled={isExporting || isStreaming === true}
>
{isExporting ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : "Export"} <ArrowRightFromLine className="w-3.5 h-3.5" />
</button>

View file

@ -47,7 +47,7 @@ const page = () => {
<Header />
<div className="flex flex-col items-center justify-center mb-8">
<h1 className="text-[64px] font-normal font-unbounded text-[#101323] ">
AI Presentation
Generate Presentation
</h1>
<p className="text-xl font-syne text-[#101323CC]">Choose a design, set preferences, and generate polished slides.</p>
</div>

View file

@ -323,20 +323,7 @@ export default function CodexConfig({
<p className="text-sm font-medium text-gray-800 truncate">
{username || email || (accountId ? `Account ${accountId}` : "ChatGPT Account")}
</p>
<span
className={cn(
"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium shrink-0",
isPro === true ? "bg-amber-100 text-amber-800" : "bg-gray-100 text-gray-700"
)}
title={`Subscription: ${planLabel}`}
>
{isPro === true ? (
<Crown className="w-3 h-3" />
) : (
<User className="w-3 h-3" />
)}
{planLabel}
</span>
</div>
{email && username && (
<p className="text-xs text-gray-500 truncate">{email}</p>

View file

@ -1,19 +1,8 @@
"use client";
import { useState, useEffect, useMemo } from "react";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { Loader2, Download, CheckCircle } from "lucide-react";
import { useSelector } from "react-redux";
import { RootState } from "@/store/store";
import { handleSaveLLMConfig } from "@/utils/storeHelpers";
import LLMProviderSelection from "./LLMSelection";
import {
checkIfSelectedOllamaModelIsPulled,
pullOllamaModel,
} from "@/utils/providerUtils";
import { LLMConfig } from "@/types/llm_config";
import { trackEvent, MixpanelEvent } from "@/utils/mixpanel";
import { usePathname } from "next/navigation";
import OnBoardingSlidebar from "./OnBoarding/OnBoardingSlidebar";
import OnBoardingHeader from "./OnBoarding/OnBoardingHeader";
import ModeSelectStep from "./OnBoarding/ModeSelectStep";
@ -21,130 +10,22 @@ import PresentonMode from "./OnBoarding/PresentonMode";
import GenerationWithImage from "./OnBoarding/GenerationWithImage";
import FinalStep from "./OnBoarding/FinalStep";
// Button state interface
interface ButtonState {
isLoading: boolean;
isDisabled: boolean;
text: string;
showProgress: boolean;
progressPercentage?: number;
status?: string;
}
const getTaperedSideOffset = (offset: number, top: number) => {
const taperMultiplier = Math.max(0.72, 1.85 - top * 0.012);
return Math.min(29, Number((offset * taperMultiplier).toFixed(2)));
};
export default function Home() {
const router = useRouter();
const pathname = usePathname();
const [step, setStep] = useState<number>(1)
const [selectedMode, setSelectedMode] = useState<string>("presenton")
const config = useSelector((state: RootState) => state.userConfig);
const [llmConfig, setLlmConfig] = useState<LLMConfig>(config.llm_config);
const [downloadingModel, setDownloadingModel] = useState<{
name: string;
size: number | null;
downloaded: number | null;
status: string;
done: boolean;
} | null>(null);
const [showDownloadModal, setShowDownloadModal] = useState<boolean>(false);
const [buttonState, setButtonState] = useState<ButtonState>({
isLoading: false,
isDisabled: false,
text: "Save Configuration",
showProgress: false
});
const canChangeKeys = config.can_change_keys;
const downloadProgress = useMemo(() => {
if (downloadingModel && downloadingModel.downloaded !== null && downloadingModel.size !== null) {
return Math.round((downloadingModel.downloaded / downloadingModel.size) * 100);
}
return 0;
}, [downloadingModel?.downloaded, downloadingModel?.size]);
const handleSaveConfig = async () => {
trackEvent(MixpanelEvent.Home_SaveConfiguration_Button_Clicked, { pathname });
try {
setButtonState(prev => ({
...prev,
isLoading: true,
isDisabled: true,
text: "Saving Configuration..."
}));
// API: save config
trackEvent(MixpanelEvent.Home_SaveConfiguration_API_Call);
// API CALL: save config
await handleSaveLLMConfig(llmConfig);
if (llmConfig.LLM === "ollama" && llmConfig.OLLAMA_MODEL) {
// API: check model pulled
trackEvent(MixpanelEvent.Home_CheckOllamaModelPulled_API_Call);
const isPulled = await checkIfSelectedOllamaModelIsPulled(llmConfig.OLLAMA_MODEL);
if (!isPulled) {
setShowDownloadModal(true);
// API: download model
trackEvent(MixpanelEvent.Home_DownloadOllamaModel_API_Call);
await handleModelDownload();
}
}
toast.info("Configuration saved successfully");
setButtonState(prev => ({
...prev,
isLoading: false,
isDisabled: false,
text: "Save Configuration"
}));
// Track navigation from -> to
trackEvent(MixpanelEvent.Navigation, { from: pathname, to: "/upload" });
router.push("/upload");
} catch (error) {
toast.info(error instanceof Error ? error.message : "Failed to save configuration");
setButtonState(prev => ({
...prev,
isLoading: false,
isDisabled: false,
text: "Save Configuration"
}));
}
};
const handleModelDownload = async () => {
try {
await pullOllamaModel(llmConfig.OLLAMA_MODEL!, setDownloadingModel);
}
finally {
setDownloadingModel(null);
setShowDownloadModal(false);
}
};
useEffect(() => {
if (downloadingModel && downloadingModel.downloaded !== null && downloadingModel.size !== null) {
const percentage = Math.round(((downloadingModel.downloaded / downloadingModel.size) * 100));
setButtonState({
isLoading: true,
isDisabled: true,
text: `Downloading Model (${percentage}%)`,
showProgress: true,
progressPercentage: percentage,
status: downloadingModel.status
});
}
if (downloadingModel && downloadingModel.done) {
setTimeout(() => {
setShowDownloadModal(false);
setDownloadingModel(null);
toast.info("Model downloaded successfully!");
}, 2000);
}
}, [downloadingModel]);
useEffect(() => {
if (!canChangeKeys) {

View file

@ -86,13 +86,13 @@ const FinalStep = () => {
<img src="/final_onboarding.png" alt="presenton" className='w-[118px] h-[98px] object-contain' />
<h1 className='text-black text-[30px] font-normal font-unbounded py-2.5'>Welcome on board!</h1>
<p className='text-[#000000CC] text-xl font-normal font-syne'>Your AI workspace is ready. Let&apos;s create your first presentation.</p>
<p className='text-[#000000CC] text-xl font-normal font-syne'>Youre all set. Lets create your first presentation.</p>
{trackingEnabled !== null && (
<div className='flex items-center gap-3 mt-8 px-5 py-3.5 rounded-[10px] border border-[#EDEEEF] bg-white'>
<div>
<p className='text-sm font-medium text-[#191919] font-syne'>Anonymous Tracking</p>
<p className='text-[11px] text-[#9CA3AF] font-syne leading-tight mt-0.5'>Help improve Presenton with anonymous usage data.</p>
<p className='text-sm font-medium text-[#191919] font-syne'>Usage analytics</p>
<p className='text-[11px] text-[#9CA3AF] font-syne leading-tight mt-0.5'>Help improve Presenton by sharing anonymous usage data.</p>
</div>
<Switch
checked={trackingEnabled}
@ -102,7 +102,7 @@ const FinalStep = () => {
</div>
)}
<button onClick={handleGoToUpload} className='bg-[#7C51F8] px-[23px] mt-8 py-[15px] rounded-[70px] text-white text-lg font-syne font-semibold'>My First Presentation</button>
<button onClick={handleGoToUpload} className='bg-[#7C51F8] px-[23px] mt-8 py-[15px] rounded-[70px] text-white text-lg font-syne font-semibold'>My First Presentation 🚀</button>
<button onClick={fireRealisticConfetti} className='mt-3 flex items-center gap-1.5 text-sm text-[#7A5AF8] font-syne font-medium hover:underline'>
<PartyPopper className='w-4 h-4' /> Celebrate again!
</button>

View file

@ -6,8 +6,8 @@ const ModeSelectStep = ({ selectedMode, setStep, setSelectedMode }: { selectedMo
<div className='max-w-[650px]'>
<div className='mb-[70px]'>
<h2 className='mb-4 text-black text-[26px] font-normal font-unbounded '>Lets set up your AI workspace</h2>
<p className='text-[#000000CC] text-xl font-normal font-syne'>First, choose the intelligence behind your presentation generation.</p>
<h2 className='mb-4 text-black text-[26px] font-normal font-unbounded '>Choose how you want to generate presentations</h2>
<p className='text-[#000000CC] text-xl font-normal font-syne'>Pick a generation mode first. Youll connect your model providers in the next step.</p>
</div>
<div className='space-y-5'>
<div onClick={() => {
@ -21,17 +21,17 @@ const ModeSelectStep = ({ selectedMode, setStep, setSelectedMode }: { selectedMo
<div className=''>
<div className='flex items-start gap-2 relative '>
<h3 className='text-black text-[18px] font-medium font-syne'>Presenton</h3>
<p className='bg-[#F4F3FF] px-3 py-1.5 rounded-[30px] text-[#7A5AF8] text-[9px] absolute left-[95px] top-[-10px]'>PPTX</p>
<h3 className='text-black text-[18px] font-medium font-syne'>Template Presentation Mode</h3>
<p className='bg-[#F4F3FF] px-3 py-1.5 rounded-[30px] text-[#7A5AF8] text-[9px] absolute left-[260px] top-[-10px]'>PPTX Export </p>
</div>
<p className='text-[#999999] text-[14px] font-normal font-syne'>Optimized for fast, structured slide generation.</p>
<p className='text-[#999999] text-[14px] font-normal font-syne'>Best for structured decks, editing, and PPTX export. Requires text and image providers.</p>
</div>
</div>
<ChevronRight className='w-6 h-6 text-[#B3B3B3]' />
</div>
<div
className='border font-syne border-[#EDEEEF] cursor-not-allowed rounded-[11px] p-3 flex items-center justify-between gap-6 relative'>
<p className='text-black absolute top-1/2 -translate-y-1/2 right-14 flex items-center justify-center text-[14px] font-normal bg-[#F4F3FF] px-3 py-1.5 rounded-[30px]'>Coming soon</p>
<p className='text-black absolute top-[20px] right-14 flex items-center justify-center text-[14px] font-normal bg-[#F4F3FF] px-3 py-1.5 rounded-[30px]'>Coming soon</p>
<div className='flex items-center gap-6'>
<div className='rounded-[4px] bg-[#FFF6ED] p-[12px] w-[74px] h-[74px] flex items-center justify-center'>
@ -40,10 +40,10 @@ const ModeSelectStep = ({ selectedMode, setStep, setSelectedMode }: { selectedMo
<div className=''>
<div className='flex items-start gap-2 relative '>
<h3 className='text-black text-[18px] font-medium font-syne'>Generate with Image Model</h3>
<h3 className='text-black text-[18px] font-medium font-syne'>Image Slides Mode</h3>
<p className='bg-[#F4F3FF] px-3 py-1.5 rounded-[30px] text-[#7A5AF8] text-[9px] absolute left-[180px] top-[-10px]'>No PPTX Export </p>
</div>
<p className='text-[#999999] text-[14px] font-normal font-syne'>Instantly generate presentation slides as images.</p>
<p className='text-[#999999] text-[14px] font-normal font-syne'> Best for visual slide generation from image models. No PPTX export.</p>
</div>
</div>
<ChevronRight className='w-6 h-6 text-[#B3B3B3]' />
@ -56,7 +56,7 @@ const ModeSelectStep = ({ selectedMode, setStep, setSelectedMode }: { selectedMo
setStep(2);
}}
className='border font-syne border-[#EDEEEF] bg-[#7C51F8] rounded-[58px] px-5 py-2.5 text-white text-xs font-semibold'>
Start With {selectedMode === "presenton" ? "Presenton" : "Image Model"}
Continue to providers
</button>
</div>
</div>

View file

@ -353,7 +353,7 @@ const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep:
<h3 className="text-xl font-normal text-[#191919] pb-1.5">Text Generation Settings</h3>
<p className=" text-sm text-gray-500">
Choosing where text contets come from
Choosing where text content comes from
</p>
</div>
</div>

View file

@ -297,20 +297,7 @@ export default function CodexConfig({
<p className="text-sm font-medium text-gray-800 truncate">
{username || email || (accountId ? `Account ${accountId}` : "ChatGPT Account")}
</p>
<span
className={cn(
"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium shrink-0",
isPro === true ? "bg-amber-100 text-amber-800" : "bg-gray-100 text-gray-700"
)}
title={`Subscription: ${planLabel}`}
>
{isPro === true ? (
<Crown className="w-3 h-3" />
) : (
<User className="w-3 h-3" />
)}
{planLabel}
</span>
</div>
{email && username && (
<p className="text-xs text-gray-500 truncate">{email}</p>

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-columns3-cog-icon lucide-columns-3-cog"><path d="M10.5 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v5.5"/><path d="m14.3 19.6 1-.4"/><path d="M15 3v7.5"/><path d="m15.2 16.9-.9-.3"/><path d="m16.6 21.7.3-.9"/><path d="m16.8 15.3-.4-1"/><path d="m19.1 15.2.3-.9"/><path d="m19.6 21.7-.4-1"/><path d="m20.7 16.8 1-.4"/><path d="m21.7 19.4-.9-.3"/><path d="M9 3v18"/><circle cx="18" cy="18" r="3"/></svg>

After

Width:  |  Height:  |  Size: 610 B

View file

@ -130,7 +130,7 @@ export const LLM_PROVIDERS: Record<string, LLMProviderOption> = {
value: "custom",
label: "Custom",
description: "OpenAI-compatible LLM",
icon: "/icons/custom.png",
icon: "/providers/custom.svg",
},
};

View file

@ -224,7 +224,7 @@ const TextProvider = ({
</div>
<h3 className="text-xl font-normal text-[#191919] py-2.5">Text Generation Settings</h3>
<p className=" text-sm text-gray-500">
Choosing where text contets come from
Choosing where text content comes from
</p>
</div>
<div>

View file

@ -341,7 +341,7 @@ const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep:
<h3 className="text-xl font-normal text-[#191919] pb-1.5">Text Generation Settings</h3>
<p className=" text-sm text-gray-500">
Choosing where text contets come from
Choosing where text content comes from
</p>
</div>
</div>