Skip to content

Commit 7c9f818

Browse files
committed
feat: new apps
1 parent bde741c commit 7c9f818

File tree

1 file changed

+165
-0
lines changed

1 file changed

+165
-0
lines changed
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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 HashGeneratorPage() {
9+
usePageTitle('Hash Generator');
10+
const [inputText, setInputText] = useState('');
11+
const [hashes, setHashes] = useState({
12+
sha1: '',
13+
sha256: '',
14+
sha512: '',
15+
});
16+
const { addToast } = useToast();
17+
18+
const generateHash = async (algorithm) => {
19+
if (!inputText) {
20+
setHashes(prev => ({ ...prev, [algorithm.toLowerCase().replace('sha-','sha')]: '' }));
21+
return;
22+
}
23+
24+
try {
25+
const textEncoder = new TextEncoder();
26+
const data = textEncoder.encode(inputText);
27+
const hashBuffer = await crypto.subtle.digest(algorithm, data);
28+
const hashArray = Array.from(new Uint8Array(hashBuffer));
29+
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
30+
setHashes(prev => ({ ...prev, [algorithm.toLowerCase().replace('sha-','sha')]: hashHex }));
31+
} catch (error) {
32+
addToast({
33+
title: 'Error',
34+
message: `Failed to generate ${algorithm} hash.`,
35+
duration: 3000,
36+
});
37+
setHashes(prev => ({ ...prev, [algorithm.toLowerCase().replace('sha-','sha')]: 'Error' }));
38+
}
39+
};
40+
41+
const generateAllHashes = () => {
42+
generateHash('SHA-1');
43+
generateHash('SHA-256');
44+
generateHash('SHA-512');
45+
};
46+
47+
const copyToClipboard = (text) => {
48+
navigator.clipboard.writeText(text)
49+
.then(() => {
50+
addToast({
51+
title: 'Success',
52+
message: 'Copied to clipboard!',
53+
duration: 2000,
54+
});
55+
})
56+
.catch(() => {
57+
addToast({
58+
title: 'Error',
59+
message: 'Failed to copy!',
60+
duration: 2000,
61+
});
62+
});
63+
};
64+
65+
const cardStyle = {
66+
backgroundColor: colors['app-alpha-10'],
67+
borderColor: colors['app-alpha-50'],
68+
color: colors.app,
69+
};
70+
71+
const detailTextColor = colors['app-light'];
72+
73+
return (
74+
<div className="py-16 sm:py-24">
75+
<div className="mx-auto max-w-7xl px-6 lg:px-8 text-gray-300">
76+
<Link
77+
to="/apps"
78+
className="text-article hover:underline flex items-center justify-center gap-2 text-lg mb-4"
79+
>
80+
<ArrowLeftIcon size={24} /> Back to Apps
81+
</Link>
82+
<h1 className="text-4xl font-bold tracking-tight sm:text-6xl mb-4 flex items-center">
83+
<span className="codex-color">fc</span>
84+
<span className="separator-color">::</span>
85+
<span className="apps-color">apps</span>
86+
<span className="separator-color">::</span>
87+
<span className="single-app-color">hash</span>
88+
</h1>
89+
<hr className="border-gray-700" />
90+
<div className="flex justify-center items-center mt-16">
91+
<div
92+
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"
93+
style={cardStyle}
94+
>
95+
<div
96+
className="absolute top-0 left-0 w-full h-full opacity-10"
97+
style={{
98+
backgroundImage:
99+
'radial-gradient(circle, white 1px, transparent 1px)',
100+
backgroundSize: '10px 10px',
101+
}}
102+
></div>
103+
<div className="relative z-10 p-1">
104+
<div className="mb-4">
105+
<label className="block text-lg font-semibold mb-2" style={{ color: cardStyle.color }}>Input Text</label>
106+
<textarea
107+
className="w-full h-32 p-4 bg-gray-900/50 font-mono resize-y border rounded-md focus:ring-0"
108+
style={{ borderColor: cardStyle.borderColor, color: detailTextColor }}
109+
value={inputText}
110+
onChange={(e) => setInputText(e.target.value)}
111+
placeholder="Enter text to hash..."
112+
/>
113+
</div>
114+
<div className="flex justify-center gap-4 mb-4">
115+
<button
116+
onClick={generateAllHashes}
117+
className="px-6 py-2 rounded-md text-lg font-arvo font-normal transition-colors duration-300 ease-in-out"
118+
style={{
119+
backgroundColor: 'rgba(0, 0, 0, 0.2)',
120+
color: cardStyle.color,
121+
borderColor: cardStyle.borderColor,
122+
border: '1px solid',
123+
'--hover-bg-color': colors['app-alpha-50'],
124+
}}
125+
onMouseEnter={(e) => e.currentTarget.style.backgroundColor = 'var(--hover-bg-color)'}
126+
onMouseLeave={(e) => e.currentTarget.style.backgroundColor = 'rgba(0, 0, 0, 0.2)'}
127+
>
128+
Generate All Hashes
129+
</button>
130+
</div>
131+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
132+
<HashOutput title="SHA-1" value={hashes.sha1} onCopy={copyToClipboard} detailTextColor={detailTextColor} />
133+
<HashOutput title="SHA-256" value={hashes.sha256} onCopy={copyToClipboard} detailTextColor={detailTextColor} />
134+
<HashOutput title="SHA-512" value={hashes.sha512} onCopy={copyToClipboard} detailTextColor={detailTextColor} />
135+
</div>
136+
</div>
137+
</div>
138+
</div>
139+
</div>
140+
</div>
141+
);
142+
}
143+
144+
const HashOutput = ({ title, value, onCopy, detailTextColor }) => (
145+
<div className="flex flex-col">
146+
<label className="text-sm font-medium text-gray-400 mb-1">{title}</label>
147+
<div className="relative">
148+
<textarea
149+
readOnly
150+
value={value}
151+
className="w-full p-2 border rounded-md bg-gray-800/50 font-mono text-sm resize-none"
152+
style={{ borderColor: colors['app-alpha-50'], color: detailTextColor }}
153+
rows="3"
154+
/>
155+
<button
156+
onClick={() => onCopy(value)}
157+
className="absolute top-2 right-2 px-2 py-1 bg-gray-700 text-white text-xs rounded hover:bg-gray-600"
158+
>
159+
Copy
160+
</button>
161+
</div>
162+
</div>
163+
);
164+
165+
export default HashGeneratorPage;

0 commit comments

Comments
 (0)