2026-06-02 18:59:07 +03:30
"use client" ;
import { useState } from "react" ;
const inp = "w-full rounded-lg border border-[#262b40] bg-[#0c0e1a] px-3 py-2 text-sm text-gray-100 outline-none focus:border-indigo-500" ;
const lbl = "mb-1 block text-xs font-medium text-gray-400" ;
const btn = "rounded-lg bg-indigo-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-indigo-500 disabled:opacity-50" ;
const card = "rounded-xl border border-[#1e2235] bg-[#0f1120]" ;
/** Per-user admin power-actions + CRM notes, opened as a modal from the Users table. */
export function UserActions ( { row } : { row : Record < string , unknown > ; reload ? : ( ) = > void } ) {
const id = String ( row . id ) ;
const [ open , setOpen ] = useState ( false ) ;
const [ msg , setMsg ] = useState < string | null > ( null ) ;
const [ busy , setBusy ] = useState ( false ) ;
// form state
const [ balance , setBalance ] = useState ( "" ) ; const [ balanceAdd , setBalanceAdd ] = useState ( true ) ;
const [ pw , setPw ] = useState ( "" ) ;
const [ seconds , setSeconds ] = useState ( "" ) ; const [ renders , setRenders ] = useState ( "" ) ;
const [ planDays , setPlanDays ] = useState ( "" ) ;
const [ tags , setTags ] = useState ( "" ) ; const [ note , setNote ] = useState ( "" ) ; const [ status , setStatus ] = useState ( "new" ) ;
2026-06-02 22:42:01 +03:30
// discount / affiliate
const [ dcCode , setDcCode ] = useState ( "" ) ; const [ dcKind , setDcKind ] = useState ( "Percentage" ) ;
const [ dcValue , setDcValue ] = useState ( "" ) ; const [ dcProfit , setDcProfit ] = useState ( "" ) ; const [ dcDays , setDcDays ] = useState ( "30" ) ;
// videos
const [ vids , setVids ] = useState < Array < Record < string , unknown > > | null > ( null ) ;
const createDiscount = async ( ) = > {
setBusy ( true ) ; setMsg ( null ) ;
const expires = new Date ( Date . now ( ) + ( Number ( dcDays ) || 30 ) * 864 e5 ) . toISOString ( ) ;
const res = await fetch ( ` /api/admin/resource/discounts ` , {
method : "POST" , headers : { "Content-Type" : "application/json" } ,
body : JSON.stringify ( {
name : dcCode || ` user- ${ id . slice ( 0 , 8 ) } ` , code : dcCode , kind : dcKind , value : Number ( dcValue ) || 0 ,
owner_user_id : id , owner_profit_percentage : Number ( dcProfit ) || 0 , expires_at : expires ,
} ) ,
} ) ;
const d = await res . json ( ) . catch ( ( ) = > null ) ;
setMsg ( res . ok ? ( Number ( dcProfit ) > 0 ? "کد افیلیت ساخته شد ✓" : "کد تخفیف ساخته شد ✓" ) : ( d ? . error ? . message ? ? d ? . error ? ? "خطا" ) ) ;
setBusy ( false ) ;
} ;
const loadVideos = async ( ) = > {
const r = await fetch ( ` /api/admin/resource/saved-projects/by-user/ ${ id } ?pageSize=50 ` , { cache : "no-store" } )
. then ( ( x ) = > x . json ( ) ) . catch ( ( ) = > null ) ;
setVids ( r ? . data ? ? r ? . items ? ? ( Array . isArray ( r ) ? r : [ ] ) ) ;
} ;
2026-06-02 18:59:07 +03:30
const call = async ( path : string , body : object , ok : string ) = > {
setBusy ( true ) ; setMsg ( null ) ;
const res = await fetch ( ` /api/admin/resource/ ${ path } ` , {
method : "POST" , headers : { "Content-Type" : "application/json" } , body : JSON.stringify ( body ) ,
} ) ;
const d = await res . json ( ) . catch ( ( ) = > null ) ;
setMsg ( res . ok ? ok : ( d ? . error ? . message ? ? d ? . error ? ? "خطا" ) ) ;
setBusy ( false ) ;
} ;
const loadCrm = async ( ) = > {
const r = await fetch ( ` /api/admin/resource/users/ ${ id } /crm ` , { cache : "no-store" } ) . then ( ( x ) = > x . json ( ) ) . catch ( ( ) = > null ) ;
if ( r ) { setTags ( ( r . tags ? ? [ ] ) . join ( ", " ) ) ; setNote ( r . note ? ? "" ) ; setStatus ( r . status ? ? "new" ) ; }
} ;
const saveCrm = async ( ) = > {
setBusy ( true ) ; setMsg ( null ) ;
const res = await fetch ( ` /api/admin/resource/users/ ${ id } /crm ` , {
method : "PUT" , headers : { "Content-Type" : "application/json" } ,
body : JSON.stringify ( { tags : tags.split ( "," ) . map ( ( t ) = > t . trim ( ) ) . filter ( Boolean ) , note , status } ) ,
} ) ;
setMsg ( res . ok ? "یادداشت ذخیره شد ✓" : "خطا" ) ; setBusy ( false ) ;
} ;
return (
< >
< button className = "rounded-lg border border-[#262b40] px-3 py-1.5 text-xs text-gray-300 hover:bg-[#161a2e]" onClick = { ( ) = > { setOpen ( true ) ; setMsg ( null ) ; loadCrm ( ) ; } } > م د ی ر ی ت < / button >
{ open && (
< div className = "fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4 text-right" dir = "rtl" onClick = { ( ) = > setOpen ( false ) } >
< div className = { ` ${ card } max-h-[85vh] w-full max-w-lg overflow-y-auto p-5 ` } onClick = { ( e ) = > e . stopPropagation ( ) } >
< h2 className = "text-sm font-semibold text-white" > م د ی ر ی ت ک ا ر ب ر : { String ( row . email ? ? row . full_name ? ? id ) } < / h2 >
{ msg && < p className = "mt-2 rounded-lg bg-[#12152a] px-3 py-2 text-xs text-gray-300" > { msg } < / p > }
< div className = "mt-4 space-y-4" >
< div className = { ` ${ card } p-3 ` } >
< label className = { lbl } > م و ج و د ی ( ر ی ا ل ) < / label >
< div className = "flex gap-2" >
< input className = { inp } type = "number" value = { balance } onChange = { ( e ) = > setBalance ( e . target . value ) } / >
< label className = "flex items-center gap-1 whitespace-nowrap text-xs text-gray-400" > < input type = "checkbox" checked = { balanceAdd } onChange = { ( e ) = > setBalanceAdd ( e . target . checked ) } / > ا ف ز و د ن < / label >
< button className = { btn } disabled = { busy || ! balance } onClick = { ( ) = > call ( ` users/ ${ id } /balance ` , { amount_minor : Number ( balance ) , add : balanceAdd } , "موجودی بهروزرسانی شد ✓" ) } > ا ع م ا ل < / button >
< / div >
< / div >
< div className = { ` ${ card } p-3 ` } >
< label className = { lbl } > ش ا ر ژ ر ن د ر < / label >
< div className = "flex flex-wrap gap-2" >
< input className = { ` ${ inp } max-w-[120px] ` } type = "number" placeholder = "ثانیه" value = { seconds } onChange = { ( e ) = > setSeconds ( e . target . value ) } / >
< input className = { ` ${ inp } max-w-[120px] ` } type = "number" placeholder = "تعداد رندر" value = { renders } onChange = { ( e ) = > setRenders ( e . target . value ) } / >
< button className = { btn } disabled = { busy || ( ! seconds && ! renders ) } onClick = { ( ) = > call ( ` users/ ${ id } /charge ` , { seconds : Number ( seconds ) || 0 , render_count : Number ( renders ) || 0 } , "شارژ اضافه شد ✓" ) } > ا ف ز و د ن < / button >
< / div >
< / div >
< div className = { ` ${ card } p-3 ` } >
< label className = { lbl } > ت م د ی د پ ل ن ( ر و ز ) < / label >
< div className = "flex gap-2" >
< input className = { inp } type = "number" value = { planDays } onChange = { ( e ) = > setPlanDays ( e . target . value ) } / >
< button className = { btn } disabled = { busy || ! planDays } onClick = { ( ) = > call ( ` users/ ${ id } /grant-plan ` , { plan_id : "00000000-0000-0000-0000-000000000000" , days : Number ( planDays ) } , "پلن تمدید شد ✓" ) } > ت م د ی د < / button >
< / div >
< / div >
< div className = { ` ${ card } p-3 ` } >
< label className = { lbl } > ت غ ی ی ر ر م ز ع ب و ر < / label >
< div className = "flex gap-2" >
< input className = { inp } type = "text" value = { pw } onChange = { ( e ) = > setPw ( e . target . value ) } placeholder = "رمز جدید" / >
< button className = { btn } disabled = { busy || pw . length < 8 } onClick = { ( ) = > call ( ` users/ ${ id } /password ` , { new_password : pw } , "رمز تغییر کرد ✓" ) } > ت غ ی ی ر < / button >
< / div >
< / div >
< div className = { ` ${ card } flex items-center justify-between p-3 ` } >
< span className = "text-sm text-gray-300" > د س ت ر س ی م د ی ر ( م د ر ا ت و ر ) < / span >
< div className = "flex gap-2" >
< button className = { btn } disabled = { busy } onClick = { ( ) = > call ( ` users/ ${ id } /moderator ` , { enabled : true } , "مدیر شد ✓" ) } > ا ع ط ا < / button >
< button className = "rounded-lg border border-[#262b40] px-3 py-1.5 text-xs text-gray-300 hover:bg-[#161a2e]" disabled = { busy } onClick = { ( ) = > call ( ` users/ ${ id } /moderator ` , { enabled : false } , "لغو شد ✓" ) } > ل غ و < / button >
< / div >
< / div >
2026-06-02 22:42:01 +03:30
< div className = { ` ${ card } p-3 ` } >
< label className = { lbl } > ک د ت خ ف ی ف / ا ف ی ل ی ت ( د ر ص د س و د & gt ; ۰ ی ع ن ی ا ف ی ل ی ت ) < / label >
< div className = "grid gap-2 sm:grid-cols-2" >
< input className = { inp } placeholder = "کد" value = { dcCode } onChange = { ( e ) = > setDcCode ( e . target . value ) } / >
< select className = { inp } value = { dcKind } onChange = { ( e ) = > setDcKind ( e . target . value ) } >
< option value = "Percentage" > د ر ص د ی < / option >
< option value = "FixedAmount" > م ب ل غ ث ا ب ت < / option >
< option value = "RenderCredits" > ا ع ت ب ا ر ر ن د ر < / option >
< / select >
< input className = { inp } type = "number" placeholder = "مقدار" value = { dcValue } onChange = { ( e ) = > setDcValue ( e . target . value ) } / >
< input className = { inp } type = "number" placeholder = "٪ سود افیلیت" value = { dcProfit } onChange = { ( e ) = > setDcProfit ( e . target . value ) } / >
< input className = { inp } type = "number" placeholder = "اعتبار (روز)" value = { dcDays } onChange = { ( e ) = > setDcDays ( e . target . value ) } / >
< / div >
< button className = { ` ${ btn } mt-2 ` } disabled = { busy || ! dcCode || ! dcValue } onClick = { createDiscount } > س ا خ ت ک د < / button >
< / div >
< div className = { ` ${ card } p-3 ` } >
< div className = "flex items-center justify-between" >
< label className = { lbl } > و ی د ی و ه ا ی ک ا ر ب ر < / label >
< button className = "rounded-lg border border-[#262b40] px-2.5 py-1 text-xs text-gray-300 hover:bg-[#161a2e]" onClick = { loadVideos } > ب ا ر گ ذ ا ر ی < / button >
< / div >
{ vids != null && (
vids . length === 0 ? < p className = "text-xs text-gray-500" > و ی د ی و ی ی ی ا ف ت ن ش د . < / p > : (
< ul className = "mt-1 max-h-40 space-y-1 overflow-y-auto text-sm text-gray-300" >
{ vids . map ( ( v ) = > (
< li key = { String ( v . id ) } className = "flex items-center justify-between rounded bg-[#0c0e1a] px-2 py-1" >
< span className = "truncate" > { String ( v . name ? ? v . original_project_name ? ? "—" ) } < / span >
< span className = "text-[11px] text-gray-500" > { String ( v . type ? ? "" ) } · { String ( v . resolution ? ? "" ) } < / span >
< / li >
) ) }
< / ul >
)
) }
< / div >
2026-06-02 18:59:07 +03:30
< div className = { ` ${ card } p-3 ` } >
< label className = { lbl } > ی ا د د ا ش ت CRM < / label >
< div className = "grid gap-2" >
< input className = { inp } placeholder = "برچسبها (با کاما)" value = { tags } onChange = { ( e ) = > setTags ( e . target . value ) } / >
< select className = { inp } value = { status } onChange = { ( e ) = > setStatus ( e . target . value ) } >
< option value = "new" > ج د ی د < / option > < option value = "contacted" > ت م ا س گ ر ف ت ه < / option > < option value = "customer" > م ش ت ر ی < / option > < option value = "churned" > ر ی ز ش ک ر د ه < / option >
< / select >
< textarea className = { ` ${ inp } min-h-[60px] ` } placeholder = "یادداشت" value = { note } onChange = { ( e ) = > setNote ( e . target . value ) } / >
< div > < button className = { btn } disabled = { busy } onClick = { saveCrm } > ذ خ ی ر ه ی ا د د ا ش ت < / button > < / div >
< / div >
< / div >
< / div >
< div className = "mt-4 flex justify-end" >
< button className = "rounded-lg border border-[#262b40] px-4 py-2 text-sm text-gray-300 hover:bg-[#161a2e]" onClick = { ( ) = > setOpen ( false ) } > ب س ت ن < / button >
< / div >
< / div >
< / div >
) }
< / >
) ;
}