Skip to content
This repository was archived by the owner on Dec 11, 2025. It is now read-only.

Commit ba52c39

Browse files
author
lain
committed
✨ feat(ui): Add user subscribe note
1 parent 5eac6a9 commit ba52c39

File tree

29 files changed

+283
-0
lines changed

29 files changed

+283
-0
lines changed

apps/admin/services/admin/typings.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2443,6 +2443,7 @@ declare namespace API {
24432443
upload: number;
24442444
token: string;
24452445
status: number;
2446+
note: string;
24462447
created_at: number;
24472448
updated_at: number;
24482449
};
@@ -2462,6 +2463,7 @@ declare namespace API {
24622463
upload: number;
24632464
token: string;
24642465
status: number;
2466+
note: string;
24652467
created_at: number;
24662468
updated_at: number;
24672469
};

apps/user/app/(main)/(user)/dashboard/content.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client';
22

33
import { Display } from '@/components/display';
4+
import EditNote from '@/components/subscribe/edit-note';
45
import Renewal from '@/components/subscribe/renewal';
56
import ResetTraffic from '@/components/subscribe/reset-traffic';
67
import Unsubscribe from '@/components/subscribe/unsubscribe';
@@ -249,9 +250,16 @@ export default function Content() {
249250
<CardTitle className='font-medium'>
250251
{item.subscribe.name}
251252
<p className='text-foreground/50 mt-1 text-sm'>{formatDate(item.start_time)}</p>
253+
{item.note && (
254+
<p className='text-muted-foreground mt-2 flex items-center gap-1.5 text-sm font-normal'>
255+
<Icon icon='uil:file-edit-alt' className='size-4' />
256+
{item.note}
257+
</p>
258+
)}
252259
</CardTitle>
253260
{item.status !== 4 && (
254261
<div className='flex flex-wrap gap-2'>
262+
<EditNote id={item.id} currentNote={item.note} onSuccess={refetch} />
255263
<AlertDialog>
256264
<AlertDialogTrigger asChild>
257265
<Button size='sm' variant='destructive'>
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
'use client';
2+
3+
import { updateUserSubscribeNote } from '@/services/user/subscribe';
4+
import { Button } from '@workspace/ui/components/button';
5+
import {
6+
Dialog,
7+
DialogContent,
8+
DialogDescription,
9+
DialogFooter,
10+
DialogHeader,
11+
DialogTitle,
12+
DialogTrigger,
13+
} from '@workspace/ui/components/dialog';
14+
import { Textarea } from '@workspace/ui/components/textarea';
15+
import { Icon } from '@workspace/ui/custom-components/icon';
16+
import { LoaderCircle } from 'lucide-react';
17+
import { useTranslations } from 'next-intl';
18+
import { useCallback, useState, useTransition } from 'react';
19+
import { toast } from 'sonner';
20+
21+
interface EditNoteProps {
22+
id: number;
23+
currentNote?: string;
24+
onSuccess?: () => void;
25+
}
26+
27+
export default function EditNote({ id, currentNote = '', onSuccess }: Readonly<EditNoteProps>) {
28+
const t = useTranslations('dashboard');
29+
const [open, setOpen] = useState<boolean>(false);
30+
const [note, setNote] = useState<string>(currentNote);
31+
const [loading, startTransition] = useTransition();
32+
33+
const handleSubmit = useCallback(async () => {
34+
startTransition(async () => {
35+
try {
36+
await updateUserSubscribeNote({
37+
user_subscribe_id: id,
38+
note: note,
39+
});
40+
toast.success(t('noteUpdateSuccess'));
41+
setOpen(false);
42+
if (onSuccess) {
43+
onSuccess();
44+
}
45+
} catch (error) {
46+
toast.error(t('noteUpdateFailed'));
47+
}
48+
});
49+
}, [id, note, onSuccess, t]);
50+
51+
return (
52+
<Dialog open={open} onOpenChange={setOpen}>
53+
<DialogTrigger asChild>
54+
<Button size='sm' variant='outline'>
55+
<Icon icon='uil:edit' className='mr-1 size-4' />
56+
{t('editNote')}
57+
</Button>
58+
</DialogTrigger>
59+
<DialogContent className='sm:max-w-[425px]'>
60+
<DialogHeader>
61+
<DialogTitle>{t('editNote')}</DialogTitle>
62+
<DialogDescription>{t('noteDescription')}</DialogDescription>
63+
</DialogHeader>
64+
<div className='grid gap-4 py-4'>
65+
<Textarea
66+
placeholder={t('notePlaceholder')}
67+
value={note}
68+
onChange={(e) => setNote(e.target.value)}
69+
maxLength={500}
70+
rows={4}
71+
className='resize-none'
72+
/>
73+
<p className='text-muted-foreground text-xs'>
74+
{note.length}/500 {t('characters')}
75+
</p>
76+
</div>
77+
<DialogFooter>
78+
<Button variant='outline' onClick={() => setOpen(false)} disabled={loading}>
79+
{t('cancel')}
80+
</Button>
81+
<Button onClick={handleSubmit} disabled={loading}>
82+
{loading && <LoaderCircle className='mr-2 size-4 animate-spin' />}
83+
{t('save')}
84+
</Button>
85+
</DialogFooter>
86+
</DialogContent>
87+
</Dialog>
88+
);
89+
}

apps/user/locales/cs-CZ/dashboard.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
{
22
"cancel": "zrušit",
3+
"characters": "znaků",
34
"confirm": "Potvrdit",
45
"confirmResetSubscription": "Opravdu chcete obnovit adresu předplatného?",
56
"copy": "kopírovat",
67
"copyFailure": "Kopírování selhalo, prosím zkopírujte ručně",
78
"copySuccess": "Kopírování úspěšné",
89
"deducted": "Zrušeno",
910
"download": "stáhnout",
11+
"editNote": "Upravit poznámku",
1012
"expirationDays": "Doba platnosti/dny",
1113
"expired": "Vypršelo",
1214
"finished": "Provoz vyčerpán",
@@ -17,11 +19,16 @@
1719
"nextResetDays": "Příští reset/den",
1820
"noLimit": "Bez omezení",
1921
"noReset": "Žádné obnovení",
22+
"noteDescription": "Přidejte vlastní poznámku pro identifikaci tohoto předplatného (např. 'Osobní VPN', 'Pracovní předplatné')",
23+
"notePlaceholder": "Zadejte poznámku k tomuto předplatnému...",
24+
"noteUpdateFailed": "Nepodařilo se aktualizovat poznámku",
25+
"noteUpdateSuccess": "Poznámka byla úspěšně aktualizována",
2026
"prompt": "Výzva",
2127
"purchaseSubscription": "Zakoupit předplatné",
2228
"qrCode": "QR kód",
2329
"resetSubscription": "Obnovit adresu předplatného",
2430
"resetSuccess": "Obnovení bylo úspěšné",
31+
"save": "Uložit",
2532
"scanToSubscribe": "Naskenujte pro odběr",
2633
"subscriptionUrl": "Odběrná adresa",
2734
"totalTraffic": "Celkový provoz",

apps/user/locales/de-DE/dashboard.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
{
22
"cancel": "Abbrechen",
3+
"characters": "Zeichen",
34
"confirm": "Bestätigen",
45
"confirmResetSubscription": "Möchten Sie die Abonnementadresse wirklich zurücksetzen?",
56
"copy": "Kopieren",
67
"copyFailure": "Kopieren fehlgeschlagen, bitte manuell kopieren",
78
"copySuccess": "Kopieren erfolgreich",
89
"deducted": "Storniert",
910
"download": "Herunterladen",
11+
"editNote": "Notiz bearbeiten",
1012
"expirationDays": "Ablaufzeit/Tage",
1113
"expired": "Abgelaufen",
1214
"finished": "Verkehr erschöpft",
@@ -17,11 +19,16 @@
1719
"nextResetDays": "Nächster Reset/Tage",
1820
"noLimit": "Kein Limit",
1921
"noReset": "Kein Zurücksetzen",
22+
"noteDescription": "Fügen Sie eine benutzerdefinierte Notiz hinzu, um dieses Abonnement zu identifizieren (z.B. 'Persönliches VPN', 'Arbeitsabonnement')",
23+
"notePlaceholder": "Geben Sie eine Notiz für dieses Abonnement ein...",
24+
"noteUpdateFailed": "Notiz konnte nicht aktualisiert werden",
25+
"noteUpdateSuccess": "Notiz erfolgreich aktualisiert",
2026
"prompt": "Aufforderung",
2127
"purchaseSubscription": "Abonnement kaufen",
2228
"qrCode": "QR-Code",
2329
"resetSubscription": "Abonnement-Adresse zurücksetzen",
2430
"resetSuccess": "Zurücksetzen erfolgreich",
31+
"save": "Speichern",
2532
"scanToSubscribe": "Scannen zum Abonnieren",
2633
"subscriptionUrl": "Abonnement-Adresse",
2734
"totalTraffic": "Gesamtverkehr",

apps/user/locales/en-US/dashboard.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
{
22
"cancel": "Cancel",
3+
"characters": "characters",
34
"confirm": "Confirm",
45
"confirmResetSubscription": "Are you sure you want to reset the subscription address?",
56
"copy": "Copy",
67
"copyFailure": "Copy failed, please copy manually",
78
"copySuccess": "Copy successful",
89
"deducted": "Canceled",
910
"download": "Download",
11+
"editNote": "Edit Note",
1012
"expirationDays": "Expiration Days",
1113
"expired": "Expired",
1214
"finished": "Traffic exhausted",
@@ -17,11 +19,16 @@
1719
"nextResetDays": "Next Reset in Days",
1820
"noLimit": "No Limit",
1921
"noReset": "No Reset",
22+
"noteDescription": "Add a custom note to help you identify this subscription (e.g., 'Personal VPN', 'Work subscription')",
23+
"notePlaceholder": "Enter a note for this subscription...",
24+
"noteUpdateFailed": "Failed to update note",
25+
"noteUpdateSuccess": "Note updated successfully",
2026
"prompt": "Prompt",
2127
"purchaseSubscription": "Purchase Subscription",
2228
"qrCode": "QR Code",
2329
"resetSubscription": "Reset Subscription Address",
2430
"resetSuccess": "Reset successful",
31+
"save": "Save",
2532
"scanToSubscribe": "Scan to Subscribe",
2633
"subscriptionUrl": "Subscription URL",
2734
"totalTraffic": "Total Traffic",

apps/user/locales/es-ES/dashboard.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
{
22
"cancel": "Cancelar",
3+
"characters": "caracteres",
34
"confirm": "Confirmar",
45
"confirmResetSubscription": "¿Confirmar el restablecimiento de la dirección de suscripción?",
56
"copy": "Copiar",
67
"copyFailure": "Error al copiar, por favor copia manualmente",
78
"copySuccess": "Copia exitosa",
89
"deducted": "Cancelado",
910
"download": "descargar",
11+
"editNote": "Editar nota",
1012
"expirationDays": "Días de vencimiento",
1113
"expired": "Caducado",
1214
"finished": "Tráfico agotado",
@@ -17,11 +19,16 @@
1719
"nextResetDays": "Próximo reinicio/días",
1820
"noLimit": "Sin límite",
1921
"noReset": "Sin Reinicio",
22+
"noteDescription": "Añade una nota personalizada para identificar esta suscripción (ej. 'VPN Personal', 'Suscripción de trabajo')",
23+
"notePlaceholder": "Introduce una nota para esta suscripción...",
24+
"noteUpdateFailed": "Error al actualizar la nota",
25+
"noteUpdateSuccess": "Nota actualizada con éxito",
2026
"prompt": "sugerencia",
2127
"purchaseSubscription": "Comprar suscripción",
2228
"qrCode": "Código QR",
2329
"resetSubscription": "Restablecer dirección de suscripción",
2430
"resetSuccess": "Restablecimiento exitoso",
31+
"save": "Guardar",
2532
"scanToSubscribe": "Escanear para suscribirse",
2633
"subscriptionUrl": "Dirección de suscripción",
2734
"totalTraffic": "Tráfico total",

apps/user/locales/es-MX/dashboard.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
{
22
"cancel": "Cancelar",
3+
"characters": "caracteres",
34
"confirm": "Confirmar",
45
"confirmResetSubscription": "¿Confirma que desea restablecer la dirección de suscripción?",
56
"copy": "copiar",
67
"copyFailure": "Error al copiar, por favor copia manualmente",
78
"copySuccess": "Copia exitosa",
89
"deducted": "Cancelado",
910
"download": "descargar",
11+
"editNote": "Editar nota",
1012
"expirationDays": "Días de vencimiento",
1113
"expired": "Expirado",
1214
"finished": "Tráfico agotado",
@@ -17,11 +19,16 @@
1719
"nextResetDays": "Próximo reinicio/días",
1820
"noLimit": "Sin Límite",
1921
"noReset": "Sin Reinicio",
22+
"noteDescription": "Añade una nota personalizada para identificar esta suscripción (ej. 'VPN Personal', 'Suscripción de trabajo')",
23+
"notePlaceholder": "Introduce una nota para esta suscripción...",
24+
"noteUpdateFailed": "Error al actualizar la nota",
25+
"noteUpdateSuccess": "Nota actualizada con éxito",
2026
"prompt": "Sugerencia",
2127
"purchaseSubscription": "Comprar suscripción",
2228
"qrCode": "Código QR",
2329
"resetSubscription": "Restablecer dirección de suscripción",
2430
"resetSuccess": "Restablecimiento exitoso",
31+
"save": "Guardar",
2532
"scanToSubscribe": "Escanear para suscribirse",
2633
"subscriptionUrl": "URL de suscripción",
2734
"totalTraffic": "Tráfico total",

apps/user/locales/fa-IR/dashboard.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
{
22
"cancel": "لغو",
3+
"characters": "نویسه",
34
"confirm": "تأیید",
45
"confirmResetSubscription": "آیا مطمئن هستید که می‌خواهید آدرس اشتراک را بازنشانی کنید؟",
56
"copy": "کپی",
67
"copyFailure": "کپی ناموفق بود، لطفاً به صورت دستی کپی کنید",
78
"copySuccess": "کپی با موفقیت انجام شد",
89
"deducted": "لغو شده",
910
"download": "دانلود",
11+
"editNote": "ویرایش یادداشت",
1012
"expirationDays": "روزهای انقضا",
1113
"expired": "منقضی شده",
1214
"finished": "ترافیک تمام شده",
@@ -17,11 +19,16 @@
1719
"nextResetDays": "روزهای باقی‌مانده تا بازنشانی بعدی",
1820
"noLimit": "بدون محدودیت",
1921
"noReset": "عدم بازنشانی",
22+
"noteDescription": "یک یادداشت سفارشی برای شناسایی این اشتراک اضافه کنید (مثلاً 'VPN شخصی'، 'اشتراک کاری')",
23+
"notePlaceholder": "یادداشتی برای این اشتراک وارد کنید...",
24+
"noteUpdateFailed": "به‌روزرسانی یادداشت ناموفق بود",
25+
"noteUpdateSuccess": "یادداشت با موفقیت به‌روزرسانی شد",
2026
"prompt": "پیشنهاد",
2127
"purchaseSubscription": "خرید اشتراک",
2228
"qrCode": "کد QR",
2329
"resetSubscription": "بازنشانی آدرس اشتراک",
2430
"resetSuccess": "بازنشانی با موفقیت انجام شد",
31+
"save": "ذخیره",
2532
"scanToSubscribe": "اسکن کنید تا مشترک شوید",
2633
"subscriptionUrl": "آدرس اشتراک",
2734
"totalTraffic": "کل ترافیک",

apps/user/locales/fi-FI/dashboard.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
{
22
"cancel": "Peruuta",
3+
"characters": "merkkiä",
34
"confirm": "Vahvista",
45
"confirmResetSubscription": "Haluatko varmasti nollata tilausosoitteen?",
56
"copy": "kopioi",
67
"copyFailure": "Kopiointi epäonnistui, kopioi manuaalisesti",
78
"copySuccess": "Kopiointi onnistui",
89
"deducted": "Peruutettu",
910
"download": "lataa",
11+
"editNote": "Muokkaa muistiinpanoa",
1012
"expirationDays": "Vanhentumispäivät",
1113
"expired": "Vanhentunut",
1214
"finished": "Liikenne loppunut",
@@ -17,11 +19,16 @@
1719
"nextResetDays": "Seuraava nollaus/päivää",
1820
"noLimit": "Ei rajoitusta",
1921
"noReset": "Ei nollata",
22+
"noteDescription": "Lisää mukautettu muistiinpano tämän tilauksen tunnistamiseksi (esim. 'Henkilökohtainen VPN', 'Työtilaus')",
23+
"notePlaceholder": "Kirjoita muistiinpano tälle tilaukselle...",
24+
"noteUpdateFailed": "Muistiinpanon päivitys epäonnistui",
25+
"noteUpdateSuccess": "Muistiinpano päivitetty onnistuneesti",
2026
"prompt": "kehotus",
2127
"purchaseSubscription": "Osta tilaus",
2228
"qrCode": "QR-koodi",
2329
"resetSubscription": "Nollaa tilausosoite",
2430
"resetSuccess": "Nollaus onnistui",
31+
"save": "Tallenna",
2532
"scanToSubscribe": "Skannaa tilataksesi",
2633
"subscriptionUrl": "Tilausosoite",
2734
"totalTraffic": "Kokonaisliikenne",

0 commit comments

Comments
 (0)