wallet screen
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
module Admin
|
||||
class BalancesController < Admin::ApplicationController
|
||||
def valid_action?(name, resource = resource_class)
|
||||
["destroy"].exclude?(name.to_s) && super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
class ApplicationController < ActionController::Base
|
||||
include Pundit
|
||||
|
||||
before_action :configure_devise_permitted_parameters, if: :devise_controller?
|
||||
|
||||
protected
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
class GraphqlController < ApplicationController
|
||||
# protect_from_forgery with: :null_session
|
||||
|
||||
def execute
|
||||
variables = prepare_variables(params[:variables])
|
||||
query = params[:query]
|
||||
|
||||
13
app/graphql/types/balance_type.rb
Normal file
13
app/graphql/types/balance_type.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
module Types
|
||||
class BalanceType < Types::BaseObject
|
||||
implements GraphQL::Types::Relay::Node
|
||||
global_id_field :id
|
||||
|
||||
graphql_name "Balance"
|
||||
|
||||
field :id, ID, null: false
|
||||
field :currency, CurrencyType, null: false
|
||||
field :amount, String, null: false
|
||||
end
|
||||
end
|
||||
12
app/graphql/types/currency_type.rb
Normal file
12
app/graphql/types/currency_type.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
module Types
|
||||
class CurrencyType < Types::BaseObject
|
||||
implements GraphQL::Types::Relay::Node
|
||||
global_id_field :id
|
||||
|
||||
graphql_name "Currency"
|
||||
|
||||
field :id, ID, null: false
|
||||
field :name, String, null: false
|
||||
end
|
||||
end
|
||||
@@ -8,5 +8,10 @@ module Types
|
||||
def current_user
|
||||
context[:current_user]
|
||||
end
|
||||
|
||||
field :balances, BalanceType.connection_type, null: false
|
||||
def balances
|
||||
Pundit.policy_scope(current_user, Balance)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
module Types
|
||||
class UserType < Types::BaseObject
|
||||
# implements GraphQL::Types::Relay::Node
|
||||
|
||||
global_id_field :id
|
||||
|
||||
graphql_name "User"
|
||||
|
||||
field :id, ID, null: false
|
||||
field :first_name, String, null: false
|
||||
field :last_name, String, null: false
|
||||
|
||||
@@ -4,15 +4,22 @@ class XStakeSchema < GraphQL::Schema
|
||||
query(Types::QueryType)
|
||||
|
||||
def self.resolve_type(abstract_type, obj, ctx)
|
||||
raise(GraphQL::RequiredImplementationMissingError)
|
||||
case obj
|
||||
when Currency
|
||||
Types::CurrencyType
|
||||
when Balance
|
||||
Types::BalanceType
|
||||
else
|
||||
raise(GraphQL::RequiredImplementationMissingError, "Unexpected object: #{obj}")
|
||||
end
|
||||
end
|
||||
|
||||
def self.id_from_object(object, type_definition, query_ctx)
|
||||
def self.id_from_object(object, type_definition, ctx)
|
||||
GraphQL::Schema::UniqueWithinType.encode(type_definition.name, object.id)
|
||||
end
|
||||
|
||||
def self.object_from_id(id, query_ctx)
|
||||
def self.object_from_id(id, ctx)
|
||||
type_name, item_id = GraphQL::Schema::UniqueWithinType.decode(id)
|
||||
type_name.constantize.find(item_id)
|
||||
Pundit.policy_scope(ctx[:current_user], type_name.constantize).find(item_id)
|
||||
end
|
||||
end
|
||||
|
||||
122
app/javascript/__generated__/schema.graphql
generated
122
app/javascript/__generated__/schema.graphql
generated
@@ -1,5 +1,127 @@
|
||||
type Balance implements Node {
|
||||
amount: Float!
|
||||
currency: Currency!
|
||||
id: ID!
|
||||
}
|
||||
|
||||
"""
|
||||
The connection type for Balance.
|
||||
"""
|
||||
type BalanceConnection {
|
||||
"""
|
||||
A list of edges.
|
||||
"""
|
||||
edges: [BalanceEdge]
|
||||
|
||||
"""
|
||||
A list of nodes.
|
||||
"""
|
||||
nodes: [Balance]
|
||||
|
||||
"""
|
||||
Information to aid in pagination.
|
||||
"""
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
"""
|
||||
An edge in a connection.
|
||||
"""
|
||||
type BalanceEdge {
|
||||
"""
|
||||
A cursor for use in pagination.
|
||||
"""
|
||||
cursor: String!
|
||||
|
||||
"""
|
||||
The item at the end of the edge.
|
||||
"""
|
||||
node: Balance
|
||||
}
|
||||
|
||||
type Currency implements Node {
|
||||
id: ID!
|
||||
name: String!
|
||||
}
|
||||
|
||||
"""
|
||||
An object with an ID.
|
||||
"""
|
||||
interface Node {
|
||||
"""
|
||||
ID of the object.
|
||||
"""
|
||||
id: ID!
|
||||
}
|
||||
|
||||
"""
|
||||
Information about pagination in a connection.
|
||||
"""
|
||||
type PageInfo {
|
||||
"""
|
||||
When paginating forwards, the cursor to continue.
|
||||
"""
|
||||
endCursor: String
|
||||
|
||||
"""
|
||||
When paginating forwards, are there more items?
|
||||
"""
|
||||
hasNextPage: Boolean!
|
||||
|
||||
"""
|
||||
When paginating backwards, are there more items?
|
||||
"""
|
||||
hasPreviousPage: Boolean!
|
||||
|
||||
"""
|
||||
When paginating backwards, the cursor to continue.
|
||||
"""
|
||||
startCursor: String
|
||||
}
|
||||
|
||||
type Query {
|
||||
balances(
|
||||
"""
|
||||
Returns the elements in the list that come after the specified cursor.
|
||||
"""
|
||||
after: String
|
||||
|
||||
"""
|
||||
Returns the elements in the list that come before the specified cursor.
|
||||
"""
|
||||
before: String
|
||||
|
||||
"""
|
||||
Returns the first _n_ elements from the list.
|
||||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
last: Int
|
||||
): BalanceConnection!
|
||||
currentUser: User
|
||||
|
||||
"""
|
||||
Fetches an object given its ID.
|
||||
"""
|
||||
node(
|
||||
"""
|
||||
ID of the object.
|
||||
"""
|
||||
id: ID!
|
||||
): Node
|
||||
|
||||
"""
|
||||
Fetches a list of objects given a list of IDs.
|
||||
"""
|
||||
nodes(
|
||||
"""
|
||||
IDs of the objects.
|
||||
"""
|
||||
ids: [ID!]!
|
||||
): [Node]!
|
||||
}
|
||||
|
||||
type User {
|
||||
|
||||
@@ -2,14 +2,17 @@ import type { FC } from "react";
|
||||
import React from "react";
|
||||
import { Switch, Route } from "react-router-dom";
|
||||
|
||||
import { Home } from "./pages";
|
||||
import { Home, Wallet } from "./pages";
|
||||
|
||||
export const Routes: FC = () => {
|
||||
return (
|
||||
<Switch>
|
||||
<Route path="/">
|
||||
<Route exact path="/">
|
||||
<Home />
|
||||
</Route>
|
||||
<Route exact path="/wallet">
|
||||
<Wallet />
|
||||
</Route>
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,10 +14,6 @@ const MenuItems: MenuItem[] = [
|
||||
label: "Início",
|
||||
path: "/",
|
||||
},
|
||||
{
|
||||
label: "Stake",
|
||||
path: "/stake",
|
||||
},
|
||||
{
|
||||
label: "Carteira",
|
||||
path: "/wallet",
|
||||
|
||||
94
app/javascript/src/pages/Wallet/Wallet.tsx
Normal file
94
app/javascript/src/pages/Wallet/Wallet.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import { graphql } from "babel-plugin-relay/macro";
|
||||
import type { FC } from "react";
|
||||
import React from "react";
|
||||
import { useLazyLoadQuery } from "react-relay";
|
||||
|
||||
import { tokens } from "../../constants/pancake/Tokens";
|
||||
import type { WalletQuery } from "./__generated__/WalletQuery.graphql";
|
||||
|
||||
export const Wallet: FC = () => {
|
||||
const { balances } = useLazyLoadQuery<WalletQuery>(
|
||||
graphql`
|
||||
query WalletQuery {
|
||||
balances {
|
||||
nodes {
|
||||
id
|
||||
amount
|
||||
currency {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{}
|
||||
);
|
||||
|
||||
const tokensList = Object.values(tokens);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full w-full overflow-x-hidden mt-16">
|
||||
<div className="container mx-auto px-4 sm:px-8 max-w-3xl">
|
||||
<div className="py-8">
|
||||
<div className="-mx-4 sm:-mx-8 px-4 sm:px-8 py-4 overflow-x-auto">
|
||||
<div className="inline-block min-w-full shadow rounded-lg overflow-hidden">
|
||||
<table className="min-w-full leading-normal">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-5 py-3 bg-white border-b border-gray-200 text-gray-800 text-left text-sm uppercase font-normal"
|
||||
>
|
||||
Moeda
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-5 py-3 bg-white border-b border-gray-200 text-gray-800 text-left text-sm uppercase font-normal"
|
||||
>
|
||||
Saldo
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{balances.nodes?.map((balance) => {
|
||||
const token = tokensList.find(
|
||||
({ symbol }) => symbol === balance?.currency.name
|
||||
);
|
||||
|
||||
return (
|
||||
<tr key={balance?.id}>
|
||||
<td className="px-5 py-5 border-b border-gray-200 bg-white text-sm">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0">
|
||||
<a href="/" className="block relative">
|
||||
<img
|
||||
alt="profil"
|
||||
src={`https://pancakeswap.finance/images/tokens/${token?.address["56"]}.svg`}
|
||||
className="mx-auto object-cover rounded-full h-10 w-10 "
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<p className="text-gray-900 whitespace-no-wrap">
|
||||
{balance?.currency.name}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-5 py-5 border-b border-gray-200 bg-white text-sm">
|
||||
<p className="text-gray-900 whitespace-no-wrap">
|
||||
{balance?.amount}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
165
app/javascript/src/pages/Wallet/__generated__/WalletQuery.graphql.ts
generated
Normal file
165
app/javascript/src/pages/Wallet/__generated__/WalletQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,165 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from "relay-runtime";
|
||||
export type WalletQueryVariables = {};
|
||||
export type WalletQueryResponse = {
|
||||
readonly balances: {
|
||||
readonly nodes: ReadonlyArray<{
|
||||
readonly id: string;
|
||||
readonly amount: number;
|
||||
readonly currency: {
|
||||
readonly name: string;
|
||||
};
|
||||
} | null> | null;
|
||||
};
|
||||
};
|
||||
export type WalletQuery = {
|
||||
readonly response: WalletQueryResponse;
|
||||
readonly variables: WalletQueryVariables;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
query WalletQuery {
|
||||
balances {
|
||||
nodes {
|
||||
id
|
||||
amount
|
||||
currency {
|
||||
name
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v1 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "amount",
|
||||
"storageKey": null
|
||||
},
|
||||
v2 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "WalletQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "BalanceConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "balances",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Balance",
|
||||
"kind": "LinkedField",
|
||||
"name": "nodes",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
(v0/*: any*/),
|
||||
(v1/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Currency",
|
||||
"kind": "LinkedField",
|
||||
"name": "currency",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Query",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Operation",
|
||||
"name": "WalletQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "BalanceConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "balances",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Balance",
|
||||
"kind": "LinkedField",
|
||||
"name": "nodes",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
(v0/*: any*/),
|
||||
(v1/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Currency",
|
||||
"kind": "LinkedField",
|
||||
"name": "currency",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "6b8d0c664bd2d9df4d323c19c4a823a5",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "WalletQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query WalletQuery {\n balances {\n nodes {\n id\n amount\n currency {\n name\n id\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
(node as any).hash = '428f4f1ab769f9056dd38ec641a30733';
|
||||
export default node;
|
||||
1
app/javascript/src/pages/Wallet/index.ts
Normal file
1
app/javascript/src/pages/Wallet/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./Wallet";
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./Home";
|
||||
export * from "./Wallet";
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./Home";
|
||||
10
app/policies/balance_policy.rb
Normal file
10
app/policies/balance_policy.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
class BalancePolicy < ApplicationPolicy
|
||||
class Scope < Scope
|
||||
def resolve
|
||||
return scope.none if user.nil?
|
||||
|
||||
scope.where(user_id: user.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
206
db/seeds.rb
206
db/seeds.rb
@@ -9,10 +9,208 @@ user = User.create!(
|
||||
password: "password"
|
||||
)
|
||||
|
||||
currency = Currency.create!(name: "CAKE")
|
||||
tokens = [
|
||||
"BNB",
|
||||
"CAKE",
|
||||
"CHESS",
|
||||
"TITAN",
|
||||
"ONE",
|
||||
"MASK",
|
||||
"DVI",
|
||||
"ADX",
|
||||
"BSCPAD",
|
||||
"RABBIT",
|
||||
"FORM",
|
||||
"TXL",
|
||||
"ORBS",
|
||||
"COS",
|
||||
"BUNNY",
|
||||
"ALICE",
|
||||
"FOR",
|
||||
"BUX",
|
||||
"NULS",
|
||||
"BELT",
|
||||
"RAMP",
|
||||
"BFI",
|
||||
"DEXE",
|
||||
"BEL",
|
||||
"TPT",
|
||||
"WATCH",
|
||||
"xMARK",
|
||||
"bMXX",
|
||||
"IOTX",
|
||||
"BOR",
|
||||
"bOPEN",
|
||||
"DODO",
|
||||
"SWINGBY",
|
||||
"BRY",
|
||||
"ZEE",
|
||||
"SWGb",
|
||||
"SWG",
|
||||
"SFP",
|
||||
"LINA",
|
||||
"LIT",
|
||||
"HGET",
|
||||
"BDO",
|
||||
"EGLD",
|
||||
"UST",
|
||||
"wSOTE",
|
||||
"FRONT",
|
||||
"Helmet",
|
||||
"BTCST",
|
||||
"BSCX",
|
||||
"TEN",
|
||||
"bALBT",
|
||||
"ASR",
|
||||
"ATM",
|
||||
"OG",
|
||||
"REEF",
|
||||
"DITTO",
|
||||
"JUV",
|
||||
"PSG",
|
||||
"VAI",
|
||||
"wBNB",
|
||||
"BLINK",
|
||||
"UNFI",
|
||||
"TWT",
|
||||
"HARD",
|
||||
"bROOBEE",
|
||||
"STAX",
|
||||
"NAR",
|
||||
"NYA",
|
||||
"CTK",
|
||||
"INJ",
|
||||
"SXP",
|
||||
"ALPHA",
|
||||
"XVS",
|
||||
"SUSHI",
|
||||
"COMP",
|
||||
"SYRUP",
|
||||
"BIFI",
|
||||
"DUSK",
|
||||
"BUSD",
|
||||
"ETH",
|
||||
"BETH",
|
||||
"mAMZN",
|
||||
"mGOOGL",
|
||||
"mNFLX",
|
||||
"mTSLA",
|
||||
"LTC",
|
||||
"USDC",
|
||||
"DAI",
|
||||
"ADA",
|
||||
"BAND",
|
||||
"DOT",
|
||||
"EOS",
|
||||
"LINK",
|
||||
"USDT",
|
||||
"BTCB",
|
||||
"XRP",
|
||||
"ATOM",
|
||||
"YFII",
|
||||
"XTZ",
|
||||
"BCH",
|
||||
"YFI",
|
||||
"UNI",
|
||||
"FIL",
|
||||
"BAKE",
|
||||
"BURGER",
|
||||
"bDIGG",
|
||||
"bBadger",
|
||||
"TRADE",
|
||||
"PNT",
|
||||
"MIR",
|
||||
"pBTC",
|
||||
"LTO",
|
||||
"pCWS",
|
||||
"ZIL",
|
||||
"LIEN",
|
||||
"SWTH",
|
||||
"DFT",
|
||||
"GUM",
|
||||
"DEGO",
|
||||
"NRV",
|
||||
"EASY",
|
||||
"ODDZ",
|
||||
"HOO",
|
||||
"APYS",
|
||||
"BONDLY",
|
||||
"TKO",
|
||||
"ITAM",
|
||||
"ARPA",
|
||||
"EPS",
|
||||
"JGN",
|
||||
"TLM",
|
||||
"PERL",
|
||||
"ALPA",
|
||||
"HZN",
|
||||
"SUTER",
|
||||
"CGG",
|
||||
"MIX",
|
||||
"HAKKA",
|
||||
"XED",
|
||||
"τBTC",
|
||||
"ALPACA",
|
||||
"DFD",
|
||||
"LMT",
|
||||
"BTT",
|
||||
"TRX",
|
||||
"WIN",
|
||||
"mCOIN",
|
||||
"MATH",
|
||||
"KUN",
|
||||
"QSD",
|
||||
"HYFI",
|
||||
"OIN",
|
||||
"DOGE",
|
||||
"FINE",
|
||||
"ONE",
|
||||
"PMON",
|
||||
"HOTCROSS",
|
||||
"τDOGE",
|
||||
"BTR",
|
||||
"UBXT",
|
||||
"WMASS",
|
||||
"RFOX",
|
||||
"XEND",
|
||||
"CYC",
|
||||
"CHR",
|
||||
"KALM",
|
||||
"DERI",
|
||||
"WELL",
|
||||
"WEX",
|
||||
"WAULTx",
|
||||
"pOPEN",
|
||||
"EZ",
|
||||
"VRT",
|
||||
"TUSD",
|
||||
"MTRG",
|
||||
"KTN",
|
||||
"QKC",
|
||||
"bCFX",
|
||||
"MX",
|
||||
"ATA",
|
||||
"MBOX",
|
||||
"BORING",
|
||||
"MARSH",
|
||||
"AMPL",
|
||||
"O3",
|
||||
"HAI",
|
||||
"HTB",
|
||||
"WOO",
|
||||
"$DG",
|
||||
]
|
||||
|
||||
Balance.create!(
|
||||
currencies = tokens.map do |token|
|
||||
Currency.create!(name: token)
|
||||
end
|
||||
|
||||
currencies.each do |currency|
|
||||
random_floating_number = (rand * (10000 - 0) + 0)
|
||||
|
||||
Balance.create!(
|
||||
user_id: user.id,
|
||||
currency_id: currency.id,
|
||||
amount: 153124.72088
|
||||
)
|
||||
amount: random_floating_number
|
||||
)
|
||||
end
|
||||
|
||||
6
spec/policies/balance_policy_spec.rb
Normal file
6
spec/policies/balance_policy_spec.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe(BalancePolicy, type: :policy) do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
||||
Reference in New Issue
Block a user