mirror of
https://github.com/kyantech/Palmr.git
synced 2025-10-23 06:11:58 +00:00
[RELEASE] v3.1.7-beta (#181)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "palmr-docs",
|
||||
"version": "3.1.6-beta",
|
||||
"version": "3.1.7-beta",
|
||||
"description": "Docs for Palmr",
|
||||
"private": true,
|
||||
"author": "Daniel Luiz Alves <daniel@kyantech.com.br>",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "palmr-api",
|
||||
"version": "3.1.6-beta",
|
||||
"version": "3.1.7-beta",
|
||||
"description": "API for Palmr",
|
||||
"private": true,
|
||||
"author": "Daniel Luiz Alves <daniel@kyantech.com.br>",
|
||||
|
@@ -6,7 +6,7 @@
|
||||
echo "🔐 Palmr Password Reset Tool"
|
||||
echo "============================="
|
||||
|
||||
# Check if we're in the right directory
|
||||
# Check if we're in the right directory and set DATABASE_URL
|
||||
if [ ! -f "package.json" ]; then
|
||||
echo "❌ Error: This script must be run from the server directory (/app/server)"
|
||||
echo " Current directory: $(pwd)"
|
||||
@@ -14,18 +14,26 @@ if [ ! -f "package.json" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Set DATABASE_URL if not already set
|
||||
if [ -z "$DATABASE_URL" ]; then
|
||||
export DATABASE_URL="file:/app/server/prisma/palmr.db"
|
||||
fi
|
||||
|
||||
# Ensure database directory exists
|
||||
mkdir -p /app/server/prisma
|
||||
|
||||
# Function to check if tsx is available
|
||||
check_tsx() {
|
||||
# Check if tsx binary exists in node_modules
|
||||
if [ -f "node_modules/.bin/tsx" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
||||
# Fallback: try npx
|
||||
if npx tsx --version >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -39,7 +47,7 @@ install_tsx_only() {
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
return $?
|
||||
}
|
||||
|
||||
@@ -62,7 +70,7 @@ ensure_prisma() {
|
||||
if [ -d "node_modules/@prisma/client" ] && [ -f "node_modules/@prisma/client/index.js" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
||||
echo "📦 Generating Prisma client..."
|
||||
if npx prisma generate --silent >/dev/null 2>&1; then
|
||||
echo "✅ Prisma client ready"
|
||||
@@ -81,14 +89,14 @@ if check_tsx; then
|
||||
echo "✅ tsx is ready"
|
||||
else
|
||||
echo "📦 tsx not found, installing..."
|
||||
|
||||
|
||||
# Try quick tsx-only install first
|
||||
if install_tsx_only && check_tsx; then
|
||||
echo "✅ tsx installed successfully"
|
||||
else
|
||||
echo "⚠️ Quick install failed, installing all dependencies..."
|
||||
install_all_deps
|
||||
|
||||
|
||||
# Final check
|
||||
if ! check_tsx; then
|
||||
echo "❌ Error: tsx is still not available after full installation"
|
||||
@@ -119,4 +127,4 @@ if [ -f "node_modules/.bin/tsx" ]; then
|
||||
node_modules/.bin/tsx src/scripts/reset-password.ts "$@"
|
||||
else
|
||||
npx tsx src/scripts/reset-password.ts "$@"
|
||||
fi
|
||||
fi
|
@@ -159,7 +159,8 @@
|
||||
"passwordLabel": "كلمة المرور",
|
||||
"create": "إنشاء مشاركة",
|
||||
"success": "تم إنشاء المشاركة بنجاح",
|
||||
"error": "فشل في إنشاء المشاركة"
|
||||
"error": "فشل في إنشاء المشاركة",
|
||||
"namePlaceholder": "أدخل اسمًا لمشاركتك"
|
||||
},
|
||||
"dashboard": {
|
||||
"loadError": "فشل في تحميل بيانات لوحة التحكم",
|
||||
@@ -1659,7 +1660,8 @@
|
||||
"title": "إفلات الملفات للرفع",
|
||||
"description": "حرر للرفع ملفاتك"
|
||||
},
|
||||
"pasteSuccess": "{count, plural, =1 {تم لصق الصورة ورفعها بنجاح} other {تم لصق # صور ورفعها بنجاح}}"
|
||||
"pasteSuccess": "{count, plural, =1 {تم لصق الصورة ورفعها بنجاح} other {تم لصق # صور ورفعها بنجاح}}",
|
||||
"filesQueued": "{count, plural, one {# ملف في الصف} other {# ملفات في الصف}}"
|
||||
},
|
||||
"users": {
|
||||
"modes": {
|
||||
|
@@ -159,7 +159,8 @@
|
||||
"passwordLabel": "Passwort",
|
||||
"create": "Freigabe Erstellen",
|
||||
"success": "Freigabe erfolgreich erstellt",
|
||||
"error": "Fehler beim Erstellen der Freigabe"
|
||||
"error": "Fehler beim Erstellen der Freigabe",
|
||||
"namePlaceholder": "Geben Sie einen Namen für Ihre Freigabe ein"
|
||||
},
|
||||
"dashboard": {
|
||||
"loadError": "Fehler beim Laden der Dashboard-Daten",
|
||||
@@ -1657,7 +1658,8 @@
|
||||
"title": "Dateien zum Hochladen ablegen",
|
||||
"description": "Loslassen, um Ihre Dateien hochzuladen"
|
||||
},
|
||||
"pasteSuccess": "{count, plural, =1 {Bild erfolgreich eingefügt und hochgeladen} other {# Bilder erfolgreich eingefügt und hochgeladen}}"
|
||||
"pasteSuccess": "{count, plural, =1 {Bild erfolgreich eingefügt und hochgeladen} other {# Bilder erfolgreich eingefügt und hochgeladen}}",
|
||||
"filesQueued": "{count, plural, one {# Datei in der Warteschlange} other {# Dateien in der Warteschlange}}"
|
||||
},
|
||||
"users": {
|
||||
"modes": {
|
||||
|
@@ -149,6 +149,7 @@
|
||||
"createShare": {
|
||||
"title": "Create Share",
|
||||
"nameLabel": "Share Name",
|
||||
"namePlaceholder": "Enter a name for your share",
|
||||
"descriptionLabel": "Description",
|
||||
"descriptionPlaceholder": "Enter a description (optional)",
|
||||
"expirationLabel": "Expiration Date",
|
||||
@@ -1634,6 +1635,7 @@
|
||||
"selectFile": "Click to select a file",
|
||||
"selectMultipleFiles": "Click to select one or multiple files",
|
||||
"dragAndDrop": "or drag and drop files here",
|
||||
"filesQueued": "{count, plural, one {# file queued for upload} other {# files queued for upload}}",
|
||||
"preview": "Preview",
|
||||
"uploadProgress": "Upload progress",
|
||||
"upload": "Upload",
|
||||
@@ -1745,4 +1747,4 @@
|
||||
"nameRequired": "Name is required",
|
||||
"required": "This field is required"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -159,7 +159,8 @@
|
||||
"passwordLabel": "Contraseña",
|
||||
"create": "Crear Compartir",
|
||||
"success": "Compartir creado exitosamente",
|
||||
"error": "Error al crear compartir"
|
||||
"error": "Error al crear compartir",
|
||||
"namePlaceholder": "Ingrese un nombre para su compartir"
|
||||
},
|
||||
"dashboard": {
|
||||
"loadError": "Error al cargar los datos del tablero",
|
||||
@@ -1657,7 +1658,8 @@
|
||||
"title": "Suelta archivos para subir",
|
||||
"description": "Suelta para subir tus archivos"
|
||||
},
|
||||
"pasteSuccess": "{count, plural, =1 {Imagen pegada y subida exitosamente} other {# imágenes pegadas y subidas exitosamente}}"
|
||||
"pasteSuccess": "{count, plural, =1 {Imagen pegada y subida exitosamente} other {# imágenes pegadas y subidas exitosamente}}",
|
||||
"filesQueued": "{count, plural, one {# archivo en cola para subir} other {# archivos en cola para subir}}"
|
||||
},
|
||||
"users": {
|
||||
"modes": {
|
||||
|
@@ -159,7 +159,8 @@
|
||||
"passwordLabel": "Mot de Passe",
|
||||
"create": "Créer un Partage",
|
||||
"success": "Partage créé avec succès",
|
||||
"error": "Échec de la création du partage"
|
||||
"error": "Échec de la création du partage",
|
||||
"namePlaceholder": "Entrez un nom pour votre partage"
|
||||
},
|
||||
"dashboard": {
|
||||
"loadError": "Échec du chargement des données du tableau de bord",
|
||||
@@ -1657,7 +1658,8 @@
|
||||
"title": "Déposer des fichiers pour télécharger",
|
||||
"description": "Relâchez pour télécharger vos fichiers"
|
||||
},
|
||||
"pasteSuccess": "{count, plural, =1 {Image collée et téléchargée avec succès} other {# images collées et téléchargées avec succès}}"
|
||||
"pasteSuccess": "{count, plural, =1 {Image collée et téléchargée avec succès} other {# images collées et téléchargées avec succès}}",
|
||||
"filesQueued": "{count, plural, one {# fichier en attente de téléchargement} other {# fichiers en attente de téléchargement}}"
|
||||
},
|
||||
"users": {
|
||||
"modes": {
|
||||
|
@@ -159,7 +159,8 @@
|
||||
"passwordLabel": "पासवर्ड",
|
||||
"create": "साझाकरण बनाएं",
|
||||
"success": "साझाकरण सफलतापूर्वक बनाया गया",
|
||||
"error": "साझाकरण बनाने में विफल"
|
||||
"error": "साझाकरण बनाने में विफल",
|
||||
"namePlaceholder": "अपने साझाकरण के लिए एक नाम दर्ज करें"
|
||||
},
|
||||
"dashboard": {
|
||||
"loadError": "डैशबोर्ड डेटा लोड करने में त्रुटि",
|
||||
@@ -1657,7 +1658,8 @@
|
||||
"title": "अपलोड करने के लिए फ़ाइलें छोड़ें",
|
||||
"description": "अपनी फ़ाइलें अपलोड करने के लिए छोड़ें"
|
||||
},
|
||||
"pasteSuccess": "{count, plural, =1 {छवि सफलतापूर्वक चिपकाई और अपलोड की गई} other {# छवियाँ सफलतापूर्वक चिपकाई और अपलोड की गईं}}"
|
||||
"pasteSuccess": "{count, plural, =1 {छवि सफलतापूर्वक चिपकाई और अपलोड की गई} other {# छवियाँ सफलतापूर्वक चिपकाई और अपलोड की गईं}}",
|
||||
"filesQueued": "{count, plural, one {# फ़ाइल अपलोड के लिए इंतजार में है} other {# फ़ाइलें अपलोड के लिए इंतजार में हैं}}"
|
||||
},
|
||||
"users": {
|
||||
"modes": {
|
||||
|
@@ -159,7 +159,8 @@
|
||||
"passwordLabel": "Password",
|
||||
"create": "Crea Condivisione",
|
||||
"success": "Condivisione creata con successo",
|
||||
"error": "Errore nella creazione della condivisione"
|
||||
"error": "Errore nella creazione della condivisione",
|
||||
"namePlaceholder": "Inserisci un nome per la tua condivisione"
|
||||
},
|
||||
"dashboard": {
|
||||
"loadError": "Errore durante il caricamento dei dati del pannello di controllo",
|
||||
@@ -1657,7 +1658,8 @@
|
||||
"title": "Rilascia i file per caricarli",
|
||||
"description": "Rilascia per caricare i tuoi file"
|
||||
},
|
||||
"pasteSuccess": "{count, plural, =1 {Immagine incollata e caricata con successo} other {# immagini incollate e caricate con successo}}"
|
||||
"pasteSuccess": "{count, plural, =1 {Immagine incollata e caricata con successo} other {# immagini incollate e caricate con successo}}",
|
||||
"filesQueued": "{count, plural, one {# file in coda per il caricamento} other {# files in coda per il caricamento}}"
|
||||
},
|
||||
"users": {
|
||||
"modes": {
|
||||
|
@@ -159,7 +159,8 @@
|
||||
"passwordLabel": "パスワード",
|
||||
"create": "共有を作成",
|
||||
"success": "共有が正常に作成されました",
|
||||
"error": "共有の作成に失敗しました"
|
||||
"error": "共有の作成に失敗しました",
|
||||
"namePlaceholder": "共有の名前を入力してください"
|
||||
},
|
||||
"dashboard": {
|
||||
"loadError": "ダッシュボードデータの読み込みに失敗しました",
|
||||
@@ -1657,7 +1658,8 @@
|
||||
"title": "アップロードするファイルをドロップ",
|
||||
"description": "ファイルをアップロードするにはリリースしてください"
|
||||
},
|
||||
"pasteSuccess": "{count, plural, =1 {画像が貼り付けられ、正常にアップロードされました} other {#個の画像が貼り付けられ、正常にアップロードされました}}"
|
||||
"pasteSuccess": "{count, plural, =1 {画像が貼り付けられ、正常にアップロードされました} other {#個の画像が貼り付けられ、正常にアップロードされました}}",
|
||||
"filesQueued": "{count, plural, one {# ファイルがアップロード待ち} other {# ファイルがアップロード待ち}}"
|
||||
},
|
||||
"users": {
|
||||
"modes": {
|
||||
|
@@ -159,7 +159,8 @@
|
||||
"passwordLabel": "비밀번호",
|
||||
"create": "공유 생성",
|
||||
"success": "공유가 성공적으로 생성되었습니다",
|
||||
"error": "공유 생성에 실패했습니다"
|
||||
"error": "공유 생성에 실패했습니다",
|
||||
"namePlaceholder": "공유 이름을 입력하세요"
|
||||
},
|
||||
"dashboard": {
|
||||
"loadError": "대시보드 데이터를 불러오는데 실패했습니다",
|
||||
@@ -1657,7 +1658,8 @@
|
||||
"title": "업로드할 파일 드롭",
|
||||
"description": "파일을 업로드하려면 놓으세요"
|
||||
},
|
||||
"pasteSuccess": "{count, plural, =1 {이미지가 성공적으로 붙여넣기 및 업로드되었습니다} other {# 개의 이미지가 성공적으로 붙여넣기 및 업로드되었습니다}}"
|
||||
"pasteSuccess": "{count, plural, =1 {이미지가 성공적으로 붙여넣기 및 업로드되었습니다} other {# 개의 이미지가 성공적으로 붙여넣기 및 업로드되었습니다}}",
|
||||
"filesQueued": "{count, plural, one {# 파일이 업로드 대기 중} other {# 파일이 업로드 대기 중}}"
|
||||
},
|
||||
"users": {
|
||||
"modes": {
|
||||
|
@@ -159,7 +159,8 @@
|
||||
"passwordLabel": "Wachtwoord",
|
||||
"create": "Delen Maken",
|
||||
"success": "Delen succesvol aangemaakt",
|
||||
"error": "Fout bij het aanmaken van delen"
|
||||
"error": "Fout bij het aanmaken van delen",
|
||||
"namePlaceholder": "Voer een naam in voor uw delen"
|
||||
},
|
||||
"dashboard": {
|
||||
"loadError": "Fout bij het laden van controlepaneel gegevens",
|
||||
@@ -1657,7 +1658,8 @@
|
||||
"title": "Sleep bestanden om te uploaden",
|
||||
"description": "Laat los om je bestanden te uploaden"
|
||||
},
|
||||
"pasteSuccess": "{count, plural, =1 {Afbeelding geplakt en succesvol geüpload} other {# afbeeldingen geplakt en succesvol geüpload}}"
|
||||
"pasteSuccess": "{count, plural, =1 {Afbeelding geplakt en succesvol geüpload} other {# afbeeldingen geplakt en succesvol geüpload}}",
|
||||
"filesQueued": "{count, plural, one {# bestand in de wachtrij voor upload} other {# bestanden in de wachtrij voor upload}}"
|
||||
},
|
||||
"users": {
|
||||
"modes": {
|
||||
|
@@ -159,7 +159,8 @@
|
||||
"passwordLabel": "Hasło",
|
||||
"create": "Utwórz Udostępnienie",
|
||||
"success": "Udostępnienie utworzone pomyślnie",
|
||||
"error": "Nie udało się utworzyć udostępnienia"
|
||||
"error": "Nie udało się utworzyć udostępnienia",
|
||||
"namePlaceholder": "Wprowadź nazwę dla swojego udostępnienia"
|
||||
},
|
||||
"dashboard": {
|
||||
"loadError": "Nie udało się załadować danych panelu głównego",
|
||||
@@ -1657,7 +1658,8 @@
|
||||
"title": "Upuść pliki, aby przesłać",
|
||||
"description": "Zwolnij, aby przesłać pliki"
|
||||
},
|
||||
"pasteSuccess": "{count, plural, =1 {Obraz wklejony i przesłany pomyślnie} other {# obrazy wklejone i przesłane pomyślnie}}"
|
||||
"pasteSuccess": "{count, plural, =1 {Obraz wklejony i przesłany pomyślnie} other {# obrazy wklejone i przesłane pomyślnie}}",
|
||||
"filesQueued": "{count, plural, one {# plik w kolejce do przesyłania} other {# pliki w kolejce do przesyłania}}"
|
||||
},
|
||||
"users": {
|
||||
"modes": {
|
||||
|
@@ -159,7 +159,8 @@
|
||||
"passwordLabel": "Senha",
|
||||
"create": "Criar compartilhamento",
|
||||
"success": "Compartilhamento criado com sucesso",
|
||||
"error": "Falha ao criar compartilhamento"
|
||||
"error": "Falha ao criar compartilhamento",
|
||||
"namePlaceholder": "Digite um nome para seu compartilhamento"
|
||||
},
|
||||
"dashboard": {
|
||||
"loadError": "Falha ao carregar dados do painel",
|
||||
@@ -643,7 +644,7 @@
|
||||
"edit": "Editar",
|
||||
"delete": "Excluir",
|
||||
"viewFiles": "Arquivos Recebidos",
|
||||
"viewQrCode": "[TO_TRANSLATE] View QR Code"
|
||||
"viewQrCode": "Ver QR Code"
|
||||
},
|
||||
"empty": {
|
||||
"title": "Nenhum link de recebimento criado",
|
||||
@@ -1657,7 +1658,8 @@
|
||||
"continue": "Continuar Uploads",
|
||||
"cancel": "Cancelar Uploads"
|
||||
},
|
||||
"pasteSuccess": "{count, plural, =1 {Imagem colada e enviada com sucesso} other {# imagens coladas e enviadas com sucesso}}"
|
||||
"pasteSuccess": "{count, plural, =1 {Imagem colada e enviada com sucesso} other {# imagens coladas e enviadas com sucesso}}",
|
||||
"filesQueued": "{count, plural, one {# arquivo na fila para upload} other {# arquivos na fila para upload}}"
|
||||
},
|
||||
"users": {
|
||||
"modes": {
|
||||
|
@@ -159,7 +159,8 @@
|
||||
"success": "Общий доступ успешно создан",
|
||||
"error": "Не удалось создать общий доступ",
|
||||
"descriptionLabel": "Описание",
|
||||
"descriptionPlaceholder": "Введите описание (опционально)"
|
||||
"descriptionPlaceholder": "Введите описание (опционально)",
|
||||
"namePlaceholder": "Введите имя для вашего общего доступа"
|
||||
},
|
||||
"dashboard": {
|
||||
"loadError": "Ошибка загрузки данных панели управления",
|
||||
@@ -1657,7 +1658,8 @@
|
||||
"title": "Перетащите файлы для загрузки",
|
||||
"description": "Отпустите, чтобы загрузить файлы"
|
||||
},
|
||||
"pasteSuccess": "{count, plural, =1 {Изображение вставлено и успешно загружено} other {# изображений вставлено и успешно загружено}}"
|
||||
"pasteSuccess": "{count, plural, =1 {Изображение вставлено и успешно загружено} other {# изображений вставлено и успешно загружено}}",
|
||||
"filesQueued": "{count, plural, one {# файл в очереди для загрузки} other {# файлов в очереди для загрузки}}"
|
||||
},
|
||||
"users": {
|
||||
"modes": {
|
||||
|
@@ -159,7 +159,8 @@
|
||||
"success": "Paylaşım başarıyla oluşturuldu",
|
||||
"error": "Paylaşım oluşturulamadı",
|
||||
"descriptionLabel": "Açıklama",
|
||||
"descriptionPlaceholder": "Açıklama girin (isteğe bağlı)"
|
||||
"descriptionPlaceholder": "Açıklama girin (isteğe bağlı)",
|
||||
"namePlaceholder": "Paylaşımınız için bir ad girin"
|
||||
},
|
||||
"dashboard": {
|
||||
"loadError": "Gösterge paneli verileri yüklenemedi",
|
||||
@@ -1657,7 +1658,8 @@
|
||||
"title": "Yüklemek için dosyaları bırakın",
|
||||
"description": "Dosyalarınızı yüklemek için bırakın"
|
||||
},
|
||||
"pasteSuccess": "{count, plural, =1 {Görüntü yapıştırıldı ve başarıyla yüklendi} other {# görüntü yapıştırıldı ve başarıyla yüklendi}}"
|
||||
"pasteSuccess": "{count, plural, =1 {Görüntü yapıştırıldı ve başarıyla yüklendi} other {# görüntü yapıştırıldı ve başarıyla yüklendi}}",
|
||||
"filesQueued": "{count, plural, one {# dosya yükleme için bekliyor} other {# dosya yükleme için bekliyor}}"
|
||||
},
|
||||
"users": {
|
||||
"modes": {
|
||||
|
@@ -159,7 +159,8 @@
|
||||
"passwordLabel": "密码",
|
||||
"create": "创建分享",
|
||||
"success": "分享创建成功",
|
||||
"error": "创建分享失败"
|
||||
"error": "创建分享失败",
|
||||
"namePlaceholder": "输入分享名称"
|
||||
},
|
||||
"dashboard": {
|
||||
"loadError": "加载仪表盘数据失败",
|
||||
@@ -1657,7 +1658,8 @@
|
||||
"title": "拖放文件以上传",
|
||||
"description": "松开以上传您的文件"
|
||||
},
|
||||
"pasteSuccess": "{count, plural, =1 {图片已成功粘贴并上传} other {# 张图片已成功粘贴并上传}}"
|
||||
"pasteSuccess": "{count, plural, =1 {图片已成功粘贴并上传} other {# 张图片已成功粘贴并上传}}",
|
||||
"filesQueued": "{count, plural, one {# 文件正在等待上传} other {# 文件正在等待上传}}"
|
||||
},
|
||||
"users": {
|
||||
"modes": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "palmr-web",
|
||||
"version": "3.1.6-beta",
|
||||
"version": "3.1.7-beta",
|
||||
"description": "Frontend for Palmr",
|
||||
"private": true,
|
||||
"author": "Daniel Luiz Alves <daniel@kyantech.com.br>",
|
||||
|
@@ -29,7 +29,7 @@ export function Navbar() {
|
||||
{appLogo && <img alt="App Logo" className="h-8 w-8 object-contain rounded" src={appLogo} />}
|
||||
<p className="font-bold text-2xl">{appName}</p>
|
||||
</Link>
|
||||
<nav className="hidden lg:flex ml-2 gap-4">
|
||||
<nav className="hidden md:flex ml-2 gap-4">
|
||||
{siteConfig.navItems.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
@@ -44,7 +44,7 @@ export function Navbar() {
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
<div className="hidden md:flex items-center gap-2">
|
||||
<div className="hidden lg:flex items-center gap-2">
|
||||
<LanguageSwitcher />
|
||||
<ModeToggle />
|
||||
|
||||
@@ -56,7 +56,7 @@ export function Navbar() {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 sm:hidden">
|
||||
<div className="flex items-center gap-2 md:hidden">
|
||||
<LanguageSwitcher />
|
||||
<ModeToggle />
|
||||
<Sheet open={isMenuOpen} onOpenChange={setIsMenuOpen}>
|
||||
@@ -79,6 +79,16 @@ export function Navbar() {
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
<Link
|
||||
href={siteConfig.links.sponsor}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-foreground text-lg font-medium flex items-center gap-2"
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
>
|
||||
<IconHeart className="h-4 w-4 text-destructive" />
|
||||
Sponsor
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</SheetContent>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, type ReactNode } from "react";
|
||||
import { useEffect, useState, type ReactNode } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { useAuth } from "@/contexts/auth-context";
|
||||
@@ -14,16 +14,21 @@ type ProtectedRouteProps = {
|
||||
export function ProtectedRoute({ children, requireAdmin = false }: ProtectedRouteProps) {
|
||||
const { isAuthenticated, isAdmin } = useAuth();
|
||||
const router = useRouter();
|
||||
const [hasCheckedAuth, setHasCheckedAuth] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isAuthenticated === false) {
|
||||
router.replace("/login");
|
||||
} else if (requireAdmin && isAdmin === false) {
|
||||
router.replace("/dashboard");
|
||||
if (isAuthenticated !== null) {
|
||||
setHasCheckedAuth(true);
|
||||
|
||||
if (isAuthenticated === false) {
|
||||
router.replace("/login");
|
||||
} else if (requireAdmin && isAdmin === false) {
|
||||
router.replace("/dashboard");
|
||||
}
|
||||
}
|
||||
}, [isAuthenticated, isAdmin, requireAdmin, router]);
|
||||
|
||||
if (isAuthenticated === null || (requireAdmin && isAdmin === null)) {
|
||||
if (!hasCheckedAuth || isAuthenticated === null || (requireAdmin && isAdmin === null)) {
|
||||
return <LoadingScreen />;
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { IconLogout, IconSettings, IconUser, IconUsers } from "@tabler/icons-react";
|
||||
@@ -21,8 +22,22 @@ import { logout as logoutAPI } from "@/http/endpoints";
|
||||
export function Navbar() {
|
||||
const t = useTranslations();
|
||||
const router = useRouter();
|
||||
const { user, isAdmin, logout } = useAuth();
|
||||
const { user, isAdmin, logout, isAuthenticated } = useAuth();
|
||||
const { appName, appLogo } = useAppInfo();
|
||||
const [isNavigating, setIsNavigating] = useState(false);
|
||||
|
||||
const handleLogoClick = async () => {
|
||||
if (isNavigating || !isAuthenticated) return;
|
||||
|
||||
try {
|
||||
setIsNavigating(true);
|
||||
router.replace("/dashboard");
|
||||
} catch (err) {
|
||||
console.error("Error navigating to dashboard:", err);
|
||||
} finally {
|
||||
setTimeout(() => setIsNavigating(false), 500);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
@@ -39,10 +54,15 @@ export function Navbar() {
|
||||
<div className="container flex h-16 max-w-screen-xl items-center mx-auto lg:px-6">
|
||||
<div className="flex flex-1 items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Link href="/dashboard" className="flex items-center gap-2 cursor-pointer">
|
||||
<div
|
||||
onClick={handleLogoClick}
|
||||
className={`flex items-center gap-2 cursor-pointer transition-opacity ${
|
||||
isNavigating ? "opacity-50" : "opacity-100"
|
||||
}`}
|
||||
>
|
||||
{appLogo && <img alt={t("navbar.logoAlt")} className="h-8 w-8 object-contain rounded" src={appLogo} />}
|
||||
<p className="font-bold text-2xl">{appName}</p>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 cursor-pointer">
|
||||
|
@@ -37,7 +37,15 @@ export function CreateShareModal({ isOpen, onClose, onSuccess }: CreateShareModa
|
||||
name: formData.name,
|
||||
description: formData.description || undefined,
|
||||
password: formData.isPasswordProtected ? formData.password : undefined,
|
||||
expiration: formData.expiresAt ? new Date(formData.expiresAt).toISOString() : undefined,
|
||||
expiration: formData.expiresAt
|
||||
? (() => {
|
||||
const dateValue = formData.expiresAt;
|
||||
if (dateValue.length === 10) {
|
||||
return new Date(dateValue + "T23:59:59").toISOString();
|
||||
}
|
||||
return new Date(dateValue).toISOString();
|
||||
})()
|
||||
: undefined,
|
||||
maxViews: formData.maxViews ? parseInt(formData.maxViews) : undefined,
|
||||
files: [],
|
||||
});
|
||||
@@ -71,7 +79,16 @@ export function CreateShareModal({ isOpen, onClose, onSuccess }: CreateShareModa
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>{t("createShare.nameLabel")}</Label>
|
||||
<Input value={formData.name} onChange={(e) => setFormData({ ...formData, name: e.target.value })} />
|
||||
<Input
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
onPaste={(e) => {
|
||||
e.preventDefault();
|
||||
const pastedText = e.clipboardData.getData("text");
|
||||
setFormData({ ...formData, name: pastedText });
|
||||
}}
|
||||
placeholder={t("createShare.namePlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
@@ -93,6 +110,12 @@ export function CreateShareModal({ isOpen, onClose, onSuccess }: CreateShareModa
|
||||
type="datetime-local"
|
||||
value={formData.expiresAt}
|
||||
onChange={(e) => setFormData({ ...formData, expiresAt: e.target.value })}
|
||||
onBlur={(e) => {
|
||||
const value = e.target.value;
|
||||
if (value && value.length === 10) {
|
||||
setFormData({ ...formData, expiresAt: value + "T23:59" });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@@ -124,7 +124,11 @@ export function UploadFileModal({ isOpen, onClose, onSuccess }: UploadFileModalP
|
||||
let previewUrl: string | undefined;
|
||||
|
||||
if (file.type.startsWith("image/")) {
|
||||
previewUrl = URL.createObjectURL(file);
|
||||
try {
|
||||
previewUrl = URL.createObjectURL(file);
|
||||
} catch (error) {
|
||||
console.warn("Failed to create preview URL:", error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -142,6 +146,10 @@ export function UploadFileModal({ isOpen, onClose, onSuccess }: UploadFileModalP
|
||||
const newUploads = Array.from(files).map(createFileUpload);
|
||||
setFileUploads((prev) => [...prev, ...newUploads]);
|
||||
setHasShownSuccessToast(false);
|
||||
|
||||
if (newUploads.length > 0) {
|
||||
toast.info(t("uploadFile.filesQueued", { count: newUploads.length }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -164,7 +172,12 @@ export function UploadFileModal({ isOpen, onClose, onSuccess }: UploadFileModalP
|
||||
const handleDrop = (event: React.DragEvent) => {
|
||||
event.preventDefault();
|
||||
setIsDragOver(false);
|
||||
handleFilesSelect(event.dataTransfer.files);
|
||||
event.stopPropagation();
|
||||
|
||||
const files = event.dataTransfer.files;
|
||||
if (files.length > 0) {
|
||||
handleFilesSelect(files);
|
||||
}
|
||||
};
|
||||
|
||||
const renderFileIcon = (fileName: string) => {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "palmr-monorepo",
|
||||
"version": "3.1.6-beta",
|
||||
"version": "3.1.7-beta",
|
||||
"description": "Palmr monorepo with Husky configuration",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.6.0",
|
||||
|
Reference in New Issue
Block a user