Skip to content
This repository was archived by the owner on Dec 11, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/admin/services/admin/typings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2443,6 +2443,7 @@ declare namespace API {
upload: number;
token: string;
status: number;
note: string;
created_at: number;
updated_at: number;
};
Expand All @@ -2462,6 +2463,7 @@ declare namespace API {
upload: number;
token: string;
status: number;
note: string;
created_at: number;
updated_at: number;
};
Expand Down
8 changes: 8 additions & 0 deletions apps/user/app/(main)/(user)/dashboard/content.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import { Display } from '@/components/display';
import EditNote from '@/components/subscribe/edit-note';
import Renewal from '@/components/subscribe/renewal';
import ResetTraffic from '@/components/subscribe/reset-traffic';
import Unsubscribe from '@/components/subscribe/unsubscribe';
Expand Down Expand Up @@ -249,9 +250,16 @@ export default function Content() {
<CardTitle className='font-medium'>
{item.subscribe.name}
<p className='text-foreground/50 mt-1 text-sm'>{formatDate(item.start_time)}</p>
{item.note && (
<p className='text-muted-foreground mt-2 flex items-center gap-1.5 text-sm font-normal'>
<Icon icon='uil:file-edit-alt' className='size-4' />
{item.note}
</p>
)}
</CardTitle>
{item.status !== 4 && (
<div className='flex flex-wrap gap-2'>
<EditNote id={item.id} currentNote={item.note} onSuccess={refetch} />
<AlertDialog>
<AlertDialogTrigger asChild>
<Button size='sm' variant='destructive'>
Expand Down
89 changes: 89 additions & 0 deletions apps/user/components/subscribe/edit-note.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
'use client';

import { updateUserSubscribeNote } from '@/services/user/subscribe';
import { Button } from '@workspace/ui/components/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@workspace/ui/components/dialog';
import { Textarea } from '@workspace/ui/components/textarea';
import { Icon } from '@workspace/ui/custom-components/icon';
import { LoaderCircle } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { useCallback, useState, useTransition } from 'react';
import { toast } from 'sonner';

interface EditNoteProps {
id: number;
currentNote?: string;
onSuccess?: () => void;
}

export default function EditNote({ id, currentNote = '', onSuccess }: Readonly<EditNoteProps>) {
const t = useTranslations('dashboard');
const [open, setOpen] = useState<boolean>(false);
const [note, setNote] = useState<string>(currentNote);
const [loading, startTransition] = useTransition();

const handleSubmit = useCallback(async () => {
startTransition(async () => {
try {
await updateUserSubscribeNote({
user_subscribe_id: id,
note: note,
});
toast.success(t('noteUpdateSuccess'));
setOpen(false);
if (onSuccess) {
onSuccess();
}
} catch (error) {
toast.error(t('noteUpdateFailed'));
}
});
}, [id, note, onSuccess, t]);

return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button size='sm' variant='outline'>
<Icon icon='uil:edit' className='mr-1 size-4' />
{t('editNote')}
</Button>
</DialogTrigger>
<DialogContent className='sm:max-w-[425px]'>
<DialogHeader>
<DialogTitle>{t('editNote')}</DialogTitle>
<DialogDescription>{t('noteDescription')}</DialogDescription>
</DialogHeader>
<div className='grid gap-4 py-4'>
<Textarea
placeholder={t('notePlaceholder')}
value={note}
onChange={(e) => setNote(e.target.value)}
maxLength={500}
rows={4}
className='resize-none'
/>
<p className='text-muted-foreground text-xs'>
{note.length}/500 {t('characters')}
</p>
</div>
<DialogFooter>
<Button variant='outline' onClick={() => setOpen(false)} disabled={loading}>
{t('cancel')}
</Button>
<Button onClick={handleSubmit} disabled={loading}>
{loading && <LoaderCircle className='mr-2 size-4 animate-spin' />}
{t('save')}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
7 changes: 7 additions & 0 deletions apps/user/locales/cs-CZ/dashboard.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
{
"cancel": "zrušit",
"characters": "znaků",
"confirm": "Potvrdit",
"confirmResetSubscription": "Opravdu chcete obnovit adresu předplatného?",
"copy": "kopírovat",
"copyFailure": "Kopírování selhalo, prosím zkopírujte ručně",
"copySuccess": "Kopírování úspěšné",
"deducted": "Zrušeno",
"download": "stáhnout",
"editNote": "Upravit poznámku",
"expirationDays": "Doba platnosti/dny",
"expired": "Vypršelo",
"finished": "Provoz vyčerpán",
Expand All @@ -17,11 +19,16 @@
"nextResetDays": "Příští reset/den",
"noLimit": "Bez omezení",
"noReset": "Žádné obnovení",
"noteDescription": "Přidejte vlastní poznámku pro identifikaci tohoto předplatného (např. 'Osobní VPN', 'Pracovní předplatné')",
"notePlaceholder": "Zadejte poznámku k tomuto předplatnému...",
"noteUpdateFailed": "Nepodařilo se aktualizovat poznámku",
"noteUpdateSuccess": "Poznámka byla úspěšně aktualizována",
"prompt": "Výzva",
"purchaseSubscription": "Zakoupit předplatné",
"qrCode": "QR kód",
"resetSubscription": "Obnovit adresu předplatného",
"resetSuccess": "Obnovení bylo úspěšné",
"save": "Uložit",
"scanToSubscribe": "Naskenujte pro odběr",
"subscriptionUrl": "Odběrná adresa",
"totalTraffic": "Celkový provoz",
Expand Down
7 changes: 7 additions & 0 deletions apps/user/locales/de-DE/dashboard.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
{
"cancel": "Abbrechen",
"characters": "Zeichen",
"confirm": "Bestätigen",
"confirmResetSubscription": "Möchten Sie die Abonnementadresse wirklich zurücksetzen?",
"copy": "Kopieren",
"copyFailure": "Kopieren fehlgeschlagen, bitte manuell kopieren",
"copySuccess": "Kopieren erfolgreich",
"deducted": "Storniert",
"download": "Herunterladen",
"editNote": "Notiz bearbeiten",
"expirationDays": "Ablaufzeit/Tage",
"expired": "Abgelaufen",
"finished": "Verkehr erschöpft",
Expand All @@ -17,11 +19,16 @@
"nextResetDays": "Nächster Reset/Tage",
"noLimit": "Kein Limit",
"noReset": "Kein Zurücksetzen",
"noteDescription": "Fügen Sie eine benutzerdefinierte Notiz hinzu, um dieses Abonnement zu identifizieren (z.B. 'Persönliches VPN', 'Arbeitsabonnement')",
"notePlaceholder": "Geben Sie eine Notiz für dieses Abonnement ein...",
"noteUpdateFailed": "Notiz konnte nicht aktualisiert werden",
"noteUpdateSuccess": "Notiz erfolgreich aktualisiert",
"prompt": "Aufforderung",
"purchaseSubscription": "Abonnement kaufen",
"qrCode": "QR-Code",
"resetSubscription": "Abonnement-Adresse zurücksetzen",
"resetSuccess": "Zurücksetzen erfolgreich",
"save": "Speichern",
"scanToSubscribe": "Scannen zum Abonnieren",
"subscriptionUrl": "Abonnement-Adresse",
"totalTraffic": "Gesamtverkehr",
Expand Down
7 changes: 7 additions & 0 deletions apps/user/locales/en-US/dashboard.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
{
"cancel": "Cancel",
"characters": "characters",
"confirm": "Confirm",
"confirmResetSubscription": "Are you sure you want to reset the subscription address?",
"copy": "Copy",
"copyFailure": "Copy failed, please copy manually",
"copySuccess": "Copy successful",
"deducted": "Canceled",
"download": "Download",
"editNote": "Edit Note",
"expirationDays": "Expiration Days",
"expired": "Expired",
"finished": "Traffic exhausted",
Expand All @@ -17,11 +19,16 @@
"nextResetDays": "Next Reset in Days",
"noLimit": "No Limit",
"noReset": "No Reset",
"noteDescription": "Add a custom note to help you identify this subscription (e.g., 'Personal VPN', 'Work subscription')",
"notePlaceholder": "Enter a note for this subscription...",
"noteUpdateFailed": "Failed to update note",
"noteUpdateSuccess": "Note updated successfully",
"prompt": "Prompt",
"purchaseSubscription": "Purchase Subscription",
"qrCode": "QR Code",
"resetSubscription": "Reset Subscription Address",
"resetSuccess": "Reset successful",
"save": "Save",
"scanToSubscribe": "Scan to Subscribe",
"subscriptionUrl": "Subscription URL",
"totalTraffic": "Total Traffic",
Expand Down
7 changes: 7 additions & 0 deletions apps/user/locales/es-ES/dashboard.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
{
"cancel": "Cancelar",
"characters": "caracteres",
"confirm": "Confirmar",
"confirmResetSubscription": "¿Confirmar el restablecimiento de la dirección de suscripción?",
"copy": "Copiar",
"copyFailure": "Error al copiar, por favor copia manualmente",
"copySuccess": "Copia exitosa",
"deducted": "Cancelado",
"download": "descargar",
"editNote": "Editar nota",
"expirationDays": "Días de vencimiento",
"expired": "Caducado",
"finished": "Tráfico agotado",
Expand All @@ -17,11 +19,16 @@
"nextResetDays": "Próximo reinicio/días",
"noLimit": "Sin límite",
"noReset": "Sin Reinicio",
"noteDescription": "Añade una nota personalizada para identificar esta suscripción (ej. 'VPN Personal', 'Suscripción de trabajo')",
"notePlaceholder": "Introduce una nota para esta suscripción...",
"noteUpdateFailed": "Error al actualizar la nota",
"noteUpdateSuccess": "Nota actualizada con éxito",
"prompt": "sugerencia",
"purchaseSubscription": "Comprar suscripción",
"qrCode": "Código QR",
"resetSubscription": "Restablecer dirección de suscripción",
"resetSuccess": "Restablecimiento exitoso",
"save": "Guardar",
"scanToSubscribe": "Escanear para suscribirse",
"subscriptionUrl": "Dirección de suscripción",
"totalTraffic": "Tráfico total",
Expand Down
7 changes: 7 additions & 0 deletions apps/user/locales/es-MX/dashboard.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
{
"cancel": "Cancelar",
"characters": "caracteres",
"confirm": "Confirmar",
"confirmResetSubscription": "¿Confirma que desea restablecer la dirección de suscripción?",
"copy": "copiar",
"copyFailure": "Error al copiar, por favor copia manualmente",
"copySuccess": "Copia exitosa",
"deducted": "Cancelado",
"download": "descargar",
"editNote": "Editar nota",
"expirationDays": "Días de vencimiento",
"expired": "Expirado",
"finished": "Tráfico agotado",
Expand All @@ -17,11 +19,16 @@
"nextResetDays": "Próximo reinicio/días",
"noLimit": "Sin Límite",
"noReset": "Sin Reinicio",
"noteDescription": "Añade una nota personalizada para identificar esta suscripción (ej. 'VPN Personal', 'Suscripción de trabajo')",
"notePlaceholder": "Introduce una nota para esta suscripción...",
"noteUpdateFailed": "Error al actualizar la nota",
"noteUpdateSuccess": "Nota actualizada con éxito",
"prompt": "Sugerencia",
"purchaseSubscription": "Comprar suscripción",
"qrCode": "Código QR",
"resetSubscription": "Restablecer dirección de suscripción",
"resetSuccess": "Restablecimiento exitoso",
"save": "Guardar",
"scanToSubscribe": "Escanear para suscribirse",
"subscriptionUrl": "URL de suscripción",
"totalTraffic": "Tráfico total",
Expand Down
7 changes: 7 additions & 0 deletions apps/user/locales/fa-IR/dashboard.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
{
"cancel": "لغو",
"characters": "نویسه",
"confirm": "تأیید",
"confirmResetSubscription": "آیا مطمئن هستید که می‌خواهید آدرس اشتراک را بازنشانی کنید؟",
"copy": "کپی",
"copyFailure": "کپی ناموفق بود، لطفاً به صورت دستی کپی کنید",
"copySuccess": "کپی با موفقیت انجام شد",
"deducted": "لغو شده",
"download": "دانلود",
"editNote": "ویرایش یادداشت",
"expirationDays": "روزهای انقضا",
"expired": "منقضی شده",
"finished": "ترافیک تمام شده",
Expand All @@ -17,11 +19,16 @@
"nextResetDays": "روزهای باقی‌مانده تا بازنشانی بعدی",
"noLimit": "بدون محدودیت",
"noReset": "عدم بازنشانی",
"noteDescription": "یک یادداشت سفارشی برای شناسایی این اشتراک اضافه کنید (مثلاً 'VPN شخصی'، 'اشتراک کاری')",
"notePlaceholder": "یادداشتی برای این اشتراک وارد کنید...",
"noteUpdateFailed": "به‌روزرسانی یادداشت ناموفق بود",
"noteUpdateSuccess": "یادداشت با موفقیت به‌روزرسانی شد",
"prompt": "پیشنهاد",
"purchaseSubscription": "خرید اشتراک",
"qrCode": "کد QR",
"resetSubscription": "بازنشانی آدرس اشتراک",
"resetSuccess": "بازنشانی با موفقیت انجام شد",
"save": "ذخیره",
"scanToSubscribe": "اسکن کنید تا مشترک شوید",
"subscriptionUrl": "آدرس اشتراک",
"totalTraffic": "کل ترافیک",
Expand Down
7 changes: 7 additions & 0 deletions apps/user/locales/fi-FI/dashboard.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
{
"cancel": "Peruuta",
"characters": "merkkiä",
"confirm": "Vahvista",
"confirmResetSubscription": "Haluatko varmasti nollata tilausosoitteen?",
"copy": "kopioi",
"copyFailure": "Kopiointi epäonnistui, kopioi manuaalisesti",
"copySuccess": "Kopiointi onnistui",
"deducted": "Peruutettu",
"download": "lataa",
"editNote": "Muokkaa muistiinpanoa",
"expirationDays": "Vanhentumispäivät",
"expired": "Vanhentunut",
"finished": "Liikenne loppunut",
Expand All @@ -17,11 +19,16 @@
"nextResetDays": "Seuraava nollaus/päivää",
"noLimit": "Ei rajoitusta",
"noReset": "Ei nollata",
"noteDescription": "Lisää mukautettu muistiinpano tämän tilauksen tunnistamiseksi (esim. 'Henkilökohtainen VPN', 'Työtilaus')",
"notePlaceholder": "Kirjoita muistiinpano tälle tilaukselle...",
"noteUpdateFailed": "Muistiinpanon päivitys epäonnistui",
"noteUpdateSuccess": "Muistiinpano päivitetty onnistuneesti",
"prompt": "kehotus",
"purchaseSubscription": "Osta tilaus",
"qrCode": "QR-koodi",
"resetSubscription": "Nollaa tilausosoite",
"resetSuccess": "Nollaus onnistui",
"save": "Tallenna",
"scanToSubscribe": "Skannaa tilataksesi",
"subscriptionUrl": "Tilausosoite",
"totalTraffic": "Kokonaisliikenne",
Expand Down
7 changes: 7 additions & 0 deletions apps/user/locales/fr-FR/dashboard.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
{
"cancel": "Annuler",
"characters": "caractères",
"confirm": "Confirmer",
"confirmResetSubscription": "Confirmez-vous la réinitialisation de l'adresse d'abonnement ?",
"copy": "Copier",
"copyFailure": "Échec de la copie, veuillez copier manuellement",
"copySuccess": "Copie réussie",
"deducted": "Annulé",
"download": "télécharger",
"editNote": "Modifier la note",
"expirationDays": "Date d'expiration/jours",
"expired": "Expiré",
"finished": "Trafic épuisé",
Expand All @@ -17,11 +19,16 @@
"nextResetDays": "Prochain réinitialisation/jours",
"noLimit": "Pas de limite",
"noReset": "Pas de réinitialisation",
"noteDescription": "Ajoutez une note personnalisée pour identifier cet abonnement (par ex. 'VPN personnel', 'Abonnement professionnel')",
"notePlaceholder": "Entrez une note pour cet abonnement...",
"noteUpdateFailed": "Échec de la mise à jour de la note",
"noteUpdateSuccess": "Note mise à jour avec succès",
"prompt": "invite",
"purchaseSubscription": "Acheter un abonnement",
"qrCode": "Code QR",
"resetSubscription": "Réinitialiser l'adresse d'abonnement",
"resetSuccess": "Réinitialisation réussie",
"save": "Enregistrer",
"scanToSubscribe": "Scannez pour vous abonner",
"subscriptionUrl": "URL d'abonnement",
"totalTraffic": "Trafic total",
Expand Down
Loading