1
0
Fork 0
forked from Simnation/Main
This commit is contained in:
Nordi98 2025-08-05 10:47:16 +02:00
parent 30bef7f1a5
commit 9e0a584816
52 changed files with 11959 additions and 0 deletions

View file

@ -0,0 +1,459 @@
Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)
https://creativecommons.org/licenses/by-nc-sa/4.0/
This is a human-readable summary of (and not a substitute for) the license. Disclaimer.
You are free to:
Share — copy and redistribute the material in any medium or format
Adapt — remix, transform, and build upon the material
The licensor cannot revoke these freedoms as long as you follow the license terms.
Under the following terms:
Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
NonCommercial — You may not use the material for commercial purposes.
ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.
No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
Notices:
You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation.
No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material.
Creative Commons Corporation ("Creative Commons") is not a law firm and
does not provide legal services or legal advice. Distribution of
Creative Commons public licenses does not create a lawyer-client or
other relationship. Creative Commons makes its licenses and related
information available on an "as-is" basis. Creative Commons gives no
warranties regarding its licenses, any material licensed under their
terms and conditions, or any related information. Creative Commons
disclaims all liability for damages resulting from their use to the
fullest extent possible.
Using Creative Commons Public Licenses
Creative Commons public licenses provide a standard set of terms and
conditions that creators and other rights holders may use to share
original works of authorship and other material subject to copyright
and certain other rights specified in the public license below. The
following considerations are for informational purposes only, are not
exhaustive, and do not form part of our licenses.
Considerations for licensors: Our public licenses are
intended for use by those authorized to give the public
permission to use material in ways otherwise restricted by
copyright and certain other rights. Our licenses are
irrevocable. Licensors should read and understand the terms
and conditions of the license they choose before applying it.
Licensors should also secure all rights necessary before
applying our licenses so that the public can reuse the
material as expected. Licensors should clearly mark any
material not subject to the license. This includes other CC-
licensed material, or material used under an exception or
limitation to copyright. More considerations for licensors:
wiki.creativecommons.org/Considerations_for_licensors
Considerations for the public: By using one of our public
licenses, a licensor grants the public permission to use the
licensed material under specified terms and conditions. If
the licensor's permission is not necessary for any reason--for
example, because of any applicable exception or limitation to
copyright--then that use is not regulated by the license. Our
licenses grant only permissions under copyright and certain
other rights that a licensor has authority to grant. Use of
the licensed material may still be restricted for other
reasons, including because others have copyright or other
rights in the material. A licensor may make special requests,
such as asking that all changes be marked or described.
Although not required by our licenses, you are encouraged to
respect those requests where reasonable. More considerations
for the public:
wiki.creativecommons.org/Considerations_for_licensees
=======================================================================
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
Public License
By exercising the Licensed Rights (defined below), You accept and agree
to be bound by the terms and conditions of this Creative Commons
Attribution-NonCommercial-ShareAlike 4.0 International Public License
("Public License"). To the extent this Public License may be
interpreted as a contract, You are granted the Licensed Rights in
consideration of Your acceptance of these terms and conditions, and the
Licensor grants You such rights in consideration of benefits the
Licensor receives from making the Licensed Material available under
these terms and conditions.
Section 1 -- Definitions.
a. Adapted Material means material subject to Copyright and Similar
Rights that is derived from or based upon the Licensed Material
and in which the Licensed Material is translated, altered,
arranged, transformed, or otherwise modified in a manner requiring
permission under the Copyright and Similar Rights held by the
Licensor. For purposes of this Public License, where the Licensed
Material is a musical work, performance, or sound recording,
Adapted Material is always produced where the Licensed Material is
synched in timed relation with a moving image.
b. Adapter's License means the license You apply to Your Copyright
and Similar Rights in Your contributions to Adapted Material in
accordance with the terms and conditions of this Public License.
c. BY-NC-SA Compatible License means a license listed at
creativecommons.org/compatiblelicenses, approved by Creative
Commons as essentially the equivalent of this Public License.
d. Copyright and Similar Rights means copyright and/or similar rights
closely related to copyright including, without limitation,
performance, broadcast, sound recording, and Sui Generis Database
Rights, without regard to how the rights are labeled or
categorized. For purposes of this Public License, the rights
specified in Section 2(b)(1)-(2) are not Copyright and Similar
Rights.
e. Effective Technological Measures means those measures that, in the
absence of proper authority, may not be circumvented under laws
fulfilling obligations under Article 11 of the WIPO Copyright
Treaty adopted on December 20, 1996, and/or similar international
agreements.
f. Exceptions and Limitations means fair use, fair dealing, and/or
any other exception or limitation to Copyright and Similar Rights
that applies to Your use of the Licensed Material.
g. License Elements means the license attributes listed in the name
of a Creative Commons Public License. The License Elements of this
Public License are Attribution, NonCommercial, and ShareAlike.
h. Licensed Material means the artistic or literary work, database,
or other material to which the Licensor applied this Public
License.
i. Licensed Rights means the rights granted to You subject to the
terms and conditions of this Public License, which are limited to
all Copyright and Similar Rights that apply to Your use of the
Licensed Material and that the Licensor has authority to license.
j. Licensor means the individual(s) or entity(ies) granting rights
under this Public License.
k. NonCommercial means not primarily intended for or directed towards
commercial advantage or monetary compensation. For purposes of
this Public License, the exchange of the Licensed Material for
other material subject to Copyright and Similar Rights by digital
file-sharing or similar means is NonCommercial provided there is
no payment of monetary compensation in connection with the
exchange.
l. Share means to provide material to the public by any means or
process that requires permission under the Licensed Rights, such
as reproduction, public display, public performance, distribution,
dissemination, communication, or importation, and to make material
available to the public including in ways that members of the
public may access the material from a place and at a time
individually chosen by them.
m. Sui Generis Database Rights means rights other than copyright
resulting from Directive 96/9/EC of the European Parliament and of
the Council of 11 March 1996 on the legal protection of databases,
as amended and/or succeeded, as well as other essentially
equivalent rights anywhere in the world.
n. You means the individual or entity exercising the Licensed Rights
under this Public License. Your has a corresponding meaning.
Section 2 -- Scope.
a. License grant.
1. Subject to the terms and conditions of this Public License,
the Licensor hereby grants You a worldwide, royalty-free,
non-sublicensable, non-exclusive, irrevocable license to
exercise the Licensed Rights in the Licensed Material to:
a. reproduce and Share the Licensed Material, in whole or
in part, for NonCommercial purposes only; and
b. produce, reproduce, and Share Adapted Material for
NonCommercial purposes only.
2. Exceptions and Limitations. For the avoidance of doubt, where
Exceptions and Limitations apply to Your use, this Public
License does not apply, and You do not need to comply with
its terms and conditions.
3. Term. The term of this Public License is specified in Section
6(a).
4. Media and formats; technical modifications allowed. The
Licensor authorizes You to exercise the Licensed Rights in
all media and formats whether now known or hereafter created,
and to make technical modifications necessary to do so. The
Licensor waives and/or agrees not to assert any right or
authority to forbid You from making technical modifications
necessary to exercise the Licensed Rights, including
technical modifications necessary to circumvent Effective
Technological Measures. For purposes of this Public License,
simply making modifications authorized by this Section 2(a)
(4) never produces Adapted Material.
5. Downstream recipients.
a. Offer from the Licensor -- Licensed Material. Every
recipient of the Licensed Material automatically
receives an offer from the Licensor to exercise the
Licensed Rights under the terms and conditions of this
Public License.
b. Additional offer from the Licensor -- Adapted Material.
Every recipient of Adapted Material from You
automatically receives an offer from the Licensor to
exercise the Licensed Rights in the Adapted Material
under the conditions of the Adapter's License You apply.
c. No downstream restrictions. You may not offer or impose
any additional or different terms or conditions on, or
apply any Effective Technological Measures to, the
Licensed Material if doing so restricts exercise of the
Licensed Rights by any recipient of the Licensed
Material.
6. No endorsement. Nothing in this Public License constitutes or
may be construed as permission to assert or imply that You
are, or that Your use of the Licensed Material is, connected
with, or sponsored, endorsed, or granted official status by,
the Licensor or others designated to receive attribution as
provided in Section 3(a)(1)(A)(i).
b. Other rights.
1. Moral rights, such as the right of integrity, are not
licensed under this Public License, nor are publicity,
privacy, and/or other similar personality rights; however, to
the extent possible, the Licensor waives and/or agrees not to
assert any such rights held by the Licensor to the limited
extent necessary to allow You to exercise the Licensed
Rights, but not otherwise.
2. Patent and trademark rights are not licensed under this
Public License.
3. To the extent possible, the Licensor waives any right to
collect royalties from You for the exercise of the Licensed
Rights, whether directly or through a collecting society
under any voluntary or waivable statutory or compulsory
licensing scheme. In all other cases the Licensor expressly
reserves any right to collect such royalties, including when
the Licensed Material is used other than for NonCommercial
purposes.
Section 3 -- License Conditions.
Your exercise of the Licensed Rights is expressly made subject to the
following conditions.
a. Attribution.
1. If You Share the Licensed Material (including in modified
form), You must:
a. retain the following if it is supplied by the Licensor
with the Licensed Material:
i. identification of the creator(s) of the Licensed
Material and any others designated to receive
attribution, in any reasonable manner requested by
the Licensor (including by pseudonym if
designated);
ii. a copyright notice;
iii. a notice that refers to this Public License;
iv. a notice that refers to the disclaimer of
warranties;
v. a URI or hyperlink to the Licensed Material to the
extent reasonably practicable;
b. indicate if You modified the Licensed Material and
retain an indication of any previous modifications; and
c. indicate the Licensed Material is licensed under this
Public License, and include the text of, or the URI or
hyperlink to, this Public License.
2. You may satisfy the conditions in Section 3(a)(1) in any
reasonable manner based on the medium, means, and context in
which You Share the Licensed Material. For example, it may be
reasonable to satisfy the conditions by providing a URI or
hyperlink to a resource that includes the required
information.
3. If requested by the Licensor, You must remove any of the
information required by Section 3(a)(1)(A) to the extent
reasonably practicable.
b. ShareAlike.
In addition to the conditions in Section 3(a), if You Share
Adapted Material You produce, the following conditions also apply.
1. The Adapter's License You apply must be a Creative Commons
license with the same License Elements, this version or
later, or a BY-NC-SA Compatible License.
2. You must include the text of, or the URI or hyperlink to, the
Adapter's License You apply. You may satisfy this condition
in any reasonable manner based on the medium, means, and
context in which You Share Adapted Material.
3. You may not offer or impose any additional or different terms
or conditions on, or apply any Effective Technological
Measures to, Adapted Material that restrict exercise of the
rights granted under the Adapter's License You apply.
Section 4 -- Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that
apply to Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
to extract, reuse, reproduce, and Share all or a substantial
portion of the contents of the database for NonCommercial purposes
only;
b. if You include all or a substantial portion of the database
contents in a database in which You have Sui Generis Database
Rights, then the database in which You have Sui Generis Database
Rights (but not its individual contents) is Adapted Material,
including for purposes of Section 3(b); and
c. You must comply with the conditions in Section 3(a) if You Share
all or a substantial portion of the contents of the database.
For the avoidance of doubt, this Section 4 supplements and does not
replace Your obligations under this Public License where the Licensed
Rights include other Copyright and Similar Rights.
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
c. The disclaimer of warranties and limitation of liability provided
above shall be interpreted in a manner that, to the extent
possible, most closely approximates an absolute disclaimer and
waiver of all liability.
Section 6 -- Term and Termination.
a. This Public License applies for the term of the Copyright and
Similar Rights licensed here. However, if You fail to comply with
this Public License, then Your rights under this Public License
terminate automatically.
b. Where Your right to use the Licensed Material has terminated under
Section 6(a), it reinstates:
1. automatically as of the date the violation is cured, provided
it is cured within 30 days of Your discovery of the
violation; or
2. upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any
right the Licensor may have to seek remedies for Your violations
of this Public License.
c. For the avoidance of doubt, the Licensor may also offer the
Licensed Material under separate terms or conditions or stop
distributing the Licensed Material at any time; however, doing so
will not terminate this Public License.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
License.
Section 7 -- Other Terms and Conditions.
a. The Licensor shall not be bound by any additional or different
terms or conditions communicated by You unless expressly agreed.
b. Any arrangements, understandings, or agreements regarding the
Licensed Material not stated herein are separate from and
independent of the terms and conditions of this Public License.
Section 8 -- Interpretation.
a. For the avoidance of doubt, this Public License does not, and
shall not be interpreted to, reduce, limit, restrict, or impose
conditions on any use of the Licensed Material that could lawfully
be made without permission under this Public License.
b. To the extent possible, if any provision of this Public License is
deemed unenforceable, it shall be automatically reformed to the
minimum extent necessary to make it enforceable. If the provision
cannot be reformed, it shall be severed from this Public License
without affecting the enforceability of the remaining terms and
conditions.
c. No term or condition of this Public License will be waived and no
failure to comply consented to unless expressly agreed to by the
Licensor.
d. Nothing in this Public License constitutes or may be interpreted
as a limitation upon, or waiver of, any privileges and immunities
that apply to the Licensor or You, including from the legal
processes of any jurisdiction or authority.
=======================================================================
Creative Commons is not a party to its public
licenses. Notwithstanding, Creative Commons may elect to apply one of
its public licenses to material it publishes and in those instances
will be considered the “Licensor.” The text of the Creative Commons
public licenses is dedicated to the public domain under the CC0 Public
Domain Dedication. Except for the limited purpose of indicating that
material is shared under a Creative Commons public license or as
otherwise permitted by the Creative Commons policies published at
creativecommons.org/policies, Creative Commons does not authorize the
use of the trademark "Creative Commons" or any other trademark or logo
of Creative Commons without its prior written consent including,
without limitation, in connection with any unauthorized modifications
to any of its public licenses or any other arrangements,
understandings, or agreements concerning use of licensed material. For
the avoidance of doubt, this paragraph does not form part of the
public licenses.
Creative Commons may be contacted at creativecommons.org.

View file

@ -0,0 +1,54 @@
# ps-banking
Compatible with QBCore and ESX.
# Depedency
1. [qb-core](https://github.com/qbcore-framework/qb-core) or [ESX](https://github.com/esx-framework)
2. [ox_lib](https://github.com/overextended/ox_lib)
# Installation
* Download release files.
* Drag and drop resource into your server files.
* Start resource through server.cfg.
* Add the ps-banking sql file to your database.
* Restart your server.
## Exports
### Create Bill
```bash
# Creates a bill invoice in the bank
exports["ps-banking"]:createBill({
identifier = "HVZ84591", -- citizen id
description = "Utility Bill",
type = "Expense",
amount = 150.00,
})
```
# Features
### Overview Tab:
Includes all essential features such as managing your bills, withdrawing all money, depositing cash, transferring money weekly via Simmy, viewing the latest transactions, and handling unpaid invoices.
![image](https://github.com/user-attachments/assets/7f22afa7-f4d8-427a-b9eb-42ef8b660801)
### Bills
Enables you to send and receive bills.
![image](https://github.com/user-attachments/assets/6d51ffb3-992c-4032-986c-c033c694302a)
### History
Displays a history of all transactions with options to delete specific transactions.
![image](https://github.com/user-attachments/assets/7beabc27-304a-402b-89e4-7d338140e498)
### Stats and Reports
Provides a summary of all transactions made, including total amounts, current balance, and transaction trends by date and amount.
![image](https://github.com/user-attachments/assets/879f0a59-818d-4a4e-a204-c1be4fc22057)
### Accounts
Allows you to create, add, or remove accounts, rename accounts, and perform deposits or withdrawals from specific accounts.
![image](https://github.com/user-attachments/assets/3ec1d109-1346-4148-aa17-f869972f2001)
### ATM Access
Deposit and withdraw from ATMs.
![image](https://github.com/user-attachments/assets/49c135aa-295c-40ed-aa15-962a939e36ae)
# Credits
* [BachPB](https://github.com/BachPB)

View file

@ -0,0 +1,363 @@
local framework = nil
RegisterNUICallback("ps-banking:client:getLocales", function(_, cb)
cb(lib.getLocales())
end)
RegisterNUICallback("ps-banking:client:hideUI", function(_, cb)
SetNuiFocus(false, false)
cb({})
end)
RegisterNUICallback("ps-banking:client:phoneOption", function(_, cb)
cb(Config.LBPhone)
end)
-- Banks
RegisterNetEvent('ps-banking:client:open:bank')
AddEventHandler('ps-banking:client:open:bank', function()
Citizen.Wait(100)
SendNUIMessage({
action = "openBank",
})
SetNuiFocus(true, true)
end)
Citizen.CreateThread(function()
local zoneId = 1
for _, location in pairs(Config.BankLocations.Coords) do
local zoneName = "bank_" .. zoneId
if Config.TargetSystem == "interact" then
exports.interact:AddInteraction({
coords = vec3(location.x, location.y, location.z),
distance = 2.5,
interactDst = 2.5,
id = locale("openBank").."interact",
name = locale("openBank").."interact:name",
options = {
{
label = 'Access Bank',
action = function()
SendNUIMessage({
action = "openBank",
})
SetNuiFocus(true, true)
end,
},
}
})
elseif Config.TargetSystem == "ox_target" then
exports.ox_target:addBoxZone({
name = zoneName,
coords = vector3(location.x, location.y, location.z),
size = vec3(2, 2, 2),
options = {
{
event = "ps-banking:client:open:bank",
icon = "fas fa-credit-card",
label = locale("openBank"),
},
},
})
else
exports["qb-target"]:AddBoxZone(zoneName, vector3(location.x, location.y, location.z), 1.5, 1.6, {
name = zoneName,
heading = 0.0,
debugPoly = false,
minZ = location.z - 1,
maxZ = location.z + 1,
}, {
options = {
{
icon = "fas fa-credit-card",
label = locale("openBank"),
action = function()
SendNUIMessage({
action = "openBank",
})
SetNuiFocus(true, true)
end,
},
},
distance = 2.5,
})
zoneId = zoneId + 1
end
for i = 1, #Config.BankLocations.Coords do
local blip = AddBlipForCoord(vector3(Config.BankLocations.Coords[i].x, Config.BankLocations.Coords[i].y,
Config.BankLocations.Coords[i].z))
SetBlipSprite(blip, Config.BankLocations.Blips.sprite)
SetBlipDisplay(blip, 4)
SetBlipScale(blip, Config.BankLocations.Blips.scale)
SetBlipColour(blip, Config.BankLocations.Blips.color)
SetBlipAsShortRange(blip, true)
BeginTextCommandSetBlipName("STRING")
AddTextComponentSubstringPlayerName(Config.BankLocations.Blips.name)
EndTextCommandSetBlipName(blip)
end
end
end)
-- ATMs
RegisterNetEvent('ps-banking:client:open:atm')
AddEventHandler('ps-banking:client:open:atm', function()
Citizen.Wait(100)
ATM_Animation()
SendNUIMessage({
action = "openATM",
})
SetNuiFocus(true, true)
end)
function ATM_Animation()
lib.playAnim(cache.ped, Config.ATM_Animation.dict, Config.ATM_Animation.name, 8.0, -8.0, -1, Config.ATM_Animation.flag, 0, false, 0, false)
Wait(GetAnimDuration(Config.ATM_Animation.dict,Config.ATM_Animation.name) * 1000)
ClearPedTasks(cache.ped)
end
Citizen.CreateThread(function()
if Config.TargetSystem == "interact" then
for _, ATM_Models in ipairs(Config.ATM_Models) do
exports.interact:AddModelInteraction({
model = ATM_Models,
offset = vec3(0.0, 0.0, 1.0),
name = locale("openATM") .. "interact:name",
id = locale("openATM") .. "interact",
distance = 2.5,
interactDst = 2.5,
options = {
{
label = locale("openATM"),
event = "ps-banking:client:open:atm",
},
}
})
end
elseif Config.TargetSystem == "ox_target" then
for _, ATM_Models in ipairs(Config.ATM_Models) do
exports.ox_target:addModel(ATM_Models, {
icon = "fas fa-solid fa-money-bills",
label = locale("openATM"),
event = "ps-banking:client:open:atm",
canInteract = function(_, distance)
return distance < 2.5
end,
})
end
else
exports["qb-target"]:AddTargetModel(Config.ATM_Models, {
options = {
{
icon = "fas fa-solid fa-money-bills",
label = locale("openATM"),
action = function()
ATM_Animation()
SendNUIMessage({
action = "openATM",
})
SetNuiFocus(true, true)
end,
},
},
distance = 2.5,
})
end
end)
if GetResourceState("es_extended") == "started" then
framework = "ESX"
ESX = exports["es_extended"]:getSharedObject()
elseif GetResourceState("qb-core") == "started" then
framework = "QBCore"
QBCore = exports["qb-core"]:GetCoreObject()
else
return error(locale("no_framework_found"))
end
local function getPlayerAccounts()
local accounts = {}
if framework == "ESX" then
accounts = ESX.GetPlayerData().accounts
elseif framework == "QBCore" then
accounts = QBCore.Functions.GetPlayerData().money
end
return accounts
end
RegisterNUICallback("ps-banking:client:getBills", function(data, cb)
local success = lib.callback.await("ps-banking:server:getBills", false)
cb(success)
end)
RegisterNUICallback("ps-banking:client:payBill", function(data, cb)
local success = lib.callback.await("ps-banking:server:payBill", false, data.id)
cb(success)
end)
RegisterNUICallback("ps-banking:client:getHistory", function(data, cb)
local success = lib.callback.await("ps-banking:server:getHistory", false)
cb(success)
end)
RegisterNUICallback("ps-banking:client:deleteHistory", function(data, cb)
local success = lib.callback.await("ps-banking:server:deleteHistory", false)
cb(success)
end)
RegisterNUICallback("ps-banking:client:payAllBills", function(data, cb)
local success = lib.callback.await("ps-banking:server:payAllBills", false)
cb(success)
end)
RegisterNUICallback("ps-banking:client:getWeeklySummary", function(data, cb)
local summary = lib.callback.await("ps-banking:server:getWeeklySummary", false)
cb(summary)
end)
RegisterNUICallback("ps-banking:client:transferMoney", function(data, cb)
local success, message = lib.callback.await("ps-banking:server:transferMoney", false, data)
cb({
success = success,
message = message,
})
end)
RegisterNUICallback("ps-banking:client:getTransactionStats", function(data, cb)
local success = lib.callback.await("ps-banking:server:getTransactionStats", false)
cb(success)
end)
if framework == "ESX" then
RegisterNetEvent("esx:setAccountMoney", function(account)
local moneyData = {}
for k, v in pairs(getPlayerAccounts()) do
table.insert(moneyData, {
amount = v.money,
name = v.name == "money" and "cash" or v.name,
})
end
Wait(50)
TriggerServerEvent("ps-banking:server:logClient", account, moneyData)
end)
elseif framework == "QBCore" then
RegisterNetEvent("QBCore:Client:OnMoneyChange", function(moneyType, amount)
local moneyData = {}
for k, v in pairs(getPlayerAccounts()) do
table.insert(moneyData, {
name = k,
amount = v,
})
end
Wait(50)
TriggerServerEvent("ps-banking:server:logClient", {
name = "bank",
moneyType = moneyType,
amount = amount,
}, moneyData)
end)
end
RegisterNUICallback("ps-banking:client:createNewAccount", function(data, cb)
local success = lib.callback.await("ps-banking:server:createNewAccount", false, data.newAccount)
cb({
success = success,
})
end)
RegisterNUICallback("ps-banking:client:getUser", function(data, cb)
local success = lib.callback.await("ps-banking:server:getUser", false)
cb(success)
end)
RegisterNUICallback("ps-banking:client:getAccounts", function(data, cb)
local success = lib.callback.await("ps-banking:server:getAccounts", false)
cb(success)
end)
RegisterNUICallback("ps-banking:client:deleteAccount", function(data, cb)
local success = lib.callback.await("ps-banking:server:deleteAccount", false, data.accountId)
cb({
success = success,
})
end)
RegisterNUICallback("ps-banking:client:withdrawFromAccount", function(data, cb)
local success = lib.callback.await("ps-banking:server:withdrawFromAccount", false, data.accountId, data.amount)
cb({
success = success,
})
end)
RegisterNUICallback("ps-banking:client:depositToAccount", function(data, cb)
local success = lib.callback.await("ps-banking:server:depositToAccount", false, data.accountId, data.amount)
cb({
success = success,
})
end)
RegisterNUICallback("ps-banking:client:addUserToAccount", function(data, cb)
local success = lib.callback.await("ps-banking:server:addUserToAccount", false, data.accountId, data.userId)
cb(success)
end)
RegisterNUICallback("ps-banking:client:removeUserFromAccount", function(data, cb)
local success = lib.callback.await("ps-banking:server:removeUserFromAccount", false, data.accountId, data.user)
cb({
success = success,
})
end)
RegisterNUICallback("ps-banking:client:renameAccount", function(data, cb)
local success = lib.callback.await("ps-banking:server:renameAccount", false, data.id, data.newName)
cb({
success = success,
})
end)
RegisterNUICallback("ps-banking:client:copyAccountNumber", function(data, cb)
lib.setClipboard(data.accountNumber)
cb(true)
end)
RegisterNUICallback("ps-banking:client:ATMwithdraw", function(data, cb)
local success = lib.callback.await("ps-banking:server:ATMwithdraw", false, data.amount)
cb(success)
end)
RegisterNUICallback("ps-banking:client:ATMdeposit", function(data, cb)
local success = lib.callback.await("ps-banking:server:ATMdeposit", false, data.amount)
cb(success)
end)
RegisterNUICallback("ps-banking:client:getMoneyTypes", function(data, cb)
local moneyData = {}
if framework == "ESX" then
for k, v in pairs(getPlayerAccounts()) do
table.insert(moneyData, {
amount = v.money,
name = v.name == "money" and "cash" or v.name,
})
end
elseif framework == "QBCore" then
local PlayerData = QBCore.Functions.GetPlayerData()
for k, v in pairs(PlayerData.money) do
table.insert(moneyData, {
amount = v,
name = k,
})
end
end
--print(json.encode(moneyData))
cb(moneyData)
end)
RegisterNUICallback("ps-banking:client:getAmountPresets", function(_, cb)
cb(json.encode({
withdrawAmounts = Config.PresetATM_Amounts.Amounts,
depositAmounts = Config.PresetATM_Amounts.Amounts,
grid = Config.PresetATM_Amounts.Grid,
}))
end)

View file

@ -0,0 +1,51 @@
lib.locale()
Config = {}
Config.LBPhone = false -- Does your server use lb-phone?
Config.TargetSystem = "qb-target" -- Change to your target script like ox_target, qb-target, Or use devyn's interact.
Config.Currency = {
lang = "de", -- da-DK
currency = "USD", -- DKK
}
Config.BankLocations = {
Coords = {
vector3(149.05, -1041.3, 29.37),
vector3(313.32, -280.03, 54.17),
vector3(-351.94, -50.72, 49.04),
vector3(-1212.68, -331.83, 37.78),
vector3(-2961.67, 482.31, 15.7),
vector3(1175.64, 2707.71, 38.09),
vector3(247.65, 223.87, 106.29),
vector3(-111.98, 6470.56, 31.63),
},
Blips = {
name = "Bank",
sprite = 108,
color = 2,
scale = 0.55,
},
}
-- ATM stuff
Config.PresetATM_Amounts = { -- The preset amounts
Amounts = {
2000,
5000,
10000,
},
Grid = 3, -- How many there should be on each
}
Config.ATM_Animation = { -- Anim when opening ATM
dict = "anim@amb@prop_human_atm@interior@male@enter",
name = "enter",
flag = 49,
}
Config.ATM_Models = {
"prop_atm_01",
"prop_atm_02",
"prop_atm_03",
"prop_fleeca_atm",
}

View file

@ -0,0 +1,35 @@
fx_version 'cerulean'
game 'gta5'
name 'ps-banking'
author 'Project Sloth'
version '1.0.4'
ui_page 'html/index.html'
-- ui_page 'http://localhost:5173'
shared_scripts {
'@ox_lib/init.lua',
'config.lua',
}
client_scripts {
'client/**/*',
}
server_scripts {
'@oxmysql/lib/MySQL.lua',
'server/**/*',
}
files {
'html/**/*',
'locales/**/*',
}
dependencies {
'ox_lib',
'oxmysql'
}
lua54 'yes'

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Bach Banking</title>
<link
rel="stylesheet"
href="https://site-assets.fontawesome.com/releases/v6.6.0/css/all.css" />
<link
rel="stylesheet"
href="https://site-assets.fontawesome.com/releases/v6.6.0/css/sharp-duotone-solid.css" />
<link
rel="stylesheet"
href="https://site-assets.fontawesome.com/releases/v6.6.0/css/sharp-thin.css" />
<link
rel="stylesheet"
href="https://site-assets.fontawesome.com/releases/v6.6.0/css/sharp-solid.css" />
<link
rel="stylesheet"
href="https://site-assets.fontawesome.com/releases/v6.6.0/css/sharp-regular.css" />
<link
rel="stylesheet"
href="https://site-assets.fontawesome.com/releases/v6.6.0/css/sharp-light.css" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" />
<script type="module" crossorigin src="./index.js"></script>
<link rel="stylesheet" href="./index.css">
</head>
<body>
<div id="app"></div>
<style>
body {
width: 100vw;
height: 100vh;
}
::-webkit-scrollbar {
display: none;
}
::-webkit-outer-spin-button,
::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
</style>
</body>
</html>

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,139 @@
{
"atm": "Bankomat",
"cash": "Hotovost",
"bank_balance": "Stav na účtu",
"deposit_amount": "Částka k vkladu",
"withdraw_amount": "Částka k výběru",
"submit": "Odeslat",
"close": "Zavřít",
"overview": "Přehled",
"bills": "Faktury",
"history": "Historie",
"withdraw": "Vybrat",
"deposit": "Vložit",
"stats": "Statistiky",
"transactions": "Transakce",
"transaction": "Transakce",
"total": "Celkem",
"search_transactions": "Hledat transakce...",
"description": "Popis",
"type": "Typ",
"time_ago": "Před",
"amount": "Částka",
"date": "Datum",
"pay_invoice": "Zaplatit fakturu",
"payment_completed": "Platba dokončena",
"from": "Od",
"delete_all_transactions": "Smazat všechny transakce",
"are_you_sure": "Jsi si jistý?",
"delete_confirmation": "Opravdu chceš smazat všechny své transakce? (Udělěj to pouze, pokud se menu zpomaluje!)",
"cancel": "Zrušit",
"confirm": "Potvrdit",
"history_empty": "Tvoje historie je prázdná",
"all_history_deleted": "Všechny tvé historie byly smazány",
"error": "Chyba",
"success": "Úspěch",
"new_cash": "Nová hotovost",
"withdraw_success": "Výběr úspěšný",
"withdraw_error": "Na tvém účtu není dostatek peněz",
"withdraw_button": "VYBRAT",
"new_bank": "Nový stav účtu",
"current_cash": "Aktuální hotovost",
"deposit_success": "Vklad úspěšný",
"deposit_error": "Nemáš dostatek hotovosti",
"deposit_button": "VLOŽIT",
"total_balance": "Celkový zůstatek",
"quick_actions": "Rychlé akce",
"transfer_money": "Převést peníze",
"easy_transfer": "Snadno převést peníze lidem",
"transfer": "Převést",
"pay_bills": "Zaplatit faktury",
"pay_pending_bills": "Rychle zaplatit své neuhrazené faktury",
"pay": "Zaplatit",
"withdraw_all_money": "Vybrat všechny peníze",
"withdraw_all_from_account": "Vybrat všechny peníze z účtu",
"deposit_cash": "Vložit hotovost",
"deposit_all_cash": "Vložit všechnu hotovost na účet",
"weekly_summary": "Týdenní přehled",
"income": "Příjem",
"expenses": "Výdaje",
"report": "Zpráva",
"latest_transactions": "Poslední transakce",
"see_all": "ZOBRAZIT VŠE",
"unpaid_bills": "Nezaplacené faktury",
"no_unpaid_bills": "Žádné nezaplacené faktury",
"confirm_pay_all_bills": "Opravdu chceš zaplatit všechny své faktury?",
"pay_all_bills": "Zaplatit všechny faktury",
"pay_all_bills_success": "Všechny tvoje faktury byly zaplaceny!",
"pay_all_bills_error": "Nemáš žádné faktury",
"payment_method": "Způsob platby",
"phone_number": "Telefonní číslo",
"id": "ID",
"id_or_phone_number": "ID nebo telefonní číslo",
"no_cash_on_you": "Nemáš u sebe žádnou hotovost",
"deposit_all_success": "Všechny tvoje peníze byly vloženy",
"no_money_on_account": "Tvůj účet je prázdný",
"withdraw_all_success": "Vybral jsi všechny peníze z účtu",
"invoices": "Faktury",
"statistics_reports": "Statistiky a zprávy",
"balance_trend": "Trendy zůstatku",
"balance": "Zůstatek",
"used": "Použito",
"month": "Měsíc",
"balance_dkk": "Zůstatek",
"withdrawn": "Vybral jsi",
"deposited": "Vložil jsi",
"no_transactions": "Žádné nedávné transakce",
"transactions_trend": "Trendy transakcí",
"total_transactions": "Celkem transakcí",
"accounts": "Účty",
"account_number_copied": "Číslo účtu zkopírováno do schránky",
"new_user_to_account": "Nový uživatel na účet",
"server_id": "ID serveru",
"add_user": "Přidat uživatele",
"new_account_name": "Nový název účtu",
"new_name": "Nový název",
"rename": "Přejmenovat",
"create_new_account": "Vytvořit nový účet",
"account_holder": "Majitel účtu",
"initial_balance": "Počáteční zůstatek",
"create": "Vytvořit",
"delete_account": "Smazat účet",
"are_you_sure_you_want_to_delete_this_account": "Opravdu chceš smazat tento účet?",
"delete": "Smazat",
"remove_user_from_account": "Odebrat uživatele z účtu",
"select_user": "Vyber uživatele",
"remove": "Odebrat",
"withdraw_from_account": "Vybrat z účtu",
"deposit_to_account": "Vložit na účet",
"removed_successfully": "úspěšně odstraněn",
"select_account_and_user": "Prosím vyber účet a uživatele",
"account_deleted_successfully": "Účet úspěšně smazán",
"new_account_created_successfully": "Nový účet úspěšně vytvořen",
"withdrew": "Vybral jsi",
"successfully": "úspěšně",
"select_valid_account_and_amount": "Prosím vyber platný účet a částku",
"openBank": "Přístup do banky",
"openATM": "Přístup k bankomatu",
"account_deletion_failed": "Smazání účtu selhalo",
"withdrawal_failed": "Výběr selhal",
"deposit_failed": "Vklad selhal",
"user_added_successfully": "úspěšně přidán",
"user_addition_failed": "Přidání uživatele selhalo",
"new_account_creation_failed": "Vytvoření nového účtu selhalo",
"account_renamed_successfully": "Účet úspěšně přejmenován",
"account_rename_failed": "Přejmenování účtu selhalo",
"rename_account": "Změnit název",
"no_framework_found": "Nebyl nalezen žádný framework",
"cannot_send_self_money": "Nemůžeš poslat peníze sám sobě",
"money_sent": "Poslal jsi %s %s",
"received_money": "Obdržel jsi %s od %s",
"no_money": "Nemáš dostatek peněz",
"user_not_in_city": "Uživatel není ve městě",
"transaction_description": "Transakce",
"cannot_add_self": "Nemůžeš přidat sám sebe",
"player_not_found": "Hráč nenalezen",
"target_player_not_found": "Cílový hráč nenalezen",
"user_already_in_account": "Uživatel je již na účtu",
"account_not_found": "Účet nenalezen"
}

View file

@ -0,0 +1,112 @@
{
"atm": "Hæveautomat",
"cash": "Kontanter",
"bank_balance": "Kontooversigt",
"deposit_amount": "Indbetalingsbeløb",
"withdraw_amount": "Hævningsbeløb",
"submit": "Tilføj",
"close": "Luk",
"overview": "Oversigt",
"bills": "Regninger",
"history": "Historik",
"withdraw": "Hæv",
"deposit": "Indsæt",
"stats": "Statistik",
"transactions": "Transaktioner",
"transaction": "Transaktion",
"total": "Total",
"search_transactions": "Søg i transaktioner...",
"description": "Beskrivelse",
"type": "Type",
"time_ago": "Tid siden",
"amount": "Beløb",
"date": "Dato",
"pay_invoice": "Betal faktura",
"payment_completed": "Betaling fuldført",
"from": "Fra",
"delete_all_transactions": "Slet alle transaktioner",
"are_you_sure": "Er du sikker?",
"delete_confirmation": "Er du sikker på, at du vil slette alle dine transaktioner? (Gør kun dette, hvis menuen er langsom!)",
"cancel": "Annullér",
"confirm": "Bekræft",
"history_empty": "Din historik er tom",
"all_history_deleted": "Du har slettet din historik",
"error": "Fejl",
"success": "Succés",
"new_cash": "Friske Kontanter",
"withdraw_success": "Hævning gennemført",
"withdraw_error": "Din bankbeholdning er for lav",
"withdraw_button": "HÆV",
"new_bank": "Ny Bank konto",
"current_cash": "Nuværende Kontanter",
"deposit_success": "Indbetaling Lykkedes",
"deposit_error": "Du mangler kontanter",
"deposit_button": "INDSÆT",
"total_balance": "Total balance",
"quick_actions": "Hurtige handlinger",
"transfer_money": "Overfør Penge",
"easy_transfer": "Nem-overførsel til personer",
"transfer": "Overfør",
"pay_bills": "Betal Regninger",
"pay_pending_bills": "Betal hurtigt dine udestående regninger",
"pay": "Betal",
"withdraw_all_money": "Tøm kontoen",
"withdraw_all_from_account": "Hæv alle dine penge fra din konto",
"deposit_cash": "Indsæt Kontanter",
"deposit_all_cash": "Indsæt alle dine kontanter på din konto",
"weekly_summary": "Uge Oversigt",
"income": "Indkomst",
"expenses": "Udgifter",
"report": "Rapport",
"latest_transactions": "Seneste Transaktioner",
"see_all": "SE ALLE",
"unpaid_bills": "Ubetalte Fakturaer",
"no_unpaid_bills": "Ingen ubetalte fakturaer",
"confirm_pay_all_bills": "Er du sikker på, at du vil betale alle dine regninger?",
"pay_all_bills": "Betal Alle Regninger",
"pay_all_bills_success": "Du har betalt alle dine regninger!",
"pay_all_bills_error": "Du har ingen regninger",
"payment_method": "Betalingsmetode",
"phone_number": "Telefonnummer",
"id": "ID",
"id_or_phone_number": "ID eller Telefonnummer",
"no_cash_on_you": "Du har ingen kontanter på dig",
"deposit_all_success": "Alle dine kontanter er indsat",
"no_money_on_account": "Din konto er tom",
"withdraw_all_success": "Du har hævet alle dine penge fra kontoen",
"invoices": "Fakturaer",
"statistics_reports": "Statistik og Rapportering",
"balance_trend": "Balance oversigt",
"balance": "Balance",
"used": "Brugt",
"month": "Måned",
"balance_dkk": "Balance DKK",
"withdrawn": "Du har hævet",
"deposited": "Du har indsat",
"no_transactions": "Ingen nylige transaktioner",
"transactions_trend": "Transaktions Udvikling",
"total_transactions": "Total Transaktioner",
"accounts": "Konti",
"account_number_copied": "Kontonummer kopieret til udklipsholder",
"new_user_to_account": "Ny bruger til konto",
"server_id": "Server ID",
"add_user": "Tilføj Bruger",
"new_account_name": "Nyt Kontonavn",
"new_name": "Nyt Navn",
"rename": "Omdøb",
"create_new_account": "Opret Ny Konto",
"account_holder": "Kontoholder",
"initial_balance": "Startbeholdning",
"create": "Opret",
"delete_account": "Slet Konto",
"are_you_sure_you_want_to_delete_this_account": "Er du sikker på, at du vil slette denne konto?",
"delete": "Slet",
"remove_user_from_account": "Fjern Bruger fra Konto",
"select_user": "Vælg Bruger",
"remove": "Fjern",
"withdraw_from_account": "Hæv fra Konto",
"deposit_to_account": "Indsæt på Konto",
"removed_successfully": "Fjernet Succesfuldt",
"select_account_and_user": "Vælg venligst en konto og en bruger",
"account_deleted_successfully": "Konto sletning, gennemført!"
}

View file

@ -0,0 +1,139 @@
{
"atm": "Geldautomat",
"cash": "Bargeld",
"bank_balance": "Kontostand",
"deposit_amount": "Einzahlungsbetrag",
"withdraw_amount": "Abhebungsbetrag",
"submit": "Bestätigen",
"close": "Schließen",
"overview": "Überblick",
"bills": "Rechnungen",
"history": "Verlauf",
"withdraw": "Abheben",
"deposit": "Einzahlen",
"stats": "Statistiken",
"transactions": "Transaktionen",
"transaction": "Transaktion",
"total": "Gesamt",
"search_transactions": "Transaktionen suchen...",
"description": "Beschreibung",
"type": "Typ",
"time_ago": "Vor",
"amount": "Betrag",
"date": "Datum",
"pay_invoice": "Rechnung bezahlen",
"payment_completed": "Zahlung abgeschlossen",
"from": "Von",
"delete_all_transactions": "Alle Transaktionen löschen",
"are_you_sure": "Bist du sicher?",
"delete_confirmation": "Möchtest du wirklich alle deine Transaktionen löschen? (Nur tun, wenn das Menü hängt!)",
"cancel": "Abbrechen",
"confirm": "Bestätigen",
"history_empty": "Dein Verlauf ist leer",
"all_history_deleted": "Du hast deinen gesamten Verlauf gelöscht",
"error": "Fehler",
"success": "Erfolg",
"new_cash": "Neues Bargeld",
"withdraw_success": "Abhebung erfolgreich",
"withdraw_error": "Dein Bankkonto hat nicht genügend Guthaben",
"withdraw_button": "ABHEBEN",
"new_bank": "Neuer Kontostand",
"current_cash": "Aktuelles Bargeld",
"deposit_success": "Einzahlung erfolgreich",
"deposit_error": "Du hast nicht genügend Bargeld",
"deposit_button": "EINZAHLEN",
"total_balance": "Gesamtguthaben",
"quick_actions": "Schnellaktionen",
"transfer_money": "Geld überweisen",
"easy_transfer": "Einfach Geld an Personen überweisen",
"transfer": "Überweisen",
"pay_bills": "Rechnungen bezahlen",
"pay_pending_bills": "Schnell deine ausstehenden Rechnungen bezahlen",
"pay": "Bezahlen",
"withdraw_all_money": "Alles Geld abheben",
"withdraw_all_from_account": "Hebe dein gesamtes Geld von deinem Konto ab",
"deposit_cash": "Bargeld einzahlen",
"deposit_all_cash": "Zahle dein gesamtes Bargeld auf dein Konto ein",
"weekly_summary": "Wöchentliche Zusammenfassung",
"income": "Einkommen",
"expenses": "Ausgaben",
"report": "Bericht",
"latest_transactions": "Neueste Transaktionen",
"see_all": "ALLE ANSEHEN",
"unpaid_bills": "Unbezahlte Rechnungen",
"no_unpaid_bills": "Keine unbezahlten Rechnungen",
"confirm_pay_all_bills": "Möchtest du wirklich alle deine Rechnungen bezahlen?",
"pay_all_bills": "Alle Rechnungen bezahlen",
"pay_all_bills_success": "Du hast alle deine Rechnungen bezahlt!",
"pay_all_bills_error": "Du hast keine Rechnungen",
"payment_method": "Zahlungsmethode",
"phone_number": "Telefonnummer",
"id": "ID",
"id_or_phone_number": "ID oder Telefonnummer",
"no_cash_on_you": "Du hast kein Bargeld bei dir",
"deposit_all_success": "Dein gesamtes Bargeld wurde eingezahlt",
"no_money_on_account": "Dein Konto ist leer",
"withdraw_all_success": "Du hast dein gesamtes Geld vom Konto abgehoben",
"invoices": "Rechnungen",
"statistics_reports": "Statistiken und Berichte",
"balance_trend": "Kontostand-Trend",
"balance": "Kontostand",
"used": "Verwendet",
"month": "Monat",
"balance_dkk": "Kontostand",
"withdrawn": "Du hast abgehoben",
"deposited": "Du hast eingezahlt",
"no_transactions": "Keine aktuellen Transaktionen",
"transactions_trend": "Transaktions-Trend",
"total_transactions": "Gesamttransaktionen",
"accounts": "Konten",
"account_number_copied": "Kontonummer in die Zwischenablage kopiert",
"new_user_to_account": "Neuer Benutzer zum Konto hinzufügen",
"server_id": "Server-ID",
"add_user": "Benutzer hinzufügen",
"new_account_name": "Neuer Kontoname",
"new_name": "Neuer Name",
"rename": "Umbenennen",
"create_new_account": "Neues Konto erstellen",
"account_holder": "Kontoinhaber",
"initial_balance": "Anfangsguthaben",
"create": "Erstellen",
"delete_account": "Konto löschen",
"are_you_sure_you_want_to_delete_this_account": "Möchtest du dieses Konto wirklich löschen?",
"delete": "Löschen",
"remove_user_from_account": "Benutzer vom Konto entfernen",
"select_user": "Benutzer auswählen",
"remove": "Entfernen",
"withdraw_from_account": "Vom Konto abheben",
"deposit_to_account": "Auf Konto einzahlen",
"removed_successfully": "Erfolgreich entfernt",
"select_account_and_user": "Bitte wähle ein Konto und einen Benutzer aus",
"account_deleted_successfully": "Konto erfolgreich gelöscht",
"new_account_created_successfully": "Neues Konto erfolgreich erstellt",
"withdrew": "Abgehoben",
"successfully": "Erfolgreich",
"select_valid_account_and_amount": "Bitte wähle ein gültiges Konto und einen Betrag aus",
"openBank": "Bankzugang",
"openATM": "Geldautomatenzugang",
"account_deletion_failed": "Kontolöschung fehlgeschlagen",
"withdrawal_failed": "Abhebung fehlgeschlagen",
"deposit_failed": "Einzahlung fehlgeschlagen",
"user_added_successfully": "Erfolgreich hinzugefügt",
"user_addition_failed": "Benutzer konnte nicht hinzugefügt werden",
"new_account_creation_failed": "Erstellung eines neuen Kontos fehlgeschlagen",
"account_renamed_successfully": "Konto erfolgreich umbenannt",
"account_rename_failed": "Umbenennung des Kontos fehlgeschlagen",
"rename_account": "Namen ändern",
"no_framework_found": "Kein Framework gefunden",
"cannot_send_self_money": "Du kannst dir selbst kein Geld senden",
"money_sent": "Du hast %s an %s gesendet",
"received_money": "Du hast %s von %s erhalten",
"no_money": "Du hast nicht genug Geld",
"user_not_in_city": "Benutzer ist nicht in der Stadt",
"transaction_description": "Transaktion",
"cannot_add_self": "Du kannst dich nicht selbst hinzufügen",
"player_not_found": "Spieler nicht gefunden",
"target_player_not_found": "Zielspieler nicht gefunden",
"user_already_in_account": "Benutzer ist bereits im Konto",
"account_not_found": "Konto nicht gefunden"
}

View file

@ -0,0 +1,139 @@
{
"atm": "ATM",
"cash": "Cash",
"bank_balance": "Bank Balance",
"deposit_amount": "Deposit Amount",
"withdraw_amount": "Withdraw Amount",
"submit": "Submit",
"close": "Close",
"overview": "Overview",
"bills": "Bills",
"history": "History",
"withdraw": "Withdraw",
"deposit": "Deposit",
"stats": "Stats",
"transactions": "Transactions",
"transaction": "Transaction",
"total": "Total",
"search_transactions": "Search transactions...",
"description": "Description",
"type": "Type",
"time_ago": "Time Ago",
"amount": "Amount",
"date": "Date",
"pay_invoice": "Pay Invoice",
"payment_completed": "Payment Completed",
"from": "From",
"delete_all_transactions": "Delete All Transactions",
"are_you_sure": "Are you sure?",
"delete_confirmation": "Are you sure you want to delete all your transactions? (Only do this if the menu lags!)",
"cancel": "Cancel",
"confirm": "Confirm",
"history_empty": "Your history is empty",
"all_history_deleted": "You have deleted all your history",
"error": "Error",
"success": "Success",
"new_cash": "New Cash",
"withdraw_success": "Withdrawal Successful",
"withdraw_error": "Your bank account does not have enough funds",
"withdraw_button": "WITHDRAW",
"new_bank": "New Bank Balance",
"current_cash": "Current Cash",
"deposit_success": "Deposit Successful",
"deposit_error": "You do not have enough cash",
"deposit_button": "DEPOSIT",
"total_balance": "Total Balance",
"quick_actions": "Quick Actions",
"transfer_money": "Transfer Money",
"easy_transfer": "Easily transfer money to people",
"transfer": "Transfer",
"pay_bills": "Pay Bills",
"pay_pending_bills": "Quickly pay your pending bills",
"pay": "Pay",
"withdraw_all_money": "Withdraw All Money",
"withdraw_all_from_account": "Withdraw all your money from your account",
"deposit_cash": "Deposit Cash",
"deposit_all_cash": "Deposit all your cash into your account",
"weekly_summary": "Weekly Summary",
"income": "Income",
"expenses": "Expenses",
"report": "Report",
"latest_transactions": "Latest Transactions",
"see_all": "SEE ALL",
"unpaid_bills": "Unpaid Invoices",
"no_unpaid_bills": "No unpaid invoices",
"confirm_pay_all_bills": "Are you sure you want to pay all your bills?",
"pay_all_bills": "Pay All Bills",
"pay_all_bills_success": "You have paid all your bills!",
"pay_all_bills_error": "You have no bills",
"payment_method": "Payment Method",
"phone_number": "Phone Number",
"id": "ID",
"id_or_phone_number": "ID or Phone Number",
"no_cash_on_you": "You have no cash on you",
"deposit_all_success": "All your cash has been deposited",
"no_money_on_account": "Your account is empty",
"withdraw_all_success": "You have withdrawn all your money from the account",
"invoices": "Invoices",
"statistics_reports": "Statistics and Reports",
"balance_trend": "Balance Trend",
"balance": "Balance",
"used": "Used",
"month": "Month",
"balance_dkk": "Balance",
"withdrawn": "You have withdrawn",
"deposited": "You have deposited",
"no_transactions": "No recent transactions",
"transactions_trend": "Transactions Trend",
"total_transactions": "Total Transactions",
"accounts": "Accounts",
"account_number_copied": "Account number copied to clipboard",
"new_user_to_account": "New user to account",
"server_id": "Server ID",
"add_user": "Add User",
"new_account_name": "New Account Name",
"new_name": "New Name",
"rename": "Rename",
"create_new_account": "Create New Account",
"account_holder": "Account Holder",
"initial_balance": "Initial Balance",
"create": "Create",
"delete_account": "Delete Account",
"are_you_sure_you_want_to_delete_this_account": "Are you sure you want to delete this account?",
"delete": "Delete",
"remove_user_from_account": "Remove User from Account",
"select_user": "Select User",
"remove": "Remove",
"withdraw_from_account": "Withdraw from Account",
"deposit_to_account": "Deposit to Account",
"removed_successfully": "Removed Successfully",
"select_account_and_user": "Please select an account and a user",
"account_deleted_successfully": "Account deleted successfully",
"new_account_created_successfully": "New account created successfully",
"withdrew": "Withdrew",
"successfully": "Successfully",
"select_valid_account_and_amount": "Please select a valid account and amount",
"openBank": "Access Bank",
"openATM": "Access ATM",
"account_deletion_failed": "Account deletion failed",
"withdrawal_failed": "Withdrawal failed",
"deposit_failed": "Deposit failed",
"user_added_successfully": "User added successfully",
"user_addition_failed": "Failed to add user",
"new_account_creation_failed": "Failed to create new account",
"account_renamed_successfully": "Account renamed successfully",
"account_rename_failed": "Account rename failed",
"rename_account": "Change name",
"no_framework_found": "No framework found",
"cannot_send_self_money": "You cannot send money to yourself",
"money_sent": "You have sent %s to %s",
"received_money": "You have received %s from %s",
"no_money": "You do not have enough money",
"user_not_in_city": "User is not in the city",
"transaction_description": "Transaction",
"cannot_add_self": "You cannot add yourself",
"player_not_found": "Player not found",
"target_player_not_found": "Target player not found",
"user_already_in_account": "User is already in the account",
"account_not_found": "Account not found"
}

View file

@ -0,0 +1,140 @@
{
"atm": "Cajero Automático",
"cash": "Efectivo",
"bank_balance": "Saldo Bancario",
"deposit_amount": "Monto a Depositar",
"withdraw_amount": "Monto a Retirar",
"submit": "Enviar",
"close": "Cerrar",
"overview": "Resumen",
"bills": "Facturas",
"history": "Historial",
"withdraw": "Retirar",
"deposit": "Depositar",
"stats": "Estadísticas",
"transactions": "Transacciones",
"transaction": "Transacción",
"total": "Total",
"search_transactions": "Buscar transacciones...",
"description": "Descripción",
"type": "Tipo",
"time_ago": "Hace Tiempo",
"amount": "Cantidad",
"date": "Fecha",
"pay_invoice": "Pagar Factura",
"payment_completed": "Pago Completado",
"from": "De",
"delete_all_transactions": "Eliminar Todas las Transacciones",
"are_you_sure": "¿Estás seguro?",
"delete_confirmation": "¿Estás seguro de que deseas eliminar todas tus transacciones? (¡Haz esto solo si el menú se retrasa!)",
"cancel": "Cancelar",
"confirm": "Confirmar",
"history_empty": "Tu historial está vacío",
"all_history_deleted": "Has eliminado todo tu historial",
"error": "Error",
"success": "Éxito",
"new_cash": "Nuevo Efectivo",
"withdraw_success": "Retiro Exitoso",
"withdraw_error": "Tu cuenta bancaria no tiene suficientes fondos",
"withdraw_button": "RETIRAR",
"new_bank": "Nuevo Saldo Bancario",
"current_cash": "Efectivo Actual",
"deposit_success": "Depósito Exitoso",
"deposit_error": "No tienes suficiente efectivo",
"deposit_button": "DEPOSITAR",
"total_balance": "Saldo Total",
"quick_actions": "Acciones Rápidas",
"transfer_money": "Transferir Dinero",
"easy_transfer": "Transfiere dinero fácilmente a personas",
"transfer": "Transferir",
"pay_bills": "Pagar Facturas",
"pay_pending_bills": "Paga rápidamente tus facturas pendientes",
"pay": "Pagar",
"withdraw_all_money": "Retirar Todo el Dinero",
"withdraw_all_from_account": "Retirar todo tu dinero de tu cuenta",
"deposit_cash": "Depositar Efectivo",
"deposit_all_cash": "Depositar todo tu efectivo en tu cuenta",
"weekly_summary": "Resumen Semanal",
"income": "Ingresos",
"expenses": "Gastos",
"report": "Informe",
"latest_transactions": "Últimas Transacciones",
"see_all": "VER TODO",
"unpaid_bills": "Facturas No Pagadas",
"no_unpaid_bills": "No hay facturas no pagadas",
"confirm_pay_all_bills": "¿Estás seguro de que deseas pagar todas tus facturas?",
"pay_all_bills": "Pagar Todas las Facturas",
"pay_all_bills_success": "¡Has pagado todas tus facturas!",
"pay_all_bills_error": "No tienes facturas",
"payment_method": "Método de Pago",
"phone_number": "Número de Teléfono",
"id": "ID",
"id_or_phone_number": "ID o Número de Teléfono",
"no_cash_on_you": "No tienes efectivo contigo",
"deposit_all_success": "Todo tu efectivo ha sido depositado",
"no_money_on_account": "Tu cuenta está vacía",
"withdraw_all_success": "Has retirado todo tu dinero de la cuenta",
"invoices": "Facturas",
"statistics_reports": "Estadísticas e Informes",
"balance_trend": "Tendencia de Saldo",
"balance": "Saldo",
"used": "Usado",
"month": "Mes",
"balance_dkk": "Saldo",
"withdrawn": "Has retirado",
"deposited": "Has depositado",
"no_transactions": "No hay transacciones recientes",
"transactions_trend": "Tendencia de Transacciones",
"total_transactions": "Total de Transacciones",
"accounts": "Cuentas",
"account_number_copied": "Número de cuenta copiado al portapapeles",
"new_user_to_account": "Nuevo usuario en la cuenta",
"server_id": "ID del Servidor",
"add_user": "Añadir Usuario",
"new_account_name": "Nuevo Nombre de Cuenta",
"new_name": "Nuevo Nombre",
"rename": "Renombrar",
"create_new_account": "Crear Nueva Cuenta",
"account_holder": "Titular de la Cuenta",
"initial_balance": "Saldo Inicial",
"create": "Crear",
"delete_account": "Eliminar Cuenta",
"are_you_sure_you_want_to_delete_this_account": "¿Estás seguro de que deseas eliminar esta cuenta?",
"delete": "Eliminar",
"remove_user_from_account": "Eliminar Usuario de la Cuenta",
"select_user": "Seleccionar Usuario",
"remove": "Eliminar",
"withdraw_from_account": "Retirar de la Cuenta",
"deposit_to_account": "Depositar en la Cuenta",
"removed_successfully": "Eliminado Exitosamente",
"select_account_and_user": "Por favor, selecciona una cuenta y un usuario",
"account_deleted_successfully": "Cuenta eliminada exitosamente",
"new_account_created_successfully": "Nueva cuenta creada exitosamente",
"withdrew": "Retiró",
"successfully": "Exitosamente",
"select_valid_account_and_amount": "Por favor, selecciona una cuenta válida y una cantidad",
"openBank": "Acceder al Banco",
"openATM": "Acceder al Cajero Automático",
"account_deletion_failed": "Fallo al eliminar la cuenta",
"withdrawal_failed": "Fallo en el retiro",
"deposit_failed": "Fallo en el depósito",
"user_added_successfully": "Usuario añadido exitosamente",
"user_addition_failed": "Fallo al añadir usuario",
"new_account_creation_failed": "Fallo al crear nueva cuenta",
"account_renamed_successfully": "Cuenta renombrada exitosamente",
"account_rename_failed": "Fallo al renombrar la cuenta",
"rename_account": "Cambiar nombre",
"no_framework_found": "No se encontró el framework",
"cannot_send_self_money": "No puedes enviarte dinero a ti mismo",
"money_sent": "Has enviado %s a %s",
"received_money": "Has recibido %s de %s",
"no_money": "No tienes suficiente dinero",
"user_not_in_city": "El usuario no está en la ciudad",
"transaction_description": "Transacción",
"cannot_add_self": "No puedes añadirte a ti mismo",
"player_not_found": "Jugador no encontrado",
"target_player_not_found": "Jugador objetivo no encontrado",
"user_already_in_account": "El usuario ya está en la cuenta",
"account_not_found": "Cuenta no encontrada"
}

View file

@ -0,0 +1,139 @@
{
"atm": "ATM",
"cash": "Argent liquide",
"bank_balance": "Solde bancaire",
"deposit_amount": "Montant du dépôt",
"withdraw_amount": "Montant du retrait",
"submit": "Soumettre",
"close": "Fermer",
"overview": "Aperçu",
"bills": "Factures",
"history": "Historique",
"withdraw": "Retirer",
"deposit": "Déposer",
"stats": "Statistiques",
"transactions": "Transactions",
"transaction": "Transaction",
"total": "Total",
"search_transactions": "Rechercher des transactions...",
"description": "Description",
"type": "Type",
"time_ago": "Il y a",
"amount": "Montant",
"date": "Date",
"pay_invoice": "Payer la facture",
"payment_completed": "Paiement effectué",
"from": "De",
"delete_all_transactions": "Supprimer toutes les transactions",
"are_you_sure": "Êtes-vous sûr?",
"delete_confirmation": "Êtes-vous sûr de vouloir supprimer toutes vos transactions ? (Faites cela uniquement si le menu est lent !)",
"cancel": "Annuler",
"confirm": "Confirmer",
"history_empty": "Votre historique est vide",
"all_history_deleted": "Vous avez supprimé tout votre historique",
"error": "Erreur",
"success": "Succès",
"new_cash": "Nouvel argent liquide",
"withdraw_success": "Retrait réussi",
"withdraw_error": "Votre compte bancaire n'a pas assez de fonds",
"withdraw_button": "RETIRER",
"new_bank": "Nouveau solde bancaire",
"current_cash": "Argent liquide actuel",
"deposit_success": "Dépôt réussi",
"deposit_error": "Vous n'avez pas assez d'argent liquide",
"deposit_button": "DÉPOSER",
"total_balance": "Solde total",
"quick_actions": "Actions rapides",
"transfer_money": "Transférer de l'argent",
"easy_transfer": "Transférer facilement de l'argent aux personnes",
"transfer": "Transférer",
"pay_bills": "Payer les factures",
"pay_pending_bills": "Payer rapidement vos factures en attente",
"pay": "Payer",
"withdraw_all_money": "Retirer tout l'argent",
"withdraw_all_from_account": "Retirer tout votre argent de votre compte",
"deposit_cash": "Déposer de l'argent liquide",
"deposit_all_cash": "Déposer tout votre argent liquide sur votre compte",
"weekly_summary": "Résumé hebdomadaire",
"income": "Revenus",
"expenses": "Dépenses",
"report": "Rapport",
"latest_transactions": "Dernières transactions",
"see_all": "VOIR TOUT",
"unpaid_bills": "Factures impayées",
"no_unpaid_bills": "Aucune facture impayée",
"confirm_pay_all_bills": "Êtes-vous sûr de vouloir payer toutes vos factures ?",
"pay_all_bills": "Payer toutes les factures",
"pay_all_bills_success": "Vous avez payé toutes vos factures !",
"pay_all_bills_error": "Vous n'avez aucune facture",
"payment_method": "Méthode de paiement",
"phone_number": "Numéro de téléphone",
"id": "ID",
"id_or_phone_number": "ID ou numéro de téléphone",
"no_cash_on_you": "Vous n'avez pas d'argent liquide sur vous",
"deposit_all_success": "Tout votre argent liquide a été déposé",
"no_money_on_account": "Votre compte est vide",
"withdraw_all_success": "Vous avez retiré tout votre argent du compte",
"invoices": "Factures",
"statistics_reports": "Statistiques et rapports",
"balance_trend": "Tendance du solde",
"balance": "Solde",
"used": "Utilisé",
"month": "Mois",
"balance_dkk": "Solde",
"withdrawn": "Vous avez retiré",
"deposited": "Vous avez déposé",
"no_transactions": "Aucune transaction récente",
"transactions_trend": "Tendance des transactions",
"total_transactions": "Total des transactions",
"accounts": "Comptes",
"account_number_copied": "Numéro de compte copié dans le presse-papiers",
"new_user_to_account": "Nouvel utilisateur sur le compte",
"server_id": "ID serveur",
"add_user": "Ajouter un utilisateur",
"new_account_name": "Nouveau nom de compte",
"new_name": "Nouveau nom",
"rename": "Renommer",
"create_new_account": "Créer un nouveau compte",
"account_holder": "Titulaire du compte",
"initial_balance": "Solde initial",
"create": "Créer",
"delete_account": "Supprimer le compte",
"are_you_sure_you_want_to_delete_this_account": "Êtes-vous sûr de vouloir supprimer ce compte ?",
"delete": "Supprimer",
"remove_user_from_account": "Supprimer l'utilisateur du compte",
"select_user": "Sélectionner l'utilisateur",
"remove": "Supprimer",
"withdraw_from_account": "Retirer du compte",
"deposit_to_account": "Déposer sur le compte",
"removed_successfully": "Supprimé avec succès",
"select_account_and_user": "Veuillez sélectionner un compte et un utilisateur",
"account_deleted_successfully": "Compte supprimé avec succès",
"new_account_created_successfully": "Nouveau compte créé avec succès",
"withdrew": "Retiré",
"successfully": "Avec succès",
"select_valid_account_and_amount": "Veuillez sélectionner un compte valide et un montant",
"openBank": "Accéder à la banque",
"openATM": "Accéder au ATM",
"account_deletion_failed": "Échec de la suppression du compte",
"withdrawal_failed": "Échec du retrait",
"deposit_failed": "Échec du dépôt",
"user_added_successfully": "Ajouté avec succès",
"user_addition_failed": "Échec de l'ajout de l'utilisateur",
"new_account_creation_failed": "Échec de la création du nouveau compte",
"account_renamed_successfully": "Compte renommé avec succès",
"account_rename_failed": "Échec du renommage du compte",
"rename_account": "Changer le nom",
"no_framework_found": "Aucun framework trouvé",
"cannot_send_self_money": "Vous ne pouvez pas vous envoyer de l'argent",
"money_sent": "Vous avez envoyé %s à %s",
"received_money": "Vous avez reçu %s de %s",
"no_money": "Vous n'avez pas assez d'argent",
"user_not_in_city": "L'utilisateur n'est pas en ville",
"transaction_description": "Transaction",
"cannot_add_self": "Vous ne pouvez pas vous ajouter",
"player_not_found": "Joueur non trouvé",
"target_player_not_found": "Joueur cible non trouvé",
"user_already_in_account": "L'utilisateur est déjà dans le compte",
"account_not_found": "Compte non trouvé"
}

View file

@ -0,0 +1,140 @@
{
"atm": "Geldautomaat",
"cash": "Contant",
"bank_balance": "Banksaldo",
"deposit_amount": "Stortingsbedrag",
"withdraw_amount": "Opnamembedrag",
"submit": "Verzenden",
"close": "Sluiten",
"overview": "Overzicht",
"bills": "Rekeningen",
"history": "Geschiedenis",
"withdraw": "Opnemen",
"deposit": "Storten",
"stats": "Statistieken",
"transactions": "Transacties",
"transaction": "Transactie",
"total": "Totaal",
"search_transactions": "Zoek transacties...",
"description": "Omschrijving",
"type": "Type",
"time_ago": "Tijd geleden",
"amount": "Bedrag",
"date": "Datum",
"pay_invoice": "Factuur betalen",
"payment_completed": "Betaling voltooid",
"from": "Van",
"delete_all_transactions": "Verwijder alle transacties",
"are_you_sure": "Weet je het zeker?",
"delete_confirmation": "Weet je zeker dat je al je transacties wilt verwijderen? (Doe dit alleen wanneer je lag ervaart!)",
"cancel": "Annuleren",
"confirm": "Bevestigen",
"history_empty": "Je geschiedenis is leeg",
"all_history_deleted": "Je hebt al je geschiedenis verwijderd",
"error": "Fout",
"success": "Succes",
"new_cash": "Nieuw Contant",
"withdraw_success": "Opname geslaagd",
"withdraw_error": "Je bankrekening heeft niet genoeg saldo",
"withdraw_button": "OPNEMEN",
"new_bank": "Nieuw Banksaldo",
"current_cash": "Huidig Contant",
"deposit_success": "Storting geslaagd",
"deposit_error": "Je hebt niet genoeg contant geld",
"deposit_button": "STORTEN",
"total_balance": "Totaal Saldo",
"quick_actions": "Snelle Acties",
"transfer_money": "Geld Overmaken",
"easy_transfer": "Eenvoudig geld overmaken naar mensen",
"transfer": "Overmaken",
"pay_bills": "Rekeningen Betalen",
"pay_pending_bills": "Snel je openstaande rekeningen betalen",
"pay": "Betalen",
"withdraw_all_money": "Trek al het geld op",
"withdraw_all_from_account": "Trek al je geld van je rekening op",
"deposit_cash": "Contant Geld Storten",
"deposit_all_cash": "Stort al je contant geld op je rekening",
"weekly_summary": "Wekelijks Overzicht",
"income": "Inkomen",
"expenses": "Uitgaven",
"report": "Rapport",
"latest_transactions": "Laatste Transacties",
"see_all": "TOON ALLE",
"unpaid_bills": "Onbetaalde Facturen",
"no_unpaid_bills": "Geen onbetaalde facturen",
"confirm_pay_all_bills": "Weet je zeker dat je al je rekeningen wilt betalen?",
"pay_all_bills": "Betaal Alle Rekeningen",
"pay_all_bills_success": "Je hebt al je rekeningen betaald!",
"pay_all_bills_error": "Je hebt geen rekeningen",
"payment_method": "Betaalmethode",
"phone_number": "Telefoonnummer",
"id": "ID",
"id_or_phone_number": "ID of Telefoonnummer",
"no_cash_on_you": "Je hebt geen contant geld bij je",
"deposit_all_success": "Al je contant geld is gestort",
"no_money_on_account": "Je rekening is leeg",
"withdraw_all_success": "Je hebt al je geld van de rekening opgenomen",
"invoices": "Facturen",
"statistics_reports": "Statistieken en Rapporten",
"balance_trend": "Saldo Trend",
"balance": "Saldo",
"used": "Gebruikt",
"month": "Maand",
"balance_dkk": "Saldo",
"withdrawn": "Je hebt opgenomen",
"deposited": "Je hebt gestort",
"no_transactions": "Geen recente transacties",
"transactions_trend": "Transacties Trend",
"total_transactions": "Totaal Aantal Transacties",
"accounts": "Rekeningen",
"account_number_copied": "Rekeningnummer gekopieerd naar klembord",
"new_user_to_account": "Nieuwe gebruiker aan rekening",
"server_id": "Server ID",
"add_user": "Voeg Gebruiker Toe",
"new_account_name": "Nieuwe Rekeningnaam",
"new_name": "Nieuwe Naam",
"rename": "Hernoemen",
"create_new_account": "Nieuwe Rekening Aanmaken",
"account_holder": "Rekeninghouder",
"initial_balance": "Aanvankelijk Saldo",
"create": "Aanmaken",
"delete_account": "Verwijder Rekening",
"are_you_sure_you_want_to_delete_this_account": "Weet je zeker dat je deze rekening wilt verwijderen?",
"delete": "Verwijderen",
"remove_user_from_account": "Verwijder Gebruiker van Rekening",
"select_user": "Selecteer Gebruiker",
"remove": "Verwijderen",
"withdraw_from_account": "Opnemen van Rekening",
"deposit_to_account": "Storten op Rekening",
"removed_successfully": "Succesvol verwijderd",
"select_account_and_user": "Selecteer een rekening en een gebruiker",
"account_deleted_successfully": "Rekening succesvol verwijderd",
"new_account_created_successfully": "Nieuwe rekening succesvol aangemaakt",
"withdrew": "Opgenomen",
"successfully": "Succesvol",
"select_valid_account_and_amount": "Selecteer een geldige rekening en bedrag",
"openBank": "Toegang tot Bank",
"openATM": "Toegang tot Geldautomaat",
"account_deletion_failed": "Verwijderen van rekening mislukt",
"withdrawal_failed": "Opname mislukt",
"deposit_failed": "Storting mislukt",
"user_added_successfully": "Succesvol toegevoegd",
"user_addition_failed": "Toevoegen van gebruiker mislukt",
"new_account_creation_failed": "Aanmaken van nieuwe rekening mislukt",
"account_renamed_successfully": "Rekening succesvol hernoemd",
"account_rename_failed": "Hernoemen van rekening mislukt",
"rename_account": "Naam wijzigen",
"no_framework_found": "Geen framework gevonden",
"cannot_send_self_money": "Je kunt geen geld naar jezelf sturen",
"money_sent": "Je hebt %s naar %s gestuurd",
"received_money": "Je hebt %s ontvangen van %s",
"no_money": "Je hebt niet genoeg geld",
"user_not_in_city": "Gebruiker is niet in de stad",
"transaction_description": "Transactie",
"cannot_add_self": "Je kunt jezelf niet toevoegen",
"player_not_found": "Speler niet gevonden",
"target_player_not_found": "Doelspeler niet gevonden",
"user_already_in_account": "Gebruiker is reeds toegevoegd",
"account_not_found": "Rekening niet gevonden"
}

View file

@ -0,0 +1,139 @@
{
"atm": "Caixa Eletrônico",
"cash": "Dinheiro",
"bank_balance": "Saldo Bancário",
"deposit_amount": "Valor do Depósito",
"withdraw_amount": "Valor do Saque",
"submit": "Enviar",
"close": "Fechar",
"overview": "Visão Geral",
"bills": "Contas",
"history": "Histórico",
"withdraw": "Sacar",
"deposit": "Depositar",
"stats": "Estatísticas",
"transactions": "Transações",
"transaction": "Transação",
"total": "Total",
"search_transactions": "Buscar transações...",
"description": "Descrição",
"type": "Tipo",
"time_ago": "Tempo Atrás",
"amount": "Valor",
"date": "Data",
"pay_invoice": "Pagar Fatura",
"payment_completed": "Pagamento Concluído",
"from": "De",
"delete_all_transactions": "Excluir Todas as Transações",
"are_you_sure": "Você tem certeza?",
"delete_confirmation": "Você tem certeza que deseja excluir todas as suas transações? (Faça isso apenas se o menu estiver lento!)",
"cancel": "Cancelar",
"confirm": "Confirmar",
"history_empty": "Seu histórico está vazio",
"all_history_deleted": "Você excluiu todo o seu histórico",
"error": "Erro",
"success": "Sucesso",
"new_cash": "Novo Dinheiro",
"withdraw_success": "Saque Bem-Sucedido",
"withdraw_error": "Sua conta bancária não tem fundos suficientes",
"withdraw_button": "SACAR",
"new_bank": "Novo Saldo Bancário",
"current_cash": "Dinheiro Atual",
"deposit_success": "Depósito Bem-Sucedido",
"deposit_error": "Você não tem dinheiro suficiente",
"deposit_button": "DEPOSITAR",
"total_balance": "Saldo Total",
"quick_actions": "Ações Rápidas",
"transfer_money": "Transferir Dinheiro",
"easy_transfer": "Transfira dinheiro facilmente para pessoas",
"transfer": "Transferir",
"pay_bills": "Pagar Contas",
"pay_pending_bills": "Pague rapidamente suas contas pendentes",
"pay": "Pagar",
"withdraw_all_money": "Sacar Todo o Dinheiro",
"withdraw_all_from_account": "Sacar todo o dinheiro da sua conta",
"deposit_cash": "Depositar Dinheiro",
"deposit_all_cash": "Depositar todo o seu dinheiro na sua conta",
"weekly_summary": "Resumo Semanal",
"income": "Renda",
"expenses": "Despesas",
"report": "Relatório",
"latest_transactions": "Últimas Transações",
"see_all": "VER TUDO",
"unpaid_bills": "Faturas Não Pagas",
"no_unpaid_bills": "Nenhuma fatura não paga",
"confirm_pay_all_bills": "Você tem certeza que deseja pagar todas as suas contas?",
"pay_all_bills": "Pagar Todas as Contas",
"pay_all_bills_success": "Você pagou todas as suas contas!",
"pay_all_bills_error": "Você não tem contas",
"payment_method": "Método de Pagamento",
"phone_number": "Número de Telefone",
"id": "ID",
"id_or_phone_number": "ID ou Número de Telefone",
"no_cash_on_you": "Você não tem dinheiro com você",
"deposit_all_success": "Todo o seu dinheiro foi depositado",
"no_money_on_account": "Sua conta está vazia",
"withdraw_all_success": "Você sacou todo o dinheiro da conta",
"invoices": "Faturas",
"statistics_reports": "Estatísticas e Relatórios",
"balance_trend": "Tendência do Saldo",
"balance": "Saldo",
"used": "Usado",
"month": "Mês",
"balance_dkk": "Saldo",
"withdrawn": "Você sacou",
"deposited": "Você depositou",
"no_transactions": "Nenhuma transação recente",
"transactions_trend": "Tendência das Transações",
"total_transactions": "Total de Transações",
"accounts": "Contas",
"account_number_copied": "Número da conta copiado para a área de transferência",
"new_user_to_account": "Novo usuário na conta",
"server_id": "ID do Servidor",
"add_user": "Adicionar Usuário",
"new_account_name": "Novo Nome da Conta",
"new_name": "Novo Nome",
"rename": "Renomear",
"create_new_account": "Criar Nova Conta",
"account_holder": "Titular da Conta",
"initial_balance": "Saldo Inicial",
"create": "Criar",
"delete_account": "Excluir Conta",
"are_you_sure_you_want_to_delete_this_account": "Você tem certeza que deseja excluir esta conta?",
"delete": "Excluir",
"remove_user_from_account": "Remover Usuário da Conta",
"select_user": "Selecionar Usuário",
"remove": "Remover",
"withdraw_from_account": "Sacar da Conta",
"deposit_to_account": "Depositar na Conta",
"removed_successfully": "Removido com Sucesso",
"select_account_and_user": "Por favor, selecione uma conta e um usuário",
"account_deleted_successfully": "Conta excluída com sucesso",
"new_account_created_successfully": "Nova conta criada com sucesso",
"withdrew": "Sacou",
"successfully": "Com Sucesso",
"select_valid_account_and_amount": "Por favor, selecione uma conta e um valor válidos",
"openBank": "Acessar Banco",
"openATM": "Acessar Caixa Eletrônico",
"account_deletion_failed": "Falha na exclusão da conta",
"withdrawal_failed": "Falha no saque",
"deposit_failed": "Falha no depósito",
"user_added_successfully": "adicionado com sucesso",
"user_addition_failed": "Falha ao adicionar usuário",
"new_account_creation_failed": "Falha ao criar nova conta",
"account_renamed_successfully": "Conta renomeada com sucesso",
"account_rename_failed": "Falha ao renomear a conta",
"rename_account": "Alterar nome",
"no_framework_found": "Nenhum framework encontrado",
"cannot_send_self_money": "Você não pode enviar dinheiro para si mesmo",
"money_sent": "Você enviou %s para %s",
"received_money": "Você recebeu %s de %s",
"no_money": "Você não tem dinheiro suficiente",
"user_not_in_city": "Usuário não está na cidade",
"transaction_description": "Transação",
"cannot_add_self": "Você não pode adicionar a si mesmo",
"player_not_found": "Jogador não encontrado",
"target_player_not_found": "Jogador alvo não encontrado",
"user_already_in_account": "Usuário já está na conta",
"account_not_found": "Conta não encontrada"
}

View file

@ -0,0 +1,139 @@
{
"atm": "Multibanco",
"cash": "Dinheiro",
"bank_balance": "Saldo Bancário",
"deposit_amount": "Valor do Depósito",
"withdraw_amount": "Valor do Levantamento",
"submit": "Submeter",
"close": "Fechar",
"overview": "Visão Geral",
"bills": "Faturas",
"history": "Histórico",
"withdraw": "Levantar",
"deposit": "Depositar",
"stats": "Estatísticas",
"transactions": "Transações",
"transaction": "Transação",
"total": "Total",
"search_transactions": "Pesquisar transações...",
"description": "Descrição",
"type": "Tipo",
"time_ago": "Tempo Decorrido",
"amount": "Montante",
"date": "Data",
"pay_invoice": "Pagar Fatura",
"payment_completed": "Pagamento Concluído",
"from": "De",
"delete_all_transactions": "Eliminar Todas as Transações",
"are_you_sure": "Tem a certeza?",
"delete_confirmation": "Tem a certeza de que deseja eliminar todas as suas transações? (Faça-o apenas se o menu estiver lento!)",
"cancel": "Cancelar",
"confirm": "Confirmar",
"history_empty": "O seu histórico está vazio",
"all_history_deleted": "Eliminou todo o seu histórico",
"error": "Erro",
"success": "Sucesso",
"new_cash": "Novo Dinheiro",
"withdraw_success": "Levantamento Bem-Sucedido",
"withdraw_error": "A sua conta bancária não tem fundos suficientes",
"withdraw_button": "LEVANTAR",
"new_bank": "Novo Saldo Bancário",
"current_cash": "Dinheiro Atual",
"deposit_success": "Depósito Bem-Sucedido",
"deposit_error": "Não tem dinheiro suficiente",
"deposit_button": "DEPOSITAR",
"total_balance": "Saldo Total",
"quick_actions": "Ações Rápidas",
"transfer_money": "Transferir Dinheiro",
"easy_transfer": "Transfira dinheiro facilmente para outras pessoas",
"transfer": "Transferir",
"pay_bills": "Pagar Faturas",
"pay_pending_bills": "Pague rapidamente as suas faturas pendentes",
"pay": "Pagar",
"withdraw_all_money": "Levantar Todo o Dinheiro",
"withdraw_all_from_account": "Levantar todo o dinheiro da sua conta",
"deposit_cash": "Depositar Dinheiro",
"deposit_all_cash": "Depositar todo o seu dinheiro na sua conta",
"weekly_summary": "Resumo Semanal",
"income": "Rendimento",
"expenses": "Despesas",
"report": "Relatório",
"latest_transactions": "Últimas Transações",
"see_all": "VER TODAS",
"unpaid_bills": "Faturas Não Pagas",
"no_unpaid_bills": "Não tem faturas não pagas",
"confirm_pay_all_bills": "Tem a certeza de que deseja pagar todas as suas faturas?",
"pay_all_bills": "Pagar Todas as Faturas",
"pay_all_bills_success": "Pagou todas as suas faturas!",
"pay_all_bills_error": "Não tem faturas",
"payment_method": "Método de Pagamento",
"phone_number": "Número de Telefone",
"id": "ID",
"id_or_phone_number": "ID ou Número de Telefone",
"no_cash_on_you": "Não tem dinheiro consigo",
"deposit_all_success": "Todo o seu dinheiro foi depositado",
"no_money_on_account": "A sua conta está vazia",
"withdraw_all_success": "Levantou todo o dinheiro da conta",
"invoices": "Faturas",
"statistics_reports": "Estatísticas e Relatórios",
"balance_trend": "Tendência do Saldo",
"balance": "Saldo",
"used": "Utilizado",
"month": "Mês",
"balance_dkk": "Saldo",
"withdrawn": "Levantou",
"deposited": "Depositou",
"no_transactions": "Nenhuma transação recente",
"transactions_trend": "Tendência das Transações",
"total_transactions": "Total de Transações",
"accounts": "Contas",
"account_number_copied": "Número da conta copiado para a área de transferência",
"new_user_to_account": "Novo utilizador na conta",
"server_id": "ID do Servidor",
"add_user": "Adicionar Utilizador",
"new_account_name": "Novo Nome da Conta",
"new_name": "Novo Nome",
"rename": "Renomear",
"create_new_account": "Criar Nova Conta",
"account_holder": "Titular da Conta",
"initial_balance": "Saldo Inicial",
"create": "Criar",
"delete_account": "Eliminar Conta",
"are_you_sure_you_want_to_delete_this_account": "Tem a certeza de que deseja eliminar esta conta?",
"delete": "Eliminar",
"remove_user_from_account": "Remover Utilizador da Conta",
"select_user": "Selecionar Utilizador",
"remove": "Remover",
"withdraw_from_account": "Levantar da Conta",
"deposit_to_account": "Depositar na Conta",
"removed_successfully": "Removido com Sucesso",
"select_account_and_user": "Por favor, selecione uma conta e um utilizador",
"account_deleted_successfully": "Conta eliminada com sucesso",
"new_account_created_successfully": "Nova conta criada com sucesso",
"withdrew": "Levantou",
"successfully": "com sucesso",
"select_valid_account_and_amount": "Por favor, selecione uma conta e um montante válido",
"openBank": "Aceder ao Banco",
"openATM": "Aceder ao Multibanco",
"account_deletion_failed": "Falha na eliminação da conta",
"withdrawal_failed": "Falha no levantamento",
"deposit_failed": "Falha no depósito",
"user_added_successfully": "adicionado com sucesso",
"user_addition_failed": "Falha ao adicionar utilizador",
"new_account_creation_failed": "Falha ao criar nova conta",
"account_renamed_successfully": "Conta renomeada com sucesso",
"account_rename_failed": "Falha ao renomear a conta",
"rename_account": "Alterar nome",
"no_framework_found": "Nenhum framework encontrado",
"cannot_send_self_money": "Não pode enviar dinheiro para si mesmo",
"money_sent": "Enviou %s para %s",
"received_money": "Recebeu %s de %s",
"no_money": "Não tem dinheiro suficiente",
"user_not_in_city": "Utilizador não está na cidade",
"transaction_description": "Transação",
"cannot_add_self": "Não pode adicionar a si mesmo",
"player_not_found": "Jogador não encontrado",
"target_player_not_found": "Jogador alvo não encontrado",
"user_already_in_account": "Utilizador já está na conta",
"account_not_found": "Conta não encontrada"
}

View file

@ -0,0 +1,140 @@
{
"atm": "ATM",
"cash": "Nakit",
"bank_balance": "Banka Bakiyesi",
"deposit_amount": "Para Yatırma Miktarı",
"withdraw_amount": "Para Çekme Miktarı",
"submit": "Gönder",
"close": "Kapat",
"overview": "Görünüm",
"bills": "Faturalar",
"history": "Geçmiş",
"withdraw": "Para Çekme",
"deposit": "Para Yatırma",
"stats": "İstatistikler",
"transactions": "İşlemler",
"transaction": "İşlem",
"total": "Toplam",
"search_transactions": "İşlemleri ara...",
"description": "Açıklama",
"type": "Tip",
"time_ago": "Zaman Önce",
"amount": "Miktar",
"date": "Tarih",
"pay_invoice": "Fatura Öde",
"payment_completed": "Ödeme Tamamlandı",
"from": "Tarafından",
"delete_all_transactions": "Tüm Ödemeleri Sil",
"are_you_sure": "Emin misiniz?",
"delete_confirmation": "Tüm ödemeleri silmek istediğinizden emin misiniz? (Lütfen bunu sadece menü gecikmeleri oluyorsa yapın!)",
"cancel": "İptal",
"confirm": "Onayla",
"history_empty": "Geçmişiniz boş",
"all_history_deleted": "Tüm geçmişinizi sildiniz",
"error": "Hata",
"success": "Başarılı",
"new_cash": "Yeni Nakit",
"withdraw_success": "Para Çekme Başarılı",
"withdraw_error": "Banka hesabınızda yeterli bakiye yok",
"withdraw_button": "PARA ÇEK",
"new_bank": "Yeni Banka Bakiyesi",
"current_cash": "Mevcut Nakit",
"deposit_success": "Para Yatırma Başarılı",
"deposit_error": "Yeterli nakitiniz yok",
"deposit_button": "PARA YATIR",
"total_balance": "Toplam Bakiye",
"quick_actions": "Hızlı İşlemler",
"transfer_money": "Para Transferi",
"easy_transfer": "İnsanlara kolayca para aktarın",
"transfer": "Transfer",
"pay_bills": "Faturaları Öde",
"pay_pending_bills": "Bekleyen faturalarınızı hızlıca ödeyin",
"pay": "Öde",
"withdraw_all_money": "Tüm Paranızı Çekin",
"withdraw_all_from_account": "Hesabınızda bulunan tüm parayı çekin",
"deposit_cash": "Nakit Yatır",
"deposit_all_cash": "Tüm paranızı hesabınıza yatırın",
"weekly_summary": "Haftalık Özet",
"income": "Gelir",
"expenses": "Masraflar",
"report": "Rapor",
"latest_transactions": "Son İşlemler",
"see_all": "TÜMÜNÜ GÖSTER",
"unpaid_bills": "Ödenmemiş Faturalar",
"no_unpaid_bills": "Ödenmemiş faturanız yok",
"confirm_pay_all_bills": "Tüm faturalarınızı ödemek istediğinizden emin misiniz?",
"pay_all_bills": "Tüm Faturaları Öde",
"pay_all_bills_success": "Tüm faturalarınızı ödediniz!",
"pay_all_bills_error": "Size kesilen bir fatura yok",
"payment_method": "Ödeme Yöntemi",
"phone_number": "Telefon Numarası",
"id": "ID",
"id_or_phone_number": "ID veya Telefon Numarası",
"no_cash_on_you": "Üzerinizde nakit yok",
"deposit_all_success": "Tüm paranız yatırıldı",
"no_money_on_account": "Hesabınızda para yok",
"withdraw_all_success": "Hesabınızdaki tüm parayı çektiniz",
"invoices": "Faturalar",
"statistics_reports": "İstatistikler ve Raporlar",
"balance_trend": "Denge Trendi",
"balance": "Denge",
"used": "Kullanıldı",
"month": "Ay",
"balance_dkk": "Balance",
"withdrawn": "Para çektiniz",
"deposited": "Para yatırdınız",
"no_transactions": "Son işlemler yok",
"transactions_trend": "Transactions Trend",
"total_transactions": "Toplam İşlemler",
"accounts": "Hesaplar",
"account_number_copied": "Hesap numarası panonuza kopyalandı",
"new_user_to_account": "Hesabınıza yeni kullanıcı",
"server_id": "Sunucu ID",
"add_user": "Kullanıcı Ekle",
"new_account_name": "Yeni Hesap İsmi",
"new_name": "Yeni İsim",
"rename": "Yeniden Adlandır",
"create_new_account": "Yeni Hesap Oluştur",
"account_holder": "Hesap Sahibi",
"initial_balance": "Başlangıç Bakiyesi",
"create": "Oluştur",
"delete_account": "Hesabı Sil",
"are_you_sure_you_want_to_delete_this_account": "Bu hesabı silmek istediğinizden emin misiniz?",
"delete": "Sil",
"remove_user_from_account": "Hesaptan Kullanıcı Kaldır",
"select_user": "Kullanıcı Seç",
"remove": "Kaldır",
"withdraw_from_account": "Hesaptan Para Çekme",
"deposit_to_account": "Hesaba Para Yatırmak",
"removed_successfully": "Başarıyla Kaldırıldı",
"select_account_and_user": "Lütfen hesap ve kullanıcı seçin",
"account_deleted_successfully": "Hesap başarıyla kaldırıldı",
"new_account_created_successfully": "Yeni hesap başarıyla oluşturuldu",
"withdrew": "Çekildi",
"successfully": "Başarılı",
"select_valid_account_and_amount": "Lütfen geçerli bir hesap ve tutar seçin",
"openBank": "Bankaya Eriş",
"openATM": "ATM'e Eriş",
"account_deletion_failed": "Hesap silme işlemi başarısız oldu",
"withdrawal_failed": "Para çekme işlemi başarısız oldu",
"deposit_failed": "Para yatırma işlemi başarısız oldu",
"user_added_successfully": "Kullanıcı başarıyla eklendi",
"user_addition_failed": "Kullanıcı ekleme başarısız oldu",
"new_account_creation_failed": "Yeni hesap oluşturma işlemi başarısız oldu",
"account_renamed_successfully": "Hesap başarıyla yeniden adlandırıldı",
"account_rename_failed": "Hesap yeniden adlandırma işlemi başarısız oldu",
"rename_account": "İsmi değiştir",
"no_framework_found": "Geçerli bir framework bulunamadı",
"cannot_send_self_money": "Kendinize para gönderemezsiniz",
"money_sent": "%s kişisine %s tutar gönderildi",
"received_money": "%s miktar parayı %s kişisinden aldınız",
"no_money": "Yeterli miktarda paranız bulunmuyor",
"user_not_in_city": "Kullanıcı şehirde değil",
"transaction_description": "İşlem",
"cannot_add_self": "Kendinize ekleyemezsiniz",
"player_not_found": "Oyuncu bulunamadı",
"target_player_not_found": "Hedef oyuncu bulunamadı",
"user_already_in_account": "Kullanıcı zaten hesapta bulunuyor",
"account_not_found": "Hesap bulunamadı"
}

View file

@ -0,0 +1,54 @@
CREATE TABLE
`ps_banking_transactions` (
`id` INT NOT NULL AUTO_INCREMENT,
`identifier` VARCHAR(50) NOT NULL,
`description` VARCHAR(255) NOT NULL,
`type` VARCHAR(50) NOT NULL,
`amount` DECIMAL(10, 2) NOT NULL,
`date` DATE NOT NULL,
`isIncome` BOOLEAN NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB;
CREATE TABLE
`ps_banking_bills` (
`id` INT NOT NULL AUTO_INCREMENT,
`identifier` VARCHAR(50) NOT NULL,
`description` VARCHAR(255) NOT NULL,
`type` VARCHAR(50) NOT NULL,
`amount` DECIMAL(10, 2) NOT NULL,
`date` DATE NOT NULL,
`isPaid` BOOLEAN NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB;
CREATE TABLE
`ps_banking_accounts` (
`id` INT NOT NULL AUTO_INCREMENT,
`balance` BIGINT NOT NULL,
`holder` VARCHAR(255) NOT NULL,
`cardNumber` VARCHAR(255) NOT NULL,
`users` JSON NOT NULL,
`owner` JSON NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB;
/* Dummy data (Ignore)
INSERT INTO `ps_banking_bills` (`identifier`, `description`, `type`, `amount`, `date`, `isPaid`) VALUES
('char1:df6c12c50e2712c57b1386e7103d5a372fb960a0', 'Utility Bill', 'Expense', 150.00, '2024-07-20', 0);
INSERT INTO `ps_banking_transactions` (`identifier`, `description`, `type`, `amount`, `date`, `isIncome`) VALUES
('char1:df6c12c50e2712c57b1386e7103d5a372fb960a0', 'Opened a new account', 'From account', 1000.00, '2022-08-13', 0),
('char1:df6c12c50e2712c57b1386e7103d5a372fb960a0', 'Deposited 500 DKK to account', 'To account', 500.00, '2022-08-13', 1),
('char1:df6c12c50e2712c57b1386e7103d5a372fb960a0', 'Deposited 500 DKK to account', 'To account', 500.00, '2022-08-13', 1),
('char1:df6c12c50e2712c57b1386e7103d5a372fb960a0', 'Withdrew 500 DKK from ATM', 'From account', -500.00, '2022-08-13', 0),
('char1:df6c12c50e2712c57b1386e7103d5a372fb960a0', 'Deposited 500 DKK to account', 'To account', 500.00, '2022-08-13', 1),
('char2:ab12c34d5e67f89g01234h56789i012j34kl567m8', 'Opened a new account', 'From account', 2000.00, '2022-08-14', 0),
('char2:ab12c34d5e67f89g01234h56789i012j34kl567m8', 'Deposited 1000 DKK to account', 'To account', 1000.00, '2022-08-14', 1),
('char2:ab12c34d5e67f89g01234h56789i012j34kl567m8', 'Withdrew 300 DKK from ATM', 'From account', -300.00, '2022-08-14', 0),
('char2:ab12c34d5e67f89g01234h56789i012j34kl567m8', 'Deposited 400 DKK to account', 'To account', 400.00, '2022-08-14', 1),
('char2:ab12c34d5e67f89g01234h56789i012j34kl567m8', 'Withdrew 200 DKK from ATM', 'From account', -200.00, '2022-08-14', 0);
*/

View file

@ -0,0 +1,458 @@
lib.versionCheck('Project-Sloth/ps-banking')
assert(lib.checkDependency('ox_lib', '3.20.0', true))
local framework = nil
if GetResourceState("es_extended") == "started" then
framework = "ESX"
ESX = exports["es_extended"]:getSharedObject()
elseif GetResourceState("qb-core") == "started" then
framework = "QBCore"
QBCore = exports["qb-core"]:GetCoreObject()
else
return error(locale("no_framework_found"))
end
local function getPlayerIdentifier(player)
if framework == "ESX" then
return player.getIdentifier()
elseif framework == "QBCore" then
return player.PlayerData.citizenid
end
end
local function getPlayerFromId(source)
if framework == "ESX" then
return ESX.GetPlayerFromId(source)
elseif framework == "QBCore" then
return QBCore.Functions.GetPlayer(source)
end
end
local function getPlayerAccounts(player)
if framework == "ESX" then
return player.getAccount("bank").money
elseif framework == "QBCore" then
return player.PlayerData.money["bank"]
end
end
local function getName(player)
if framework == "ESX" then
return player.getName()
elseif framework == "QBCore" then
return player.PlayerData.charinfo.firstname .. " " .. player.PlayerData.charinfo.lastname
end
end
lib.callback.register("ps-banking:server:getHistory", function(source)
local xPlayer = getPlayerFromId(source)
local identifier = getPlayerIdentifier(xPlayer)
local result = MySQL.query.await('SELECT * FROM ps_banking_transactions WHERE identifier = ?', { identifier })
return result
end)
lib.callback.register("ps-banking:server:deleteHistory", function(source)
local xPlayer = getPlayerFromId(source)
local identifier = getPlayerIdentifier(xPlayer)
MySQL.query.await('DELETE FROM ps_banking_transactions WHERE identifier = ?', { identifier })
return true
end)
lib.callback.register("ps-banking:server:payAllBills", function(source)
local xPlayer = getPlayerFromId(source)
local identifier = getPlayerIdentifier(xPlayer)
local result = MySQL.query.await('SELECT SUM(amount) as total FROM ps_banking_bills WHERE identifier = ? AND isPaid = 0', { identifier })
local totalAmount = result[1].total or 0
local bankBalance = getPlayerAccounts(xPlayer)
if tonumber(bankBalance) >= tonumber(totalAmount) then
if framework == "ESX" then
xPlayer.removeAccountMoney("bank", tonumber(totalAmount))
elseif framework == "QBCore" then
xPlayer.Functions.RemoveMoney("bank", tonumber(totalAmount))
end
MySQL.query.await('DELETE FROM ps_banking_bills WHERE identifier = ?', { identifier })
return true
else
return false
end
end)
lib.callback.register("ps-banking:server:getWeeklySummary", function(source)
local xPlayer = getPlayerFromId(source)
local identifier = getPlayerIdentifier(xPlayer)
local receivedResult = MySQL.query.await('SELECT SUM(amount) as totalReceived FROM ps_banking_transactions WHERE identifier = ? AND isIncome = ? AND DATE(date) >= DATE(NOW() - INTERVAL 7 DAY)', { identifier, true })
local totalReceived = receivedResult[1].totalReceived or 0
local usedResult = MySQL.query.await('SELECT SUM(amount) as totalUsed FROM ps_banking_transactions WHERE identifier = ? AND isIncome = ? AND DATE(date) >= DATE(NOW() - INTERVAL 7 DAY)', { identifier, false })
local totalUsed = usedResult[1].totalUsed or 0
return {
totalReceived = totalReceived,
totalUsed = totalUsed,
}
end)
lib.callback.register("ps-banking:server:transferMoney", function(source, data)
local xPlayer = getPlayerFromId(source)
local targetPlayer = getPlayerFromId(data.id)
local amount = tonumber(data.amount)
if data.id == source and data.method == "id" then
return false, locale("cannot_send_self_money")
end
if xPlayer and targetPlayer and amount > 0 then
local xPlayerBalance = getPlayerAccounts(xPlayer)
if xPlayerBalance >= amount then
if data.method == "id" then
if framework == "ESX" then
xPlayer.removeAccountMoney("bank", amount)
targetPlayer.addAccountMoney("bank", amount)
elseif framework == "QBCore" then
xPlayer.Functions.RemoveMoney("bank", amount)
targetPlayer.Functions.AddMoney("bank", amount)
end
return true, locale("money_sent", amount, getName(targetPlayer))
elseif data.method == "phone" then
exports["lb-phone"]:AddTransaction(targetPlayer.identifier, amount,
locale("received_money", getName(xPlayer), amount))
return true, locale("money_sent", amount, getName(targetPlayer))
end
else
return false, locale("no_money")
end
else
return false, locale("user_not_in_city")
end
end)
local function logTransaction(identifier, description, accountName, amount, isIncome)
MySQL.insert.await('INSERT INTO ps_banking_transactions (identifier, description, type, amount, date, isIncome) VALUES (?, ?, ?, ?, NOW(), ?)', { identifier, description, accountName, amount, isIncome })
end
RegisterNetEvent("ps-banking:server:logClient", function(account, moneyData)
if account.name ~= "bank" then
return
end
local src = source
local xPlayer = getPlayerFromId(src)
local identifier = getPlayerIdentifier(xPlayer)
local previousBankBalance = 0
if moneyData then
for _, data in ipairs(moneyData) do
if data.name == "bank" then
previousBankBalance = data.amount
break
end
end
end
local currentBankBalance = getPlayerAccounts(xPlayer)
local amountChange = currentBankBalance - previousBankBalance
if amountChange ~= 0 then
local isIncome = currentBankBalance >= previousBankBalance and true or false
local description = locale("transaction_description")
logTransaction(identifier, description, account.name, math.abs(amountChange), isIncome)
end
end)
lib.callback.register("ps-banking:server:getTransactionStats", function(source)
local xPlayer = getPlayerFromId(source)
local identifier = getPlayerIdentifier(xPlayer)
local result = MySQL.query.await('SELECT COUNT(*) as totalCount, SUM(amount) as totalAmount FROM ps_banking_transactions WHERE identifier = ?', { identifier })
local transactionData = MySQL.query.await('SELECT amount, date FROM ps_banking_transactions WHERE identifier = ? ORDER BY date DESC LIMIT 50', { identifier })
return {
totalCount = result[1].totalCount,
totalAmount = result[1].totalAmount,
transactionData = transactionData,
}
end)
lib.callback.register("ps-banking:server:createNewAccount", function(source, newAccount)
local xPlayer = getPlayerFromId(source)
if not xPlayer then
return false
end
MySQL.insert.await('INSERT INTO ps_banking_accounts (balance, holder, cardNumber, users, owner) VALUES (?, ?, ?, ?, ?)', { newAccount.balance, newAccount.holder, newAccount.cardNumber, json.encode(newAccount.users), json.encode(newAccount.owner) })
return true
end)
lib.callback.register("ps-banking:server:getUser", function(source)
local xPlayer = getPlayerFromId(source)
if not xPlayer then
return false
end
return {
name = getName(xPlayer),
identifier = getPlayerIdentifier(xPlayer),
}
end)
lib.callback.register("ps-banking:server:getAccounts", function(source)
local xPlayer = getPlayerFromId(source)
if not xPlayer then
return false
end
local playerIdentifier = getPlayerIdentifier(xPlayer)
local accounts = MySQL.query.await('SELECT * FROM ps_banking_accounts')
local result = {}
for _, account in ipairs(accounts) do
local accountData = {
id = account.id,
balance = account.balance,
holder = account.holder,
cardNumber = account.cardNumber,
users = json.decode(account.users),
owner = json.decode(account.owner),
}
if accountData.owner.identifier == playerIdentifier then
accountData.owner.state = true
table.insert(result, accountData)
else
for _, user in ipairs(accountData.users) do
if user.identifier == playerIdentifier then
accountData.owner.state = false
table.insert(result, accountData)
break
end
end
end
end
return result
end)
lib.callback.register("ps-banking:server:deleteAccount", function(source, accountId)
local xPlayer = getPlayerFromId(source)
if not xPlayer then
return false
end
MySQL.query.await('DELETE FROM ps_banking_accounts WHERE id = ?', { accountId })
return true
end)
lib.callback.register("ps-banking:server:withdrawFromAccount", function(source, accountId, amount)
local xPlayer = getPlayerFromId(source)
if not xPlayer then
return false
end
local account = MySQL.query.await('SELECT * FROM ps_banking_accounts WHERE id = ?', { accountId })
if #account > 0 then
local balance = account[1].balance
if balance >= amount then
local affectedRows = MySQL.update.await('UPDATE ps_banking_accounts SET balance = balance - ? WHERE id = ?', { amount, accountId })
if affectedRows > 0 then
if framework == "ESX" then
xPlayer.addAccountMoney("bank", amount)
elseif framework == "QBCore" then
xPlayer.Functions.AddMoney("bank", amount)
end
return true
else
return false
end
else
return false
end
else
return false
end
end)
lib.callback.register("ps-banking:server:depositToAccount", function(source, accountId, amount)
local xPlayer = getPlayerFromId(source)
if not xPlayer then
return false
end
if getPlayerAccounts(xPlayer) >= amount then
local affectedRows = MySQL.update.await('UPDATE ps_banking_accounts SET balance = balance + ? WHERE id = ?', { amount, accountId })
if affectedRows > 0 then
if framework == "ESX" then
xPlayer.removeAccountMoney("bank", amount)
elseif framework == "QBCore" then
xPlayer.Functions.RemoveMoney("bank", amount)
end
return true
else
return false
end
else
return false
end
end)
lib.callback.register("ps-banking:server:addUserToAccount", function(source, accountId, userId)
local xPlayer = getPlayerFromId(source)
local targetPlayer = getPlayerFromId(userId)
local promise = promise.new()
if source == userId then
return {
success = false,
message = locale("cannot_add_self"),
}
end
if not xPlayer then
return {
success = false,
message = locale("player_not_found"),
}
end
if not targetPlayer then
return {
success = false,
message = locale("target_player_not_found"),
}
end
local accounts = MySQL.query.await('SELECT * FROM ps_banking_accounts WHERE id = ?', { accountId })
if #accounts > 0 then
local account = accounts[1]
local users = json.decode(account.users)
for _, user in ipairs(users) do
if user.identifier == userId then
return {
success = false,
message = locale("user_already_in_account"),
}
end
end
table.insert(users, {
name = getName(targetPlayer),
identifier = getPlayerIdentifier(targetPlayer),
})
local affectedRows = MySQL.update.await('UPDATE ps_banking_accounts SET users = ? WHERE id = ?', { json.encode(users), accountId })
return {
success = affectedRows > 0,
userName = getName(targetPlayer),
}
else
return {
success = false,
message = locale("account_not_found"),
}
end
end)
lib.callback.register("ps-banking:server:removeUserFromAccount", function(source, accountId, userId)
local xPlayer = getPlayerFromId(source)
if not xPlayer then
return false
end
local accounts = MySQL.query.await('SELECT * FROM ps_banking_accounts WHERE id = ?', { accountId })
if #accounts > 0 then
local account = accounts[1]
local users = json.decode(account.users)
local updatedUsers = {}
for _, user in ipairs(users) do
if user.identifier ~= userId then
table.insert(updatedUsers, user)
end
end
local affectedRows = MySQL.update.await('UPDATE ps_banking_accounts SET users = ? WHERE id = ?', { json.encode(updatedUsers), accountId })
return affectedRows > 0
else
return false
end
end)
lib.callback.register("ps-banking:server:renameAccount", function(source, id, newName)
local xPlayer = getPlayerFromId(source)
if not xPlayer then
return false
end
local affectedRows = MySQL.update.await('UPDATE ps_banking_accounts SET holder = ? WHERE id = ?', { newName, id })
return affectedRows > 0
end)
lib.callback.register("ps-banking:server:ATMwithdraw", function(source, amount)
local xPlayer = getPlayerFromId(source)
local bankBalance = getPlayerAccounts(xPlayer)
if bankBalance >= amount then
if framework == "ESX" then
xPlayer.removeAccountMoney("bank", amount)
xPlayer.addMoney(amount)
elseif framework == "QBCore" then
xPlayer.Functions.RemoveMoney("bank", amount)
xPlayer.Functions.AddMoney("cash", amount)
end
return true
else
return false
end
end)
lib.callback.register("ps-banking:server:ATMdeposit", function(source, amount)
local xPlayer = getPlayerFromId(source)
local cashBalance = nil
if framework == "ESX" then
cashBalance = xPlayer.getMoney()
elseif framework == "QBCore" then
cashBalance = xPlayer.PlayerData.money["cash"]
end
if cashBalance >= amount then
if framework == "ESX" then
xPlayer.removeMoney(amount)
xPlayer.addAccountMoney("bank", amount)
elseif framework == "QBCore" then
xPlayer.Functions.RemoveMoney("cash", amount)
xPlayer.Functions.AddMoney("bank", amount)
end
return true
else
return false
end
end)
lib.callback.register("ps-banking:server:getBills", function(source)
local xPlayer = getPlayerFromId(source)
local identifier = getPlayerIdentifier(xPlayer)
local result = MySQL.query.await('SELECT * FROM ps_banking_bills WHERE identifier = ?', { identifier })
return result
end)
lib.callback.register("ps-banking:server:payBill", function(source, billId)
local xPlayer = getPlayerFromId(source)
local identifier = getPlayerIdentifier(xPlayer)
local result = MySQL.query.await('SELECT * FROM ps_banking_bills WHERE id = ? AND identifier = ? AND isPaid = 0', { billId, identifier })
if #result == 0 then
return false
end
local bill = result[1]
local amount = bill.amount
if tonumber(getPlayerAccounts(xPlayer)) >= tonumber(amount) then
if framework == "ESX" then
xPlayer.removeAccountMoney("bank", tonumber(amount))
elseif framework == "QBCore" then
xPlayer.Functions.RemoveMoney("bank", tonumber(amount))
end
MySQL.query.await('DELETE FROM ps_banking_bills WHERE id = ?', { billId })
return true
else
return false
end
end)
function createBill(data)
local identifier = data.identifier
local description = data.description
local type = data.type
local amount = data.amount
MySQL.insert.await('INSERT INTO ps_banking_bills (identifier, description, type, amount, date, isPaid) VALUES (?, ?, ?, ?, NOW(), ?)', { identifier, description, type, amount, false })
end
exports("createBill", createBill)
--[[ EXAMPLE
exports["ps-banking"]:createBill({
identifier = "char1:df6c12c50e2712c57b1386e7103d5a372fb960a0",
description = "Utility Bill",
type = "Expense",
amount = 150.00,
})
]]

View file

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
build
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View file

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Bach Banking</title>
<link
rel="stylesheet"
href="https://site-assets.fontawesome.com/releases/v6.6.0/css/all.css" />
<link
rel="stylesheet"
href="https://site-assets.fontawesome.com/releases/v6.6.0/css/sharp-duotone-solid.css" />
<link
rel="stylesheet"
href="https://site-assets.fontawesome.com/releases/v6.6.0/css/sharp-thin.css" />
<link
rel="stylesheet"
href="https://site-assets.fontawesome.com/releases/v6.6.0/css/sharp-solid.css" />
<link
rel="stylesheet"
href="https://site-assets.fontawesome.com/releases/v6.6.0/css/sharp-regular.css" />
<link
rel="stylesheet"
href="https://site-assets.fontawesome.com/releases/v6.6.0/css/sharp-light.css" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<style>
body {
width: 100vw;
height: 100vh;
}
::-webkit-scrollbar {
display: none;
}
::-webkit-outer-spin-button,
::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
</style>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,48 @@
{
"name": "web",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"dev:game": "vite build --watch",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^3.0.2",
"@tsconfig/svelte": "^5.0.4",
"@types/d3-interpolate": "^3.0.4",
"@types/d3-scale": "^4.0.8",
"@types/d3-selection": "^3.0.10",
"@types/d3-shape": "^3.1.6",
"@types/d3-transition": "^3.0.8",
"autoprefixer": "^10.4.19",
"postcss": "^8.4.38",
"postcss-load-config": "^5.0.3",
"svelte": "^4.2.12",
"svelte-check": "^3.6.9",
"svelte-preprocess": "^5.1.3",
"tailwindcss": "^3.4.3",
"tslib": "^2.6.2",
"typescript": "^5.4.3",
"vite": "^4.5.3"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.5.2",
"@rollup/plugin-dsv": "^3.0.4",
"apexcharts": "^3.48.0",
"bits-ui": "^0.21.10",
"chart.js": "^3.9.1",
"d3": "^7.9.0",
"d3-scale": "^4.0.2",
"layercake": "^8.1.1",
"lucide-svelte": "^0.368.0",
"svelte-apexcharts": "^1.0.2",
"svelte-chartjs": "^3.1.5",
"svelte-fa": "^4.0.2",
"svelte-radix": "^1.1.0"
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,13 @@
const tailwindcss = require("tailwindcss");
const autoprefixer = require("autoprefixer");
const config = {
plugins: [
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
tailwindcss(),
//But others, like autoprefixer, need to run after,
autoprefixer,
],
};
module.exports = config;

View file

@ -0,0 +1,58 @@
<script lang="ts">
import VisibilityProvider from "./providers/VisibilityProvider.svelte";
import Main from "./components/Main.svelte";
import { debugData } from "./utils/debugData";
import { slide, fade } from "svelte/transition";
import { notifications, showATM } from "../src/store/data";
import { visibility } from "../src/store/stores";
debugData([
{
action: "openBank",
data: true,
},
]);
</script>
<main>
<VisibilityProvider>
<Main />
</VisibilityProvider>
{#if $showATM}
<div
class="absolute bottom-44 right-[22%] grid grid-cols-1 gap-2 select-none"
>
{#each $notifications as notification (notification.id)}
<div
class="bg-gray-900 text-blue-200 py-3 px-6 rounded-lg shadow-xl flex items-center space-x-3 transform transition-transform duration-500 border border-gray-700/50"
in:slide={{ duration: 300 }}
out:fade={{ duration: 300 }}
>
<i class="fa-duotone fa-{notification.icon} text-2xl"></i>
<div>
<p class="font-bold">{notification.title}</p>
<p>{notification.message}</p>
</div>
</div>
{/each}
</div>
{:else}
<div
class="absolute bottom-24 right-[12%] grid grid-cols-1 gap-2 select-none"
>
{#each $notifications as notification (notification.id)}
<div
class="bg-gray-900 text-blue-200 py-3 px-6 rounded-lg shadow-xl flex items-center space-x-3 transform transition-transform duration-500 border border-gray-700/50"
in:slide={{ duration: 300 }}
out:fade={{ duration: 300 }}
>
<i class="fa-duotone fa-{notification.icon} text-2xl"></i>
<div>
<p class="font-bold">{notification.title}</p>
<p>{notification.message}</p>
</div>
</div>
{/each}
</div>
{/if}
</main>

View file

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View file

@ -0,0 +1,272 @@
<script lang="ts">
import { writable, get } from "svelte/store";
import { onMount } from "svelte";
import { fetchNui } from "../utils/fetchNui";
import { slide, fade, scale } from "svelte/transition";
import { quintOut } from "svelte/easing";
import {
showATM,
currentCash,
bankBalance,
Notify,
type Notification,
Locales,
Currency,
} from "../store/data";
let customWithdraw = writable(0);
let customDeposit = writable(0);
let withdrawAmounts = writable([]);
let depositAmounts = writable([]);
let gridColsPreset = writable(3);
$: (customDeposit = currentCash), (customWithdraw = bankBalance);
async function getAmountPresets() {
try {
const response = await fetchNui("ps-banking:client:getAmountPresets", {});
const amounts = JSON.parse(response);
withdrawAmounts.set(amounts.withdrawAmounts);
depositAmounts.set(amounts.depositAmounts);
gridColsPreset.set(amounts.grid);
} catch (error) {
console.error(error);
}
}
async function updateBalances() {
try {
const response = await fetchNui("ps-banking:client:getMoneyTypes", {});
const bank = response.find(
(item: { name: string }) => item.name === "bank"
);
const cash = response.find(
(item: { name: string }) => item.name === "cash"
);
if (bank) {
bankBalance.set(bank.amount);
}
if (cash) {
currentCash.set(cash.amount);
}
} catch (error) {
console.error(error);
}
}
async function heav(amount: number) {
try {
const response = await fetchNui("ps-banking:client:ATMwithdraw", {
amount: amount,
});
if (response) {
Notify(
`${$Locales.withdrawn} ${amount.toLocaleString($Currency.lang, {
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
})}`,
$Locales.payment_completed,
"coins"
);
await updateBalances();
} else {
Notify($Locales.no_money_on_account, $Locales.error, "credit-card");
}
} catch (error) {
console.error(error);
}
}
async function deposit(amount: number) {
try {
const response = await fetchNui("ps-banking:client:ATMdeposit", {
amount: amount,
});
if (response) {
Notify(
`${$Locales.deposited} ${amount.toLocaleString($Currency.lang, {
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
})}`,
$Locales.payment_completed,
"coins"
);
await updateBalances();
} else {
Notify($Locales.no_cash_on_you, $Locales.error, "credit-card");
}
} catch (error) {
console.error(error);
}
}
async function getLocales() {
try {
const response = await fetchNui("ps-banking:client:getLocales", {});
Locales.set(response);
} catch (error) {
console.error(error);
}
}
onMount(() => {
getAmountPresets();
getLocales();
updateBalances();
const keyHandler = (e: KeyboardEvent) => {
if (get(showATM) && ["Escape"].includes(e.code)) {
fetchNui("ps-banking:client:hideUI");
showATM.set(false);
}
};
window.addEventListener("keydown", keyHandler);
return () => window.removeEventListener("keydown", keyHandler);
});
</script>
<div class="absolute w-screen h-screen flex items-center justify-center">
<div
class="absolute inset-0 flex items-center justify-center"
in:scale={{ duration: 1000, easing: quintOut }}
out:scale={{ duration: 1000, easing: quintOut }}
>
<div
class="h-auto w-[60%] bg-gray-800 rounded-3xl p-8 shadow-2xl relative border border-blue-200/10"
>
<div class="text-4xl font-bold text-center text-blue-200 mb-6">
<i class="fa-duotone fa-atm text-blue-200 mr-2"></i>{$Locales.atm}
</div>
<div class="grid grid-cols-2 gap-6 mb-8">
<div
class="bg-gray-700 p-6 rounded-2xl shadow-lg flex flex-col items-center justify-center"
>
<div class="text-2xl font-semibold text-blue-100 mb-2">
<i class="fa-duotone fa-money-bill-wave text-md mr-2"></i>
{$Locales.cash}
</div>
<div class="text-4xl font-bold text-blue-400">
{$currentCash.toLocaleString($Currency.lang, {
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
})}
</div>
</div>
<div
class="bg-gray-700 p-6 rounded-2xl shadow-lg flex flex-col items-center justify-center"
>
<div class="text-2xl font-semibold text-blue-100 mb-2">
<i class="fa-duotone fa-vault text-md mr-2"
></i>{$Locales.bank_balance}
</div>
<div class="text-4xl font-bold text-blue-400">
{$bankBalance.toLocaleString($Currency.lang, {
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
})}
</div>
</div>
</div>
<div class="grid" style={`grid-template-columns: repeat(${$gridColsPreset}, minmax(0, 1fr)); gap: 10px;`}>
{#each $withdrawAmounts as amount}
<button
class="bg-blue-600/10 border border-blue-500 hover:bg-blue-800/50 text-white font-bold py-4 px-6 rounded-xl duration-500 cursor-pointer flex items-center justify-center gap-2"
on:click={() => {
heav(amount);
}}
>
<i class="fa-duotone fa-money-bill-wave text-lg"
></i>{$Locales.withdraw}
{amount.toLocaleString($Currency.lang, {
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
})}
</button>
{/each}
{#each $depositAmounts as amount}
<button
class="bg-green-600/10 border border-green-500 hover:bg-green-800/50 text-white font-bold py-4 px-6 rounded-xl duration-500 cursor-pointer flex items-center justify-center gap-2"
on:click={() => {
deposit(amount);
}}
>
<i class="fa-duotone fa-credit-card text-lg"></i>{$Locales.deposit}
{amount.toLocaleString($Currency.lang, {
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
})}
</button>
{/each}
</div>
<div class="grid grid-cols-2 gap-4 mt-4">
<div class="bg-gray-700 p-4 rounded-xl shadow-lg">
<div class="flex items-center mb-2">
<i
class="fa-duotone fa-money-check-edit text-xl text-green-400 mr-2"
></i>
<label for="Deposit" class="text-lg font-semibold text-white">
{$Locales.deposit_amount}
</label>
</div>
<input
type="number"
id="Deposit"
bind:value={$customDeposit}
class="w-full bg-gray-800 text-white font-bold pl-4 pr-4 py-3 rounded-lg border border-green-200/10 focus:outline-none focus:border-green-400/50 transition-colors duration-500 placeholder-gray-500"
placeholder={$Locales.deposit_amount}
/>
<button
class="mt-2 w-full bg-green-600/10 border border-green-500 hover:bg-green-800/50 text-white font-bold py-2 px-4 rounded-xl duration-500 cursor-pointer flex items-center justify-center gap-2"
on:click={() => {
deposit(get(customDeposit));
}}
>
<i class="fa-duotone fa-piggy-bank text-lg"></i>
{$Locales.submit}
</button>
</div>
<div class="bg-gray-700 p-4 rounded-xl shadow-lg">
<div class="flex items-center mb-2">
<i class="fa-duotone fa-money-check-edit text-xl text-blue-400 mr-2"
></i>
<label for="Withdraw" class="text-lg font-semibold text-white">
{$Locales.withdraw_amount}
</label>
</div>
<input
type="number"
id="Withdraw"
bind:value={$customWithdraw}
class="w-full bg-gray-800 font-bold text-white pl-4 pr-4 py-3 rounded-lg border border-blue-200/10 focus:outline-none focus:border-blue-400/50 transition-colors duration-500 placeholder-gray-500"
placeholder={$Locales.withdraw_amount}
/>
<button
class="mt-2 w-full bg-blue-600/10 border border-blue-500 hover:bg-blue-800/50 text-white font-bold py-2 px-4 rounded-xl duration-500 cursor-pointer flex items-center justify-center gap-2"
on:click={() => {
heav(get(customWithdraw));
}}
>
<i class="fa-duotone fa-money-bill-wave text-lg"></i>
{$Locales.submit}
</button>
</div>
</div>
<div class="absolute top-4 right-4 transform -translate-x-1/2 text-white">
<button
class="text-blue-200/50 hover:text-blue-500/50 font-bold py-2 px-4 rounded-xl duration-500 cursor-pointer flex items-center justify-center gap-2 text-gray-300 py-4 transition-all duration-500 rounded-xl hover:text-blue-300 duration-500 hover:cursor-pointer hover:bg-gray-800/80"
on:click={() => {
showATM.set(false);
fetchNui("ps-banking:client:hideUI");
}}
>
<i class="fa-duotone fa-times-circle text-2xl"></i>
</button>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,801 @@
<script lang="ts">
import { writable, get, derived } from "svelte/store";
import { slide, scale, fade } from "svelte/transition";
import { quintOut } from "svelte/easing";
import { Notify, type Notification, Locales, Currency } from "../store/data";
import { fetchNui } from "../utils/fetchNui";
import { onMount } from "svelte";
let userData = writable({});
let accounts = writable([]);
let totalBalance = derived(accounts, ($accounts) =>
$accounts.reduce((acc, account) => acc + account.balance, 0)
);
let showModal = writable(false);
let showRenameModal = writable(false);
let showCreateAccountModal = writable(false);
let showDeleteModal = writable(false);
let showRemoveUserModal = writable(false);
let showWithdrawModal = writable(false);
let showDepositModal = writable(false);
let newServerId = writable("");
let newAccountName = writable("");
let newAccountHolder = writable("");
let newAccountBalance = writable(0);
let selectedAccount = writable<number | null>(null);
let selectedUser = writable("");
let transactionAmount = writable<number>(0);
async function renameAccount(id: number) {
const newName = get(newAccountName);
if (newName) {
try {
const response = await fetchNui("ps-banking:client:renameAccount", {
id,
newName,
});
if (response.success) {
accounts.update((accs) =>
accs.map((acc) =>
acc.id === id ? { ...acc, holder: newName } : acc
)
);
newAccountName.set("");
showRenameModal.set(false);
getAccounts();
Notify(
$Locales.account_renamed_successfully,
$Locales.success,
"check-circle"
);
} else {
Notify(
$Locales.account_rename_failed,
$Locales.error,
"exclamation-circle"
);
}
} catch (error) {
console.error(error);
Notify(
$Locales.account_rename_failed,
$Locales.error,
"exclamation-circle"
);
}
}
}
async function copyAccountNumber(id: number) {
const account = get(accounts).find((acc) => acc.id === id);
if (account) {
await fetchNui("ps-banking:client:copyAccountNumber", {
accountNumber: account.cardNumber,
});
Notify($Locales.account_number_copied, $Locales.success, "clipboard");
}
}
async function addUserToAccount(accountId: number, userId: string) {
try {
const response = await fetchNui("ps-banking:client:addUserToAccount", {
accountId,
userId,
});
if (response.success) {
accounts.update((accs) => {
const updatedAccounts = accs.map((acc) => {
if (acc.id === accountId) {
return {
...acc,
users: [
...acc.users,
{ name: response.userName, identifier: userId },
],
};
}
return acc;
});
return updatedAccounts;
});
Notify(
`${response.userName} ${$Locales.user_added_successfully}`,
$Locales.success,
"check-circle"
);
showModal.set(false);
newServerId.set("0");
getAccounts();
} else {
Notify(response.message, $Locales.error, "exclamation-circle");
}
} catch (error) {
console.error(error);
Notify(
$Locales.user_addition_failed,
$Locales.error,
"exclamation-circle"
);
}
}
async function removeUserFromAccount() {
const accountId = get(selectedAccount);
const user = get(selectedUser);
if (accountId !== null && user) {
try {
const response = await fetchNui(
"ps-banking:client:removeUserFromAccount",
{ accountId, user }
);
if (response.success) {
accounts.update((accs) => {
const updatedAccounts = accs.map((acc) =>
acc.id === accountId
? {
...acc,
users: acc.users.filter((u) => u.identifier !== user),
}
: acc
);
return updatedAccounts;
});
Notify(
`${$Locales.removed_successfully}`,
$Locales.success,
"check-circle"
);
selectedUser.set("");
showRemoveUserModal.set(false);
getAccounts();
} else {
Notify(
$Locales.user_removal_failed,
$Locales.error,
"exclamation-circle"
);
}
} catch (error) {
console.error(error);
Notify(
$Locales.user_removal_failed,
$Locales.error,
"exclamation-circle"
);
}
} else {
Notify(
$Locales.select_account_and_user,
$Locales.error,
"exclamation-circle"
);
}
}
async function deleteAccount(accountId: number) {
const response = await fetchNui("ps-banking:client:deleteAccount", {
accountId,
});
if (response.success) {
accounts.update((accs) => accs.filter((acc) => acc.id !== accountId));
Notify(
$Locales.account_deleted_successfully,
$Locales.success,
"check-circle"
);
showDeleteModal.set(false);
} else {
Notify(
$Locales.account_deletion_failed,
$Locales.error,
"exclamation-circle"
);
}
}
function formatCardNumber(cardNumber: string): string {
return cardNumber.match(/.{1,4}/g)?.join(" ") || cardNumber;
}
async function createNewAccount() {
const holder = get(newAccountHolder);
const balance = get(newAccountBalance);
const newId = Math.max(...get(accounts).map((acc) => acc.id)) + 1;
const rawCardNumber = Math.random().toString().slice(2, 18);
const cardNumber = formatCardNumber(rawCardNumber);
const newAccount = {
id: newId,
balance: balance,
holder: holder,
cardNumber: cardNumber,
users: [],
owner: {
state: true,
name: get(userData).name,
identifier: get(userData).identifier,
},
};
const response = await fetchNui("ps-banking:client:createNewAccount", {
newAccount,
});
if (response.success) {
accounts.update((accs) => [...accs, newAccount]);
newAccountHolder.set("");
newAccountBalance.set(0);
showCreateAccountModal.set(false);
getAccounts();
Notify(
$Locales.new_account_created_successfully,
$Locales.success,
"check-circle"
);
} else {
Notify(
$Locales.new_account_creation_failed,
$Locales.error,
"exclamation-circle"
);
}
}
async function withdrawFromAccount() {
const accountId = get(selectedAccount);
const amount = get(transactionAmount);
if (accountId !== null && amount > 0) {
const response = await fetchNui("ps-banking:client:withdrawFromAccount", {
accountId,
amount,
});
if (response.success) {
accounts.update((accs) => {
const updatedAccounts = accs.map((acc) =>
acc.id === accountId && acc.balance >= amount
? { ...acc, balance: acc.balance - amount }
: acc
);
return updatedAccounts;
});
Notify(
`${$Locales.withdrew} ${amount} ${$Locales.successfully}`,
$Locales.success,
"check-circle"
);
transactionAmount.set(0);
showWithdrawModal.set(false);
} else {
Notify(
$Locales.withdrawal_failed,
$Locales.error,
"exclamation-circle"
);
}
} else {
Notify(
$Locales.select_valid_account_and_amount,
$Locales.error,
"exclamation-circle"
);
}
}
async function depositToAccount() {
const accountId = get(selectedAccount);
const amount = get(transactionAmount);
if (accountId !== null && amount > 0) {
const response = await fetchNui("ps-banking:client:depositToAccount", {
accountId,
amount,
});
if (response.success) {
accounts.update((accs) => {
const updatedAccounts = accs.map((acc) =>
acc.id === accountId
? { ...acc, balance: acc.balance + amount }
: acc
);
return updatedAccounts;
});
Notify(
`${$Locales.deposited} ${amount} ${$Locales.successfully}`,
$Locales.success,
"check-circle"
);
transactionAmount.set(0);
showDepositModal.set(false);
} else {
Notify($Locales.deposit_failed, $Locales.error, "exclamation-circle");
}
} else {
Notify(
$Locales.select_valid_account_and_amount,
$Locales.error,
"exclamation-circle"
);
}
}
async function getUser() {
try {
const response = await fetchNui("ps-banking:client:getUser", {});
userData.set(response);
} catch (error) {
console.error(error);
}
}
async function getAccounts() {
try {
const response = await fetchNui("ps-banking:client:getAccounts", {});
accounts.set(response);
} catch (error) {
console.error(error);
}
}
onMount(() => {
getUser();
getAccounts();
});
</script>
<div class="absolute w-full h-full bg-gray-800 text-white">
<div
class="absolute w-[90%] h-full p-6 overflow-auto left-[130px]"
in:slide={{ duration: 1000, easing: quintOut }}
>
<div
class="bg-gray-700/10 p-8 rounded-xl shadow-lg border border-blue-200/5"
>
<div class="text-5xl font-extrabold text-left text-blue-400 mb-10">
<i class="fa-duotone fa-users text-3xl text-blue-200 mr-3"></i>
{$Locales.accounts}
</div>
<div class="relative grid grid-cols-3 gap-y-10 gap-x-4 w-[90%]">
{#each $accounts as account (account.id)}
<div
class="py-8 px-8 w-auto h-auto rounded-2xl shadow-lg flex flex-col justify-between bg-[#1c2333] text-blue-400 relative"
out:fade={{ duration: 1000, easing: quintOut }}
>
<div class="flex justify-between items-center">
<div class="text-2xl font-bold">
{account.balance.toLocaleString($Currency.lang, {
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
})}
</div>
<div class="text-sm font-semibold">#{account.id}</div>
</div>
<div class="text-xl mt-2">{account.cardNumber}</div>
<div class="flex justify-between items-center mt-4">
<div class="text-lg">{account.owner.name} - {account.holder}</div>
<div class="flex space-x-2">
<button
class="text-gray-400 hover:text-blue-300 duration-500"
on:click={() => copyAccountNumber(account.id)}
>
<i class="fa-duotone fa-copy"></i>
</button>
{#if account.owner.identifier === get(userData).identifier || account.users.some(user => user.identifier === get(userData).identifier)}
<button
class="text-gray-400 hover:text-blue-300 duration-500"
on:click={() => showRenameModal.set(account.id)}
>
<i class="fa-duotone fa-pen"></i>
</button>
{#if account.owner.identifier === get(userData).identifier}
<button
class="text-gray-400 hover:text-blue-300 duration-500"
on:click={() => showModal.set(account.id)}
>
<i class="fa-duotone fa-user-plus"></i>
</button>
<button
class="text-gray-400 hover:text-blue-300 duration-500"
on:click={() => {
selectedAccount.set(account.id);
selectedUser.set("");
showRemoveUserModal.set(true);
}}
>
<i class="fa-duotone fa-user-minus"></i>
</button>
<button
class="text-red-400 hover:text-red-300 duration-500"
on:click={() => {
selectedAccount.set(account.id);
showDeleteModal.set(true);
}}
>
<i class="fa-duotone fa-trash"></i>
</button>
{/if}
<button
class="text-green-400 hover:text-green-300 duration-500"
on:click={() => {
selectedAccount.set(account.id);
showDepositModal.set(true);
}}
>
<i class="fa-duotone fa-arrow-up"></i>
</button>
<button
class="text-yellow-400 hover:text-yellow-300 duration-500"
on:click={() => {
selectedAccount.set(account.id);
showWithdrawModal.set(true);
}}
>
<i class="fa-duotone fa-arrow-down"></i>
</button>
{/if}
</div>
</div>
</div>
{/each}
</div>
<button
class="bg-[#1c2333] mt-6 py-8 px-8 w-[250px] h-[200px] rounded-2xl shadow-lg flex items-center justify-center cursor-pointer border border-dashed border-blue-400 hover:border-blue-600 transition-all duration-500"
on:click={() => showCreateAccountModal.set(true)}
>
<i class="fa-duotone fa-plus text-4xl text-gray-200"></i>
</button>
</div>
</div>
</div>
{#if $showModal}
<div
class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50"
>
<div
class="bg-[#1c2333] p-8 rounded-lg shadow-2xl w-96 relative"
in:scale={{ duration: 250, easing: quintOut }}
out:scale={{ duration: 250, easing: quintOut }}
>
<h2 class="text-2xl font-bold text-blue-400 mb-4 flex items-center">
<i class="fa-duotone fa-exchange-alt mr-2"></i>
{$Locales.new_user_to_account}
</h2>
<div class="mb-4">
<label class="block text-blue-400 mb-2" for="ServerID"
>{$Locales.server_id}</label
>
<div class="relative">
<input
type="number"
id="ServerID"
bind:value={$newServerId}
class="w-full bg-[#283040] text-white font-bold pl-4 pr-12 py-3 rounded-lg border border-blue-400 focus:outline-none focus:border-blue-600 transition-colors duration-500 placeholder-gray-500"
placeholder="ID"
/>
<i
class="fa-duotone fa-id-card absolute top-1/2 right-4 transform -translate-y-1/2 text-blue-400"
></i>
</div>
</div>
<div class="flex justify-center space-x-4">
<button
class="bg-red-600 text-white py-2 px-4 rounded-lg flex items-center transition-colors duration-300"
on:click={() => {
showModal.set(false);
newServerId.set(0);
}}
>
<i class="fa-duotone fa-times-circle text-lg mr-2"></i>
{$Locales.cancel}
</button>
<button
class="bg-blue-600 text-white py-2 px-4 rounded-lg flex items-center transition-colors duration-300"
on:click={() => addUserToAccount(get(showModal), $newServerId)}
>
<i class="fa-duotone fa-check-circle text-lg mr-2"></i>
{$Locales.add_user}
</button>
</div>
</div>
</div>
{/if}
{#if $showRenameModal}
<div
class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50"
>
<div
class="bg-[#1c2333] p-8 rounded-lg shadow-2xl w-96 relative"
in:scale={{ duration: 250, easing: quintOut }}
out:scale={{ duration: 250, easing: quintOut }}
>
<h2 class="text-2xl font-bold text-blue-400 mb-4 flex items-center">
<i class="fa-duotone fa-edit mr-2"></i>
{$Locales.rename_account}
</h2>
<div class="mb-4">
<label class="block text-blue-400 mb-2" for="AccountName"
>{$Locales.new_account_name}</label
>
<div class="relative">
<input
type="text"
id="AccountName"
bind:value={$newAccountName}
class="w-full bg-[#283040] text-white font-bold pl-4 pr-12 py-3 rounded-lg border border-blue-400 focus:outline-none focus:border-blue-600 transition-colors duration-500 placeholder-gray-500"
placeholder={$Locales.new_name}
/>
<i
class="fa-duotone fa-pen-nib absolute top-1/2 right-4 transform -translate-y-1/2 text-blue-400"
></i>
</div>
</div>
<div class="flex justify-center space-x-4">
<button
class="bg-red-600 text-white py-2 px-4 rounded-lg flex items-center transition-colors duration-300"
on:click={() => showRenameModal.set(false)}
>
<i class="fa-duotone fa-times-circle text-lg mr-2"></i>
{$Locales.cancel}
</button>
<button
class="bg-blue-600 text-white py-2 px-4 rounded-lg flex items-center transition-colors duration-300"
on:click={() => renameAccount(get(showRenameModal))}
>
<i class="fa-duotone fa-check-circle text-lg mr-2"></i>
{$Locales.rename}
</button>
</div>
</div>
</div>
{/if}
{#if $showCreateAccountModal}
<div
class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50"
>
<div
class="bg-[#1c2333] p-8 rounded-lg shadow-2xl w-96 relative"
in:scale={{ duration: 250, easing: quintOut }}
out:scale={{ duration: 250, easing: quintOut }}
>
<h2 class="text-2xl font-bold text-blue-400 mb-4 flex items-center">
<i class="fa-duotone fa-plus mr-2"></i>
{$Locales.create_new_account}
</h2>
<div class="mb-4">
<label class="block text-blue-400 mb-2" for="AccountHolder"
>{$Locales.account_holder}</label
>
<div class="relative">
<input
type="text"
id="AccountHolder"
bind:value={$newAccountHolder}
class="w-full bg-[#283040] text-white font-bold pl-4 pr-12 py-3 rounded-lg border border-blue-400 focus:outline-none focus:border-blue-600 transition-colors duration-500 placeholder-gray-500"
placeholder={$Locales.account_holder}
/>
<i
class="fa-duotone fa-user absolute top-1/2 right-4 transform -translate-y-1/2 text-blue-400"
></i>
</div>
</div>
<div class="flex justify-center space-x-4">
<button
class="bg-red-600 text-white py-2 px-4 rounded-lg flex items-center transition-colors duration-300"
on:click={() => showCreateAccountModal.set(false)}
>
<i class="fa-duotone fa-times-circle text-lg mr-2"></i>
{$Locales.cancel}
</button>
<button
class="bg-blue-600 text-white py-2 px-4 rounded-lg flex items-center transition-colors duration-300"
on:click={createNewAccount}
>
<i class="fa-duotone fa-check-circle text-lg mr-2"></i>
{$Locales.create}
</button>
</div>
</div>
</div>
{/if}
{#if $showDeleteModal}
<div
class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50"
>
<div
class="bg-[#1c2333] p-8 rounded-lg shadow-2xl w-96 relative"
in:scale={{ duration: 250, easing: quintOut }}
out:scale={{ duration: 250, easing: quintOut }}
>
<h2 class="text-2xl font-bold text-red-400 mb-4 flex items-center">
<i class="fa-duotone fa-exclamation-triangle mr-2"></i>
{$Locales.delete_account}
</h2>
<p class="text-blue-400 mb-4">
{$Locales.are_you_sure_you_want_to_delete_this_account}
</p>
<div class="flex justify-center space-x-4">
<button
class="bg-gray-600 text-white py-2 px-4 rounded-lg flex items-center transition-colors duration-300"
on:click={() => showDeleteModal.set(false)}
>
<i class="fa-duotone fa-times-circle text-lg mr-2"></i>
{$Locales.cancel}
</button>
<button
class="bg-red-600 text-white py-2 px-4 rounded-lg flex items-center transition-colors duration-300"
on:click={() => deleteAccount(get(selectedAccount))}
>
<i class="fa-duotone fa-check-circle text-lg mr-2"></i>
{$Locales.delete}
</button>
</div>
</div>
</div>
{/if}
{#if $showRemoveUserModal}
<div
class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50"
>
<div
class="bg-[#1c2333] p-8 rounded-lg shadow-2xl w-96 relative"
in:scale={{ duration: 250, easing: quintOut }}
out:scale={{ duration: 250, easing: quintOut }}
>
<h2 class="text-2xl font-bold text-blue-400 mb-4 flex items-center">
<i class="fa-duotone fa-user-minus mr-2"></i>
{$Locales.remove_user_from_account}
</h2>
<div class="mb-4">
<label class="block text-blue-400 mb-2" for="UserSelect"
>{$Locales.select_user}</label
>
<div class="relative">
<select
id="UserSelect"
bind:value={$selectedUser}
class="w-full bg-[#283040] text-white font-bold pl-4 pr-12 py-3 rounded-lg border border-blue-400 focus:outline-none focus:border-blue-600 transition-colors duration-500 placeholder-gray-500 appearance-none"
style="background-image: none; -moz-appearance: none; -webkit-appearance: none;"
>
{#each $accounts.find((acc) => acc.id === $selectedAccount)?.users as user}
<option
value={user.identifier}
class="bg-[#283040] text-white rounded-xl font-bold pl-4 pr-12 py-4 rounded-lg transition-colors duration-500 hover:bg-blue-300/20 hover:text-gray-200 border-b border-blue-200"
>
{user.name}
</option>
{/each}
</select>
<i
class="fa-duotone fa-user absolute top-1/2 right-4 transform -translate-y-1/2 text-blue-400"
></i>
</div>
</div>
<div class="flex justify-center space-x-4">
<button
class="bg-gray-600 text-white py-2 px-4 rounded-lg flex items-center transition-colors duration-300"
on:click={() => {
showRemoveUserModal.set(false);
selectedUser.set("");
}}
>
<i class="fa-duotone fa-times-circle text-lg mr-2"></i>
{$Locales.cancel}
</button>
<button
class="bg-red-600 text-white py-2 px-4 rounded-lg flex items-center transition-colors duration-300"
on:click={removeUserFromAccount}
>
<i class="fa-duotone fa-check-circle text-lg mr-2"></i>
{$Locales.remove}
</button>
</div>
</div>
</div>
{/if}
{#if $showWithdrawModal}
<div
class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50"
>
<div
class="bg-[#1c2333] p-8 rounded-lg shadow-2xl w-96 relative"
in:scale={{ duration: 250, easing: quintOut }}
out:scale={{ duration: 250, easing: quintOut }}
>
<h2 class="text-2xl font-bold text-yellow-400 mb-4 flex items-center">
<i class="fa-duotone fa-arrow-down mr-2"></i>
{$Locales.withdraw_from_account}
</h2>
<div class="mb-4">
<label class="block text-yellow-400 mb-2" for="WithdrawAmount"
>{$Locales.withdraw_amount}</label
>
<div class="relative">
<input
type="number"
id="WithdrawAmount"
bind:value={$transactionAmount}
class="w-full bg-[#283040] text-white font-bold pl-4 pr-12 py-3 rounded-lg border border-yellow-400 focus:outline-none focus:border-yellow-600 transition-colors duration-500 placeholder-gray-500"
placeholder="0"
/>
<i
class="fa-duotone fa-dollar-sign absolute top-1/2 right-4 transform -translate-y-1/2 text-yellow-400"
></i>
</div>
</div>
<div class="flex justify-center space-x-4">
<button
class="bg-gray-600 text-white py-2 px-4 rounded-lg flex items-center transition-colors duration-300"
on:click={() => {
showWithdrawModal.set(false);
transactionAmount.set(0);
}}
>
<i class="fa-duotone fa-times-circle text-lg mr-2"></i>
{$Locales.cancel}
</button>
<button
class="bg-yellow-600 text-white py-2 px-4 rounded-lg flex items-center transition-colors duration-300"
on:click={withdrawFromAccount}
>
<i class="fa-duotone fa-check-circle text-lg mr-2"></i>
{$Locales.withdraw}
</button>
</div>
</div>
</div>
{/if}
{#if $showDepositModal}
<div
class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50"
>
<div
class="bg-[#1c2333] p-8 rounded-lg shadow-2xl w-96 relative"
in:scale={{ duration: 250, easing: quintOut }}
out:scale={{ duration: 250, easing: quintOut }}
>
<h2 class="text-2xl font-bold text-green-400 mb-4 flex items-center">
<i class="fa-duotone fa-arrow-up mr-2"></i>
{$Locales.deposit_to_account}
</h2>
<div class="mb-4">
<label class="block text-green-400 mb-2" for="DepositAmount"
>{$Locales.deposit_amount}</label
>
<div class="relative">
<input
type="number"
id="DepositAmount"
bind:value={$transactionAmount}
class="w-full bg-[#283040] text-white font-bold pl-4 pr-12 py-3 rounded-lg border border-green-400 focus:outline-none focus:border-green-600 transition-colors duration-500 placeholder-gray-500"
placeholder="0"
/>
<i
class="fa-duotone fa-dollar-sign absolute top-1/2 right-4 transform -translate-y-1/2 text-green-400"
></i>
</div>
</div>
<div class="flex justify-center space-x-4">
<button
class="bg-gray-600 text-white py-2 px-4 rounded-lg flex items-center transition-colors duration-300"
on:click={() => {
showDepositModal.set(false);
transactionAmount.set(0);
}}
>
<i class="fa-duotone fa-times-circle text-lg mr-2"></i>
{$Locales.cancel}
</button>
<button
class="bg-green-600 text-white py-2 px-4 rounded-lg flex items-center transition-colors duration-300"
on:click={depositToAccount}
>
<i class="fa-duotone fa-check-circle text-lg mr-2"></i>
{$Locales.deposit}
</button>
</div>
</div>
</div>
{/if}

View file

@ -0,0 +1,153 @@
<script lang="ts">
import { writable } from "svelte/store";
import { onMount } from "svelte";
import { fetchNui } from "../utils/fetchNui";
import { slide, fade, scale } from "svelte/transition";
import { quintOut } from "svelte/easing";
import { Bills } from "../store/data";
import { Notify, Locales, Currency } from "../store/data";
let transactions = Bills;
let searchQuery = writable("");
$: filteredTransactions = $transactions.filter(
(transaction) =>
transaction.description
.toLowerCase()
.includes($searchQuery.toLowerCase()) ||
transaction.type.toLowerCase().includes($searchQuery.toLowerCase())
);
function formatDate(dateString: string) {
const options: Intl.DateTimeFormatOptions = {
year: "numeric",
month: "numeric",
day: "numeric",
};
return new Date(dateString).toLocaleDateString(undefined, options);
}
async function payBill(transaction: { id: any; type: any }) {
try {
const result = await fetchNui("ps-banking:client:payBill", {
id: transaction.id,
});
if (result) {
Notify(
`${$Locales.pay_invoice} #${transaction.id} ${$Locales.from} ${transaction.type}`,
$Locales.payment_completed,
"coins"
);
transactions.update((items) => {
const index = items.findIndex((t) => transaction.id === t.id);
if (index !== -1) {
items.splice(index, 1);
}
return items;
});
return true;
} else {
Notify(`${$Locales.no_money_on_account}`, `${$Locales.error}`, "coins");
return false;
}
} catch (error) {
console.error(error);
return false;
}
}
onMount(async () => {
try {
const response = await fetchNui("ps-banking:client:getBills", {});
Bills.set(response);
} catch (error) {
console.error(error);
}
});
</script>
<div class="absolute w-full h-full bg-gray-800 text-white">
<div
class="absolute w-[90%] h-full p-6 overflow-auto left-[130px]"
in:slide={{ duration: 1000, easing: quintOut }}
>
<div
class="bg-gray-800/50 p-8 rounded-lg shadow-lg border border-blue-200/5"
>
<div class="flex justify-between items-center mb-6">
<div class="flex items-center">
<i class="fa-duotone fa-list text-3xl text-blue-200 mr-3"></i>
<h2 class="text-3xl font-bold text-blue-200">{$Locales.bills}</h2>
</div>
<div class="bg-[#334155] rounded-full px-3 py-1 flex items-center">
<i class="fa-duotone fa-file-invoice-dollar text-gray-400 mr-2"></i>
<span class="text-sm text-gray-400 mr-2">{$Locales.total}</span>
<span class="text-lg font-semibold text-white">
{$transactions.length}
</span>
</div>
</div>
<div class="relative mb-6">
<i class="fa-duotone fa-search absolute left-4 top-4 text-gray-400"></i>
<input
type="text"
class="w-full bg-gray-800 text-white pl-10 pr-4 py-3 rounded-lg border border-blue-200/10 focus:outline-none focus:border-blue-400/50 transition-colors duration-500 placeholder-gray-500"
placeholder={$Locales.search_transactions}
bind:value={$searchQuery}
/>
</div>
<div class="space-y-6">
{#each filteredTransactions as transaction (transaction.id)}
<div
class="p-4 bg-[#334155] rounded-lg flex justify-between items-center"
out:slide={{ duration: 1000, easing: quintOut }}
>
<div class="flex flex-col space-y-1">
<div class="flex items-center space-x-2">
<i class="fa-duotone fa-file-invoice text-2xl text-[#f1f5f9]"
></i>
<span class="font-semibold text-[#f1f5f9]"
>{transaction.description} #{transaction.id}</span
>
</div>
<div class="flex items-center space-x-2">
<i class="fa-duotone fa-user text-sm text-gray-400"></i>
<span class="text-sm text-gray-400">{transaction.type}</span>
</div>
<div class="flex items-center space-x-2">
<i class="fa-duotone fa-clock text-xs text-gray-500"></i>
<span class="text-xs text-gray-500"
>{formatDate(transaction.date)}</span
>
</div>
</div>
<div class="text-right flex flex-col items-end space-y-1">
<span
class={`text-lg font-bold ${transaction.isIncome ? "text-green-500" : "text-red-500"}`}
>
<i class="fa-duotone fa-coins text-lg text-gray-400 mr-2"></i>
{transaction.isIncome ? "+" : "-"}
{transaction.amount.toLocaleString($Currency.lang, {
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
})}
</span>
{#if !transaction.isPaid}
<button
class="bg-blue-600/10 border border-blue-500 hover:bg-blue-800/50 text-white font-bold py-2 px-4 rounded-lg flex items-center transition-colors duration-300"
on:click={() => {
payBill(transaction);
}}
>
<i class="fa-duotone fa-money-check-edit text-lg mr-2"></i>
{$Locales.pay_invoice}
</button>
{/if}
</div>
</div>
{/each}
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,113 @@
<script lang="ts">
import { writable } from "svelte/store";
import { slide } from "svelte/transition";
import { quintOut } from "svelte/easing";
import { fetchNui } from "../utils/fetchNui";
import {
Notify,
currentCash,
bankBalance,
Locales,
Currency,
} from "../store/data";
let withdrawAmount = writable($bankBalance);
$: newBank = $bankBalance - $withdrawAmount;
async function handleWithdraw() {
if ($bankBalance < $withdrawAmount) {
Notify(
`${$Locales.withdraw_error} ${$withdrawAmount.toLocaleString(
$Currency.lang,
{
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
}
)}`,
$Locales.error,
"coins"
);
} else {
Notify(
`${$Locales.withdraw_success} ${$withdrawAmount.toLocaleString(
$Currency.lang,
{
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
}
)}`,
$Locales.withdraw_success,
"coins"
);
await fetchNui("ps-banking:client:ATMwithdraw", {
amount: $withdrawAmount,
});
currentCash.update((cash) => cash + $withdrawAmount);
bankBalance.update((balance) => balance - $withdrawAmount);
withdrawAmount.set(0);
}
}
</script>
<!-- svelte-ignore a11y-label-has-associated-control -->
<div class="absolute w-full h-full bg-gray-800 text-white">
<div
class="absolute w-[90%] h-full p-6 overflow-auto left-[130px]"
in:slide={{ duration: 1000, easing: quintOut }}
>
<div
class="bg-gray-800/50 p-8 rounded-lg shadow-lg border border-blue-200/5"
>
<h2 class="text-3xl font-bold mb-6">{$Locales.withdraw}</h2>
<div class="mb-12">
<label class="block text-blue-200 mb-1">{$Locales.bank_balance}</label>
<div class="flex items-center text-2xl font-semibold">
<i class="fa-duotone fa-university text-gray-400 mr-2"></i>
{$bankBalance.toLocaleString($Currency.lang, {
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
})}
</div>
</div>
<div class="mb-12">
<label class="block text-blue-200 mb-1">{$Locales.amount}</label>
<div class="relative">
<i
class="fa-duotone fa-money-bill-wave absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"
></i>
<input
type="number"
class="w-full rounded bg-gray-700/50 text-white pl-10 pr-4 py-3 border border-blue-200/10 rounded-lg focus:outline-none
focus:border-blue-400/50 transition-colors duration-500"
bind:value={$withdrawAmount}
/>
</div>
</div>
<div class="mb-12">
<label class="block text-blue-200 mb-1">{$Locales.new_bank}</label>
<div class="flex items-center text-2xl font-semibold">
<i class="fa-duotone fa-coins text-gray-400 mr-2"></i>
{newBank.toLocaleString($Currency.lang, {
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
})}
</div>
</div>
<button
class="w-full bg-blue-600/10 hover:bg-blue-700/10 text-white font-bold py-3 rounded transition duration-300 flex items-center justify-center border border-blue-500/50"
on:click={handleWithdraw}
>
<i class="fa-duotone fa-money-check-edit text-lg mr-2"></i>
{$Locales.withdraw_button}
</button>
</div>
</div>
</div>

View file

@ -0,0 +1,206 @@
<script lang="ts">
import { writable } from "svelte/store";
import { onMount } from "svelte";
import { quintOut } from "svelte/easing";
import { slide, fade, scale } from "svelte/transition";
import { fetchNui } from "../utils/fetchNui";
import {
showOverview,
showBills,
showHistory,
notifications,
Bills,
Notify,
Transactions,
Locales,
Currency,
type Notification,
} from "../store/data";
// Sample data
let transactions = Transactions;
let searchQuery = writable("");
let showDeleteAllModal = writable(false);
$: filteredTransactions = $transactions.filter(
(transaction) =>
transaction.description
.toLowerCase()
.includes($searchQuery.toLowerCase()) ||
transaction.type.toLowerCase().includes($searchQuery.toLowerCase())
);
function confirmDeleteAllTransactions() {
showDeleteAllModal.set(true);
}
async function deleteAllTransactions() {
if ($transactions.length === 0) {
Notify($Locales.history_empty, $Locales.error, "file-invoice");
showDeleteAllModal.set(false);
} else {
transactions.set([]);
showDeleteAllModal.set(false);
Notify($Locales.all_history_deleted, $Locales.success, "file-invoice");
try {
const history = await fetchNui("ps-banking:client:deleteHistory", {});
transactions.set([]);
} catch (error) {
console.error(error);
}
}
}
function formatDate(dateString: string) {
const options: Intl.DateTimeFormatOptions = {
year: "numeric",
month: "numeric",
day: "numeric",
};
return new Date(dateString).toLocaleDateString(undefined, options);
}
onMount(async () => {
try {
const history = await fetchNui("ps-banking:client:getHistory", {});
transactions.set(history);
} catch (error) {
console.error(error);
}
});
</script>
<div class="absolute w-full h-full bg-gray-800">
<div
class="absolute w-[90%] h-full p-6 overflow-auto left-[130px] text-blue-200"
in:slide={{ duration: 1000, easing: quintOut }}
>
<div
class="bg-gray-800/50 p-8 rounded-lg shadow-lg border border-blue-200/5"
>
<div class="flex items-center mb-4">
<i class="fa-duotone fa-list text-2xl text-blue-400 mr-3"></i>
<h2 class="text-2xl font-bold">{$Locales.history}</h2>
</div>
<div class="flex justify-between items-center mb-4">
<div class="flex items-center">
<i class="fa-duotone fa-wallet text-xl text-gray-400 mr-2"></i>
<span class="text-gray-400">{$Locales.total}</span>
</div>
<div class="absolute right-16 top-10">
<i class="fa-duotone fa-receipt text-xl text-gray-400 mr-2"></i>
<span class="text-xl text-white font-semibold">
{filteredTransactions.length}
</span>
</div>
<button
class="bg-gray-700/50 text-blue-200 py-2 px-4 rounded-lg flex items-center hover:bg-gray-500/50 transition duration-500 border border-gray-500/20"
on:click={confirmDeleteAllTransactions}
>
<i class="fa-duotone fa-trash-alt mr-2"
></i>{$Locales.delete_all_transactions}
</button>
</div>
<div class="relative mb-6">
<i class="fa-duotone fa-search absolute left-4 top-4 text-gray-400"></i>
<input
type="text"
class="w-full rounded bg-gray-700/50 text-white pl-10 pr-4 py-3 border border-blue-200/10 rounded-lg focus:outline-none
focus:border-blue-400/50 transition-colors duration-500"
placeholder={$Locales.search_transactions}
bind:value={$searchQuery}
/>
</div>
<div class="space-y-4">
{#each filteredTransactions as transaction (transaction.id)}
<div
class="bg-[#334155] p-4 rounded-lg shadow-md transition duration-300 border border-blue-200/5"
out:slide={{ duration: 500 }}
>
<div class="flex justify-between items-center">
<div class="flex items-center">
<i
class={`fa-duotone ${transaction.isIncome ? "fa-arrow-down-to-arc" : "fa-arrow-up-from-arc"} text-xl mr-3 ${transaction.isIncome ? "text-green-400" : "text-red-400"}`}
></i>
<div>
<div class="flex items-center">
<i
class="fa-duotone fa-file-invoice text-lg text-gray-300 mr-2"
></i>
<p class="text-lg font-bold">
{transaction.description} #{transaction.id}
</p>
</div>
<div class="flex items-center">
<i
class="fa-duotone fa-exchange-alt text-sm text-gray-400 mr-2"
></i>
<p class="text-sm text-gray-400">{transaction.type}</p>
</div>
<div class="flex items-center">
<i class="fa-duotone fa-clock text-xs text-gray-500 mr-2"
></i>
<p class="text-xs text-gray-500">
{formatDate(transaction.date)}
</p>
</div>
</div>
</div>
<div class="text-right">
<div class="flex items-center">
<i class="fa-duotone fa-coins text-lg text-gray-400 mr-2"></i>
<p
class={`text-lg font-bold ${transaction.isIncome ? "text-green-500" : "text-red-500"}`}
>
{transaction.isIncome ? "+" : "-"}
{transaction.amount.toLocaleString($Currency.lang, {
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
})}
</p>
</div>
</div>
</div>
</div>
{/each}
</div>
</div>
</div>
</div>
{#if $showDeleteAllModal}
<div
class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50"
>
<div
class="bg-gray-700 p-8 rounded-lg shadow-lg w-96"
in:scale={{ duration: 250, easing: quintOut }}
out:scale={{ duration: 250, easing: quintOut }}
>
<div class="flex items-center mb-4">
<i class="fa-duotone fa-question-circle text-3xl text-blue-400 mr-3"
></i>
<h2 class="text-2xl text-blue-200 font-bold">
{$Locales.are_you_sure}
</h2>
</div>
<p class="text-gray-300 mb-6">
{$Locales.delete_confirmation}
</p>
<div class="flex justify-between items-center">
<button
class="flex items-center bg-red-600 hover:bg-red-700 text-white py-2 px-4 rounded focus:outline-none"
on:click={() => showDeleteAllModal.set(false)}
>
<i class="fa-duotone fa-times-circle mr-2"></i>{$Locales.cancel}
</button>
<button
class="flex items-center bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded focus:outline-none"
on:click={deleteAllTransactions}
>
<i class="fa-duotone fa-check-circle mr-2"></i>{$Locales.confirm}
</button>
</div>
</div>
</div>
{/if}

View file

@ -0,0 +1,115 @@
<script lang="ts">
import { onMount } from "svelte";
import { writable } from "svelte/store";
import { slide } from "svelte/transition";
import { quintOut } from "svelte/easing";
import {
Notify,
currentCash,
bankBalance,
Locales,
Currency,
} from "../store/data";
import { fetchNui } from "../utils/fetchNui";
let depositAmount = writable($currentCash);
$: newCash = $currentCash - $depositAmount;
async function handleDeposit() {
if ($currentCash < $depositAmount) {
Notify(
`${$Locales.deposit_error} ${$depositAmount.toLocaleString(
$Currency.lang,
{
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
}
)}`,
$Locales.error,
"coins"
);
} else {
Notify(
`${$Locales.deposit_success} ${$depositAmount.toLocaleString(
$Currency.lang,
{
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
}
)} `,
$Locales.deposit_success,
"coins"
);
await fetchNui("ps-banking:client:ATMdeposit", {
amount: $depositAmount,
});
currentCash.update((cash) => cash - $depositAmount);
bankBalance.update((balance) => balance + $depositAmount);
depositAmount.set(0);
}
}
</script>
<!-- svelte-ignore a11y-label-has-associated-control -->
<div class="absolute w-full h-full bg-gray-800 text-white">
<div
class="absolute w-[90%] h-full p-6 overflow-auto left-[130px]"
in:slide={{ duration: 1000, easing: quintOut }}
>
<div
class="bg-gray-800/50 p-8 rounded-lg shadow-lg border border-blue-200/5"
>
<h2 class="text-3xl font-bold mb-6">{$Locales.deposit}</h2>
<div class="mb-12">
<label class="block text-blue-200 mb-1">{$Locales.current_cash}</label>
<div class="flex items-center text-2xl font-semibold">
<i class="fa-duotone fa-wallet text-gray-400 mr-2"></i>
{$currentCash.toLocaleString($Currency.lang, {
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
})}
</div>
</div>
<div class="mb-12">
<label class="block text-blue-200 mb-1">{$Locales.amount}</label>
<div class="relative">
<i
class="fa-duotone fa-money-bill-wave absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"
></i>
<input
type="number"
class="w-full rounded bg-gray-700/50 text-white pl-10 pr-4 py-3 border border-blue-200/10 rounded-lg focus:outline-none
focus:border-blue-400/50 transition-colors duration-500"
bind:value={$depositAmount}
/>
</div>
</div>
<div class="mb-12">
<label class="block text-blue-200 mb-1">{$Locales.new_cash}</label>
<div class="flex items-center text-2xl font-semibold">
<i class="fa-duotone fa-coins text-gray-400 mr-2"></i>
{newCash.toLocaleString($Currency.lang, {
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
})}
</div>
</div>
<button
class="w-full bg-blue-600/10 hover:bg-blue-700/10 text-white font-bold py-3 rounded transition duration-300 flex items-center justify-center border border-blue-500/50"
on:click={handleDeposit}
>
<i class="fa-duotone fa-money-check-edit text-lg mr-2"></i>
{$Locales.deposit_button}
</button>
</div>
</div>
</div>

View file

@ -0,0 +1,297 @@
<script lang="ts">
import { onMount } from "svelte";
import { useNuiEvent } from "../utils/useNuiEvent";
import { fetchNui } from "../utils/fetchNui";
import { visibility } from "../store/stores";
import OverviewPage from "./Overview.svelte";
import BillsPage from "./Bills.svelte";
import HistoryPage from "./History.svelte";
import HeavPage from "./Heav.svelte";
import IndseatPage from "./Indseat.svelte";
import StatsPage from "./Stats.svelte";
import AccountsPage from "./Accounts.svelte";
import { slide, fade, scale } from "svelte/transition";
import { quintOut } from "svelte/easing";
import {
showOverview,
showBills,
showHistory,
showHeav,
showIndseat,
showStats,
showAccounts,
Locales,
bankBalance,
currentCash,
} from "../store/data";
import { writable } from "svelte/store";
async function updateBalances() {
try {
const response = await fetchNui("ps-banking:client:getMoneyTypes", {});
const bank = response.find(
(item: { name: string }) => item.name === "bank"
);
const cash = response.find(
(item: { name: string }) => item.name === "cash"
);
if (bank) {
bankBalance.set(bank.amount);
}
if (cash) {
currentCash.set(cash.amount);
}
} catch (error) {
console.error(error);
}
}
onMount(async () => {
updateBalances();
try {
const response = await fetchNui("ps-banking:client:getLocales", {});
Locales.set(response);
} catch (error) {
console.error(error);
}
});
</script>
<div
class="h-screen w-screen flex flex-col items-center justify-center select-none overflow-hidden"
>
<div
class="absolute w-[80%] h-[90%] rounded-xl overflow-hidden"
in:scale={{ duration: 1000, easing: quintOut }}
out:fade={{ duration: 1000, easing: quintOut }}
>
{#if $showOverview}
<OverviewPage />
{:else if $showBills}
<BillsPage />
{:else if $showHistory}
<HistoryPage />
{:else if $showHeav}
<HeavPage />
{:else if $showIndseat}
<IndseatPage />
{:else if $showStats}
<StatsPage />
{:else if $showAccounts}
<AccountsPage />
{/if}
<!-- SideBar -->
<div
class="relative bg-gray-700/90 left-0 border border-gray-600/40 h-full w-28 flex flex-col items-center rounded-l-xl overflow-hidden"
>
<div class="relative h-full w-full top-3 left-[2px] space-y-2">
<label
class="text-white font-bold p-0 rounded flex flex-col items-center uppercase w-[97%]"
>
<input
type="radio"
name="radio"
value="overview"
class="hidden peer"
checked={$showOverview}
on:change={() => {
showOverview.set(true);
showBills.set(false);
showHistory.set(false);
showHeav.set(false);
showIndseat.set(false);
showStats.set(false);
showAccounts.set(false);
}}
/>
<span
class="w-[97%] relative flex flex-col items-center text-gray-300 py-4 peer-checked:shadow-md transition-all duration-500 rounded-xl
peer-checked:text-blue-400 peer-checked:shadow-lg hover:text-blue-300 duration-500 peer-checked:bg-gray-600 hover:cursor-pointer hover:bg-gray-800/80"
>
<i class="fa-duotone fa-house text-3xl text-blue-300 mb-2"></i>
<span class="relative">{$Locales.overview}</span>
</span>
</label>
<label
class="text-white font-bold p-0 rounded flex flex-col items-center uppercase w-[97%]"
>
<input
type="radio"
name="radio"
value="send"
class="hidden peer"
checked={$showBills}
on:change={() => {
showOverview.set(false);
showBills.set(true);
showHistory.set(false);
showHeav.set(false);
showIndseat.set(false);
showStats.set(false);
showAccounts.set(false);
}}
/>
<span
class="w-[97%] relative flex flex-col items-center text-gray-300 py-4 peer-checked:shadow-md transition-all duration-500 rounded-xl
peer-checked:text-blue-400 peer-checked:shadow-lg hover:text-blue-300 duration-500 peer-checked:bg-gray-600 hover:cursor-pointer hover:bg-gray-800/80"
>
<i class="fa-duotone fa-file-invoice text-3xl text-blue-300 mb-2"
></i>
<span class="relative">{$Locales.bills}</span>
</span>
</label>
<label
class="text-white font-bold p-0 rounded flex flex-col items-center uppercase w-[97%]"
>
<input
type="radio"
name="radio"
value="history"
class="hidden peer"
checked={$showHistory}
on:change={() => {
showOverview.set(false);
showBills.set(false);
showHistory.set(true);
showHeav.set(false);
showIndseat.set(false);
showStats.set(false);
showAccounts.set(false);
}}
/>
<span
class="w-[97%] relative flex flex-col items-center text-gray-300 py-4 peer-checked:shadow-md transition-all duration-500 rounded-xl
peer-checked:text-blue-400 peer-checked:shadow-lg hover:text-blue-300 duration-500 peer-checked:bg-gray-600 hover:cursor-pointer hover:bg-gray-800/80"
>
<i class="fa-duotone fa-circle-dollar text-3xl text-blue-300 mb-2"
></i>
<span class="relative">{$Locales.history}</span>
</span>
</label>
<label
class="text-white font-bold p-0 rounded flex flex-col items-center uppercase w-[97%]"
>
<input
type="radio"
name="radio"
value="control"
class="hidden peer"
checked={$showHeav}
on:change={() => {
showOverview.set(false);
showBills.set(false);
showHistory.set(false);
showHeav.set(true);
showIndseat.set(false);
showStats.set(false);
showAccounts.set(false);
}}
/>
<span
class="w-[97%] relative flex flex-col items-center text-gray-300 py-4 peer-checked:shadow-md transition-all duration-500 rounded-xl
peer-checked:text-blue-400 peer-checked:shadow-lg hover:text-blue-300 duration-500 peer-checked:bg-gray-600 hover:cursor-pointer hover:bg-gray-800/80"
>
<i class="fa-duotone fa-minus text-3xl text-blue-300 mb-2"></i>
<span class="relative">{$Locales.withdraw}</span>
</span>
</label>
<label
class="text-white font-bold p-0 rounded flex flex-col items-center uppercase w-[97%]"
>
<input
type="radio"
name="radio"
value="control"
class="hidden peer"
checked={$showIndseat}
on:change={() => {
showOverview.set(false);
showBills.set(false);
showHistory.set(false);
showHeav.set(false);
showIndseat.set(true);
showStats.set(false);
showAccounts.set(false);
}}
/>
<span
class="w-[97%] relative flex flex-col items-center text-gray-300 py-4 peer-checked:shadow-md transition-all duration-500 rounded-xl
peer-checked:text-blue-400 peer-checked:shadow-lg hover:text-blue-300 duration-500 peer-checked:bg-gray-600 hover:cursor-pointer hover:bg-gray-800/80"
>
<i class="fa-duotone fa-plus text-3xl text-blue-300 mb-2"></i>
<span class="relative">{$Locales.deposit}</span>
</span>
</label>
<label
class="text-white font-bold p-0 rounded flex flex-col items-center uppercase w-[97%]"
>
<input
type="radio"
name="radio"
value="control"
class="hidden peer"
checked={$showStats}
on:change={() => {
showOverview.set(false);
showBills.set(false);
showHistory.set(false);
showHeav.set(false);
showIndseat.set(false);
showStats.set(true);
showAccounts.set(false);
}}
/>
<span
class="w-[97%] relative flex flex-col items-center text-gray-300 py-4 peer-checked:shadow-md transition-all duration-500 rounded-xl
peer-checked:text-blue-400 peer-checked:shadow-lg hover:text-blue-300 duration-500 peer-checked:bg-gray-600 hover:cursor-pointer hover:bg-gray-800/80"
>
<i class="fa-duotone fa-chart-simple text-3xl text-blue-300 mb-2"
></i>
<span class="relative">{$Locales.stats}</span>
</span>
</label>
<label
class="text-white font-bold p-0 rounded flex flex-col items-center uppercase w-[97%]"
>
<input
type="radio"
name="radio"
value="control"
class="hidden peer"
checked={$showAccounts}
on:change={() => {
showOverview.set(false);
showBills.set(false);
showHistory.set(false);
showHeav.set(false);
showIndseat.set(false);
showStats.set(false);
showAccounts.set(true);
}}
/>
<span
class="w-[97%] relative flex flex-col items-center text-gray-300 py-4 peer-checked:shadow-md transition-all duration-500 rounded-xl
peer-checked:text-blue-400 peer-checked:shadow-lg hover:text-blue-300 duration-500 peer-checked:bg-gray-600 hover:cursor-pointer hover:bg-gray-800/80"
>
<i class="fa-duotone fa-users text-3xl text-blue-300 mb-2"></i>
<span class="relative">{$Locales.accounts}</span>
</span>
</label>
<!-- Close -->
<div class="relative -bottom-48 left-[.5px]">
<button
class="w-[95%] text-blue-200 font-bold uppercase p-5 rounded-lg hover:bg-gray-800/80 duration-500 h-[100px] flex flex-col items-center"
on:click={() => {
fetchNui("ps-banking:client:hideUI");
visibility.set(false);
}}
>
<i class="fa-duotone fa-circle-xmark text-3xl text-blue-300 mb-2"
></i>
<span class="relative">{$Locales.close}</span>
</button>
</div>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,755 @@
<script lang="ts">
import { writable } from "svelte/store";
import { onMount } from "svelte";
import Chart from "chart.js/auto";
import { fetchNui } from "../utils/fetchNui";
import { quintOut } from "svelte/easing";
import { slide, fade, scale } from "svelte/transition";
import {
showOverview,
showBills,
showHistory,
showHeav,
notifications,
Bills,
Notify,
Transactions,
currentCash,
bankBalance,
Locales,
Currency,
type Notification,
} from "../store/data";
let notificationId = 0;
let transactions = Bills;
let phone = false;
let showSureModalBills = writable(false);
let showTransferModal = writable(false);
let transferData = writable({
idOrPhone: "",
amount: 0,
confirm: false,
contactType: "none",
});
let weeklyData = writable({
totalReceived: 0,
totalUsed: 0,
});
let chart: Chart;
let chartCanvas: HTMLCanvasElement;
async function fetchWeeklySummary() {
try {
const response = await fetchNui("ps-banking:client:getWeeklySummary", {});
if (response) {
weeklyData.set(response);
}
} catch (error) {
console.error(error);
}
}
async function updateBalances() {
try {
const response = await fetchNui("ps-banking:client:getMoneyTypes", {});
const bank = response.find(
(item: { name: string }) => item.name === "bank"
);
const cash = response.find(
(item: { name: string }) => item.name === "cash"
);
if (bank) {
bankBalance.set(bank.amount);
}
if (cash) {
currentCash.set(cash.amount);
}
} catch (error) {
console.error(error);
}
}
async function payAllBills() {
const success = await fetchNui("ps-banking:client:payAllBills", {});
if (success) {
await getBills();
Notify(
$Locales.pay_all_bills_success,
$Locales.payment_completed,
"money-bill"
);
} else {
Notify(
$Locales.pay_all_bills_error,
$Locales.error,
"circle-exclamation"
);
}
}
function openModal() {
showTransferModal.set(true);
}
function closeModal() {
showTransferModal.set(false);
transferData.set({
idOrPhone: "",
amount: 0,
confirm: false,
contactType: "none",
});
}
async function getBills() {
try {
const response = await fetchNui("ps-banking:client:getBills", {});
Bills.set(response);
} catch (error) {
console.error(error);
}
}
async function getHistory() {
try {
const history = await fetchNui("ps-banking:client:getHistory", {});
Transactions.set(history);
} catch (error) {
console.error(error);
}
}
async function confirmTransfer(id: any, amount: any, method: any) {
try {
const response = await fetchNui("ps-banking:client:transferMoney", {
id: id,
amount: amount,
method: method,
});
if (response.success) {
Notify(response.message, $Locales.payment_completed, "user");
} else {
Notify(response.message, $Locales.error, "user");
}
} catch (error) {
console.error(error);
}
transferData.update((data) => {
data.confirm = true;
return data;
});
showTransferModal.set(false);
transferData.set({
idOrPhone: "",
amount: 0,
confirm: false,
contactType: "none",
});
}
let bankData = {
balance: $bankBalance,
cash: $currentCash,
transactions: $Transactions,
};
$: bankData = {
balance: $bankBalance,
cash: $currentCash,
transactions: $Transactions,
};
async function heav() {
try {
const response = await fetchNui("ps-banking:client:ATMwithdraw", {
amount: $bankBalance,
});
if (response) {
updateStuff();
}
} catch (error) {
console.error(error);
}
}
async function deposit() {
try {
const response = await fetchNui("ps-banking:client:ATMdeposit", {
amount: $currentCash,
});
if (response) {
updateStuff();
}
} catch (error) {
console.error(error);
}
}
function createChart() {
if (chartCanvas) {
chart = new Chart(chartCanvas, {
type: "bar",
data: {
labels: [$Locales.income, $Locales.expenses],
datasets: [
{
label: $Locales.weekly_summary,
data: [0, 0],
backgroundColor: ["#3b82f6", "#ef4444"],
},
],
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true,
},
},
},
});
}
}
$: {
weeklyData.subscribe((data) => {
if (chart) {
chart.data.datasets[0].data = [data.totalReceived, data.totalUsed];
chart.update();
}
});
}
async function updateStuff() {
// Hot update
await getBills();
await getHistory();
await fetchWeeklySummary();
await updateBalances();
}
async function phoneOption() {
try {
const response = await fetchNui("ps-banking:client:phoneOption", {});
phone = response
} catch (error) {
console.error(error);
}
}
onMount(async () => {
createChart();
updateStuff();
updateStuff();
phoneOption();
});
</script>
<div class="absolute w-full h-full bg-gray-800">
<div
class="absolute top-0 left-[240px] w-screen h-screen overflow-hidden p-6 text-white"
in:slide={{ duration: 1000, easing: quintOut }}
>
<!-- Quick Actions -->
<div class="mb-8">
<div class="text-xl mb-4 text-blue-200">{$Locales.total_balance}</div>
<div class="text-3xl font-bold mb-8 text-blue-300">
{$bankBalance.toLocaleString($Currency.lang, {
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
})}
</div>
</div>
<div class="mb-2">
<div class="text-2xl mb-4 text-blue-200">{$Locales.quick_actions}</div>
<div class="flex space-x-4">
<div
class="flex flex-col items-center bg-gray-700 rounded-xl p-4 w-[280px]"
>
<i
class="fa-duotone fa-arrow-right-arrow-left text-4xl mb-2 text-blue-400"
></i>
<div class="text-2xl font-bold mb-2 text-blue-200">
{$Locales.transfer_money}
</div>
<div class="text-sm mb-2 text-gray-400">
{$Locales.easy_transfer}
</div>
<button
class="bg-blue-600/10 border border-blue-500 hover:bg-blue-800/50 text-white font-bold py-2 px-4 mt-4 duration-500 rounded-lg cursor-pointer"
on:click={openModal}
>
{$Locales.transfer}
</button>
</div>
<div
class="flex flex-col items-center bg-gray-700 rounded-xl p-4 w-[280px]"
>
<i
class="fa-duotone fa-file-invoice-dollar text-4xl mb-2 text-blue-400"
></i>
<div class="text-2xl font-bold mb-2 text-blue-200">
{$Locales.pay_bills}
</div>
<div class="text-sm mb-2 text-gray-400">
{$Locales.pay_pending_bills}
</div>
<button
class="relative -bottom-auto bg-blue-600/10 border border-blue-500 hover:bg-blue-800/50 text-white font-bold py-2 px-8 mt-4 duration-500 rounded-lg cursor-pointer"
on:click={() => {
showSureModalBills.set(true);
}}
>
{$Locales.pay}
</button>
</div>
<div
class="flex flex-col items-center bg-gray-700 rounded-xl p-4 w-[280px]"
>
<i class="fa-duotone fa-credit-card text-4xl mb-2 text-blue-400"></i>
<div class="text-2xl font-bold mb-2 text-blue-200">
{$Locales.withdraw_all_money}
</div>
<div class="text-sm mb-2 text-gray-400">
{$Locales.withdraw_all_from_account}
</div>
<button
class="bg-blue-600/10 border border-blue-500 hover:bg-blue-800/50 text-white font-bold py-2 px-4 mt-4 duration-500 rounded-lg cursor-pointer"
on:click={() => {
if ($bankBalance <= 0) {
Notify(
$Locales.no_money_on_account,
$Locales.error,
"credit-card"
);
} else {
Notify(
$Locales.withdraw_all_success,
$Locales.success,
"credit-card"
);
setTimeout(() => {
heav();
}, 200);
}
}}
>
{$Locales.withdraw}
</button>
</div>
<div
class="flex flex-col items-center bg-gray-700 rounded-xl p-4 w-[280px]"
>
<i class="fa-duotone fa-piggy-bank text-4xl mb-2 text-blue-400"></i>
<div class="text-2xl font-bold mb-2 text-blue-200">
{$Locales.deposit_cash}
</div>
<div class="text-sm mb-2 text-gray-400">
{$Locales.deposit_all_cash}
</div>
<button
class="bg-blue-600/10 border border-blue-500 hover:bg-blue-800/50 text-white font-bold py-2 px-4 mt-4 duration-500 rounded-lg cursor-pointer"
on:click={() => {
if ($currentCash <= 0) {
Notify($Locales.no_cash_on_you, $Locales.error, "coins");
} else {
Notify($Locales.deposit_all_success, $Locales.success, "coins");
setTimeout(() => {
deposit();
}, 200);
}
}}
>
{$Locales.deposit}
</button>
</div>
</div>
</div>
<!-- Lower Section -->
<div class="flex space-x-4 mt-4">
<!-- Weekly Summary -->
<div class="bg-gray-700 rounded-xl p-6 w-[380px] h-[400px] flex-none">
<div class="flex items-center mb-4">
<i class="fa-duotone fa-calendar-week text-2xl text-blue-400 mr-2"
></i>
<span class="text-blue-200 font-bold text-xl"
>{$Locales.weekly_summary}</span
>
</div>
<div
class="space-y-4 border border-dashed border-blue-400 rounded-lg p-4"
>
<div class="flex justify-between border-b border-gray-600 pb-2">
<span>{$Locales.income}</span>
<span class="text-blue-400">
{#if $weeklyData.totalReceived !== undefined}
{$weeklyData.totalReceived.toLocaleString($Currency.lang, {
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
})}
{:else}
0
{/if}
</span>
</div>
<div class="flex justify-between border-b border-gray-600 pb-2">
<span>{$Locales.expenses}</span>
<span class="text-red-400">
{#if $weeklyData.totalUsed !== undefined}
{$weeklyData.totalUsed.toLocaleString($Currency.lang, {
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
})}
{:else}
0
{/if}
</span>
</div>
<div class="mt-6">
<div class="flex items-center mb-2">
<i class="fa-duotone fa-chart-bar text-xl text-blue-400 mr-2"></i>
<span>{$Locales.report}</span>
</div>
<div>
<canvas bind:this={chartCanvas}></canvas>
</div>
</div>
</div>
</div>
<!-- Latest Transactions -->
<div class="bg-gray-700 rounded-xl p-6 w-[380px] h-[400px] flex-none">
<div class="flex justify-between items-center mb-4">
<div class="flex items-center">
<i class="fa-duotone fa-file-invoice text-2xl text-blue-400 mr-2"
></i>
<span class="text-blue-200 font-bold text-xl"
>{$Locales.latest_transactions}</span
>
</div>
<div class="bg-gray-600 rounded-full px-2 py-1">
<span class="text-white text-sm"
>{bankData.transactions.length}</span
>
</div>
</div>
<div
class="space-y-3 border border-dashed border-blue-400 rounded-lg p-4 h-[310px]"
>
{#if bankData.transactions.length > 0}
{#each bankData.transactions.slice(0, 5) as transaction}
<div class="space-y-2">
<div class="flex justify-between">
<span class="truncate">{transaction.description}</span>
<p
class={`text-md font-bold ${transaction.isIncome ? "text-green-500" : "text-red-500"}`}
>
{transaction.isIncome ? "+" : "-"}
{transaction.amount.toLocaleString($Currency.lang, {
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
})}
</p>
</div>
<div class="border-b border-gray-600"></div>
</div>
{/each}
<div class="mt-6 flex justify-center">
<button
class="bg-blue-600/10 border border-blue-500 hover:bg-blue-800/50 text-white font-bold py-2 px-4 mt-4 duration-500 rounded-lg cursor-pointer"
on:click={() => {
showOverview.set(false);
showBills.set(false);
showHistory.set(true);
showHeav.set(false);
}}
>
{$Locales.see_all}
</button>
</div>
{:else}
<div class="p-4 text-center text-blue-200">
<i class="fa-duotone fa-check-circle text-2xl mb-2"></i>
<p>{$Locales.no_transactions}</p>
</div>
{/if}
</div>
</div>
<div class="bg-gray-700 rounded-xl p-4 w-[380px] h-[400px] flex-none">
<div class="flex justify-between items-center mb-2">
<div class="flex items-center">
<i class="fa-duotone fa-file-exclamation text-xl text-blue-400 mr-2"
></i>
<span class="text-blue-200 font-bold text-lg"
>{$Locales.unpaid_bills}</span
>
</div>
<div class="bg-gray-600 rounded-full px-2 py-1">
<span class="text-white text-sm">{$transactions.length}</span>
</div>
</div>
<div
class="space-y-0 border border-dashed border-blue-400 p-1 rounded-lg overflow-auto mt-6 h-[310px]"
>
{#if $transactions.length > 0}
{#each $transactions.slice(0, 2) as transaction (transaction.id)}
{#if !transaction.isPaid}
<div class="p-2 rounded-lg flex justify-between items-center">
<div class="flex flex-col">
<div class="flex items-center">
<span class="font-semibold text-[#f1f5f9]"
>{transaction.description} #{transaction.id}</span
>
</div>
<div class="flex items-center mt-1">
<span class="text-sm text-gray-400"
>{transaction.type}</span
>
</div>
<div class="flex items-center mt-1">
<span class="text-xs text-gray-500"
>{transaction.timeAgo}</span
>
</div>
</div>
<div class="text-right flex flex-col items-end">
<span
class={`text-lg ${transaction.isIncome ? "text-green-400" : "text-red-400"}`}
>
{transaction.isIncome ? "+" : "-"}
{transaction.amount.toLocaleString($Currency.lang, {
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
})}
</span>
<div class="flex items-center mt-0">
<div class="text-sm text-gray-400">
{transaction.date}
</div>
</div>
</div>
</div>
<div class="border-b border-gray-600"></div>
{/if}
{/each}
<div class="mb-4 space-y-2 text-center">
<div class="mt-[70px] flex justify-center">
<button
class="bg-blue-600/10 border border-blue-500 hover:bg-blue-800/50 text-white font-bold py-2 px-4 duration-500 rounded-lg cursor-pointer"
on:click={() => {
showOverview.set(false);
showBills.set(true);
showHistory.set(false);
showHeav.set(false);
}}
>
{$Locales.see_all}
</button>
</div>
</div>
{:else}
<div class="p-4 text-center text-blue-200">
<i class="fa-duotone fa-check-circle text-2xl mb-2"></i>
<p>{$Locales.no_unpaid_bills}</p>
</div>
{/if}
</div>
</div>
</div>
</div>
<!-- MODALS -->
{#if $showTransferModal}
<!-- svelte-ignore a11y-label-has-associated-control -->
<div
class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50"
>
<div
class="p-8 bg-gray-800 rounded-lg shadow-lg w-96"
in:scale={{ duration: 250, easing: quintOut }}
out:scale={{ duration: 250, easing: quintOut }}
>
<div class="flex items-center mb-4">
<i
class="fa-duotone fa-arrow-right-arrow-left text-3xl text-blue-400 mr-3"
></i>
<h2 class="text-2xl text-blue-200 font-bold">
{$Locales.transfer_money}
</h2>
</div>
<!-- Payment Method Selection -->
<div class="mb-6">
<p class="capitalize font-semibold text-blue-200 mb-2">
{$Locales.payment_method}
</p>
<div class="flex flex-col space-y-4">
{#if phone}
<label
class="flex items-center cursor-pointer bg-gray-700/50 rounded-lg p-3 border border-gray-600/20 hover:border-blue-400 transition duration-300"
>
<input
type="radio"
name="payment"
value="phone"
bind:group={$transferData.contactType}
class="hidden peer"
/>
<i class="fa-duotone fa-phone text-lg text-blue-400 mr-3"></i>
<span class="text-white font-bold">{$Locales.phone_number}</span
>
<div class="ml-auto hidden peer-checked:block">
<i class="fa-duotone fa-check-circle text-blue-400"></i>
</div>
</label>
{/if}
<label
class="flex items-center cursor-pointer bg-gray-700/50 rounded-lg p-3 border border-gray-600/20 hover:border-blue-400 transition duration-300"
>
<input
type="radio"
name="payment"
value="id"
bind:group={$transferData.contactType}
class="hidden peer"
/>
<i class="fa-duotone fa-id-badge text-lg text-blue-400 mr-3"></i>
<span class="text-white font-bold">{$Locales.id}</span>
<div class="ml-auto hidden peer-checked:block">
<i class="fa-duotone fa-check-circle text-blue-400"></i>
</div>
</label>
</div>
</div>
<!-- ID or Phone Number Input -->
{#if $transferData.contactType === "phone" || $transferData.contactType === "id"}
<div class="mb-6">
<label class="block text-gray-400 mb-2">
<i class="fa-duotone fa-id-card text-blue-400 mr-2"></i>
{#if phone}
{$Locales.id_or_phone_number}
{:else}
{$Locales.id}
{/if}
</label>
<div class="relative">
<input
type="number"
min="1"
class="w-full p-3 bg-gray-700/50 text-white pr-10 border border-blue-200/10 rounded-lg focus:outline-none
focus:border-blue-400/50 transition-colors duration-500"
bind:value={$transferData.idOrPhone}
/>
<i
class="fa-duotone fa-user absolute top-1/2 right-3 transform -translate-y-1/2 text-gray-400"
></i>
</div>
</div>
{/if}
<!-- Amount Input -->
<div class="mb-6">
<label class="block text-gray-400 mb-2">
<i class="fa-duotone fa-money-bill-wave text-blue-400 mr-2"
></i>{$Locales.amount}
</label>
<div class="relative">
<input
type="number"
min="1"
class="w-full p-3 bg-gray-700/50 text-white pr-10 border border-blue-200/10 rounded-lg focus:outline-none
focus:border-blue-400/50 transition-colors duration-500"
bind:value={$transferData.amount}
/>
<i
class="fa-duotone fa-dollar-sign absolute top-1/2 right-3 transform -translate-y-1/2 text-gray-400"
></i>
</div>
</div>
<!-- Action Buttons -->
<div class="flex justify-between items-center mt-6">
<button
class="flex items-center bg-red-600 hover:bg-red-700 text-white py-2 px-4 rounded focus:outline-none"
on:click={closeModal}
>
<i class="fa-duotone fa-times-circle mr-2"></i>{$Locales.cancel}
</button>
<button
class="flex items-center bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded focus:outline-none"
on:click={async () => {
confirmTransfer(
$transferData.idOrPhone,
$transferData.amount,
$transferData.contactType
);
}}
>
<i class="fa-duotone fa-check-circle mr-2"></i>{$Locales.confirm}
</button>
</div>
</div>
</div>
{/if}
{#if $showSureModalBills}
<div
class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50"
>
<div
class="bg-gray-700 p-8 rounded-lg shadow-lg w-96"
in:scale={{ duration: 250, easing: quintOut }}
out:scale={{ duration: 250, easing: quintOut }}
>
<div class="flex items-center mb-4">
<i class="fa-duotone fa-question-circle text-3xl text-blue-400 mr-3"
></i>
<h2 class="text-2xl text-blue-200 font-bold">
{$Locales.are_you_sure}
</h2>
</div>
<p class="text-gray-300 mb-6">
{$Locales.confirm_pay_all_bills}
</p>
<div class="flex justify-between items-center">
<button
class="flex items-center bg-red-600 hover:bg-red-700 text-white py-2 px-4 rounded focus:outline-none"
on:click={() => {
showSureModalBills.set(false);
}}
>
<i class="fa-duotone fa-times-circle mr-2"></i>{$Locales.cancel}
</button>
<button
class="flex items-center bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded focus:outline-none"
on:click={async () => {
if ($transactions.length > 0) {
await payAllBills();
showSureModalBills.set(false);
} else {
showSureModalBills.set(false);
Notify(
$Locales.pay_all_bills_error,
$Locales.error,
"circle-exclamation"
);
}
}}
>
<i class="fa-duotone fa-check-circle mr-2"></i>{$Locales.confirm}
</button>
</div>
</div>
</div>
{/if}
</div>

View file

@ -0,0 +1,228 @@
<script lang="ts">
import { writable } from "svelte/store";
import { slide, scale } from "svelte/transition";
import { quintOut } from "svelte/easing";
import { onMount } from "svelte";
import {
Notify,
currentCash,
bankBalance,
Locales,
Currency,
} from "../store/data";
import { fetchNui } from "../utils/fetchNui";
import {
Chart,
LineController,
LineElement,
PointElement,
LinearScale,
Title,
CategoryScale,
Tooltip,
} from "chart.js";
let totalTransactions = writable(0);
let totalAmount = writable(0);
let dataSheets = {
Transactions: [],
Dates: [],
};
let chart: Chart<"line", never[], string>;
async function fetchTransactionStats() {
try {
const stats = await fetchNui("ps-banking:client:getTransactionStats", {});
totalTransactions.set(stats.totalCount);
totalAmount.set(stats.totalAmount);
const transactionData = stats.transactionData.map(
(stat: { amount: any }) => stat.amount
);
const transactionDates = stats.transactionData.map(
(stat: { date: any }) => new Date(stat.date).toLocaleDateString()
);
dataSheets.Transactions = transactionData;
dataSheets.Dates = transactionDates;
if (chart) {
chart.data.labels = transactionDates;
chart.data.datasets[0].data = transactionData;
chart.update();
}
} catch (error) {
console.error("Error fetching transaction stats:", error);
}
}
Chart.register(
LineController,
LineElement,
PointElement,
LinearScale,
Title,
CategoryScale,
Tooltip
);
onMount(() => {
fetchTransactionStats();
// @ts-expect-error
const ctx = document.getElementById("transactionChart").getContext("2d");
chart = new Chart(ctx, {
type: "line",
data: {
labels: dataSheets.Dates,
datasets: [
{
label: "Transactions",
data: dataSheets.Transactions,
borderColor: "rgba(59, 130, 246, 1)",
backgroundColor: "rgba(59, 130, 246, 0.2)",
fill: true,
tension: 0.5,
pointStyle: "rectRounded",
pointRadius: 5,
pointHoverRadius: 7,
borderWidth: 2,
},
],
},
options: {
responsive: true,
plugins: {
legend: {
display: true,
position: "top",
labels: {
usePointStyle: true,
color: "white",
},
},
tooltip: {
backgroundColor: "rgba(0,0,0,0.7)",
titleColor: "white",
bodyColor: "white",
cornerRadius: 4,
displayColors: false,
},
},
scales: {
x: {
display: true,
title: {
display: true,
text: $Locales.date,
color: "white",
},
ticks: {
color: "white",
},
grid: {
color: "rgba(255,255,255,0.1)",
},
},
y: {
display: true,
title: {
display: true,
text: $Locales.amount,
color: "white",
},
ticks: {
color: "white",
},
grid: {
color: "rgba(255,255,255,0.1)",
},
suggestedMin: 0,
suggestedMax: dataSheets.Transactions.reduce(
(acc, val) => acc + val,
0
),
},
},
},
});
});
</script>
<!-- svelte-ignore a11y-label-has-associated-control -->
<div class="absolute w-full h-full bg-gray-800 text-white">
<div
class="absolute w-[90%] h-full p-6 overflow-auto left-[130px]"
in:slide={{ duration: 1000, easing: quintOut }}
>
<div class="bg-gray-700/20 rounded-lg shadow-md p-6">
<h2 class="text-3xl font-bold mb-6">{$Locales.statistics_reports}</h2>
<div class="flex justify-between items-center mb-6">
<div class="flex items-center">
<i class="fa-duotone fa-chart-line text-3xl text-blue-200 mr-3"></i>
<h3 class="text-2xl font-semibold text-blue-200">
{$Locales.overview}
</h3>
</div>
<div class="bg-gray-500/40 rounded-xl px-3 py-1 flex items-center">
<i class="fa-duotone fa-wallet text-gray-400 mr-2"></i>
<span class="text-lg font-semibold text-white">
{$Locales.total_balance}: {#if $bankBalance !== undefined}
{$bankBalance.toLocaleString($Currency.lang, {
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
})}
{/if}
</span>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<div class="bg-gray-700 p-4 rounded-lg shadow">
<div class="flex justify-between items-center mb-4">
<h4 class="text-xl font-semibold text-blue-200">
{$Locales.total_transactions}
</h4>
<i class="fa-duotone fa-exchange-alt text-yellow-400 text-2xl"></i>
</div>
<p class="text-2xl font-bold">{$totalTransactions}</p>
</div>
<div class="bg-gray-700 p-4 rounded-lg shadow">
<div class="flex justify-between items-center mb-4">
<h4 class="text-xl font-semibold text-blue-200">
{$Locales.amount}
</h4>
<i class="fa-duotone fa-coins text-green-400 text-2xl"></i>
</div>
<p class="text-2xl font-bold">
{#if $totalAmount !== undefined}
{$totalAmount.toLocaleString($Currency.lang, {
style: "currency",
currency: $Currency.currency,
minimumFractionDigits: 0,
})}
{:else}
0
{/if}
</p>
</div>
</div>
<div class="bg-gray-700 p-4 rounded-lg shadow">
<div class="flex justify-between items-center mb-4">
<h4 class="text-xl font-semibold text-blue-200">
{$Locales.transactions_trend}
</h4>
<i class="fa-duotone fa-chart-line text-blue-400 text-2xl"></i>
</div>
<div
class="relative w-[1000px] left-[15%]"
in:scale={{ duration: 2500, easing: quintOut }}
>
<canvas id="transactionChart"></canvas>
</div>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,3 @@
/// <reference types="@sveltejs/kit" />
declare module '*.csv';

View file

@ -0,0 +1,8 @@
import "./app.pcss";
import App from "./App.svelte";
const app = new App({
target: document.getElementById("app")!,
});
export default app;

View file

@ -0,0 +1,46 @@
<script lang="ts">
import { useNuiEvent } from "../utils/useNuiEvent";
import { fetchNui } from "../utils/fetchNui";
import { onMount } from "svelte";
import { visibility } from "../store/stores";
import ATM from "../components/ATM.svelte";
import { showATM } from "../store/data";
let isVisible: boolean;
useNuiEvent<boolean>("openATM", () => {
showATM.set(true);
});
visibility.subscribe((visible) => {
isVisible = visible;
});
useNuiEvent<boolean>("openBank", () => {
visibility.set(true);
});
useNuiEvent<boolean>("hideGarageMenu", () => {
visibility.set(false);
});
onMount(() => {
const keyHandler = (e: KeyboardEvent) => {
if (isVisible && ["Escape"].includes(e.code)) {
fetchNui("ps-banking:client:hideUI");
visibility.set(false);
}
};
window.addEventListener("keydown", keyHandler);
return () => window.removeEventListener("keydown", keyHandler);
});
</script>
<main>
{#if isVisible}
<slot />
{/if}
{#if $showATM}
<ATM />
{/if}
</main>

View file

@ -0,0 +1,250 @@
import { writable, type Writable } from "svelte/store";
export interface Notification {
id: number;
message: string;
title: string;
icon: string;
}
export const notifications = writable<Notification[]>([]);
let notificationId = 0;
export function Notify(message: string, title: string, icon: string) {
notificationId += 1;
const newNotification: Notification = {
id: notificationId,
message,
title,
icon,
};
notifications.update((n) => [...n, newNotification]);
setTimeout(() => {
notifications.update((n) =>
n.filter((notification) => notification.id !== newNotification.id)
);
}, 2000);
}
export const showOverview = writable(true);
export const showBills = writable(false);
export const showHistory = writable(false);
export const showHeav = writable(false);
export const showIndseat = writable(false);
export const showStats = writable(false);
export const showAccounts = writable(false);
export const showATM = writable(false);
export const currentCash = writable(500);
export const bankBalance = writable(10000);
export const Currency = writable({
lang: "en", // da-DK
currency: "USD", // DKK
});
export const Locales = writable({
atm: "ATM",
cash: "Cash",
bank_balance: "Bank Balance",
deposit_amount: "Deposit Amount",
withdraw_amount: "Withdraw Amount",
submit: "Submit",
close: "Close",
overview: "Overview",
bills: "Bills",
history: "History",
withdraw: "Withdraw",
deposit: "Deposit",
stats: "Stats",
transactions: "Transactions",
total: "Total",
search_transactions: "Search transactions...",
description: "Description",
type: "Type",
time_ago: "Time Ago",
amount: "Amount",
date: "Date",
pay_invoice: "Pay Invoice",
payment_completed: "Payment Completed",
from: "From",
delete_all_transactions: "Delete All Transactions",
are_you_sure: "Are you sure?",
delete_confirmation:
"Are you sure you want to delete all your transactions? (Only do this if the menu lags!)",
cancel: "Cancel",
confirm: "Confirm",
history_empty: "Your history is empty",
all_history_deleted: "You have deleted all your history",
error: "Error",
success: "Success",
new_cash: "New Cash",
withdraw_success: "Withdrawal Successful",
withdraw_error: "Your bank account does not have enough funds",
withdraw_button: "WITHDRAW",
new_bank: "New Bank Balance",
current_cash: "Current Cash",
deposit_success: "Deposit Successful",
deposit_error: "You do not have enough cash",
deposit_button: "DEPOSIT",
total_balance: "Total Balance",
quick_actions: "Quick Actions",
transfer_money: "Transfer Money",
easy_transfer: "Easily transfer money to people",
transfer: "Transfer",
pay_bills: "Pay Bills",
pay_pending_bills: "Quickly pay your pending bills",
pay: "Pay",
withdraw_all_money: "Withdraw All Money",
withdraw_all_from_account: "Withdraw all your money from your account",
deposit_cash: "Deposit Cash",
deposit_all_cash: "Deposit all your cash into your account",
weekly_summary: "Weekly Summary",
income: "Income",
expenses: "Expenses",
report: "Report",
latest_transactions: "Latest Transactions",
see_all: "SEE ALL",
unpaid_bills: "Unpaid Invoices",
no_unpaid_bills: "No unpaid invoices",
confirm_pay_all_bills: "Are you sure you want to pay all your bills?",
pay_all_bills: "Pay All Bills",
pay_all_bills_success: "You have paid all your bills!",
pay_all_bills_error: "You have no bills",
payment_method: "Payment Method",
phone_number: "Phone Number",
id: "ID",
id_or_phone_number: "ID or Phone Number",
no_cash_on_you: "You have no cash on you",
deposit_all_success: "All your cash has been deposited",
no_money_on_account: "Your account is empty",
withdraw_all_success: "You have withdrawn all your money from the account",
invoices: "Invoices",
statistics_reports: "Statistics and Reports",
balance_trend: "Balance Trend",
balance: "Balance",
used: "Used",
month: "Month",
balance_dkk: "Balance",
withdrawn: "You have withdrawn",
deposited: "You have deposited",
no_transactions: "No recent transactions",
transactions_trend: "Transactions Trend",
total_transactions: "Total Transactions",
accounts: "Accounts",
account_number_copied: "Account number copied to clipboard",
new_user_to_account: "New user to account",
server_id: "Server ID",
add_user: "Add User",
new_account_name: "New Account Name",
new_name: "New Name",
rename: "Rename",
create_new_account: "Create New Account",
account_holder: "Account Holder",
initial_balance: "Initial Balance",
create: "Create",
delete_account: "Delete Account",
are_you_sure_you_want_to_delete_this_account:
"Are you sure you want to delete this account?",
delete: "Delete",
remove_user_from_account: "Remove User from Account",
select_user: "Select User",
remove: "Remove",
withdraw_from_account: "Withdraw from Account",
deposit_to_account: "Deposit to Account",
removed_successfully: "removed Successfully",
select_account_and_user: "Please select an account and a user",
account_deleted_successfully: "Account deleted successfully",
new_account_created_successfully: "New account created successfully",
withdrew: "Withdrew",
successfully: "Successfully",
select_valid_account_and_amount: "Please select a valid account and amount",
openBank: "Access Bank",
openATM: "Access ATM",
account_deletion_failed: "Account deletion failed",
withdrawal_failed: "Withdrawal failed",
deposit_failed: "Deposit failed",
user_added_successfully: "added successfully",
user_addition_failed: "Failed to add user",
new_account_creation_failed: "Failed to create new account",
account_renamed_successfully: "Account renamed successfully",
account_rename_failed: "Account rename failed",
rename_account: "Change name",
});
export const Transactions: Writable<Array<any>> = writable([
// {
// id: 8,
// description: "Åbnede en ny konto",
// type: "Fra konto",
// amount: 1000,
// date: "2022/08/13",
// timeAgo: "For 18 timer siden",
// isIncome: false,
// },
// {
// id: 7,
// description: "Indsatte 500 DKK på konto",
// type: "Til konto",
// amount: 500,
// date: "2022/08/13",
// timeAgo: "For 18 timer siden",
// isIncome: true,
// },
// {
// id: 6,
// description: "Indsatte 500 DKK på konto",
// type: "Til konto",
// amount: 500,
// date: "2022/08/13",
// timeAgo: "For 18 timer siden",
// isIncome: true,
// },
// {
// id: 5,
// description: "Hævede 500 DKK fra en hæveautomat",
// type: "Fra konto",
// amount: -500,
// date: "2022/08/13",
// timeAgo: "For 18 timer siden",
// isIncome: false,
// },
// {
// id: 4,
// description: "Indsatte 500 DKK på konto",
// type: "Til konto",
// amount: 500,
// date: "2022/08/13",
// timeAgo: "For 18 timer siden",
// isIncome: true,
// },
]);
export const Bills: Writable<Array<any>> = writable([
// {
// id: 1,
// description: "Mekaniker Regning",
// type: "Auto Exotic",
// amount: 1000,
// date: "2022/08/13",
// timeAgo: "For 18 timer siden",
// isPaid: false,
// },
// {
// id: 2,
// description: "Mekaniker Regning",
// type: "Auto Exotic",
// amount: 1000,
// date: "2022/08/13",
// timeAgo: "For 18 timer siden",
// isPaid: false,
// },
// {
// id: 3,
// description: "Mekaniker Regning",
// type: "Auto Exotic",
// amount: 1000,
// date: "2022/08/13",
// timeAgo: "For 18 timer siden",
// isPaid: false,
// },
]);

View file

@ -0,0 +1,3 @@
import { writable } from "svelte/store";
export const visibility = writable(false);

View file

@ -0,0 +1,30 @@
import {isEnvBrowser} from "./misc";
interface DebugEvent<T = any> {
action: string;
data: T;
}
/**
* Emulates dispatching an event using SendNuiMessage in the lua scripts.
* This is used when developing in browser
*
* @param events - The event you want to cover
* @param timer - How long until it should trigger (ms)
*/
export const debugData = <P>(events: DebugEvent<P>[], timer = 1000): void => {
if (isEnvBrowser()) {
for (const event of events) {
setTimeout(() => {
window.dispatchEvent(
new MessageEvent("message", {
data: {
action: event.action,
data: event.data,
},
})
);
}, timer);
}
}
};

View file

@ -0,0 +1,27 @@
/**
* @param eventName - The endpoint eventname to target
* @param data - Data you wish to send in the NUI Callback
*
* @return returnData - A promise for the data sent back by the NuiCallbacks CB argument
*/
export async function fetchNui<T = any>(
eventName: string,
data: unknown = {}
): Promise<T> {
const options = {
method: "post",
headers: {
"Content-Type": "application/json; charset=UTF-8",
},
body: JSON.stringify(data),
};
const resourceName = (window as any).GetParentResourceName
? (window as any).GetParentResourceName()
: "nui-frame-app";
const resp = await fetch(`https://${resourceName}/${eventName}`, options);
return await resp.json();
}

View file

@ -0,0 +1 @@
export const isEnvBrowser = (): boolean => !(window as any).invokeNative;

View file

@ -0,0 +1,51 @@
import { onDestroy } from "svelte";
interface NuiMessage<T = unknown> {
action: string;
data: T;
}
/**
* A function that manage events listeners for receiving data from the client scripts
* @param action The specific `action` that should be listened for.
* @param handler The callback function that will handle data relayed by this function
*
* @example
* useNuiEvent<{visibility: true, wasVisible: 'something'}>('setVisible', (data) => {
* // whatever logic you want
* })
*
**/
type NuiEventHandler<T = any> = (data: T) => void;
const eventListeners = new Map<string, NuiEventHandler[]>();
const eventListener = (event: MessageEvent<NuiMessage>) => {
const { action, data } = event.data;
const handlers = eventListeners.get(action);
if (handlers) {
handlers.forEach((handler) => handler(data));
}
};
window.addEventListener("message", eventListener);
export function useNuiEvent<T = unknown>(
action: string,
handler: NuiEventHandler<T>
) {
const handlers = eventListeners.get(action) || [];
handlers.push(handler);
eventListeners.set(action, handlers);
onDestroy(() => {
const handlers = eventListeners.get(action) || [];
eventListeners.set(
action,
handlers.filter((h) => h !== handler)
);
});
}

View file

@ -0,0 +1,2 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />

View file

@ -0,0 +1,8 @@
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
import sveltePreprocess from "svelte-preprocess";
export default {
// Consult https://github.com/sveltejs/svelte-preprocess
// for more information about preprocessors
preprocess: [sveltePreprocess(), vitePreprocess({})],
};

View file

@ -0,0 +1,12 @@
/** @type {import('tailwindcss').Config}*/
const config = {
content: ["./src/**/*.{html,js,svelte,ts}"],
theme: {
extend: {},
},
plugins: [],
};
module.exports = config;

View file

@ -0,0 +1,25 @@
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"strict": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable checkJs if you'd like to use dynamic types in JS.
* Note that setting allowJs false does not prevent the use
* of JS in `.svelte` files.
*/
"allowJs": true,
"checkJs": true,
"isolatedModules": true
},
"include": ["src/**/*"],
"exclude": ["node_modules/*", "public/*"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View file

@ -0,0 +1,8 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node"
},
"include": ["vite.config.ts"]
}

View file

@ -0,0 +1,18 @@
import { defineConfig } from 'vite';
import { svelte } from '@sveltejs/vite-plugin-svelte';
export default defineConfig({
plugins: [svelte()],
base: './',
build: {
minify: true,
outDir: '../html',
rollupOptions: {
output: {
entryFileNames: '[name].js',
chunkFileNames: '[name].js',
assetFileNames: '[name].[ext]',
},
},
},
});