87 | >(({ className, ...props }, ref) => (
88 | [role=checkbox]]:translate-y-[2px]",
92 | className
93 | )}
94 | {...props}
95 | />
96 | ))
97 | TableCell.displayName = "TableCell"
98 |
99 | const TableCaption = React.forwardRef<
100 | HTMLTableCaptionElement,
101 | React.HTMLAttributes
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | TableCaption.displayName = "TableCaption"
110 |
111 | export {
112 | Table,
113 | TableHeader,
114 | TableBody,
115 | TableFooter,
116 | TableHead,
117 | TableRow,
118 | TableCell,
119 | TableCaption,
120 | }
121 |
--------------------------------------------------------------------------------
/resources/js/Components/ui/toaster.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Toast,
3 | ToastClose,
4 | ToastDescription,
5 | ToastProvider,
6 | ToastTitle,
7 | ToastViewport,
8 | } from "@/Components/ui/toast"
9 | import { useToast } from "@/Components/ui/use-toast"
10 |
11 | export function Toaster() {
12 | const { toasts } = useToast()
13 |
14 | return (
15 |
16 | {toasts.map(function ({ id, title, description, action, ...props }) {
17 | return (
18 |
19 |
20 | {title && {title} }
21 | {description && (
22 | {description}
23 | )}
24 |
25 | {action}
26 |
27 |
28 | )
29 | })}
30 |
31 |
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/resources/js/Components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const TooltipProvider = TooltipPrimitive.Provider
7 |
8 | const Tooltip = TooltipPrimitive.Root
9 |
10 | const TooltipTrigger = TooltipPrimitive.Trigger
11 |
12 | const TooltipContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, sideOffset = 4, ...props }, ref) => (
16 |
25 | ))
26 | TooltipContent.displayName = TooltipPrimitive.Content.displayName
27 |
28 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
29 |
--------------------------------------------------------------------------------
/resources/js/Components/v1.upload-progress.tsx:
--------------------------------------------------------------------------------
1 | ;
2 |
3 | Upload a file
4 |
5 |
6 | {fileName === '' && (
7 | <>
8 |
16 | >
17 | )}
18 | fileInputRef.current && fileInputRef.current.click()}
21 | >
22 | {fileName !== '' ? `${fileName}` : 'Browse file'}
23 |
24 | {!isUploading && file !== null && (
25 |
26 | Upload
27 |
28 | )}
29 |
30 | {isUploading && (
31 |
32 | {uploadPercentage}%
33 |
34 | )}
35 | {isUploading && (
36 |
44 | )}
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/resources/js/Layouts/AuthenticatedLayout.tsx:
--------------------------------------------------------------------------------
1 | import { PropsWithChildren, ReactNode, useEffect, useState } from 'react'
2 | import { User } from '@/types'
3 | import Navigation from '@/Components/Navigation'
4 | import Footer from '@/Components/Footer'
5 | import AppHeader from '@/Components/AppHeader'
6 | import CustomToaster from '@/Components/CustomToaster'
7 |
8 | interface EventResponse {
9 | user_id: number
10 | message: string
11 | backup_file: string
12 | }
13 |
14 | export default function Authenticated({
15 | user,
16 | header,
17 | children
18 | }: PropsWithChildren<{ user: User; header?: ReactNode }>) {
19 | const [showToaster, setShowToaster] = useState(false)
20 | const [message, setMessage] = useState('')
21 |
22 | useEffect(() => {
23 | const echoInstance = (window as any).Echo
24 | echoInstance
25 | .private(`backup.${user.id}`)
26 | .listen('BackupCompleted', (event: EventResponse) => {
27 | setShowToaster(true)
28 | setMessage(event.message)
29 | })
30 | }, [])
31 |
32 | const handleCloseToaster = () => {
33 | setShowToaster(false)
34 | }
35 | return (
36 |
37 | {showToaster && (
38 |
42 | )}
43 |
44 |
45 | {/* Sidebar */}
46 |
47 |
48 |
49 |
54 |
55 |
56 | Untitled Admin v1.0
57 |
58 |
59 |
60 | {/* Main navigation */}
61 |
62 |
63 |
64 | Untitled Admin v1.0
65 |
66 |
67 | {/* End of sidebar */}
68 |
69 | {/* Main contents */}
70 |
71 | {/* Main header */}
72 |
73 | {/* End of main header */}
74 |
75 |
76 |
77 | {children}
78 |
79 |
80 | {/* Footer */}
81 |
82 | {/* End of footer */}
83 |
84 |
85 |
86 |
87 | )
88 | }
89 |
--------------------------------------------------------------------------------
/resources/js/Layouts/GuestLayout.tsx:
--------------------------------------------------------------------------------
1 | import ApplicationLogo from '@/Components/ApplicationLogo';
2 | import { Link } from '@inertiajs/react';
3 | import { PropsWithChildren } from 'react';
4 |
5 | export default function Guest({ children }: PropsWithChildren) {
6 | return (
7 |
8 |
13 |
14 |
15 | {children}
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/ConfirmPassword.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, FormEventHandler } from 'react';
2 | import GuestLayout from '@/Layouts/GuestLayout';
3 | import InputError from '@/Components/InputError';
4 | import InputLabel from '@/Components/InputLabel';
5 | import PrimaryButton from '@/Components/PrimaryButton';
6 | import TextInput from '@/Components/TextInput';
7 | import { Head, useForm } from '@inertiajs/react';
8 |
9 | export default function ConfirmPassword() {
10 | const { data, setData, post, processing, errors, reset } = useForm({
11 | password: '',
12 | });
13 |
14 | useEffect(() => {
15 | return () => {
16 | reset('password');
17 | };
18 | }, []);
19 |
20 | const submit: FormEventHandler = (e) => {
21 | e.preventDefault();
22 |
23 | post(route('password.confirm'));
24 | };
25 |
26 | return (
27 |
28 |
29 |
30 |
31 | This is a secure area of the application. Please confirm your password before continuing.
32 |
33 |
34 |
57 |
58 | );
59 | }
60 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/ForgotPassword.tsx:
--------------------------------------------------------------------------------
1 | import GuestLayout from '@/Layouts/GuestLayout';
2 | import InputError from '@/Components/InputError';
3 | import PrimaryButton from '@/Components/PrimaryButton';
4 | import TextInput from '@/Components/TextInput';
5 | import { Head, useForm } from '@inertiajs/react';
6 | import { FormEventHandler } from 'react';
7 |
8 | export default function ForgotPassword({ status }: { status?: string }) {
9 | const { data, setData, post, processing, errors } = useForm({
10 | email: '',
11 | });
12 |
13 | const submit: FormEventHandler = (e) => {
14 | e.preventDefault();
15 |
16 | post(route('password.email'));
17 | };
18 |
19 | return (
20 |
21 |
22 |
23 |
24 | Forgot your password? No problem. Just let us know your email address and we will email you a password
25 | reset link that will allow you to choose a new one.
26 |
27 |
28 | {status && {status}
}
29 |
30 |
49 |
50 | );
51 | }
52 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/Login.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, FormEventHandler } from 'react';
2 | import Checkbox from '@/Components/Checkbox';
3 | import GuestLayout from '@/Layouts/GuestLayout';
4 | import InputError from '@/Components/InputError';
5 | import InputLabel from '@/Components/InputLabel';
6 | import PrimaryButton from '@/Components/PrimaryButton';
7 | import TextInput from '@/Components/TextInput';
8 | import { Head, Link, useForm } from '@inertiajs/react';
9 |
10 | export default function Login({ status, canResetPassword }: { status?: string, canResetPassword: boolean }) {
11 | const { data, setData, post, processing, errors, reset } = useForm({
12 | email: '',
13 | password: '',
14 | remember: false,
15 | });
16 |
17 | useEffect(() => {
18 | return () => {
19 | reset('password');
20 | };
21 | }, []);
22 |
23 | const submit: FormEventHandler = (e) => {
24 | e.preventDefault();
25 |
26 | post(route('login'));
27 | };
28 |
29 | return (
30 |
31 |
32 |
33 | {status && {status}
}
34 |
35 |
95 |
96 | );
97 | }
98 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/ResetPassword.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, FormEventHandler } from 'react';
2 | import GuestLayout from '@/Layouts/GuestLayout';
3 | import InputError from '@/Components/InputError';
4 | import InputLabel from '@/Components/InputLabel';
5 | import PrimaryButton from '@/Components/PrimaryButton';
6 | import TextInput from '@/Components/TextInput';
7 | import { Head, useForm } from '@inertiajs/react';
8 |
9 | export default function ResetPassword({ token, email }: { token: string, email: string }) {
10 | const { data, setData, post, processing, errors, reset } = useForm({
11 | token: token,
12 | email: email,
13 | password: '',
14 | password_confirmation: '',
15 | });
16 |
17 | useEffect(() => {
18 | return () => {
19 | reset('password', 'password_confirmation');
20 | };
21 | }, []);
22 |
23 | const submit: FormEventHandler = (e) => {
24 | e.preventDefault();
25 |
26 | post(route('password.store'));
27 | };
28 |
29 | return (
30 |
31 |
32 |
33 |
88 |
89 | );
90 | }
91 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/VerifyEmail.tsx:
--------------------------------------------------------------------------------
1 | import GuestLayout from '@/Layouts/GuestLayout';
2 | import PrimaryButton from '@/Components/PrimaryButton';
3 | import { Head, Link, useForm } from '@inertiajs/react';
4 | import { FormEventHandler } from 'react';
5 |
6 | export default function VerifyEmail({ status }: { status?: string }) {
7 | const { post, processing } = useForm({});
8 |
9 | const submit: FormEventHandler = (e) => {
10 | e.preventDefault();
11 |
12 | post(route('verification.send'));
13 | };
14 |
15 | return (
16 |
17 |
18 |
19 |
20 | Thanks for signing up! Before getting started, could you verify your email address by clicking on the
21 | link we just emailed to you? If you didn't receive the email, we will gladly send you another.
22 |
23 |
24 | {status === 'verification-link-sent' && (
25 |
26 | A new verification link has been sent to the email address you provided during registration.
27 |
28 | )}
29 |
30 |
44 |
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/resources/js/Pages/Dashboard.tsx:
--------------------------------------------------------------------------------
1 | import { Head } from '@inertiajs/react'
2 | import { PageProps } from '@/types'
3 | import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
4 | import MonthlyRevenue from '@/Components/statistics/MonthlyRevenue'
5 | import UserEngagement from '@/Components/statistics/UserEngagement'
6 | import SalesChart from '@/Components/statistics/SalesChart'
7 | import TaskEmails from '@/Components/TaskEmails'
8 |
9 | export default function Dashboard({ auth, users }: PageProps) {
10 | return (
11 | Dashboard}
14 | >
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/resources/js/Pages/Profile/Edit.tsx:
--------------------------------------------------------------------------------
1 | import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
2 | import DeleteUserForm from './Partials/DeleteUserForm'
3 | import UpdatePasswordForm from './Partials/UpdatePasswordForm'
4 | import UpdateProfileInformationForm from './Partials/UpdateProfileInformationForm'
5 | import { Head } from '@inertiajs/react'
6 | import { PageProps } from '@/types'
7 |
8 | export default function Edit({
9 | auth,
10 | mustVerifyEmail,
11 | status
12 | }: PageProps<{ mustVerifyEmail: boolean; status?: string }>) {
13 | return (
14 |
18 | Profile
19 |
20 | }
21 | >
22 |
23 |
24 |
25 |
26 |
27 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | )
45 | }
46 |
--------------------------------------------------------------------------------
/resources/js/Pages/Profile/Partials/DeleteUserForm.tsx:
--------------------------------------------------------------------------------
1 | import { useRef, useState, FormEventHandler } from 'react';
2 | import DangerButton from '@/Components/DangerButton';
3 | import InputError from '@/Components/InputError';
4 | import InputLabel from '@/Components/InputLabel';
5 | import Modal from '@/Components/Modal';
6 | import SecondaryButton from '@/Components/SecondaryButton';
7 | import TextInput from '@/Components/TextInput';
8 | import { useForm } from '@inertiajs/react';
9 |
10 | export default function DeleteUserForm({ className = '' }: { className?: string }) {
11 | const [confirmingUserDeletion, setConfirmingUserDeletion] = useState(false);
12 | const passwordInput = useRef(null);
13 |
14 | const {
15 | data,
16 | setData,
17 | delete: destroy,
18 | processing,
19 | reset,
20 | errors,
21 | } = useForm({
22 | password: '',
23 | });
24 |
25 | const confirmUserDeletion = () => {
26 | setConfirmingUserDeletion(true);
27 | };
28 |
29 | const deleteUser: FormEventHandler = (e) => {
30 | e.preventDefault();
31 |
32 | destroy(route('profile.destroy'), {
33 | preserveScroll: true,
34 | onSuccess: () => closeModal(),
35 | onError: () => passwordInput.current?.focus(),
36 | onFinish: () => reset(),
37 | });
38 | };
39 |
40 | const closeModal = () => {
41 | setConfirmingUserDeletion(false);
42 |
43 | reset();
44 | };
45 |
46 | return (
47 |
48 |
49 | Delete Account
50 |
51 |
52 | Once your account is deleted, all of its resources and data will be permanently deleted. Before
53 | deleting your account, please download any data or information that you wish to retain.
54 |
55 |
56 |
57 | Delete Account
58 |
59 |
60 |
96 |
97 |
98 | );
99 | }
100 |
--------------------------------------------------------------------------------
/resources/js/app.tsx:
--------------------------------------------------------------------------------
1 | import './bootstrap'
2 | import '../css/app.css'
3 | import { createRoot } from 'react-dom/client'
4 | import { createInertiaApp } from '@inertiajs/react'
5 | import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'
6 | // import { createTheme, ThemeProvider } from '@mui/material/styles'
7 | // import useMediaQuery from '@mui/material/useMediaQuery'
8 | // import CssBaseline from '@mui/material/CssBaseline'
9 | import React from 'react'
10 |
11 | const appName = import.meta.env.VITE_APP_NAME || 'Laravel'
12 |
13 | // function AppWrapper({ App, props }: { App: any; props: any }) {
14 | // const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)')
15 | // const theme = React.useMemo(
16 | // () =>
17 | // createTheme({
18 | // palette: {
19 | // mode: prefersDarkMode ? 'dark' : 'light'
20 | // }
21 | // }),
22 | // [prefersDarkMode]
23 | // )
24 |
25 | // return
26 | // }
27 |
28 | createInertiaApp({
29 | title: title => `${title} - ${appName}`,
30 | resolve: name =>
31 | resolvePageComponent(
32 | `./Pages/${name}.tsx`,
33 | import.meta.glob('./Pages/**/*.tsx')
34 | ),
35 | setup({ el, App, props }) {
36 | const root = createRoot(el)
37 |
38 | root.render( )
39 | },
40 | progress: {
41 | color: '#4B5563'
42 | }
43 | })
44 |
--------------------------------------------------------------------------------
/resources/js/bootstrap.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | window.axios = axios;
3 |
4 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
5 |
6 | import './echo'
7 |
--------------------------------------------------------------------------------
/resources/js/echo.js:
--------------------------------------------------------------------------------
1 | import Echo from 'laravel-echo';
2 |
3 | import Pusher from 'pusher-js';
4 | window.Pusher = Pusher;
5 |
6 | window.Echo = new Echo({
7 | broadcaster: 'reverb',
8 | key: import.meta.env.VITE_REVERB_APP_KEY,
9 | wsHost: import.meta.env.VITE_REVERB_HOST,
10 | wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
11 | wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
12 | forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
13 | enabledTransports: ['ws', 'wss'],
14 | });
15 |
--------------------------------------------------------------------------------
/resources/js/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/resources/js/menus.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | PiLayoutDuotone,
3 | PiFilesDuotone,
4 | PiUsersThreeDuotone,
5 | PiChartLineUpDuotone,
6 | PiWarehouseDuotone,
7 | PiUsersDuotone,
8 | PiHandTapDuotone,
9 | PiChartPieDuotone,
10 | PiTrafficSignalDuotone,
11 | PiMoneyWavyDuotone,
12 | PiDatabaseDuotone,
13 | PiNoteDuotone,
14 | PiQuestionDuotone,
15 | PiLifebuoyDuotone,
16 | PiMegaphoneDuotone
17 | } from 'react-icons/pi'
18 |
19 | type MenuItem = {
20 | label: string
21 | icon: React.JSX.Element | null
22 | href?: string | null
23 | }
24 |
25 | const menuItems: MenuItem[] = [
26 | {
27 | label: 'Dashboard',
28 | icon: ,
29 | href: '/dashboard'
30 | },
31 | {
32 | label: 'Transactions',
33 | icon: ,
34 | href: '/transactions'
35 | },
36 | {
37 | label: 'Inventory',
38 | icon: ,
39 | href: '/inventory'
40 | },
41 | {
42 | label: 'Analytics',
43 | icon: ,
44 | href: '/analytics'
45 | },
46 | {
47 | label: 'separator',
48 | icon: null
49 | // href: undefined
50 | },
51 | {
52 | label: 'Subscribers',
53 | icon: ,
54 | href: '/subscribers'
55 | },
56 | {
57 | label: 'Clients',
58 | icon: ,
59 | href: '/clients'
60 | },
61 | {
62 | label: 'User Engagements',
63 | icon: ,
64 | href: '/clients'
65 | },
66 | {
67 | label: 'separator',
68 | icon: null
69 | // href: undefined
70 | },
71 | {
72 | label: 'Reports',
73 | icon: ,
74 | href: '/reports'
75 | },
76 | {
77 | label: 'Traffic Analysis',
78 | icon: ,
79 | href: '/traffic-analysis'
80 | },
81 | {
82 | label: 'Sales Data',
83 | icon: ,
84 | href: '/sales-data'
85 | },
86 | {
87 | label: 'separator',
88 | icon: null
89 | // href: undefined
90 | },
91 | {
92 | label: 'System Maintenance',
93 | icon: ,
94 | href: '/system-maintenance'
95 | },
96 | {
97 | label: 'System Logs',
98 | icon: ,
99 | href: '/system-logs'
100 | },
101 | {
102 | label: 'separator',
103 | icon: null
104 | // href: undefined
105 | },
106 | {
107 | label: 'Help Center',
108 | icon: ,
109 | href: '/help-support'
110 | },
111 | {
112 | label: 'FAQ',
113 | icon: ,
114 | href: '/faq'
115 | },
116 | {
117 | label: 'separator',
118 | icon: null
119 | // href: undefined
120 | },
121 | {
122 | label: 'User Feedback',
123 | icon: ,
124 | href: '/user-feedback'
125 | }
126 | ]
127 |
128 | export default menuItems
129 |
--------------------------------------------------------------------------------
/resources/js/types/global.d.ts:
--------------------------------------------------------------------------------
1 | import { AxiosInstance } from 'axios';
2 | import { route as ziggyRoute } from 'ziggy-js';
3 |
4 | declare global {
5 | interface Window {
6 | axios: AxiosInstance;
7 | }
8 |
9 | var route: typeof ziggyRoute;
10 | }
11 |
--------------------------------------------------------------------------------
/resources/js/types/index.d.ts:
--------------------------------------------------------------------------------
1 | export interface User {
2 | id: number;
3 | name: string;
4 | email: string;
5 | task_checked_at: string;
6 | email_verified_at: string;
7 | }
8 |
9 | export interface DatabaseBackup {
10 | id: number;
11 | user_id: number;
12 | path: string;
13 | created_at: string;
14 | updated_at: string;
15 | }
16 |
17 | export type PageProps = Record> = T & {
18 | auth: {
19 | user: User;
20 | };
21 | users: User[];
22 | backups: DatabaseBackup[];
23 | };
24 |
--------------------------------------------------------------------------------
/resources/js/types/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/resources/views/app.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{ config('app.name', 'Laravel') }}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | @routes
16 | @viteReactRefresh
17 | @vite(['resources/js/app.tsx', "resources/js/Pages/{$page['component']}.tsx"])
18 | @inertiaHead
19 |
20 |
21 | @inertia
22 |
23 |
24 |
--------------------------------------------------------------------------------
/routes/auth.php:
--------------------------------------------------------------------------------
1 | group(function () {
15 | // Route::get('register', [RegisteredUserController::class, 'create'])
16 | // ->name('register');
17 |
18 | // Route::post('register', [RegisteredUserController::class, 'store']);
19 |
20 | // Route::get('login', [AuthenticatedSessionController::class, 'create'])
21 | // ->name('login');
22 |
23 | // Route::post('login', [AuthenticatedSessionController::class, 'store']);
24 |
25 | // Route::get('forgot-password', [PasswordResetLinkController::class, 'create'])
26 | // ->name('password.request');
27 |
28 | // Route::post('forgot-password', [PasswordResetLinkController::class, 'store'])
29 | // ->name('password.email');
30 |
31 | // Route::get('reset-password/{token}', [NewPasswordController::class, 'create'])
32 | // ->name('password.reset');
33 |
34 | // Route::post('reset-password', [NewPasswordController::class, 'store'])
35 | // ->name('password.store');
36 | });
37 |
38 | Route::middleware('auth')->group(function () {
39 | Route::get('verify-email', EmailVerificationPromptController::class)
40 | ->name('verification.notice');
41 |
42 | Route::get('verify-email/{id}/{hash}', VerifyEmailController::class)
43 | ->middleware(['signed', 'throttle:6,1'])
44 | ->name('verification.verify');
45 |
46 | Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store'])
47 | ->middleware('throttle:6,1')
48 | ->name('verification.send');
49 |
50 | Route::get('confirm-password', [ConfirmablePasswordController::class, 'show'])
51 | ->name('password.confirm');
52 |
53 | Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']);
54 |
55 | Route::put('password', [PasswordController::class, 'update'])->name('password.update');
56 |
57 | Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])
58 | ->name('logout');
59 | });
60 |
--------------------------------------------------------------------------------
/routes/channels.php:
--------------------------------------------------------------------------------
1 | id === (int) $id;
7 | });
8 |
--------------------------------------------------------------------------------
/routes/console.php:
--------------------------------------------------------------------------------
1 | comment(Inspiring::quote());
8 | })->purpose('Display an inspiring quote')->hourly();
9 |
--------------------------------------------------------------------------------
/routes/web.php:
--------------------------------------------------------------------------------
1 | Route::has('login'),
15 | 'canRegister' => Route::has('register'),
16 | 'laravelVersion' => Application::VERSION,
17 | 'phpVersion' => PHP_VERSION,
18 | ]);
19 | });
20 |
21 | Route::get('/dashboard', function () {
22 | $users = User::where('id', '!=', Auth::id())->simplePaginate(10);
23 |
24 | return Inertia::render('Dashboard', ['users' => $users]);
25 | })->middleware(['auth', 'verified'])->name('dashboard');
26 |
27 | Route::middleware('auth')->group(function () {
28 | Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
29 | Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
30 | Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
31 |
32 | Route::get('/system-maintenance', function () {
33 | return Inertia::render('BackupDatabase');
34 | });
35 |
36 | Route::post('/backup', [BackupController::class, 'backup']);
37 |
38 | Route::get('/download-backup/{path}', function($path) {
39 | return response()->download(storage_path("app/$path"));
40 | });
41 |
42 | Route::get('/get-backups', function() {
43 | return DatabaseBackup::all();
44 | });
45 |
46 | Route::post('/delete-backup', [BackupController::class, 'deleteBackup']);
47 | });
48 |
49 | require __DIR__.'/auth.php';
50 |
--------------------------------------------------------------------------------
/storage/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/backupdb/0ba1b2ad547ede4e2ec9b241b9d7c7f3305f304e/storage/.DS_Store
--------------------------------------------------------------------------------
/storage/app/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !public/
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/storage/app/public/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/backupdb/0ba1b2ad547ede4e2ec9b241b9d7c7f3305f304e/storage/framework/.DS_Store
--------------------------------------------------------------------------------
/storage/framework/.gitignore:
--------------------------------------------------------------------------------
1 | compiled.php
2 | config.php
3 | down
4 | events.scanned.php
5 | maintenance.php
6 | routes.php
7 | routes.scanned.php
8 | schedule-*
9 | services.json
10 |
--------------------------------------------------------------------------------
/storage/framework/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !data/
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/storage/framework/cache/data/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/sessions/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/testing/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/views/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/logs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | import forms from '@tailwindcss/forms'
2 | import defaultTheme from 'tailwindcss/defaultTheme'
3 |
4 | /** @type {import('tailwindcss').Config} */
5 | export default {
6 | darkMode: 'media',
7 | content: [
8 | './vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',
9 | './storage/framework/views/*.php',
10 | './resources/views/**/*.blade.php',
11 | './resources/js/**/*.tsx'
12 | ],
13 |
14 | theme: {
15 | container: {
16 | center: true,
17 | padding: '2rem',
18 | screens: {
19 | '2xl': '1400px'
20 | }
21 | },
22 | extend: {
23 | colors: {
24 | 'gradient-start-light': '#cbd5e1',
25 | 'gradient-end-light': '#94a3b8',
26 | 'gradient-start-dark': '#4b5563',
27 | 'gradient-end-dark': '#1f2937',
28 | border: 'hsl(var(--border))',
29 | input: 'hsl(var(--input))',
30 | ring: 'hsl(var(--ring))',
31 | background: 'hsl(var(--background))',
32 | foreground: 'hsl(var(--foreground))',
33 | primary: {
34 | DEFAULT: 'hsl(var(--primary))',
35 | foreground: 'hsl(var(--primary-foreground))'
36 | },
37 | secondary: {
38 | DEFAULT: 'hsl(var(--secondary))',
39 | foreground: 'hsl(var(--secondary-foreground))'
40 | },
41 | destructive: {
42 | DEFAULT: 'hsl(var(--destructive))',
43 | foreground: 'hsl(var(--destructive-foreground))'
44 | },
45 | muted: {
46 | DEFAULT: 'hsl(var(--muted))',
47 | foreground: 'hsl(var(--muted-foreground))'
48 | },
49 | accent: {
50 | DEFAULT: 'hsl(var(--accent))',
51 | foreground: 'hsl(var(--accent-foreground))'
52 | },
53 | popover: {
54 | DEFAULT: 'hsl(var(--popover))',
55 | foreground: 'hsl(var(--popover-foreground))'
56 | },
57 | card: {
58 | DEFAULT: 'hsl(var(--card))',
59 | foreground: 'hsl(var(--card-foreground))'
60 | }
61 | },
62 | borderRadius: {
63 | lg: `var(--radius)`,
64 | md: `calc(var(--radius) - 2px)`,
65 | sm: 'calc(var(--radius) - 4px)'
66 | },
67 | fontFamily: {
68 | // sans: ['Inter', ...defaultTheme.fontFamily.sans],
69 | sans: ['"GeistVF", sans-serif'],
70 | mono: [
71 | 'GeistMonoVF',
72 | 'JetBrainsMono',
73 | ...defaultTheme.fontFamily.mono
74 | ]
75 | },
76 | keyframes: {
77 | 'accordion-down': {
78 | from: { height: 0 },
79 | to: { height: 'var(--radix-accordion-content-height)' }
80 | },
81 | 'accordion-up': {
82 | from: { height: 'var(--radix-accordion-content-height)' },
83 | to: { height: 0 }
84 | }
85 | },
86 | animation: {
87 | 'accordion-down': 'accordion-down 0.2s ease-out',
88 | 'accordion-up': 'accordion-up 0.2s ease-out'
89 | }
90 | }
91 | },
92 |
93 | plugins: [forms, require('tailwindcss-animate')]
94 | }
95 |
--------------------------------------------------------------------------------
/tests/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/backupdb/0ba1b2ad547ede4e2ec9b241b9d7c7f3305f304e/tests/.DS_Store
--------------------------------------------------------------------------------
/tests/Feature/Auth/AuthenticationTest.php:
--------------------------------------------------------------------------------
1 | get('/login');
7 |
8 | $response->assertStatus(200);
9 | });
10 |
11 | test('users can authenticate using the login screen', function () {
12 | $user = User::factory()->create();
13 |
14 | $response = $this->post('/login', [
15 | 'email' => $user->email,
16 | 'password' => 'password',
17 | ]);
18 |
19 | $this->assertAuthenticated();
20 | $response->assertRedirect(route('dashboard', absolute: false));
21 | });
22 |
23 | test('users can not authenticate with invalid password', function () {
24 | $user = User::factory()->create();
25 |
26 | $this->post('/login', [
27 | 'email' => $user->email,
28 | 'password' => 'wrong-password',
29 | ]);
30 |
31 | $this->assertGuest();
32 | });
33 |
34 | test('users can logout', function () {
35 | $user = User::factory()->create();
36 |
37 | $response = $this->actingAs($user)->post('/logout');
38 |
39 | $this->assertGuest();
40 | $response->assertRedirect('/');
41 | });
42 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/EmailVerificationTest.php:
--------------------------------------------------------------------------------
1 | unverified()->create();
10 |
11 | $response = $this->actingAs($user)->get('/verify-email');
12 |
13 | $response->assertStatus(200);
14 | });
15 |
16 | test('email can be verified', function () {
17 | $user = User::factory()->unverified()->create();
18 |
19 | Event::fake();
20 |
21 | $verificationUrl = URL::temporarySignedRoute(
22 | 'verification.verify',
23 | now()->addMinutes(60),
24 | ['id' => $user->id, 'hash' => sha1($user->email)]
25 | );
26 |
27 | $response = $this->actingAs($user)->get($verificationUrl);
28 |
29 | Event::assertDispatched(Verified::class);
30 | expect($user->fresh()->hasVerifiedEmail())->toBeTrue();
31 | $response->assertRedirect(route('dashboard', absolute: false).'?verified=1');
32 | });
33 |
34 | test('email is not verified with invalid hash', function () {
35 | $user = User::factory()->unverified()->create();
36 |
37 | $verificationUrl = URL::temporarySignedRoute(
38 | 'verification.verify',
39 | now()->addMinutes(60),
40 | ['id' => $user->id, 'hash' => sha1('wrong-email')]
41 | );
42 |
43 | $this->actingAs($user)->get($verificationUrl);
44 |
45 | expect($user->fresh()->hasVerifiedEmail())->toBeFalse();
46 | });
47 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/PasswordConfirmationTest.php:
--------------------------------------------------------------------------------
1 | create();
7 |
8 | $response = $this->actingAs($user)->get('/confirm-password');
9 |
10 | $response->assertStatus(200);
11 | });
12 |
13 | test('password can be confirmed', function () {
14 | $user = User::factory()->create();
15 |
16 | $response = $this->actingAs($user)->post('/confirm-password', [
17 | 'password' => 'password',
18 | ]);
19 |
20 | $response->assertRedirect();
21 | $response->assertSessionHasNoErrors();
22 | });
23 |
24 | test('password is not confirmed with invalid password', function () {
25 | $user = User::factory()->create();
26 |
27 | $response = $this->actingAs($user)->post('/confirm-password', [
28 | 'password' => 'wrong-password',
29 | ]);
30 |
31 | $response->assertSessionHasErrors();
32 | });
33 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/PasswordResetTest.php:
--------------------------------------------------------------------------------
1 | get('/forgot-password');
9 |
10 | $response->assertStatus(200);
11 | });
12 |
13 | test('reset password link can be requested', function () {
14 | Notification::fake();
15 |
16 | $user = User::factory()->create();
17 |
18 | $this->post('/forgot-password', ['email' => $user->email]);
19 |
20 | Notification::assertSentTo($user, ResetPassword::class);
21 | });
22 |
23 | test('reset password screen can be rendered', function () {
24 | Notification::fake();
25 |
26 | $user = User::factory()->create();
27 |
28 | $this->post('/forgot-password', ['email' => $user->email]);
29 |
30 | Notification::assertSentTo($user, ResetPassword::class, function ($notification) {
31 | $response = $this->get('/reset-password/'.$notification->token);
32 |
33 | $response->assertStatus(200);
34 |
35 | return true;
36 | });
37 | });
38 |
39 | test('password can be reset with valid token', function () {
40 | Notification::fake();
41 |
42 | $user = User::factory()->create();
43 |
44 | $this->post('/forgot-password', ['email' => $user->email]);
45 |
46 | Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) {
47 | $response = $this->post('/reset-password', [
48 | 'token' => $notification->token,
49 | 'email' => $user->email,
50 | 'password' => 'password',
51 | 'password_confirmation' => 'password',
52 | ]);
53 |
54 | $response
55 | ->assertSessionHasNoErrors()
56 | ->assertRedirect(route('login'));
57 |
58 | return true;
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/PasswordUpdateTest.php:
--------------------------------------------------------------------------------
1 | create();
8 |
9 | $response = $this
10 | ->actingAs($user)
11 | ->from('/profile')
12 | ->put('/password', [
13 | 'current_password' => 'password',
14 | 'password' => 'new-password',
15 | 'password_confirmation' => 'new-password',
16 | ]);
17 |
18 | $response
19 | ->assertSessionHasNoErrors()
20 | ->assertRedirect('/profile');
21 |
22 | $this->assertTrue(Hash::check('new-password', $user->refresh()->password));
23 | });
24 |
25 | test('correct password must be provided to update password', function () {
26 | $user = User::factory()->create();
27 |
28 | $response = $this
29 | ->actingAs($user)
30 | ->from('/profile')
31 | ->put('/password', [
32 | 'current_password' => 'wrong-password',
33 | 'password' => 'new-password',
34 | 'password_confirmation' => 'new-password',
35 | ]);
36 |
37 | $response
38 | ->assertSessionHasErrors('current_password')
39 | ->assertRedirect('/profile');
40 | });
41 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/RegistrationTest.php:
--------------------------------------------------------------------------------
1 | get('/register');
5 |
6 | $response->assertStatus(200);
7 | });
8 |
9 | test('new users can register', function () {
10 | $response = $this->post('/register', [
11 | 'name' => 'Test User',
12 | 'email' => 'test@example.com',
13 | 'password' => 'password',
14 | 'password_confirmation' => 'password',
15 | ]);
16 |
17 | $this->assertAuthenticated();
18 | $response->assertRedirect(route('dashboard', absolute: false));
19 | });
20 |
--------------------------------------------------------------------------------
/tests/Feature/ExampleTest.php:
--------------------------------------------------------------------------------
1 | get('/');
5 |
6 | $response->assertStatus(200);
7 | });
8 |
--------------------------------------------------------------------------------
/tests/Feature/ProfileTest.php:
--------------------------------------------------------------------------------
1 | create();
7 |
8 | $response = $this
9 | ->actingAs($user)
10 | ->get('/profile');
11 |
12 | $response->assertOk();
13 | });
14 |
15 | test('profile information can be updated', function () {
16 | $user = User::factory()->create();
17 |
18 | $response = $this
19 | ->actingAs($user)
20 | ->patch('/profile', [
21 | 'name' => 'Test User',
22 | 'email' => 'test@example.com',
23 | ]);
24 |
25 | $response
26 | ->assertSessionHasNoErrors()
27 | ->assertRedirect('/profile');
28 |
29 | $user->refresh();
30 |
31 | $this->assertSame('Test User', $user->name);
32 | $this->assertSame('test@example.com', $user->email);
33 | $this->assertNull($user->email_verified_at);
34 | });
35 |
36 | test('email verification status is unchanged when the email address is unchanged', function () {
37 | $user = User::factory()->create();
38 |
39 | $response = $this
40 | ->actingAs($user)
41 | ->patch('/profile', [
42 | 'name' => 'Test User',
43 | 'email' => $user->email,
44 | ]);
45 |
46 | $response
47 | ->assertSessionHasNoErrors()
48 | ->assertRedirect('/profile');
49 |
50 | $this->assertNotNull($user->refresh()->email_verified_at);
51 | });
52 |
53 | test('user can delete their account', function () {
54 | $user = User::factory()->create();
55 |
56 | $response = $this
57 | ->actingAs($user)
58 | ->delete('/profile', [
59 | 'password' => 'password',
60 | ]);
61 |
62 | $response
63 | ->assertSessionHasNoErrors()
64 | ->assertRedirect('/');
65 |
66 | $this->assertGuest();
67 | $this->assertNull($user->fresh());
68 | });
69 |
70 | test('correct password must be provided to delete account', function () {
71 | $user = User::factory()->create();
72 |
73 | $response = $this
74 | ->actingAs($user)
75 | ->from('/profile')
76 | ->delete('/profile', [
77 | 'password' => 'wrong-password',
78 | ]);
79 |
80 | $response
81 | ->assertSessionHasErrors('password')
82 | ->assertRedirect('/profile');
83 |
84 | $this->assertNotNull($user->fresh());
85 | });
86 |
--------------------------------------------------------------------------------
/tests/Pest.php:
--------------------------------------------------------------------------------
1 | in('Feature');
18 |
19 | /*
20 | |--------------------------------------------------------------------------
21 | | Expectations
22 | |--------------------------------------------------------------------------
23 | |
24 | | When you're writing tests, you often need to check that values meet certain conditions. The
25 | | "expect()" function gives you access to a set of "expectations" methods that you can use
26 | | to assert different things. Of course, you may extend the Expectation API at any time.
27 | |
28 | */
29 |
30 | expect()->extend('toBeOne', function () {
31 | return $this->toBe(1);
32 | });
33 |
34 | /*
35 | |--------------------------------------------------------------------------
36 | | Functions
37 | |--------------------------------------------------------------------------
38 | |
39 | | While Pest is very powerful out-of-the-box, you may have some testing code specific to your
40 | | project that you don't want to repeat in every file. Here you can also expose helpers as
41 | | global functions to help you to reduce the number of lines of code in your test files.
42 | |
43 | */
44 |
45 | function something()
46 | {
47 | // ..
48 | }
49 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | toBeTrue();
5 | });
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "module": "ESNext",
5 | "moduleResolution": "bundler",
6 | "jsx": "react-jsx",
7 | "strict": true,
8 | "isolatedModules": true,
9 | "target": "ESNext",
10 | "esModuleInterop": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "noEmit": true,
13 | "paths": {
14 | "@/*": ["./resources/js/*"],
15 | "ziggy-js": ["./vendor/tightenco/ziggy"]
16 | }
17 | },
18 | "include": ["resources/js/**/*.ts", "resources/js/**/*.tsx", "resources/js/**/*.d.ts"]
19 | }
20 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import laravel from 'laravel-vite-plugin';
3 | import react from '@vitejs/plugin-react';
4 |
5 | export default defineConfig({
6 | plugins: [
7 | laravel({
8 | input: 'resources/js/app.tsx',
9 | refresh: true,
10 | }),
11 | react(),
12 | ],
13 | });
14 |
--------------------------------------------------------------------------------