Chore: Upgrade to Laravel 12 + remove Mary and Jetstream dependencies (#141)

* docs: remove requirement for setting APP_KEY manually

* optimize date picker

* clean up modals

* spot light working

* reorganization

* add lazy load

* wip

* remove filament

* styling
This commit is contained in:
hackerESQ
2025-09-26 17:41:28 -05:00
committed by GitHub
parent 910d426ad4
commit e6f38d9481
146 changed files with 5443 additions and 3909 deletions
File diff suppressed because one or more lines are too long
@@ -1,9 +0,0 @@
<div class="min-h-screen flex flex-col sm:justify-center items-center pt-6 sm:pt-0 bg-gray-100 dark:bg-gray-900">
<div>
{{ $logo }}
</div>
<div class="w-full sm:max-w-md mt-6 px-6 py-4 bg-white dark:bg-gray-800 shadow-md overflow-hidden sm:rounded-lg">
{{ $slot }}
</div>
</div>
@@ -1,17 +0,0 @@
@props(['id' => null, 'maxWidth' => null])
<x-ib-livewire-modal :id="$id" :maxWidth="$maxWidth" {{ $attributes }} :showClose="false">
<div class="p-2">
<div class="text-lg font-medium text-gray-900 dark:text-gray-100">
{{ $title }}
</div>
<div class="mt-4 text-sm text-gray-600 dark:text-gray-400">
{{ $content }}
</div>
</div>
<div class="flex flex-row items-center justify-end mt-3 p-2 text-end">
{{ $footer }}
</div>
</x-ib-livewire-modal>
@@ -5,7 +5,7 @@
</x-forms.section-title>
<div class="mt-5 md:mt-0 md:col-span-2">
<div class="px-4 py-5 sm:p-6 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6 bg-base-100 shadow sm:rounded-lg">
{{ $content }}
</div>
</div>
@@ -15,7 +15,7 @@
</span>
@once
<x-dialog-modal wire:model.live="confirmingPassword">
<x-ui.dialog-modal wire:model.live="confirmingPassword">
<x-slot name="title">
{{ $title }}
</x-slot>
@@ -25,7 +25,7 @@
<div class="mt-4" x-data="{}" x-on:confirming-password.window="setTimeout(() => $refs.confirmable_password.focus(), 250)">
<x-input type="password" class="mt-1 block w-3/4" placeholder="{{ __('Password') }}" autocomplete="current-password"
<x-ui.input type="password" class="mt-1 block w-3/4" placeholder="{{ __('Password') }}" autocomplete="current-password"
x-ref="confirmable_password"
wire:model="confirmablePassword"
wire:keydown.enter="confirmPassword"
@@ -36,13 +36,13 @@
</x-slot>
<x-slot name="footer">
<x-button class="btn-outline" wire:click="stopConfirmingPassword" wire:loading.attr="disabled">
<x-ui.button class="btn-outline" wire:click="stopConfirmingPassword" wire:loading.attr="disabled">
{{ __('Cancel') }}
</x-button>
</x-ui.button>
<x-button type="submit" class="ms-3" dusk="confirm-password-button" wire:click="confirmPassword" wire:loading.attr="disabled">
<x-ui.button type="submit" class="ms-3" dusk="confirm-password-button" wire:click="confirmPassword" wire:loading.attr="disabled">
{{ $button }}
</x-button>
</x-ui.button>
</x-slot>
</x-dialog-modal>
</x-ui.dialog-modal>
@endonce
@@ -8,14 +8,14 @@
<div class="mt-5 md:mt-0 md:col-span-2">
<form wire:submit="{{ $submit }}">
<div class="px-4 py-5 bg-white dark:bg-gray-800 sm:p-6 shadow {{ isset($actions) ? 'sm:rounded-tl-md sm:rounded-tr-md' : 'sm:rounded-md' }}">
<div class="px-4 py-5 bg-base-100 sm:p-6 shadow {{ isset($actions) ? 'sm:rounded-tl-md sm:rounded-tr-md' : 'sm:rounded-md' }}">
<div class="grid grid-cols-6 gap-6">
{{ $form }}
</div>
</div>
@if (isset($actions))
<div class="flex items-center justify-end px-4 py-3 bg-gray-50 dark:bg-gray-800 text-end sm:px-6 shadow sm:rounded-bl-md sm:rounded-br-md">
<div class="flex items-center justify-end px-4 py-3 bg-base-100 text-end sm:px-6 shadow sm:rounded-bl-md sm:rounded-br-md">
{{ $actions }}
</div>
@endif
@@ -1,27 +0,0 @@
@php
if (isset($percent)) {
$isUp = $percent > 0;
} else {
$isUp = $costBasis <= $marketValue;
$percent = $costBasis ? (($marketValue - $costBasis) / $costBasis) * 100 : 0;
}
@endphp
@if(!empty($percent))
<x-badge class="badge-sm {{ $isUp ? 'badge-success' : 'badge-error' }} badge-outline ml-2">
<x-slot:value>
{!! $isUp ? '&#9650;' :'&#9660;' !!}
{{ Number::percentage(
$percent,
$percent < 1 ? 2 : 0
) }}
</x-slot:value>
</x-badge>
@endif
@@ -1,52 +0,0 @@
<a href="/" title="Investbrain">
<svg width="100%" height="100%" id="Layer_1" class="fill-current" data-name="Layer 1" viewBox="0 0 1001 783" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" >
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M553.875,632.571L567.884,627.131C567.884,627.131 588.94,642.044 611.341,650.542C660.03,669.007 666.181,693.68 670.67,711.697C671.541,715.201 672.368,718.512 673.431,721.293C679.103,736.17 685.326,746.904 694.882,758.003L737.893,737.748C730.866,729.455 721.087,714.1 721.273,693.007C721.419,676.837 731.456,663.936 740.313,652.55C749.261,641.048 756.99,631.115 754.689,619.792C754.428,618.501 750.205,606.681 683.457,589.378C664.971,584.588 632.955,577.931 632.955,577.931C632.955,577.931 635.967,564.803 636.504,564.91C650.287,567.669 668.765,571.64 687.293,576.443C757.295,594.586 767.837,608.754 769.682,617.83C773.076,634.571 762.843,647.722 752.948,660.444C744.551,671.243 736.616,681.44 736.507,693.555C736.275,719.609 754.703,734.781 754.889,734.933L762.945,741.43L691.292,775.182L687.22,770.803C674.012,756.601 666.101,743.826 659.014,725.231C657.68,721.736 656.764,718.071 655.801,714.191C651.633,697.476 641.744,677.506 600.566,661.887C573.409,651.585 553.875,632.571 553.875,632.571Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M469.894,617.03C491.9,625.608 537.785,632.498 578.066,616.912C606.757,605.811 625.69,585.647 634.343,556.967L635.932,544.895L650.678,549.582L650.547,550.556L649.213,560.348C639.567,592.821 617.208,616.664 584.546,629.301C557.593,639.728 525.875,641.415 498.98,637.874C484.116,635.917 475.069,632.909 464.77,628.35L469.894,617.03Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M756.363,647.659C766.597,652.226 735.904,647.812 749.88,647.831C752.018,647.834 754.181,647.777 756.363,647.659ZM756.363,647.659L759.121,630.608C776.312,645.1 814.041,614.388 822.007,607.977C847.271,587.646 859.429,573.432 865.582,531.56L857.892,525.97L871.854,519.291L871.902,520.701L871.646,533.167C868.374,581.138 854.724,595.681 826.924,619.726C805.922,637.888 779.998,646.378 756.363,647.659Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M278.425,160.523C277.934,160.447 277.438,160.37 276.948,160.286C277.44,160.359 277.93,160.439 278.425,160.523Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M56.427,281.384L53.276,278.254C53.915,253.987 64.477,230.809 81.468,211.975C77.818,216.38 74.378,221.228 71.199,226.564C59.438,246.327 55.735,264.917 56.427,281.384ZM125.55,179.848C146.913,170.054 171.349,165.376 196.488,168.027C196.773,169.34 196.992,170.047 196.992,170.047L196.819,170.025C194.125,169.67 160.165,165.575 125.55,179.848ZM876.465,148.788C875.253,140.191 872.604,131.249 868.55,122.317C872.79,131.336 875.507,140.302 876.465,148.788ZM636.968,67.078C632.455,59.767 626.37,52.471 618.591,45.74C627.058,52.458 633.412,59.75 636.968,67.078ZM830.233,73.639C814.235,60.456 794.248,49.212 770.413,41.637C761.569,38.826 752.892,36.944 744.475,35.836C730.805,34.037 717.803,34.272 705.832,35.868C719.737,33.548 733.39,33.475 746.563,35.21C778.577,39.424 807.696,54.337 830.233,73.639Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M546.485,261.415C546.455,261.429 546.424,261.444 546.394,261.458C546.389,261.461 546.389,261.461 546.385,261.46L546.485,261.415ZM546.485,261.415C547.832,260.79 549.176,260.186 550.513,259.608L546.485,261.415Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M951.448,338.201C967.066,346.748 978.94,360.259 984.357,371.461C991.028,385.267 1009.88,464.746 938.38,509.182C874.197,549.074 824.465,524.364 805.506,511.387C784.609,543.131 735.375,571.199 677.333,565.042C620.575,558.985 591.016,530.312 576.286,507.76C561.52,528.554 541.685,537.599 526.615,541.516C511.159,545.534 494.383,545.742 479.342,542.424C490.897,565.908 498.729,604.571 467.806,641.145C445.634,667.37 414.398,675.189 392.099,677.128C370.311,679.025 349.702,675.811 337.482,671.143C324.069,682.794 288.704,704.331 243.88,698.43C233.085,697.009 221.742,694 210.023,688.874C158.439,666.305 147.126,623.344 154.77,594.547C111.783,593.541 77.23,581.082 51.934,557.44C20.86,528.397 4.976,481.26 9.445,431.35C14.204,378.198 55.606,354.896 74.754,346.889C60.508,328.89 30.467,280.339 64.437,223.27C98.829,165.496 163.458,161.805 188.094,162.668C186.255,144.263 189.011,101.243 245.926,69.359C313.249,31.644 373.035,54.386 397.781,70.781C414.353,45.661 452.387,10.329 510.461,7.925C585.849,4.8 622.364,35.827 637.949,55.854C660.603,36.887 713.288,16.387 772.778,35.297C839.209,56.412 875.616,104.13 883.278,143.714C947.161,162.729 985.435,206.49 988.544,264.393C990.859,307.537 971.373,327.501 951.448,338.201ZM929.36,498.056C991.957,459.153 975.932,387.913 970.382,376.422C965.636,366.6 951.481,350.075 931.943,344.8L911.518,339.288L930.807,332.122C953.179,323.814 975.727,309.291 973.33,264.596C971.652,233.374 956.575,177.653 874.439,155.23L869.539,153.891L868.907,149.406C863.81,113.255 830.649,67.872 768.052,47.976C704.505,27.778 653.597,58.332 643.204,71.132L636.222,79.733L630.312,70.164C620.597,54.426 589.582,18.173 511.669,21.398C440.094,24.362 408.415,81.914 407.103,84.363L402.557,92.834L394.883,85.976C379.908,72.608 321.954,42.965 254.369,80.828C189.472,117.183 204.179,167.969 204.337,168.478L207.359,178.33L196.014,176.705C192.739,176.258 115.819,166.267 77.965,229.863C39.999,293.638 91.82,344.913 92.351,345.421L100.424,353.227L89.31,356.328C86.907,357.008 29.915,373.837 24.63,432.853C20.503,478.949 34.782,522.122 62.831,548.336C87.022,570.947 121.342,581.965 164.831,581.071L176.905,580.826L172.295,590.588C161.421,613.62 167.914,655.605 216.409,676.818C274.503,702.221 322.493,666.638 329.535,658.577L333.699,653.813L339.597,657.183C352.785,664.72 419.879,674.98 455.506,632.841C490.702,591.211 468.011,546.744 456.297,533.124L432.423,505.365L466.09,523.416C481.09,531.455 502.99,533.478 521.89,528.564C536.577,524.747 556.747,514.997 569.395,490.245L576.355,476.635L583.311,490.646C593.267,510.709 618.898,545.364 678.656,551.649C737.765,557.918 782.891,523.943 796.055,497.816L800.455,489.081L808.318,496.037C816.887,503.614 862.973,539.314 929.36,498.056Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M164.45,581.093C168.821,580.604 195.946,573.542 211.278,553.997C221.78,540.606 224.635,523.998 219.76,504.639C219.76,504.639 247.605,532.462 221.626,564.658C202.884,586.185 171.934,594.399 165.717,594.554L164.791,581.073C164.703,581.077 164.591,581.078 164.45,581.093ZM164.45,581.093C164.448,581.093 164.447,581.094 164.445,581.094C164.447,581.094 164.448,581.093 164.45,581.093Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M213.575,553.555C222.582,551.317 231.34,551.047 239.325,552.226C255.233,554.572 268.058,562.655 273.619,572.298C273.619,572.298 245.37,554.286 218.136,566.552L213.575,553.555Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M310.41,312.188C362.408,280.533 430.339,286.541 475.605,326.802L464.987,336.16C425.111,300.696 365.213,295.443 319.316,323.38C275.107,350.295 256.595,417.442 280.48,464.26C307.052,516.356 357.867,539.991 394.332,536.42C394.332,536.42 358.776,552.43 324.184,531.648C299.807,517 279.907,495.504 266.63,469.474C239.543,416.376 259.999,342.877 310.41,312.188Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M330.494,520.536C338.379,526.429 342.731,534.458 344.7,543.023C348.021,570.08 326.463,580.957 326.463,580.957C333.323,569.603 338.064,541.076 322.267,529.275L330.494,520.536Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M170.498,451.168C205.064,437.661 243.653,440.477 276.359,458.882L268.492,470.196C240.229,454.292 206.893,451.865 177.027,463.541C153.301,472.81 139.19,488.122 137.509,498.456C137.509,498.456 134.627,468.393 170.498,451.168Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M122.051,418.399C124.266,425.291 153.678,451.572 184.056,447.87L186.251,459.326C180.771,459.993 175.279,459.883 169.892,459.174C162.413,458.189 155.13,456.047 148.357,453.205C119.151,440.422 122.051,418.399 122.051,418.399Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M83.89,343.578C93.152,340.443 119.954,335.902 149.393,342.543C189.622,353.407 200.664,388.305 200.664,388.305C157.717,337.275 90.386,355.973 89.732,356.197L83.89,343.578Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M403.057,462.599C412.109,479.736 430.231,504.115 465.183,522.924L466.14,523.442L458.465,534.863L457.586,534.391C419.225,513.745 399.276,486.879 389.295,467.98C380.508,451.336 378.406,434.828 379.826,419.97C384.383,385.186 414.727,373.954 414.727,373.954C402.573,387.065 383.727,425.985 403.057,462.599Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M322.397,408.686C322.397,408.686 360.323,391.713 392.009,422.14L382.306,429.591C369.382,416.512 339.485,407.613 322.397,408.686Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M690.853,274.028C735.995,311.348 753.013,373.417 740.587,407.967C734.08,426.056 723.599,439.419 712.073,449.263C685.57,471.905 649.16,462.799 649.16,462.799C651.515,462.516 707.46,455.197 726.054,403.494C736.835,373.524 720.838,316.972 680.654,283.744C656.633,263.884 614.846,244.873 552.154,267.086C489.758,289.191 475.391,326.522 474.322,353.951C472.725,395.045 499.768,434.915 514.438,442.054C514.438,442.054 481.579,436.735 467.588,400.693C461.928,385.953 458.458,369.521 459.1,353.048C460.315,321.737 476.396,279.231 546.1,254.536C616.21,229.702 663.52,251.425 690.853,274.028Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M574.041,405.452C577.929,399.868 582.591,394.925 588.017,390.626C622.566,366.135 666.154,382.688 666.154,382.688C627.495,382.845 600.885,392.975 587.077,412.802C566.225,442.734 580.371,485.117 583.223,490.479L569.482,495.902C565.551,488.513 549.669,440.434 574.041,405.452Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M586.976,325.57C586.976,325.57 612.868,328.978 618.672,355.583C621.052,372.019 615.594,386.416 603.3,396.116L594.44,387.377C606.107,378.168 607.013,365.603 605.724,356.686C603.595,342.023 594.518,329.351 586.976,325.57Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M708.183,440.658C738.696,458.976 721.687,492.93 721.687,492.93C725.772,471.083 707.178,453.368 701.816,450.565L708.183,440.658Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M793.227,434.541L805.895,427.418C827.426,456.566 813.55,497.025 809.929,503.784L796.126,497.678C798.539,493.179 810.551,457.997 793.227,434.541Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M843.106,432.778C872.723,432.517 895.844,420.562 906.531,399.983C906.531,399.983 907.929,428.423 880.172,440.043C869.358,444.009 857.092,446.148 843.754,446.269C837.448,446.323 831.171,445.926 825.002,445.114C790.398,440.559 759.118,422.884 743.733,398.136C712.965,348.644 746.651,293.716 782.802,273.093C802.223,262.012 823.22,255.668 842.44,254.262C876.032,253.772 883.4,276.414 883.4,276.414C864.944,262.029 824.51,265.578 791.345,284.501C760.84,301.905 731.064,350.173 757.023,391.933C772.169,416.297 807.583,433.1 843.106,432.778Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M901.023,427.592C919.082,437.647 914.375,459.502 914.375,459.502C914.382,455.074 911.352,449.929 905.845,445.02C898.256,438.251 889.946,435.226 888.088,435.21C888.281,435.213 882.328,424.936 882.328,424.936C886.457,422.572 892.743,423.469 901.023,427.592Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M841.501,320.927C869.016,346.791 923.886,334.774 930.727,332.154L937.161,344.56C930.833,346.984 903.788,353.308 876.093,349.661C844.437,342.756 841.501,320.927 841.501,320.927Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M856.8,381.78C856.8,381.78 854.677,356.846 873.976,346.331C881.356,342.487 889.96,339.571 899.7,338.12L902.276,349.522C873.542,353.801 859.967,372.495 856.8,381.78Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M609.849,376.237C641.704,348.782 649.339,320.22 649.339,320.22C651.29,328.519 647.387,350.181 634.449,367.575C629.624,374.059 623.544,379.948 616.045,384.331L609.849,376.237Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M834.92,234.947C840.865,227.575 848.327,221.775 857.035,217.633C881.755,204.76 908.313,222.518 908.313,222.518C888.901,219.963 862.264,221.245 845.624,241.87L834.92,234.947Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M831.928,192.93C831.928,192.93 855.516,211.969 848.036,239.116C840.815,259.861 823.515,271.361 810.178,275.615L804.608,262.904C806.453,262.317 849.598,247.868 831.928,192.93Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M730.919,99.579C730.919,99.579 763.085,113.201 772.811,151.075C776.7,170.409 776.278,191.455 771.605,214.455C761.626,263.558 702.305,281.445 679.251,280.632L679.359,267.156C693.228,267.644 747.798,255.006 756.611,211.647C765.902,165.932 757.739,130.323 730.919,99.579Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M762.546,160.453C800.164,134.33 864.452,138.777 878.343,142.312L874.583,155.268C864.309,152.659 804.878,148.468 772.188,171.172L762.546,160.453Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M630.258,70.076L643.679,64.083C644.1,64.79 673.076,114.431 662.623,161.021C654.783,197.124 613.774,203.603 613.774,203.603C683.262,160.803 630.799,70.977 630.258,70.076Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M659.485,165.881C663.907,186.681 682.077,207.853 695.622,213.847C695.622,213.847 651.903,202.29 646.659,167.659L659.485,165.881Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M539.576,186.699C541.122,210.599 554.444,240.07 576.078,247.622L570.893,260.15C542.825,250.352 526.242,215.953 524.367,187.035C523.494,173.554 526.287,151.23 536.041,131.967C553.129,100.656 581.582,110.001 581.582,110.001C548.902,118.029 538.098,163.918 539.576,186.699Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M493.292,103.391C493.292,103.391 525.2,108.011 535.526,135.513C538.424,143.235 540.253,152.053 540.204,161.981L527.145,161.553C527.336,122.786 493.632,103.579 493.292,103.391Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M276.948,160.286C277.399,160.303 311.64,161.895 336.502,187.86C344.788,196.516 352.036,207.878 356.639,222.792C374.898,281.945 349.413,306.144 319.144,323.482C309.497,329.013 295.604,330.674 280.619,328.701C269.215,327.2 257.176,323.595 245.876,317.989C231.133,310.674 218.952,300.706 209.701,288.689C189.323,255.458 204.276,225.968 204.276,225.968C204.379,274.859 234.842,297.268 252.991,306.273C276.57,317.97 300.259,318.002 310.581,312.087C335.503,297.81 358.45,279.395 341.951,225.95C326.56,176.089 276.948,160.286 276.948,160.286Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M351.881,137.831C351.881,137.831 334.475,159.829 340.948,195.549L328.057,197.016C320.399,154.765 351.881,137.831 351.881,137.831Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M153.949,295.541C153.949,295.541 171.351,271.232 201.075,276.773C207.935,278.053 215.116,280.367 222.406,284.126L216.118,294.07C184.328,277.698 154.249,295.363 153.949,295.541Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M197.109,163.312C203.351,163.568 225.129,169.866 245.94,182.735C292.963,211.559 273.952,244.742 273.952,244.742C272.306,198.284 202.893,177.207 196.827,176.78L197.109,163.312Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M405.259,76.415C409.176,79.606 443.528,109.023 443.781,158.317C444.017,204.496 406.987,221.615 406.987,221.615C407.205,221.492 428.805,207.829 428.547,157.937C428.324,114.494 398.54,88.97 395.144,86.2L405.259,76.415Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M442.447,165.707C445.65,183.47 472.051,205.798 482.678,207.829C482.678,207.829 438.374,205.199 429.554,167.162L442.447,165.707Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M528.269,208.508L543.276,206.633C543.59,208.225 550.613,245.979 521.528,275.478C495.28,302.101 458.033,314.248 418.869,309.091C417.654,308.932 416.445,308.757 415.226,308.561C378.701,302.768 354.675,288.29 347.06,276.512L360.223,270.105C364.706,277.043 384.206,290.018 417.431,295.288C452.627,300.856 486.324,290.348 509.913,266.423C534.272,241.715 528.331,208.838 528.269,208.508Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M346.454,612.287C346.454,612.287 364.459,630.137 350.49,655.895C346.095,662.684 342.024,666.615 341.627,666.989L330.334,657.619C330.525,657.439 349.385,639.196 346.454,612.287Z"/>
<path {{ $attributes->merge(['class' => 'text-primary']) }} d="M746.169,21.499C756.017,22.795 765.878,25.001 775.478,28.052C846.582,50.652 881.597,100.292 890.828,137.976C955.354,158.86 994.038,204.652 997.236,264.281C999.012,297.296 988.628,322.18 966.359,338.423C979.244,347.991 988.032,359.715 992.343,368.627C996.839,377.939 1002.72,402.821 999.332,429.929C996.187,455.119 983.953,490.419 943.534,515.538C921.02,529.53 898.079,537.216 875.214,538.444C874.524,557.481 866.669,577.821 852.635,596.528C833.067,622.623 803.096,642.513 769.991,651.49C766.899,656.306 763.357,660.861 760.17,664.955C752.191,675.212 745.299,684.072 745.21,693.867C745.015,715.994 760.064,728.832 760.639,729.311L768.69,735.812C770.809,737.518 771.894,740.002 771.599,742.476C771.308,744.951 769.679,747.105 767.227,748.26L695.574,782.012C693.919,782.79 692.07,783.039 690.288,782.805C688.153,782.524 686.117,781.546 684.646,779.964L680.573,775.585C666.603,760.562 658.245,747.077 650.775,727.481C649.287,723.582 648.322,719.717 647.301,715.615C643.246,699.342 639.415,683.975 602.531,669.984C582.529,662.395 566.583,652.959 555.043,641.893C536.815,644.647 517.346,644.794 498.397,642.299C492.15,641.477 486.064,640.365 480.225,638.984C478.528,641.351 476.724,643.657 474.834,645.894C450.844,674.272 417.216,682.715 393.226,684.799C381.934,685.782 370.071,685.527 358.921,684.059C351.917,683.137 345.364,681.766 339.649,680.041C319.088,696.008 283.075,711.345 242.877,706.053C230.564,704.432 218.283,700.97 206.372,695.761C181.07,684.694 162.338,667.791 152.203,646.89C145.306,632.663 142.634,616.673 144.507,601.785C139.806,601.472 135.2,601.017 130.725,600.428C96.286,595.894 67.686,583.183 45.707,562.646C12.903,531.987 -3.897,482.581 0.767,430.49C5.257,380.339 40.511,354.902 62.285,343.855C46.313,320.624 24.457,273.681 56.708,219.503C89.53,164.361 147.812,155.406 179.02,154.885C179.027,132.85 187.435,92.873 241.101,62.81C273.272,44.788 306.984,37.939 341.311,42.462C365.525,45.65 384.1,53.896 395.041,59.951C402.448,50.197 414.871,36.551 433.253,24.573C456.287,9.566 482.031,1.374 509.768,0.225C523.796,-0.356 537.288,0.189 549.873,1.846C595.648,7.874 623.192,27.2 639.177,44.655C661.749,29.219 700.506,15.487 746.169,21.499ZM745.165,29.122C697.38,22.831 657.024,39.885 637.949,55.854C625.287,39.581 598.795,16.044 548.865,9.469C537.348,7.953 524.59,7.339 510.461,7.925C452.387,10.329 414.353,45.661 397.781,70.781C385.798,62.844 365.594,53.414 340.307,50.085C313.364,46.538 280.651,49.905 245.926,69.359C189.011,101.247 186.254,144.267 188.094,162.668C163.458,161.805 98.829,165.496 64.437,223.27C30.466,280.339 60.508,328.89 74.754,346.889C55.606,354.896 14.204,378.198 9.445,431.35C4.976,481.26 20.86,528.397 51.934,557.44C72.538,576.699 99.287,588.534 131.729,592.805C139.117,593.778 146.795,594.358 154.77,594.547C147.126,623.344 158.439,666.305 210.023,688.874C221.738,693.999 233.085,697.009 243.88,698.43C288.704,704.331 324.069,682.794 337.482,671.143C343.311,673.373 351.058,675.269 359.925,676.436C369.639,677.715 380.709,678.122 392.095,677.127C414.398,675.189 445.634,667.37 467.806,641.145C470.947,637.429 473.669,633.693 476.048,629.954C483.187,631.956 491.044,633.576 499.401,634.676C517.749,637.092 538.341,637.065 558.132,633.497C566.971,643.025 581.569,653.745 605.736,662.914C646.914,678.533 651.633,697.476 655.797,714.19C656.764,718.071 657.68,721.736 659.01,725.23C666.101,743.826 674.012,756.601 687.22,770.803L691.292,775.182L762.945,741.43L754.889,734.933C754.703,734.781 736.275,719.609 736.507,693.555C736.616,681.44 744.547,671.243 752.948,660.444C756.865,655.411 760.818,650.309 763.97,644.946C800.832,635.856 828.982,613.927 845.338,592.12C860.499,571.901 867.745,550.109 866.404,530.89C873.884,531.027 881.855,530.414 890.261,528.779C905.055,525.902 921.196,519.861 938.38,509.182C1009.87,464.746 991.028,385.267 984.357,371.461C978.94,360.259 967.066,346.748 951.448,338.201C971.373,327.501 990.859,307.537 988.544,264.393C985.435,206.49 947.161,162.729 883.274,143.713C875.616,104.13 839.209,56.412 772.778,35.297C763.372,32.308 754.136,30.303 745.165,29.122Z"/>
</svg>
<span class="sr-only">Investbrain</span>
</a>
@@ -1,55 +0,0 @@
@props([
'key' => 'modal',
'showClose' => true,
'closeOnEscape' => true,
'title' => null,
'subtitle' => null,
'persistent' => false
])
<div>
@teleport('body')
<dialog
x-data="{ open: false }"
x-on:toggle-{{ $key }}.window="open = !open"
class="relative z-50 w-auto h-auto"
@if($closeOnEscape)
@keydown.window.escape="open = false"
@endif
>
<template x-teleport="body">
<div x-transition.opacity x-show="open" class="fixed top-0 left-0 z-[99] flex items-center justify-center w-full h-full">
<div
@if(!$persistent)
@click="open=false"
@endif
class="absolute inset-0 w-full h-full bg-black bg-opacity-40"
x-show="open"
x-cloak
></div>
<x-card
x-trap.inert.noscroll="open"
:title="$title"
:subtitle="$subtitle"
{{ $attributes->merge(['class' => 'relative transform overflow-hidden rounded-md ext-left shadow-xl w-full sm:w-2/3 lg:w-1/3 m-2 sm:m-0']) }}
x-show="open"
x-cloak
>
@if ($showClose)
<x-button
icon="o-x-mark"
title="{{ __('Close') }}"
class="absolute top-4 right-4 btn-ghost btn-circle btn-sm"
@click="open = false"
/>
@endif
{{ $slot }}
</x-card>
</div>
</template>
</dialog>
@endteleport
</div>
@@ -1,10 +0,0 @@
@props(['title' => ''])
<x-card
{{ $attributes->merge(['class' => 'bg-slate-100 dark:bg-base-200 rounded-lg']) }}
>
<h2 class="text-xl mb-2 flex items-center truncate"> {{ $title }} </h2>
{{ $slot }}
</x-card>
@@ -1,47 +0,0 @@
@props([
'key' => 'drawer',
'showClose' => true,
'closeOnEscape' => true,
'title' => null,
'subtitle' => null
])
<div
x-data="{ open: false }"
x-on:toggle-{{ $key }}.window="open = !open"
x-show="open"
@if($closeOnEscape)
@keydown.window.escape="open = false"
@endif
x-trap="open"
x-bind:inert="!open"
class="fixed inset-0 flex justify-end z-50"
x-transition.opacity
x-cloak
>
<div @click="open = false" class="fixed inset-0 bg-black opacity-50"></div>
<x-card
{{ $attributes->merge(['class' => 'min-h-screen w-full md:w-3/4 xl:w-3/5 rounded-none px-8 transition overflow-y-scroll']) }}
>
@if($title)
<x-slot:title>
{!! strip_tags($title) !!}
</x-slot:title>
@endif
@if($subtitle)
<x-slot:subtitle>
{!! strip_tags($subtitle) !!}
</x-slot:subtitle>
@endif
@if ($showClose)
<x-button icon="o-x-mark" title="{{ __('Close') }}" class="btn-ghost btn-circle btn-sm absolute top-4 right-4 " @click="open = false" />
@endif
{{ $slot }}
</x-card>
</div>
@@ -1,51 +0,0 @@
@props([
'showClose' => true,
'closeOnEscape' => true,
'title' => null,
'subtitle' => null,
'persistent' => false
])
<div>
@teleport('body')
<dialog
{{ $attributes->except('wire:model')->class(["modal"]) }}
x-data="{open: @entangle($attributes->wire('model')).live }"
:class="{'modal-open !animate-none': open}"
:open="open"
@if($closeOnEscape)
@keydown.escape.window = "$wire.{{ $attributes->wire('model')->value() }} = false"
@endif
>
<x-card
:title="$title"
:subtitle="$subtitle"
{{ $attributes->merge(['class' => 'modal-box relative transform overflow-hidden rounded-md ext-left shadow-xl w-full sm:w-2/3 lg:w-1/3 m-2 sm:m-0']) }}
>
@if ($showClose)
<x-button
icon="o-x-mark"
title="{{ __('Close') }}"
class="absolute top-4 right-4 btn-ghost btn-circle btn-sm"
@click="$wire.{{ $attributes->wire('model')->value() }} = false"
/>
@endif
{{ $slot }}
</x-card>
<div class="modal-backdrop" method="dialog">
<a
@if(!$persistent)
@click="$wire.{{ $attributes->wire('model')->value() }} = false"
@endif
type="button"
title="{{ __('Close') }}"
>
{{ __('Close') }}
</a>
</div>
</dialog>
@endteleport
</div>
@@ -1,9 +0,0 @@
<div role="status" class="flex w-full animate-pulse" wire:loading.delay>
<div class="h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4"></div>
<div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px] mb-2.5"></div>
<div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5"></div>
<div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[330px] mb-2.5"></div>
<div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[300px] mb-2.5"></div>
<div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px]"></div>
<span class="sr-only">Loading...</span>
</div>
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="bg-base-200">
<head>
@include('components.partials.head')
</head>
<body class="font-sans antialiased scroll-smooth" x-data="{ sideBarOpen: false }">
@livewire('partials.nav-bar')
@livewire('partials.side-bar')
<main class="py-5 px-6 md:px-8 md:py-0 md:ml-68 mb-14">
{{ $slot }}
</main>
@if(session('toast'))
<script lang="text/javascript">
window.addEventListener('DOMContentLoaded', function () {
window.toast(JSON.parse(@json(session('toast'))))
});
</script>
@endif
<x-ui.toast />
@livewireScripts
</body>
</html>
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
@include('components.partials.head')
</head>
<body class="font-sans antialiased scroll-smooth min-h-screen" x-data="{}">
<main class="">
<x-ui.theme-selector hidden="true" />
{{ $slot }}
</main>
@livewireScripts
</body>
</html>
@@ -0,0 +1,10 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<link rel="icon" href="{{ asset('favicon.svg') }}">
<title>{{ config('app.name', 'Investbrain') }}</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
@livewireStyles
@@ -1,64 +0,0 @@
@props([
'sidebar' => null,
'content' => null,
'footer' => null,
'fullWidth' => false,
'withNav' => false,
'collapseText' => 'Collapse',
'collapseIcon' => 'o-bars-3-bottom-right',
'collapsible' => false,
'url' => route('mary.toogle-sidebar', absolute: false),
])
<main class="{{ !$fullWidth ? 'max-w-screen-2xl' : '' }} w-full mx-auto">
<div class="drawer {{ $sidebar?->attributes['right'] ? 'drawer-end' : '' }} lg:drawer-open">
<input id="{{ $sidebar?->attributes['drawer'] }}" type="checkbox" class="drawer-toggle" />
<div {{ $content->attributes->class(["drawer-content w-full mx-auto p-5 lg:px-10 lg:py-5"]) }}>
{{-- MAIN CONTENT --}}
{{ $content }}
</div>
{{-- SIDEBAR --}}
@if($sidebar)
<div
x-data="{
collapsed: {{ session('mary-sidebar-collapsed', 'false') }},
collapseText: '{{ $collapseText }}',
toggle() {
this.collapsed = !this.collapsed;
fetch('{{ $url }}?collapsed=' + this.collapsed);
this.$dispatch('sidebar-toggled', this.collapsed);
}
}"
@menu-sub-clicked="if(collapsed) { toggle() }"
@class(["drawer-side z-20 lg:z-auto", "top-0 lg:top-[73px] lg:h-[calc(100vh-73px)]" => $withNav])
>
<label for="{{ $sidebar?->attributes['drawer'] }}" aria-label="close sidebar" class="drawer-overlay"></label>
{{-- SIDEBAR CONTENT --}}
<div>
{{ $sidebar }}
{{-- SIDEBAR COLLAPSE --}}
@if($sidebar->attributes['collapsible'])
<x-mary-menu class="hidden !bg-inherit lg:block">
<x-mary-menu-item
@click="toggle"
icon="{{ $sidebar->attributes['collapse-icon'] ?? $collapseIcon }}"
title="{{ $sidebar->attributes['collapse-text'] ?? $collapseText }}" />
</x-mary-menu>
@endif
</div>
</div>
@endif
{{-- END SIDEBAR--}}
</div>
</main>
{{-- FOOTER --}}
@if($footer)
<footer {{ $footer?->attributes->class(["mx-auto w-full", "max-w-screen-2xl" => !$fullWidth ]) }}>
{{ $footer }}
</footer>
@endif
@@ -18,34 +18,31 @@ new class extends Component
// methods
}; ?>
<div class="bg-base-100 border-base-300 border-b sticky top-0 z-10">
<div class="flex justify-between items-center px-7 py-3 gap-4 mx-auto">
<div class="flex flex-0 items-center">
<label for="main-drawer" class="lg:hidden mr-3">
<x-icon name="o-bars-3" class="cursor-pointer" />
</label>
<div class="hidden md:block" style="height:2.5em">
<x-application-logo />
</div>
<nav class="z-10 p-5 ml-0 md:ml-68 md:border-0 border-b border-zinc-200 dark:border-zinc-800">
<div class="flex flex-wrap justify-between items-center">
</div>
<div class="flex flex-1 justify-center" x-data>
<x-spotlight
shortcut="slash"
search-text="{{ __('Search holdings, portfolios, or anything else...') }}"
no-results-text="{{ __('Darn! Nothing found for that search.') }}"
<div class="flex">
<x-ui.button
aria-controls="drawer-navigation"
title="{{ __('Toggle Sidebar') }}"
class="btn-circle btn-ghost btn-sm block md:hidden"
icon="o-bars-3"
@click="sideBarOpen = true"
/>
<x-button
@click.stop="$dispatch('mary-search-open')"
class="btn-sm flex-1 justify-start md:flex-none"
<div class="ml-3 w-8 hidden sm:block md:hidden"> <x-ui.logo /> </div>
</div>
<div>
<x-ui.button
@click.stop="$dispatch('toggle-spotlight')"
class="btn-sm btn-ghost bg-base-300 flex-1 justify-start md:flex-none border-none"
>
<x-slot:label>
<span class="flex items-center text-gray-400">
<x-icon name="o-magnifying-glass" class="mr-2" />
<x-ui.icon name="o-magnifying-glass" class="mr-2" />
<span class=" truncate hidden sm:block">
@lang('Click or press :key to search', ['key' => '<kbd class="kbd kbd-sm">/</kbd>'])
</span>
@@ -54,35 +51,41 @@ new class extends Component
</span>
</span>
</x-slot:label>
</x-button>
</x-ui.button>
<x-ui.spotlight
search-text="{{ __('Search holdings, portfolios, or anything else...') }}"
no-results-text="{{ __('Darn! Nothing found for that search.') }}"
/>
</div>
<div class="flex flex-0 items-center gap-4">
<x-button
<x-ui.button
title="{{ __('Documentation') }}"
icon="o-book-open"
class="btn-circle btn-ghost btn-sm"
link="https://github.com/investbrainapp/investbrain"
external
>
</x-button>
</x-ui.button>
<x-button
<x-ui.button
title="{{ __('We\'re open source!') }}"
class="btn-circle btn-ghost btn-sm"
link="https://github.com/investbrainapp/investbrain"
external
>
<x-github-icon />
</x-button>
<x-social.github-icon />
</x-ui.button>
<x-theme-toggle
<x-ui.theme-selector
id="theme-selector"
title="{{ __('Toggle Theme') }}"
class="btn-circle btn-ghost btn-sm"
darkTheme="business"
lightTheme="corporate"
/>
</div>
</div>
</div>
</nav>
@@ -19,76 +19,97 @@ new class extends Component
}; ?>
<div class="
flex
flex-col
!transition-all
!duration-100
ease-out
overflow-x-hidden
overflow-y-auto
h-screen
lg:h-[calc(100vh-73px)]
bg-base-100
lg:bg-inherit
{{ session('mary-sidebar-collapsed') == 'true' ? 'w-[70px] [&>*_summary::after]:hidden [&_.mary-hideable]:hidden [&_.display-when-collapsed]:block [&_.hidden-when-collapsed]:hidden' : null }}
{{ session('mary-sidebar-collapsed') != 'true' ? 'w-[270px] [&>*_summary::after]:block [&_.mary-hideable]:block [&_.hidden-when-collapsed]:block [&_.display-when-collapsed]:hidden' : null }}
">
<div class="flex-1">
<x-menu activate-by-route>
<div
aria-label="Sidebar"
style="background-image: url('{{ asset('images/noise.svg') }}')"
class="
h-full
bg-base-300
border-r
border-base-100
fixed
top-0
left-0
z-50
md:w-68
w-3/4
transition-transform
-translate-x-full
md:translate-x-0
"
:class="{'translate-x-0': sideBarOpen, '-translate-x-full': !sideBarOpen}"
x-data="{
responsiveSidebar() {
if (window.innerWidth >= 768) {
this.sideBarOpen = true
return;
}
this.sideBarOpen = false
}
}"
@resize.window="responsiveSidebar"
@keyup.escape.window="sideBarOpen = false"
>
<template x-teleport="body">
<div
aria-label="Overlay"
class="block md:hidden z-10 fixed w-screen h-screen inset-0 bg-black/20 backdrop-blur-sm"
x-on:click="sideBarOpen=false"
x-show="sideBarOpen"
x-cloak
></div>
</template>
<div class="h-full px-1 overflow-y-auto flex flex-col ">
<x-menu-item title="{{ __('Dashboard') }}" icon="o-home" link="{{ route('dashboard') }}" />
<x-menu-sub title="{{ __('Portfolios') }}" icon="o-document-duplicate">
@foreach (auth()->user()->portfolios as $portfolio)
<x-menu-item icon="o-document" link="{{ route('portfolio.show', ['portfolio' => $portfolio->id ]) }}" >
<x-slot:title>
{{ $portfolio->title }}
@if($portfolio->wishlist)
<x-badge value="{{ __('Wishlist') }}" class="badge-secondary badge-sm ml-2" />
@endif
</x-slot:title>
</x-menu-item>
@endforeach
<div class="w-10 m-5"> <x-ui.logo /> </div>
<x-menu-item title="{{ __('Create Portfolio') }}" icon="o-document-plus" link="{{ route('portfolio.create') }}" />
</x-menu-sub>
<x-menu-item title="{{ __('Transactions') }}" icon="o-banknotes" link="{{ route('transaction.index') }}" />
{{-- <x-menu-item title="{{ __('Reporting') }}" icon="o-chart-bar-square" link="####" /> --}}
<x-ui.menu class="space-y-2 text-wrap w-full overflow-x-hidden" activate-by-route="true">
<x-ui.menu-item icon="o-home" title="{{ __('Dashboard') }}" link="{{ route('dashboard') }}" class="font-medium text-md" />
</x-menu>
@foreach (auth()->user()->portfolios as $portfolio)
<x-ui.menu-item
:title="$portfolio->title"
icon="o-document"
:badge="$portfolio->wishlist ? __('Wishlist') : null"
badge-classes="badge-secondary badge-outline"
link="{{ route('portfolio.show', ['portfolio' => $portfolio->id ]) }}"
class="font-medium text-md"
/>
@endforeach
<x-ui.menu-item icon="o-document-plus" title="{{ __('Create Portfolio') }}" link="{{ route('portfolio.create') }}" class="font-medium text-md" />
</div>
<div class="px-3">
<x-section-border />
<x-ui.menu-item icon="o-banknotes" title="{{ __('Transactions') }}" link="{{ route('transaction.index') }}" class="font-medium text-md" />
</x-ui.menu>
<div class="flex-1"></div>
@php
$user = auth()->user();
@endphp
<x-list-item :item="$user" avatar="profile_photo_url" value="name" sub-value="email" no-separator no-hover class="mb-3 !-mt-3 rounded">
<x-ui.list-item :item="$user" avatar="profile_photo_url" value="name" sub-value="email" no-separator no-hover class="rounded mb-2">
<x-slot:actions>
<x-dropdown>
<x-ui.dropdown>
<x-slot:trigger>
<x-button icon="o-cog-6-tooth" class="btn-circle btn-ghost btn-xs" />
<x-ui.button icon="o-cog-6-tooth" class="btn-circle btn-ghost btn-sm relative transition-transform focus:rotate-90" />
</x-slot:trigger>
<x-menu-item title="{{ __('Manage Profile') }}" icon="o-user" link="{{ @route('profile.show') }}" />
<x-menu-item title="{{ __('API Tokens') }}" icon="o-command-line" link="{{ @route('api-tokens.index') }}" />
<x-menu-item title="{{ __('Import / Export Data') }}" icon="o-cloud-arrow-down" link="{{ @route('import-export') }}" />
<x-ui.menu-item title="{{ __('Manage Profile') }}" icon="o-user" link="{{ @route('profile.show') }}" />
<x-ui.menu-item title="{{ __('API Tokens') }}" icon="o-command-line" link="{{ @route('api-tokens.index') }}" />
<x-ui.menu-item title="{{ __('Import / Export Data') }}" icon="o-cloud-arrow-down" link="{{ @route('import-export') }}" />
<x-section-border class="py-1" />
<x-ui.section-border class="py-1" />
<x-menu-item title="{{ __('Log Out') }}" icon="o-power" onclick="event.preventDefault(); document.getElementById('logout').submit();" />
<x-ui.menu-item title="{{ __('Log Out') }}" icon="o-power" onclick="event.preventDefault(); document.getElementById('logout').submit();" />
<form id="logout" action="{{ route('logout') }}" method="POST" style="display: none;">
@csrf
</form>
</x-dropdown>
</x-ui.dropdown>
</x-slot:actions>
</x-list-item>
</div>
</x-ui.list-item>
</div>
</div>
@@ -1,16 +1,16 @@
<div>
@if(!empty(config('services.enabled_login_providers')))
@foreach(explode(',', config('services.enabled_login_providers')) as $provider)
<x-button
<x-ui.button
link="{{ route('oauth.redirect', ['provider' => $provider]) }}"
class="btn-sm btn-block my-1"
class="btn-sm btn-block my-1 text-white"
style='background-color: {{ config("services.$provider.color") }}'
no-wire-navigate
>
@include("components.$provider-icon")
@include("components.social.$provider-icon")
{{ __('Login with') }} {{ config("services.$provider.name") }}
</x-button>
</x-ui.button>
@endforeach
@endif
</div>

Before

Width:  |  Height:  |  Size: 472 B

After

Width:  |  Height:  |  Size: 472 B

Before

Width:  |  Height:  |  Size: 871 B

After

Width:  |  Height:  |  Size: 871 B

Before

Width:  |  Height:  |  Size: 582 B

After

Width:  |  Height:  |  Size: 582 B

Before

Width:  |  Height:  |  Size: 676 B

After

Width:  |  Height:  |  Size: 676 B

@@ -1,27 +1,26 @@
<?php
use Mary\Traits\Toast;
use App\Models\AiChat;
use App\Models\Holding;
use Illuminate\Database\Eloquent\Model;
use Livewire\Volt\Component;
use OpenAI\Factory;
use OpenAI\Responses\StreamResponse;
new class extends Component {
use Toast;
new class extends Component
{
// props
public Model $chatable;
public string $system_prompt = 'You are an investment portfolio assistant providing advice to an investor. Use the following information to provide relevant recommendations. Use the words \'likely\' or \'may\' instead of concrete statements (except for obvious statements of fact or common sense). Use github style markdown for any formatting.';
public array $suggested_prompts = [];
public array $messages = [];
public ?string $prompt = null;
public ?string $answer = null;
public bool $streaming = false;
// methods
public function mount()
{
@@ -33,10 +32,11 @@ new class extends Component {
// prevent spam
if ($this->isRateLimited() || $this->streaming) {
array_push($this->messages, [
'role' => 'assistant',
'content' => __('Hang on! You\'re doing that too much.')
'role' => 'assistant',
'content' => __('Hang on! You\'re doing that too much.'),
]);
$this->js('scrollChatWindow(250)');
return;
}
@@ -51,7 +51,7 @@ new class extends Component {
$this->js('scrollChatWindow(250)');
return;
}
}
$this->chatable->chats()->save(new AiChat(['role' => 'user', 'content' => $this->prompt]));
array_push($this->messages, ['role' => 'user', 'content' => $this->prompt]);
@@ -65,17 +65,17 @@ new class extends Component {
public function generateCompletion(): void
{
try {
$client = $this->createOpenAiClient();
$stream = $client->chat()->createStreamed([
'model' => config('openai.model'),
'messages' => [
['role' => 'system', 'content' => "Today's date is "
.now()->toDateString()
.".\n\n".$this->system_prompt],
...array_slice($this->messages, -10)
...array_slice($this->messages, -10),
],
]);
} catch (\Exception $e) {
@@ -83,14 +83,15 @@ new class extends Component {
$this->chatable->chats()->save(new AiChat(['role' => 'assistant', 'content' => $e->getMessage()]));
array_push($this->messages, ['role' => 'assistant', 'content' => $e->getMessage()]);
$this->resetPrompt();
return;
}
$this->stream(to: "answer", content: '', replace: true);
foreach($stream as $response){
if(!empty($response->choices[0]->delta->content)) {
$this->stream(to: 'answer', content: '', replace: true);
foreach ($stream as $response) {
if (! empty($response->choices[0]->delta->content)) {
$this->stream(to: 'answer', content: $response->choices[0]->delta->content, replace: false);
$this->answer .= $response->choices[0]->delta->content;
}
@@ -116,34 +117,34 @@ new class extends Component {
'name' => 'suggested_prompts_schema',
'strict' => true,
'schema' => [
"type" => "object",
"properties" => [
"suggested_prompts" => [
"type" => "array",
"items" => [
"type" => "object",
"properties" => [
"text" => [
"type" => "string",
"description" => "The suggested prompt question (no more than 5 words)"
'type' => 'object',
'properties' => [
'suggested_prompts' => [
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => [
'text' => [
'type' => 'string',
'description' => 'The suggested prompt question (no more than 5 words)',
],
'value' => [
'type' => 'string',
'description' => 'The detailed version of the question',
],
"value" => [
"type" => "string",
"description" => "The detailed version of the question"
]
],
"required" => ["text", "value"],
"additionalProperties" => false
]
]
'required' => ['text', 'value'],
'additionalProperties' => false,
],
],
],
"required" => ["suggested_prompts"],
"additionalProperties" => false
]
]
'required' => ['suggested_prompts'],
'additionalProperties' => false,
],
],
],
'messages' => [
['role' => 'system', 'content' => "
['role' => 'system', 'content' => '
Your role is to assist investors in asking thoughtful questions of their investment advisors.
When you help investors ask good questions, you should ensure the you questions you recommend
@@ -155,12 +156,12 @@ new class extends Component {
explanation.
Your response should only include valid JSON.
"],
'],
['role' => 'user', 'content' => "
Generate between 1 and 5 (no more than 5) follow up questions a savvy investor might ask their
advisor based on the following conversation:
\n\n
".json_encode(array_slice($this->messages, -4))
".json_encode(array_slice($this->messages, -4)),
],
],
]);
@@ -171,6 +172,7 @@ new class extends Component {
$this->suggested_prompts = [];
$this->error($e->getMessage());
return;
}
}
@@ -184,10 +186,10 @@ new class extends Component {
public function isRateLimited(): bool
{
$rateLimitKey = auth()->id() . '/' . $this->chatable->id;
$rateLimitKey = auth()->id().'/'.$this->chatable->id;
if (RateLimiter::tooManyAttempts($rateLimitKey, 20)) {
return true;
}
@@ -210,7 +212,6 @@ new class extends Component {
->withBaseUri($baseUri)
->make();
}
}; ?>
<div
@@ -227,15 +228,16 @@ new class extends Component {
class="fixed z-50 bottom-8 right-8"
>
{{-- toggle button --}}
<x-button
<x-ui.button
x-show="!open"
@click="$dispatch('toggle-ai-chat')"
@keyup.escape.window="open = false"
class="flex btn btn-circle md:btn-lg btn-primary"
>
<x-slot:label>
<x-icon name="o-sparkles" class="w-6 h-6 md:w-8 md:h-8"></x-icon>
<x-ui.icon name="o-sparkles" class="w-6 h-6 md:w-8 md:h-8"></x-ui.icon>
</x-slot:label>
</x-button>
</x-ui.button>
{{-- popup --}}
<div
@@ -251,17 +253,17 @@ new class extends Component {
x-transition:leave-end="opacity-0 transform translate-y-full"
x-cloak
key="ai-chat"
class="fixed bg-base-100 shadow-2xl rounded-none md:rounded-lg
inset-0 h-screen w-full
md:inset-auto md:right-6 md:bottom-6 md:w-[32rem] md:h-[46rem]"
class="fixed bg-base-300 shadow-2xl rounded-none md:rounded-lg
inset-0 h-screen w-full md:inset-auto md:right-6
md:bottom-6 md:w-[32rem] md:h-[46rem]"
>
<div
class="absolute inset-0 flex flex-col overflow-hidden p-4"
x-intersect="scrollChatWindow()"
>
<div class="flex grow-0 justify-between items-center pb-4 ">
<h2 class="text-lg text-bold">{{ __('AI Chat') }}</h2>
<x-button
<h2 class="text-lg text-bold select-none">{{ __('AI Chat') }}</h2>
<x-ui.button
icon="o-x-mark"
class="absolute top-5 right-4 btn-ghost btn-circle btn-sm"
title="{{ __('Close') }}"
@@ -284,7 +286,7 @@ new class extends Component {
bg-slate-200
dark:bg-slate-800
">
<x-icon name="o-sparkles" class="h-auto p-1 w-10" />
<x-ui.icon name="o-sparkles" class="h-auto p-1 w-10" />
</span>
<p class="leading-relaxed w-full">
<span class="block font-bold">AI</span> {{ __('Hi, how can I help?') }}
@@ -298,7 +300,7 @@ new class extends Component {
<div class="flex gap-3 mb-5 flex-1">
<span class="relative flex shrink-0 overflow-hidden rounded-full w-10 h-10">
<x-avatar :image="auth()->user()->profile_photo_url" class="!w-10" />
<x-ui.avatar :image="auth()->user()->profile_photo_url" class="!w-10" />
</span>
<p class="leading-relaxed">
@@ -319,7 +321,7 @@ new class extends Component {
bg-slate-200
dark:bg-slate-800
">
<x-icon name="o-sparkles" class="h-auto p-1 w-10" />
<x-ui.icon name="o-sparkles" class="h-auto p-1 w-10" />
</span>
<div class="leading-relaxed" >
<span class="block font-bold ">AI </span> {!! Str::markdown($message['content']) !!}
@@ -342,7 +344,7 @@ new class extends Component {
bg-slate-200
dark:bg-slate-800
">
<x-icon name="o-sparkles" class="h-auto p-1 w-10" />
<x-ui.icon name="o-sparkles" class="h-auto p-1 w-10" />
</span>
<p class="leading-relaxed" >
<span class="block font-bold ">AI </span> <span wire:stream="answer">{{ $answer }}</span>
@@ -353,40 +355,42 @@ new class extends Component {
{{-- prompt input --}}
<div class="mt-3 grow-0">
<form submit="startCompletion" >
<form submit="startCompletion">
<div class="">
@foreach($suggested_prompts as $prompt)
<x-button
<x-ui.button
class="btn-xs btn-primary btn-outline mr-1 mb-2"
wire:click="startCompletion('{{ addslashes($prompt['value']) }}')"
>{{ $prompt['text'] }}</x-button>
>{{ $prompt['text'] }}</x-ui.button>
@endforeach
</div>
<div class="flex justify-between align-bottom space-x-2 mt-1">
<div class="w-full">
<div class="w-full" >
<x-textarea
<x-ui.textarea
wire:model="prompt"
class="h-24 resize-none "
class="h-18 resize-none bg-base-200"
placeholder="{{ __('Have a question? AI might be able to help...') }}"
wire:keydown.enter.prevent="startCompletion"
autofocus
></x-textarea>
@toggle-ai-chat.window="setTimeout(() => $el.focus(), 250)"
></x-ui.textarea>
{{-- --}}
</div>
<x-button
<x-ui.button
spinner="generateCompletion"
wire:click="startCompletion"
class="btn btn-ghost h-24"
class="btn btn-ghost h-32"
icon="o-paper-airplane"
></x-button>
></x-ui.button>
</div>
<div class="w-full mt-2">
<p class="text-xs text-secondary leading-tight">{{ __('Advice generated by AI may contain errors. Use at your own risk. Always consult a licensed investment advisor.') }} </p>
<p class="text-xs text-secondary leading-tight select-none">{{ __('Advice generated by AI may contain errors. Use at your own risk. Always consult a licensed investment advisor.') }} </p>
</div>
</form>
</div>
@@ -0,0 +1,36 @@
@props([
'id' => null,
'title' => null
'icon' => null,
'description' => null,
'shadow' => false,
'dismissable' => false
])
<div
wire:key="{{ $id }}"
{{ $attributes->whereDoesntStartWith('class') }}
{{ $attributes->class(['alert rounded-md', 'shadow-md' => $shadow])}}
x-data="{ show: true }" x-show="show"
>
@if($icon)
<x-icon :name="$icon" class="self-center" />
@endif
@if($title)
<div>
<div @class(["font-bold" => $description])>{{ $title }}</div>
<div class="text-xs">{{ $description }}</div>
</div>
@else
<span>{{ $slot }}</span>
@endif
<div class="flex items-center gap-3">
{{ $actions }}
</div>
@if($dismissible)
<x-button icon="o-x-mark" @click="show = false" class="btn-xs btn-circle btn-ghost static self-start end-0" />
@endif
</div>
@@ -0,0 +1,9 @@
<div class="min-h-screen flex flex-col sm:justify-center items-center pt-6 sm:pt-0">
<div>
{{ $logo }}
</div>
<div class="w-full sm:max-w-md mt-6 px-6 py-4 bg-base-200 shadow-md overflow-hidden sm:rounded-lg">
{{ $slot }}
</div>
</div>
@@ -0,0 +1,36 @@
@props([
'id' => null,
'image' => '',
'alt' => '',
'placeholder' => '',
'fallbackImage' => null,
'title' => null,
'subtitle' => null,
])
<div class="flex items-center gap-3">
<div class="avatar @if(empty($image)) avatar-placeholder @endif">
<div {{ $attributes->class(["w-7 rounded-full", "bg-neutral text-neutral-content" => empty($image)]) }}>
@if(empty($image))
<span class="text-xs" alt="{{ $alt }}">{{ $placeholder }}</span>
@else
<img src="{{ $image }}" alt="{{ $alt }}" @if($fallbackImage) onerror="this.src='{{ $fallbackImage }}'" @endif />
@endif
</div>
</div>
@if($title || $subtitle)
<div>
@if($title)
<div @class(["font-semibold font-lg", is_string($title) ? '' : $title?->attributes->get('class') ]) >
{{ $title }}
</div>
@endif
@if($subtitle)
<div @class(["text-sm text-base-content/50", is_string($subtitle) ? '' : $subtitle?->attributes->get('class') ]) >
{{ $subtitle }}
</div>
@endif
</div>
@endif
</div>
@@ -0,0 +1,13 @@
@props([
'value' => null,
])
@php
if (isset($class)) {
$attributes->setAttributes(['class' => $class]);
}
@endphp
<div {{ $attributes->class(["badge select-none"]) }}>
{{ $value ?? $slot ?? '' }}
</div>
@@ -0,0 +1,75 @@
@props([
'type' => 'button',
'external' => false,
'link' => null,
'label' => null,
'icon' => null,
'spinner' => null,
'tooltip' => null,
'tooltipLeft' => null,
'tooltipRight' => null,
'tooltipBottom' => null,
'badge' => null,
'badgeClasses' => null,
])
@php
$tooltip = $tooltip ?? $tooltipLeft ?? $tooltipRight ?? $tooltipBottom;
$tooltipPosition = $tooltipLeft ? 'lg:tooltip-left' : ($tooltipRight ? 'lg:tooltip-right' : ($tooltipBottom ? 'lg:tooltip-bottom' : 'lg:tooltip-top'));
$spinnerTarget = $spinner ?? $attributes->whereStartsWith('wire:click')->first();
@endphp
@if($link)
<a href="{!! $link !!}"
@else
<button
@endif
{{ $attributes->whereDoesntStartWith('class')->merge(['type' => $type]) }}
type="button"
{{ $attributes->class(['btn', "!inline-flex lg:tooltip $tooltipPosition" => $tooltip]) }}
@if($link && $external)
target="_blank"
@endif
@if($link && !$external)
wire:navigate
@endif
data-tip="{{ $tooltip }}"
@if($spinner)
wire:target="{{ $spinnerTarget }}"
wire:loading.attr="disabled"
@endif
>
{{-- spinner --}}
@if($spinner)
<span wire:loading wire:target="{{ $spinnerTarget }}" class="loading loading-spinner w-5 h-5">Loading</span>
@endif
{{-- icon --}}
@if($icon)
<span class="block" @if($spinner) wire:loading.class="hidden" wire:target="{{ $spinnerTarget }}" @endif>
<x-ui.icon :name="$icon" />
</span>
@endif
{{-- label / slot --}}
@if($label)
<span>
{{ $label }}
</span>
@if(strlen($badge ?? '') > 0)
<span class="badge badge-sm {{ $badgeClasses }}">{{ $badge }}</span>
@endif
@else
{{ $slot }}
@endif
@if($link)
</a>
@else
</button>
@endif
@@ -0,0 +1,22 @@
@props([
'title' => '',
'subTitle' => '',
'dense' => false,
'expanded' => false
])
<div
{{ $attributes->merge()->class(['p-5', 'shadow-sm', 'rounded-lg', 'bg-base-100']) }}
>
@if($title)
<h3 @class(['pb-2' => !$subTitle && !$dense, 'text-xl font-bold leading-none tracking-tight flex items-center truncate'])> {{ $title }} </h3>
@endif
@if($subTitle)
<h5 @class(['pb-2' => !$dense, 'text-sm text-gray-400 flex items-center truncate'])> {{ $subTitle }} </h5>
@endif
<div @class(['mt-2' => !$dense && !$expanded, 'mt-0' => $dense, 'mt-5' => $expanded])>
{{ $slot }}
</div>
</div>
@@ -0,0 +1,62 @@
@props([
'id' => null,
'label' => null,
'right' => false,
'tight' => false,
'hint' => null,
'hintClass' => 'label-text-alt text-gray-400 py-1 pb-0',
'errorField' => null,
'errorClass' => 'text-red-500 label-text-alt p-1',
'omitError' => false,
'firstErrorOnly' => false,
])
@php
$modelName = $attributes->whereStartsWith('wire:model')->first();
$errorFieldName = $errorField ?? $modelName;
$id = $id == $modelName ? $modelName : "{$id}{$modelName}";
@endphp
<div>
<label for="{{ $id }}" class="flex gap-3 items-center cursor-pointer">
@if($right)
<span @class(["flex-1" => !$tight])>
{{ $label }}
@if($attributes->get('required'))
<span class="text-error">*</span>
@endif
</span>
@endif
<input
id="{{ $id }}"
type="checkbox"
{{ $attributes->whereDoesntStartWith('id')->merge(['class' => 'checkbox checkbox-primary']) }} />
@if(!$right)
{{ $label }}
@if($attributes->get('required'))
<span class="text-error">*</span>
@endif
@endif
</label>
{{-- ERROR --}}
@if(!$omitError && $errors->has($errorFieldName))
@foreach($errors->get($errorFieldName) as $message)
@foreach(Arr::wrap($message) as $line)
<div class="{{ $errorClass }}" x-classes="text-red-500 label-text-alt p-1">{{ $line }}</div>
@break($firstErrorOnly)
@endforeach
@break($firstErrorOnly)
@endforeach
@endif
{{-- HINT --}}
@if($hint)
<div class="{{ $hintClass }}" x-classes="label-text-alt text-gray-400 py-1 pb-0">{{ $hint }}</div>
@endif
</div>
@@ -1,7 +1,14 @@
@props(['id' => null, 'maxWidth' => null])
@props(['key' => 'confirmation'])
<x-ib-livewire-modal :id="$id" :maxWidth="$maxWidth" {{ $attributes }} :showClose="false">
<div class="p-2">
<x-ui.modal
:key="$key"
box-class="max-w-xl"
persistent="true"
no-card="true"
{{ $attributes }}
>
<div class="p-5">
<div class="sm:flex sm:items-start">
<div class="mx-auto shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<svg class="h-6 w-6 text-red-600 dark:text-red-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
@@ -10,18 +17,18 @@
</div>
<div class="mt-3 text-center sm:mt-0 sm:ms-4 sm:text-start">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">
<h3 class="text-xl font-bold text-primary-content">
{{ $title }}
</h3>
<div class="mt-4 text-sm text-gray-600 dark:text-gray-400">
<div class="mt-2 text-sm text-secondary-content">
{{ $content }}
</div>
</div>
</div>
</div>
<div class="flex flex-row items-center justify-end mt-3 p-2 text-end">
{{ $footer }}
<div class="flex flex-row items-center justify-center sm:justify-end mt-8 text-end">
{{ $footer }}
</div>
</div>
</x-ib-livewire-modal>
</x-ui.modal>
@@ -0,0 +1,261 @@
@props([
'id' => null,
'label' => null,
'icon' => null,
'hint' => null,
'hintClass' => 'label-text-alt text-gray-400 py-1 pb-0',
'errorField' => null,
'errorClass' => 'text-red-500 label-text-alt p-1',
'omitError' => false,
'firstErrorOnly' => false,
])
@php
$modelName = $attributes->whereStartsWith('wire:model')->first();
$errorFieldName = $errorField ?? $modelName;
$id = $id == $modelName ? $modelName : "{$id}{$modelName}";
@endphp
<style>
input[type="date"]::-webkit-calendar-picker-indicator {
color: transparent;
background: transparent;
}
</style>
<div
x-cloak
x-data="{
datePickerOpen: false,
datePickerValue: $wire.entangle(@js($modelName)),
datePickerMonth: '',
datePickerYear: '',
datePickerDay: '',
datePickerDaysInMonth: [],
datePickerBlankDaysInMonth: [],
datePickerMonthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
datePickerDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
datePickerDayClicked(day) {
let selectedDate = new Date(this.datePickerYear, this.datePickerMonth, day);
this.datePickerDay = day;
this.datePickerValue = this.dateToValue(selectedDate);
this.datePickerIsSelectedDate(day);
this.datePickerOpen = false;
},
datePickerPreviousMonth(){
if (this.datePickerMonth == 0) {
this.datePickerYear--;
this.datePickerMonth = 12;
}
this.datePickerMonth--;
this.datePickerCalculateDays();
},
datePickerNextMonth(){
if (this.datePickerMonth == 11) {
this.datePickerMonth = 0;
this.datePickerYear++;
} else {
this.datePickerMonth++;
}
this.datePickerCalculateDays();
},
datePickerIsSelectedDate(day) {
const d = new Date(this.datePickerYear, this.datePickerMonth, day);
return this.datePickerValue === this.dateToValue(d) ? true : false;
},
datePickerIsToday(day) {
const today = new Date();
const d = new Date(this.datePickerYear, this.datePickerMonth, day);
return today.toDateString() === d.toDateString() ? true : false;
},
datePickerCalculateDays() {
let daysInMonth = new Date(this.datePickerYear, this.datePickerMonth + 1, 0).getDate();
// find where to start calendar day of week
let dayOfWeek = new Date(this.datePickerYear, this.datePickerMonth).getDay();
let blankdaysArray = [];
for (var i = 1; i <= dayOfWeek; i++) {
blankdaysArray.push(i);
}
let daysArray = [];
for (var i = 1; i <= daysInMonth; i++) {
daysArray.push(i);
}
this.datePickerBlankDaysInMonth = blankdaysArray;
this.datePickerDaysInMonth = daysArray;
},
dateToValue(d) {
d = this.parseDate(d)
let formattedDate = ('0' + d.getDate()).slice(-2);
let formattedMonthInNumber = ('0' + (parseInt(d.getMonth()) + 1)).slice(-2);
let formattedYear = d.getFullYear();
return `${formattedYear}-${formattedMonthInNumber}-${formattedDate}`;
},
parseDate(d) {
date = new Date();
let userTimezoneOffset = date.getTimezoneOffset() * 60000;
return new Date(Date.parse(d) + userTimezoneOffset);
}
}"
x-init="
currentDate = new Date();
if (datePickerValue) {
currentDate = parseDate(datePickerValue)
}
datePickerMonth = currentDate.getMonth();
datePickerYear = currentDate.getFullYear();
datePickerDay = currentDate.getDay();
datePickerValue = currentDate.toISOString().slice(0, 10);
datePickerCalculateDays();
"
>
{{-- STANDARD LABEL --}}
@if($label)
<label for="{{ $id }}" class="pt-0 label label-text font-semibold">
<span>
{{ $label }}
@if($attributes->get('required'))
<span class="text-error">*</span>
@endif
</span>
</label>
@endif
<div class="flex-1 relative">
{{-- DESKTOP --}}
<div
x-ref="desktopDatePickerInput"
x-html="parseDate(datePickerValue).toLocaleDateString()"
x-on:keydown.escape="datePickerOpen=false"
@click="datePickerOpen=true"
{{ $attributes->class([
"hidden md:block py-2 input px-4 input-primary w-full peer appearance-none",
'ps-10' => ($icon),
'border border-dashed' => $attributes->has('readonly') && $attributes->get('readonly') == true,
'input-error' => $errors->has($errorFieldName)
]) }}
></div>
<div
x-show="datePickerOpen"
x-transition:enter="ease-out duration-200"
x-transition:enter-start="-translate-x-2"
x-transition:enter-end="translate-x-0"
@click.away="datePickerOpen = false"
class="
p-4
mt-12
top-0
left-0
max-w-lg
w-[17rem]
absolute
z-100
bg-base-100
dark:bg-base-300
rounded-box
shadow-md
select-none
"
>
<div class="flex justify-between items-center mb-2">
<div>
<span x-text="datePickerMonthNames[datePickerMonth]" class="text-lg font-bold"></span>
<span x-text="datePickerYear" class="ml-1 text-lg font-normal text-gray-600"></span>
</div>
<div>
<button @click="datePickerPreviousMonth()" type="button" class="inline-flex p-1 rounded-full transition duration-100 ease-in-out cursor-pointer focus:outline-none focus:shadow-outline hover:bg-accent/50">
<x-ui.icon name="o-chevron-left" />
</button>
<button @click="datePickerNextMonth()" type="button" class="inline-flex p-1 rounded-full transition duration-100 ease-in-out cursor-pointer focus:outline-none focus:shadow-outline hover:bg-accent/50">
<x-ui.icon name="o-chevron-right" />
</button>
</div>
</div>
<div class="grid grid-cols-7 mb-3">
<template x-for="(day, index) in datePickerDays" :key="index">
<div class="px-0.5">
<div x-text="day" class="text-xs font-medium text-center"></div>
</div>
</template>
</div>
<div class="grid grid-cols-7">
<template x-for="blankDay in datePickerBlankDaysInMonth">
<div class="p-1 text-sm text-center border border-transparent"></div>
</template>
<template x-for="(day, dayIndex) in datePickerDaysInMonth" :key="dayIndex">
<div class="px-0.5 mb-1 aspect-square">
<div
x-text="day"
@click="datePickerDayClicked(day)"
:class="{
'border border-accent/50': datePickerIsToday(day) == true,
'hover:bg-neutral-800/70': datePickerIsToday(day) == false && datePickerIsSelectedDate(day) == false,
'text-primary-content bg-primary hover:bg-primary/50': datePickerIsSelectedDate(day) == true
}"
class="flex justify-center items-center w-7 h-7 text-sm leading-none text-center rounded-full cursor-pointer"
></div>
</div>
</template>
</div>
</div>
{{-- MOBILE/NATIVE --}}
<input
type="date"
x-model="datePickerValue"
placeholder="Select date"
id="{{ $id }}"
onfocus="this.showPicker?.()"
x-ref="mobileDatePickerInput"
{{ $attributes->class([
"block md:hidden input input-primary w-full peer appearance-none",
'ps-10' => ($icon),
'border border-dashed' => $attributes->has('readonly') && $attributes->get('readonly') == true,
'input-error' => $errors->has($errorFieldName)
]) }}
/>
{{-- ICON --}}
<div @click="
if ($refs.mobileDatePickerInput?.checkVisibility()) {
$refs.mobileDatePickerInput?.showPicker()
return;
}
if(datePickerOpen) {
$refs.desktopDatePickerInput.focus();
return;
}
datePickerOpen=!datePickerOpen;
"
class="z-60 absolute top-1/2 -translate-y-1/2 end-0 p-3 cursor-pointer text-neutral-400 hover:text-neutral-500"
>
<x-ui.icon name="o-calendar" />
</div>
</div>
{{-- ERROR --}}
@if(!$omitError && $errors->has($errorFieldName))
@foreach($errors->get($errorFieldName) as $message)
@foreach(Arr::wrap($message) as $line)
<div class="{{ $errorClass }}" x-classes="text-red-500 label-text-alt p-1">{{ $line }}</div>
@break($firstErrorOnly)
@endforeach
@break($firstErrorOnly)
@endforeach
@endif
{{-- HINT --}}
@if($hint)
<div class="{{ $hintClass }}" x-classes="label-text-alt text-gray-400 py-1 pb-0">{{ $hint }}</div>
@endif
</div>
@@ -0,0 +1,25 @@
@props(['key' => 'dialog'])
<x-ui.modal
:key="$key"
box-class="max-w-xl"
persistent="true"
no-card="true"
{{ $attributes }}
>
<div class="p-5">
<div class="text-xl font-bold text-primary-content">
{{ $title }}
</div>
<div class="mt-2 text-sm text-secondary-content">
{{ $content }}
</div>
<div class="flex flex-row items-center justify-end mt-8 text-end">
{{ $footer }}
</div>
</div>
</x-ui.modal>
@@ -0,0 +1,52 @@
@props([
'key' => 'drawer',
'showClose' => true,
'closeOnEscape' => true,
'title' => null,
'subtitle' => null
])
<div
x-data="{ open: false }"
x-on:toggle-{{ $key }}.window="open = !open"
@if($closeOnEscape)
@keydown.window.escape="open = false"
@endif
x-trap="open"
x-bind:inert="!open"
class="fixed inset-0 flex justify-end z-50"
x-cloak
>
{{-- overlay --}}
<div @click="open = false" x-show="open" class="z-40 fixed inset-0 bg-black opacity-50"></div>
{{-- content --}}
<div
class="transition duration-200 ease-out transition-transform translate-x-full transform z-50 md:w-3/4 xl:w-3/5"
:class="{'translate-x-0': open, 'translate-x-full': !open}"
>
<x-ui.card
{{ $attributes->merge(['class' => 'w-full min-h-screen rounded-none px-8 overflow-y-scroll']) }}
>
@if($title)
<x-slot:title>
{!! strip_tags($title) !!}
</x-slot:title>
@endif
@if($subtitle)
<x-slot:subtitle>
{!! strip_tags($subtitle) !!}
</x-slot:subtitle>
@endif
@if ($showClose)
<x-ui.button icon="o-x-mark" title="{{ __('Close') }}" class="btn-ghost btn-circle btn-sm absolute top-4 right-4 " @click="open = false" />
@endif
{{ $slot }}
</x-ui.card>
</div>
</div>
@@ -0,0 +1,62 @@
@props([
'id' => null,
'label' => null,
'icon' => 'o-chevron-down',
'trigger' => null,
])
<details
x-data="{
dropdownOpen: false
}"
:open="dropdownOpen"
@click.outside="dropdownOpen = false"
@class(['dropdown'])
>
{{-- CUSTOM TRIGGER --}}
@if($trigger)
<summary x-ref="button" @click.prevent="dropdownOpen = !dropdownOpen" {{ $trigger->attributes->class(['list-none']) }}>
{{ $trigger }}
</summary>
@else
{{-- DEFAULT TRIGGER --}}
<summary
x-ref="button"
@click.prevent="dropdownOpen = !dropdownOpen"
{{ $attributes->class(["btn btn-ghost normal-case disabled:opacity-50 disabled:pointer-events-none"]) }}
>
{{ $label }}
<span class="transition-transform" :class="{'rotate-180': dropdownOpen }">
<x-ui.icon :name="$icon" />
</span>
</summary>
@endif
{{-- CONTENT --}}
<ul
@class([
'menu',
'absolute',
'top-0',
'p-2',
'shadow-lg',
'z-50',
'bg-base-100',
'rounded-box',
'w-auto',
'min-w-max',
])
x-anchor.bottom-start="$refs.button"
@click="dropdownOpen = false"
x-transition:enter="ease-out duration-200"
x-transition:enter-start="-translate-y-2"
x-transition:enter-end="translate-y-0"
x-cloak
>
<div wire:key="dropdown-slot-{{ $id }}">
{{ $slot }}
</div>
</ul>
</details>
@@ -0,0 +1,37 @@
@props([
'id' => null,
'title' => null,
'description' => null,
'icon' => 'o-x-circle',
'only' => []
])
<div>
@if ($errors->any())
<div {{ $attributes->class(["flex justify-start alert alert-error rounded rounded-md"]) }} >
<div class="grid gap-3">
<div class="flex gap-2">
@if($title)
<x-icon :name="$icon" class="w-6 h-6 mt-0.5" />
@endif
<div>
@if($title)
<div class="font-bold text-lg">{{ $title }}</div>
@endif
@if($description)
<div class="font-semibold">{{ $description }}</div>
@endif
</div>
</div>
<div>
<ul class="list-disc ms-3 space-y-2 sm:ms-6 pb-3">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
</div>
</div>
@endif
</div>
@@ -0,0 +1,122 @@
@props([
'id' => null,
'label' => null,
'hint' => null,
'hintClass' => 'label-text-alt text-gray-400 py-1 pb-0',
'multiple' => false,
'clearable' => true,
'hideProgress' => false,
'errorField' => null,
'errorClass' => 'text-red-500 label-text-alt p-1',
'omitError' => false,
'firstErrorOnly' => false,
])
@php
$modelName = $attributes->whereStartsWith('wire:model')->first();
$errorFieldName = $errorField ?? $modelName;
$id = $id == $modelName ? $modelName : "{$id}{$modelName}";
@endphp
<div
class="container"
x-data="{
files: @entangle($modelName),
progress: 0,
selectFiles(e) {
this.files = e.target.files[0].name
$wire.upload('{{ $modelName }}', e.target.files[0], (uploadedFilename) => {
// Success callback...
this.progress = 0;
}, () => {
// Error callback...
}, (event) => {
this.progress = event.detail.progress
}, () => {
// Cancelled callback...
})
},
reset(){
this.files = null
this.$refs.fileInput.value = null
}
}">
{{-- STANDARD LABEL --}}
@if($label)
<label for="{{ $id }}" class="pt-0 label label-text font-semibold">
<span>
{{ $label }}
@if($attributes->get('required'))
<span class="text-error">*</span>
@endif
</span>
</label>
@endif
<div {{ $attributes->class(['relative']) }}>
{{-- PROGRESS BAR --}}
@if(!$hideProgress)
<progress
x-cloak
max="100"
:value="progress"
:class="{'hidden': !progress}"
class="progress h-1 absolute -mt-2 w-56">
</progress>
@endif
<input
type="file"
x-ref="fileInput"
id="{{ $id }}"
{{ $multiple ? 'multiple="true"' : '' }}
@change="selectFiles"
{{
$attributes->whereDoesntStartWith(['wire:model', 'class'])->class([
"file-input w-full",
"!file-input-error" => $errorFieldName && $errors->has($errorFieldName) && !$omitError
])
}}
>
@if($clearable)
<span :class="{'hidden': !files}">
<x-ui.button
type="reset"
@click="reset"
class="absolute top-2 right-2 btn btn-sm btn-ghost btn-circle"
icon="o-x-mark"
></x-ui.button>
</span>
@endif
</div>
{{-- ERROR --}}
@if(!$omitError && $errors->has($errorFieldName))
@foreach($errors->get($errorFieldName) as $message)
@foreach(Arr::wrap($message) as $line)
<div class="{{ $errorClass }}" x-classes="text-error">{{ $line }}</div>
@break($firstErrorOnly)
@endforeach
@break($firstErrorOnly)
@endforeach
@endif
{{-- MULTIPLE --}}
@error($modelName.'.*')
<div class="text-error" x-classes="text-error">{{ $message }}</div>
@enderror
{{-- HINT --}}
@if($hint)
<div class="{{ $hintClass }}" x-classes="fieldset-label">{{ $hint }}</div>
@endif
</div>
@@ -11,7 +11,7 @@
@if ($actions)
@if(!$noSeparator)
<x-section-border class="my-3" />
<x-ui.section-border class="my-3" />
@endif
<div class="flex justify-end gap-3">
@@ -0,0 +1,38 @@
@props([
'small' => false,
'percent' => null,
'costBasis' => null,
'marketValue' => null
])
@php
if (!is_null($percent)) {
$isUp = $percent > 0;
} else {
$isUp = $costBasis <= $marketValue;
$percent = $costBasis ? (($marketValue - $costBasis) / $costBasis) * 100 : 0;
}
@endphp
@if(!empty($percent))
<x-ui.badge
class="{{ $small ? 'badge-xs' : 'badge-sm' }} {{ $isUp ? 'badge-success' : 'badge-error' }} badge-outline ml-2"
title="{{ Number::percentage(
$percent,
$percent < 1 ? 2 : 0
) }}"
>
<x-slot:value>
{!! $isUp ? '&#9650;' :'&#9660;' !!}
{{ Number::percentage(
abs($percent),
($percent && $small) < 1 ? 2 : 0
) }}
</x-slot:value>
</x-ui.badge>
@endif
@@ -0,0 +1,33 @@
@props([
'id' => null,
'name' => null,
'label' => null,
])
@php
$name = Str::of($name);
$icon = $name->contains('.') ? $name->replace('.', '-') : "heroicon-{$name}";
// Remove `w-*` and `h-*` classes, because it applies only for icon
$labelClasses = Str::replaceMatches('/(w-\w*)|(h-\w*)/', '', $attributes->get('class') ?? '');
@endphp
@if(strlen($label ?? '') > 0)
<div class="inline-flex items-center gap-1">
@endif
<x-icon :name="$icon"
{{
$attributes->class([
'inline',
'w-5 h-5' => !Str::contains($attributes->get('class') ?? '', ['w-', 'h-'])
])
}}
/>
@if(strlen($label ?? '') > 0)
<div class="{{ $labelClasses }}">
{{ $label }}
</div>
</div>
@endif
@@ -0,0 +1,128 @@
@props([
'id' => null,
'label' => null,
'icon' => null,
'hint' => null,
'hintClass' => 'label-text-alt text-gray-400 py-1 pb-0',
'prefix' => null,
'suffix' => null,
'prepend' => null,
'append' => null,
'errorField' => null,
'errorClass' => 'text-red-500 label-text-alt p-1',
'omitError' => false,
'firstErrorOnly' => false,
])
@php
$modelName = $attributes->whereStartsWith('wire:model')->first();
$errorFieldName = $errorField ?? $modelName;
$id = $id == $modelName ? $modelName : "{$id}{$modelName}";
@endphp
<div>
{{-- STANDARD LABEL --}}
@if($label)
<label for="{{ $id }}" class="pt-0 label label-text font-semibold">
<span>
{{ $label }}
@if($attributes->get('required'))
<span class="text-error">*</span>
@endif
</span>
</label>
@endif
{{-- PREFIX/SUFFIX/PREPEND/APPEND CONTAINER --}}
@if($prefix || $suffix || $prepend || $append)
<div class="flex">
@endif
{{-- PREFIX / PREPEND --}}
@if($prefix || $prepend)
<div
@class([
"rounded-s-lg flex items-center",
"border border-primary border-e-0 px-4" => $prefix,
"border-0" => $attributes->has('disabled') && $attributes->get('disabled') == true,
"border-dashed" => $attributes->has('readonly') && $attributes->get('readonly') == true,
"!border-error" => $errorFieldName && $errors->has($errorFieldName) && !$omitError
])
>
{{ $prepend ?? $prefix }}
</div>
@endif
<div class="flex-1 relative">
{{-- INPUT --}}
<input
id="{{ $id }}"
placeholder = "{{ $attributes->whereStartsWith('placeholder')->first() }} "
@if($attributes->has('autofocus') && $attributes->get('autofocus') == true)
autofocus
@endif
{{
$attributes
->merge(['type' => 'text'])
->class([
'input input-primary w-full peer',
'ps-10' => ($icon),
'rounded-s-none' => $prefix || $prepend,
'rounded-e-none' => $suffix || $append,
'border-e-0' => $suffix,
'border border-dashed' => $attributes->has('readonly') && $attributes->get('readonly') == true,
'input-error' => $errorFieldName && $errors->has($errorFieldName) && !$omitError
])
}}
/>
{{-- ICON --}}
@if($icon)
<x-ui.icon :name="$icon" class="z-60 absolute top-1/2 -translate-y-1/2 start-3 text-gray-400 pointer-events-none" />
@endif
</div>
{{-- SUFFIX/APPEND --}}
@if($suffix || $append)
<div
@class([
"rounded-e-lg flex items-center",
"border border-primary border-s-0" => $suffix,
"border-0" => $attributes->has('disabled') && $attributes->get('disabled') == true,
"border-dashed" => $attributes->has('readonly') && $attributes->get('readonly') == true,
"!border-error" => $errorFieldName && $errors->has($errorFieldName) && !$omitError
])
>
{{ $append ?? $suffix }}
</div>
@endif
{{-- END: PREFIX/SUFFIX/APPEND/PREPEND CONTAINER --}}
@if($prefix || $suffix || $prepend || $append)
</div>
@endif
{{-- ERROR --}}
@if(!$omitError && $errors->has($errorFieldName))
@foreach($errors->get($errorFieldName) as $message)
@foreach(Arr::wrap($message) as $line)
<div class="{{ $errorClass }}" x-classes="text-red-500 label-text-alt p-1">{{ $line }}</div>
@break($firstErrorOnly)
@endforeach
@break($firstErrorOnly)
@endforeach
@endif
{{-- HINT --}}
@if($hint)
<div class="{{ $hintClass }}" x-classes="label-text-alt text-gray-400 py-1 pb-0">{{ $hint }}</div>
@endif
</div>
@@ -0,0 +1,91 @@
@props([
'id' => null,
'item' => array(),
'avatar' => 'avatar',
'value' => 'name',
'subValue' => '',
'noSeparator' => false,
'noHover' => false,
'link' => null,
'actions' => null,
])
<div wire:key="{{ $id }}">
<div
{{ $attributes->class([
"flex justify-start items-center gap-4 px-3",
"hover:bg-base-200/50" => !$noHover,
"cursor-pointer" => $link
])
}}
>
@if($link && (data_get($item, $avatar) || !is_string($avatar)))
<div>
<a href="{{ $link }}" wire:navigate>
@endif
{{-- AVATAR --}}
@if(data_get($item, $avatar))
<div class="py-3">
<div class="avatar">
<div class="w-11 rounded-full">
<img src="{{ data_get($item, $avatar) }}" />
</div>
</div>
</div>
@endif
@if(!is_string($avatar))
<div {{ $avatar->attributes->class(["py-3"]) }}>
{{ $avatar }}
</div>
@endif
@if($link && (data_get($item, $avatar) || !is_string($avatar)))
</a>
</div>
@endif
{{-- CONTENT --}}
<div class="flex-1 overflow-hidden whitespace-nowrap text-ellipsis truncate w-0">
@if($link)
<a href="{{ $link }}" wire:navigate>
@endif
<div class="py-3">
<div @if(!is_string($value)) {{ $value->attributes->class(["font-semibold truncate"]) }} @else class="font-semibold truncate" @endif>
{{ is_string($value) ? data_get($item, $value) : $value }}
</div>
<div @if(!is_string($subValue)) {{ $subValue->attributes->class(["text-gray-400 text-sm truncate"]) }} @else class="text-gray-400 text-sm truncate" @endif>
{{ is_string($subValue) ? data_get($item, $subValue) : $subValue }}
</div>
</div>
@if($link)
</a>
@endif
</div>
{{-- ACTION --}}
@if($actions)
@if($link && !Str::of($actions)->contains([':click', '@click' , 'href']))
<a href="{{ $link }}" wire:navigate>
@endif
<div {{ $actions->attributes->class(["py-3 flex items-center gap-3"]) }}>
{{ $actions }}
</div>
@if($link && !Str::of($actions)->contains([':click', '@click' , 'href']))
</a>
@endif
@endif
</div>
@if(!$noSeparator)
<hr class="border-base-300"/>
@endif
</div>
@@ -0,0 +1 @@
<span {{ $attributes->class("loading loading-spinner") }}></span>
@@ -0,0 +1,48 @@
<a href="{{ route('dashboard') }}" title="Investbrain" alt="Investbrain Logo">
<svg width="100%" height="100%" id="Layer_1" class="fill-current" data-name="Layer 1" viewBox="0 0 1001 783" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" >
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M553.875,632.571L567.884,627.131C567.884,627.131 588.94,642.044 611.341,650.542C660.03,669.007 666.181,693.68 670.67,711.697C671.541,715.201 672.368,718.512 673.431,721.293C679.103,736.17 685.326,746.904 694.882,758.003L737.893,737.748C730.866,729.455 721.087,714.1 721.273,693.007C721.419,676.837 731.456,663.936 740.313,652.55C749.261,641.048 756.99,631.115 754.689,619.792C754.428,618.501 750.205,606.681 683.457,589.378C664.971,584.588 632.955,577.931 632.955,577.931C632.955,577.931 635.967,564.803 636.504,564.91C650.287,567.669 668.765,571.64 687.293,576.443C757.295,594.586 767.837,608.754 769.682,617.83C773.076,634.571 762.843,647.722 752.948,660.444C744.551,671.243 736.616,681.44 736.507,693.555C736.275,719.609 754.703,734.781 754.889,734.933L762.945,741.43L691.292,775.182L687.22,770.803C674.012,756.601 666.101,743.826 659.014,725.231C657.68,721.736 656.764,718.071 655.801,714.191C651.633,697.476 641.744,677.506 600.566,661.887C573.409,651.585 553.875,632.571 553.875,632.571Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M469.894,617.03C491.9,625.608 537.785,632.498 578.066,616.912C606.757,605.811 625.69,585.647 634.343,556.967L635.932,544.895L650.678,549.582L650.547,550.556L649.213,560.348C639.567,592.821 617.208,616.664 584.546,629.301C557.593,639.728 525.875,641.415 498.98,637.874C484.116,635.917 475.069,632.909 464.77,628.35L469.894,617.03Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M756.363,647.659C766.597,652.226 735.904,647.812 749.88,647.831C752.018,647.834 754.181,647.777 756.363,647.659ZM756.363,647.659L759.121,630.608C776.312,645.1 814.041,614.388 822.007,607.977C847.271,587.646 859.429,573.432 865.582,531.56L857.892,525.97L871.854,519.291L871.902,520.701L871.646,533.167C868.374,581.138 854.724,595.681 826.924,619.726C805.922,637.888 779.998,646.378 756.363,647.659Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M278.425,160.523C277.934,160.447 277.438,160.37 276.948,160.286C277.44,160.359 277.93,160.439 278.425,160.523Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M56.427,281.384L53.276,278.254C53.915,253.987 64.477,230.809 81.468,211.975C77.818,216.38 74.378,221.228 71.199,226.564C59.438,246.327 55.735,264.917 56.427,281.384ZM125.55,179.848C146.913,170.054 171.349,165.376 196.488,168.027C196.773,169.34 196.992,170.047 196.992,170.047L196.819,170.025C194.125,169.67 160.165,165.575 125.55,179.848ZM876.465,148.788C875.253,140.191 872.604,131.249 868.55,122.317C872.79,131.336 875.507,140.302 876.465,148.788ZM636.968,67.078C632.455,59.767 626.37,52.471 618.591,45.74C627.058,52.458 633.412,59.75 636.968,67.078ZM830.233,73.639C814.235,60.456 794.248,49.212 770.413,41.637C761.569,38.826 752.892,36.944 744.475,35.836C730.805,34.037 717.803,34.272 705.832,35.868C719.737,33.548 733.39,33.475 746.563,35.21C778.577,39.424 807.696,54.337 830.233,73.639Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M546.485,261.415C546.455,261.429 546.424,261.444 546.394,261.458C546.389,261.461 546.389,261.461 546.385,261.46L546.485,261.415ZM546.485,261.415C547.832,260.79 549.176,260.186 550.513,259.608L546.485,261.415Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M951.448,338.201C967.066,346.748 978.94,360.259 984.357,371.461C991.028,385.267 1009.88,464.746 938.38,509.182C874.197,549.074 824.465,524.364 805.506,511.387C784.609,543.131 735.375,571.199 677.333,565.042C620.575,558.985 591.016,530.312 576.286,507.76C561.52,528.554 541.685,537.599 526.615,541.516C511.159,545.534 494.383,545.742 479.342,542.424C490.897,565.908 498.729,604.571 467.806,641.145C445.634,667.37 414.398,675.189 392.099,677.128C370.311,679.025 349.702,675.811 337.482,671.143C324.069,682.794 288.704,704.331 243.88,698.43C233.085,697.009 221.742,694 210.023,688.874C158.439,666.305 147.126,623.344 154.77,594.547C111.783,593.541 77.23,581.082 51.934,557.44C20.86,528.397 4.976,481.26 9.445,431.35C14.204,378.198 55.606,354.896 74.754,346.889C60.508,328.89 30.467,280.339 64.437,223.27C98.829,165.496 163.458,161.805 188.094,162.668C186.255,144.263 189.011,101.243 245.926,69.359C313.249,31.644 373.035,54.386 397.781,70.781C414.353,45.661 452.387,10.329 510.461,7.925C585.849,4.8 622.364,35.827 637.949,55.854C660.603,36.887 713.288,16.387 772.778,35.297C839.209,56.412 875.616,104.13 883.278,143.714C947.161,162.729 985.435,206.49 988.544,264.393C990.859,307.537 971.373,327.501 951.448,338.201ZM929.36,498.056C991.957,459.153 975.932,387.913 970.382,376.422C965.636,366.6 951.481,350.075 931.943,344.8L911.518,339.288L930.807,332.122C953.179,323.814 975.727,309.291 973.33,264.596C971.652,233.374 956.575,177.653 874.439,155.23L869.539,153.891L868.907,149.406C863.81,113.255 830.649,67.872 768.052,47.976C704.505,27.778 653.597,58.332 643.204,71.132L636.222,79.733L630.312,70.164C620.597,54.426 589.582,18.173 511.669,21.398C440.094,24.362 408.415,81.914 407.103,84.363L402.557,92.834L394.883,85.976C379.908,72.608 321.954,42.965 254.369,80.828C189.472,117.183 204.179,167.969 204.337,168.478L207.359,178.33L196.014,176.705C192.739,176.258 115.819,166.267 77.965,229.863C39.999,293.638 91.82,344.913 92.351,345.421L100.424,353.227L89.31,356.328C86.907,357.008 29.915,373.837 24.63,432.853C20.503,478.949 34.782,522.122 62.831,548.336C87.022,570.947 121.342,581.965 164.831,581.071L176.905,580.826L172.295,590.588C161.421,613.62 167.914,655.605 216.409,676.818C274.503,702.221 322.493,666.638 329.535,658.577L333.699,653.813L339.597,657.183C352.785,664.72 419.879,674.98 455.506,632.841C490.702,591.211 468.011,546.744 456.297,533.124L432.423,505.365L466.09,523.416C481.09,531.455 502.99,533.478 521.89,528.564C536.577,524.747 556.747,514.997 569.395,490.245L576.355,476.635L583.311,490.646C593.267,510.709 618.898,545.364 678.656,551.649C737.765,557.918 782.891,523.943 796.055,497.816L800.455,489.081L808.318,496.037C816.887,503.614 862.973,539.314 929.36,498.056Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M164.45,581.093C168.821,580.604 195.946,573.542 211.278,553.997C221.78,540.606 224.635,523.998 219.76,504.639C219.76,504.639 247.605,532.462 221.626,564.658C202.884,586.185 171.934,594.399 165.717,594.554L164.791,581.073C164.703,581.077 164.591,581.078 164.45,581.093ZM164.45,581.093C164.448,581.093 164.447,581.094 164.445,581.094C164.447,581.094 164.448,581.093 164.45,581.093Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M213.575,553.555C222.582,551.317 231.34,551.047 239.325,552.226C255.233,554.572 268.058,562.655 273.619,572.298C273.619,572.298 245.37,554.286 218.136,566.552L213.575,553.555Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M310.41,312.188C362.408,280.533 430.339,286.541 475.605,326.802L464.987,336.16C425.111,300.696 365.213,295.443 319.316,323.38C275.107,350.295 256.595,417.442 280.48,464.26C307.052,516.356 357.867,539.991 394.332,536.42C394.332,536.42 358.776,552.43 324.184,531.648C299.807,517 279.907,495.504 266.63,469.474C239.543,416.376 259.999,342.877 310.41,312.188Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M330.494,520.536C338.379,526.429 342.731,534.458 344.7,543.023C348.021,570.08 326.463,580.957 326.463,580.957C333.323,569.603 338.064,541.076 322.267,529.275L330.494,520.536Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M170.498,451.168C205.064,437.661 243.653,440.477 276.359,458.882L268.492,470.196C240.229,454.292 206.893,451.865 177.027,463.541C153.301,472.81 139.19,488.122 137.509,498.456C137.509,498.456 134.627,468.393 170.498,451.168Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M122.051,418.399C124.266,425.291 153.678,451.572 184.056,447.87L186.251,459.326C180.771,459.993 175.279,459.883 169.892,459.174C162.413,458.189 155.13,456.047 148.357,453.205C119.151,440.422 122.051,418.399 122.051,418.399Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M83.89,343.578C93.152,340.443 119.954,335.902 149.393,342.543C189.622,353.407 200.664,388.305 200.664,388.305C157.717,337.275 90.386,355.973 89.732,356.197L83.89,343.578Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M403.057,462.599C412.109,479.736 430.231,504.115 465.183,522.924L466.14,523.442L458.465,534.863L457.586,534.391C419.225,513.745 399.276,486.879 389.295,467.98C380.508,451.336 378.406,434.828 379.826,419.97C384.383,385.186 414.727,373.954 414.727,373.954C402.573,387.065 383.727,425.985 403.057,462.599Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M322.397,408.686C322.397,408.686 360.323,391.713 392.009,422.14L382.306,429.591C369.382,416.512 339.485,407.613 322.397,408.686Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M690.853,274.028C735.995,311.348 753.013,373.417 740.587,407.967C734.08,426.056 723.599,439.419 712.073,449.263C685.57,471.905 649.16,462.799 649.16,462.799C651.515,462.516 707.46,455.197 726.054,403.494C736.835,373.524 720.838,316.972 680.654,283.744C656.633,263.884 614.846,244.873 552.154,267.086C489.758,289.191 475.391,326.522 474.322,353.951C472.725,395.045 499.768,434.915 514.438,442.054C514.438,442.054 481.579,436.735 467.588,400.693C461.928,385.953 458.458,369.521 459.1,353.048C460.315,321.737 476.396,279.231 546.1,254.536C616.21,229.702 663.52,251.425 690.853,274.028Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M574.041,405.452C577.929,399.868 582.591,394.925 588.017,390.626C622.566,366.135 666.154,382.688 666.154,382.688C627.495,382.845 600.885,392.975 587.077,412.802C566.225,442.734 580.371,485.117 583.223,490.479L569.482,495.902C565.551,488.513 549.669,440.434 574.041,405.452Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M586.976,325.57C586.976,325.57 612.868,328.978 618.672,355.583C621.052,372.019 615.594,386.416 603.3,396.116L594.44,387.377C606.107,378.168 607.013,365.603 605.724,356.686C603.595,342.023 594.518,329.351 586.976,325.57Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M708.183,440.658C738.696,458.976 721.687,492.93 721.687,492.93C725.772,471.083 707.178,453.368 701.816,450.565L708.183,440.658Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M793.227,434.541L805.895,427.418C827.426,456.566 813.55,497.025 809.929,503.784L796.126,497.678C798.539,493.179 810.551,457.997 793.227,434.541Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M843.106,432.778C872.723,432.517 895.844,420.562 906.531,399.983C906.531,399.983 907.929,428.423 880.172,440.043C869.358,444.009 857.092,446.148 843.754,446.269C837.448,446.323 831.171,445.926 825.002,445.114C790.398,440.559 759.118,422.884 743.733,398.136C712.965,348.644 746.651,293.716 782.802,273.093C802.223,262.012 823.22,255.668 842.44,254.262C876.032,253.772 883.4,276.414 883.4,276.414C864.944,262.029 824.51,265.578 791.345,284.501C760.84,301.905 731.064,350.173 757.023,391.933C772.169,416.297 807.583,433.1 843.106,432.778Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M901.023,427.592C919.082,437.647 914.375,459.502 914.375,459.502C914.382,455.074 911.352,449.929 905.845,445.02C898.256,438.251 889.946,435.226 888.088,435.21C888.281,435.213 882.328,424.936 882.328,424.936C886.457,422.572 892.743,423.469 901.023,427.592Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M841.501,320.927C869.016,346.791 923.886,334.774 930.727,332.154L937.161,344.56C930.833,346.984 903.788,353.308 876.093,349.661C844.437,342.756 841.501,320.927 841.501,320.927Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M856.8,381.78C856.8,381.78 854.677,356.846 873.976,346.331C881.356,342.487 889.96,339.571 899.7,338.12L902.276,349.522C873.542,353.801 859.967,372.495 856.8,381.78Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M609.849,376.237C641.704,348.782 649.339,320.22 649.339,320.22C651.29,328.519 647.387,350.181 634.449,367.575C629.624,374.059 623.544,379.948 616.045,384.331L609.849,376.237Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M834.92,234.947C840.865,227.575 848.327,221.775 857.035,217.633C881.755,204.76 908.313,222.518 908.313,222.518C888.901,219.963 862.264,221.245 845.624,241.87L834.92,234.947Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M831.928,192.93C831.928,192.93 855.516,211.969 848.036,239.116C840.815,259.861 823.515,271.361 810.178,275.615L804.608,262.904C806.453,262.317 849.598,247.868 831.928,192.93Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M730.919,99.579C730.919,99.579 763.085,113.201 772.811,151.075C776.7,170.409 776.278,191.455 771.605,214.455C761.626,263.558 702.305,281.445 679.251,280.632L679.359,267.156C693.228,267.644 747.798,255.006 756.611,211.647C765.902,165.932 757.739,130.323 730.919,99.579Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M762.546,160.453C800.164,134.33 864.452,138.777 878.343,142.312L874.583,155.268C864.309,152.659 804.878,148.468 772.188,171.172L762.546,160.453Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M630.258,70.076L643.679,64.083C644.1,64.79 673.076,114.431 662.623,161.021C654.783,197.124 613.774,203.603 613.774,203.603C683.262,160.803 630.799,70.977 630.258,70.076Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M659.485,165.881C663.907,186.681 682.077,207.853 695.622,213.847C695.622,213.847 651.903,202.29 646.659,167.659L659.485,165.881Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M539.576,186.699C541.122,210.599 554.444,240.07 576.078,247.622L570.893,260.15C542.825,250.352 526.242,215.953 524.367,187.035C523.494,173.554 526.287,151.23 536.041,131.967C553.129,100.656 581.582,110.001 581.582,110.001C548.902,118.029 538.098,163.918 539.576,186.699Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M493.292,103.391C493.292,103.391 525.2,108.011 535.526,135.513C538.424,143.235 540.253,152.053 540.204,161.981L527.145,161.553C527.336,122.786 493.632,103.579 493.292,103.391Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M276.948,160.286C277.399,160.303 311.64,161.895 336.502,187.86C344.788,196.516 352.036,207.878 356.639,222.792C374.898,281.945 349.413,306.144 319.144,323.482C309.497,329.013 295.604,330.674 280.619,328.701C269.215,327.2 257.176,323.595 245.876,317.989C231.133,310.674 218.952,300.706 209.701,288.689C189.323,255.458 204.276,225.968 204.276,225.968C204.379,274.859 234.842,297.268 252.991,306.273C276.57,317.97 300.259,318.002 310.581,312.087C335.503,297.81 358.45,279.395 341.951,225.95C326.56,176.089 276.948,160.286 276.948,160.286Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M351.881,137.831C351.881,137.831 334.475,159.829 340.948,195.549L328.057,197.016C320.399,154.765 351.881,137.831 351.881,137.831Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M153.949,295.541C153.949,295.541 171.351,271.232 201.075,276.773C207.935,278.053 215.116,280.367 222.406,284.126L216.118,294.07C184.328,277.698 154.249,295.363 153.949,295.541Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M197.109,163.312C203.351,163.568 225.129,169.866 245.94,182.735C292.963,211.559 273.952,244.742 273.952,244.742C272.306,198.284 202.893,177.207 196.827,176.78L197.109,163.312Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M405.259,76.415C409.176,79.606 443.528,109.023 443.781,158.317C444.017,204.496 406.987,221.615 406.987,221.615C407.205,221.492 428.805,207.829 428.547,157.937C428.324,114.494 398.54,88.97 395.144,86.2L405.259,76.415Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M442.447,165.707C445.65,183.47 472.051,205.798 482.678,207.829C482.678,207.829 438.374,205.199 429.554,167.162L442.447,165.707Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M528.269,208.508L543.276,206.633C543.59,208.225 550.613,245.979 521.528,275.478C495.28,302.101 458.033,314.248 418.869,309.091C417.654,308.932 416.445,308.757 415.226,308.561C378.701,302.768 354.675,288.29 347.06,276.512L360.223,270.105C364.706,277.043 384.206,290.018 417.431,295.288C452.627,300.856 486.324,290.348 509.913,266.423C534.272,241.715 528.331,208.838 528.269,208.508Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M346.454,612.287C346.454,612.287 364.459,630.137 350.49,655.895C346.095,662.684 342.024,666.615 341.627,666.989L330.334,657.619C330.525,657.439 349.385,639.196 346.454,612.287Z"/>
<path {{ $attributes->merge(['class' => 'text-accent']) }} d="M746.169,21.499C756.017,22.795 765.878,25.001 775.478,28.052C846.582,50.652 881.597,100.292 890.828,137.976C955.354,158.86 994.038,204.652 997.236,264.281C999.012,297.296 988.628,322.18 966.359,338.423C979.244,347.991 988.032,359.715 992.343,368.627C996.839,377.939 1002.72,402.821 999.332,429.929C996.187,455.119 983.953,490.419 943.534,515.538C921.02,529.53 898.079,537.216 875.214,538.444C874.524,557.481 866.669,577.821 852.635,596.528C833.067,622.623 803.096,642.513 769.991,651.49C766.899,656.306 763.357,660.861 760.17,664.955C752.191,675.212 745.299,684.072 745.21,693.867C745.015,715.994 760.064,728.832 760.639,729.311L768.69,735.812C770.809,737.518 771.894,740.002 771.599,742.476C771.308,744.951 769.679,747.105 767.227,748.26L695.574,782.012C693.919,782.79 692.07,783.039 690.288,782.805C688.153,782.524 686.117,781.546 684.646,779.964L680.573,775.585C666.603,760.562 658.245,747.077 650.775,727.481C649.287,723.582 648.322,719.717 647.301,715.615C643.246,699.342 639.415,683.975 602.531,669.984C582.529,662.395 566.583,652.959 555.043,641.893C536.815,644.647 517.346,644.794 498.397,642.299C492.15,641.477 486.064,640.365 480.225,638.984C478.528,641.351 476.724,643.657 474.834,645.894C450.844,674.272 417.216,682.715 393.226,684.799C381.934,685.782 370.071,685.527 358.921,684.059C351.917,683.137 345.364,681.766 339.649,680.041C319.088,696.008 283.075,711.345 242.877,706.053C230.564,704.432 218.283,700.97 206.372,695.761C181.07,684.694 162.338,667.791 152.203,646.89C145.306,632.663 142.634,616.673 144.507,601.785C139.806,601.472 135.2,601.017 130.725,600.428C96.286,595.894 67.686,583.183 45.707,562.646C12.903,531.987 -3.897,482.581 0.767,430.49C5.257,380.339 40.511,354.902 62.285,343.855C46.313,320.624 24.457,273.681 56.708,219.503C89.53,164.361 147.812,155.406 179.02,154.885C179.027,132.85 187.435,92.873 241.101,62.81C273.272,44.788 306.984,37.939 341.311,42.462C365.525,45.65 384.1,53.896 395.041,59.951C402.448,50.197 414.871,36.551 433.253,24.573C456.287,9.566 482.031,1.374 509.768,0.225C523.796,-0.356 537.288,0.189 549.873,1.846C595.648,7.874 623.192,27.2 639.177,44.655C661.749,29.219 700.506,15.487 746.169,21.499ZM745.165,29.122C697.38,22.831 657.024,39.885 637.949,55.854C625.287,39.581 598.795,16.044 548.865,9.469C537.348,7.953 524.59,7.339 510.461,7.925C452.387,10.329 414.353,45.661 397.781,70.781C385.798,62.844 365.594,53.414 340.307,50.085C313.364,46.538 280.651,49.905 245.926,69.359C189.011,101.247 186.254,144.267 188.094,162.668C163.458,161.805 98.829,165.496 64.437,223.27C30.466,280.339 60.508,328.89 74.754,346.889C55.606,354.896 14.204,378.198 9.445,431.35C4.976,481.26 20.86,528.397 51.934,557.44C72.538,576.699 99.287,588.534 131.729,592.805C139.117,593.778 146.795,594.358 154.77,594.547C147.126,623.344 158.439,666.305 210.023,688.874C221.738,693.999 233.085,697.009 243.88,698.43C288.704,704.331 324.069,682.794 337.482,671.143C343.311,673.373 351.058,675.269 359.925,676.436C369.639,677.715 380.709,678.122 392.095,677.127C414.398,675.189 445.634,667.37 467.806,641.145C470.947,637.429 473.669,633.693 476.048,629.954C483.187,631.956 491.044,633.576 499.401,634.676C517.749,637.092 538.341,637.065 558.132,633.497C566.971,643.025 581.569,653.745 605.736,662.914C646.914,678.533 651.633,697.476 655.797,714.19C656.764,718.071 657.68,721.736 659.01,725.23C666.101,743.826 674.012,756.601 687.22,770.803L691.292,775.182L762.945,741.43L754.889,734.933C754.703,734.781 736.275,719.609 736.507,693.555C736.616,681.44 744.547,671.243 752.948,660.444C756.865,655.411 760.818,650.309 763.97,644.946C800.832,635.856 828.982,613.927 845.338,592.12C860.499,571.901 867.745,550.109 866.404,530.89C873.884,531.027 881.855,530.414 890.261,528.779C905.055,525.902 921.196,519.861 938.38,509.182C1009.87,464.746 991.028,385.267 984.357,371.461C978.94,360.259 967.066,346.748 951.448,338.201C971.373,327.501 990.859,307.537 988.544,264.393C985.435,206.49 947.161,162.729 883.274,143.713C875.616,104.13 839.209,56.412 772.778,35.297C763.372,32.308 754.136,30.303 745.165,29.122Z"/>
</svg>
<span class="sr-only">Investbrain</span>
</a>
@@ -0,0 +1,79 @@
@props([
'id' => null,
'title' => null,
'icon' => null,
'spinner' => null,
'link' => null,
'route' => null,
'external' => false,
'noWireNavigate' => false,
'badge' => null,
'badgeClasses' => null,
'badge' => false,
'separator' => false,
'enabled' => true,
])
@aware(['activateByRoute' => false, 'activeBgColor' => 'bg-neutral text-neutral-content'])
@php
$spinnerTarget = $spinner == true ? $attributes->whereStartsWith('wire:click')->first() : $spinner;
@endphp
@if (!$enabled)
{{-- DISABLED --}}
@else
{{-- ENABLED --}}
<li
title="{{ $title }}"
{{ $attributes->class(["my-0.5 hover:text-inherit rounded-md"]) }}
>
<a
@if($link)
href="{{ $link }}"
@if($activateByRoute)
wire:current="{{ $activeBgColor }}"
@endif
@if($external)
target="_blank"
@endif
@if(!$external && !$noWireNavigate)
{{ $attributes->wire('navigate')->value() ? $attributes->wire('navigate') : 'wire:navigate' }}
@endif
@endif
@if($spinner)
wire:target="{{ $spinnerTarget }}"
wire:loading.attr="disabled"
@endif
>
{{-- SPINNER --}}
@if($spinner)
<span wire:loading wire:target="{{ $spinnerTarget }}" class="loading loading-spinner w-5 h-5"></span>
@endif
@if($icon)
<span class="block -mt-0.5" @if($spinner) wire:loading.class="hidden" wire:target="{{ $spinnerTarget }}" @endif>
<x-ui.icon :name="$icon" />
</span>
@endif
@if($title || $slot->isNotEmpty())
<span class="whitespace-nowrap">
@if($title)
{{ $title }}
@if($badge)
<span class="badge badge-sm ml-2 {{ $badgeClasses }}">{{ $badge }}</span>
@endif
@else
{{ $slot }}
@endif
</span>
@endif
</a>
</li>
@endif
@@ -0,0 +1,28 @@
@props([
'title' => null,
'icon' => null,
'separator' => false,
'activateByRoute' => false,
'activeBgColor' => 'bg-base-100'
])
<ul {{ $attributes->class(["menu rounded-md"]) }} >
@if($title)
<li class="menu-title text-inherit uppercase">
<div class="flex items-center gap-2">
@if($icon)
<x-ui.icon :name="$icon" class="w-4 h-4 inline-flex" />
@endif
{{ $title }}
</div>
</li>
@endif
@if($separator)
<hr class="mb-3"/>
@endif
{{ $slot }}
</ul>
@@ -0,0 +1,116 @@
@props([
'key' => 'modal',
'title' => null,
'subtitle' => null,
'persistent' => false,
'withoutTrapFocus' => false,
'boxClass' => '',
'noCard' => false,
'shortcut' => null,
'noTeleport' => false,
])
@if(!$noTeleport)
<template x-teleport="body">
@endif
<dialog
x-data="{
@if (!empty($attributes->whereStartsWith('wire:model')->first()))
init(){
this.$watch('wireModelValue', value => value ? this.show() : this.close())
},
wireModelValue: $wire.entangle('{{ $attributes->whereStartsWith('wire:model')->first() }}').live,
@endif
open: false,
close() {
this.open = false;
this.$el.close()
},
cancel() {
@if($persistent)
this.$refs.modalContent.classList.add('wiggle')
this.$refs.modalContent.addEventListener('animationend', (e) => {
this.$refs.modalContent.classList.remove('wiggle')
})
@else
this.close()
@endif
},
show() {
this.open = true;
@if($persistent)
this.$el.showModal();
@else
this.$el.show();
@endif
}
}"
@close="close()"
:open="open"
{{
$attributes->filter(
fn ($value, $key) => !Str::startsWith($key, 'wire:model')
)->class(["modal duration-50 z-50"])
}}
id="{{ $key }}"
x-on:toggle-{{ $key }}.window="open ? close() : show();"
@if($shortcut)
@keydown.window.prevent.{{ $shortcut }}="show();"
@endif
@keydown.escape.prevent.stop="cancel()"
@if(!$withoutTrapFocus)
x-trap="open"
x-bind:inert="!open"
@endif
>
{{-- BACKDROP --}}
<div
@click.prevent.stop="cancel()"
class="absolute inset-0 w-full h-full bg-base-300/50"
x-show="open"
></div>
{{-- MODAL CONTENT --}}
<div x-ref="modalContent" class="modal-box overflow-y-visible p-0 {{ $boxClass }}">
@if(!$noCard)
<x-ui.card
:title="$title"
:subtitle="$subtitle"
expanded="true"
>
@if (!$persistent && !$noCard)
<x-ui.button
icon="o-x-mark"
title="{{ __('Close') }}"
class="absolute top-4 right-4 btn-ghost btn-circle btn-sm z-10"
@click="close()"
tabindex="-999"
/>
@endif
{{ $slot }}
</x-ui.card>
@else
{{ $slot }}
@endif
</div>
</dialog>
@if(!$noTeleport)
</template>
@endif
@@ -0,0 +1,14 @@
@props([
'value' => 0,
'max' => 100,
'indeterminate' => null,
])
<progress
{{ $attributes->class("progress") }}
@if(!$indeterminate)
value="{{ $value }}"
max="{{ $max }}"
@endif
></progress>
@@ -1,5 +1,5 @@
<div {{ $attributes->class(['my-6' => !$attributes->has('class'), 'h-4 sm:h-auto' => $attributes->has('hide-on-mobile')]) }}>
<hr class="{{ $attributes->has('hide-on-mobile') ? 'hidden sm:block' : '' }} border-t border-gray-200 dark:border-gray-700" />
<hr class="{{ $attributes->has('hide-on-mobile') ? 'hidden sm:block' : '' }} border-t border-gray-200/50 dark:border-gray-700/50" />
</div>
@@ -0,0 +1,112 @@
@props([
'id' => null,
'label' => null,
'icon' => null,
'hint' => null,
'hintClass' => 'label-text-alt text-gray-400 ps-1 mt-2',
'placeholder' => null,
'optionValue' => 'id',
'optionLabel' => 'name',
'options' => array(),
'prepend' => null,
'append' => null,
'errorField' => null,
'errorClass' => 'text-red-500 label-text-alt p-1',
'omitError' => false,
'firstErrorOnly' => false,
])
@php
$modelName = $attributes->whereStartsWith('wire:model')->first();
$errorFieldName = $errorField ?? $modelName;
$id = $id == $modelName ? $modelName : "{$id}{$modelName}";
@endphp
<div>
{{-- STANDARD LABEL --}}
@if($label)
<label for="{{ $id }}" class="pt-0 label label-text font-semibold">
<span>
{{ $label }}
@if($attributes->get('required'))
<span class="text-error">*</span>
@endif
</span>
</label>
@endif
{{-- PREPEND/APPEND CONTAINER --}}
@if($prepend || $append)
<div class="flex">
@endif
{{-- PREPEND --}}
@if($prepend)
<div class="rounded-s-lg flex items-center bg-base-200">
{{ $prepend }}
</div>
@endif
<div class="relative flex-1">
<select
id="{{ $id }}"
{{ $attributes->whereDoesntStartWith('class') }}
{{ $attributes->class([
'select select-primary w-full font-normal',
'ps-10' => ($icon),
'rounded-s-none' => $prepend,
'rounded-e-none' => $append,
'border border-dashed' => $attributes->has('readonly') && $attributes->get('readonly') == true,
'select-error' => $errors->has($errorFieldName)
])
}}
>
@if($placeholder)
<option value="">{{ $placeholder }}</option>
@endif
@foreach ($options as $option)
<option value="{{ data_get($option, $optionValue) }}" @if(data_get($option, 'disabled')) disabled @endif>{{ data_get($option, $optionLabel) }}</option>
@endforeach
</select>
{{-- ICON --}}
@if($icon)
<x-ui.icon :name="$icon" class="z-60 absolute pointer-events-none top-1/2 -translate-y-1/2 start-3 text-gray-400" />
@endif
</div>
{{-- APPEND --}}
@if($append)
<div class="rounded-e-lg flex items-center bg-base-200">
{{ $append }}
</div>
@endif
{{-- END: APPEND/PREPEND CONTAINER --}}
@if($prepend || $append)
</div>
@endif
{{-- ERROR --}}
@if(!$omitError && $errors->has($errorFieldName))
@foreach($errors->get($errorFieldName) as $message)
@foreach(Arr::wrap($message) as $line)
<div class="text-red-500 label-text-alt p-1" x-classes="text-red-500 label-text-alt p-1">{{ $line }}</div>
@break($firstErrorOnly)
@endforeach
@break($firstErrorOnly)
@endforeach
@endif
{{-- HINT --}}
@if($hint)
<div class="{{ $hintClass }}" x-classes="label-text-alt text-gray-400 ps-1 mt-2">{{ $hint }}</div>
@endif
</div>
@@ -0,0 +1,147 @@
@props([
'id' => null,
'shortcut' => "meta.g",
'searchText' => "Search ...",
'noResultsText' => "Nothing found.",
'url' => null,
'fallbackAvatar' => null,
])
@php
$url = $url ?? route('spotlight', absolute: false);
@endphp
<div x-data="{
loading: false,
value: '',
results: [],
maxDebounce: 250,
debounceTimer: null,
controller: new AbortController(),
query: '',
searchedWithNoResults: false,
init(){
this.$watch('value', value => {
this.loading = true
this.debounce(() => this.search(), this.maxDebounce)
})
},
debounce(fn, waitTime) {
clearTimeout(this.debounceTimer)
this.debounceTimer = setTimeout(() => fn(), waitTime)
},
async search() {
if (this.value == '') {
this.results = [];
this.loading = false
return
}
try {
this.controller?.abort()
this.controller = new AbortController();
let response = await fetch(`{{$url}}?search=${this.value}&${this.query}`, { signal: this.controller.signal })
this.results = await response.json()
} catch(e) {
console.log(e)
return
}
this.loading = false
Object.keys(this.results).length
? this.searchedWithNoResults = false
: this.searchedWithNoResults = true
}
}">
<x-ui.modal
key="spotlight"
class="backdrop-blur-sm shadow-xl"
box-class="absolute top-10 lg:top-24 w-full lg:max-w-3xl "
no-card="true"
shortcut="slash"
@keydown.up="$focus.wrap().previous()"
@keydown.down="$focus.wrap().next()"
>
<div class="relative">
{{-- CLOSE --}}
<x-ui.button
title="{{ __('Close') }} (esc)"
class="absolute top-1/2 -translate-y-1/2 right-4 btn btn-ghost hover:bg-transparent border-none shadow-none btn-xs select-none z-50"
@click="close()"
@focus="$focus.lastFocused().focus()"
>
<kbd class="kbd kbd-sm">ESC</kbd>
</x-ui.button>
{{-- INPUT --}}
<x-ui.input
id="{{ $id }}"
icon="o-magnifying-glass"
x-model="value"
placeholder=" {{ $searchText }}"
class="text-xl flex w-full input my-2 py-6 border-none outline-none shadow-none border-transparent focus:shadow-none focus:outline-none focus:border-transparent"
@focus="$el.focus()"
autofocus
tabindex="1"
/>
{{-- PROGRESS --}}
<x-ui.progress
x-show="loading"
class="z-60 absolute left-0 bottom-0 w-full progress progress-secondary h-[2px]"
indeterminate="true"
/>
</div>
{{-- NO RESULTS --}}
<template x-if="searchedWithNoResults && value != ''">
<div class="bg-base-100 text-base-content/50 p-4 spotlight-element">{{ $noResultsText }}</div>
</template>
{{-- RESULTS --}}
<div
@click="close()"
@keydown.enter="close()"
>
<template x-for="(item, index) in results" :key="index">
{{-- ITEM --}}
<a x-bind:href="item.link" class="spotlight-element" wire:navigate tabindex="0">
<div class="p-4 bg-base-100 hover:bg-base-200 rounded-md">
<div class="flex gap-3 items-center">
{{-- ICON --}}
<template x-if="item.icon">
<div x-html="item.icon"></div>
</template>
{{-- AVATAR --}}
<template x-if="item.avatar && !item.icon">
<div>
<img :src="item.avatar" class="rounded-full w-11 h-11" @if($fallbackAvatar) onerror="this.src='{{ $fallbackAvatar }}'" @endif />
</div>
</template>
<div class="flex-1 overflow-hidden whitespace-nowrap text-ellipsis truncate w-0">
{{-- NAME --}}
<div x-text="item.name" class="font-semibold truncate"></div>
{{-- DESCRIPTION --}}
<template x-if="item.description">
<div x-text="item.description" class="text-base-content/50 text-sm truncate"></div>
</template>
</div>
</div>
</div>
</a>
</template>
<div x-show="results.length" class="mb-3"></div>
</div>
</x-ui.modal>
</div>
@@ -7,7 +7,7 @@
])
<div {{ $attributes->class([]) }}>
<!-- STANDARD LABEL -->
{{-- STANDARD LABEL --}}
@if($label)
<label for="{{ $uuid }}" class="pt-0 label label-text font-semibold">
<span>
@@ -52,7 +52,7 @@
@endforeach
@endif
<!-- HINT -->
{{-- HINT --}}
@if($hint)
<div x-classes="label-text-alt text-gray-400 py-1 pb-0">{{ $hint }}</div>
@endif
@@ -0,0 +1,42 @@
@props([
'id' => null,
'darkTheme' => 'dark',
'lightTheme' => 'light',
'hidden' => false,
])
<div class="{{ $hidden ? 'hidden' : '' }}">
<label
for="{{ $id }}"
x-data="{
theme: $persist(window.matchMedia('(prefers-color-scheme: dark)').matches ? '{{ $darkTheme }}' : '{{ $lightTheme }}').as('theme'),
init() {
if (this.theme == '{{ $darkTheme }}') {
this.$refs.sun.classList.add('swap-off');
this.$refs.moon.classList.add('swap-on');
} else {
this.$refs.sun.classList.add('swap-on');
this.$refs.moon.classList.add('swap-off');
}
this.setToggle()
},
setToggle() {
document.documentElement.setAttribute('data-theme', this.theme)
this.$dispatch('theme-changed', this.theme)
},
toggle() {
this.theme = this.theme == '{{ $lightTheme }}' ? '{{ $darkTheme }}' : '{{ $lightTheme }}'
this.setToggle()
}
}"
{{ $attributes->class(["swap swap-rotate"]) }}
>
<input id="{{ $id }}" type="checkbox" class="theme-controller opacity-0" @click="toggle()" :value="theme" />
<x-ui.icon x-ref="sun" name="o-sun" x-cloak />
<x-ui.icon x-ref="moon" name="o-moon" x-cloak />
</label>
</div>
<script>
document.documentElement.setAttribute("data-theme", localStorage.getItem("theme")?.replaceAll("\"", ""))
</script>
@@ -0,0 +1,59 @@
@props([
'position' => 'toast-top toast-end'
])
<div>
@persist('toast')
<div
x-cloak
x-data="{ show: false, timer: '', toast: ''}"
@toast.window="
clearTimeout(timer);
toast = $event.detail.toast
setTimeout(() => show = true, 100);
timer = setTimeout(() => show = false, $event.detail.toast.timeout);
">
<div
class="toast rounded-md fixed cursor-pointer z-[999]"
:class="toast.position || '{{ $position }}'"
x-show="show"
x-classes="alert alert-success alert-warning alert-error alert-info top-10 end-10 toast toast-top toast-bottom toast-center toast-end toast-middle toast-start"
@click="show = false"
>
<div class="alert gap-2" :class="toast.css">
<div x-html="toast.icon"></div>
<div class="grid">
<div x-html="toast.title" class="font-bold"></div>
<div x-html="toast.description" class="text-xs"></div>
</div>
</div>
</div>
</div>
<script>
window.toast = function(payload){
window.dispatchEvent(new CustomEvent('toast', {detail: payload}))
}
document.addEventListener('livewire:init', () => {
Livewire.hook('request', ({fail}) => {
fail(({status, content, preventDefault}) => {
try {
let result = JSON.parse(content);
if (result?.toast && typeof window.toast === "function") {
window.toast(result);
}
if ((result?.prevent_default ?? false) === true) {
preventDefault();
}
} catch (e) {
console.log(e)
}
})
})
})
</script>
@endpersist
</div>
@@ -0,0 +1,60 @@
@props([
'id' => null,
'label' => null,
'right' => false,
'hint' => null,
'hintClass' => 'label-text-alt text-gray-400 py-1 pb-0',
'tight' => false,
'errorField' => null,
'errorClass' => 'text-red-500 label-text-alt p-1',
'omitError' => false,
'firstErrorOnly' => false,
])
@php
$modelName = $attributes->whereStartsWith('wire:model')->first();
$errorFieldName = $errorField ?? $modelName;
$id = $id == $modelName ? $modelName : "{$id}{$modelName}";
@endphp
<div>
<label for="{{ $id }}" class="flex items-center gap-3 cursor-pointer font-semibold">
@if($right)
<span @class(["flex-1" => !$tight])>
{{ $label}}
@if($attributes->get('required'))
<span class="text-error">*</span>
@endif
</span>
@endif
<input id="{{ $id }}" type="checkbox" {{ $attributes->whereDoesntStartWith('class') }} {{ $attributes->class(['toggle toggle-primary']) }} />
@if(!$right)
{{ $label}}
@if($attributes->get('required'))
<span class="text-error">*</span>
@endif
@endif
</label>
{{-- ERROR --}}
@if(!$omitError && $errors->has($errorFieldName))
@foreach($errors->get($errorFieldName) as $message)
@foreach(Arr::wrap($message) as $line)
<div class="{{ $errorClass }}" x-classes="text-red-500 label-text-alt p-1">{{ $line }}</div>
@break($firstErrorOnly)
@endforeach
@break($firstErrorOnly)
@endforeach
@endif
{{-- HINT --}}
@if($hint)
<div class="{{ $hintClass }}" x-classes="label-text-alt text-gray-400 py-1 pb-0">{{ $hint }}</div>
@endif
</div>