From efc2861ab4f70fc1124111b879bdb239f8be131a Mon Sep 17 00:00:00 2001 From: Pc Date: Fri, 9 Jan 2026 22:40:43 +0100 Subject: [PATCH] fix: subdomain support, expiry date fix --- .../src/components/Generator.tsx | 195 +++++++++--------- 1 file changed, 102 insertions(+), 93 deletions(-) diff --git a/kittyurl-frontend/src/components/Generator.tsx b/kittyurl-frontend/src/components/Generator.tsx index 66abfb7..2cf64f2 100644 --- a/kittyurl-frontend/src/components/Generator.tsx +++ b/kittyurl-frontend/src/components/Generator.tsx @@ -1,19 +1,15 @@ import React, { useState, useEffect } from 'react'; import { PawPrint, Heart, Sparkles, Cat, Hash, - Globe, BookOpen, Shield, Calendar, + Globe, BookOpen, Shield, Clock, Settings2, AlertCircle, X, Save, RefreshCw, Copy, Check, ExternalLink, User as UserIcon } from 'lucide-react'; -// Pobieramy adres API const API_BASE = import.meta.env.VITE_API_TARGET; - -// Nazwa klucza w localStorage const TOKEN_KEY = 'jwt_token'; type CaseType = 'upper' | 'lower' | 'mixed'; -// 1. Definicja propsów, które przychodzą z App.tsx interface GeneratorProps { url: string; setUrl: (url: string) => void; @@ -27,7 +23,6 @@ interface GeneratorSettings { withSubdomain: boolean; } -// Usunęliśmy 'remoteUrl' stąd, bo teraz przychodzi z propsów (url) interface LinkFormData { uri: string; subdomain: string; @@ -40,7 +35,7 @@ interface LinkPayload { uri: string; subdomain?: string; privacy: boolean; - expiryDate: number; + expiryDate?: number; userId?: string; } @@ -50,16 +45,14 @@ interface User { email?: string; } -// 2. Dodajemy propsy do argumentów funkcji export const Generator: React.FC = ({ url, setUrl, onGenerate }) => { const [user, setUser] = useState(null); - // Stan formularza (bez remoteUrl, bo to jest teraz w 'url') const [formData, setFormData] = useState({ uri: '', subdomain: '', privacy: true, - expiryDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0] + expiryDate: '' }); const [genSettings, setGenSettings] = useState({ @@ -75,6 +68,12 @@ export const Generator: React.FC = ({ url, setUrl, onGenerate }) const [result, setResult] = useState(null); const [copied, setCopied] = useState(false); + // Obliczanie domeny bazowej i prefiksu do wyświetlenia w polu URI + const baseDomain = API_BASE.replace('api.', '').replace(/^https?:\/\//, '').split('/')[0]; + const displayPrefix = genSettings.withSubdomain && formData.subdomain + ? `${formData.subdomain}.${baseDomain}` + : baseDomain; + const getAuthHeaders = () => { const token = localStorage.getItem(TOKEN_KEY); return { @@ -83,17 +82,14 @@ export const Generator: React.FC = ({ url, setUrl, onGenerate }) }; }; - // Sprawdzenie sesji użytkownika useEffect(() => { const checkUser = async () => { const token = localStorage.getItem(TOKEN_KEY); if (!token) return; - try { const res = await fetch(`${API_BASE}/api/v1/user/account`, { headers: getAuthHeaders() }); - if (res.ok) { const data = await res.json(); setUser({ @@ -101,10 +97,6 @@ export const Generator: React.FC = ({ url, setUrl, onGenerate }) username: data.username || data.name || data.email || "User", email: data.email }); - } else { - console.log("Session expired. Logging out."); - localStorage.removeItem(TOKEN_KEY); - setUser(null); } } catch { console.log("API unreachable"); @@ -119,29 +111,21 @@ export const Generator: React.FC = ({ url, setUrl, onGenerate }) try { let endpoint = ''; const params = new URLSearchParams(); - params.append('withSubdomain', genSettings.withSubdomain.toString()); if (type === 'random') { endpoint = '/api/v1/link/short'; params.append('length', genSettings.length.toString()); params.append('alphanum', genSettings.alphanum.toString()); - - if (genSettings.case !== 'mixed') { - params.append('case', genSettings.case); - } + if (genSettings.case !== 'mixed') params.append('case', genSettings.case); } else { endpoint = '/api/v1/link/fromWordlist'; } const response = await fetch(`${API_BASE}${endpoint}?${params.toString()}`); const data = await response.json(); - if (!response.ok) throw new Error(data.error || 'Generation failed'); - - const generatedUri = data.uri || data.shortUrl || data.link || ""; - setFormData(prev => ({ ...prev, uri: generatedUri })); - + setFormData(prev => ({ ...prev, uri: data.uri || data.shortUrl || "" })); } catch (err: unknown) { if (err instanceof Error) setErrorMsg(err.message); } finally { @@ -150,7 +134,6 @@ export const Generator: React.FC = ({ url, setUrl, onGenerate }) }; const handleSubmitToDb = async () => { - // Używamy propsa 'url' zamiast formData.remoteUrl if (!url) { setErrorMsg("Meow! I need a destination URL first! 🐾"); return; @@ -166,17 +149,23 @@ export const Generator: React.FC = ({ url, setUrl, onGenerate }) try { const payload: LinkPayload = { - remoteUrl: url, // <-- Tutaj wstawiamy wartość z propsa + remoteUrl: url, uri: formData.uri, - subdomain: formData.subdomain || undefined, privacy: formData.privacy, - expiryDate: new Date(formData.expiryDate).getTime() }; - if (user && user.id) { - payload.userId = user.id; + // Dodajemy subdomenę tylko jeśli jest włączona + if (genSettings.withSubdomain && formData.subdomain) { + payload.subdomain = formData.subdomain; } + // WYSYŁANIE DATY TYLKO JEŚLI JEST PODANA + if (formData.expiryDate) { + payload.expiryDate = new Date(formData.expiryDate).getTime(); + } + + if (user?.id) payload.userId = user.id; + const response = await fetch(`${API_BASE}/api/v1/link/new`, { method: 'POST', headers: getAuthHeaders(), @@ -184,20 +173,20 @@ export const Generator: React.FC = ({ url, setUrl, onGenerate }) }); const data = await response.json(); + if (!response.ok) throw new Error(data.error || `Error ${response.status}`); - if (!response.ok) { - throw new Error(data.error || `Database error ${response.status}`); + // Budowanie końcowego linku do wyświetlenia (z subdomeną) + let finalLink = data.url; + if (!finalLink) { + const protocol = API_BASE.startsWith('https') ? 'https://' : 'http://'; + finalLink = `${protocol}${displayPrefix}/${formData.uri}`; } - const finalLink = data.url || `${API_BASE.replace('api.', '')}/${formData.uri}`; setResult(finalLink); - - // Wywołujemy callback z App.tsx (np. żeby pokazać powiadomienie) onGenerate(); - } catch (err: unknown) { if (err instanceof Error) setErrorMsg(err.message); - else setErrorMsg("Something went wrong saving to DB!"); + else setErrorMsg("Something went wrong!"); } finally { setLoading(false); } @@ -210,14 +199,13 @@ export const Generator: React.FC = ({ url, setUrl, onGenerate }) }; return ( -
+
{/* Header */}
{user ? `Logged in as ${user.username}` : 'Guest Mode (Anonymous)'}
-

KittyURL

@@ -230,7 +218,7 @@ export const Generator: React.FC = ({ url, setUrl, onGenerate })
{errorMsg} - +
)} @@ -258,7 +246,6 @@ export const Generator: React.FC = ({ url, setUrl, onGenerate })
)} - {/* Main Form */}
{/* 1. Destination URL */} @@ -271,8 +258,8 @@ export const Generator: React.FC = ({ url, setUrl, onGenerate }) type="url" placeholder="https://very-long-link.com/..." className="w-full p-4 bg-pink-50/30 border-2 border-pink-100 rounded-2xl outline-none focus:border-pink-400 focus:bg-white transition-all text-pink-600 font-medium pr-12" - value={url} // Używamy propsa - onChange={(e) => setUrl(e.target.value)} // Używamy propsa + value={url} + onChange={(e) => setUrl(e.target.value)} />
@@ -283,7 +270,6 @@ export const Generator: React.FC = ({ url, setUrl, onGenerate }) -
@@ -292,43 +278,38 @@ export const Generator: React.FC = ({ url, setUrl, onGenerate })
- setGenSettings({ ...genSettings, case: e.target.value as CaseType })}>
- -
-
+
+ {/* Dynamiczna domena przed / */} +
+ {displayPrefix} + / +
+ setFormData({ ...formData, uri: e.target.value })} /> -
/
+ {formData.uri && (
@@ -338,35 +319,66 @@ export const Generator: React.FC = ({ url, setUrl, onGenerate })
{/* 3. Database Settings */} -
-
- - setFormData({ ...formData, expiryDate: e.target.value })} /> +
+
+ {/* Datetime input (Data + Godzina) */} +
+ + setFormData({ ...formData, expiryDate: e.target.value })} + /> +

Optional: Leave empty for no expiry

+
+ +
- + {/* Subdomain Support Box */} +
+ - + {genSettings.withSubdomain && ( +
+
+ setFormData({ ...formData, subdomain: e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, '') })} + /> + .{baseDomain} +
+
+ )} +
{/* 4. Submit Button */} -
);