feat: Initialize frontend with React, Vite, and Tailwind CSS
- Set up main entry point for React application. - Create About, Home, NotFound, Privacy, and Terms pages with SEO support. - Implement API service for file uploads and task management. - Add global styles using Tailwind CSS. - Create utility functions for SEO and text processing. - Configure Vite for development and production builds. - Set up Nginx configuration for serving frontend and backend. - Add scripts for cleanup of expired files and sitemap generation. - Implement deployment script for production environment.
This commit is contained in:
49
frontend/src/pages/AboutPage.tsx
Normal file
49
frontend/src/pages/AboutPage.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
|
||||
export default function AboutPage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{t('common.about')} — {t('common.appName')}</title>
|
||||
<meta name="description" content="About our free online file conversion tools." />
|
||||
</Helmet>
|
||||
|
||||
<div className="prose mx-auto max-w-2xl dark:prose-invert">
|
||||
<h1>{t('common.about')}</h1>
|
||||
|
||||
<p>
|
||||
We provide free, fast, and secure online tools for converting, compressing,
|
||||
and processing files — PDFs, images, videos, and text.
|
||||
</p>
|
||||
|
||||
<h2>Why use our tools?</h2>
|
||||
<ul>
|
||||
<li><strong>100% Free</strong> — No hidden charges, no sign-up required.</li>
|
||||
<li><strong>Private & Secure</strong> — Files are auto-deleted within 2 hours.</li>
|
||||
<li><strong>Fast Processing</strong> — Server-side processing for reliable results.</li>
|
||||
<li><strong>Works Everywhere</strong> — Desktop, tablet, or mobile.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Available Tools</h2>
|
||||
<ul>
|
||||
<li>PDF to Word Converter</li>
|
||||
<li>Word to PDF Converter</li>
|
||||
<li>PDF Compressor</li>
|
||||
<li>Image Format Converter</li>
|
||||
<li>Video to GIF Creator</li>
|
||||
<li>Word Counter</li>
|
||||
<li>Text Cleaner & Formatter</li>
|
||||
</ul>
|
||||
|
||||
<h2>Contact</h2>
|
||||
<p>
|
||||
Have feedback or feature requests? Reach out at{' '}
|
||||
<a href="mailto:support@example.com">support@example.com</a>.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
93
frontend/src/pages/HomePage.tsx
Normal file
93
frontend/src/pages/HomePage.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
import {
|
||||
FileText,
|
||||
FileOutput,
|
||||
Minimize2,
|
||||
ImageIcon,
|
||||
Film,
|
||||
Hash,
|
||||
Eraser,
|
||||
} from 'lucide-react';
|
||||
import ToolCard from '@/components/shared/ToolCard';
|
||||
import AdSlot from '@/components/layout/AdSlot';
|
||||
|
||||
interface ToolInfo {
|
||||
key: string;
|
||||
path: string;
|
||||
icon: React.ReactNode;
|
||||
bgColor: string;
|
||||
}
|
||||
|
||||
const tools: ToolInfo[] = [
|
||||
{ key: 'pdfToWord', path: '/tools/pdf-to-word', icon: <FileText className="h-6 w-6 text-red-600" />, bgColor: 'bg-red-50' },
|
||||
{ key: 'wordToPdf', path: '/tools/word-to-pdf', icon: <FileOutput className="h-6 w-6 text-blue-600" />, bgColor: 'bg-blue-50' },
|
||||
{ key: 'compressPdf', path: '/tools/compress-pdf', icon: <Minimize2 className="h-6 w-6 text-orange-600" />, bgColor: 'bg-orange-50' },
|
||||
{ key: 'imageConvert', path: '/tools/image-converter', icon: <ImageIcon className="h-6 w-6 text-purple-600" />, bgColor: 'bg-purple-50' },
|
||||
{ key: 'videoToGif', path: '/tools/video-to-gif', icon: <Film className="h-6 w-6 text-emerald-600" />, bgColor: 'bg-emerald-50' },
|
||||
{ key: 'wordCounter', path: '/tools/word-counter', icon: <Hash className="h-6 w-6 text-blue-600" />, bgColor: 'bg-blue-50' },
|
||||
{ key: 'textCleaner', path: '/tools/text-cleaner', icon: <Eraser className="h-6 w-6 text-indigo-600" />, bgColor: 'bg-indigo-50' },
|
||||
];
|
||||
|
||||
export default function HomePage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{t('common.appName')} — {t('home.heroSub')}</title>
|
||||
<meta name="description" content={t('home.heroSub')} />
|
||||
<link rel="canonical" href={window.location.origin} />
|
||||
<script type="application/ld+json">
|
||||
{JSON.stringify({
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'WebSite',
|
||||
name: t('common.appName'),
|
||||
url: window.location.origin,
|
||||
description: t('home.heroSub'),
|
||||
potentialAction: {
|
||||
'@type': 'SearchAction',
|
||||
target: `${window.location.origin}/tools/{search_term_string}`,
|
||||
'query-input': 'required name=search_term_string',
|
||||
},
|
||||
})}
|
||||
</script>
|
||||
</Helmet>
|
||||
|
||||
{/* Hero Section */}
|
||||
<section className="py-12 text-center sm:py-16">
|
||||
<h1 className="text-4xl font-bold tracking-tight text-slate-900 sm:text-5xl">
|
||||
{t('home.hero')}
|
||||
</h1>
|
||||
<p className="mx-auto mt-4 max-w-xl text-lg text-slate-500">
|
||||
{t('home.heroSub')}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Ad Slot */}
|
||||
<AdSlot slot="home-top" format="horizontal" className="mb-8" />
|
||||
|
||||
{/* Tools Grid */}
|
||||
<section>
|
||||
<h2 className="mb-6 text-center text-xl font-semibold text-slate-800">
|
||||
{t('home.popularTools')}
|
||||
</h2>
|
||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{tools.map((tool) => (
|
||||
<ToolCard
|
||||
key={tool.key}
|
||||
to={tool.path}
|
||||
icon={tool.icon}
|
||||
title={t(`tools.${tool.key}.title`)}
|
||||
description={t(`tools.${tool.key}.shortDesc`)}
|
||||
bgColor={tool.bgColor}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Ad Slot - Bottom */}
|
||||
<AdSlot slot="home-bottom" className="mt-12" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
34
frontend/src/pages/NotFoundPage.tsx
Normal file
34
frontend/src/pages/NotFoundPage.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
import { Home } from 'lucide-react';
|
||||
|
||||
export default function NotFoundPage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>404 — {t('common.appName')}</title>
|
||||
<meta name="robots" content="noindex" />
|
||||
</Helmet>
|
||||
|
||||
<div className="flex flex-col items-center justify-center py-24 text-center">
|
||||
<p className="text-7xl font-bold text-primary-600">404</p>
|
||||
<h1 className="mt-4 text-2xl font-semibold text-slate-900">
|
||||
Page Not Found
|
||||
</h1>
|
||||
<p className="mt-2 text-slate-500">
|
||||
The page you're looking for doesn't exist or has been moved.
|
||||
</p>
|
||||
<Link
|
||||
to="/"
|
||||
className="btn-primary mt-8 inline-flex items-center gap-2"
|
||||
>
|
||||
<Home className="h-4 w-4" />
|
||||
{t('common.home')}
|
||||
</Link>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
59
frontend/src/pages/PrivacyPage.tsx
Normal file
59
frontend/src/pages/PrivacyPage.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
|
||||
export default function PrivacyPage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{t('common.privacy')} — {t('common.appName')}</title>
|
||||
<meta name="description" content="Privacy policy for our online tools." />
|
||||
</Helmet>
|
||||
|
||||
<div className="prose mx-auto max-w-2xl dark:prose-invert">
|
||||
<h1>{t('common.privacy')}</h1>
|
||||
<p><em>Last updated: {new Date().toISOString().split('T')[0]}</em></p>
|
||||
|
||||
<h2>1. Data Collection</h2>
|
||||
<p>
|
||||
We only collect files you intentionally upload for processing. We do not
|
||||
require registration, and we do not store personal information.
|
||||
</p>
|
||||
|
||||
<h2>2. File Processing & Storage</h2>
|
||||
<ul>
|
||||
<li>Uploaded files are processed on our secure servers.</li>
|
||||
<li>All uploaded and output files are <strong>automatically deleted within 2 hours</strong>.</li>
|
||||
<li>Files are stored in encrypted cloud storage during processing.</li>
|
||||
<li>We do not access, read, or share the content of your files.</li>
|
||||
</ul>
|
||||
|
||||
<h2>3. Cookies & Analytics</h2>
|
||||
<p>
|
||||
We use essential cookies to remember your language preference. We may use
|
||||
Google Analytics and Google AdSense, which may place their own cookies.
|
||||
You can manage cookie preferences in your browser settings.
|
||||
</p>
|
||||
|
||||
<h2>4. Third-Party Services</h2>
|
||||
<ul>
|
||||
<li><strong>Google AdSense</strong> — for displaying advertisements.</li>
|
||||
<li><strong>AWS S3</strong> — for temporary file storage.</li>
|
||||
</ul>
|
||||
|
||||
<h2>5. Security</h2>
|
||||
<p>
|
||||
We employ industry-standard security measures including HTTPS encryption,
|
||||
file validation, rate limiting, and automatic file cleanup.
|
||||
</p>
|
||||
|
||||
<h2>6. Contact</h2>
|
||||
<p>
|
||||
Questions about this policy? Contact us at{' '}
|
||||
<a href="mailto:support@example.com">support@example.com</a>.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
66
frontend/src/pages/TermsPage.tsx
Normal file
66
frontend/src/pages/TermsPage.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
|
||||
export default function TermsPage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{t('common.terms')} — {t('common.appName')}</title>
|
||||
<meta name="description" content="Terms of service for our online tools." />
|
||||
</Helmet>
|
||||
|
||||
<div className="prose mx-auto max-w-2xl dark:prose-invert">
|
||||
<h1>{t('common.terms')}</h1>
|
||||
<p><em>Last updated: {new Date().toISOString().split('T')[0]}</em></p>
|
||||
|
||||
<h2>1. Acceptance of Terms</h2>
|
||||
<p>
|
||||
By accessing and using SaaS-PDF, you agree to be bound by these Terms of
|
||||
Service. If you do not agree, please discontinue use immediately.
|
||||
</p>
|
||||
|
||||
<h2>2. Service Description</h2>
|
||||
<p>
|
||||
SaaS-PDF provides free online tools for file conversion, compression,
|
||||
and transformation. The service is provided “as is” without
|
||||
warranties of any kind.
|
||||
</p>
|
||||
|
||||
<h2>3. Acceptable Use</h2>
|
||||
<ul>
|
||||
<li>You may only upload files that you have the right to process.</li>
|
||||
<li>You must not upload malicious, illegal, or copyrighted content without authorization.</li>
|
||||
<li>Automated or excessive use of the service is prohibited.</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. File Handling</h2>
|
||||
<ul>
|
||||
<li>All uploaded and processed files are automatically deleted within 2 hours.</li>
|
||||
<li>We are not responsible for any data loss during processing.</li>
|
||||
<li>You are responsible for maintaining your own file backups.</li>
|
||||
</ul>
|
||||
|
||||
<h2>5. Limitation of Liability</h2>
|
||||
<p>
|
||||
SaaS-PDF shall not be liable for any direct, indirect, incidental, or
|
||||
consequential damages resulting from the use or inability to use the
|
||||
service.
|
||||
</p>
|
||||
|
||||
<h2>6. Changes to Terms</h2>
|
||||
<p>
|
||||
We reserve the right to modify these terms at any time. Continued use of
|
||||
the service after changes constitutes acceptance of the updated terms.
|
||||
</p>
|
||||
|
||||
<h2>7. Contact</h2>
|
||||
<p>
|
||||
Questions about these terms? Contact us at{' '}
|
||||
<a href="mailto:support@example.com">support@example.com</a>.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user