Skip to content

Commit

Permalink
ready
Browse files Browse the repository at this point in the history
  • Loading branch information
abhishekg999 committed Aug 26, 2024
1 parent 188ce61 commit 6bc6494
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 101 deletions.
2 changes: 1 addition & 1 deletion secret-worker/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ app.use(logger())

const api = new Hono<Context>();

api.get(`/secret/:id{${UUID_REGEX}}`, async (c) => {
api.post(`/secret/:id{${UUID_REGEX}}`, async (c) => {
const { id } = c.req.param();
const db = drizzle(c.env.DB);
const result = await db.delete(secrets).where(eq(secrets.id, id)).returning();
Expand Down
109 changes: 13 additions & 96 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,102 +1,19 @@
import { useState } from 'react';
import { Copy, Link, AlertCircle } from 'lucide-react';
import { useEffect } from 'react';
import Home from './components/Home';
import View from './components/View';
import { isValidUUID } from './lib/utils';

const OneTimeSecret = () => {
const [secret, setSecret] = useState('');
const [generatedLink, setGeneratedLink] = useState('');
const [inputEnabled, setInputEnabled] = useState(true);
const [error, setError] = useState('');

const handleCreateLink = () => {
const data = secret.trim();
if (!data.trim()) {
setError('Please enter a secret to create a link.');
return;
}

setInputEnabled(false);

const fetchData = async () => {
const response = await fetch('/api/secret/new', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ data: data.trim() }),
});

if (response.ok) {
const data = await response.json();
setGeneratedLink(`${window.location.origin}/${data.id}`);
} else {
setGeneratedLink('');
setError('An error occurred while creating the secret link. Please refresh and try again.');
}
};

fetchData();
const App = () => {
const hash = window.location.hash.substring(1);
if (!isValidUUID(hash) && window.location.pathname !== '/') {
window.location.replace('/');
}

const handleCopyLink = () => {
navigator.clipboard.writeText(generatedLink);
};

return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 to-gray-800 flex items-center justify-center p-4">
<div className="bg-gray-800 rounded-lg shadow-xl p-6 w-full max-w-md border border-gray-700">
<h1 className="text-2xl font-bold text-gray-100 mb-4 text-center">Create One-Time Secret</h1>
<textarea
className="w-full h-32 p-2 border border-gray-600 rounded-md focus:ring-2 focus:ring-purple-500 focus:border-transparent resize-none bg-gray-700 text-gray-100 disabled:opacity-50 disabled:cursor-not-allowed"
placeholder="Enter your secret here..."
value={secret}
onChange={(e) => setSecret(e.target.value)}
disabled={!inputEnabled}
maxLength={10000}
/>
<button
className={`mt-4 w-full bg-purple-600 text-white font-bold py-2 px-4 rounded-md transition duration-300 ease-in-out flex items-center justify-center ${!generatedLink
? 'hover:bg-purple-700 hover:shadow-lg transform hover:-translate-y-0.5'
: 'opacity-50 cursor-not-allowed'
}`}
onClick={handleCreateLink}
disabled={!inputEnabled}
>
<Link className="mr-2" size={18} />
Create Secret Link
</button>

{generatedLink && (
<div className="mt-4">
<p className="text-sm text-gray-400 mb-2">Your secret link:</p>
<div className="flex items-center bg-gray-700 p-2 rounded-md">
<input
type="text"
readOnly
value={generatedLink}
className="flex-grow bg-transparent text-sm text-gray-300 focus:outline-none"
/>
<button
onClick={handleCopyLink}
className="ml-2 text-purple-400 hover:text-purple-300 transition duration-300 ease-in-out transform hover:scale-110"
title="Copy to clipboard"
>
<Copy size={18} />
</button>
</div>
</div>
)}

{error && (
<div className="mt-4 bg-red-900 border border-red-700 text-red-100 px-4 py-3 rounded relative" role="alert">
<div className="flex items-center">
<AlertCircle className="mr-2" size={18} />
<span className="block sm:inline pl-2 pr-2">{error}</span>
</div>
</div>
)}
</div>
</div>
<>
{isValidUUID(hash) ? <View hash={hash} /> : <Home />}
</>
);
};

export default OneTimeSecret;
}
export default App;
102 changes: 102 additions & 0 deletions src/components/Home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { useState } from 'react';
import { Copy, Link, AlertCircle } from 'lucide-react';

const Home = () => {
const [secret, setSecret] = useState('');
const [generatedLink, setGeneratedLink] = useState('');
const [inputEnabled, setInputEnabled] = useState(true);
const [error, setError] = useState('');

const handleCreateLink = () => {
const data = secret.trim();
if (!data.trim()) {
setError('Please enter a secret to create a link.');
return;
}

setInputEnabled(false);

const fetchData = async () => {
const response = await fetch('/api/secret/new', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ data: data.trim() }),
});

if (response.ok) {
const data = await response.json();
setGeneratedLink(`${window.location.origin}/#${data.id}`);
} else {
setGeneratedLink('');
setError('An error occurred while creating the secret link. Please refresh and try again.');
}
};

fetchData();
}

const handleCopyLink = () => {
navigator.clipboard.writeText(generatedLink);
};

return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 to-gray-800 flex items-center justify-center p-4">
<div className="bg-gray-800 rounded-lg shadow-xl p-6 w-full max-w-md border border-gray-700">
<h1 className="text-2xl font-bold text-gray-100 mb-4 text-center">Create One-Time Secret</h1>
<textarea
className="w-full h-32 p-2 border border-gray-600 rounded-md focus:ring-2 focus:ring-purple-500 focus:border-transparent resize-none bg-gray-700 text-gray-100 disabled:opacity-50 disabled:cursor-not-allowed"
placeholder="Enter your secret here..."
value={secret}
onChange={(e) => setSecret(e.target.value)}
disabled={!inputEnabled}
maxLength={10000}
/>
<button
className={`mt-4 w-full bg-purple-600 text-white font-bold py-2 px-4 rounded-md transition duration-300 ease-in-out flex items-center justify-center ${inputEnabled
? 'hover:bg-purple-700 hover:shadow-lg transform hover:-translate-y-0.5'
: 'opacity-50 cursor-not-allowed'
}`}
onClick={handleCreateLink}
disabled={!inputEnabled}
>
<Link className="mr-2" size={18} />
Create Secret Link
</button>

{generatedLink && (
<div className="mt-4">
<p className="text-sm text-gray-400 mb-2">Your secret link:</p>
<div className="flex items-center bg-gray-700 p-2 rounded-md">
<input
type="text"
readOnly
value={generatedLink}
className="flex-grow bg-transparent text-sm text-gray-300 focus:outline-none"
/>
<button
onClick={handleCopyLink}
className="ml-2 text-purple-400 hover:text-purple-300 transition duration-300 ease-in-out transform hover:scale-110"
title="Copy to clipboard"
>
<Copy size={18} />
</button>
</div>
</div>
)}

{error && (
<div className="mt-4 bg-red-900 border border-red-700 text-red-100 px-4 py-3 rounded relative" role="alert">
<div className="flex items-center">
<AlertCircle className="mr-2" size={18} />
<span className="block sm:inline pl-2 pr-2">{error}</span>
</div>
</div>
)}
</div>
</div>
);
};

export default Home;
73 changes: 73 additions & 0 deletions src/components/View.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { useState } from "react";
import { Eye, AlertCircle } from "lucide-react";

type ViewProps = {
hash: string;
};

const View = ({ hash }: ViewProps) => {
const [secret, setSecret] = useState("");
const [error, setError] = useState("");
const [isViewing, setIsViewing] = useState(false);

const fetchSecret = async () => {
const response = await fetch(`/api/secret/${hash}`, {
method: 'POST',
cache: "no-store",
});
if (response.ok) {
const json: { data: string } = await response.json();
setSecret(json.data);
} else {
setError("Secret not found or has been deleted.");
}
};

const handleViewSecret = () => {
setIsViewing(true);
fetchSecret();
};

return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 to-gray-800 flex items-center justify-center p-4">
<div className="bg-gray-800 rounded-lg shadow-xl p-6 w-full max-w-md border border-gray-700">
{!isViewing ? (
<div className="text-center flex flex-col">
<p className="text-gray-300 mb-4">
Viewing this secret will delete it.
</p>
<button
className="bg-purple-600 text-white font-bold py-2 px-4 rounded-md transition duration-300 ease-in-out flex items-center justify-center hover:bg-purple-700 hover:shadow-lg transform hover:-translate-y-0.5"
onClick={handleViewSecret}
>
<Eye className="mr-2" size={18} />
View Secret
</button>
</div>
) : (
<div>
{secret ? (
<>
<h2 className="text-xl text-gray-100 font-bold mb-2">Your Secret:</h2>
<div className="bg-gray-700 p-4 rounded-md">
<p className="text-gray-300">{secret}</p>
</div>
</>
) : error ? (
<div className="mt-4 bg-red-900 border border-red-700 text-red-100 px-4 py-3 rounded relative" role="alert">
<div className="flex items-center">
<AlertCircle className="mr-2" size={18} />
<span className="block sm:inline pl-2 pr-2">{error}</span>
</div>
</div>
) : (
<p className="text-gray-300">Loading...</p>
)}
</div>
)}
</div>
</div>
);
};

export default View;
4 changes: 4 additions & 0 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

export function isValidUUID(uuid: string) {
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(uuid);
}
5 changes: 1 addition & 4 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.tsx'
import './index.css'

createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
<App />
)
8 changes: 8 additions & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,13 @@ export default defineConfig({
"@": path.resolve(__dirname, "./src"),
},
},
server: {
proxy: {
'/api': {
target: 'http://localhost:8787',
changeOrigin: true
}
}
}
})

0 comments on commit 6bc6494

Please sign in to comment.