Skip to content

Commit bf20a19

Browse files
committed
feat: new app, case converter
1 parent ed4eef4 commit bf20a19

File tree

4 files changed

+136
-1
lines changed

4 files changed

+136
-1
lines changed

src/components/AnimatedRoutes.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import AppPage from '../pages/AppPage';
2020
import IpPage from '../pages/apps/IpPage';
2121
import WordCounterPage from '../pages/apps/WordCounterPage';
2222
import TournamentBracketPage from '../pages/apps/TournamentBracketPage';
23+
import CaseConverterPage from '../pages/apps/CaseConverterPage';
2324

2425
import UsefulLinksPage from '../pages/UsefulLinksPage';
2526

@@ -330,6 +331,20 @@ function AnimatedRoutes() {
330331
</motion.div>
331332
}
332333
/>
334+
<Route
335+
path="/apps/case-converter"
336+
element={
337+
<motion.div
338+
initial="initial"
339+
animate="in"
340+
exit="out"
341+
variants={pageVariants}
342+
transition={pageTransition}
343+
>
344+
<CaseConverterPage />
345+
</motion.div>
346+
}
347+
/>
333348
{/* D&D specific 404 page */}
334349
<Route
335350
path="/dnd/*"

src/pages/AppPage.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ const apps = [
1515
title: 'Word Counter',
1616
description: 'Count words, characters, lines and paragraphs in a text.',
1717
},
18+
{
19+
to: '/apps/case-converter',
20+
title: 'Case Converter',
21+
description: 'Convert text to different cases (e.g., uppercase, lowercase, camelCase).',
22+
},
1823
// {
1924
// to: '/apps/ip',
2025
// title: 'Show my IP',
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import React, { useState } from 'react';
2+
import { Link } from 'react-router-dom';
3+
import { ArrowLeftIcon } from '@phosphor-icons/react';
4+
import colors from '../../config/colors';
5+
import usePageTitle from '../../utils/usePageTitle';
6+
import { useToast } from '../../hooks/useToast';
7+
8+
function CaseConverterPage() {
9+
usePageTitle('Case Converter');
10+
const [inputText, setInputText] = useState('');
11+
const { addToast } = useToast();
12+
13+
const convertToUpperCase = () => inputText.toUpperCase();
14+
const convertToLowerCase = () => inputText.toLowerCase();
15+
const convertToTitleCase = () => inputText.replace(/\b\w/g, char => char.toUpperCase());
16+
const convertToCamelCase = () => inputText.replace(/(?:^|\s)([a-zA-Z])/g, (_, char) => char.toUpperCase()).replace(/\s+/g, '');
17+
const convertToSnakeCase = () => inputText.toLowerCase().replace(/\s+/g, '_');
18+
const convertToKebabCase = () => inputText.toLowerCase().replace(/\s+/g, '-');
19+
20+
const copyToClipboard = (text) => {
21+
navigator.clipboard.writeText(text)
22+
.then(() => {
23+
addToast({
24+
title: 'Success',
25+
message: 'Copied to clipboard!',
26+
duration: 2000,
27+
});
28+
})
29+
.catch(() => {
30+
addToast({
31+
title: 'Error',
32+
message: 'Failed to copy!',
33+
duration: 2000,
34+
});
35+
});
36+
};
37+
38+
const cardStyle = {
39+
backgroundColor: colors['app-alpha-10'],
40+
borderColor: colors['app-alpha-50'],
41+
color: colors.app,
42+
};
43+
44+
const detailTextColor = colors['app-light'];
45+
46+
return (
47+
<div className="py-16 sm:py-24">
48+
<div className="mx-auto max-w-7xl px-6 lg:px-8 text-gray-300">
49+
<Link
50+
to="/apps"
51+
className="text-article hover:underline flex items-center justify-center gap-2 text-lg mb-4"
52+
>
53+
<ArrowLeftIcon size={24} /> Back to Apps
54+
</Link>
55+
<h1 className="text-4xl font-bold tracking-tight sm:text-6xl mb-4 flex items-center">
56+
<span className="codex-color">fc</span>
57+
<span className="separator-color">::</span>
58+
<span className="apps-color">apps</span>
59+
<span className="separator-color">::</span>
60+
<span className="single-app-color">cc</span>
61+
</h1>
62+
<hr className="border-gray-700" />
63+
<div className="flex justify-center items-center mt-16">
64+
<div
65+
className="group bg-transparent border rounded-lg shadow-2xl p-6 flex flex-col justify-between relative transform transition-all duration-300 ease-in-out scale-105 overflow-hidden h-full w-full max-w-4xl"
66+
style={cardStyle}
67+
>
68+
<div className="relative z-10 p-1">
69+
<div className="w-full h-48 resize-y overflow-auto border rounded-md" style={{ borderColor: cardStyle.borderColor }}>
70+
<textarea
71+
className="w-full h-full p-4 bg-gray-900/50 font-mono resize-none border-none focus:ring-0"
72+
style={{ color: detailTextColor }}
73+
value={inputText}
74+
onChange={(e) => setInputText(e.target.value)}
75+
placeholder="Enter text here..."
76+
/>
77+
</div>
78+
<div className="grid grid-cols-2 gap-4 mt-4">
79+
<CaseOutput title="Uppercase" value={convertToUpperCase()} onCopy={copyToClipboard} detailTextColor={detailTextColor} />
80+
<CaseOutput title="Lowercase" value={convertToLowerCase()} onCopy={copyToClipboard} detailTextColor={detailTextColor} />
81+
<CaseOutput title="Title Case" value={convertToTitleCase()} onCopy={copyToClipboard} detailTextColor={detailTextColor} />
82+
<CaseOutput title="Camel Case" value={convertToCamelCase()} onCopy={copyToClipboard} detailTextColor={detailTextColor} />
83+
<CaseOutput title="Snake Case" value={convertToSnakeCase()} onCopy={copyToClipboard} detailTextColor={detailTextColor} />
84+
<CaseOutput title="Kebab Case" value={convertToKebabCase()} onCopy={copyToClipboard} detailTextColor={detailTextColor} />
85+
</div>
86+
</div>
87+
</div>
88+
</div>
89+
</div>
90+
</div>
91+
);
92+
}
93+
94+
const CaseOutput = ({ title, value, onCopy, detailTextColor }) => (
95+
<div className="flex flex-col">
96+
<label className="text-sm font-medium text-gray-400 mb-1">{title}</label>
97+
<div className="relative">
98+
<textarea
99+
readOnly
100+
value={value}
101+
className="w-full p-2 border rounded-md bg-gray-800/50 font-mono text-sm resize-none"
102+
style={{ borderColor: colors['app-alpha-50'], color: detailTextColor }}
103+
rows="3"
104+
/>
105+
<button
106+
onClick={() => onCopy(value)}
107+
className="absolute top-2 right-2 px-2 py-1 bg-gray-700 text-white text-xs rounded hover:bg-gray-600"
108+
>
109+
Copy
110+
</button>
111+
</div>
112+
</div>
113+
);
114+
115+
export default CaseConverterPage;

src/pages/apps/TournamentBracketPage.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ function TournamentBracketPage() {
1515
color: colors.app,
1616
};
1717

18-
const detailTextColor = colors['app-light'];
18+
// const detailTextColor = colors['app-light'];
1919

2020
const buttonStyle = {
2121
borderColor: colors['app-alpha-50'],

0 commit comments

Comments
 (0)