feat: add ability to shorten url
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useAppDispatch, useAppSelector } from '../app/hooks';
|
||||
import {
|
||||
shortenUrl,
|
||||
selectShortUrl,
|
||||
selectUrlStatus,
|
||||
selectUrlError,
|
||||
clearShortUrl,
|
||||
} from '../features/url/urlSlice';
|
||||
|
||||
const Home: React.FC = () => {
|
||||
// Component-level state
|
||||
const [longUrl, setLongUrl] = useState('');
|
||||
|
||||
// Redux state
|
||||
const dispatch = useAppDispatch();
|
||||
const shortUrl = useAppSelector(selectShortUrl);
|
||||
const status = useAppSelector(selectUrlStatus);
|
||||
const error = useAppSelector(selectUrlError);
|
||||
|
||||
// Methods
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter' && longUrl.trim() !== '') {
|
||||
dispatch(shortenUrl(longUrl));
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopy = () => {
|
||||
if (shortUrl) {
|
||||
navigator.clipboard.writeText(shortUrl);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-purple-100 px-4">
|
||||
<div className="text-center space-y-6 max-w-md w-full">
|
||||
<h1 className="text-5xl text-orange-500 font-pacifico">minxa.lol</h1>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter your long URL here"
|
||||
value={longUrl}
|
||||
onChange={(e) => setLongUrl(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="w-full p-3 rounded-md text-lg border border-gray-300 shadow-sm focus:outline-none focus:ring-2 focus:ring-orange-400"
|
||||
/>
|
||||
|
||||
{/* Loading State */}
|
||||
{status === 'loading' && (
|
||||
<p className="text-gray-600 text-sm">Shortening your URL...</p>
|
||||
)}
|
||||
|
||||
{/* Error Message */}
|
||||
{status === 'failed' && error && (
|
||||
<p className="text-red-600 text-sm">{error}</p>
|
||||
)}
|
||||
|
||||
{/* Short URL Display */}
|
||||
{status === 'succeeded' && shortUrl && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-green-700 font-medium text-lg">
|
||||
Your short URL:
|
||||
<a
|
||||
href={shortUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline ml-2"
|
||||
>
|
||||
{shortUrl}
|
||||
</a>
|
||||
</p>
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className="bg-orange-500 text-white px-4 py-2 rounded hover:bg-orange-600 transition"
|
||||
>
|
||||
Copy to Clipboard
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"short_name": "minxa",
|
||||
"short_name": "minxa.lol",
|
||||
"name": "minxa.lol - URL Shortener",
|
||||
"description": "A fun and minimalist URL shortener",
|
||||
"icons": [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useAppDispatch, useAppSelector } from '../app/hooks';
|
||||
import { copyToClipboard } from '../utils/clipboard';
|
||||
import {
|
||||
shortenUrl,
|
||||
selectShortUrl,
|
||||
@@ -26,21 +27,63 @@ const Home: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopy = () => {
|
||||
if (shortUrl) {
|
||||
copyToClipboard(shortUrl)
|
||||
.then(() => console.log('Copied!'))
|
||||
.catch((err) => console.error('Copy failed', err));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-purple-100">
|
||||
<div className="text-center space-y-6">
|
||||
<h1 className="text-5xl text-orange-500 font-pacifico">minxa.lol</h1>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter your long URL here"
|
||||
value={longUrl}
|
||||
onChange={(e) => setLongUrl(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="w-80 p-3 rounded-md text-lg border border-gray-300 shadow-sm focus:outline-none focus:ring-2 focus:ring-orange-400"
|
||||
type="text"
|
||||
placeholder="Enter your long URL here"
|
||||
value={longUrl}
|
||||
onChange={(e) => setLongUrl(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="w-80 p-3 rounded-md text-lg border border-gray-300 shadow-sm focus:outline-none focus:ring-2 focus:ring-orange-400"
|
||||
/>
|
||||
|
||||
{/* Loading State */}
|
||||
{status === 'loading' && (
|
||||
<p className="text-gray-600 text-sm">Shortening your URL...</p>
|
||||
)}
|
||||
|
||||
{/* Error Message */}
|
||||
{status === 'failed' && (
|
||||
<p className="text-red-600 text-sm">{error}</p>
|
||||
)}
|
||||
|
||||
{/* Short URL Display */}
|
||||
{status === 'succeeded' && shortUrl && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-green-700 font-medium text-lg">
|
||||
Your short URL:
|
||||
<a
|
||||
href={shortUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline ml-2"
|
||||
>
|
||||
{shortUrl}
|
||||
</a>
|
||||
</p>
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className="bg-orange-500 text-white px-4 py-2 rounded hover:bg-orange-600 transition">
|
||||
Copy to Clipboard
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
export default Home;
|
||||
|
||||
|
||||
36
src/utils/clipboard.ts
Normal file
36
src/utils/clipboard.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
export function copyToClipboard(text: string): Promise<void> {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
// ✅ Modern way
|
||||
return navigator.clipboard.writeText(text);
|
||||
} else {
|
||||
// 🚨 Fallback for insecure context or unsupported browsers
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = text;
|
||||
|
||||
// Avoid scrolling to bottom
|
||||
textArea.style.position = 'fixed';
|
||||
textArea.style.top = '0';
|
||||
textArea.style.left = '0';
|
||||
textArea.style.width = '2em';
|
||||
textArea.style.height = '2em';
|
||||
textArea.style.padding = '0';
|
||||
textArea.style.border = 'none';
|
||||
textArea.style.outline = 'none';
|
||||
textArea.style.boxShadow = 'none';
|
||||
textArea.style.background = 'transparent';
|
||||
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
} catch (err) {
|
||||
console.error('Fallback: Oops, unable to copy', err);
|
||||
}
|
||||
|
||||
document.body.removeChild(textArea);
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user