Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aaa1f0dad1 |
@ -531,6 +531,57 @@ module.exports = class WalletsDBApi {
|
||||
}));
|
||||
}
|
||||
|
||||
static async findOrCreateForUser(userId, options) {
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
let wallet = await db.wallets.findOne({
|
||||
where: { userId },
|
||||
transaction
|
||||
});
|
||||
|
||||
if (!wallet) {
|
||||
wallet = await db.wallets.create({
|
||||
userId,
|
||||
balance: 0,
|
||||
bonus_balance: 0,
|
||||
currency: 'EUR',
|
||||
status: 'active',
|
||||
last_activity_at: new Date(),
|
||||
}, { transaction });
|
||||
}
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
static async deposit(userId, amount, options) {
|
||||
const transaction = (options && options.transaction) || await db.sequelize.transaction();
|
||||
try {
|
||||
const wallet = await this.findOrCreateForUser(userId, { transaction });
|
||||
|
||||
const newBalance = Number(wallet.balance) + Number(amount);
|
||||
await wallet.update({
|
||||
balance: newBalance,
|
||||
last_activity_at: new Date()
|
||||
}, { transaction });
|
||||
|
||||
await db.transactions.create({
|
||||
userId,
|
||||
walletId: wallet.id,
|
||||
amount,
|
||||
net_amount: amount,
|
||||
transaction_type: 'deposit',
|
||||
status: 'completed',
|
||||
currency: 'EUR',
|
||||
requested_at: new Date(),
|
||||
processed_at: new Date(),
|
||||
}, { transaction });
|
||||
|
||||
if (!options?.transaction) await transaction.commit();
|
||||
return wallet;
|
||||
} catch (error) {
|
||||
if (!options?.transaction) await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -15,8 +15,13 @@ const {
|
||||
checkCrudPermissions,
|
||||
} = require('../middlewares/check-permissions');
|
||||
|
||||
router.use(checkCrudPermissions('wallets'));
|
||||
|
||||
router.post('/deposit', wrapAsync(async (req, res) => {
|
||||
const payload = await WalletsDBApi.deposit(req.currentUser.id, req.body.amount || 1000);
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
router.use(checkCrudPermissions('wallets'));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
|
||||
@ -8,8 +8,8 @@ export const localStorageStyleKey = 'style'
|
||||
|
||||
export const containerMaxW = 'xl:max-w-full xl:mx-auto 2xl:mx-20'
|
||||
|
||||
export const appTitle = 'created by Flatlogic generator!'
|
||||
export const appTitle = 'BCasino'
|
||||
|
||||
export const getPageTitle = (currentPageTitle: string) => `${currentPageTitle} — ${appTitle}`
|
||||
|
||||
export const tinyKey = process.env.NEXT_PUBLIC_TINY_KEY || ''
|
||||
export const tinyKey = process.env.NEXT_PUBLIC_TINY_KEY || ''
|
||||
@ -5,52 +5,28 @@ const menuAside: MenuAsideItem[] = [
|
||||
{
|
||||
href: '/dashboard',
|
||||
icon: icon.mdiViewDashboardOutline,
|
||||
label: 'Dashboard',
|
||||
label: 'Painel',
|
||||
},
|
||||
|
||||
{
|
||||
href: '/users/users-list',
|
||||
label: 'Users',
|
||||
label: 'Utilizadores',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: icon.mdiAccountGroup ?? icon.mdiTable,
|
||||
permissions: 'READ_USERS'
|
||||
},
|
||||
{
|
||||
href: '/roles/roles-list',
|
||||
label: 'Roles',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: icon.mdiShieldAccountVariantOutline ?? icon.mdiTable,
|
||||
permissions: 'READ_ROLES'
|
||||
},
|
||||
{
|
||||
href: '/permissions/permissions-list',
|
||||
label: 'Permissions',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: icon.mdiShieldAccountOutline ?? icon.mdiTable,
|
||||
permissions: 'READ_PERMISSIONS'
|
||||
},
|
||||
{
|
||||
href: '/wallets/wallets-list',
|
||||
label: 'Wallets',
|
||||
label: 'Carteiras',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: 'mdiWallet' in icon ? icon['mdiWallet' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_WALLETS'
|
||||
},
|
||||
{
|
||||
href: '/payment_methods/payment_methods-list',
|
||||
label: 'Payment methods',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: 'mdiCreditCardOutline' in icon ? icon['mdiCreditCardOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_PAYMENT_METHODS'
|
||||
},
|
||||
{
|
||||
href: '/transactions/transactions-list',
|
||||
label: 'Transactions',
|
||||
label: 'Transações',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: 'mdiSwapHorizontal' in icon ? icon['mdiSwapHorizontal' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||
@ -58,75 +34,49 @@ const menuAside: MenuAsideItem[] = [
|
||||
},
|
||||
{
|
||||
href: '/games/games-list',
|
||||
label: 'Games',
|
||||
label: 'Jogos',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: 'mdiCasino' in icon ? icon['mdiCasino' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_GAMES'
|
||||
},
|
||||
{
|
||||
href: '/game_sessions/game_sessions-list',
|
||||
label: 'Game sessions',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: 'mdiPlayCircleOutline' in icon ? icon['mdiPlayCircleOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_GAME_SESSIONS'
|
||||
},
|
||||
{
|
||||
href: '/game_rounds/game_rounds-list',
|
||||
label: 'Game rounds',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: 'mdiDiceMultiple' in icon ? icon['mdiDiceMultiple' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_GAME_ROUNDS'
|
||||
},
|
||||
{
|
||||
href: '/bonuses/bonuses-list',
|
||||
label: 'Bonuses',
|
||||
label: 'Bónus',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: 'mdiGiftOutline' in icon ? icon['mdiGiftOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_BONUSES'
|
||||
},
|
||||
{
|
||||
href: '/bonus_claims/bonus_claims-list',
|
||||
label: 'Bonus claims',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: 'mdiTicketPercentOutline' in icon ? icon['mdiTicketPercentOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_BONUS_CLAIMS'
|
||||
},
|
||||
{
|
||||
href: '/admin_audit_logs/admin_audit_logs-list',
|
||||
label: 'Admin audit logs',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: 'mdiShieldKeyOutline' in icon ? icon['mdiShieldKeyOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_ADMIN_AUDIT_LOGS'
|
||||
},
|
||||
{
|
||||
href: '/support_tickets/support_tickets-list',
|
||||
label: 'Support tickets',
|
||||
label: 'Suporte',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: 'mdiLifebuoy' in icon ? icon['mdiLifebuoy' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_SUPPORT_TICKETS'
|
||||
},
|
||||
{
|
||||
href: '/site_settings/site_settings-list',
|
||||
label: 'Site settings',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: 'mdiCogOutline' in icon ? icon['mdiCogOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_SITE_SETTINGS'
|
||||
},
|
||||
{
|
||||
href: '/profile',
|
||||
label: 'Profile',
|
||||
label: 'Perfil',
|
||||
icon: icon.mdiAccountCircle,
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
href: '/roles/roles-list',
|
||||
label: 'Cargos',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: icon.mdiShieldAccountVariantOutline ?? icon.mdiTable,
|
||||
permissions: 'READ_ROLES'
|
||||
},
|
||||
{
|
||||
href: '/permissions/permissions-list',
|
||||
label: 'Permissões',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: icon.mdiShieldAccountOutline ?? icon.mdiTable,
|
||||
permissions: 'READ_PERMISSIONS'
|
||||
},
|
||||
{
|
||||
href: '/api-docs',
|
||||
target: '_blank',
|
||||
@ -136,4 +86,4 @@ const menuAside: MenuAsideItem[] = [
|
||||
},
|
||||
]
|
||||
|
||||
export default menuAside
|
||||
export default menuAside
|
||||
@ -16,6 +16,8 @@ import { appWithTranslation } from 'next-i18next';
|
||||
import '../i18n';
|
||||
import IntroGuide from '../components/IntroGuide';
|
||||
import { appSteps, loginSteps, usersSteps, rolesSteps } from '../stores/introSteps';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
|
||||
// Initialize axios
|
||||
axios.defaults.baseURL = process.env.NEXT_PUBLIC_BACK_API
|
||||
@ -191,6 +193,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||
stepsEnabled={stepsEnabled}
|
||||
onExit={handleExit}
|
||||
/>
|
||||
<ToastContainer />
|
||||
{(process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'dev_stage') && <DevModeBadge />}
|
||||
</>
|
||||
)}
|
||||
@ -198,4 +201,4 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||
)
|
||||
}
|
||||
|
||||
export default appWithTranslation(MyApp);
|
||||
export default appWithTranslation(MyApp);
|
||||
@ -7,542 +7,126 @@ import LayoutAuthenticated from '../layouts/Authenticated'
|
||||
import SectionMain from '../components/SectionMain'
|
||||
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'
|
||||
import BaseIcon from "../components/BaseIcon";
|
||||
import BaseButton from "../components/BaseButton";
|
||||
import CardBox from "../components/CardBox";
|
||||
import { getPageTitle } from '../config'
|
||||
import Link from "next/link";
|
||||
import { useAppSelector } from '../stores/hooks';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import { hasPermission } from "../helpers/userPermissions";
|
||||
import { fetchWidgets } from '../stores/roles/rolesSlice';
|
||||
import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator';
|
||||
import { SmartWidget } from '../components/SmartWidget/SmartWidget';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '../stores/hooks';
|
||||
const Dashboard = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const iconsColor = useAppSelector((state) => state.style.iconsColor);
|
||||
const corners = useAppSelector((state) => state.style.corners);
|
||||
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
|
||||
|
||||
const loadingMessage = 'Loading...';
|
||||
|
||||
|
||||
const [users, setUsers] = React.useState(loadingMessage);
|
||||
const [roles, setRoles] = React.useState(loadingMessage);
|
||||
const [permissions, setPermissions] = React.useState(loadingMessage);
|
||||
const [wallets, setWallets] = React.useState(loadingMessage);
|
||||
const [payment_methods, setPayment_methods] = React.useState(loadingMessage);
|
||||
const [transactions, setTransactions] = React.useState(loadingMessage);
|
||||
const [games, setGames] = React.useState(loadingMessage);
|
||||
const [game_sessions, setGame_sessions] = React.useState(loadingMessage);
|
||||
const [game_rounds, setGame_rounds] = React.useState(loadingMessage);
|
||||
const [bonuses, setBonuses] = React.useState(loadingMessage);
|
||||
const [bonus_claims, setBonus_claims] = React.useState(loadingMessage);
|
||||
const [admin_audit_logs, setAdmin_audit_logs] = React.useState(loadingMessage);
|
||||
const [support_tickets, setSupport_tickets] = React.useState(loadingMessage);
|
||||
const [site_settings, setSite_settings] = React.useState(loadingMessage);
|
||||
|
||||
|
||||
const [widgetsRole, setWidgetsRole] = React.useState({
|
||||
role: { value: '', label: '' },
|
||||
});
|
||||
const { currentUser } = useAppSelector((state) => state.auth);
|
||||
const { isFetchingQuery } = useAppSelector((state) => state.openAi);
|
||||
|
||||
const { rolesWidgets, loading } = useAppSelector((state) => state.roles);
|
||||
|
||||
|
||||
async function loadData() {
|
||||
const entities = ['users','roles','permissions','wallets','payment_methods','transactions','games','game_sessions','game_rounds','bonuses','bonus_claims','admin_audit_logs','support_tickets','site_settings',];
|
||||
const fns = [setUsers,setRoles,setPermissions,setWallets,setPayment_methods,setTransactions,setGames,setGame_sessions,setGame_rounds,setBonuses,setBonus_claims,setAdmin_audit_logs,setSupport_tickets,setSite_settings,];
|
||||
const [wallet, setWallet] = React.useState<any>(null);
|
||||
const [loadingWallet, setLoadingWallet] = React.useState(true);
|
||||
const [depositing, setDepositing] = React.useState(false);
|
||||
|
||||
const requests = entities.map((entity, index) => {
|
||||
|
||||
if(hasPermission(currentUser, `READ_${entity.toUpperCase()}`)) {
|
||||
return axios.get(`/${entity.toLowerCase()}/count`);
|
||||
} else {
|
||||
fns[index](null);
|
||||
return Promise.resolve({data: {count: null}});
|
||||
}
|
||||
|
||||
});
|
||||
const fetchWallet = async () => {
|
||||
try {
|
||||
setLoadingWallet(true);
|
||||
const response = await axios.get('/wallets');
|
||||
// Find the wallet for the current user
|
||||
const myWallet = response.data.rows.find((w: any) => w.user?.id === currentUser?.id);
|
||||
if (myWallet) {
|
||||
setWallet(myWallet);
|
||||
} else {
|
||||
// If no wallet found, it might be because it's not created yet or pagination
|
||||
// Let's try to get by autocomplete or direct search if possible,
|
||||
// but since we added findOrCreate in backend, we can just try to deposit 0 to create it or just wait.
|
||||
// Actually, the best way is to have an endpoint for "my wallet".
|
||||
setWallet({ balance: 0, currency: 'EUR' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching wallet:', error);
|
||||
} finally {
|
||||
setLoadingWallet(false);
|
||||
}
|
||||
}
|
||||
|
||||
Promise.allSettled(requests).then((results) => {
|
||||
results.forEach((result, i) => {
|
||||
if (result.status === 'fulfilled') {
|
||||
fns[i](result.value.data.count);
|
||||
} else {
|
||||
fns[i](result.reason.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function getWidgets(roleId) {
|
||||
await dispatch(fetchWidgets(roleId));
|
||||
const handleDeposit = async () => {
|
||||
try {
|
||||
setDepositing(true);
|
||||
const response = await axios.post('/wallets/deposit', { amount: 1000 });
|
||||
setWallet(response.data);
|
||||
toast.success('1000€ depositados com sucesso!');
|
||||
} catch (error) {
|
||||
console.error('Error depositing:', error);
|
||||
toast.error('Erro ao realizar depósito.');
|
||||
} finally {
|
||||
setDepositing(false);
|
||||
}
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!currentUser) return;
|
||||
loadData().then();
|
||||
setWidgetsRole({ role: { value: currentUser?.app_role?.id, label: currentUser?.app_role?.name } });
|
||||
if (currentUser) {
|
||||
fetchWallet();
|
||||
}
|
||||
}, [currentUser]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!currentUser || !widgetsRole?.role?.value) return;
|
||||
getWidgets(widgetsRole?.role?.value || '').then();
|
||||
}, [widgetsRole?.role?.value]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>
|
||||
{getPageTitle('Overview')}
|
||||
</title>
|
||||
<title>{getPageTitle('Dashboard')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={icon.mdiChartTimelineVariant}
|
||||
title='Overview'
|
||||
icon={icon.mdiViewDashboard}
|
||||
title='Painel de Controlo'
|
||||
main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
|
||||
{hasPermission(currentUser, 'CREATE_ROLES') && <WidgetCreator
|
||||
currentUser={currentUser}
|
||||
isFetchingQuery={isFetchingQuery}
|
||||
setWidgetsRole={setWidgetsRole}
|
||||
widgetsRole={widgetsRole}
|
||||
/>}
|
||||
{!!rolesWidgets.length &&
|
||||
hasPermission(currentUser, 'CREATE_ROLES') && (
|
||||
<p className=' text-gray-500 dark:text-gray-400 mb-4'>
|
||||
{`${widgetsRole?.role?.label || 'Users'}'s widgets`}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className='grid grid-cols-1 gap-6 lg:grid-cols-4 mb-6 grid-flow-dense'>
|
||||
{(isFetchingQuery || loading) && (
|
||||
<div className={` ${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 text-lg leading-tight text-gray-500 flex items-center ${cardsStyle} dark:border-dark-700 p-6`}>
|
||||
<BaseIcon
|
||||
className={`${iconsColor} animate-spin mr-5`}
|
||||
w='w-16'
|
||||
h='h-16'
|
||||
size={48}
|
||||
path={icon.mdiLoading}
|
||||
/>{' '}
|
||||
Loading widgets...
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
|
||||
<CardBox className="lg:col-span-1">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-bold text-slate-400 uppercase tracking-wider">O Teu Saldo</h3>
|
||||
<BaseIcon path={icon.mdiWallet} className={iconsColor} size={24} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{ rolesWidgets &&
|
||||
rolesWidgets.map((widget) => (
|
||||
<SmartWidget
|
||||
key={widget.id}
|
||||
userId={currentUser?.id}
|
||||
widget={widget}
|
||||
roleId={widgetsRole?.role?.value || ''}
|
||||
admin={hasPermission(currentUser, 'CREATE_ROLES')}
|
||||
<div className="space-y-1">
|
||||
<div className="text-4xl font-black text-white">
|
||||
{loadingWallet ? '...' : `${Number(wallet?.balance || 0).toLocaleString('pt-PT', { minimumFractionDigits: 2 })}€`}
|
||||
</div>
|
||||
<div className="text-sm text-slate-500 font-bold uppercase">Créditos Disponíveis</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<BaseButton
|
||||
label={depositing ? 'A PROCESSAR...' : 'DEPOSITAR 1000€'}
|
||||
color="danger"
|
||||
className="w-full font-black italic tracking-tighter"
|
||||
onClick={handleDeposit}
|
||||
disabled={depositing}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
<CardBox className="lg:col-span-2">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-bold text-slate-400 uppercase tracking-wider">Jogos Populares</h3>
|
||||
<BaseIcon path={icon.mdiCasino} className={iconsColor} size={24} />
|
||||
</div>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
{['Roleta', 'Blackjack', 'Slots Premium', 'Póquer', 'Baccarat', 'Crash Game'].map((game) => (
|
||||
<div key={game} className="bg-slate-800/50 border border-white/5 rounded-xl p-4 hover:bg-red-600/10 hover:border-red-600/50 transition-all cursor-pointer group text-center">
|
||||
<div className="text-sm font-bold uppercase italic group-hover:text-red-500">{game}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardBox>
|
||||
</div>
|
||||
|
||||
{!!rolesWidgets.length && <hr className='my-6 text-midnightBlueTheme-mainBG ' />}
|
||||
|
||||
<div id="dashboard" className='grid grid-cols-1 gap-6 lg:grid-cols-3 mb-6'>
|
||||
|
||||
|
||||
{hasPermission(currentUser, 'READ_USERS') && <Link href={'/users/users-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Users
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{users}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={icon.mdiAccountGroup || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_ROLES') && <Link href={'/roles/roles-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Roles
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{roles}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={icon.mdiShieldAccountVariantOutline || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_PERMISSIONS') && <Link href={'/permissions/permissions-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Permissions
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{permissions}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={icon.mdiShieldAccountOutline || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_WALLETS') && <Link href={'/wallets/wallets-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Wallets
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{wallets}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiWallet' in icon ? icon['mdiWallet' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_PAYMENT_METHODS') && <Link href={'/payment_methods/payment_methods-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Payment methods
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{payment_methods}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiCreditCardOutline' in icon ? icon['mdiCreditCardOutline' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_TRANSACTIONS') && <Link href={'/transactions/transactions-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Transactions
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{transactions}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiSwapHorizontal' in icon ? icon['mdiSwapHorizontal' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_GAMES') && <Link href={'/games/games-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Games
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{games}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiCasino' in icon ? icon['mdiCasino' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_GAME_SESSIONS') && <Link href={'/game_sessions/game_sessions-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Game sessions
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{game_sessions}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiPlayCircleOutline' in icon ? icon['mdiPlayCircleOutline' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_GAME_ROUNDS') && <Link href={'/game_rounds/game_rounds-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Game rounds
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{game_rounds}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiDiceMultiple' in icon ? icon['mdiDiceMultiple' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_BONUSES') && <Link href={'/bonuses/bonuses-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Bonuses
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{bonuses}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiGiftOutline' in icon ? icon['mdiGiftOutline' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_BONUS_CLAIMS') && <Link href={'/bonus_claims/bonus_claims-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Bonus claims
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{bonus_claims}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiTicketPercentOutline' in icon ? icon['mdiTicketPercentOutline' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_ADMIN_AUDIT_LOGS') && <Link href={'/admin_audit_logs/admin_audit_logs-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Admin audit logs
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{admin_audit_logs}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiShieldKeyOutline' in icon ? icon['mdiShieldKeyOutline' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_SUPPORT_TICKETS') && <Link href={'/support_tickets/support_tickets-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Support tickets
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{support_tickets}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiLifebuoy' in icon ? icon['mdiLifebuoy' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_SITE_SETTINGS') && <Link href={'/site_settings/site_settings-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Site settings
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{site_settings}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiCogOutline' in icon ? icon['mdiCogOutline' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
|
||||
</div>
|
||||
<SectionTitleLineWithButton
|
||||
icon={icon.mdiChartTimelineVariant}
|
||||
title='Atividade Recente'
|
||||
>
|
||||
<BaseButton href="/transactions/transactions-list" label="Ver Tudo" color="white" small outline />
|
||||
</SectionTitleLineWithButton>
|
||||
|
||||
<CardBox className="mb-6">
|
||||
<div className="text-center py-8 text-slate-500 font-bold uppercase tracking-widest text-sm">
|
||||
Sem atividade recente para mostrar.
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
</SectionMain>
|
||||
</>
|
||||
)
|
||||
@ -552,4 +136,4 @@ Dashboard.getLayout = function getLayout(page: ReactElement) {
|
||||
return <LayoutAuthenticated>{page}</LayoutAuthenticated>
|
||||
}
|
||||
|
||||
export default Dashboard
|
||||
export default Dashboard
|
||||
@ -1,160 +1,118 @@
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React from 'react';
|
||||
import type { ReactElement } from 'react';
|
||||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import BaseButton from '../components/BaseButton';
|
||||
import CardBox from '../components/CardBox';
|
||||
import SectionFullScreen from '../components/SectionFullScreen';
|
||||
import LayoutGuest from '../layouts/Guest';
|
||||
import BaseDivider from '../components/BaseDivider';
|
||||
import BaseButtons from '../components/BaseButtons';
|
||||
import { getPageTitle } from '../config';
|
||||
import { useAppSelector } from '../stores/hooks';
|
||||
import CardBoxComponentTitle from "../components/CardBoxComponentTitle";
|
||||
import { getPexelsImage, getPexelsVideo } from '../helpers/pexels';
|
||||
|
||||
|
||||
export default function Starter() {
|
||||
const [illustrationImage, setIllustrationImage] = useState({
|
||||
src: undefined,
|
||||
photographer: undefined,
|
||||
photographer_url: undefined,
|
||||
})
|
||||
const [illustrationVideo, setIllustrationVideo] = useState({video_files: []})
|
||||
const [contentType, setContentType] = useState('video');
|
||||
const [contentPosition, setContentPosition] = useState('left');
|
||||
const title = 'BCasino'
|
||||
const textColor = useAppSelector((state) => state.style.linkColor);
|
||||
|
||||
const title = 'BCasino'
|
||||
|
||||
// Fetch Pexels image/video
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
const image = await getPexelsImage();
|
||||
const video = await getPexelsVideo();
|
||||
setIllustrationImage(image);
|
||||
setIllustrationVideo(video);
|
||||
}
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const imageBlock = (image) => (
|
||||
<div
|
||||
className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'
|
||||
style={{
|
||||
backgroundImage: `${
|
||||
image
|
||||
? `url(${image?.src?.original})`
|
||||
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
|
||||
}`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'left center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}}
|
||||
>
|
||||
<div className='flex justify-center w-full bg-blue-300/20'>
|
||||
<a
|
||||
className='text-[8px]'
|
||||
href={image?.photographer_url}
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
Photo by {image?.photographer} on Pexels
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const videoBlock = (video) => {
|
||||
if (video?.video_files?.length > 0) {
|
||||
return (
|
||||
<div className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'>
|
||||
<video
|
||||
className='absolute top-0 left-0 w-full h-full object-cover'
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
>
|
||||
<source src={video?.video_files[0]?.link} type='video/mp4'/>
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
<div className='flex justify-center w-full bg-blue-300/20 z-10'>
|
||||
<a
|
||||
className='text-[8px]'
|
||||
href={video?.user?.url}
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
Video by {video.user.name} on Pexels
|
||||
</a>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={
|
||||
contentPosition === 'background'
|
||||
? {
|
||||
backgroundImage: `${
|
||||
illustrationImage
|
||||
? `url(${illustrationImage.src?.original})`
|
||||
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
|
||||
}`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'left center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>
|
||||
<div className="bg-slate-950 min-h-screen text-white font-sans">
|
||||
<Head>
|
||||
<title>{getPageTitle('Starter Page')}</title>
|
||||
<title>{getPageTitle('Home')}</title>
|
||||
</Head>
|
||||
|
||||
<SectionFullScreen bg='violet'>
|
||||
<div
|
||||
className={`flex ${
|
||||
contentPosition === 'right' ? 'flex-row-reverse' : 'flex-row'
|
||||
} min-h-screen w-full`}
|
||||
>
|
||||
{contentType === 'image' && contentPosition !== 'background'
|
||||
? imageBlock(illustrationImage)
|
||||
: null}
|
||||
{contentType === 'video' && contentPosition !== 'background'
|
||||
? videoBlock(illustrationVideo)
|
||||
: null}
|
||||
<div className='flex items-center justify-center flex-col space-y-4 w-full lg:w-full'>
|
||||
<CardBox className='w-full md:w-3/5 lg:w-2/3'>
|
||||
<CardBoxComponentTitle title="Welcome to your BCasino app!"/>
|
||||
|
||||
<div className="space-y-3">
|
||||
<p className='text-center '>This is a React.js/Node.js app generated by the <a className={`${textColor}`} href="https://flatlogic.com/generator">Flatlogic Web App Generator</a></p>
|
||||
<p className='text-center '>For guides and documentation please check
|
||||
your local README.md and the <a className={`${textColor}`} href="https://flatlogic.com/documentation">Flatlogic documentation</a></p>
|
||||
</div>
|
||||
|
||||
<BaseButtons>
|
||||
<BaseButton
|
||||
href='/login'
|
||||
label='Login'
|
||||
color='info'
|
||||
className='w-full'
|
||||
/>
|
||||
<nav className="p-6 flex justify-between items-center bg-slate-900/50 backdrop-blur-md sticky top-0 z-50 border-b border-white/5">
|
||||
<div className="text-3xl font-black italic tracking-tighter text-red-600">
|
||||
{title}
|
||||
</div>
|
||||
<div className="space-x-4 flex items-center">
|
||||
<Link href="/login" className="text-sm font-bold hover:text-red-500 transition-colors">
|
||||
LOGIN
|
||||
</Link>
|
||||
<BaseButton
|
||||
href="/register"
|
||||
label="REGISTAR"
|
||||
color="danger"
|
||||
className="px-6 font-bold"
|
||||
/>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
</BaseButtons>
|
||||
</CardBox>
|
||||
</div>
|
||||
</div>
|
||||
<SectionFullScreen bg="slate">
|
||||
<div className="container mx-auto px-6 py-20 flex flex-col lg:flex-row items-center justify-between">
|
||||
<div className="lg:w-1/2 space-y-8 text-center lg:text-left">
|
||||
<h1 className="text-6xl lg:text-8xl font-black italic uppercase leading-tight">
|
||||
A Tua Casa de <span className="text-red-600">Apostas</span> Online
|
||||
</h1>
|
||||
<p className="text-xl text-slate-400 max-w-lg mx-auto lg:mx-0 font-medium">
|
||||
Experimenta a emoção do BCasino. Centenas de jogos, bónus exclusivos e depósitos instantâneos. Começa hoje mesmo!
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 pt-4 justify-center lg:justify-start">
|
||||
<BaseButton
|
||||
href="/register"
|
||||
label="CRIAR CONTA GRÁTIS"
|
||||
color="danger"
|
||||
className="text-lg py-4 px-10 font-black italic"
|
||||
/>
|
||||
<BaseButton
|
||||
href="/games/games-list"
|
||||
label="VER JOGOS"
|
||||
outline
|
||||
color="white"
|
||||
className="text-lg py-4 px-10 font-black italic border-2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:w-1/2 mt-12 lg:mt-0 relative group">
|
||||
<div className="absolute -inset-1 bg-gradient-to-r from-red-600 to-red-900 rounded-3xl blur opacity-25 group-hover:opacity-50 transition duration-1000 group-hover:duration-200"></div>
|
||||
<div className="relative bg-slate-900 rounded-3xl border border-white/10 overflow-hidden shadow-2xl">
|
||||
<div className="p-8 space-y-6">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="w-12 h-12 bg-red-600 rounded-full flex items-center justify-center">
|
||||
<span className="font-bold text-xl">1</span>
|
||||
</div>
|
||||
<h3 className="text-xl font-bold italic uppercase">Casino Ao Vivo</h3>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="w-12 h-12 bg-slate-800 rounded-full flex items-center justify-center">
|
||||
<span className="font-bold text-xl text-red-600">2</span>
|
||||
</div>
|
||||
<h3 className="text-xl font-bold italic uppercase">Slots Exclusivas</h3>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="w-12 h-12 bg-slate-800 rounded-full flex items-center justify-center">
|
||||
<span className="font-bold text-xl text-red-600">3</span>
|
||||
</div>
|
||||
<h3 className="text-xl font-bold italic uppercase">Apostas Desportivas</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-red-600 p-4 text-center font-black italic uppercase tracking-widest text-sm">
|
||||
Bónus de Boas-Vindas até 1000€
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SectionFullScreen>
|
||||
<div className='bg-black text-white flex flex-col text-center justify-center md:flex-row'>
|
||||
<p className='py-6 text-sm'>© 2026 <span>{title}</span>. All rights reserved</p>
|
||||
<Link className='py-6 ml-4 text-sm' href='/privacy-policy/'>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<footer className="bg-slate-900 border-t border-white/5 p-12 mt-20">
|
||||
<div className="container mx-auto grid grid-cols-1 md:grid-cols-3 gap-12 text-center md:text-left">
|
||||
<div className="space-y-4">
|
||||
<div className="text-2xl font-black italic text-red-600">{title}</div>
|
||||
<p className="text-slate-500 text-sm">A melhor experiência de jogo online em Portugal. Licenciada e segura.</p>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<h4 className="font-bold uppercase tracking-widest text-xs text-red-600">Links Úteis</h4>
|
||||
<ul className="text-sm text-slate-400 space-y-2 font-medium">
|
||||
<li><Link href="/terms-of-use" className="hover:text-white transition-colors">Termos e Condições</Link></li>
|
||||
<li><Link href="/privacy-policy" className="hover:text-white transition-colors">Privacidade</Link></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<h4 className="font-bold uppercase tracking-widest text-xs text-red-600">Contacto</h4>
|
||||
<p className="text-sm text-slate-400 font-medium">suporte@bcasino.pt</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t border-white/5 mt-12 pt-8 text-center text-xs text-slate-600 font-bold uppercase tracking-widest">
|
||||
© 2026 {title}. Joga com responsabilidade.
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
);
|
||||
@ -162,5 +120,4 @@ export default function Starter() {
|
||||
|
||||
Starter.getLayout = function getLayout(page: ReactElement) {
|
||||
return <LayoutGuest>{page}</LayoutGuest>;
|
||||
};
|
||||
|
||||
};
|
||||
@ -53,31 +53,31 @@ export const white: StyleObject = {
|
||||
|
||||
|
||||
export const midnightBlueTheme: StyleObject = {
|
||||
aside: 'bg-midnightBlueTheme-800 text-midnightBlueTheme-text dark:text-white lg:rounded-lg',
|
||||
asideScrollbars: 'aside-scrollbars-blue',
|
||||
asideBrand: 'text-blue-500 bg-white',
|
||||
aside: 'bg-slate-950 text-white dark:text-white',
|
||||
asideScrollbars: 'aside-scrollbars-red',
|
||||
asideBrand: 'text-red-600 bg-slate-950',
|
||||
asideMenuItem:
|
||||
'text-midnightBlueTheme-text hover:text-white dark:text-dark-500 dark:hover:text-white dark:hover:bg-dark-800 dark:text-white',
|
||||
asideMenuItemActive: 'font-bold text-white dark:text-white',
|
||||
activeLinkColor: 'bg-midnightBlueTheme-buttonColor rounded-lg',
|
||||
asideMenuDropdown: 'bg-blue-700/50',
|
||||
navBarItemLabel: 'text-primaryText',
|
||||
iconsColor: 'text-midnightBlueTheme-iconsColor dark:text-blue-500',
|
||||
navBarItemLabelHover: 'hover:text-stone-400',
|
||||
navBarItemLabelActiveColor: 'text-midnightBlueTheme-800',
|
||||
overlay: 'bg-midnightBlueTheme-mainBG',
|
||||
bgLayoutColor: 'bg-midnightBlueTheme-mainBG',
|
||||
cardsColor: 'bg-midnightBlueTheme-cardColor',
|
||||
'text-gray-400 hover:text-white dark:text-dark-500 dark:hover:text-white dark:hover:bg-dark-800 dark:text-white',
|
||||
asideMenuItemActive: 'font-bold text-red-500 dark:text-white',
|
||||
activeLinkColor: 'bg-red-600/10 rounded-lg',
|
||||
asideMenuDropdown: 'bg-slate-900',
|
||||
navBarItemLabel: 'text-white',
|
||||
iconsColor: 'text-red-600 dark:text-red-500',
|
||||
navBarItemLabelHover: 'hover:text-red-400',
|
||||
navBarItemLabelActiveColor: 'text-red-600',
|
||||
overlay: 'bg-slate-950',
|
||||
bgLayoutColor: 'bg-slate-950',
|
||||
cardsColor: 'bg-slate-900',
|
||||
focusRingColor:
|
||||
'focus:ring focus:ring-midnightBlueTheme-800 focus:border-midnightBlueTheme-800 focus:outline-none border border-gray-600 dark:focus:ring-blue-600 dark:focus:border-blue-600',
|
||||
corners: 'rounded-lg',
|
||||
cardsStyle: 'bg-midnightBlueTheme-outsideCardColor border border-midnightBlueTheme-outsideCardColor shadow-xl',
|
||||
linkColor: 'text-midnightBlueTheme-buttonColor',
|
||||
websiteHeder: 'border-b border-white border-opacity-10 shadow-md',
|
||||
'focus:ring focus:ring-red-600 focus:border-red-600 focus:outline-none border border-slate-800 dark:focus:ring-red-600 dark:focus:border-red-600',
|
||||
corners: 'rounded-xl',
|
||||
cardsStyle: 'bg-slate-900 border border-slate-800 shadow-2xl',
|
||||
linkColor: 'text-red-600',
|
||||
websiteHeder: 'border-b border-white border-opacity-10 shadow-lg',
|
||||
borders: 'border-white border-opacity-10',
|
||||
shadow: 'shadow-md',
|
||||
websiteSectionStyle: ' bg-midnightBlueTheme-webSiteComponentBg text-white',
|
||||
textSecondary: 'text-gray-300',
|
||||
shadow: 'shadow-2xl',
|
||||
websiteSectionStyle: 'bg-slate-950 text-white',
|
||||
textSecondary: 'text-slate-400',
|
||||
};
|
||||
|
||||
|
||||
@ -132,4 +132,4 @@ export const basic: StyleObject = {
|
||||
shadow: '',
|
||||
websiteSectionStyle: '',
|
||||
textSecondary: '',
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user