diff --git a/code/modules/economy/ATM.dm b/code/modules/economy/ATM.dm
index f5690ccdfa..704700139f 100644
--- a/code/modules/economy/ATM.dm
+++ b/code/modules/economy/ATM.dm
@@ -123,330 +123,327 @@ log transactions
else
..()
+/obj/machinery/atm/tgui_status(mob/user)
+ . = ..()
+ if(issilicon(user))
+ return STATUS_CLOSE
+
+/obj/machinery/atm/tgui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui, custom_state)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "AutomatedTellerMachine", name)
+ ui.open()
+
+/obj/machinery/atm/tgui_static_data(mob/user)
+ var/list/data = ..()
+ data["machine_id"] = machine_id
+ return data
+
+/obj/machinery/atm/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state)
+ var/list/data = ..()
+
+ data["emagged"] = emagged
+ if(emagged > 0)
+ return data
+
+ data["held_card"] = held_card
+ data["locked_down"] = ticks_left_locked_down
+ if(ticks_left_locked_down > 0)
+ return data
+
+ data["authenticated_account"] = null
+ data["suspended"] = FALSE
+ if(authenticated_account)
+ if(authenticated_account.suspended)
+ data["suspended"] = TRUE
+ return data
+
+ var/list/transactions = list()
+ for(var/datum/transaction/T as anything in authenticated_account.transaction_log)
+ UNTYPED_LIST_ADD(transactions, list(
+ "date" = T.date,
+ "time" = T.time,
+ "target_name" = T.target_name,
+ "purpose" = T.purpose,
+ "amount" = T.amount,
+ "source_terminal" = T.source_terminal
+ ))
+
+ data["authenticated_account"] = list(
+ "owner_name" = authenticated_account.owner_name,
+ "money" = authenticated_account.money,
+ "security_level" = authenticated_account.security_level,
+ "transactions" = transactions,
+ )
+
+ return data
+
+/obj/machinery/atm/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ // This is also a logout
+ if("insert_card")
+ if(held_card)
+ release_held_id(usr)
+ else
+ if(emagged > 0)
+ to_chat(usr, span_red("[icon2html(src, usr.client)] The ATM card reader rejected your ID because this machine has been sabotaged!"))
+ else
+ var/obj/item/I = usr.get_active_hand()
+ if(istype(I, /obj/item/card/id))
+ usr.drop_item(src)
+ held_card = I
+ . = TRUE
+
+ if("logout")
+ if(held_card)
+ release_held_id(usr)
+ authenticated_account = null
+ . = TRUE
+
+ // Balance statement
+ if("balance_statement")
+ if(!authenticated_account)
+ return
+
+ var/obj/item/paper/R = new(loc)
+ R.name = "Account balance: [authenticated_account.owner_name]"
+ R.info = "NT Automated Teller Account Statement "
+ R.info += "Account holder: [authenticated_account.owner_name] "
+ R.info += "Account number: [authenticated_account.account_number] "
+ R.info += "Balance: $[authenticated_account.money] "
+ R.info += "Date and time: [stationtime2text()], [current_date_string] "
+ R.info += "Service terminal ID: [machine_id] "
+
+ //stamp the paper
+ var/image/stampoverlay = image('icons/obj/bureaucracy.dmi')
+ stampoverlay.icon_state = "paper_stamp-cent"
+ if(!R.stamped)
+ R.stamped = new
+ R.stamped += /obj/item/stamp
+ R.add_overlay(stampoverlay)
+ R.stamps += "
This paper has been stamped by the Automatic Teller Machine. "
+
+ if(prob(50))
+ playsound(src, 'sound/items/polaroid1.ogg', 50, 1)
+ else
+ playsound(src, 'sound/items/polaroid2.ogg', 50, 1)
+ . = TRUE
+
+ // Transaction logs
+ if("print_transaction")
+ if(!authenticated_account)
+ return
+
+ var/obj/item/paper/R = new(loc)
+ R.name = "Transaction logs: [authenticated_account.owner_name]"
+ R.info = "Transaction logs "
+ R.info += "Account holder: [authenticated_account.owner_name] "
+ R.info += "Account number: [authenticated_account.account_number] "
+ R.info += "Date and time: [stationtime2text()], [current_date_string] "
+ R.info += "Service terminal ID: [machine_id] "
+ R.info += ""
+ R.info += ""
+ R.info += "Date "
+ R.info += "Time "
+ R.info += "Target "
+ R.info += "Purpose "
+ R.info += "Value "
+ R.info += "Source terminal ID "
+ R.info += " "
+ for(var/datum/transaction/T in authenticated_account.transaction_log)
+ R.info += ""
+ R.info += "[T.date] "
+ R.info += "[T.time] "
+ R.info += "[T.target_name] "
+ R.info += "[T.purpose] "
+ R.info += "$[T.amount] "
+ R.info += "[T.source_terminal] "
+ R.info += " "
+ R.info += "
"
+
+ //stamp the paper
+ var/image/stampoverlay = image('icons/obj/bureaucracy.dmi')
+ stampoverlay.icon_state = "paper_stamp-cent"
+ if(!R.stamped)
+ R.stamped = new
+ R.stamped += /obj/item/stamp
+ R.add_overlay(stampoverlay)
+ R.stamps += "This paper has been stamped by the Automatic Teller Machine. "
+
+ if(prob(50))
+ playsound(src, 'sound/items/polaroid1.ogg', 50, 1)
+ else
+ playsound(src, 'sound/items/polaroid2.ogg', 50, 1)
+ . = TRUE
+
+ if("change_security_level")
+ if(authenticated_account)
+ var/new_sec_level = clamp(text2num(params["new_security_level"]), 0, 2)
+ authenticated_account.security_level = new_sec_level
+ . = TRUE
+
+ if("attempt_auth")
+ if(ticks_left_locked_down)
+ return
+ var/tried_account_num = held_card ? held_card.associated_account_number : text2num(params["account_num"])
+ var/tried_pin = text2num(params["account_pin"])
+
+ // check if they have low security enabled
+ if(!tried_account_num)
+ scan_user(usr)
+ else
+ authenticated_account = attempt_account_access(tried_account_num, tried_pin, held_card && held_card.associated_account_number == tried_account_num ? 2 : 1)
+
+ if(!authenticated_account)
+ number_incorrect_tries++
+ if(previous_account_number == tried_account_num)
+ if(number_incorrect_tries > max_pin_attempts)
+ //lock down the atm
+ ticks_left_locked_down = 30
+ playsound(src, 'sound/machines/buzz-two.ogg', 50, 1)
+
+ //create an entry in the account transaction log
+ var/datum/money_account/failed_account = get_account(tried_account_num)
+ if(failed_account)
+ var/datum/transaction/T = new()
+ T.target_name = failed_account.owner_name
+ T.purpose = "Unauthorised login attempt"
+ T.source_terminal = machine_id
+ T.date = current_date_string
+ T.time = stationtime2text()
+ failed_account.transaction_log.Add(T)
+ else
+ to_chat(usr, span_red("[icon2html(src, usr.client)] Incorrect pin/account combination entered, [max_pin_attempts - number_incorrect_tries] attempts remaining."))
+ previous_account_number = tried_account_num
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 50, 1)
+ else
+ to_chat(usr, span_red("[icon2html(src, usr.client)] incorrect pin/account combination entered."))
+ number_incorrect_tries = 0
+ else
+ playsound(src, 'sound/machines/twobeep.ogg', 50, 1)
+ ticks_left_timeout = 120
+ view_screen = NO_SCREEN
+
+ //create a transaction log entry
+ var/datum/transaction/T = new()
+ T.target_name = authenticated_account.owner_name
+ T.purpose = "Remote terminal access"
+ T.source_terminal = machine_id
+ T.date = current_date_string
+ T.time = stationtime2text()
+ authenticated_account.transaction_log.Add(T)
+
+ to_chat(usr, span_blue("[icon2html(src, usr.client)] Access granted. Welcome user '[authenticated_account.owner_name].'"))
+
+ previous_account_number = tried_account_num
+ . = TRUE
+
+ if("transfer")
+ if(!authenticated_account)
+ return
+ var/transfer_amount = text2num(params["funds_amount"])
+ transfer_amount = round(transfer_amount, 0.01)
+ if(transfer_amount <= 0)
+ tgui_alert_async(usr, "That is not a valid amount.")
+ else if(transfer_amount <= authenticated_account.money)
+ var/target_account_number = text2num(params["target_acc_number"])
+ var/transfer_purpose = params["purpose"]
+ if(charge_to_account(target_account_number, authenticated_account.owner_name, transfer_purpose, machine_id, transfer_amount))
+ to_chat(usr, "[icon2html(src, usr.client)]Funds transfer successful. ")
+ authenticated_account.money -= transfer_amount
+
+ //create an entry in the account transaction log
+ var/datum/transaction/T = new()
+ T.target_name = "Account #[target_account_number]"
+ T.purpose = transfer_purpose
+ T.source_terminal = machine_id
+ T.date = current_date_string
+ T.time = stationtime2text()
+ T.amount = "([transfer_amount])"
+ authenticated_account.transaction_log.Add(T)
+ else
+ to_chat(usr, "[icon2html(src, usr.client)]Funds transfer failed. ")
+
+ else
+ to_chat(usr, "[icon2html(src, usr.client)]You don't have enough funds to do that! ")
+ . = TRUE
+
+ if("e_withdrawal")
+ var/amount = max(text2num(params["funds_amount"]),0)
+ amount = round(amount, 0.01)
+ if(amount <= 0)
+ tgui_alert_async(usr, "That is not a valid amount.")
+ return
+
+ if(!authenticated_account)
+ return
+
+ if(amount <= authenticated_account.money)
+ playsound(src, 'sound/machines/chime.ogg', 50, 1)
+
+ //remove the money
+ authenticated_account.money -= amount
+
+ // spawn_money(amount,src.loc)
+ spawn_ewallet(amount,src.loc,usr)
+
+ //create an entry in the account transaction log
+ var/datum/transaction/T = new()
+ T.target_name = authenticated_account.owner_name
+ T.purpose = "Credit withdrawal"
+ T.amount = "([amount])"
+ T.source_terminal = machine_id
+ T.date = current_date_string
+ T.time = stationtime2text()
+ authenticated_account.transaction_log.Add(T)
+ else
+ to_chat(usr, "[icon2html(src, usr.client)]You don't have enough funds to do that! ")
+ . = TRUE
+
+ if("withdrawal")
+ var/amount = max(text2num(params["funds_amount"]),0)
+ amount = round(amount, 0.01)
+ if(amount <= 0)
+ tgui_alert_async(usr, "That is not a valid amount.")
+ return
+
+ if(!authenticated_account)
+ return
+
+ if(amount <= authenticated_account.money)
+ playsound(src, 'sound/machines/chime.ogg', 50, 1)
+
+ //remove the money
+ authenticated_account.money -= amount
+
+ spawn_money(amount,src.loc,usr)
+
+ //create an entry in the account transaction log
+ var/datum/transaction/T = new()
+ T.target_name = authenticated_account.owner_name
+ T.purpose = "Credit withdrawal"
+ T.amount = "([amount])"
+ T.source_terminal = machine_id
+ T.date = current_date_string
+ T.time = stationtime2text()
+ authenticated_account.transaction_log.Add(T)
+ else
+ to_chat(usr, "[icon2html(src, usr.client)]You don't have enough funds to do that! ")
+ . = TRUE
+
+ if(.)
+ playsound(src, "keyboard", 50, TRUE)
+
/obj/machinery/atm/attack_hand(mob/user as mob)
if(istype(user, /mob/living/silicon))
to_chat (user, span_warning("A firewall prevents you from interfacing with this device!"))
return
if(get_dist(src,user) <= 1)
-
- //js replicated from obj/machinery/computer/card
- var/dat = "Automatic Teller Machine "
- dat += "For all your monetary needs! "
- dat += "This terminal is [machine_id]. Report this code when contacting IT Support "
-
- if(emagged > 0)
- dat += "Card: LOCKED Unauthorized terminal access detected! This ATM has been locked. Please contact IT Support. "
- else
- dat += "Card: [held_card ? held_card.name : "------"] "
-
- if(ticks_left_locked_down > 0)
- dat += span_warning("Maximum number of pin attempts exceeded! Access to this ATM has been temporarily disabled.")
- else if(authenticated_account)
- if(authenticated_account.suspended)
- dat += span_red(span_bold("Access to this account has been suspended, and the funds within frozen."))
- else
- switch(view_screen)
- if(CHANGE_SECURITY_LEVEL)
- dat += "Select a new security level for this account: "
- var/text = "Zero - Either the account number or card is required to access this account. EFTPOS transactions will require a card and ask for a pin, but not verify the pin is correct."
- if(authenticated_account.security_level != 0)
- text = "[text] "
- dat += "[text] "
- text = "One - An account number and pin must be manually entered to access this account and process transactions."
- if(authenticated_account.security_level != 1)
- text = "[text] "
- dat += "[text] "
- text = "Two - In addition to account number and pin, a card is required to access this account and process transactions."
- if(authenticated_account.security_level != 2)
- text = "[text] "
- dat += "[text] "
- dat += "Back "
- if(VIEW_TRANSACTION_LOGS)
- dat += "Transaction logs "
- dat += "Back "
- dat += ""
- dat += ""
- dat += "Date "
- dat += "Time "
- dat += "Target "
- dat += "Purpose "
- dat += "Value "
- dat += "Source terminal ID "
- dat += " "
- for(var/datum/transaction/T in authenticated_account.transaction_log)
- dat += ""
- dat += "[T.date] "
- dat += "[T.time] "
- dat += "[T.target_name] "
- dat += "[T.purpose] "
- dat += "$[T.amount] "
- dat += "[T.source_terminal] "
- dat += " "
- dat += "
"
- dat += "Print "
- if(TRANSFER_FUNDS)
- dat += "Account balance: $[authenticated_account.money] "
- dat += "Back "
- dat += ""
- else
- dat += "Welcome, [authenticated_account.owner_name]. "
- dat += "Account balance: $[authenticated_account.money]"
- dat += ""
- dat += "Change account security level "
- dat += "Make transfer "
- dat += "View transaction log "
- dat += "Print balance statement "
- dat += "Logout "
- else
- dat += ""
-
- user << browse(dat,"window=atm;size=550x650")
- else
- user << browse(null,"window=atm")
-
-/obj/machinery/atm/Topic(var/href, var/href_list)
- if(href_list["choice"])
- switch(href_list["choice"])
- if("transfer")
- if(authenticated_account)
- var/transfer_amount = text2num(href_list["funds_amount"])
- transfer_amount = round(transfer_amount, 0.01)
- if(transfer_amount <= 0)
- tgui_alert_async(usr, "That is not a valid amount.")
- else if(transfer_amount <= authenticated_account.money)
- var/target_account_number = text2num(href_list["target_acc_number"])
- var/transfer_purpose = href_list["purpose"]
- if(charge_to_account(target_account_number, authenticated_account.owner_name, transfer_purpose, machine_id, transfer_amount))
- to_chat(usr, "[icon2html(src, usr.client)]Funds transfer successful. ")
- authenticated_account.money -= transfer_amount
-
- //create an entry in the account transaction log
- var/datum/transaction/T = new()
- T.target_name = "Account #[target_account_number]"
- T.purpose = transfer_purpose
- T.source_terminal = machine_id
- T.date = current_date_string
- T.time = stationtime2text()
- T.amount = "([transfer_amount])"
- authenticated_account.transaction_log.Add(T)
- else
- to_chat(usr, "[icon2html(src, usr.client)]Funds transfer failed. ")
-
- else
- to_chat(usr, "[icon2html(src, usr.client)]You don't have enough funds to do that! ")
- if("view_screen")
- view_screen = text2num(href_list["view_screen"])
- if("change_security_level")
- if(authenticated_account)
- var/new_sec_level = max( min(text2num(href_list["new_security_level"]), 2), 0)
- authenticated_account.security_level = new_sec_level
- if("attempt_auth")
- if(!ticks_left_locked_down)
- var/tried_account_num = held_card ? held_card.associated_account_number : text2num(href_list["account_num"])
- var/tried_pin = text2num(href_list["account_pin"])
-
- // check if they have low security enabled
-
- if (!tried_account_num)
- scan_user(usr)
- else
- authenticated_account = attempt_account_access(tried_account_num, tried_pin, held_card && held_card.associated_account_number == tried_account_num ? 2 : 1)
-
- if(!authenticated_account)
- number_incorrect_tries++
- if(previous_account_number == tried_account_num)
- if(number_incorrect_tries > max_pin_attempts)
- //lock down the atm
- ticks_left_locked_down = 30
- playsound(src, 'sound/machines/buzz-two.ogg', 50, 1)
-
- //create an entry in the account transaction log
- var/datum/money_account/failed_account = get_account(tried_account_num)
- if(failed_account)
- var/datum/transaction/T = new()
- T.target_name = failed_account.owner_name
- T.purpose = "Unauthorised login attempt"
- T.source_terminal = machine_id
- T.date = current_date_string
- T.time = stationtime2text()
- failed_account.transaction_log.Add(T)
- else
- to_chat(usr, span_red("[icon2html(src, usr.client)] Incorrect pin/account combination entered, [max_pin_attempts - number_incorrect_tries] attempts remaining."))
- previous_account_number = tried_account_num
- playsound(src, 'sound/machines/buzz-sigh.ogg', 50, 1)
- else
- to_chat(usr, span_red("[icon2html(src, usr.client)] incorrect pin/account combination entered."))
- number_incorrect_tries = 0
- else
- playsound(src, 'sound/machines/twobeep.ogg', 50, 1)
- ticks_left_timeout = 120
- view_screen = NO_SCREEN
-
- //create a transaction log entry
- var/datum/transaction/T = new()
- T.target_name = authenticated_account.owner_name
- T.purpose = "Remote terminal access"
- T.source_terminal = machine_id
- T.date = current_date_string
- T.time = stationtime2text()
- authenticated_account.transaction_log.Add(T)
-
- to_chat(usr, span_blue("[icon2html(src, usr.client)] Access granted. Welcome user '[authenticated_account.owner_name].'"))
-
- previous_account_number = tried_account_num
- if("e_withdrawal")
- var/amount = max(text2num(href_list["funds_amount"]),0)
- amount = round(amount, 0.01)
- if(amount <= 0)
- tgui_alert_async(usr, "That is not a valid amount.")
- else if(authenticated_account && amount > 0)
- if(amount <= authenticated_account.money)
- playsound(src, 'sound/machines/chime.ogg', 50, 1)
-
- //remove the money
- authenticated_account.money -= amount
-
- // spawn_money(amount,src.loc)
- spawn_ewallet(amount,src.loc,usr)
-
- //create an entry in the account transaction log
- var/datum/transaction/T = new()
- T.target_name = authenticated_account.owner_name
- T.purpose = "Credit withdrawal"
- T.amount = "([amount])"
- T.source_terminal = machine_id
- T.date = current_date_string
- T.time = stationtime2text()
- authenticated_account.transaction_log.Add(T)
- else
- to_chat(usr, "[icon2html(src, usr.client)]You don't have enough funds to do that! ")
- if("withdrawal")
- var/amount = max(text2num(href_list["funds_amount"]),0)
- amount = round(amount, 0.01)
- if(amount <= 0)
- tgui_alert_async(usr, "That is not a valid amount.")
- else if(authenticated_account && amount > 0)
- if(amount <= authenticated_account.money)
- playsound(src, 'sound/machines/chime.ogg', 50, 1)
-
- //remove the money
- authenticated_account.money -= amount
-
- spawn_money(amount,src.loc,usr)
-
- //create an entry in the account transaction log
- var/datum/transaction/T = new()
- T.target_name = authenticated_account.owner_name
- T.purpose = "Credit withdrawal"
- T.amount = "([amount])"
- T.source_terminal = machine_id
- T.date = current_date_string
- T.time = stationtime2text()
- authenticated_account.transaction_log.Add(T)
- else
- to_chat(usr, "[icon2html(src, usr.client)]You don't have enough funds to do that! ")
- if("balance_statement")
- if(authenticated_account)
- var/obj/item/paper/R = new(src.loc)
- R.name = "Account balance: [authenticated_account.owner_name]"
- R.info = "NT Automated Teller Account Statement "
- R.info += "Account holder: [authenticated_account.owner_name] "
- R.info += "Account number: [authenticated_account.account_number] "
- R.info += "Balance: $[authenticated_account.money] "
- R.info += "Date and time: [stationtime2text()], [current_date_string] "
- R.info += "Service terminal ID: [machine_id] "
-
- //stamp the paper
- var/image/stampoverlay = image('icons/obj/bureaucracy.dmi')
- stampoverlay.icon_state = "paper_stamp-cent"
- if(!R.stamped)
- R.stamped = new
- R.stamped += /obj/item/stamp
- R.add_overlay(stampoverlay)
- R.stamps += "This paper has been stamped by the Automatic Teller Machine. "
-
- if(prob(50))
- playsound(src, 'sound/items/polaroid1.ogg', 50, 1)
- else
- playsound(src, 'sound/items/polaroid2.ogg', 50, 1)
- if ("print_transaction")
- if(authenticated_account)
- var/obj/item/paper/R = new(src.loc)
- R.name = "Transaction logs: [authenticated_account.owner_name]"
- R.info = "Transaction logs "
- R.info += "Account holder: [authenticated_account.owner_name] "
- R.info += "Account number: [authenticated_account.account_number] "
- R.info += "Date and time: [stationtime2text()], [current_date_string] "
- R.info += "Service terminal ID: [machine_id] "
- R.info += ""
- R.info += ""
- R.info += "Date "
- R.info += "Time "
- R.info += "Target "
- R.info += "Purpose "
- R.info += "Value "
- R.info += "Source terminal ID "
- R.info += " "
- for(var/datum/transaction/T in authenticated_account.transaction_log)
- R.info += ""
- R.info += "[T.date] "
- R.info += "[T.time] "
- R.info += "[T.target_name] "
- R.info += "[T.purpose] "
- R.info += "$[T.amount] "
- R.info += "[T.source_terminal] "
- R.info += " "
- R.info += "
"
-
- //stamp the paper
- var/image/stampoverlay = image('icons/obj/bureaucracy.dmi')
- stampoverlay.icon_state = "paper_stamp-cent"
- if(!R.stamped)
- R.stamped = new
- R.stamped += /obj/item/stamp
- R.add_overlay(stampoverlay)
- R.stamps += "This paper has been stamped by the Automatic Teller Machine. "
-
- if(prob(50))
- playsound(src, 'sound/items/polaroid1.ogg', 50, 1)
- else
- playsound(src, 'sound/items/polaroid2.ogg', 50, 1)
-
- if("insert_card")
- if(!held_card)
- //this might happen if the user had the browser window open when somebody emagged it
- if(emagged > 0)
- to_chat(usr, span_red("[icon2html(src, usr.client)] The ATM card reader rejected your ID because this machine has been sabotaged!"))
- else
- var/obj/item/I = usr.get_active_hand()
- if (istype(I, /obj/item/card/id))
- usr.drop_item()
- I.loc = src
- held_card = I
- else
- release_held_id(usr)
- if("logout")
- authenticated_account = null
- //usr << browse(null,"window=atm")
-
- src.attack_hand(usr)
+ tgui_interact(user)
//stolen wholesale and then edited a bit from newscasters, which are awesome and by Agouri
/obj/machinery/atm/proc/scan_user(mob/living/carbon/human/human_user as mob)
diff --git a/tgui/packages/tgui/interfaces/AutomatedTellerMachine.tsx b/tgui/packages/tgui/interfaces/AutomatedTellerMachine.tsx
new file mode 100644
index 0000000000..ef1b517299
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/AutomatedTellerMachine.tsx
@@ -0,0 +1,702 @@
+import { useEffect, useState } from 'react';
+import { useBackend } from 'tgui/backend';
+import { Window } from 'tgui/layouts';
+import {
+ Box,
+ Button,
+ DmIcon,
+ Icon,
+ Input,
+ LabeledList,
+ Section,
+ Stack,
+ Table,
+} from 'tgui-core/components';
+
+export type Transaction = {
+ date: string;
+ time: number;
+ target_name: string;
+ purpose: string;
+ amount: number;
+ source_terminal: string;
+};
+
+export type Account = {
+ owner_name: string;
+ money: number;
+ security_level: number;
+ transactions: Transaction[];
+};
+
+export type AutomatedTellerMachineData = {
+ machine_id: string;
+ emagged: number;
+ held_card: string | null;
+ locked_down: number;
+ suspended: boolean;
+ authenticated_account: Account;
+};
+
+export const AutomatedTellerMachine = (props) => {
+ const { act, data } = useBackend();
+
+ return (
+
+
+
+ {data.authenticated_account ? (
+
+ ) : (
+
+ )}
+
+
+
+ );
+};
+
+enum Menu {
+ Main = 'main',
+ Withdraw = 'withdraw',
+ Balance = 'balance',
+ Transfer = 'transfer',
+ More = 'more',
+}
+
+const AuthenticatedScreen = (props) => {
+ const [menu, setMenu] = useState(Menu.Main);
+
+ switch (menu) {
+ case Menu.Main: {
+ return ;
+ }
+ case Menu.Withdraw: {
+ return ;
+ }
+ case Menu.Balance: {
+ return ;
+ }
+ case Menu.Transfer: {
+ return ;
+ }
+ case Menu.More: {
+ return ;
+ }
+ }
+};
+
+const MainMenu = (props: {
+ setMenu: React.Dispatch>;
+}) => {
+ const { setMenu } = props;
+ const { act, data } = useBackend();
+
+ return (
+
+
+
+
+ setMenu(Menu.Withdraw)}>
+
+ Withdraw
+
+
+
+ setMenu(Menu.More)}>
+
+ More Services
+
+
+
+
+
+
+
+ setMenu(Menu.Balance)}>
+ Balance
+
+
+
+
+ setMenu(Menu.Transfer)}>
+ Transfer
+
+
+
+
+ act('logout')}>
+ {data.held_card ? 'Return Card' : 'Logout'}
+
+
+
+
+
+
+ );
+};
+
+const WithdrawMenu = (props: {
+ setMenu: React.Dispatch>;
+}) => {
+ const { act, data } = useBackend();
+ const { setMenu } = props;
+ const [custom, setCustom] = useState(false);
+ const [useCard, setUseCard] = useState(false);
+
+ const withdrawFunction = (val) => {
+ if (useCard) {
+ act('e_withdrawal', { funds_amount: val });
+ } else {
+ act('withdrawal', { funds_amount: val });
+ }
+ setMenu(Menu.Main);
+ };
+
+ if (custom) {
+ return (
+
+ );
+ }
+
+ return (
+ setMenu(Menu.Main)}>
+ Back To Main Menu
+
+ }
+ >
+
+ Account Balance: ${data.authenticated_account.money}
+
+
+
+
+ setUseCard(false)}
+ >
+ Cash
+
+
+
+ setUseCard(true)}
+ >
+ Chargecard
+
+
+
+
+
+
+
+
+ withdrawFunction(10)}>
+ $10
+
+
+
+ withdrawFunction(100)}>
+ $100
+
+
+
+ withdrawFunction(500)}>
+ $500
+
+
+
+
+
+
+
+ withdrawFunction(50)}>
+ $50
+
+
+
+ withdrawFunction(200)}>
+ $200
+
+
+
+ setCustom(true)}>
+ Other
+
+
+
+
+
+
+ );
+};
+
+const CustomWithdrawal = (props: {
+ withdrawFunction: (val: number) => void;
+ useCard: boolean;
+ setCustom: React.Dispatch>;
+ setMenu: React.Dispatch>;
+}) => {
+ const { act, data } = useBackend();
+ const { withdrawFunction, useCard, setCustom, setMenu } = props;
+ const [money, setMoney] = useState(1000);
+ return (
+
+ );
+};
+
+const BalanceMenu = (props: {
+ setMenu: React.Dispatch>;
+}) => {
+ const { act, data } = useBackend();
+ const { setMenu } = props;
+ return (
+ setMenu(Menu.Main)}>
+ Back To Main Menu
+
+ }
+ fill
+ >
+
+
+
+ Current Funds: ${data.authenticated_account.money}
+
+
+
+ act('balance_statement')}
+ >
+ Print Balance Statement
+
+ act('print_transaction')}>
+ Print Transactions
+
+
+
+
+
+ );
+};
+
+const TransactionLog = (props: { transactions: Transaction[] }) => {
+ const { transactions } = props;
+
+ return (
+
+
+
+ Date
+ Time
+ Target
+ Purpose
+ Value
+ Source Terminal ID
+
+ {transactions.map((transaction, index) => (
+
+ {transaction.date}
+ {transaction.time}
+ {transaction.target_name}
+ {transaction.purpose}
+ ${transaction.amount}
+ {transaction.source_terminal}
+
+ ))}
+
+
+ );
+};
+
+const MoreMenu = (props: {
+ setMenu: React.Dispatch>;
+}) => {
+ const { act, data } = useBackend();
+ const { setMenu } = props;
+ return (
+ setMenu(Menu.Main)}>
+ Back To Main Menu
+
+ }
+ >
+
+ Account Security Settings
+
+ act('change_security_level', { new_security_level: 0 })}
+ >
+ Zero - Either the account number or card is required to access this
+ account. EFTPOS transactions will require a card and ask for a pin, but
+ not verify the pin is correct.
+
+ act('change_security_level', { new_security_level: 1 })}
+ >
+ One - An account number and pin must be manually entered to access this
+ account and process transactions.
+
+ act('change_security_level', { new_security_level: 2 })}
+ >
+ Two - In addition to account number and pin, a card is required to
+ access this account and process transactions.
+
+
+ );
+};
+
+const TransferMenu = (props: {
+ setMenu: React.Dispatch>;
+}) => {
+ const { act, data } = useBackend();
+ const [accountNum, setAccountNum] = useState(100000);
+ const updateAccountNum = (val) => {
+ let newVal = parseInt(val, 10);
+ if (isNaN(newVal)) {
+ setAccountNum(100000);
+ } else {
+ setAccountNum(newVal);
+ }
+ };
+
+ const [money, setMoney] = useState(0);
+ const updateMoney = (val) => {
+ let newVal = parseInt(val, 10);
+ if (isNaN(newVal)) {
+ setMoney(0);
+ } else {
+ setMoney(newVal);
+ }
+ };
+
+ const [purpose, setPurpose] = useState('Funds transfer');
+
+ const { setMenu } = props;
+ return (
+
+ );
+};
+
+const LoginScreen = (props: { machine_id: string; card: string | null }) => {
+ const { act } = useBackend();
+ const { machine_id, card } = props;
+
+ const [account, setAccount] = useState('');
+ const [pin, setPin] = useState('');
+
+ return (
+
+
+
+
+ act('insert_card')}
+ >
+
+
+
+
+
+ {card ? card : 'Insert ID'}
+
+
+
+
+
+
+
+ setAccount(val)}
+ onInput={(e, val) => setAccount(val)}
+ />
+
+
+ setPin(val)}
+ onInput={(e, val) => setPin(val)}
+ />
+
+
+
+ act('attempt_auth', {
+ account_num: account,
+ account_pin: pin,
+ })
+ }
+ >
+
+
+
+
+
+ Log In
+
+
+
+
+
+
+ act('insert_card')}
+ style={{ cursor: 'pointer' }}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export const CardSlot = (props) => {
+ return (
+
+ {/* Top */}
+
+ {/* Faceplate */}
+
+ {/* Slot */}
+
+ {/* Arrows */}
+
+
+ {/* Card */}
+
+ {/* Slot */}
+
+ {/* Card Outline */}
+
+ {/* Chip */}
+
+
+
+ );
+};
+
+export const AnimatedIDCard = (props) => {
+ const [glitch, setGlitch] = useState(false);
+
+ useEffect(() => {
+ if (Math.random() * 100 < 1) {
+ // 1% chance
+ setGlitch(true);
+ }
+ }, []);
+
+ return (
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/styles/interfaces/AutomatedTellerMachine.scss b/tgui/packages/tgui/styles/interfaces/AutomatedTellerMachine.scss
new file mode 100644
index 0000000000..0ca797eab4
--- /dev/null
+++ b/tgui/packages/tgui/styles/interfaces/AutomatedTellerMachine.scss
@@ -0,0 +1,42 @@
+.AutomatedTellerMachine__Card {
+ animation: cardAnim 0.75s linear 0s infinite normal both;
+}
+
+@keyframes cardAnim {
+ 0% {
+ transform: translate(0, 40px);
+ }
+
+ 25% {
+ transform: translate(0, -32px);
+ }
+
+ 100% {
+ transform: translate(0, 40px);
+ }
+}
+
+.AutomatedTellerMachine__Card--glitch {
+ animation: cardAnimGlitch 0.2s linear 0s infinite normal both;
+ transform-origin: 'center';
+}
+
+@keyframes cardAnimGlitch {
+ 0% {
+ transform: translate(0, 40px) rotate(-45deg);
+ }
+
+ 25% {
+ transform: translate(0, -32px) rotate(45deg);
+ }
+
+ 100% {
+ transform: translate(0, 40px) rotate(-45deg);
+ }
+}
+
+.AutomatedTellerMachine__Table,
+.AutomatedTellerMachine__Table td {
+ border: 4px solid #aaa;
+ border-style: ridge;
+}
diff --git a/tgui/packages/tgui/styles/main.scss b/tgui/packages/tgui/styles/main.scss
index effa918048..7945c9ca7e 100644
--- a/tgui/packages/tgui/styles/main.scss
+++ b/tgui/packages/tgui/styles/main.scss
@@ -45,6 +45,7 @@
@include meta.load-css('./components/Tooltip.scss');
// Interfaces
+@include meta.load-css('./interfaces/AutomatedTellerMachine.scss');
@include meta.load-css('./interfaces/ListInput.scss');
@include meta.load-css('./interfaces/InputModal.scss');
@include meta.load-css('./interfaces/IntegratedCircuit.scss');