Project Code Snapshot

This page contains the full source code of all project files for backup and review purposes.

tsconfig.json
{ "compilerOptions": { "target": "ES2017", "lib": [ "dom", "dom.iterable", "esnext" ], "allowJs": true, "skipLibCheck": true, "strict": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "react-jsx", "incremental": true, "plugins": [ { "name": "next" } ], "paths": { "@/*": [ "./src/*" ] } }, "include": [ "next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts" ], "exclude": [ "node_modules" ] }
tailwind.config.ts
import type {Config} from 'tailwindcss'; export default { darkMode: ['class'], content: [ './src/pages/**/*.{js,ts,jsx,tsx,mdx}', './src/components/**/*.{js,ts,jsx,tsx,mdx}', './src/app/**/*.{js,ts,jsx,tsx,mdx}', ], theme: { extend: { fontFamily: { body: ['"PT Sans"', 'sans-serif'], headline: ['"Space Grotesk"', 'sans-serif'], code: ['monospace'], }, colors: { background: 'hsl(var(--background))', foreground: 'hsl(var(--foreground))', card: { DEFAULT: 'hsl(var(--card))', foreground: 'hsl(var(--card-foreground))', }, popover: { DEFAULT: 'hsl(var(--popover))', foreground: 'hsl(var(--popover-foreground))', }, primary: { DEFAULT: 'hsl(var(--primary))', foreground: 'hsl(var(--primary-foreground))', }, secondary: { DEFAULT: 'hsl(var(--secondary))', foreground: 'hsl(var(--secondary-foreground))', }, muted: { DEFAULT: 'hsl(var(--muted))', foreground: 'hsl(var(--muted-foreground))', }, accent: { DEFAULT: 'hsl(var(--accent))', foreground: 'hsl(var(--accent-foreground))', }, destructive: { DEFAULT: 'hsl(var(--destructive))', foreground: 'hsl(var(--destructive-foreground))', }, border: 'hsl(var(--border))', input: 'hsl(var(--input))', ring: 'hsl(var(--ring))', chart: { '1': 'hsl(var(--chart-1))', '2': 'hsl(var(--chart-2))', '3': 'hsl(var(--chart-3))', '4': 'hsl(var(--chart-4))', '5': 'hsl(var(--chart-5))', }, sidebar: { DEFAULT: 'hsl(var(--sidebar-background))', foreground: 'hsl(var(--sidebar-foreground))', primary: 'hsl(var(--sidebar-primary))', 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))', accent: 'hsl(var(--sidebar-accent))', 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))', border: 'hsl(var(--sidebar-border))', ring: 'hsl(var(--sidebar-ring))', }, }, borderRadius: { lg: 'var(--radius)', md: 'calc(var(--radius) - 2px)', sm: 'calc(var(--radius) - 4px)', }, keyframes: { 'accordion-down': { from: { height: '0', }, to: { height: 'var(--radix-accordion-content-height)', }, }, 'accordion-up': { from: { height: 'var(--radix-accordion-content-height)', }, to: { height: '0', }, }, }, animation: { 'accordion-down': 'accordion-down 0.2s ease-out', 'accordion-up': 'accordion-up 0.2s ease-out', }, }, }, plugins: [require('tailwindcss-animate')], } satisfies Config;
postcss.config.mjs
/** @type {import('postcss-load-config').Config} */ const config = { plugins: { tailwindcss: {}, }, }; export default config;
package.json
{ "name": "nextn", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev --turbopack -p 9002", "genkit:dev": "genkit start -- tsx src/ai/dev.ts", "genkit:watch": "genkit start -- tsx --watch src/ai/dev.ts", "build": "NODE_ENV=production next build", "start": "next start", "lint": "next lint", "typecheck": "tsc --noEmit" }, "dependencies": { "@genkit-ai/google-genai": "^1.20.0", "@genkit-ai/next": "^1.20.0", "@hookform/resolvers": "^4.1.3", "@radix-ui/react-accordion": "^1.2.3", "@radix-ui/react-alert-dialog": "^1.1.6", "@radix-ui/react-avatar": "^1.1.3", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-collapsible": "^1.1.11", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", "@radix-ui/react-label": "^2.1.2", "@radix-ui/react-menubar": "^1.1.6", "@radix-ui/react-popover": "^1.1.6", "@radix-ui/react-progress": "^1.1.2", "@radix-ui/react-radio-group": "^1.2.3", "@radix-ui/react-scroll-area": "^1.2.3", "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-slider": "^1.2.3", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.1.3", "@radix-ui/react-tabs": "^1.1.3", "@radix-ui/react-toast": "^1.2.6", "@radix-ui/react-tooltip": "^1.1.8", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^3.6.0", "dotenv": "^16.5.0", "embla-carousel-react": "^8.6.0", "firebase": "^11.9.1", "genkit": "^1.20.0", "glob": "^11.0.0", "lucide-react": "^0.475.0", "next": "15.3.3", "patch-package": "^8.0.0", "react": "^18.3.1", "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", "react-hook-form": "^7.54.2", "recharts": "^2.15.1", "tailwind-merge": "^3.0.1", "tailwindcss-animate": "^1.0.7", "zod": "^3.24.2" }, "devDependencies": { "@types/glob": "^8.1.0", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", "genkit-cli": "^1.20.0", "postcss": "^8", "tailwindcss": "^3.4.1", "typescript": "^5" } }
next.config.ts
import type {NextConfig} from 'next'; const nextConfig: NextConfig = { /* config options here */ typescript: { ignoreBuildErrors: true, }, eslint: { ignoreDuringBuilds: true, }, images: { remotePatterns: [ { protocol: 'https', hostname: 'placehold.co', port: '', pathname: '/**', }, { protocol: 'https', hostname: 'images.unsplash.com', port: '', pathname: '/**', }, { protocol: 'https', hostname: 'picsum.photos', port: '', pathname: '/**', }, ], }, }; export default nextConfig;
next-env.d.ts
/// <reference types="next" /> /// <reference types="next/image-types/global" /> import "./.next/dev/types/routes.d.ts"; import "./.next/dev/types/root-params.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
icon.png
�PNG  IHDR\r�fIDATx��%U�5�V�׹E���Q1KF��V���� Fŀ����͒ #����2�n�,J� �%t~�^��}��u��t��}�=���>Uuj�:�T���v�RFE �6yۡS��i��i����YC�L���͡�7���)�p��i� ��ѝ�'ot��p����ɏHCyD,�>�H� >,���i�� N���Qi�'J��I�Js�C‰�NC�E����胏IC�h�ش���?�.yB/�oZtB��%�������I��iQW� ��K���l������iPX$t��d{߿��QxB|�m��xb<Fx�`�œdo�'-<�I�.|���<f�[��.|Ϧ��9��'i�]����]O>U�M��z궣��`�N�ԩ� �:�uçM=�>mڏ�O�6T��/|ı�>�$x(���ؗ�. ��$aa<A���!���ҍ�E�6AD.4���՘����n�M �!@� P�Rmc' +3l�Q�l$�T���m�I��*��Y6vk�A����$\HK��@RL6RR�%��7!�$�xov!���/U�CA)߱`��4�b�]OR"��ໞr�ໟ��~�f؀���)S�7|�OԧM�Vrn �F� �pdJigɎ�QEWH�H�����{�A�$m���}�J�"�q �e�v��>b�EU��ߵɎ؇�j��1�?۳fn@M�|�ѷi�σ )[���B�| �X�c�[WW�����3�1=.��E������jأ���DT�F�P�Q��$��~;'�H$^�:�6�Χ�UR�V���w=�y�r���uH�=b�t�ԗ�>�������WiEx���=$'��E��6�h�� ݖ�J�d��$��vq�tj�n��I׶j�=8 Ep���Fu )M6b Ѐ @2`�xء".;3t����@UM�n��t_u�qRL�8T �� 2k�Y#\(ž5�@ كB�<�.f�� ���d%�=����U��|ڃ�G?�…�z�K�[���i�i�w �1�çO~k}���j ��F�&^���)�+������@��|b��%�D& -=����(�c�;�!ew� ���e�����>ށm��� E����������J��]��:��2i��&o��d��6����v��^r�m6�[V�d�"�Me���}m �Fժ��76S�յG�����]��6So�������c=����0�U��k������g���%�ZI�E�oNX����x�e�G?�� ��gh���� �1���gL�P}Ɣ��T��� <�d�YT��@�� ��l.{�ѴJ�p�!(j- �R��2���V"�� 4����s�aG.�݉ �jJ�Zf)A2tfK� ͭ����a����l��ˇ6� q6v�����B����4]�i���6�"��@cA.��" �_p�4� m��ud=Zq�I�IY q`BunE�Zw�-<��'�;꩏�zP���>6qS����ğ+럘v�����3{Ȧ���i^2 ���d� Z�m�M>@�[>UY�]���>��OWQ_�s ՍC��!��u����#]b�����<�Ƨ���c���e�P�Q5�bW�ء ��7��Q,U{��y2��n,j<.}|�y�VBjo2�[w_�ɬ*�j譔u��Xc��+��+&2�ׅ���3��e�5�>TU��ugp��#���v���u2�S�=}��IMu����X�/1 ͭ4q� I@�BW@cG�0 l��P�̄P��"$�†S {9R /i����K�PMcG�.�*���qK�!�Kے)�2 T��*aQHJ�B�k�.��э�Y%sK��-�I��� ��MBj�A�=v[��]�<���b�8A���I��% #e1BCY�"�*]s�q��J���#��t9י�t�J�����>�Su5���(So����A�ZDU�ٽ�}�2�ʨj"(�KQϫ�-�m�Hv�[��v���W�����׶2t�ڍ�(�����6�"��o�����k�>�8�����fH�]��m�+�jxt�l3�/U1m�6:5���Y>+���� R\���Ae37�Kz�?j\�{���Um�Gm�a�lt��;P+]�p')r��D�ۖѫ�ǦK���b_��/�����y m,�1���������Gl��z�3u�H��=㟓Xk����R!�r1"33k�"�^����0Ep��B栔�p�XH�UJ�i� @R6�`�c�eܨ��r�HGi�� N(d������ ق(�@6�� ��U�6 �դA���,�5�Ռ [sH@Z.�%�P )&)) ���]�u;%m���TK9�@��[Ȯ*�� @F��,)�Ԏ5����BLM�Ѩ�����s��l�����N�S�|�n���'���mJ�����P�u��3rȦQ���c �K�@F��ۈ��8��= �q�v��~�cΒ����$�,m�eJ�9 �� ��1x��F�x֑��e����y�1�oq��G����}@��Y��ҾCj��1j�.�X`���m���qʞl׾R�Ӭ:��[[��U�c�A�8��?��\�՛�� �F��#>�}H�*U҆d)�2 u�M�DF�6�V��˖ � k�ly�'萺V�tG����v�+�6�3&�g�OI�}Y'MŠ�����eS4ȥ��K�g$>�=�a |�0�Jܦ3T=�?���������|��g |�>x�tɥ��������½�N��2c�I����N�G}�>�#�L,G�7��,��_��;y1�"���Lw�b8��-O���qc���N_�8}��>N[�~����������K����?���]�����ߝ�g����<v�'�g����������6�-��% ���T���c�\�`a��6JϚIf���� �$,n'�k������ �|���;��K�U?���ۤ�6)KC����CV�ٵ��v��a������ػ?qju�;v��]���������?p��3�}��Y���7������;[�K���w̞|���a�'s��vքO�ό�����>�?���߼k�'�{�q�IS�������j��.�9Bs��2 E] 6Z�4 _��Q�~�T{� �X��$pƂ#6�T?� �Uȫ�¼�ʯ- nP � �U�P�6쒲!���]�| �1�>w�j��M�����w�W��U�R7���̝t�o�����������IUa� �1�%�%�R���#]@�`�Aw �Bi'H�JRu7�����"����V}l�Xub���I*��j�["I b Ɋ��HL��7�_��g�L��<n��<�ѷ4|x�m��>����}����;^&��.�W$�ܾ�F!���6Bo�:�5i� �5��-uG[����ZO��/�>���J��ݗ8 �Z�7$�w"񌊝g?�%���J)X�L��o�1�3�~ S��`:C�� ][]�Z#�b�AJ�*�%�Ղ�u��!� l��?�be�&}�wj�4#��̨D�4)��'s#5�Ұ�0|2��Hd����8n��g�����<���tƟu��>s��Ǐ�%S}����Aך.;]���ju�����}���|���hsI_�u�1��-NU׵V�Z��� �t,u�tVT�$�IC�$$�BWky7�����W��y�V����"R�*��?7��O<��ϯ�k�<��ݺ u1�G ��t��KO8v�۶�k����N�����VZ|��ٙT�P�ڤL)���d�r��*ݝ��w��^ ��9n�����ߗ�����qg����g����:��k��u�F��}-�*umV�[=���� ޺�����K]�u�&�t��'�I�n�\… ����l�H��Ā��9k⿙',�=J)X �tί~?��_�Y�>X��͈k����Y��l䑁�� )�8�湃���2�͛=�����5���&��I{�r�3�5cUW��T+c�5\)�}v�>ʝ�:��1rܞ�`�R)����L8��K���S�����V<�[]ںn}]��e�>��K��NA\�gT��m��j'Y�H]# }l����!cIDATƏ��7��2��^n�BCcM*������;��|�}�\j�@�D�_��&�s�{S��y-52��EqA氢i%@�%9��\Y��-��(�= �>�-�N�V����2)�� �.L�=�Y�b~J<�:v�^<v�U6��k&����&��˽@�n��[�J���545�:ל�Շ���÷~�M��5�;���s \�B:� daqlt~�����f�կJPJ�� F`�9�<U��$�C6�s+�/�Vv��R�J��̟[x�6'b���b�%���'��>���&C��f]�N�07d>�U��o� K|���sƟ��&�s���?�+Z�xs��741t�k��|�]mTQ���y�o}��:�Ւ��o_��w�� ���HI�!3ۄ�BB��;�"�����x�/ߪ+�#���!��՟��R��. �(Y%~c�[�������N��Te��)��p�ŤGv�\�O� 9\e��ȷk�0 �)XO"0��[?�;��wWz�|�7'hŐ=,悧�Q%�l�a�~j�Va�J@���G��������������Z#[!M�"���4��:�s�����w���_�V�銟]���8��GI1jS�?~���=+X��]snY����a ]���IRP6S֋>t5��ʦ���}K�{&>��νb��Xz��{�x��W t����^3@��b1)4\����s��&��tY��^�j��U��)����N5T�"�5Ѐ"'�6��g���>����O:����=�κ񞉟��I̓^��F�Q�![���i��VEY% `��Ig)k��R�Pn���$��d�~��d2�|u��� J6�L��;�?��#���h�O�4aT/I�5q^3��ϒ�aׇ��?:�#JPG�\E�)jPI��Z� ��&�����(�D`�����|8? ͬ���\������<Dy(��J�������2�ʟ��������kP����d�%|&~��w"�۽���\>�!����z}��Cw|X?*�� �?㕈Qo��������E���M����RJJ�����$xS4O�V�/dk����mV���W:ԃ ?�����H��FC}\e ���=B)M�@���L������t��b)=��C��[�A���� �ie�R ����o�5�$H�� � "���(?��RJƌ����+-��?��y5��!J�<z,�(��(+����� �5_YI�y��JS�3�h����|��[�cL�K��� _���u�^�]�{�U��)�(|-�?tߡ{��� '����!�<���P��[�� ������ӂ��eG`�y��-�o��4�B��h��3�����X�^+RW(��_;׭�Wz�e�4��M��dV��'����|g�}�l�|����]�'c>��5���R��<o|G�}͊= ,wȷ�Ч����P�� d��;�������p&�ûA~ r�E�dpH�b< `9�r'�z��)#m#he�j�L4���3R�t���O^�q�n%%cD@��dԋ��P3�jm�y� ��Tc�qË>$�r�jyz��&oK��r 4�a�H#���N� ��<PJ��È���z����B���c�;�v�m�e�@]��0T6r���}�ԧ!�����3^9�-xx�t�OOx��T�У��a��2`�����Y2�ӧnN�0� ��m�1�I!$S��G)%�.��]b�A���� �I�J<l�+w�Q2��� �]Ԩ*�$-������l �t����z�QP"��"0�_�)�1ANU���S�]0���.`� �:���((�@Sv�Xh�P����ǟ"S�%%�8 &jn����sϼ �2�A��|��[[�����85i�m��)�B6�>�<���FA����������A�)���_���MT�j5Ym�VUg�wKM�c7�n!�ݗ8 �Z������r JV_&�G�տku׼�D�@�`�+�q�A{l����&�z�ҳ�XY$VzŸ���H�ީ9pwU��Ć�����j�>E��nA�Q�n?��4d�S���P�K� 3�3&=��쯖I� �����ۏo���G)%%�=�.�����nP�4?�y(Xh8����z��c�2f�k��������Ʀ^�M�=\VQU�$HiJJ�H�k�9��(x��!�&�X�c������ @Ic�/m,��Ic�d� ����}YJ-(XC��k?��@@ 1' `1�`��DH�O�R��9^�Qt{�;YD�Jq���>u*��q-�����xz��RӴ;?[�ϙs��[.>�%�����\�~ڽ���r %()�6��ygg���?�)((Xs�\M��;=�y�J�2(i@�&��U�HL��Ž�9B6MkoS��Kx����]P"P"�f#�/_1W���. 7ߡ��hn6w���DP/�0*�S�m�~����!T�j�xIӇ�������� hV�"�l1#\��>o���0�s���p/T�FTM'�<��ݥ�D�D`�D`�ׯ�Q���@LJ�QC隣�Yϼ��տ��tT���IgF+���-����_a(�D`�G����aPs԰�%E{�s\ƨU�M��H*���ө$R�y�O�>����Z"P"�V#0�볾���'��S���Z���)1�mF7�Ӧ�<1�A ����U��RJJ�"y�9IMP��D [d9�����/��Mu�~J��'���!"w��q��(m�@��ڎ����y�9kٝ����3�K:���]@7���9��Y��B���������@���$��J�+��146 ��M�&e��%�D�ŠE/��X��uv���G��G]�i:7V~I���rZ���N�ԩ���LR�~!ZZ�ҡB�g<���������祗2Q��iԼ�, ��j����_7�J� #퐳��F�s�#���ဈں.���Ҕ�W4s�:Y������PҜ��#P��Pf��b<�L��Yn��RK�-Is������b���BU1"$XI�b� � ���:�&\k��D�D��"�C�����.y�z����m�: @�PV� �tW~��(�_��{��ZP"P"�v#���{�,�M�Q;H�\� �fP2@j�U:m���X�%��E>����!Pn�������u#s6��UMi�34�kt�{�n[ �N< (1=�B "�$A��](�D�D�o#@Ps����K�[�Ҁx |��+&l:j�JFj�����$J-(���t_LaMV�$ً�l��Ʀ�i��\-6���FBI�CA��Z��R��>x���j3�0ˌXۧU)���!�d�Pʐ]�� �2߇RJJ�6�5�@s5�u�����z���NAs���iPv�PK� ��V(wIA�@�F�I�45k3 М���d5�����$! !er��S;���D�D�#� ��9��s� ��[�3�i��(+@pfP?ЍZRL�8&wJ����D`�E`YG�&�sU�5��.�Ŗ�<M����z��C���zz����)M�@�@_F�1�_>[svȫ����n�]�����e ��Q�jek픃(�� C�%}����� �5v�/��UJӜ-�����:e���Ʈ>����E�%}���&�C�|���=B�8M��� ��FD�J)X�X�ó{�.����Bz ��*=ۏ����SC~n�+�m�))�W�RKJ�<u��y�j��m�f��v��)���|>�Z5T��i� 3Dd)�D�D��#���I@66��)����V�$$�1 (�����}b�i�&rsk�f�Q���Q�R@ �R"P"��#���og�%4��@�i��Kwy�Wrȫ�H��ǮMJ-(X7"��n�5�c���y�oQ�LeC���! � �3+m���G`�۶z傷m��Gly�dZ��-S�[�o�*-,G�uZpx/�I �bl+)�y�4?K��jIDAT��4�M�vi�����ÌҼ�vH�߰C���3^/���ҼC�������n��}N�k"yȮi�k��Ҝ��?8�U��pΫ��r声&��bXXR38 �4�-�8#�t3�%����J-X���l5e�۶�VB}�����*=�{�^_q�%�e c#�G�Ͳ͚�d���(E�v�D-b;�PDF��k�pEc�������O�{���j�����c�(- �GH��u`��U�a �KqK����MA�� G`p~:[W�KH�0�� F6R<W鲩6����Z�^�6Kc�[ؕd����v9�г�ɳ_��K��ʊ�s�{lH�>b]�uJ�J �V��Gۡ/QJ�� F`��[�k�P�ﺬz>g�חڰy5R?�ҵΪ����Uq�e ��զK�ZC����y�ڱ��������:W�T3��H����U��Xj�o�9����WE*3� lN��MB��(�D`�"P�|.u A TԐjB�RVHZ}D2v�Q��!�V�/dc�ϰ�h�et���l��z@�vsK#x�&���yOF�������t��A$���*m����*%+�D��e�F5.&ٴ ]wjcEjeۡG����c;����3�]�8Np+�}��ټT��ƪ�;�w�]�۷�'�t����z��Az̖�\1Tیʙ�2e�`��((��xX�Co������� j7�*nF@R^��bX �u��#~�d� 9��j)��Z3��k� � �2�`u��7=��4nK{Zi�8+883g�OW������@J7�ڊէ!��I�j,�6�[�јt�е����le�k:7bd?m?��|���>f��f�A$9TE�1l�n�;�k�}U���3s���xd� �#(C���^��6�Z�������_�g�� ��dT/�E���� �����u9���j� h?�3���_����aM5�U�]�Io�ͮ�����U�T�H"�P��T��������U�Q4څ���n�W�����I�m��n��d�ߝ�f������� �^�y�i|!���t�U_!��y���m���J�d�H5`��InH&(�D`�#0�󿼒l���W���Zz_nF@�(3C2�[��FkG[(F���-�Y� PA2$0"�r����IfV{Y�06R�3vvǜ���o�-�Ue����,�������\&�}�>{�fC�'�4���B�'`�>J��4,�F==��t$�ӡ~P?]����1\OgmTө~��tyO�� ��Z}k��կ��R�CS����K\�j�^떆l� ��j[����АF�gUÌ��{�����ꞃ��\�P1�g�D6X p!H��<�L=���N���?����f��/��� �&}q�@���gM�`4�I��+?�5M������7���`���S�@+�3�pk/�B�0�7T�f���@�}e2�ՙ�Y!YTM��PJS"�nE`�!;?�J�/�r�݇�.�6_��ye��p�De0���+���j94d�g������4���\lKJ � �@�B�1[��N���u0��3+ | �NH_̣A��3s�^d�-f �����(�� H.��>�Hmvk��~np��KF$B/�D`�������a^���]����j(�k]B���g�����H�#�ܧo<�v��ϱ��϶ �`󎚇"C��,�RD����_���;�EHՎ���W���[� �DO��=T#����;���Fj�<h��Y!`�SB�x�7{K���{x�NO�$|Y�ݎ��u\޺�[����YK��H�\�&�l��������$=v ��h�A���H`�,�B5�����}�f��T_�d��v{�c�e��|]F�-�fD��,��z>��*o�Xũq3��.�X�(KFZpbP�����;a���d,�D��#p���!T_ӊ��ױ�����l�k�r����ҕ���ٗ�mt�2znd�v+�����j�A����m��A�x��Ρ�P%�,�n�� �`�%J)����nO7̯��F�@�`�4�J��3pU' ��pZ�EL��ZP:�h�}X*��d��=�#��l��a� 6�Y�Q���%}����i�I��Rݾ��y�2�r����>sɫ:~��K�������.׾lK�0lo��p�Ǫ R\��lG�Mb���� '�d� ���e��9O�1|)���]��d/(݀|!A� ���&�ׯ�#�4>�EӇ=�P����� ��?�ZyZkll�Bq�и�[[�p:$=�(YX(}��R�PJF`�!�>a��o���._�q���`e�s�D\ז�u�Js��_�Y�^?�;����w�ڵ����x�[�v�>GsK�|I�B�&+� ��@� ��RJ�$�^���S� z�׵ ]�Pi�[���)��Ww��M�l�]U(-�����0)b0b?������my���Y�"�!JKia{R��,(��{�.�����ҳ{/_�rFk�4<渖������ɗ^�'�FA~��P?�����{�V���>�C�8�� ��z���=����M�@)�%z/8�O��w<㠅�x���Xt�3����Z��t�kv������ʿ-t����:")�}���A c悔�z鬿b�����ԩw?�+��˪�_;.�f�l�k�8�[����3��\W:E4�x�/u�D ��ɏ|����� �%ZkNґ>\3�\�ÅoցN�m�5&?����|�*Z���|�f׬m��� d�ɺ~�c/�5gi�T�Jh���:��l�y�֥�_m�U�'x������t&@�� Y� ;JY HG=�1� �}�i 6}7��p+]n� ��'��������J;� n��0'l�r��fU��+���# I��0HC��- �O��o� T<ZJ�ZfT� �NY���ƚ�޴`�E ����:�U�_��k�����`>8��y�L~�Z�#�ɯ�&�EB\�Q��m��UaU+�Z����^>�kW�-�e4��'v���g��ݬ=�ƪ����1o�E�c��_d�|]�'s��3wQ[ꪊ@L������K_� ��CQL�^����6;Vo���cCh�b����W~��N{4h���-�E:hڌ+��r�@�~�e���X�eU��#�Fk�r�F�t^��� "5��� ]�BUVqd�D�� y����`WQU�l_�=�b�߄l�_�S�|�qm��]z�|Wa�Z�8�ұZ�y���&ZT�tڥ�߳����+Ҏ����� ]���hh� �*HQ0T�=�Ude��$@�%��P�Î@:♛ � T�F1W�����2G�4�� �|��X�K;� n�s�ٲ�2��� ���R���f\�w�P�m� ��y@���BT@w�Pa$O��?� 3Cd<�β��>���H�+��{WJ�J�b&-��1J��yURǨJGm~B��m;�uf[;�����ݼ�Y�؜c�>���Ý�q���a� A���$��^�~d(��$}���yܖ��RD�S=�;����21��% ���f�#�޹��p���Z����4X���?W_�Yx���<����w�j}Β聬�u���z�uC���=���߇�)�+(�/��h�eT(���*��G�vԹ��Up���@בR�Wy % �N�f�˨*K�+�����X}�����:摆�ʨ��x�˭��ɐ�f�>���NYp�f����E�O��_����߸f�G]z�+��%4b� gi��+�a0�X�c���4�<8���wI�;2gjTl2)& t�����:��h��(�@4��YP���ւ$�B���N� ���X�KL~�y�'u6 ĩ3�`i��7�႔�O]�x�����@s�!ni@�*�B�(��/�H��@d �W�2�*_d�x��O��C�T ��ݭLÆV���[� �6D��}îN�#���#�Xg?���]�X�:����_a�����oY�}/Wu�[(���ں��R��� �j�nrMIDAT����=f�^�������!�4���P�rG��-���Bp%Ť�R���j �/� {W��o�� ޶Ż���y���o��lu�!u�h���uZ%�*��C�x��@Ŗѐ�a�U��<Fh�Q�0$`)�B\wz�������K7�M��Y�v��V�CG &�@�ߥ�K!tU�U׈�e�Ʊ���$U� ��m{@V�A��'��~_$��6#:}|]J���5;��c����瓊��s��J��b��-x�fM��笊�_{��xǾE|��c8ql��26�F��c{�<�9��� 3I@�A5IJy�������}"bm��<bi�b��"ܓ��Ru���m��K�GU,4I{I…�:��K> �����;P��� 4�m_����a��=���4��D�ՀFۋ�fIr�o��7%g8q�d�y�2#��vҷ���1��v@u�hUKY���&�}�����1���gA��le��M�E�Q���w���O��K> �_��е4�� �|t���Z��-X�p���{���1X�8z�AX�/Ї �qy���� ���-*R��P�S A,lP�E��eD��Q�����A1 )�(r�D�%��:���g�~�:�'�68�o[\�������ݡqd��lU��dd�t'��{�gſ�[w� ���FIR# �C�.���� �>����j)l�Jm4 �h�a�ճq���q�/�Bi�b��|��#3��J~n����K��'��z� �l�PU�T�k�2׆R���� �(=���0����nR�n套�����HoӾ� �߉�m��ȧ]���Y��c ߺo>��u�y�p���g��ʟV�{�U�i7Ћ�Q�u[��M��+��x��j��qy̢v�`�+U(#4��@R*GHDe��Y2���j����P��8���n��rg�d���'?��K1<�o�_T��6m����І$��C TԐjB�L�$����a�n������U���"6Fe=�7d�����_��g��l�m#*�(�)]��(U��*�m�Dj��Q�䏕)O~{�x���î��g��������B� ���Us'��F��*���2D�&۵��3���u�����:�Q]��nח�G�Ȱ��E�h|Z��E��M���ڷ:1j>��J���CV� �0��gDYQ��K�M��F���@�B�RO�x���O*�p�(%�I�%��s8��;n��ѴI����;��K�j�v��U�A���o��mX��A��u� � Ń "��q��_��;C�?�?�� �@�"c�FY3Ə4yO�����q��Q��� @�B����\b퐢jg<+�.�����+�ʯL��8��8h��q��S�U��'z�u�Oe]��&�:�{'�}��Y�޵�R4���V #��&�@L~�����է�x���#��F�حg�ƛpm]�;p��a�NE���bdy|����x�1��ke�-�|>�H*F�_"�dS �P�.�wn����ߑ�иq��� ������?U�/�m� M������{�yQƝ@�����Ս�j7�A��F�������y����1�#J� �D�W����>be�I/N�66,,����Yf�K3�6�3n ��t���"Tᢜ�+�LP6n�#F�7��]��g~�(�ƤE�G��'Ѫ�R�'�9�����"��j�#����z ڿEޭC�`.�j�U��$��m�����>d�gu�� 4��v!5��fix2k`bA0 i蠍V��<�JKwlS�V�(����h��J���^!��VFf@2�!6����.����̟�B��1b�K����(/�0'�H���Y&����X$-�A�W@�'ж�.x�v�c�ه�� _��2ѼA�����1��^�"?�UX,D3q@� ̻��+T�.��<bh܈B� �m %'ݤF뿄�g������ �5�u.�j�r��'��5^6�3o�_<���x�3��OgF��^i����o�����Yڝ��:�Ϯ�$0�ջoV��/���|�8v��� J5�v,���~�7�����i�����dU�)��v|����1�`;t^�-�☫t�<N��ȼ�{>ۍHp^P6���e׎ �x�OX��b5��(>�M6d�8��"��X%��V~,V�$���H+��㶐 .�Mq5���J�@L~��k�;���>��4Z��~�L��U����X����c4�${A���uե+��#Xm�Ӛ���%�S6AҌ M��J�Ș����|Üwl��{�w�b��ʖ�z#�?*֫�߇ �����k�GjuRP/�A�dj����^����_����7�ҟ �~ݮ�W���uw���?N{<��W3��-z�ԥ�w]��jufJ��<@��>�1 ۔tO�ϚZwU�����kcwӃ �BU�He4f ���`ʜc6���1����ߞ9A��"n��Y � �Xu���W}��J4mH����z.�1�$�B�${A�-��s�~œ��C��EU�<�w'$���/$t���j��)�]��A����������юQ�6� ȶOlr�e:g�pY�hR� x���U˰A����ǜ�d�DV2d�#C� ��y���w��E3��#����&/�6�a�|�g�����g~qY�I�i�#������X�;�*��g���[�|i�;��O0�C����_�̯�}�|̑}�X���\�\ݩ��)_���y�5��ߝqe]כ���8���uZ��aJ�o�+��w��Jc�Zk���}��e��� �}�� 9e �8G�ú^7s�~�?�?tȝ�3m�-b�b��"��b:xu=���,�u�����������ԗ�b�TKBR-� ��$?;�Нߢ��F,����KݵT2'� =ݾ��3��|�5B�M�����<�˞�՞�\����o��ɕ߼���3$�\1V+1o�/�XJҜ�z�[2҇�@��H�7uʻ�t�iV Q���RT[�Rkݟ;L�?�W?���I����E$�CX����Mc�f7vY�_=���ɯm�����/��`��vې����7L�l�]���*#�@;���n{�/:��Jm!��� ��r��)�����Ktz�a��[> D1i���/vw�]�rÓPO��_�;�� �V��:��B1�W`�$���w�e>�C{c?t��_i���?b+k�t�Hg@��Z�4�3g�� ~P�3�m9chm��[<�;U���}�8�ht�>���]N���q�Ev���0뗍;�}1�5ڨt���������ĩx�"�z�C�I���W�ڍ��w�:�# 1����?�PUш}d�g|ٴ�l�te��6�L~e�U�-�d����ېePE�R� � �P�zX<��up�����֍@�=oSq MUG�_��lr���X��q`�O>'�z?��/��m����.nX}v��?1�5�ya�J�|s'���ᖆ}!�3o�����&_z�P�j���]aWR�~�î;Igg ��F�T�#G#�1�O\������=+)�R�`ni�-������K�m���FU^z�����,}R�V��w~;E�2�|- o(�~-�I����R���۷���|�u�֍�6�^��1I�nо�F��Z��F������ �S�D.��ں�z�����j/!N�>W��E�PTX�N������ʯA��< ����L��k��E�9Gr������}n!;I@��n@�� �"�ʁ!��?��k���{Z��S�Azo ���B~Ff)™�B?l�c�(��U� u���}ZR�݈��@��ݧ�]�r���}���0��I� ?�L����<���꜂�fXo�y��f� ���ϔ�[��HWU��']v��GFR�ꌀ�_�5��CW��UYq�+g(+��K�R��eWm4[�m�{�fOH�:��ѧ�6ݓ�a���� �t���ue���{A'�'���H� @� PgH@�kH[�����r�:e3�#A�_����n��Y�C)k,��DYIDATԑ�� �{a�����b!�Jɉ¤��ݶu�����8|�V��}qNnzad��K���r齝:�����{/�$p�O>�%�C:�Q� H�7?N_篪n��,e��}- �d���_�E��m����߇�y�!�+q�x_+�r ����ے��X�˼������q� 9=��l�s$�s���4X���;��;��?���x��L�.��'�Ƅ��@7)��@fiނ����[x��[������ʚ�ߏyOq�[��.�&�SA� Z�DTmzt��œ����u6�䌨���ԝ�V,s����8��c]P7��f��u}��Y��ԙM��G��O׉�o�[:��Ȱ�+۠�1�U4}� �ݚ����-�_1YKu����h�l7����J���� p���*&Md�Sa],~��t:Z��O��n�:?ęYP�������T�p]}��C�q`�t2P�p>}�H ��pKþ��.���_W&?�r��?��GY23���@YApu���J�+y�T۲N����З4�}��'�U��FN�zkO遪�_�������OB=��?8YK�z�� 9��X���u)��꟮Ģqo*��9k��{����Y+�k`vنD� g��%��pqKqbݺ�~�j� �s�\��� �:�X����*�]�'?�B'����#$Ϡ��q٢3����7�͟�B��34간_p�����,����y@i!?�5:�ԍ�^KYGjL��G�~�k�Ld@7:�|*>W)��IRU}Ӝ�N��P&���t'���N� � �T#�$K��!���3��T��G�]��{d���C 1 �J I�H�BT0��Vz�ڮ��u��?�iO�XC�����F�`�h�ɠu�)�ƹʊ�nh�� ��i����pB�Q��""� 沩��;U5�Ѝ��(�T+�;�{Ćk|�� �t��� ����Ue~��-d��'�r�u��3�T%�������h�:iV �0�I�j���uz��:���Qx�u�~���gA� �c��?'�p���C��|b��ɗ��+�"�=�'����;��K}%!H��-�����uR����k�n � �(& ��ϩ��I���躁7< �ԋ����$� Au9� ���Wot��Cs�fo�az��_;�x�Gwcx՗�Y�!�;��l� ��l\D5� �9E���w?����� x���3�r�v� h�-�jT��ڹU]�;�3e�+H��o\���_�u촯���7f�P��}���:��"����H��s@W�RB �<ˊ2R�H6 �zn� �aI�~��;����1<�u#4�a��j(4!�ɏl��(e��@\�>R�W~C\+>C��%��+��X�C6��r[�;,�k����j��ʖ�z+�晿��Q���/�uCܢ�᭍���d$�����+���s�� � ��BQL,i��������*�j����%c,��Xl�����(�2b���(�La�� aE�����'jH����x�q�".���3>)����s��e�ξ��RK֫��OJ��GV�lѝ&@��8�a��h�2��ӣڀ������Q)�skb��g���a*M����1Vz�eC���F��j�)�$)�`�,�2��*Vwip�-��y�1D^�UC�d>hҙ�_��E��w��̥� }�U�nP !;\�,`i��G͸�E�x��\(��'a* �=�_VV~��"�v��Z�ǰ���4��c�LZX�g���m�+��>�/k�s�M��onѕHs��2��R��h����~�� 5�:$-��#����J$C"����=�}ь��L?���/5��Z��Uu�g♷�J)�"PU@�j�����Ԋ���Kb���M�W?1��z�Y���>��W ���=������i�g/�f�|��+cYHZ�Z�y�O�]���$0^P1�l��D��I���u��Ջ� �<᯿���g�jˉg�~��so�k����o���Z?.�3�1�t�53�!��N@��jvh9um��JU��bز�ZW"��"�霴h����t蒉��[)�,p�8���L��yK����q�;�1V|xn�� @��d�Ԡj�BvY���o{�JS"P"���D�dO�,[�����]�U��tm�F��&(�D�D`Y� _�YM힕K�� ���\+�>H��E�@7��In JJ�;Z�'%-�1��������`Eb6�^� �H�f1b�X�RKJ�=�6�RV����쪮��%�9����!q㠥)(���Tm��p�@���ϮXq�]0�}D1��0ȒPJ���#�7�Js����l}���K~<0�%���J�|�4 ��>c������i~���R�@�NZ���g~}3 [���9�:�;�_�N���,{��,*w�J�%��q ��X���*��� � ���YI�*6wiJJ�2��9�W��,��gWT#?�� n �^U���K�%%�"�/�pgy�P����@҂��Ȋ����,)C=��K/�D�D�?#�y�q����Ԕ�B�ڑ*}�,)�$-B3�X@v�oPJ�@?G ՝��,0�(��K�4�+��N����n�K�2�-�T�PJ�@G��# �~�O��I6�I��R��N=tn�2Z*/`��B��8�R"P"0*��$V��2���_��j�'{�V�ᔠ=�[(5�����Ii7�K-(���H�ձX3���'�֞R��7��o��΄�ϔLԍ$ �����=[MA)%%}��������wDp=��!5��'��AE�ڔnH�^��T�b����n� �,Qj�@�@�E`��;'x�SsvD&M��7�<�ң?�t� � ��D~���*=���D������ ����+=�[�Ñ���3��Z��q�V{�]w�6C�����]jT�&���_'�N��֒�6%��y�����Np mnP� #Il_��7�RJJ�&�m~�xT�^�ᕟn} p�׿�v���ZW=8]$�^�Mw�#��C�:����ԭ���>����GiNrj�2�H�U'�,�f{���CI� І�_Pk� =�p�JC)%t����Ng'�JOdY) hz�m1�|yԪ�����g��2���ꧽ�N�|�X��KR�s��9V�Գ򷼕��w�aw��O�����I� H�*D�D��PJ�@�@�D��+�tk�e�쭯�����EKcş�E_�}���ٗ,�ا^���O�IA�@��ڍ���~��Z�-� #�?z������2*���$A 0r�`��:OU��Q�AF��NZs��ʂe��H��/���Q��_xͨ��JZ�� �R\�x��Q�m�.%%k'����mS�I�r|�[s��q�w�Z�$�h��n���?܇��[ �S�j4D���]J)X{ ǽ|� OM�c�V��}��U�^ ye���$x��6Rm3lI<��� ��D`�G��}/����-�Kz�u�X�,��kb�Z�7� KHR���p�E �^�(E�l藓�7�r��T���\�o�:ոQ��P�WmO���;��Wq��{K�vX�6 5ꃃ��D�D`�F �:8� ii�Js�"l�u�k���g���Qu�`/Q)SP�݆�a�.Ip�Eo߬�PJ)Xs���i�U��������w����ֽj���C_LHw'�nza�o���V��r�8���$�����#�]�>c�1��~{Q}QY 4E z����?�YOl:Q"��F�N���|�М�{����/n=�w�5�1�;���Z���`�H�����V�n�$��v%%�7u5Ns��MZ�k!�p��u[�}�p�E�ǬKM�ξ�N���+�� A���5�������ARJ-(XM�m�����&��ҏaK��׽�Υ g� ����E$�1�� @�����'Aw "8>��ɏ0)((X��a��?B+��I�o �p�zu�9C�"���e&�����7���:r� � �[�/��IDAT�����R"��E`m�Є�����dx�i sl$}����/� �Q��2�r�!%��ƪ�ڛ��Y��P_{�9f�Q��;�Ҕ���ܲۅ{'�������j���Vk5�:�z���6�C&� ��~��+j��v�l�J0`�v�� (�D�D`�E��N�RV�%�֬��q�o{�<d�$� � �,��=�Ͳ=����-��^P"P"��"p��_>.��3!����٬ ���,�,W��7�� ��^D�P+ \lF�7���b{�R"�D`m�­{]�}�w�w�����ܸ<c^����L8I+�M^�2�y_+�ڤ{���:E�Ҥ�N'�[�%%+�4�9ASkR�⚴�ZH ���!Sț��I'��r��N��M�k��D�:hZ�Ue�е�g����m�Y�R"P"���u��~6i��������~D�mgM�\�r��N�ۄ����i%�X���KJK�:H��R����m��]j�@��rF��ݿ����5�F0�N�G~z����6V��P�~'��� �DԂ�W�@6IY��o��(�RKֹ��ߺ��G��4� �����n�c:V��pȏ�I)i�W#� ��~s)� �n�t ������f%%cG��=��o�pfB^�ku�\�M�Z�z�ϒ�+t��F]���( ��f�אĘ!{�hZ �@0U3f��cPJ�@�����<�Fg���o�V���ʕ��o�R ��G�4�@R���/��Z�n���'�N74�+�]%%#�TC����X�@�-t�U?e�[��z,�J��N�(��$��Vw��PhZ�a��� ߺ�O%K-����޲��4':�w��W����j�>��be���3��[�]�y����~4��ɓ�> qMi��o�99K-��#p�.�;$EB�Iw�^�}�����ZH���1t����{�>��!FՇ���I���8��A��iF��Ј�D�[�e�O��l��y��?�O�߂e��Z���E;^����s����2��_�Ni�%&��������v�DÚu��o�f����lp�eϋu�WG'-�-��m �y��ѯ��}^�UPVI�8&�[�+�;� X ���f��y��7o{czî�PJ�@E`u �׻�7��=/�1�:�Z�[_��;�<��]Uc�VՎx�m��CC��?�II{���WzRe��@�jTuǀm�w��aᛷ�R[�Z"��F��=����Τ? U�$-����25��-���-��ҹ��<�UTVY�x&��?T�6�9Hh�AF$�4���f��:��=��d)�D`���/��X�v��z����]�[I�� ����U����T�p_�����������h�;5�Ґ)��T��|��;ʮ���%������/OZ{��tM��#���8���� VqY� ����[����NPh$% DK�f����Μw���@)%k)���~�';gB�� ���F����GsjU�*�KG�dU�?w���ē�����2�^ �. �OB�ȍ�}췁����=� ;n����s�u׋��e��~[_߿+���^_�w�m��씝�,��D�:�:����󢫥����N��M'V����X�Mq5Z[�n!_H����p��Cw|��w���5�����ui�=?��w����t�>'��a����O��͗ �;��nCRӃ�KWU�>s�E���PN�w�γ�v�����Z"Џ��޲��g%T�$p��I�Rf`L�Z�~#��ÎW=������?��7��a> ���h/(��&����س"����]>��7o�饖����No���Ъ�Q�s%����Yj����<뻯��;�qO���}o�(�8�1�_�v�L���X�S�"H�����K��8f�� ��>d��ߐ)����ܲ�EMX0�ڴb�ۯ˛ͳ~W^_ <s��M��:�5�|B}���N|�F��"��F�@8'T�[W�%s�� �_��'�Xj�������_�v�H^���=�/��J���W� <w�Y���,�<�œf M�� �$�l!��V*%Ɲ@H������ĝ���›�+�t�w=��W�����r��v�u�/�9\��i�$��䘀�^��a��.����2}��!���@{~�ϻ�(�: PT(�]IfYG�@�ǂ�QUU]?�ջ���W��tȾS�(�D`�#�� n���)����7�r��� �\Ϫ: �|�Ar�JI�:y���= k�����t�O>�j��|�_҂��Z�$}[��^U��mKEI_�6|��N��]���9�������c[�������m�|v[��>0w�֔��_�“��em���'�ZH=�:8󳾶 =��MuJ�x�>�&k���ೞr�O�=�����F�2D4�g����!�M�����_��e��QJ�� D��]?���w��e�~����զV Z������O�i������s�����|�OީI{ �nJ6���2 ��m�ꮶ��pYs������O�����;����7����:h���ܶ������̽����ݾ˹3~�����5��bYP�Q�1u�~ț�x�W�����;�K_$����\�or���i(`�Bi0kj�"{)���] �#pSu�ȕ���~��3|�>g<p��/������� "��u�������f�Ϝ��Ϛٙ��$\ɪs Y���q`�A�r���~K�kz��է�&L_�����h���+���/�u�� �N��@=(-�ـ�h7+7�ʨ*��t����,�����)겗|�E��7ax��^��υO=���������ᾗ<���x�^�����x��O���/.?Z���s���gL���N��n{�����i{�v�����?������w�����ov����/��X���{�H] D ���8�A�Q㯻<��ƞ)�&�Ҫ���Y�5�n��Ўl�Wt700~:H6�xR��Ȝ�z$H�4��LJ�G�ax�����,>��W%\�ͬRuS�3���9��/~�"���_��;�� |��^8������ͼ��}�ڃ�0���3��߁3�,���Kg�c������4��˸W���|��[�B\��x��{��|��{�{�|��{v��kf޳�q���k��ͼ{��ͼG�{�CŅ���ϼ{�;I�t�̻����ޘ����o���ۉo���5���������3���[G����G��K�ș�2��[Hnq��?[�c�][-�;�ǭ���ǭ�7�ۼ��9V8~��=A���;���߹݉w��?|��n��E�,z��:馊�L��e����q��G�?Ի�;o ��!�&�T:$?=0<��V}��i��gtc� L��މjx;M�s59 N�Z�5�$�@Z��im�Զr+�K�����K)��t�Z��m� H ������^��M`/�6H��oT��Q۸o�����w�1�� �x�Xбr_h5C�>�D?��Fcט����Z_����}����kA>�l�������}ާa�6��'C}��p���'T1>H*+�H��Ơce�xc�D��� ~/��P@Z�h��I� 2����7_�#}��wn]q���zA_���)U�[:��8���?"�z;�I�#�CP�����=2� �E}!K/(�P���0�C��fXo�9��2�6� \(kĺ�ql�ni����#\�� �,�ͭ�A@}����2@�0l$�� Jk�,��Z/�ni����Y�a���ץh�W���>��cz�l�DŽ��5��f���O�q�m� ���܈u�(��(�!N��n�����9<�D|,%ܧ����'9TGq������� �j�c��6��A��X���W��>[��� ��9�h�/K����9���]���>�D-xH����ۇot��s�חy������)ǽ헥�c���/����(�@�[I���.�$�O���P��}�_���u��?�B2�ᩑ���h�:����d[�})�c���n�k^��g>�6�3��k��gIDATL]�@Ս����L�ڵ�N�#�� �J-��^�G@V�j3(+K�<@��D~�� �@�d�-{����>s�p��A�T� ��!�ږA��m��V�����=ˑ�̽�"RDZ��Ҁ|!A�'\�eP_tW]#��h����ui}�i_l߱��l��~���M����j��f��ޝ�~�o���:8�fȓ.���i_���NG���>T���y�Ts�����w��'l�ʦ%Ƭk���������6▆�oep�\m3¦�,�lƉ�q��Wu�<^3��y?R���V���èN�y"�ѯ�Y&��r$�9��-����m���O�&cp�C����|��J���#lx#�Fod�/M&M����?��i�}KCwm�>H��pR���mgp��W��N��e�Nm�'u�]�.��č.�f� �V`u SuHu� ���|�q4�����]Tl �?�P_��}�䳴��-�> �k� �7��ϼ����3¦���m�B��*�O��rKD�Z�.���!�#)ޅ;K_�o�̽�3t�Ɇ��K�m[4�G�/�_w�m��}�p'�o�h���"�tꁭ�����n;�����һ�Eh=8��S�z�U�nt�U��ͫ��Pz4j�^Kܷ����ϨZ|�6�AU.�lku���X�/�'���7�}�Y����Ͼ��Y�gd��F����ǰm���F��?�<6ok�|�>ٗ�g>2�Ͷ^���-�82Fo�����E hې�W��˶�3lN��F� ]���$����f��@�[��Α�[5��/x�6��߶���V�p���^��.��;͸��]v�}몗=�+�� ��$��jH�%[Ȗ[�|�a�4�6Q�8��rfT#����n�#>�-�@p��dt��j3H�:H�=�-I��ֲ��<�B<$/�dF@>��b`��9�Ŋ��c����˖˃�o�G<�X�q�>��5�I����׽r��׽�e��:����~�߱Ec=>��N�߼�G��޻7��=1㿦T57g�C����~, �%�1,$e�;�v%�+j��,�Q~o+��}F�/��}���I�9㘐nX��}I��������'5�m��9�k���3�� c��6������w����kA��FR�-�����ej�#�����{]���C���[^��)[]��=���U���W\� �lP `��u�w����.�� q�����s�G^��ݬ�/�ެ�㴔������H^A�Gu����w�$\�K4b]pĖ�)@>�m� �� �e��5��Q �/�_R�Y��d�CDim��]X�=�- �^�6�8������U�A��~'ܚ��G���շ$/����4b�ͩ泷��6��v�����^w�׿�vl������i�IDAT3�0L5��IEND�B`�
dev.nix
# To learn more about how to use Nix to configure your environment # see: https://firebase.google.com/docs/studio/customize-workspace {pkgs}: { # Which nixpkgs channel to use. channel = "stable-24.11"; # or "unstable" # Use https://search.nixos.org/packages to find packages packages = [ pkgs.nodejs_20 pkgs.zulu ]; # Sets environment variables in the workspace env = {}; # This adds a file watcher to startup the firebase emulators. The emulators will only start if # a firebase.json file is written into the user's directory services.firebase.emulators = { # Disabling because we are using prod backends right now detect = false; projectId = "demo-app"; services = ["auth" "firestore"]; }; idx = { # Search for the extensions you want on https://open-vsx.org/ and use "publisher.id" extensions = [ # "vscodevim.vim" ]; workspace = { onCreate = { default.openFiles = [ "src/app/page.tsx" ]; }; }; # Enable previews and customize configuration previews = { enable = true; previews = { web = { command = ["npm" "run" "dev" "--" "--port" "$PORT" "--hostname" "0.0.0.0"]; manager = "web"; }; }; }; }; }
components.json
{ "$schema": "https://ui.shadcn.com/schema.json", "style": "default", "rsc": true, "tsx": true, "tailwind": { "config": "tailwind.config.ts", "css": "src/app/globals.css", "baseColor": "neutral", "cssVariables": true, "prefix": "" }, "aliases": { "components": "@/components", "utils": "@/lib/utils", "ui": "@/components/ui", "lib": "@/lib", "hooks": "@/hooks" }, "iconLibrary": "lucide" }
apphosting.yaml
# Settings to manage and configure a Firebase App Hosting backend. # https://firebase.google.com/docs/app-hosting/configure runConfig: # Increase this value if you'd like to automatically spin up # more instances in response to increased traffic. maxInstances: 1
README.md
# Firebase Studio This is a NextJS starter in Firebase Studio. To get started, take a look at src/app/page.tsx.
docs/blueprint.md
# **App Name**: SoccerPro AI ## Core Features: - Live Match Tracking: Display live scores and match stats in real-time. - Player Performance Analysis: Provide detailed player statistics and performance metrics. - AI-Powered Match Predictions: Use machine learning tool to predict match outcomes based on historical data and current form. - Personalized Recommendations: AI suggests relevant articles, players, and matches based on user preferences. - Player Search and Comparison: Enable users to search for players and compare their stats. - Wallet: Allow users to earn digital currency (ex. non-mintable points) for prediction success. ## Style Guidelines: - Primary color: Deep sky blue (#00BFFF) for a modern and energetic feel, related to sports and open skies. - Background color: Very light blue (#E0FFFF), provides a clean and spacious feel while maintaining thematic consistency with the primary color. - Accent color: Light green (#90EE90), provides a sense of accuracy and life. It is the color used for goals, completed actions and successful predictions. - Body font: 'PT Sans', a modern sans-serif font, for comfortable reading on various devices. - Headline font: 'Space Grotesk', a tech-style sans-serif font, giving a sharp modern appearance to the titles. - Use clear and recognizable icons from the Feather and Material Icons sets to represent different features and actions. - Employ a grid-based layout with rounded cards to organize content and create a visually appealing user interface.
src/hooks/use-toast.ts
"use client" // Inspired by react-hot-toast library import * as React from "react" import type { ToastActionElement, ToastProps, } from "@/components/ui/toast" const TOAST_LIMIT = 1 const TOAST_REMOVE_DELAY = 1000000 type ToasterToast = ToastProps & { id: string title?: React.ReactNode description?: React.ReactNode action?: ToastActionElement } const actionTypes = { ADD_TOAST: "ADD_TOAST", UPDATE_TOAST: "UPDATE_TOAST", DISMISS_TOAST: "DISMISS_TOAST", REMOVE_TOAST: "REMOVE_TOAST", } as const let count = 0 function genId() { count = (count + 1) % Number.MAX_SAFE_INTEGER return count.toString() } type ActionType = typeof actionTypes type Action = | { type: ActionType["ADD_TOAST"] toast: ToasterToast } | { type: ActionType["UPDATE_TOAST"] toast: Partial<ToasterToast> } | { type: ActionType["DISMISS_TOAST"] toastId?: ToasterToast["id"] } | { type: ActionType["REMOVE_TOAST"] toastId?: ToasterToast["id"] } interface State { toasts: ToasterToast[] } const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>() const addToRemoveQueue = (toastId: string) => { if (toastTimeouts.has(toastId)) { return } const timeout = setTimeout(() => { toastTimeouts.delete(toastId) dispatch({ type: "REMOVE_TOAST", toastId: toastId, }) }, TOAST_REMOVE_DELAY) toastTimeouts.set(toastId, timeout) } export const reducer = (state: State, action: Action): State => { switch (action.type) { case "ADD_TOAST": return { ...state, toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), } case "UPDATE_TOAST": return { ...state, toasts: state.toasts.map((t) => t.id === action.toast.id ? { ...t, ...action.toast } : t ), } case "DISMISS_TOAST": { const { toastId } = action // ! Side effects ! - This could be extracted into a dismissToast() action, // but I'll keep it here for simplicity if (toastId) { addToRemoveQueue(toastId) } else { state.toasts.forEach((toast) => { addToRemoveQueue(toast.id) }) } return { ...state, toasts: state.toasts.map((t) => t.id === toastId || toastId === undefined ? { ...t, open: false, } : t ), } } case "REMOVE_TOAST": if (action.toastId === undefined) { return { ...state, toasts: [], } } return { ...state, toasts: state.toasts.filter((t) => t.id !== action.toastId), } } } const listeners: Array<(state: State) => void> = [] let memoryState: State = { toasts: [] } function dispatch(action: Action) { memoryState = reducer(memoryState, action) listeners.forEach((listener) => { listener(memoryState) }) } type Toast = Omit<ToasterToast, "id"> function toast({ ...props }: Toast) { const id = genId() const update = (props: ToasterToast) => dispatch({ type: "UPDATE_TOAST", toast: { ...props, id }, }) const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) dispatch({ type: "ADD_TOAST", toast: { ...props, id, open: true, onOpenChange: (open) => { if (!open) dismiss() }, }, }) return { id: id, dismiss, update, } } function useToast() { const [state, setState] = React.useState<State>(memoryState) React.useEffect(() => { listeners.push(setState) return () => { const index = listeners.indexOf(setState) if (index > -1) { listeners.splice(index, 1) } } }, [state]) return { ...state, toast, dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), } } export { useToast, toast }
src/hooks/use-mobile.tsx
import * as React from "react" const MOBILE_BREAKPOINT = 768 export function useIsMobile() { const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined) React.useEffect(() => { const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) const onChange = () => { setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) } mql.addEventListener("change", onChange) setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) return () => mql.removeEventListener("change", onChange) }, []) return !!isMobile }
src/lib/utils.ts
import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) }
src/lib/placeholder-images.ts
import data from './placeholder-images.json'; export type ImagePlaceholder = { id: string; description: string; imageUrl: string; imageHint: string; }; export const PlaceHolderImages: ImagePlaceholder[] = data.placeholderImages;
src/lib/placeholder-images.json
{ "placeholderImages": [ { "id": "user-avatar", "description": "Avatar for the current user", "imageUrl": "https://images.unsplash.com/photo-1610014197264-b8c410ac3fe2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3NDE5ODJ8MHwxfHNlYXJjaHw2fHxwcm9maWxlJTIwcGVyc29ufGVufDB8fHx8MTc1OTUzMTMzNnww&ixlib=rb-4.1.0&q=80&w=1080", "imageHint": "profile person" }, { "id": "player-1", "description": "Avatar for Erling Haaland", "imageUrl": "https://images.unsplash.com/photo-1610736342165-4eeb4aef66ca?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3NDE5ODJ8MHwxfHNlYXJjaHw2fHxmb290YmFsbCUyMHBsYXllcnxlbnwwfHx8fDE3NTk1Nzg4ODh8MA&ixlib=rb-4.1.0&q=80&w=1080", "imageHint": "football player" }, { "id": "player-2", "description": "Avatar for Kylian Mbappé", "imageUrl": "https://images.unsplash.com/photo-1610736342165-4eeb4aef66ca?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3NDE5ODJ8MHwxfHNlYXJjaHw2fHxmb290YmFsbCUyMHBsYXllcnxlbnwwfHx8fDE3NTk1Nzg4ODh8MA&ixlib=rb-4.1.0&q=80&w=1080", "imageHint": "football player" }, { "id": "player-3", "description": "Avatar for Jude Bellingham", "imageUrl": "https://images.unsplash.com/photo-1505748641491-f7ee2fd6fb4d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3NDE5ODJ8MHwxfHNlYXJjaHwxMHx8Zm9vdGJhbGwlMjBwbGF5ZXJ8ZW58MHx8fHwxNzU5NTc4ODg4fDA&ixlib=rb-4.1.0&q=80&w=1080", "imageHint": "football player" }, { "id": "player-4", "description": "Avatar for Vinicius Jr", "imageUrl": "https://images.unsplash.com/photo-1579952363873-27f3bade9f55?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3NDE5ODJ8MHwxfHNlYXJjaHwyfHxmb290YmFsbCUyMHBsYXllcnxlbnwwfHx8fDE3NTk1Nzg4ODh8MA&ixlib=rb-4.1.0&q=80&w=1080", "imageHint": "football player" }, { "id": "player-5", "description": "Avatar for Bukayo Saka", "imageUrl": "https://images.unsplash.com/photo-1517809923521-4f465a315183?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3NDE5ODJ8MHwxfHNlYXJjaHw3fHxmb290YmFsbCUyMHBsYXllcnxlbnwwfHx8fDE3NTk1Nzg4ODh8MA&ixlib=rb-4.1.0&q=80&w=1080", "imageHint": "football player" }, { "id": "team-logo-1", "description": "Logo for Manchester City", "imageUrl": "https://images.unsplash.com/photo-1640182837698-d1dee88748b1?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3NDE5ODJ8MHwxfHNlYXJjaHw5fHx0ZWFtJTIwbG9nb3xlbnwwfHx8fDE3NTk1OTMyOTB8MA&ixlib=rb-4.1.0&q=80&w=1080", "imageHint": "team logo" }, { "id": "team-logo-2", "description": "Logo for Liverpool", "imageUrl": "https://images.unsplash.com/photo-1632937925343-d8d581769c8a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3NDE5ODJ8MHwxfHNlYXJjaHwzfHx0ZWFtJTIwbG9nb3xlbnwwfHx8fDE3NTk1OTMyOTB8MA&ixlibrb-4.1.0&q=80&w=1080", "imageHint": "team logo" }, { "id": "team-logo-3", "description": "Logo for Arsenal", "imageUrl": "https://images.unsplash.com/photo-1625392085233-3f10b25fac1d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3NDE5ODJ8MHwxfHNlYXJjaHw2fHx0ZWFtJTIwbG9nb3xlbnwwfHx8fDE3NTk1OTMyOTB8MA&ixlibrb-4.1.0&q=80&w=1080", "imageHint": "team logo" }, { "id": "team-logo-4", "description": "Logo for Chelsea", "imageUrl": "https://images.unsplash.com/photo-1631607054640-876e1cd7ea27?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3NDE5ODJ8MHwxfHNlYXJjaHwyfHx0ZWFtJTIwbG9nb3xlbnwwfHx8fDE3NTk1OTMyOTB8MA&ixlibrb-4.1.0&q=80&w=1080", "imageHint": "team logo" } ] }
src/lib/mock-data.ts
import { PlaceHolderImages } from './placeholder-images'; const findImage = (id: string) => PlaceHolderImages.find((img) => img.id === id); export const featuredPlayers = [ { id: '1', name: 'Erling Haaland', team: 'Manchester City', value: '€180M', trend: 'up', position: 'ST', age: 24, avatar: findImage('player-1'), }, { id: '2', name: 'Kylian Mbappé', team: 'Paris SG', value: '€160M', trend: 'stable', position: 'LW', age: 25, avatar: findImage('player-2'), }, { id: '3', name: 'Jude Bellingham', team: 'Real Madrid', value: '€120M', trend: 'up', position: 'CM', age: 21, avatar: findImage('player-3'), }, { id: '4', name: 'Vinicius Jr', team: 'Real Madrid', value: '€150M', trend: 'up', position: 'LW', age: 24, avatar: findImage('player-4'), }, { id: '5', name: 'Bukayo Saka', team: 'Arsenal', value: '€120M', trend: 'up', position: 'RW', age: 22, avatar: findImage('player-5'), }, ]; export const liveMatches = [ { id: '1', homeTeam: 'Man City', awayTeam: 'Liverpool', homeScore: 2, awayScore: 1, time: "63'", status: 'Second Half', isLive: true, homeLogo: findImage('team-logo-1'), awayLogo: findImage('team-logo-2'), }, { id: '2', homeTeam: 'Arsenal', awayTeam: 'Chelsea', homeScore: 1, awayScore: 1, time: "HT", status: 'Half Time', isLive: true, homeLogo: findImage('team-logo-3'), awayLogo: findImage('team-logo-4'), }, ]; export const upcomingMatches = [ { id: '3', homeTeam: 'Real Madrid', awayTeam: 'Barcelona', homeScore: 0, awayScore: 0, time: "21:00", status: 'Tomorrow', isLive: false, homeLogo: { imageUrl: 'https://picsum.photos/seed/rm/100/100', imageHint: 'team logo'}, awayLogo: { imageUrl: 'https://picsum.photos/seed/bar/100/100', imageHint: 'team logo'}, }, { id: '4', homeTeam: 'Man United', awayTeam: 'Tottenham', homeScore: 0, awayScore: 0, time: "17:30", status: 'Saturday', isLive: false, homeLogo: { imageUrl: 'https://picsum.photos/seed/mun/100/100', imageHint: 'team logo'}, awayLogo: { imageUrl: 'https://picsum.photos/seed/tot/100/100', imageHint: 'team logo'}, }, ] export const walletTransactions = [ { id: '1', type: 'credit', amount: '+€50.00', description: 'Prediction Win: Man City vs Liverpool', date: '2024-07-28', }, { id: '2', type: 'debit', amount: '-€10.00', description: 'Fantasy League Entry', date: '2024-07-27', }, { id: '3', type: 'credit', amount: '+€25.50', description: 'Prediction Win: Arsenal vs Chelsea', date: '2024-07-26', }, { id: '4', type: 'credit', amount: '+€5.00', description: 'Daily Login Bonus', date: '2024-07-26', }, ]; export const analyticsStats = { totalPredictions: 156, correctPredictions: 132, accuracy: 84.6, profit: 245, roi: 15.2, }; export const performanceChartData = [ { date: 'Jul 22', accuracy: 78 }, { date: 'Jul 23', accuracy: 85 }, { date: 'Jul 24', accuracy: 82 }, { date: 'Jul 25', accuracy: 91 }, { date: 'Jul 26', accuracy: 88 }, { date: 'Jul 27', accuracy: 93 }, { date: 'Jul 28', accuracy: 89 }, ]; export const newsItems = [ { id: '1', title: 'Transfer Window Heats Up: Major Moves Expected', excerpt: 'Top clubs across Europe are preparing for a busy summer transfer window, with several high-profile players linked to major moves.', image: { imageUrl: 'https://picsum.photos/seed/news1/400/200', imageHint: 'stadium lights'} }, { id: '2', title: 'Tactical Revolution: The Rise of the Inverted Full-back', excerpt: 'Managers are increasingly adopting the inverted full-back role, changing the dynamics of modern football tactics. We break down how it works.', image: { imageUrl: 'https://picsum.photos/seed/news2/400/200', imageHint: 'tactic board'} }, { id: '3', title: 'Youth Spotlight: The Next Generation of Superstars', excerpt: 'A look at the most promising young talents from around the world who are poised to make a big impact in the coming seasons.', image: { imageUrl: 'https://picsum.photos/seed/news3/400/200', imageHint: 'young player'} } ]
src/app/page.tsx
import Link from 'next/link'; import { Apple, Chrome, Smartphone, } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Separator } from '@/components/ui/separator'; import Logo from '@/components/logo'; export default function LoginPage() { return ( <main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-br from-background to-secondary p-4"> <div className="w-full max-w-md"> <Card className="shadow-2xl"> <CardHeader className="text-center"> <div className="mx-auto mb-4"> <Logo /> </div> <CardTitle className="font-headline text-3xl">Welcome Back!</CardTitle> <CardDescription>Sign in to your analyst account to continue</CardDescription> </CardHeader> <CardContent> <form className="space-y-4"> <div className="space-y-2"> <Label htmlFor="email">Email Address</Label> <Input id="email" type="email" placeholder="user@example.com" /> </div> <div className="space-y-2"> <div className="flex items-center justify-between"> <Label htmlFor="password">Password</Label> <Link href="#" className="text-sm font-medium text-primary/80 hover:text-primary"> Forgot password? </Link> </div> <Input id="password" type="password" placeholder="••••••••" /> </div> <Button asChild className="w-full font-bold"> <Link href="/dashboard">Sign In</Link> </Button> </form> <div className="my-6 flex items-center"> <Separator className="flex-1" /> <span className="mx-4 text-xs uppercase text-muted-foreground">Or continue with</span> <Separator className="flex-1" /> </div> <div className="grid grid-cols-1 gap-2 sm:grid-cols-3"> <Button variant="outline"> <Chrome className="mr-2 h-4 w-4" /> Google </Button> <Button variant="outline"> <Apple className="mr-2 h-4 w-4" /> Apple </Button> <Button variant="outline"> <Smartphone className="mr-2 h-4 w-4" /> Phone </Button> </div> </CardContent> <CardFooter className="justify-center"> <p className="text-sm text-muted-foreground"> Don&apos;t have an account?{' '} <Link href="#" className="font-semibold text-primary/90 hover:text-primary"> Sign up </Link> </p> </CardFooter> </Card> </div> </main> ); }
src/app/layout.tsx
import type { Metadata } from 'next'; import { Toaster } from '@/components/ui/toaster'; import './globals.css'; export const metadata: Metadata = { title: 'SoccerPro AI', description: 'AI-powered football intelligence platform.', }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <html lang="en" suppressHydrationWarning> <head> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" /> <link href="https://fonts.googleapis.com/css2?family=PT+Sans:ital,wght@0,400;0,700;1,400;1,700&family=Space+Grotesk:wght@300..700&display=swap" rel="stylesheet" /> </head> <body className="font-body antialiased"> {children} <Toaster /> </body> </html> ); }
src/app/globals.css
@tailwind base; @tailwind components; @tailwind utilities; body { font-family: Arial, Helvetica, sans-serif; } @layer base { :root { --background: 240 5% 96.1%; --foreground: 240 10% 3.9%; --card: 240 5% 100%; --card-foreground: 240 10% 3.9%; --popover: 240 5% 100%; --popover-foreground: 240 10% 3.9%; --primary: 221.2 83.2% 53.3%; --primary-foreground: 210 40% 98%; --secondary: 240 4.8% 95.9%; --secondary-foreground: 240 5.9% 10%; --muted: 240 4.8% 95.9%; --muted-foreground: 240 3.8% 46.1%; --accent: 240 4.8% 95.9%; --accent-foreground: 240 5.9% 10%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 210 40% 98%; --border: 240 5.9% 90%; --input: 240 5.9% 90%; --ring: 221.2 83.2% 53.3%; --chart-1: 221.2 83.2% 53.3%; --chart-2: 166.2 83.2% 53.3%; --chart-3: 351.2 83.2% 53.3%; --chart-4: 281.2 83.2% 53.3%; --chart-5: 41.2 83.2% 53.3%; --radius: 0.5rem; --sidebar-background: 240 5% 96.1%; --sidebar-foreground: 240 10% 3.9%; --sidebar-primary: 221.2 83.2% 53.3%; --sidebar-primary-foreground: 210 40% 98%; --sidebar-accent: 240 4.8% 95.9%; --sidebar-accent-foreground: 240 5.9% 10%; --sidebar-border: 240 5.9% 90%; --sidebar-ring: 221.2 83.2% 53.3%; } .dark { --background: 240 10% 3.9%; --foreground: 210 40% 98%; --card: 240 10% 3.9%; --card-foreground: 210 40% 98%; --popover: 240 10% 3.9%; --popover-foreground: 210 40% 98%; --primary: 210 40% 98%; --primary-foreground: 240 5.9% 10%; --secondary: 240 3.7% 15.9%; --secondary-foreground: 210 40% 98%; --muted: 240 3.7% 15.9%; --muted-foreground: 240 5% 64.9%; --accent: 240 3.7% 15.9%; --accent-foreground: 210 40% 98%; --destructive: 0 62.8% 30.6%; --destructive-foreground: 210 40% 98%; --border: 240 3.7% 15.9%; --input: 240 3.7% 15.9%; --ring: 210 40% 98%; --chart-1: 210 40% 98%; --chart-2: 160 40% 98%; --chart-3: 350 40% 98%; --chart-4: 280 40% 98%; --chart-5: 40 40% 98%; --sidebar-background: 240 10% 3.9%; --sidebar-foreground: 210 40% 98%; --sidebar-primary: 210 40% 98%; --sidebar-primary-foreground: 240 5.9% 10%; --sidebar-accent: 240 3.7% 15.9%; --sidebar-accent-foreground: 210 40% 98%; --sidebar-border: 240 3.7% 15.9%; --sidebar-ring: 210 40% 98%; } } @layer base { * { @apply border-border; } body { @apply bg-background text-foreground; } }
src/app/favicon.ico
 h600 �%�  �F*(    0h� :]��CW��IM�SG�Z@�b8�h2�v0㯏7ۯ�?ҟ�E�0-j�`0h��8h� �@��D͏�E�`%p�0)o��HP� LL�PP��G̟�H� "s�� ��@Y�JP�0OJ�XH� �@� �C̟w��:_��PP� QI�UE�0�@ҟy��4c��8`� SG�XH� �8ؿ{��(p� +l��0h��5a��5`�0PH� SG�2ߟ��%p��(p� @p�9`��;[�pML�n.����"r��&p�P'o��,l�o0h� <\�o?Z��GP�of3���� u��"r�� p�,l�@.j��3d��:Z�``8� ��� x� u�� x� 0p�/j��Z?� ��� x� "v��%u�0-l��UD� ����� ��"t��#s�P)m��PP�MM��� ���#s��&q�_BV�JP�0��_ �����0h� 3e��;^�_ ��0 �����������}��x�� u��$s��(o��*k��0h� ���<|<<}|�pNr.ppq>x�<<?<���(0` $  @P�=Y�PBV�DU�EQ�HO��JK��MJ��PH��SE��UC��XA��Z>��]<��_:��a7��d4��g2��i0��k.��p.��y0�߂2�ߋ6�ϓ7د�=֏�=�P�@�8`� 8`��;]��=Z��@X��BU��ES��GQ��JO��LL��OJ��QG��SE��VC��X@��[>��^<��`9��b7��d4��g2��j/��l-��r.��{0���4���7���:���<���?��BϏ�@� 2d��4d��6a��9_��<\��@Z�CV�PDT�@HX� HP� p(� x0� �5�0�2�`�9ڏ�;ֿ�=���@���D���Fˏ�P�0h� /i��0h��3e��5c��8`� �@� �?ҟ�A���D���G�߿H� +j�0+j��-i��.i��0h� �@�0�A�߹E���H���J�0(p� *n��+k��,j��0p��@��C͟�D���H���H� p�(o��)m��+m��PJ�0OK�PH� �C̯�E���I��%p��'o��(n�� p�IP�PJN��MK��PJ��P��C�߿F���Hŀ p� $p��&p��+p�0HX� FQ��HP��LM��MK��NH��H� �D���G���H� #q��$r��%r��AU�DT��GR�IM�PLM��NJ��PI��C͟�D���Hʟ p�"s��#r��(x� @X� @X��BV��PP�KQ�_ML��NJ��PH�PP��H� �B���E���P�#v�P!t��#t��=[�p>Z��@W�JP�0ML��OI��RG��UE�0�@Ͽ�C���F�P u��!v�� v��:]��<\��>[�_PP� NK��OI��RF��TD�p�@р�@���D�w��v�� u�`9`��:^��<\�@PP�NK�PH��RF��TD��=�O�>���BЯx��w�� z�06`��8`��9]�pPJ�oQH��SF��UE��<�@�<���?ҿx��x�� x� 3c�P6b��8_��PL�@QG��TE��WC��8� �9���;��z��x�� x� 5c��6a��8`� PE�0QG��TD��VB��8� �7���;��z��y��0h�@3e��4c��7a��8`� TH�@QF��UD��UE�0�5���8��{��z��.k�p-i��/h��1g��3e��5b��7`��8`� RH�RE��UD���3���5��|��{��0p�+l��+k��,j��.j��/h��1g��3d��5b��8`��5`�0PH� QH��SE��UE�0z0���3��}��|��(o��)m��*m��-m�`8h� 3c��6a��8_��9]�pOI�QG��TG�pr.��|1��}��}��&p�'o��(n��0p� @p�5c��7a��9^��<\��@X� NL�OI��PF�m-��u/��~��}�� p�%q��&p��(p�@5c�o7`��:^��<\��=Y�pLL�NK��PH�k.��o-������$t�@$r��%q��5`�09`��:^��=[��?Z��@P�IO�LM��MI�Pi1��k.�������� u�`#s��$r�8^�;]��=[��?X��@X� DT�@GQ��IN��PP�g2��j1�������� u�`"s��$t��(p�`'o��)m��+l��+k��.k�p(h� <`�@;\��=Z��@X��BU��ES��GP�oe4��g1�������� t�O!t��#u�� p� %q��'q��'o��(n��*m��*k��,k��-j��/i��0f�p0e�08`� <\��>Z��@W��EU��c6��f4�������� x� !u��!u��"s��$r��&q��&q�_(p�@(p� )l�P+m��,k��-i��.h��0g��2e��5b��6`��:^��=\��@Y�a8��d5�������� w�� t��!s��"t�� ��.k�_.j��.h��1g��3d��6b��8_��;^�__:��b7��������u�` v��!u��#s�P.k�_-i��/i��0i�_^<��`:��������w�� v��!u��.j�.i��0i��\=��];���������� x� w�� u��!t�� ��.j��/i��0j�`X@� Z@��\=�� �������� x� w�� t��!s�� p� -j�`.j��/i��X@� WA��[>�� ��� ��� ��@ ��v�� t��!t��%u�00h� -k��.j��UE�0UC��W@�� ��� ��� ��P v��!u��"t��"r�p+k��-j��RE�`TE��VC� �� ��� �� u�o!t��"s��$r��+l��,k��PI�RG��TF� ��P ��� ���$x�@"t��#s��#s�� p�0p�)l��+k��MK�PH��SI�P�� ��� ����� x� !s��#s��$s�� p�)m�p)n��)m��HP� KM��NJ��PP���� ��� ��� x� "u��#r��$q�� p�'p��(n��(l�@HQ�IN��KK��� ��� ����� x� "s��#r��$q��&p��'p��@U�0ES��HQ��PP� ����� ����� x� "s��#r��&q��(p� @`�@W��CU��FR������� ��� x� #s��(x� =\�?Y��AW��PP��� ��� ��� �����0`�7_��;]��=\��@X� ��0��� ��� ��� ��00h� 3c��6b��8_��:Z�0�� ��� ��� ��� ����� 0h� /j��/h��1f��4c��8`� �� ��� ��� ��� ��� ��������`��0�� �� (p� (p� (p�@)p�P*n��+l��,k��-j��.i��0h��� ��� ��� ��� ���������������������~��}��}��{��z��y��x��w��v�� u��!t��"r��$r��%q��&p��(o��)n��*m��+l��-k��0p� �� ��P ��� ���������������������~��}��|��{��z��z��x��w�� v��!u��"t��"s��%r��%q��'p��(n��&p�P p��������?���������������������������������?�����������ǃ�������������������?�?����?���?��?�8?��?�|��?����?�����?����?���?�?�?��?�?��?����?����������������������������c����#�������������������������������������������( @   8`� >Z�CW�FT��IO��MK��QH��TE��XA��\=��`:��c7��g3��k/��q.��~1�ߌ6�ߙ9֯�>Ӏ�@�00h� 3c��7`��;]��@Y�DT�HP�`JM�`MJ�`RH�`XE�`Z@�``=�`b8�`e5�`h0�`m-�`x2�`�4݀�8ٟ�<��@���D̯�H� +e�0.i��1g��5b�`�@�@�A�߼E��J�0+j�0+l��-k��0p��@��C͟�F���J�0(p� )n��*m��LL�MI�p�C̟�G���H� &r��'p�� p�FR�JO��MK��PJ�0�P��E���Gȟ%u�0#r��(t�@@X�@CU��HP�@KM�NJ��PH�`�B�`�E���H� v�"t��>[�@X�NM�OJ��PH��B�߻D� v�� u��9]��<Z�NL�oPI��SG�P@��=ү�Aοw�� v��8`��;_�PJ�0PG��UE��X@� �<��?��y��x�`5c�o7`��PH� QG��UD��X@� �8�`�;��z��{�_0e�`5d��6`��PH� QF��UC��2�`�8��{��{�_-l�P-j��/h��2g��5b��8`�SI�PSF��TD�@}2�`�4��|��z�`'m�p*m��+k��,h�@0h� 4d��6b��:`��@`�PH��RF�r-�`{0��~��~�_ p�&o��(n�6c�_7`��9]��<\�@OJ�PG�j-�`q.����}�`$r�p&q��8`� 8`��;\��>Y�KN��MK�h2�`l/�������`"s��#r�� p�(p� p�9^�o<\��?X��DT�@GQ��LP�@e2�`h0�������`"s��#t��%p�0'q��(o��)m��+k��,k��.h��0e�0:`�0=[��@W��FT�b5�`e4�������_ v�P!t��#s��$r��(p�@(p� ,l�@,j�.j��/h��2d��5a��:^��>Z�`8�`b7�������_ v��!t��0h� .i��/g��3c�P8`� ]:�`_9�������` u�` u�� s�_0h� .i��0h�`Z=�`]<�� ��� ��` v� u��"t��-j��/i��XB�`Z?�� ��� �� w�o!u��"u��,j�o-j��TD�WB�� ��� ��� x�@!u��"t��(p� -l�P,k��QH��TF� �� ��� x� !s��#r��(p� )m�p+m��NJ��PH��� ��� ��_ ��!t��#r��(p� 'o��)o��HP�@KN��PJ�0��� ����� ��"t��$r��&p��(p� @`�CS��GR��� ��� ��� ��#s��(p� ?[��AW��HX� ��0 ��� �����0`�6a��;^��@Z�0��0 ��� ��� ��@-j�`0g��3c��:`�0�� ��� ��� ����������`��`��`z�`z�`z�`x�`x�` x�`#u�`#r�`'p�p(p��)m��*k��-j��/j��0h� ��0 �� ���������������~��}��|��y��x��w��!u��"s��#r��&p��(o��*l��0p� �?��������Ï������?�?��?��?���?���?���?���?�<�?�?? �?1��?!�?�?��?���?���?���?���?�O������������������?
src/app/actions.ts
'use server'; import { personalizedRecommendations, PersonalizedRecommendationsInput } from '@/ai/flows/personalized-recommendations'; export async function getPersonalizedRecommendations(input: PersonalizedRecommendationsInput) { try { const recommendations = await personalizedRecommendations(input); return { success: true, data: recommendations }; } catch (error) { console.error('Error getting personalized recommendations:', error); return { success: false, error: 'Failed to get recommendations.' }; } }
src/ai/genkit.ts
import {genkit} from 'genkit'; import {googleAI} from '@genkit-ai/google-genai'; export const ai = genkit({ plugins: [googleAI()], model: 'googleai/gemini-2.5-flash', });
src/ai/dev.ts
import { config } from 'dotenv'; config(); import '@/ai/flows/personalized-recommendations.ts';
src/ai/ai-match-predictions.ts
'use server'; /** * @fileOverview An AI-powered match prediction flow. * * - aiMatchPredictions - A function that handles the match prediction process. * - AiMatchPredictionsInput - The input type for the aiMatchPredictions function. * - AiMatchPredictionsOutput - The return type for the aiMatchPredictions function. */ import {ai} from '@/ai/genkit'; import {z} from 'genkit'; const AiMatchPredictionsInputSchema = z.object({ team1: z.string().describe('The name of the first team.'), team2: z.string().describe('The name of the second team.'), historicalData: z.string().describe('Historical data for both teams.'), currentForm: z.string().describe('Current form of both teams.'), }); export type AiMatchPredictionsInput = z.infer<typeof AiMatchPredictionsInputSchema>; const AiMatchPredictionsOutputSchema = z.object({ predictedWinner: z.string().describe('The predicted winner of the match.'), confidenceLevel: z.number().describe('The confidence level of the prediction (0-1).'), reasoning: z.string().describe('The AI reasoning behind the prediction.'), }); export type AiMatchPredictionsOutput = z.infer<typeof AiMatchPredictionsOutputSchema>; export async function aiMatchPredictions(input: AiMatchPredictionsInput): Promise<AiMatchPredictionsOutput> { return aiMatchPredictionsFlow(input); } const prompt = ai.definePrompt({ name: 'aiMatchPredictionsPrompt', input: {schema: AiMatchPredictionsInputSchema}, output: {schema: AiMatchPredictionsOutputSchema}, prompt: `You are an AI expert in predicting soccer match outcomes. Based on the provided historical data and current form of the teams, predict the winner of the match. Also, provide a confidence level (0-1) for your prediction and the reasoning behind it. Team 1: {{{team1}}} Team 2: {{{team2}}} Historical Data: {{{historicalData}}} Current Form: {{{currentForm}}} \n Output your prediction in JSON format. `, }); const aiMatchPredictionsFlow = ai.defineFlow( { name: 'aiMatchPredictionsFlow', inputSchema: AiMatchPredictionsInputSchema, outputSchema: AiMatchPredictionsOutputSchema, }, async input => { const {output} = await prompt(input); return output!; } );
src/components/logo.tsx
import { Disc3 } from 'lucide-react'; import { cn } from '@/lib/utils'; type LogoProps = { className?: string; iconOnly?: boolean; }; export default function Logo({ className, iconOnly = false }: LogoProps) { return ( <div className={cn('flex items-center gap-2', className)}> <Disc3 className="h-8 w-8 text-primary" /> {!iconOnly && ( <h1 className="font-headline text-2xl font-bold text-foreground"> SoccerPro <span className="text-primary">AI</span> </h1> )} </div> ); }
src/app/code-snapshot/page.tsx
import { promises as fs } from 'fs'; import path from 'path'; import { glob } from 'glob'; // Function to escape HTML special characters function escapeHtml(unsafe: string) { if (!unsafe) return ''; return unsafe .replace(/&/g, "&amp;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;") .replace(/"/g, "&quot;") .replace(/'/g, "&#039;"); } async function getProjectFiles() { const projectRoot = process.cwd(); // Exclude node_modules, .next, and other non-source files const files = await glob('**/*', { cwd: projectRoot, nodir: true, ignore: [ 'node_modules/**', '.next/**', 'public/code-snapshot.html', '*.lockb', 'package-lock.json' ] }); const fileContents = await Promise.all( files.map(async (file) => { try { const content = await fs.readFile(path.join(projectRoot, file), 'utf-8'); return { path: file, content }; } catch (error) { console.error(`Error reading file ${file}:`, error); return { path: file, content: 'Error reading file.' }; } }) ); return fileContents; } export default async function CodeSnapshotPage() { const files = await getProjectFiles(); return ( <div style={{ fontFamily: 'monospace', whiteSpace: 'pre-wrap', padding: '20px', backgroundColor: '#f5f5f5' }}> <h1 style={{ fontFamily: 'sans-serif', color: '#333' }}>Project Code Snapshot</h1> <p style={{ fontFamily: 'sans-serif', color: '#555' }}>This page contains the full source code of all project files for backup and review purposes.</p> {files.map((file, index) => ( <details key={index} style={{ marginBottom: '20px', border: '1px solid #ddd', borderRadius: '5px', overflow: 'hidden' }}> <summary style={{ padding: '10px', backgroundColor: '#eee', cursor: 'pointer', fontWeight: 'bold' }}> {file.path} </summary> <div style={{ padding: '15px', backgroundColor: '#fff', borderTop: '1px solid #ddd', overflowX: 'auto' }}> <code dangerouslySetInnerHTML={{ __html: escapeHtml(file.content) }}></code> </div> </details> ))} </div> ); }
src/ai/flows/personalized-recommendations.ts
'use server'; /** * @fileOverview A personalized recommendation AI agent. * * - personalizedRecommendations - A function that handles the recommendation process. * - PersonalizedRecommendationsInput - The input type for the personalizedRecommendations function. * - PersonalizedRecommendationsOutput - The return type for the personalizedRecommendations function. */ import {ai} from '@/ai/genkit'; import {z} from 'genkit'; const PersonalizedRecommendationsInputSchema = z.object({ userPreferences: z .string() .describe('The user preferences for teams, players, and sports.'), interests: z.string().describe('The user interests.'), }); export type PersonalizedRecommendationsInput = z.infer<typeof PersonalizedRecommendationsInputSchema>; const PersonalizedRecommendationsOutputSchema = z.object({ articles: z.array(z.string()).describe('Recommended articles based on user preferences.'), players: z.array(z.string()).describe('Recommended players based on user preferences.'), matches: z.array(z.string()).describe('Recommended matches based on user preferences.'), }); export type PersonalizedRecommendationsOutput = z.infer<typeof PersonalizedRecommendationsOutputSchema>; export async function personalizedRecommendations( input: PersonalizedRecommendationsInput ): Promise<PersonalizedRecommendationsOutput> { return personalizedRecommendationsFlow(input); } const prompt = ai.definePrompt({ name: 'personalizedRecommendationsPrompt', input: {schema: PersonalizedRecommendationsInputSchema}, output: {schema: PersonalizedRecommendationsOutputSchema}, prompt: `You are an expert sports recommender specializing in personalized recommendations. You will use this information to recommend articles, players, and matches to the user. User Preferences: {{{userPreferences}}} User Interests: {{{interests}}} Recommend articles, players, and matches based on the user's preferences and interests. Return the output in JSON format.`, }); const personalizedRecommendationsFlow = ai.defineFlow( { name: 'personalizedRecommendationsFlow', inputSchema: PersonalizedRecommendationsInputSchema, outputSchema: PersonalizedRecommendationsOutputSchema, }, async input => { const {output} = await prompt(input); return output!; } );
src/components/layout/sidebar-nav.tsx
'use client'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; import { Home, Search, Wallet, BarChart2, User, Lightbulb, Settings, FileCode, } from 'lucide-react'; import { SidebarMenu, SidebarMenuItem, SidebarMenuButton, } from '@/components/ui/sidebar'; const navItems = [ { href: '/dashboard', label: 'Home', icon: Home }, { href: '/dashboard/predictions', label: 'Predictions', icon: Lightbulb }, { href: '/dashboard/discover', label: 'Discover', icon: Search }, { href: '/dashboard/wallet', label: 'Wallet', icon: Wallet }, { href: '/dashboard/analytics', label: 'Analytics', icon: BarChart2 }, ]; const secondaryNavItems = [ { href: '/dashboard/profile', label: 'Profile', icon: User }, { href: '/dashboard/settings', label: 'Settings', icon: Settings }, { href: '/code-snapshot', label: 'Code Snapshot', icon: FileCode }, ] export default function SidebarNav() { const pathname = usePathname(); return ( <div className="flex flex-col h-full"> <SidebarMenu className="flex-1"> {navItems.map((item) => ( <SidebarMenuItem key={item.href}> <SidebarMenuButton asChild isActive={pathname === item.href} tooltip={{ children: item.label }} > <Link href={item.href}> <item.icon /> <span>{item.label}</span> </Link> </SidebarMenuButton> </SidebarMenuItem> ))} </SidebarMenu> <SidebarMenu> {secondaryNavItems.map((item) => ( <SidebarMenuItem key={item.href}> <SidebarMenuButton asChild isActive={pathname === item.href} tooltip={{ children: item.label }} > <Link href={item.href}> <item.icon /> <span>{item.label}</span> </Link> </SidebarMenuButton> </SidebarMenuItem> ))} </SidebarMenu> </div> ); }
src/components/dashboard/stat-card.tsx
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { cn } from '@/lib/utils'; import type { LucideIcon } from 'lucide-react'; type StatCardProps = { title: string; value: string; icon: LucideIcon; description?: string; className?: string; }; export default function StatCard({ title, value, icon: Icon, description, className }: StatCardProps) { return ( <Card className={cn('shadow-md hover:shadow-lg transition-shadow', className)}> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardTitle className="text-sm font-medium">{title}</CardTitle> <Icon className="h-4 w-4 text-muted-foreground" /> </CardHeader> <CardContent> <div className="text-2xl font-bold">{value}</div> {description && <p className="text-xs text-muted-foreground">{description}</p>} </CardContent> </Card> ); }
src/components/dashboard/player-card.tsx
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Card, CardContent } from '@/components/ui/card'; import { TrendingUp, TrendingDown } from 'lucide-react'; type Player = { id: string; name: string; team: string; value: string; trend: 'up' | 'stable' | 'down'; avatar?: { imageUrl: string; imageHint: string; }; }; type PlayerCardProps = { player: Player; }; export default function PlayerCard({ player }: PlayerCardProps) { const getTrendIcon = () => { switch (player.trend) { case 'up': return <TrendingUp className="h-5 w-5 text-green-500" />; case 'down': return <TrendingDown className="h-5 w-5 text-red-500" />; default: return <div className="h-5 w-5" />; // Placeholder for stable } }; const fallback = player.name .split(' ') .map((n) => n[0]) .join(''); return ( <Card className="h-full w-full overflow-hidden transition-transform hover:scale-105 hover:shadow-xl"> <CardContent className="p-4"> <div className="flex items-center justify-between mb-3"> <Avatar className="h-12 w-12 border-2 border-primary/50"> {player.avatar && <AvatarImage src={player.avatar.imageUrl} alt={player.name} data-ai-hint={player.avatar.imageHint} />} <AvatarFallback>{fallback}</AvatarFallback> </Avatar> {getTrendIcon()} </div> <h3 className="font-bold truncate text-base">{player.name}</h3> <p className="text-xs text-muted-foreground truncate">{player.team}</p> <p className="font-headline font-semibold text-lg mt-2 text-primary">{player.value}</p> </CardContent> </Card> ); }
src/components/dashboard/performance-chart.tsx
'use client'; import { TrendingUp } from 'lucide-react'; import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts'; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from '@/components/ui/card'; import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent, } from '@/components/ui/chart'; import { performanceChartData } from '@/lib/mock-data'; const chartConfig = { accuracy: { label: 'Accuracy', color: 'hsl(var(--primary))', }, } satisfies ChartConfig; export default function PerformanceChart() { return ( <div className="h-[300px] w-full"> <ChartContainer config={chartConfig}> <AreaChart accessibilityLayer data={performanceChartData} margin={{ left: 12, right: 12, }} > <CartesianGrid vertical={false} /> <XAxis dataKey="date" tickLine={false} axisLine={false} tickMargin={8} tickFormatter={(value) => value.slice(0, 6)} /> <YAxis domain={[70, 100]} tickLine={false} axisLine={false} tickMargin={8} tickFormatter={(value) => `${value}%`} /> <ChartTooltip cursor={false} content={<ChartTooltipContent indicator="dot" />} /> <defs> <linearGradient id="fillAccuracy" x1="0" y1="0" x2="0" y2="1"> <stop offset="5%" stopColor="hsl(var(--primary))" stopOpacity={0.8} /> <stop offset="95%" stopColor="hsl(var(--primary))" stopOpacity={0.1} /> </linearGradient> </defs> <Area dataKey="accuracy" type="natural" fill="url(#fillAccuracy)" stroke="hsl(var(--primary))" stackId="a" /> </AreaChart> </ChartContainer> </div> ); }
src/components/dashboard/match-card.tsx
import Image from 'next/image'; import { Card, CardContent } from '@/components/ui/card'; import { Separator } from '@/components/ui/separator'; import { Badge } from '@/components/ui/badge'; type Match = { id: string; homeTeam: string; awayTeam: string; homeScore: number; awayScore: number; time: string; status: string; isLive: boolean; homeLogo?: { imageUrl: string; imageHint: string }; awayLogo?: { imageUrl: string; imageHint: string }; }; type MatchCardProps = { match: Match; }; export default function MatchCard({ match }: MatchCardProps) { return ( <Card className="shadow-md hover:shadow-lg transition-shadow"> <CardContent className="p-4"> <div className="flex items-center justify-between"> <div className="flex flex-1 items-center justify-end gap-3"> <span className="font-semibold text-right hidden sm:inline">{match.homeTeam}</span> <span className="font-semibold text-right sm:hidden">{match.homeTeam.substring(0,3).toUpperCase()}</span> {match.homeLogo && ( <Image src={match.homeLogo.imageUrl} alt={`${match.homeTeam} logo`} width={40} height={40} className="rounded-full" data-ai-hint={match.homeLogo.imageHint} /> )} </div> <div className="mx-4 text-center"> <div className="text-2xl font-bold font-headline tracking-tight"> {match.homeScore} - {match.awayScore} </div> </div> <div className="flex flex-1 items-center gap-3"> {match.awayLogo && ( <Image src={match.awayLogo.imageUrl} alt={`${match.awayTeam} logo`} width={40} height={40} className="rounded-full" data-ai-hint={match.awayLogo.imageHint} /> )} <span className="font-semibold text-left hidden sm:inline">{match.awayTeam}</span> <span className="font-semibold text-left sm:hidden">{match.awayTeam.substring(0,3).toUpperCase()}</span> </div> </div> <Separator className="my-3" /> <div className="flex items-center justify-center gap-4 text-sm text-muted-foreground"> {match.isLive && ( <Badge variant="destructive" className="bg-red-500 text-white animate-pulse"> LIVE </Badge> )} <span>{match.time}</span> <Separator orientation="vertical" className="h-4" /> <span>{match.status}</span> </div> </CardContent> </Card> ); }
src/components/dashboard/ai-insight-card.tsx
'use client'; import { useEffect, useState, useTransition } from 'react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Skeleton } from '@/components/ui/skeleton'; import { Lightbulb, Newspaper, Users, Shield, AlertCircle } from 'lucide-react'; import { getPersonalizedRecommendations } from '@/app/actions'; import type { PersonalizedRecommendationsOutput } from '@/ai/flows/personalized-recommendations'; export default function AiInsightCard() { const [isPending, startTransition] = useTransition(); const [recommendations, setRecommendations] = useState<PersonalizedRecommendationsOutput | null>(null); const [error, setError] = useState<string | null>(null); useEffect(() => { startTransition(async () => { setError(null); const result = await getPersonalizedRecommendations({ userPreferences: 'Loves Premier League, follows Manchester City, favorite player is Erling Haaland.', interests: 'Interested in transfer news and tactical analysis.', }); if (result.success) { setRecommendations(result.data); } else { setError(result.error); } }); }, []); return ( <Card className="shadow-lg border-primary/20 h-full flex flex-col"> <CardHeader className="flex flex-row items-start gap-4"> <div className="bg-primary/10 p-2 rounded-full"> <Lightbulb className="h-6 w-6 text-primary" /> </div> <div> <CardTitle className="font-headline text-xl">AI Insight of the Day</CardTitle> <CardDescription>Personalized for you</CardDescription> </div> </CardHeader> <CardContent className="flex-grow"> {isPending && ( <div className="space-y-4"> <Skeleton className="h-6 w-3/4" /> <Skeleton className="h-4 w-full" /> <Skeleton className="h-4 w-5/6" /> <Skeleton className="h-4 w-1/2" /> </div> )} {error && ( <div className="flex flex-col items-center justify-center text-center text-destructive bg-destructive/10 p-4 rounded-md h-full"> <AlertCircle className="h-8 w-8 mb-2" /> <p className="font-semibold">Could not load insights</p> <p className="text-sm">{error}</p> </div> )} {recommendations && ( <div className="space-y-4 text-sm"> {recommendations.articles?.length > 0 && ( <div className="space-y-2"> <h4 className="font-semibold flex items-center gap-2"><Newspaper className="h-4 w-4 text-primary/80" /> Recommended Articles:</h4> <ul className="list-disc list-inside text-muted-foreground space-y-1"> {recommendations.articles.slice(0, 2).map((article, i) => <li key={i}>{article}</li>)} </ul> </div> )} {recommendations.players?.length > 0 && ( <div className="space-y-2"> <h4 className="font-semibold flex items-center gap-2"><Users className="h-4 w-4 text-primary/80" /> Players to Watch:</h4> <p className="text-muted-foreground">{recommendations.players.join(', ')}</p> </div> )} {recommendations.matches?.length > 0 && ( <div className="space-y-2"> <h4 className="font-semibold flex items-center gap-2"><Shield className="h-4 w-4 text-primary/80" /> Upcoming Matches:</h4> <p className="text-muted-foreground">{recommendations.matches.join(' vs ')}</p> </div> )} </div> )} </CardContent> <CardFooter> <Button variant="ghost" className="w-full">View Full Report</Button> </CardFooter> </Card> ); }
src/app/dashboard/page.tsx
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, } from '@/components/ui/carousel'; import StatCard from '@/components/dashboard/stat-card'; import PlayerCard from '@/components/dashboard/player-card'; import MatchCard from '@/components/dashboard/match-card'; import { BarChart, Users, Target } from 'lucide-react'; import { featuredPlayers, liveMatches } from '@/lib/mock-data'; import AiInsightCard from '@/components/dashboard/ai-insight-card'; export default function DashboardPage() { return ( <div className="space-y-6"> <h1 className="font-headline text-3xl font-bold">Home Dashboard</h1> <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> <StatCard title="Players Tracked" value="247" icon={Users} description="12 more than last week" /> <StatCard title="Prediction Accuracy" value="83%" icon={Target} description="+1.2% since last month" /> <StatCard title="AI Reports Generated" value="12" icon={BarChart} description="3 new reports available" /> </div> <div className="grid grid-cols-1 gap-6 lg:grid-cols-3"> <div className="space-y-6 lg:col-span-2"> <div> <h2 className="font-headline text-2xl font-semibold mb-4">Trending Players</h2> <Carousel opts={{ align: 'start', }} className="w-full" > <CarouselContent> {featuredPlayers.map((player) => ( <CarouselItem key={player.id} className="basis-1/2 md:basis-1/3 lg:basis-1/4"> <PlayerCard player={player} /> </CarouselItem> ))} </CarouselContent> <CarouselPrevious className="hidden sm:flex" /> <CarouselNext className="hidden sm:flex" /> </Carousel> </div> <div> <h2 className="font-headline text-2xl font-semibold mb-4">Live Matches</h2> <div className="space-y-4"> {liveMatches.map((match) => ( <MatchCard key={match.id} match={match} /> ))} </div> </div> </div> <div className="lg:col-span-1"> <AiInsightCard /> </div> </div> </div> ); }
src/app/dashboard/layout.tsx
import Link from 'next/link'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Input } from '@/components/ui/input'; import { SidebarProvider, Sidebar, SidebarHeader, SidebarContent, SidebarFooter, SidebarTrigger, SidebarInset, } from '@/components/ui/sidebar'; import { Search, Settings, User, LogOut } from 'lucide-react'; import Logo from '@/components/logo'; import { PlaceHolderImages } from '@/lib/placeholder-images'; import SidebarNav from '@/components/layout/sidebar-nav'; import { Button } from '@/components/ui/button'; export default function DashboardLayout({ children, }: { children: React.ReactNode; }) { const userAvatar = PlaceHolderImages.find((img) => img.id === 'user-avatar'); return ( <SidebarProvider> <div className="flex min-h-screen"> <Sidebar> <SidebarHeader> <Logo iconOnly className="group-data-[collapsible=icon]:hidden" /> </SidebarHeader> <SidebarContent> <SidebarNav /> </SidebarContent> <SidebarFooter> <DropdownMenu> <DropdownMenuTrigger asChild> <Button variant="ghost" className="h-12 w-full justify-start p-2 group-data-[collapsible=icon]:h-10 group-data-[collapsible=icon]:w-10 group-data-[collapsible=icon]:justify-center"> <Avatar className="h-8 w-8"> <AvatarImage src={userAvatar?.imageUrl} alt="User Avatar" data-ai-hint={userAvatar?.imageHint} /> <AvatarFallback>AJ</AvatarFallback> </Avatar> <div className="ml-2 min-w-0 flex-1 text-left group-data-[collapsible=icon]:hidden"> <p className="truncate text-sm font-medium">Alex Johnson</p> <p className="truncate text-xs text-sidebar-foreground/70">Pro Analyst</p> </div> </Button> </DropdownMenuTrigger> <DropdownMenuContent side="right" align="start" className="w-56"> <DropdownMenuLabel>My Account</DropdownMenuLabel> <DropdownMenuSeparator /> <DropdownMenuItem asChild> <Link href="/dashboard/profile"> <User className="mr-2 h-4 w-4" /> <span>Profile</span> </Link> </DropdownMenuItem> <DropdownMenuItem asChild> <Link href="/dashboard/settings"> <Settings className="mr-2 h-4 w-4" /> <span>Settings</span> </Link> </DropdownMenuItem> <DropdownMenuSeparator /> <DropdownMenuItem asChild> <Link href="/"> <LogOut className="mr-2 h-4 w-4" /> <span>Log out</span> </Link> </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </SidebarFooter> </Sidebar> <SidebarInset> <header className="sticky top-0 z-10 flex h-16 items-center gap-4 border-b bg-background/80 px-4 backdrop-blur-sm md:px-6"> <SidebarTrigger className="md:hidden" /> <div className="flex-1"> <form> <div className="relative"> <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" /> <Input type="search" placeholder="Search players, teams, matches..." className="w-full bg-secondary pl-9 md:w-[300px] lg:w-[400px]" /> </div> </form> </div> <p className="text-sm text-muted-foreground hidden md:block">Welcome back, Alex!</p> </header> <main className="flex-1 p-4 md:p-6">{children}</main> </SidebarInset> </div> </SidebarProvider> ); }
src/components/ui/tooltip.tsx
"use client" import * as React from "react" import * as TooltipPrimitive from "@radix-ui/react-tooltip" import { cn } from "@/lib/utils" const TooltipProvider = TooltipPrimitive.Provider const Tooltip = TooltipPrimitive.Root const TooltipTrigger = TooltipPrimitive.Trigger const TooltipContent = React.forwardRef< React.ElementRef<typeof TooltipPrimitive.Content>, React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> >(({ className, sideOffset = 4, ...props }, ref) => ( <TooltipPrimitive.Content ref={ref} sideOffset={sideOffset} className={cn( "z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className )} {...props} /> )) TooltipContent.displayName = TooltipPrimitive.Content.displayName export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
src/components/ui/toaster.tsx
"use client" import { useToast } from "@/hooks/use-toast" import { Toast, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport, } from "@/components/ui/toast" export function Toaster() { const { toasts } = useToast() return ( <ToastProvider> {toasts.map(function ({ id, title, description, action, ...props }) { return ( <Toast key={id} {...props}> <div className="grid gap-1"> {title && <ToastTitle>{title}</ToastTitle>} {description && ( <ToastDescription>{description}</ToastDescription> )} </div> {action} <ToastClose /> </Toast> ) })} <ToastViewport /> </ToastProvider> ) }
src/components/ui/toast.tsx
"use client" import * as React from "react" import * as ToastPrimitives from "@radix-ui/react-toast" import { cva, type VariantProps } from "class-variance-authority" import { X } from "lucide-react" import { cn } from "@/lib/utils" const ToastProvider = ToastPrimitives.Provider const ToastViewport = React.forwardRef< React.ElementRef<typeof ToastPrimitives.Viewport>, React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport> >(({ className, ...props }, ref) => ( <ToastPrimitives.Viewport ref={ref} className={cn( "fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]", className )} {...props} /> )) ToastViewport.displayName = ToastPrimitives.Viewport.displayName const toastVariants = cva( "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", { variants: { variant: { default: "border bg-background text-foreground", destructive: "destructive group border-destructive bg-destructive text-destructive-foreground", }, }, defaultVariants: { variant: "default", }, } ) const Toast = React.forwardRef< React.ElementRef<typeof ToastPrimitives.Root>, React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & VariantProps<typeof toastVariants> >(({ className, variant, ...props }, ref) => { return ( <ToastPrimitives.Root ref={ref} className={cn(toastVariants({ variant }), className)} {...props} /> ) }) Toast.displayName = ToastPrimitives.Root.displayName const ToastAction = React.forwardRef< React.ElementRef<typeof ToastPrimitives.Action>, React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action> >(({ className, ...props }, ref) => ( <ToastPrimitives.Action ref={ref} className={cn( "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive", className )} {...props} /> )) ToastAction.displayName = ToastPrimitives.Action.displayName const ToastClose = React.forwardRef< React.ElementRef<typeof ToastPrimitives.Close>, React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close> >(({ className, ...props }, ref) => ( <ToastPrimitives.Close ref={ref} className={cn( "absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600", className )} toast-close="" {...props} > <X className="h-4 w-4" /> </ToastPrimitives.Close> )) ToastClose.displayName = ToastPrimitives.Close.displayName const ToastTitle = React.forwardRef< React.ElementRef<typeof ToastPrimitives.Title>, React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title> >(({ className, ...props }, ref) => ( <ToastPrimitives.Title ref={ref} className={cn("text-sm font-semibold", className)} {...props} /> )) ToastTitle.displayName = ToastPrimitives.Title.displayName const ToastDescription = React.forwardRef< React.ElementRef<typeof ToastPrimitives.Description>, React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description> >(({ className, ...props }, ref) => ( <ToastPrimitives.Description ref={ref} className={cn("text-sm opacity-90", className)} {...props} /> )) ToastDescription.displayName = ToastPrimitives.Description.displayName type ToastProps = React.ComponentPropsWithoutRef<typeof Toast> type ToastActionElement = React.ReactElement<typeof ToastAction> export { type ToastProps, type ToastActionElement, ToastProvider, ToastViewport, Toast, ToastTitle, ToastDescription, ToastClose, ToastAction, }
src/components/ui/textarea.tsx
import * as React from 'react'; import {cn} from '@/lib/utils'; const Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<'textarea'>>( ({className, ...props}, ref) => { return ( <textarea className={cn( 'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm', className )} ref={ref} {...props} /> ); } ); Textarea.displayName = 'Textarea'; export {Textarea};
src/components/ui/tabs.tsx
"use client" import * as React from "react" import * as TabsPrimitive from "@radix-ui/react-tabs" import { cn } from "@/lib/utils" const Tabs = TabsPrimitive.Root const TabsList = React.forwardRef< React.ElementRef<typeof TabsPrimitive.List>, React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> >(({ className, ...props }, ref) => ( <TabsPrimitive.List ref={ref} className={cn( "inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground", className )} {...props} /> )) TabsList.displayName = TabsPrimitive.List.displayName const TabsTrigger = React.forwardRef< React.ElementRef<typeof TabsPrimitive.Trigger>, React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> >(({ className, ...props }, ref) => ( <TabsPrimitive.Trigger ref={ref} className={cn( "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm", className )} {...props} /> )) TabsTrigger.displayName = TabsPrimitive.Trigger.displayName const TabsContent = React.forwardRef< React.ElementRef<typeof TabsPrimitive.Content>, React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content> >(({ className, ...props }, ref) => ( <TabsPrimitive.Content ref={ref} className={cn( "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", className )} {...props} /> )) TabsContent.displayName = TabsPrimitive.Content.displayName export { Tabs, TabsList, TabsTrigger, TabsContent }
src/components/ui/table.tsx
import * as React from "react" import { cn } from "@/lib/utils" const Table = React.forwardRef< HTMLTableElement, React.HTMLAttributes<HTMLTableElement> >(({ className, ...props }, ref) => ( <div className="relative w-full overflow-auto"> <table ref={ref} className={cn("w-full caption-bottom text-sm", className)} {...props} /> </div> )) Table.displayName = "Table" const TableHeader = React.forwardRef< HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement> >(({ className, ...props }, ref) => ( <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} /> )) TableHeader.displayName = "TableHeader" const TableBody = React.forwardRef< HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement> >(({ className, ...props }, ref) => ( <tbody ref={ref} className={cn("[&_tr:last-child]:border-0", className)} {...props} /> )) TableBody.displayName = "TableBody" const TableFooter = React.forwardRef< HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement> >(({ className, ...props }, ref) => ( <tfoot ref={ref} className={cn( "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className )} {...props} /> )) TableFooter.displayName = "TableFooter" const TableRow = React.forwardRef< HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement> >(({ className, ...props }, ref) => ( <tr ref={ref} className={cn( "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", className )} {...props} /> )) TableRow.displayName = "TableRow" const TableHead = React.forwardRef< HTMLTableCellElement, React.ThHTMLAttributes<HTMLTableCellElement> >(({ className, ...props }, ref) => ( <th ref={ref} className={cn( "h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0", className )} {...props} /> )) TableHead.displayName = "TableHead" const TableCell = React.forwardRef< HTMLTableCellElement, React.TdHTMLAttributes<HTMLTableCellElement> >(({ className, ...props }, ref) => ( <td ref={ref} className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)} {...props} /> )) TableCell.displayName = "TableCell" const TableCaption = React.forwardRef< HTMLTableCaptionElement, React.HTMLAttributes<HTMLTableCaptionElement> >(({ className, ...props }, ref) => ( <caption ref={ref} className={cn("mt-4 text-sm text-muted-foreground", className)} {...props} /> )) TableCaption.displayName = "TableCaption" export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption, }
src/components/ui/switch.tsx
"use client" import * as React from "react" import * as SwitchPrimitives from "@radix-ui/react-switch" import { cn } from "@/lib/utils" const Switch = React.forwardRef< React.ElementRef<typeof SwitchPrimitives.Root>, React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root> >(({ className, ...props }, ref) => ( <SwitchPrimitives.Root className={cn( "peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input", className )} {...props} ref={ref} > <SwitchPrimitives.Thumb className={cn( "pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0" )} /> </SwitchPrimitives.Root> )) Switch.displayName = SwitchPrimitives.Root.displayName export { Switch }
src/components/ui/slider.tsx
"use client" import * as React from "react" import * as SliderPrimitive from "@radix-ui/react-slider" import { cn } from "@/lib/utils" const Slider = React.forwardRef< React.ElementRef<typeof SliderPrimitive.Root>, React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root> >(({ className, ...props }, ref) => ( <SliderPrimitive.Root ref={ref} className={cn( "relative flex w-full touch-none select-none items-center", className )} {...props} > <SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary"> <SliderPrimitive.Range className="absolute h-full bg-primary" /> </SliderPrimitive.Track> <SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" /> </SliderPrimitive.Root> )) Slider.displayName = SliderPrimitive.Root.displayName export { Slider }
src/components/ui/skeleton.tsx
import { cn } from "@/lib/utils" function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) { return ( <div className={cn("animate-pulse rounded-md bg-muted", className)} {...props} /> ) } export { Skeleton }
src/components/ui/sidebar.tsx
"use client" import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { VariantProps, cva } from "class-variance-authority" import { PanelLeft } from "lucide-react" import { useIsMobile } from "@/hooks/use-mobile" import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Separator } from "@/components/ui/separator" import { Sheet, SheetContent } from "@/components/ui/sheet" import { Skeleton } from "@/components/ui/skeleton" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip" const SIDEBAR_COOKIE_NAME = "sidebar_state" const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 const SIDEBAR_WIDTH = "16rem" const SIDEBAR_WIDTH_MOBILE = "18rem" const SIDEBAR_WIDTH_ICON = "3rem" const SIDEBAR_KEYBOARD_SHORTCUT = "b" type SidebarContext = { state: "expanded" | "collapsed" open: boolean setOpen: (open: boolean) => void openMobile: boolean setOpenMobile: (open: boolean) => void isMobile: boolean toggleSidebar: () => void } const SidebarContext = React.createContext<SidebarContext | null>(null) function useSidebar() { const context = React.useContext(SidebarContext) if (!context) { throw new Error("useSidebar must be used within a SidebarProvider.") } return context } const SidebarProvider = React.forwardRef< HTMLDivElement, React.ComponentProps<"div"> & { defaultOpen?: boolean open?: boolean onOpenChange?: (open: boolean) => void } >( ( { defaultOpen = true, open: openProp, onOpenChange: setOpenProp, className, style, children, ...props }, ref ) => { const isMobile = useIsMobile() const [openMobile, setOpenMobile] = React.useState(false) // This is the internal state of the sidebar. // We use openProp and setOpenProp for control from outside the component. const [_open, _setOpen] = React.useState(defaultOpen) const open = openProp ?? _open const setOpen = React.useCallback( (value: boolean | ((value: boolean) => boolean)) => { const openState = typeof value === "function" ? value(open) : value if (setOpenProp) { setOpenProp(openState) } else { _setOpen(openState) } // This sets the cookie to keep the sidebar state. document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}` }, [setOpenProp, open] ) // Helper to toggle the sidebar. const toggleSidebar = React.useCallback(() => { return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open) }, [isMobile, setOpen, setOpenMobile]) // Adds a keyboard shortcut to toggle the sidebar. React.useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if ( event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey) ) { event.preventDefault() toggleSidebar() } } window.addEventListener("keydown", handleKeyDown) return () => window.removeEventListener("keydown", handleKeyDown) }, [toggleSidebar]) // We add a state so that we can do data-state="expanded" or "collapsed". // This makes it easier to style the sidebar with Tailwind classes. const state = open ? "expanded" : "collapsed" const contextValue = React.useMemo<SidebarContext>( () => ({ state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar, }), [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] ) return ( <SidebarContext.Provider value={contextValue}> <TooltipProvider delayDuration={0}> <div style={ { "--sidebar-width": SIDEBAR_WIDTH, "--sidebar-width-icon": SIDEBAR_WIDTH_ICON, ...style, } as React.CSSProperties } className={cn( "group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar", className )} ref={ref} {...props} > {children} </div> </TooltipProvider> </SidebarContext.Provider> ) } ) SidebarProvider.displayName = "SidebarProvider" const Sidebar = React.forwardRef< HTMLDivElement, React.ComponentProps<"div"> & { side?: "left" | "right" variant?: "sidebar" | "floating" | "inset" collapsible?: "offcanvas" | "icon" | "none" } >( ( { side = "left", variant = "sidebar", collapsible = "offcanvas", className, children, ...props }, ref ) => { const { isMobile, state, openMobile, setOpenMobile } = useSidebar() if (collapsible === "none") { return ( <div className={cn( "flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground", className )} ref={ref} {...props} > {children} </div> ) } if (isMobile) { return ( <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}> <SheetContent data-sidebar="sidebar" data-mobile="true" className="w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden" style={ { "--sidebar-width": SIDEBAR_WIDTH_MOBILE, } as React.CSSProperties } side={side} > <div className="flex h-full w-full flex-col">{children}</div> </SheetContent> </Sheet> ) } return ( <div ref={ref} className="group peer hidden md:block text-sidebar-foreground" data-state={state} data-collapsible={state === "collapsed" ? collapsible : ""} data-variant={variant} data-side={side} > {/* This is what handles the sidebar gap on desktop */} <div className={cn( "duration-200 relative h-svh w-[--sidebar-width] bg-transparent transition-[width] ease-linear", "group-data-[collapsible=offcanvas]:w-0", "group-data-[side=right]:rotate-180", variant === "floating" || variant === "inset" ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]" : "group-data-[collapsible=icon]:w-[--sidebar-width-icon]" )} /> <div className={cn( "duration-200 fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] ease-linear md:flex", side === "left" ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]" : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]", // Adjust the padding for floating and inset variants. variant === "floating" || variant === "inset" ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]" : "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l", className )} {...props} > <div data-sidebar="sidebar" className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow" > {children} </div> </div> </div> ) } ) Sidebar.displayName = "Sidebar" const SidebarTrigger = React.forwardRef< React.ElementRef<typeof Button>, React.ComponentProps<typeof Button> >(({ className, onClick, ...props }, ref) => { const { toggleSidebar } = useSidebar() return ( <Button ref={ref} data-sidebar="trigger" variant="ghost" size="icon" className={cn("h-7 w-7", className)} onClick={(event) => { onClick?.(event) toggleSidebar() }} {...props} > <PanelLeft /> <span className="sr-only">Toggle Sidebar</span> </Button> ) }) SidebarTrigger.displayName = "SidebarTrigger" const SidebarRail = React.forwardRef< HTMLButtonElement, React.ComponentProps<"button"> >(({ className, ...props }, ref) => { const { toggleSidebar } = useSidebar() return ( <button ref={ref} data-sidebar="rail" aria-label="Toggle Sidebar" tabIndex={-1} onClick={toggleSidebar} title="Toggle Sidebar" className={cn( "absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex", "[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize", "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize", "group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar", "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2", "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2", className )} {...props} /> ) }) SidebarRail.displayName = "SidebarRail" const SidebarInset = React.forwardRef< HTMLDivElement, React.ComponentProps<"main"> >(({ className, ...props }, ref) => { return ( <main ref={ref} className={cn( "relative flex min-h-svh flex-1 flex-col bg-background", "peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow", className )} {...props} /> ) }) SidebarInset.displayName = "SidebarInset" const SidebarInput = React.forwardRef< React.ElementRef<typeof Input>, React.ComponentProps<typeof Input> >(({ className, ...props }, ref) => { return ( <Input ref={ref} data-sidebar="input" className={cn( "h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring", className )} {...props} /> ) }) SidebarInput.displayName = "SidebarInput" const SidebarHeader = React.forwardRef< HTMLDivElement, React.ComponentProps<"div"> >(({ className, ...props }, ref) => { return ( <div ref={ref} data-sidebar="header" className={cn("flex flex-col gap-2 p-2", className)} {...props} /> ) }) SidebarHeader.displayName = "SidebarHeader" const SidebarFooter = React.forwardRef< HTMLDivElement, React.ComponentProps<"div"> >(({ className, ...props }, ref) => { return ( <div ref={ref} data-sidebar="footer" className={cn("flex flex-col gap-2 p-2", className)} {...props} /> ) }) SidebarFooter.displayName = "SidebarFooter" const SidebarSeparator = React.forwardRef< React.ElementRef<typeof Separator>, React.ComponentProps<typeof Separator> >(({ className, ...props }, ref) => { return ( <Separator ref={ref} data-sidebar="separator" className={cn("mx-2 w-auto bg-sidebar-border", className)} {...props} /> ) }) SidebarSeparator.displayName = "SidebarSeparator" const SidebarContent = React.forwardRef< HTMLDivElement, React.ComponentProps<"div"> >(({ className, ...props }, ref) => { return ( <div ref={ref} data-sidebar="content" className={cn( "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden", className )} {...props} /> ) }) SidebarContent.displayName = "SidebarContent" const SidebarGroup = React.forwardRef< HTMLDivElement, React.ComponentProps<"div"> >(({ className, ...props }, ref) => { return ( <div ref={ref} data-sidebar="group" className={cn("relative flex w-full min-w-0 flex-col p-2", className)} {...props} /> ) }) SidebarGroup.displayName = "SidebarGroup" const SidebarGroupLabel = React.forwardRef< HTMLDivElement, React.ComponentProps<"div"> & { asChild?: boolean } >(({ className, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : "div" return ( <Comp ref={ref} data-sidebar="group-label" className={cn( "duration-200 flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opa] ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0", className )} {...props} /> ) }) SidebarGroupLabel.displayName = "SidebarGroupLabel" const SidebarGroupAction = React.forwardRef< HTMLButtonElement, React.ComponentProps<"button"> & { asChild?: boolean } >(({ className, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : "button" return ( <Comp ref={ref} data-sidebar="group-action" className={cn( "absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", // Increases the hit area of the button on mobile. "after:absolute after:-inset-2 after:md:hidden", "group-data-[collapsible=icon]:hidden", className )} {...props} /> ) }) SidebarGroupAction.displayName = "SidebarGroupAction" const SidebarGroupContent = React.forwardRef< HTMLDivElement, React.ComponentProps<"div"> >(({ className, ...props }, ref) => ( <div ref={ref} data-sidebar="group-content" className={cn("w-full text-sm", className)} {...props} /> )) SidebarGroupContent.displayName = "SidebarGroupContent" const SidebarMenu = React.forwardRef< HTMLUListElement, React.ComponentProps<"ul"> >(({ className, ...props }, ref) => ( <ul ref={ref} data-sidebar="menu" className={cn("flex w-full min-w-0 flex-col gap-1", className)} {...props} /> )) SidebarMenu.displayName = "SidebarMenu" const SidebarMenuItem = React.forwardRef< HTMLLIElement, React.ComponentProps<"li"> >(({ className, ...props }, ref) => ( <li ref={ref} data-sidebar="menu-item" className={cn("group/menu-item relative", className)} {...props} /> )) SidebarMenuItem.displayName = "SidebarMenuItem" const sidebarMenuButtonVariants = cva( "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", { variants: { variant: { default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", outline: "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]", }, size: { default: "h-8 text-sm", sm: "h-7 text-xs", lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0", }, }, defaultVariants: { variant: "default", size: "default", }, } ) const SidebarMenuButton = React.forwardRef< HTMLButtonElement, React.ComponentProps<"button"> & { asChild?: boolean isActive?: boolean tooltip?: string | React.ComponentProps<typeof TooltipContent> } & VariantProps<typeof sidebarMenuButtonVariants> >( ( { asChild = false, isActive = false, variant = "default", size = "default", tooltip, className, ...props }, ref ) => { const Comp = asChild ? Slot : "button" const { isMobile, state } = useSidebar() const button = ( <Comp ref={ref} data-sidebar="menu-button" data-size={size} data-active={isActive} className={cn(sidebarMenuButtonVariants({ variant, size }), className)} {...props} /> ) if (!tooltip) { return button } if (typeof tooltip === "string") { tooltip = { children: tooltip, } } return ( <Tooltip> <TooltipTrigger asChild>{button}</TooltipTrigger> <TooltipContent side="right" align="center" hidden={state !== "collapsed" || isMobile} {...tooltip} /> </Tooltip> ) } ) SidebarMenuButton.displayName = "SidebarMenuButton" const SidebarMenuAction = React.forwardRef< HTMLButtonElement, React.ComponentProps<"button"> & { asChild?: boolean showOnHover?: boolean } >(({ className, asChild = false, showOnHover = false, ...props }, ref) => { const Comp = asChild ? Slot : "button" return ( <Comp ref={ref} data-sidebar="menu-action" className={cn( "absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0", // Increases the hit area of the button on mobile. "after:absolute after:-inset-2 after:md:hidden", "peer-data-[size=sm]/menu-button:top-1", "peer-data-[size=default]/menu-button:top-1.5", "peer-data-[size=lg]/menu-button:top-2.5", "group-data-[collapsible=icon]:hidden", showOnHover && "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0", className )} {...props} /> ) }) SidebarMenuAction.displayName = "SidebarMenuAction" const SidebarMenuBadge = React.forwardRef< HTMLDivElement, React.ComponentProps<"div"> >(({ className, ...props }, ref) => ( <div ref={ref} data-sidebar="menu-badge" className={cn( "absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground select-none pointer-events-none", "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground", "peer-data-[size=sm]/menu-button:top-1", "peer-data-[size=default]/menu-button:top-1.5", "peer-data-[size=lg]/menu-button:top-2.5", "group-data-[collapsible=icon]:hidden", className )} {...props} /> )) SidebarMenuBadge.displayName = "SidebarMenuBadge" const SidebarMenuSkeleton = React.forwardRef< HTMLDivElement, React.ComponentProps<"div"> & { showIcon?: boolean } >(({ className, showIcon = false, ...props }, ref) => { // Random width between 50 to 90%. const width = React.useMemo(() => { return `${Math.floor(Math.random() * 40) + 50}%` }, []) return ( <div ref={ref} data-sidebar="menu-skeleton" className={cn("rounded-md h-8 flex gap-2 px-2 items-center", className)} {...props} > {showIcon && ( <Skeleton className="size-4 rounded-md" data-sidebar="menu-skeleton-icon" /> )} <Skeleton className="h-4 flex-1 max-w-[--skeleton-width]" data-sidebar="menu-skeleton-text" style={ { "--skeleton-width": width, } as React.CSSProperties } /> </div> ) }) SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton" const SidebarMenuSub = React.forwardRef< HTMLUListElement, React.ComponentProps<"ul"> >(({ className, ...props }, ref) => ( <ul ref={ref} data-sidebar="menu-sub" className={cn( "mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5", "group-data-[collapsible=icon]:hidden", className )} {...props} /> )) SidebarMenuSub.displayName = "SidebarMenuSub" const SidebarMenuSubItem = React.forwardRef< HTMLLIElement, React.ComponentProps<"li"> >(({ ...props }, ref) => <li ref={ref} {...props} />) SidebarMenuSubItem.displayName = "SidebarMenuSubItem" const SidebarMenuSubButton = React.forwardRef< HTMLAnchorElement, React.ComponentProps<"a"> & { asChild?: boolean size?: "sm" | "md" isActive?: boolean } >(({ asChild = false, size = "md", isActive, className, ...props }, ref) => { const Comp = asChild ? Slot : "a" return ( <Comp ref={ref} data-sidebar="menu-sub-button" data-size={size} data-active={isActive} className={cn( "flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground", "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground", size === "sm" && "text-xs", size === "md" && "text-sm", "group-data-[collapsible=icon]:hidden", className )} {...props} /> ) }) SidebarMenuSubButton.displayName = "SidebarMenuSubButton" export { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, useSidebar, }
src/components/ui/sheet.tsx
"use client" import * as React from "react" import * as SheetPrimitive from "@radix-ui/react-dialog" import { cva, type VariantProps } from "class-variance-authority" import { X } from "lucide-react" import { cn } from "@/lib/utils" const Sheet = SheetPrimitive.Root const SheetTrigger = SheetPrimitive.Trigger const SheetClose = SheetPrimitive.Close const SheetPortal = SheetPrimitive.Portal const SheetOverlay = React.forwardRef< React.ElementRef<typeof SheetPrimitive.Overlay>, React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay> >(({ className, ...props }, ref) => ( <SheetPrimitive.Overlay className={cn( "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", className )} {...props} ref={ref} /> )) SheetOverlay.displayName = SheetPrimitive.Overlay.displayName const sheetVariants = cva( "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", { variants: { side: { top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", bottom: "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", right: "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", }, }, defaultVariants: { side: "right", }, } ) interface SheetContentProps extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>, VariantProps<typeof sheetVariants> {} const SheetContent = React.forwardRef< React.ElementRef<typeof SheetPrimitive.Content>, SheetContentProps >(({ side = "right", className, children, ...props }, ref) => ( <SheetPortal> <SheetOverlay /> <SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props} > {children} <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary"> <X className="h-4 w-4" /> <span className="sr-only">Close</span> </SheetPrimitive.Close> </SheetPrimitive.Content> </SheetPortal> )) SheetContent.displayName = SheetPrimitive.Content.displayName const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => ( <div className={cn( "flex flex-col space-y-2 text-center sm:text-left", className )} {...props} /> ) SheetHeader.displayName = "SheetHeader" const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => ( <div className={cn( "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className )} {...props} /> ) SheetFooter.displayName = "SheetFooter" const SheetTitle = React.forwardRef< React.ElementRef<typeof SheetPrimitive.Title>, React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title> >(({ className, ...props }, ref) => ( <SheetPrimitive.Title ref={ref} className={cn("text-lg font-semibold text-foreground", className)} {...props} /> )) SheetTitle.displayName = SheetPrimitive.Title.displayName const SheetDescription = React.forwardRef< React.ElementRef<typeof SheetPrimitive.Description>, React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description> >(({ className, ...props }, ref) => ( <SheetPrimitive.Description ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} /> )) SheetDescription.displayName = SheetPrimitive.Description.displayName export { Sheet, SheetPortal, SheetOverlay, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription, }
src/components/ui/separator.tsx
"use client" import * as React from "react" import * as SeparatorPrimitive from "@radix-ui/react-separator" import { cn } from "@/lib/utils" const Separator = React.forwardRef< React.ElementRef<typeof SeparatorPrimitive.Root>, React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root> >( ( { className, orientation = "horizontal", decorative = true, ...props }, ref ) => ( <SeparatorPrimitive.Root ref={ref} decorative={decorative} orientation={orientation} className={cn( "shrink-0 bg-border", orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]", className )} {...props} /> ) ) Separator.displayName = SeparatorPrimitive.Root.displayName export { Separator }
src/components/ui/select.tsx
"use client" import * as React from "react" import * as SelectPrimitive from "@radix-ui/react-select" import { Check, ChevronDown, ChevronUp } from "lucide-react" import { cn } from "@/lib/utils" const Select = SelectPrimitive.Root const SelectGroup = SelectPrimitive.Group const SelectValue = SelectPrimitive.Value const SelectTrigger = React.forwardRef< React.ElementRef<typeof SelectPrimitive.Trigger>, React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> >(({ className, children, ...props }, ref) => ( <SelectPrimitive.Trigger ref={ref} className={cn( "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", className )} {...props} > {children} <SelectPrimitive.Icon asChild> <ChevronDown className="h-4 w-4 opacity-50" /> </SelectPrimitive.Icon> </SelectPrimitive.Trigger> )) SelectTrigger.displayName = SelectPrimitive.Trigger.displayName const SelectScrollUpButton = React.forwardRef< React.ElementRef<typeof SelectPrimitive.ScrollUpButton>, React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton> >(({ className, ...props }, ref) => ( <SelectPrimitive.ScrollUpButton ref={ref} className={cn( "flex cursor-default items-center justify-center py-1", className )} {...props} > <ChevronUp className="h-4 w-4" /> </SelectPrimitive.ScrollUpButton> )) SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName const SelectScrollDownButton = React.forwardRef< React.ElementRef<typeof SelectPrimitive.ScrollDownButton>, React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton> >(({ className, ...props }, ref) => ( <SelectPrimitive.ScrollDownButton ref={ref} className={cn( "flex cursor-default items-center justify-center py-1", className )} {...props} > <ChevronDown className="h-4 w-4" /> </SelectPrimitive.ScrollDownButton> )) SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName const SelectContent = React.forwardRef< React.ElementRef<typeof SelectPrimitive.Content>, React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content> >(({ className, children, position = "popper", ...props }, ref) => ( <SelectPrimitive.Portal> <SelectPrimitive.Content ref={ref} className={cn( "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className )} position={position} {...props} > <SelectScrollUpButton /> <SelectPrimitive.Viewport className={cn( "p-1", position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]" )} > {children} </SelectPrimitive.Viewport> <SelectScrollDownButton /> </SelectPrimitive.Content> </SelectPrimitive.Portal> )) SelectContent.displayName = SelectPrimitive.Content.displayName const SelectLabel = React.forwardRef< React.ElementRef<typeof SelectPrimitive.Label>, React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label> >(({ className, ...props }, ref) => ( <SelectPrimitive.Label ref={ref} className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)} {...props} /> )) SelectLabel.displayName = SelectPrimitive.Label.displayName const SelectItem = React.forwardRef< React.ElementRef<typeof SelectPrimitive.Item>, React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item> >(({ className, children, ...props }, ref) => ( <SelectPrimitive.Item ref={ref} className={cn( "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className )} {...props} > <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <SelectPrimitive.ItemIndicator> <Check className="h-4 w-4" /> </SelectPrimitive.ItemIndicator> </span> <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> </SelectPrimitive.Item> )) SelectItem.displayName = SelectPrimitive.Item.displayName const SelectSeparator = React.forwardRef< React.ElementRef<typeof SelectPrimitive.Separator>, React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator> >(({ className, ...props }, ref) => ( <SelectPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} /> )) SelectSeparator.displayName = SelectPrimitive.Separator.displayName export { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectLabel, SelectItem, SelectSeparator, SelectScrollUpButton, SelectScrollDownButton, }
src/components/ui/scroll-area.tsx
"use client" import * as React from "react" import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" import { cn } from "@/lib/utils" const ScrollArea = React.forwardRef< React.ElementRef<typeof ScrollAreaPrimitive.Root>, React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> >(({ className, children, ...props }, ref) => ( <ScrollAreaPrimitive.Root ref={ref} className={cn("relative overflow-hidden", className)} {...props} > <ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]"> {children} </ScrollAreaPrimitive.Viewport> <ScrollBar /> <ScrollAreaPrimitive.Corner /> </ScrollAreaPrimitive.Root> )) ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName const ScrollBar = React.forwardRef< React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>, React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar> >(({ className, orientation = "vertical", ...props }, ref) => ( <ScrollAreaPrimitive.ScrollAreaScrollbar ref={ref} orientation={orientation} className={cn( "flex touch-none select-none transition-colors", orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-[1px]", orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent p-[1px]", className )} {...props} > <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" /> </ScrollAreaPrimitive.ScrollAreaScrollbar> )) ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName export { ScrollArea, ScrollBar }
src/components/ui/radio-group.tsx
"use client" import * as React from "react" import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" import { Circle } from "lucide-react" import { cn } from "@/lib/utils" const RadioGroup = React.forwardRef< React.ElementRef<typeof RadioGroupPrimitive.Root>, React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root> >(({ className, ...props }, ref) => { return ( <RadioGroupPrimitive.Root className={cn("grid gap-2", className)} {...props} ref={ref} /> ) }) RadioGroup.displayName = RadioGroupPrimitive.Root.displayName const RadioGroupItem = React.forwardRef< React.ElementRef<typeof RadioGroupPrimitive.Item>, React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item> >(({ className, ...props }, ref) => { return ( <RadioGroupPrimitive.Item ref={ref} className={cn( "aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", className )} {...props} > <RadioGroupPrimitive.Indicator className="flex items-center justify-center"> <Circle className="h-2.5 w-2.5 fill-current text-current" /> </RadioGroupPrimitive.Indicator> </RadioGroupPrimitive.Item> ) }) RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName export { RadioGroup, RadioGroupItem }
src/components/ui/progress.tsx
"use client" import * as React from "react" import * as ProgressPrimitive from "@radix-ui/react-progress" import { cn } from "@/lib/utils" const Progress = React.forwardRef< React.ElementRef<typeof ProgressPrimitive.Root>, React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root> >(({ className, value, ...props }, ref) => ( <ProgressPrimitive.Root ref={ref} className={cn( "relative h-4 w-full overflow-hidden rounded-full bg-secondary", className )} {...props} > <ProgressPrimitive.Indicator className="h-full w-full flex-1 bg-primary transition-all" style={{ transform: `translateX(-${100 - (value || 0)}%)` }} /> </ProgressPrimitive.Root> )) Progress.displayName = ProgressPrimitive.Root.displayName export { Progress }
src/components/ui/popover.tsx
"use client" import * as React from "react" import * as PopoverPrimitive from "@radix-ui/react-popover" import { cn } from "@/lib/utils" const Popover = PopoverPrimitive.Root const PopoverTrigger = PopoverPrimitive.Trigger const PopoverContent = React.forwardRef< React.ElementRef<typeof PopoverPrimitive.Content>, React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( <PopoverPrimitive.Portal> <PopoverPrimitive.Content ref={ref} align={align} sideOffset={sideOffset} className={cn( "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className )} {...props} /> </PopoverPrimitive.Portal> )) PopoverContent.displayName = PopoverPrimitive.Content.displayName export { Popover, PopoverTrigger, PopoverContent }
src/components/ui/menubar.tsx
"use client" import * as React from "react" import * as MenubarPrimitive from "@radix-ui/react-menubar" import { Check, ChevronRight, Circle } from "lucide-react" import { cn } from "@/lib/utils" function MenubarMenu({ ...props }: React.ComponentProps<typeof MenubarPrimitive.Menu>) { return <MenubarPrimitive.Menu {...props} /> } function MenubarGroup({ ...props }: React.ComponentProps<typeof MenubarPrimitive.Group>) { return <MenubarPrimitive.Group {...props} /> } function MenubarPortal({ ...props }: React.ComponentProps<typeof MenubarPrimitive.Portal>) { return <MenubarPrimitive.Portal {...props} /> } function MenubarRadioGroup({ ...props }: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) { return <MenubarPrimitive.RadioGroup {...props} /> } function MenubarSub({ ...props }: React.ComponentProps<typeof MenubarPrimitive.Sub>) { return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} /> } const Menubar = React.forwardRef< React.ElementRef<typeof MenubarPrimitive.Root>, React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root> >(({ className, ...props }, ref) => ( <MenubarPrimitive.Root ref={ref} className={cn( "flex h-10 items-center space-x-1 rounded-md border bg-background p-1", className )} {...props} /> )) Menubar.displayName = MenubarPrimitive.Root.displayName const MenubarTrigger = React.forwardRef< React.ElementRef<typeof MenubarPrimitive.Trigger>, React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger> >(({ className, ...props }, ref) => ( <MenubarPrimitive.Trigger ref={ref} className={cn( "flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground", className )} {...props} /> )) MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName const MenubarSubTrigger = React.forwardRef< React.ElementRef<typeof MenubarPrimitive.SubTrigger>, React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & { inset?: boolean } >(({ className, inset, children, ...props }, ref) => ( <MenubarPrimitive.SubTrigger ref={ref} className={cn( "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground", inset && "pl-8", className )} {...props} > {children} <ChevronRight className="ml-auto h-4 w-4" /> </MenubarPrimitive.SubTrigger> )) MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName const MenubarSubContent = React.forwardRef< React.ElementRef<typeof MenubarPrimitive.SubContent>, React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent> >(({ className, ...props }, ref) => ( <MenubarPrimitive.SubContent ref={ref} className={cn( "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className )} {...props} /> )) MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName const MenubarContent = React.forwardRef< React.ElementRef<typeof MenubarPrimitive.Content>, React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content> >( ( { className, align = "start", alignOffset = -4, sideOffset = 8, ...props }, ref ) => ( <MenubarPrimitive.Portal> <MenubarPrimitive.Content ref={ref} align={align} alignOffset={alignOffset} sideOffset={sideOffset} className={cn( "z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className )} {...props} /> </MenubarPrimitive.Portal> ) ) MenubarContent.displayName = MenubarPrimitive.Content.displayName const MenubarItem = React.forwardRef< React.ElementRef<typeof MenubarPrimitive.Item>, React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & { inset?: boolean } >(({ className, inset, ...props }, ref) => ( <MenubarPrimitive.Item ref={ref} className={cn( "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", inset && "pl-8", className )} {...props} /> )) MenubarItem.displayName = MenubarPrimitive.Item.displayName const MenubarCheckboxItem = React.forwardRef< React.ElementRef<typeof MenubarPrimitive.CheckboxItem>, React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem> >(({ className, children, checked, ...props }, ref) => ( <MenubarPrimitive.CheckboxItem ref={ref} className={cn( "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className )} checked={checked} {...props} > <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <MenubarPrimitive.ItemIndicator> <Check className="h-4 w-4" /> </MenubarPrimitive.ItemIndicator> </span> {children} </MenubarPrimitive.CheckboxItem> )) MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName const MenubarRadioItem = React.forwardRef< React.ElementRef<typeof MenubarPrimitive.RadioItem>, React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem> >(({ className, children, ...props }, ref) => ( <MenubarPrimitive.RadioItem ref={ref} className={cn( "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className )} {...props} > <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <MenubarPrimitive.ItemIndicator> <Circle className="h-2 w-2 fill-current" /> </MenubarPrimitive.ItemIndicator> </span> {children} </MenubarPrimitive.RadioItem> )) MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName const MenubarLabel = React.forwardRef< React.ElementRef<typeof MenubarPrimitive.Label>, React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & { inset?: boolean } >(({ className, inset, ...props }, ref) => ( <MenubarPrimitive.Label ref={ref} className={cn( "px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className )} {...props} /> )) MenubarLabel.displayName = MenubarPrimitive.Label.displayName const MenubarSeparator = React.forwardRef< React.ElementRef<typeof MenubarPrimitive.Separator>, React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator> >(({ className, ...props }, ref) => ( <MenubarPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} /> )) MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName const MenubarShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => { return ( <span className={cn( "ml-auto text-xs tracking-widest text-muted-foreground", className )} {...props} /> ) } MenubarShortcut.displayname = "MenubarShortcut" export { Menubar, MenubarMenu, MenubarTrigger, MenubarContent, MenubarItem, MenubarSeparator, MenubarLabel, MenubarCheckboxItem, MenubarRadioGroup, MenubarRadioItem, MenubarPortal, MenubarSubContent, MenubarSubTrigger, MenubarGroup, MenubarSub, MenubarShortcut, }
src/components/ui/label.tsx
"use client" import * as React from "react" import * as LabelPrimitive from "@radix-ui/react-label" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const labelVariants = cva( "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" ) const Label = React.forwardRef< React.ElementRef<typeof LabelPrimitive.Root>, React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants> >(({ className, ...props }, ref) => ( <LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} /> )) Label.displayName = LabelPrimitive.Root.displayName export { Label }
src/components/ui/input.tsx
import * as React from "react" import { cn } from "@/lib/utils" const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>( ({ className, type, ...props }, ref) => { return ( <input type={type} className={cn( "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", className )} ref={ref} {...props} /> ) } ) Input.displayName = "Input" export { Input }
src/components/ui/form.tsx
"use client" import * as React from "react" import * as LabelPrimitive from "@radix-ui/react-label" import { Slot } from "@radix-ui/react-slot" import { Controller, FormProvider, useFormContext, type ControllerProps, type FieldPath, type FieldValues, } from "react-hook-form" import { cn } from "@/lib/utils" import { Label } from "@/components/ui/label" const Form = FormProvider type FormFieldContextValue< TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues> > = { name: TName } const FormFieldContext = React.createContext<FormFieldContextValue>( {} as FormFieldContextValue ) const FormField = < TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues> >({ ...props }: ControllerProps<TFieldValues, TName>) => { return ( <FormFieldContext.Provider value={{ name: props.name }}> <Controller {...props} /> </FormFieldContext.Provider> ) } const useFormField = () => { const fieldContext = React.useContext(FormFieldContext) const itemContext = React.useContext(FormItemContext) const { getFieldState, formState } = useFormContext() const fieldState = getFieldState(fieldContext.name, formState) if (!fieldContext) { throw new Error("useFormField should be used within <FormField>") } const { id } = itemContext return { id, name: fieldContext.name, formItemId: `${id}-form-item`, formDescriptionId: `${id}-form-item-description`, formMessageId: `${id}-form-item-message`, ...fieldState, } } type FormItemContextValue = { id: string } const FormItemContext = React.createContext<FormItemContextValue>( {} as FormItemContextValue ) const FormItem = React.forwardRef< HTMLDivElement, React.HTMLAttributes<HTMLDivElement> >(({ className, ...props }, ref) => { const id = React.useId() return ( <FormItemContext.Provider value={{ id }}> <div ref={ref} className={cn("space-y-2", className)} {...props} /> </FormItemContext.Provider> ) }) FormItem.displayName = "FormItem" const FormLabel = React.forwardRef< React.ElementRef<typeof LabelPrimitive.Root>, React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> >(({ className, ...props }, ref) => { const { error, formItemId } = useFormField() return ( <Label ref={ref} className={cn(error && "text-destructive", className)} htmlFor={formItemId} {...props} /> ) }) FormLabel.displayName = "FormLabel" const FormControl = React.forwardRef< React.ElementRef<typeof Slot>, React.ComponentPropsWithoutRef<typeof Slot> >(({ ...props }, ref) => { const { error, formItemId, formDescriptionId, formMessageId } = useFormField() return ( <Slot ref={ref} id={formItemId} aria-describedby={ !error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}` } aria-invalid={!!error} {...props} /> ) }) FormControl.displayName = "FormControl" const FormDescription = React.forwardRef< HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement> >(({ className, ...props }, ref) => { const { formDescriptionId } = useFormField() return ( <p ref={ref} id={formDescriptionId} className={cn("text-sm text-muted-foreground", className)} {...props} /> ) }) FormDescription.displayName = "FormDescription" const FormMessage = React.forwardRef< HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement> >(({ className, children, ...props }, ref) => { const { error, formMessageId } = useFormField() const body = error ? String(error?.message ?? "") : children if (!body) { return null } return ( <p ref={ref} id={formMessageId} className={cn("text-sm font-medium text-destructive", className)} {...props} > {body} </p> ) }) FormMessage.displayName = "FormMessage" export { useFormField, Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField, }
src/components/ui/dropdown-menu.tsx
"use client" import * as React from "react" import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" import { Check, ChevronRight, Circle } from "lucide-react" import { cn } from "@/lib/utils" const DropdownMenu = DropdownMenuPrimitive.Root const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger const DropdownMenuGroup = DropdownMenuPrimitive.Group const DropdownMenuPortal = DropdownMenuPrimitive.Portal const DropdownMenuSub = DropdownMenuPrimitive.Sub const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup const DropdownMenuSubTrigger = React.forwardRef< React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>, React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & { inset?: boolean } >(({ className, inset, children, ...props }, ref) => ( <DropdownMenuPrimitive.SubTrigger ref={ref} className={cn( "flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", inset && "pl-8", className )} {...props} > {children} <ChevronRight className="ml-auto" /> </DropdownMenuPrimitive.SubTrigger> )) DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName const DropdownMenuSubContent = React.forwardRef< React.ElementRef<typeof DropdownMenuPrimitive.SubContent>, React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent> >(({ className, ...props }, ref) => ( <DropdownMenuPrimitive.SubContent ref={ref} className={cn( "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className )} {...props} /> )) DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName const DropdownMenuContent = React.forwardRef< React.ElementRef<typeof DropdownMenuPrimitive.Content>, React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> >(({ className, sideOffset = 4, ...props }, ref) => ( <DropdownMenuPrimitive.Portal> <DropdownMenuPrimitive.Content ref={ref} sideOffset={sideOffset} className={cn( "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className )} {...props} /> </DropdownMenuPrimitive.Portal> )) DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName const DropdownMenuItem = React.forwardRef< React.ElementRef<typeof DropdownMenuPrimitive.Item>, React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & { inset?: boolean } >(({ className, inset, ...props }, ref) => ( <DropdownMenuPrimitive.Item ref={ref} className={cn( "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", inset && "pl-8", className )} {...props} /> )) DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName const DropdownMenuCheckboxItem = React.forwardRef< React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>, React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem> >(({ className, children, checked, ...props }, ref) => ( <DropdownMenuPrimitive.CheckboxItem ref={ref} className={cn( "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className )} checked={checked} {...props} > <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <DropdownMenuPrimitive.ItemIndicator> <Check className="h-4 w-4" /> </DropdownMenuPrimitive.ItemIndicator> </span> {children} </DropdownMenuPrimitive.CheckboxItem> )) DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName const DropdownMenuRadioItem = React.forwardRef< React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>, React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem> >(({ className, children, ...props }, ref) => ( <DropdownMenuPrimitive.RadioItem ref={ref} className={cn( "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className )} {...props} > <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <DropdownMenuPrimitive.ItemIndicator> <Circle className="h-2 w-2 fill-current" /> </DropdownMenuPrimitive.ItemIndicator> </span> {children} </DropdownMenuPrimitive.RadioItem> )) DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName const DropdownMenuLabel = React.forwardRef< React.ElementRef<typeof DropdownMenuPrimitive.Label>, React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & { inset?: boolean } >(({ className, inset, ...props }, ref) => ( <DropdownMenuPrimitive.Label ref={ref} className={cn( "px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className )} {...props} /> )) DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName const DropdownMenuSeparator = React.forwardRef< React.ElementRef<typeof DropdownMenuPrimitive.Separator>, React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator> >(({ className, ...props }, ref) => ( <DropdownMenuPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} /> )) DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => { return ( <span className={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...props} /> ) } DropdownMenuShortcut.displayName = "DropdownMenuShortcut" export { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuGroup, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup, }
src/components/ui/dialog.tsx
"use client" import * as React from "react" import * as DialogPrimitive from "@radix-ui/react-dialog" import { X } from "lucide-react" import { cn } from "@/lib/utils" const Dialog = DialogPrimitive.Root const DialogTrigger = DialogPrimitive.Trigger const DialogPortal = DialogPrimitive.Portal const DialogClose = DialogPrimitive.Close const DialogOverlay = React.forwardRef< React.ElementRef<typeof DialogPrimitive.Overlay>, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> >(({ className, ...props }, ref) => ( <DialogPrimitive.Overlay ref={ref} className={cn( "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", className )} {...props} /> )) DialogOverlay.displayName = DialogPrimitive.Overlay.displayName const DialogContent = React.forwardRef< React.ElementRef<typeof DialogPrimitive.Content>, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> >(({ className, children, ...props }, ref) => ( <DialogPortal> <DialogOverlay /> <DialogPrimitive.Content ref={ref} className={cn( "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", className )} {...props} > {children} <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"> <X className="h-4 w-4" /> <span className="sr-only">Close</span> </DialogPrimitive.Close> </DialogPrimitive.Content> </DialogPortal> )) DialogContent.displayName = DialogPrimitive.Content.displayName const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => ( <div className={cn( "flex flex-col space-y-1.5 text-center sm:text-left", className )} {...props} /> ) DialogHeader.displayName = "DialogHeader" const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => ( <div className={cn( "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className )} {...props} /> ) DialogFooter.displayName = "DialogFooter" const DialogTitle = React.forwardRef< React.ElementRef<typeof DialogPrimitive.Title>, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title> >(({ className, ...props }, ref) => ( <DialogPrimitive.Title ref={ref} className={cn( "text-lg font-semibold leading-none tracking-tight", className )} {...props} /> )) DialogTitle.displayName = DialogPrimitive.Title.displayName const DialogDescription = React.forwardRef< React.ElementRef<typeof DialogPrimitive.Description>, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description> >(({ className, ...props }, ref) => ( <DialogPrimitive.Description ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} /> )) DialogDescription.displayName = DialogPrimitive.Description.displayName export { Dialog, DialogPortal, DialogOverlay, DialogClose, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription, }
src/components/ui/collapsible.tsx
"use client" import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" const Collapsible = CollapsiblePrimitive.Root const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent export { Collapsible, CollapsibleTrigger, CollapsibleContent }
src/components/ui/checkbox.tsx
"use client" import * as React from "react" import * as CheckboxPrimitive from "@radix-ui/react-checkbox" import { Check } from "lucide-react" import { cn } from "@/lib/utils" const Checkbox = React.forwardRef< React.ElementRef<typeof CheckboxPrimitive.Root>, React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> >(({ className, ...props }, ref) => ( <CheckboxPrimitive.Root ref={ref} className={cn( "peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground", className )} {...props} > <CheckboxPrimitive.Indicator className={cn("flex items-center justify-center text-current")} > <Check className="h-4 w-4" /> </CheckboxPrimitive.Indicator> </CheckboxPrimitive.Root> )) Checkbox.displayName = CheckboxPrimitive.Root.displayName export { Checkbox }
src/components/ui/chart.tsx
"use client" import * as React from "react" import * as RechartsPrimitive from "recharts" import { cn } from "@/lib/utils" // Format: { THEME_NAME: CSS_SELECTOR } const THEMES = { light: "", dark: ".dark" } as const export type ChartConfig = { [k in string]: { label?: React.ReactNode icon?: React.ComponentType } & ( | { color?: string; theme?: never } | { color?: never; theme: Record<keyof typeof THEMES, string> } ) } type ChartContextProps = { config: ChartConfig } const ChartContext = React.createContext<ChartContextProps | null>(null) function useChart() { const context = React.useContext(ChartContext) if (!context) { throw new Error("useChart must be used within a <ChartContainer />") } return context } const ChartContainer = React.forwardRef< HTMLDivElement, React.ComponentProps<"div"> & { config: ChartConfig children: React.ComponentProps< typeof RechartsPrimitive.ResponsiveContainer >["children"] } >(({ id, className, children, config, ...props }, ref) => { const uniqueId = React.useId() const chartId = `chart-${id || uniqueId.replace(/:/g, "")}` return ( <ChartContext.Provider value={{ config }}> <div data-chart={chartId} ref={ref} className={cn( "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none", className )} {...props} > <ChartStyle id={chartId} config={config} /> <RechartsPrimitive.ResponsiveContainer> {children} </RechartsPrimitive.ResponsiveContainer> </div> </ChartContext.Provider> ) }) ChartContainer.displayName = "Chart" const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { const colorConfig = Object.entries(config).filter( ([, config]) => config.theme || config.color ) if (!colorConfig.length) { return null } return ( <style dangerouslySetInnerHTML={{ __html: Object.entries(THEMES) .map( ([theme, prefix]) => ` ${prefix} [data-chart=${id}] { ${colorConfig .map(([key, itemConfig]) => { const color = itemConfig.theme?.[theme as keyof typeof itemConfig.theme] || itemConfig.color return color ? ` --color-${key}: ${color};` : null }) .join("\n")} } ` ) .join("\n"), }} /> ) } const ChartTooltip = RechartsPrimitive.Tooltip const ChartTooltipContent = React.forwardRef< HTMLDivElement, React.ComponentProps<typeof RechartsPrimitive.Tooltip> & React.ComponentProps<"div"> & { hideLabel?: boolean hideIndicator?: boolean indicator?: "line" | "dot" | "dashed" nameKey?: string labelKey?: string } >( ( { active, payload, className, indicator = "dot", hideLabel = false, hideIndicator = false, label, labelFormatter, labelClassName, formatter, color, nameKey, labelKey, }, ref ) => { const { config } = useChart() const tooltipLabel = React.useMemo(() => { if (hideLabel || !payload?.length) { return null } const [item] = payload const key = `${labelKey || item.dataKey || item.name || "value"}` const itemConfig = getPayloadConfigFromPayload(config, item, key) const value = !labelKey && typeof label === "string" ? config[label as keyof typeof config]?.label || label : itemConfig?.label if (labelFormatter) { return ( <div className={cn("font-medium", labelClassName)}> {labelFormatter(value, payload)} </div> ) } if (!value) { return null } return <div className={cn("font-medium", labelClassName)}>{value}</div> }, [ label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey, ]) if (!active || !payload?.length) { return null } const nestLabel = payload.length === 1 && indicator !== "dot" return ( <div ref={ref} className={cn( "grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl", className )} > {!nestLabel ? tooltipLabel : null} <div className="grid gap-1.5"> {payload.map((item, index) => { const key = `${nameKey || item.name || item.dataKey || "value"}` const itemConfig = getPayloadConfigFromPayload(config, item, key) const indicatorColor = color || item.payload.fill || item.color return ( <div key={item.dataKey} className={cn( "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground", indicator === "dot" && "items-center" )} > {formatter && item?.value !== undefined && item.name ? ( formatter(item.value, item.name, item, index, item.payload) ) : ( <> {itemConfig?.icon ? ( <itemConfig.icon /> ) : ( !hideIndicator && ( <div className={cn( "shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]", { "h-2.5 w-2.5": indicator === "dot", "w-1": indicator === "line", "w-0 border-[1.5px] border-dashed bg-transparent": indicator === "dashed", "my-0.5": nestLabel && indicator === "dashed", } )} style={ { "--color-bg": indicatorColor, "--color-border": indicatorColor, } as React.CSSProperties } /> ) )} <div className={cn( "flex flex-1 justify-between leading-none", nestLabel ? "items-end" : "items-center" )} > <div className="grid gap-1.5"> {nestLabel ? tooltipLabel : null} <span className="text-muted-foreground"> {itemConfig?.label || item.name} </span> </div> {item.value && ( <span className="font-mono font-medium tabular-nums text-foreground"> {item.value.toLocaleString()} </span> )} </div> </> )} </div> ) })} </div> </div> ) } ) ChartTooltipContent.displayName = "ChartTooltip" const ChartLegend = RechartsPrimitive.Legend const ChartLegendContent = React.forwardRef< HTMLDivElement, React.ComponentProps<"div"> & Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & { hideIcon?: boolean nameKey?: string } >( ( { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey }, ref ) => { const { config } = useChart() if (!payload?.length) { return null } return ( <div ref={ref} className={cn( "flex items-center justify-center gap-4", verticalAlign === "top" ? "pb-3" : "pt-3", className )} > {payload.map((item) => { const key = `${nameKey || item.dataKey || "value"}` const itemConfig = getPayloadConfigFromPayload(config, item, key) return ( <div key={item.value} className={cn( "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground" )} > {itemConfig?.icon && !hideIcon ? ( <itemConfig.icon /> ) : ( <div className="h-2 w-2 shrink-0 rounded-[2px]" style={{ backgroundColor: item.color, }} /> )} {itemConfig?.label} </div> ) })} </div> ) } ) ChartLegendContent.displayName = "ChartLegend" // Helper to extract item config from a payload. function getPayloadConfigFromPayload( config: ChartConfig, payload: unknown, key: string ) { if (typeof payload !== "object" || payload === null) { return undefined } const payloadPayload = "payload" in payload && typeof payload.payload === "object" && payload.payload !== null ? payload.payload : undefined let configLabelKey: string = key if ( key in payload && typeof payload[key as keyof typeof payload] === "string" ) { configLabelKey = payload[key as keyof typeof payload] as string } else if ( payloadPayload && key in payloadPayload && typeof payloadPayload[key as keyof typeof payloadPayload] === "string" ) { configLabelKey = payloadPayload[ key as keyof typeof payloadPayload ] as string } return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config] } export { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent, ChartStyle, }
src/components/ui/carousel.tsx
"use client" import * as React from "react" import useEmblaCarousel, { type UseEmblaCarouselType, } from "embla-carousel-react" import { ArrowLeft, ArrowRight } from "lucide-react" import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" type CarouselApi = UseEmblaCarouselType[1] type UseCarouselParameters = Parameters<typeof useEmblaCarousel> type CarouselOptions = UseCarouselParameters[0] type CarouselPlugin = UseCarouselParameters[1] type CarouselProps = { opts?: CarouselOptions plugins?: CarouselPlugin orientation?: "horizontal" | "vertical" setApi?: (api: CarouselApi) => void } type CarouselContextProps = { carouselRef: ReturnType<typeof useEmblaCarousel>[0] api: ReturnType<typeof useEmblaCarousel>[1] scrollPrev: () => void scrollNext: () => void canScrollPrev: boolean canScrollNext: boolean } & CarouselProps const CarouselContext = React.createContext<CarouselContextProps | null>(null) function useCarousel() { const context = React.useContext(CarouselContext) if (!context) { throw new Error("useCarousel must be used within a <Carousel />") } return context } const Carousel = React.forwardRef< HTMLDivElement, React.HTMLAttributes<HTMLDivElement> & CarouselProps >( ( { orientation = "horizontal", opts, setApi, plugins, className, children, ...props }, ref ) => { const [carouselRef, api] = useEmblaCarousel( { ...opts, axis: orientation === "horizontal" ? "x" : "y", }, plugins ) const [canScrollPrev, setCanScrollPrev] = React.useState(false) const [canScrollNext, setCanScrollNext] = React.useState(false) const onSelect = React.useCallback((api: CarouselApi) => { if (!api) { return } setCanScrollPrev(api.canScrollPrev()) setCanScrollNext(api.canScrollNext()) }, []) const scrollPrev = React.useCallback(() => { api?.scrollPrev() }, [api]) const scrollNext = React.useCallback(() => { api?.scrollNext() }, [api]) const handleKeyDown = React.useCallback( (event: React.KeyboardEvent<HTMLDivElement>) => { if (event.key === "ArrowLeft") { event.preventDefault() scrollPrev() } else if (event.key === "ArrowRight") { event.preventDefault() scrollNext() } }, [scrollPrev, scrollNext] ) React.useEffect(() => { if (!api || !setApi) { return } setApi(api) }, [api, setApi]) React.useEffect(() => { if (!api) { return } onSelect(api) api.on("reInit", onSelect) api.on("select", onSelect) return () => { api?.off("select", onSelect) } }, [api, onSelect]) return ( <CarouselContext.Provider value={{ carouselRef, api: api, opts, orientation: orientation || (opts?.axis === "y" ? "vertical" : "horizontal"), scrollPrev, scrollNext, canScrollPrev, canScrollNext, }} > <div ref={ref} onKeyDownCapture={handleKeyDown} className={cn("relative", className)} role="region" aria-roledescription="carousel" {...props} > {children} </div> </CarouselContext.Provider> ) } ) Carousel.displayName = "Carousel" const CarouselContent = React.forwardRef< HTMLDivElement, React.HTMLAttributes<HTMLDivElement> >(({ className, ...props }, ref) => { const { carouselRef, orientation } = useCarousel() return ( <div ref={carouselRef} className="overflow-hidden"> <div ref={ref} className={cn( "flex", orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col", className )} {...props} /> </div> ) }) CarouselContent.displayName = "CarouselContent" const CarouselItem = React.forwardRef< HTMLDivElement, React.HTMLAttributes<HTMLDivElement> >(({ className, ...props }, ref) => { const { orientation } = useCarousel() return ( <div ref={ref} role="group" aria-roledescription="slide" className={cn( "min-w-0 shrink-0 grow-0 basis-full", orientation === "horizontal" ? "pl-4" : "pt-4", className )} {...props} /> ) }) CarouselItem.displayName = "CarouselItem" const CarouselPrevious = React.forwardRef< HTMLButtonElement, React.ComponentProps<typeof Button> >(({ className, variant = "outline", size = "icon", ...props }, ref) => { const { orientation, scrollPrev, canScrollPrev } = useCarousel() return ( <Button ref={ref} variant={variant} size={size} className={cn( "absolute h-8 w-8 rounded-full", orientation === "horizontal" ? "-left-12 top-1/2 -translate-y-1/2" : "-top-12 left-1/2 -translate-x-1/2 rotate-90", className )} disabled={!canScrollPrev} onClick={scrollPrev} {...props} > <ArrowLeft className="h-4 w-4" /> <span className="sr-only">Previous slide</span> </Button> ) }) CarouselPrevious.displayName = "CarouselPrevious" const CarouselNext = React.forwardRef< HTMLButtonElement, React.ComponentProps<typeof Button> >(({ className, variant = "outline", size = "icon", ...props }, ref) => { const { orientation, scrollNext, canScrollNext } = useCarousel() return ( <Button ref={ref} variant={variant} size={size} className={cn( "absolute h-8 w-8 rounded-full", orientation === "horizontal" ? "-right-12 top-1/2 -translate-y-1/2" : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90", className )} disabled={!canScrollNext} onClick={scrollNext} {...props} > <ArrowRight className="h-4 w-4" /> <span className="sr-only">Next slide</span> </Button> ) }) CarouselNext.displayName = "CarouselNext" export { type CarouselApi, Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext, }
src/components/ui/card.tsx
import * as React from "react" import { cn } from "@/lib/utils" const Card = React.forwardRef< HTMLDivElement, React.HTMLAttributes<HTMLDivElement> >(({ className, ...props }, ref) => ( <div ref={ref} className={cn( "rounded-lg border bg-card text-card-foreground shadow-sm", className )} {...props} /> )) Card.displayName = "Card" const CardHeader = React.forwardRef< HTMLDivElement, React.HTMLAttributes<HTMLDivElement> >(({ className, ...props }, ref) => ( <div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} /> )) CardHeader.displayName = "CardHeader" const CardTitle = React.forwardRef< HTMLDivElement, React.HTMLAttributes<HTMLDivElement> >(({ className, ...props }, ref) => ( <div ref={ref} className={cn( "text-2xl font-semibold leading-none tracking-tight", className )} {...props} /> )) CardTitle.displayName = "CardTitle" const CardDescription = React.forwardRef< HTMLDivElement, React.HTMLAttributes<HTMLDivElement> >(({ className, ...props }, ref) => ( <div ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} /> )) CardDescription.displayName = "CardDescription" const CardContent = React.forwardRef< HTMLDivElement, React.HTMLAttributes<HTMLDivElement> >(({ className, ...props }, ref) => ( <div ref={ref} className={cn("p-6 pt-0", className)} {...props} /> )) CardContent.displayName = "CardContent" const CardFooter = React.forwardRef< HTMLDivElement, React.HTMLAttributes<HTMLDivElement> >(({ className, ...props }, ref) => ( <div ref={ref} className={cn("flex items-center p-6 pt-0", className)} {...props} /> )) CardFooter.displayName = "CardFooter" export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
src/components/ui/calendar.tsx
"use client" import * as React from "react" import { ChevronLeft, ChevronRight } from "lucide-react" import { DayPicker } from "react-day-picker" import { cn } from "@/lib/utils" import { buttonVariants } from "@/components/ui/button" export type CalendarProps = React.ComponentProps<typeof DayPicker> function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) { return ( <DayPicker showOutsideDays={showOutsideDays} className={cn("p-3", className)} classNames={{ months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0", month: "space-y-4", caption: "flex justify-center pt-1 relative items-center", caption_label: "text-sm font-medium", nav: "space-x-1 flex items-center", nav_button: cn( buttonVariants({ variant: "outline" }), "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100" ), nav_button_previous: "absolute left-1", nav_button_next: "absolute right-1", table: "w-full border-collapse space-y-1", head_row: "flex", head_cell: "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]", row: "flex w-full mt-2", cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20", day: cn( buttonVariants({ variant: "ghost" }), "h-9 w-9 p-0 font-normal aria-selected:opacity-100" ), day_range_end: "day-range-end", day_selected: "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground", day_today: "bg-accent text-accent-foreground", day_outside: "day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground", day_disabled: "text-muted-foreground opacity-50", day_range_middle: "aria-selected:bg-accent aria-selected:text-accent-foreground", day_hidden: "invisible", ...classNames, }} components={{ IconLeft: ({ className, ...props }) => ( <ChevronLeft className={cn("h-4 w-4", className)} {...props} /> ), IconRight: ({ className, ...props }) => ( <ChevronRight className={cn("h-4 w-4", className)} {...props} /> ), }} {...props} /> ) } Calendar.displayName = "Calendar" export { Calendar }
src/components/ui/button.tsx
import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-10 px-4 py-2", sm: "h-9 rounded-md px-3", lg: "h-11 rounded-md px-8", icon: "h-10 w-10", }, }, defaultVariants: { variant: "default", size: "default", }, } ) export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> { asChild?: boolean } const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( ({ className, variant, size, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : "button" return ( <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} /> ) } ) Button.displayName = "Button" export { Button, buttonVariants }
src/components/ui/badge.tsx
import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const badgeVariants = cva( "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", { variants: { variant: { default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", outline: "text-foreground", }, }, defaultVariants: { variant: "default", }, } ) export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {} function Badge({ className, variant, ...props }: BadgeProps) { return ( <div className={cn(badgeVariants({ variant }), className)} {...props} /> ) } export { Badge, badgeVariants }
src/components/ui/avatar.tsx
"use client" import * as React from "react" import * as AvatarPrimitive from "@radix-ui/react-avatar" import { cn } from "@/lib/utils" const Avatar = React.forwardRef< React.ElementRef<typeof AvatarPrimitive.Root>, React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> >(({ className, ...props }, ref) => ( <AvatarPrimitive.Root ref={ref} className={cn( "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className )} {...props} /> )) Avatar.displayName = AvatarPrimitive.Root.displayName const AvatarImage = React.forwardRef< React.ElementRef<typeof AvatarPrimitive.Image>, React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image> >(({ className, ...props }, ref) => ( <AvatarPrimitive.Image ref={ref} className={cn("aspect-square h-full w-full", className)} {...props} /> )) AvatarImage.displayName = AvatarPrimitive.Image.displayName const AvatarFallback = React.forwardRef< React.ElementRef<typeof AvatarPrimitive.Fallback>, React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback> >(({ className, ...props }, ref) => ( <AvatarPrimitive.Fallback ref={ref} className={cn( "flex h-full w-full items-center justify-center rounded-full bg-muted", className )} {...props} /> )) AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName export { Avatar, AvatarImage, AvatarFallback }
src/components/ui/alert.tsx
import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const alertVariants = cva( "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", { variants: { variant: { default: "bg-background text-foreground", destructive: "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", }, }, defaultVariants: { variant: "default", }, } ) const Alert = React.forwardRef< HTMLDivElement, React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants> >(({ className, variant, ...props }, ref) => ( <div ref={ref} role="alert" className={cn(alertVariants({ variant }), className)} {...props} /> )) Alert.displayName = "Alert" const AlertTitle = React.forwardRef< HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement> >(({ className, ...props }, ref) => ( <h5 ref={ref} className={cn("mb-1 font-medium leading-none tracking-tight", className)} {...props} /> )) AlertTitle.displayName = "AlertTitle" const AlertDescription = React.forwardRef< HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement> >(({ className, ...props }, ref) => ( <div ref={ref} className={cn("text-sm [&_p]:leading-relaxed", className)} {...props} /> )) AlertDescription.displayName = "AlertDescription" export { Alert, AlertTitle, AlertDescription }
src/components/ui/alert-dialog.tsx
"use client" import * as React from "react" import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" import { cn } from "@/lib/utils" import { buttonVariants } from "@/components/ui/button" const AlertDialog = AlertDialogPrimitive.Root const AlertDialogTrigger = AlertDialogPrimitive.Trigger const AlertDialogPortal = AlertDialogPrimitive.Portal const AlertDialogOverlay = React.forwardRef< React.ElementRef<typeof AlertDialogPrimitive.Overlay>, React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay> >(({ className, ...props }, ref) => ( <AlertDialogPrimitive.Overlay className={cn( "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", className )} {...props} ref={ref} /> )) AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName const AlertDialogContent = React.forwardRef< React.ElementRef<typeof AlertDialogPrimitive.Content>, React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content> >(({ className, ...props }, ref) => ( <AlertDialogPortal> <AlertDialogOverlay /> <AlertDialogPrimitive.Content ref={ref} className={cn( "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", className )} {...props} /> </AlertDialogPortal> )) AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => ( <div className={cn( "flex flex-col space-y-2 text-center sm:text-left", className )} {...props} /> ) AlertDialogHeader.displayName = "AlertDialogHeader" const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => ( <div className={cn( "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className )} {...props} /> ) AlertDialogFooter.displayName = "AlertDialogFooter" const AlertDialogTitle = React.forwardRef< React.ElementRef<typeof AlertDialogPrimitive.Title>, React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title> >(({ className, ...props }, ref) => ( <AlertDialogPrimitive.Title ref={ref} className={cn("text-lg font-semibold", className)} {...props} /> )) AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName const AlertDialogDescription = React.forwardRef< React.ElementRef<typeof AlertDialogPrimitive.Description>, React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description> >(({ className, ...props }, ref) => ( <AlertDialogPrimitive.Description ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} /> )) AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName const AlertDialogAction = React.forwardRef< React.ElementRef<typeof AlertDialogPrimitive.Action>, React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action> >(({ className, ...props }, ref) => ( <AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} /> )) AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName const AlertDialogCancel = React.forwardRef< React.ElementRef<typeof AlertDialogPrimitive.Cancel>, React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel> >(({ className, ...props }, ref) => ( <AlertDialogPrimitive.Cancel ref={ref} className={cn( buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className )} {...props} /> )) AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName export { AlertDialog, AlertDialogPortal, AlertDialogOverlay, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogFooter, AlertDialogTitle, AlertDialogDescription, AlertDialogAction, AlertDialogCancel, }
src/components/ui/accordion.tsx
"use client" import * as React from "react" import * as AccordionPrimitive from "@radix-ui/react-accordion" import { ChevronDown } from "lucide-react" import { cn } from "@/lib/utils" const Accordion = AccordionPrimitive.Root const AccordionItem = React.forwardRef< React.ElementRef<typeof AccordionPrimitive.Item>, React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item> >(({ className, ...props }, ref) => ( <AccordionPrimitive.Item ref={ref} className={cn("border-b", className)} {...props} /> )) AccordionItem.displayName = "AccordionItem" const AccordionTrigger = React.forwardRef< React.ElementRef<typeof AccordionPrimitive.Trigger>, React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger> >(({ className, children, ...props }, ref) => ( <AccordionPrimitive.Header className="flex"> <AccordionPrimitive.Trigger ref={ref} className={cn( "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180", className )} {...props} > {children} <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" /> </AccordionPrimitive.Trigger> </AccordionPrimitive.Header> )) AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName const AccordionContent = React.forwardRef< React.ElementRef<typeof AccordionPrimitive.Content>, React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content> >(({ className, children, ...props }, ref) => ( <AccordionPrimitive.Content ref={ref} className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down" {...props} > <div className={cn("pb-4 pt-0", className)}>{children}</div> </AccordionPrimitive.Content> )) AccordionContent.displayName = AccordionPrimitive.Content.displayName export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
src/app/dashboard/wallet/page.tsx
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; import { Badge } from '@/components/ui/badge'; import { Send, ArrowDownLeft, RefreshCw, TrendingUp, TrendingDown } from 'lucide-react'; import { walletTransactions } from '@/lib/mock-data'; import { cn } from '@/lib/utils'; export default function WalletPage() { const walletBalance = 2450.00; return ( <div className="space-y-6"> <h1 className="font-headline text-3xl font-bold">Wallet</h1> <div className="grid grid-cols-1 gap-6 lg:grid-cols-3"> <div className="lg:col-span-1 space-y-6"> <Card className="shadow-lg"> <CardHeader> <CardTitle>Total Balance</CardTitle> <CardDescription>Your available points for predictions.</CardDescription> </CardHeader> <CardContent> <p className="font-headline text-5xl font-bold text-primary">€{walletBalance.toFixed(2)}</p> <p className="text-sm text-muted-foreground mt-1">≈ 0.0021 BTC</p> </CardContent> </Card> <Card> <CardHeader> <CardTitle>Actions</CardTitle> </CardHeader> <CardContent className="grid grid-cols-3 gap-2"> <Button><Send className="mr-2 h-4 w-4" /> Send</Button> <Button><ArrowDownLeft className="mr-2 h-4 w-4" /> Receive</Button> <Button><RefreshCw className="mr-2 h-4 w-4" /> Swap</Button> </CardContent> </Card> </div> <div className="lg:col-span-2"> <Card className="shadow-lg h-full"> <CardHeader> <CardTitle>Recent Transactions</CardTitle> <CardDescription>A log of your recent wallet activity.</CardDescription> </CardHeader> <CardContent> <Table> <TableHeader> <TableRow> <TableHead className="w-[100px]">Type</TableHead> <TableHead>Description</TableHead> <TableHead>Date</TableHead> <TableHead className="text-right">Amount</TableHead> </TableRow> </TableHeader> <TableBody> {walletTransactions.map((tx) => ( <TableRow key={tx.id}> <TableCell> <Badge variant={tx.type === 'credit' ? 'default' : 'destructive'} className={cn(tx.type === 'credit' && 'bg-accent text-accent-foreground')}> {tx.type === 'credit' ? <TrendingUp className="mr-2 h-4 w-4" /> : <TrendingDown className="mr-2 h-4 w-4" />} {tx.type} </Badge> </TableCell> <TableCell className="font-medium">{tx.description}</TableCell> <TableCell className="text-muted-foreground">{tx.date}</TableCell> <TableCell className={cn( "text-right font-semibold", tx.type === 'credit' ? 'text-green-600' : 'text-red-600' )}>{tx.amount}</TableCell> </TableRow> ))} </TableBody> </Table> </CardContent> </Card> </div> </div> </div> ); }
src/app/dashboard/settings/page.tsx
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Separator } from '@/components/ui/separator'; import { Switch } from '@/components/ui/switch'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Bell, Palette, Lock, User, Save } from 'lucide-react'; import { Label } from '@/components/ui/label'; export default function SettingsPage() { return ( <div className="space-y-6"> <h1 className="font-headline text-3xl font-bold">Settings</h1> <Card> <CardHeader> <CardTitle className="flex items-center gap-2"><User className="h-5 w-5" /> Account</CardTitle> <CardDescription>Manage your account settings and preferences.</CardDescription> </CardHeader> <CardContent className="space-y-4"> <div> <Label htmlFor="email">Email</Label> <p className="text-sm text-muted-foreground">alex.johnson@example.com</p> </div> <div> <Label htmlFor="plan">Subscription Plan</Label> <p className="font-semibold">Pro Analyst</p> <Button variant="link" className="p-0 h-auto">Upgrade Plan</Button> </div> </CardContent> </Card> <Card> <CardHeader> <CardTitle className="flex items-center gap-2"><Bell className="h-5 w-5" /> Notifications</CardTitle> <CardDescription>Choose how you want to be notified.</CardDescription> </CardHeader> <CardContent className="space-y-4"> <div className="flex items-center justify-between"> <Label htmlFor="email-notifications" className="flex flex-col gap-1"> <span>Email Notifications</span> <span className="font-normal text-muted-foreground text-sm">Receive emails about your account, predictions, and news.</span> </Label> <Switch id="email-notifications" defaultChecked /> </div> <Separator /> <div className="flex items-center justify-between"> <Label htmlFor="push-notifications" className="flex flex-col gap-1"> <span>Push Notifications</span> <span className="font-normal text-muted-foreground text-sm">Get push notifications on your devices for real-time updates.</span> </Label> <Switch id="push-notifications" /> </div> </CardContent> </Card> <Card> <CardHeader> <CardTitle className="flex items-center gap-2"><Palette className="h-5 w-5" /> Appearance</CardTitle> <CardDescription>Customize the look and feel of the application.</CardDescription> </CardHeader> <CardContent className="space-y-4"> <div className="flex items-center justify-between"> <Label htmlFor="theme">Theme</Label> <Select defaultValue="system"> <SelectTrigger className="w-[180px]"> <SelectValue placeholder="Select theme" /> </SelectTrigger> <SelectContent> <SelectItem value="light">Light</SelectItem> <SelectItem value="dark">Dark</SelectItem> <SelectItem value="system">System</SelectItem> </SelectContent> </Select> </div> </CardContent> </Card> <Card> <CardHeader> <CardTitle className="flex items-center gap-2"><Lock className="h-5 w-5" /> Security</CardTitle> <CardDescription>Manage your password and security settings.</CardDescription> </CardHeader> <CardContent className="space-y-4"> <Button>Change Password</Button> <div className="flex items-center justify-between rounded-lg border p-3"> <div className="space-y-0.5"> <Label className="text-base">Two-Factor Authentication</Label> <p className="text-sm text-muted-foreground">Add an extra layer of security to your account.</p> </div> <Button variant="outline">Enable</Button> </div> </CardContent> </Card> <div className="flex justify-end"> <Button><Save className="mr-2 h-4 w-4" /> Save Changes</Button> </div> </div> ); }
src/app/dashboard/profile/page.tsx
import Link from 'next/link'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import StatCard from '@/components/dashboard/stat-card'; import { Bell, ChevronRight, Globe, LogOut, Moon, Shield, User, BarChart, Target, Users, Gem } from 'lucide-react'; import { PlaceHolderImages } from '@/lib/placeholder-images'; const profileOptions = [ { icon: User, text: 'Edit Profile', href: '#' }, { icon: Bell, text: 'Notifications', href: '#' }, { icon: Shield, text: 'Privacy & Security', href: '#' }, ]; const preferenceOptions = [ { icon: Globe, text: 'Language', href: '#' }, { icon: Moon, text: 'Dark Mode', href: '#' }, ]; export default function ProfilePage() { const userAvatar = PlaceHolderImages.find((img) => img.id === 'user-avatar'); return ( <div className="space-y-6"> <Card className="shadow-lg overflow-hidden"> <div className="bg-primary/10 h-24" /> <CardContent className="p-6 text-center -mt-16"> <Avatar className="h-24 w-24 mx-auto border-4 border-card shadow-md"> <AvatarImage src={userAvatar?.imageUrl} alt="User Avatar" data-ai-hint={userAvatar?.imageHint} /> <AvatarFallback>AJ</AvatarFallback> </Avatar> <h2 className="font-headline text-2xl font-bold mt-4">Alex Johnson</h2> <p className="text-muted-foreground">alex.johnson@example.com</p> <p className="mt-1 font-semibold text-primary">Pro Analyst</p> </CardContent> </Card> <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4"> <StatCard title="Predictions" value="847" icon={BarChart} /> <StatCard title="Accuracy" value="83%" icon={Target} /> <StatCard title="Following" value="24" icon={Users} /> <StatCard title="Points" value="2,450" icon={Gem} /> </div> <div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <Card> <CardHeader> <CardTitle>Account Settings</CardTitle> </CardHeader> <CardContent className="space-y-2"> {profileOptions.map((item) => ( <Button variant="ghost" className="w-full justify-between" asChild key={item.text}> <Link href={item.href}> <div className="flex items-center gap-3"> <item.icon className="h-4 w-4 text-muted-foreground" /> <span>{item.text}</span> </div> <ChevronRight className="h-4 w-4 text-muted-foreground" /> </Link> </Button> ))} </CardContent> </Card> <Card> <CardHeader> <CardTitle>Preferences</CardTitle> </CardHeader> <CardContent className="space-y-2"> {preferenceOptions.map((item) => ( <Button variant="ghost" className="w-full justify-between" asChild key={item.text}> <Link href={item.href}> <div className="flex items-center gap-3"> <item.icon className="h-4 w-4 text-muted-foreground" /> <span>{item.text}</span> </div> <ChevronRight className="h-4 w-4 text-muted-foreground" /> </Link> </Button> ))} </CardContent> </Card> </div> <div className="text-center"> <Button variant="destructive" asChild> <Link href="/"> <LogOut className="mr-2 h-4 w-4" /> Logout </Link> </Button> </div> </div> ); }
src/app/dashboard/predictions/page.tsx
'use client'; import { useState } from 'react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Lightbulb, LoaderCircle, CheckCircle, AlertTriangle } from 'lucide-react'; import { aiMatchPredictions } from '@/ai/ai-match-predictions'; import type { AiMatchPredictionsOutput } from '@/ai/ai-match-predictions'; type PredictionState = 'idle' | 'loading' | 'success' | 'error'; export default function PredictionsPage() { const [team1, setTeam1] = useState('Manchester City'); const [team2, setTeam2] = useState('Liverpool'); const [historicalData, setHistoricalData] = useState('Team 1 won 3 of last 5 matchups. Team 2 has a strong home record.'); const [currentForm, setCurrentForm] = useState('Team 1 is on a 5-game winning streak. Team 2 has several key players injured.'); const [prediction, setPrediction] = useState<AiMatchPredictionsOutput | null>(null); const [predictionState, setPredictionState] = useState<PredictionState>('idle'); const handlePredict = async () => { setPredictionState('loading'); setPrediction(null); try { const result = await aiMatchPredictions({ team1, team2, historicalData, currentForm, }); setPrediction(result); setPredictionState('success'); } catch (error) { console.error('Prediction failed:', error); setPredictionState('error'); } }; return ( <div className="space-y-6"> <h1 className="font-headline text-3xl font-bold">AI Match Predictions</h1> <Card className="shadow-lg"> <CardHeader> <CardTitle>Generate a New Prediction</CardTitle> <CardDescription> Enter the details of the match you want to predict. The more context you provide, the better the AI prediction will be. </CardDescription> </CardHeader> <CardContent className="space-y-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="space-y-2"> <Label htmlFor="team1">Team 1</Label> <Input id="team1" placeholder="e.g., Manchester City" value={team1} onChange={(e) => setTeam1(e.target.value)} /> </div> <div className="space-y-2"> <Label htmlFor="team2">Team 2</Label> <Input id="team2" placeholder="e.g., Liverpool" value={team2} onChange={(e) => setTeam2(e.target.value)} /> </div> </div> <div className="space-y-2"> <Label htmlFor="historicalData">Historical Context</Label> <Textarea id="historicalData" placeholder="E.g., Head-to-head record, previous encounters, rivalry history..." value={historicalData} onChange={(e) => setHistoricalData(e.target.value)} /> </div> <div className="space-y-2"> <Label htmlFor="currentForm">Current Form & News</Label> <Textarea id="currentForm" placeholder="E.g., Recent match results, player injuries, team morale..." value={currentForm} onChange={(e) => setCurrentForm(e.target.value)} /> </div> </CardContent> <CardFooter> <Button onClick={handlePredict} disabled={predictionState === 'loading' || !team1 || !team2}> {predictionState === 'loading' ? ( <> <LoaderCircle className="mr-2 h-4 w-4 animate-spin" /> Analyzing... </> ) : ( 'Predict Outcome' )} </Button> </CardFooter> </Card> {predictionState !== 'idle' && ( <Card> <CardHeader> <CardTitle>Prediction Result</CardTitle> </CardHeader> <CardContent> {predictionState === 'loading' && ( <div className="flex items-center justify-center p-12"> <LoaderCircle className="h-12 w-12 animate-spin text-primary" /> </div> )} {predictionState === 'error' && ( <div className="flex flex-col items-center justify-center p-12 text-center text-destructive"> <AlertTriangle className="h-12 w-12 mb-4" /> <h3 className="text-xl font-semibold">Prediction Failed</h3> <p>The AI model could not generate a prediction. Please try again later.</p> </div> )} {predictionState === 'success' && prediction && ( <div className="space-y-4"> <div className="text-center"> <p className="text-muted-foreground">Predicted Winner</p> <h2 className="text-3xl font-bold text-primary">{prediction.predictedWinner}</h2> </div> <div className="flex justify-around items-center"> <div className="text-center"> <p className="text-muted-foreground">Confidence</p> <p className="text-2xl font-semibold">{Math.round(prediction.confidenceLevel * 100)}%</p> </div> </div> <div> <h4 className="font-semibold flex items-center gap-2 mb-2"><Lightbulb className="h-5 w-5 text-primary/80"/> AI Reasoning</h4> <p className="text-muted-foreground bg-secondary/50 p-4 rounded-md border">{prediction.reasoning}</p> </div> </div> )} </CardContent> </Card> )} </div> ); }
src/app/dashboard/discover/page.tsx
'use client'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Input } from '@/components/ui/input'; import { Search } from 'lucide-react'; import PlayerCard from '@/components/dashboard/player-card'; import MatchCard from '@/components/dashboard/match-card'; import { featuredPlayers, liveMatches, upcomingMatches, newsItems } from '@/lib/mock-data'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import Image from 'next/image'; import Link from 'next/link'; import { Button } from '@/components/ui/button'; function TeamCard({ team }: { team: { name: string; logo: any, league: string } }) { return ( <Card className="hover:shadow-lg transition-shadow"> <CardContent className="p-4 flex flex-col items-center text-center"> {team.logo && <Image src={team.logo.imageUrl} alt={`${team.name} logo`} width={64} height={64} className="rounded-full mb-4" data-ai-hint={team.logo.imageHint} />} <p className="font-bold">{team.name}</p> <p className="text-sm text-muted-foreground">{team.league}</p> </CardContent> </Card> ) } function NewsCard({ item }: { item: any }) { return ( <Card className="flex flex-col overflow-hidden hover:shadow-lg transition-shadow"> {item.image && <Image src={item.image.imageUrl} alt={item.title} width={400} height={200} className="w-full h-32 object-cover" data-ai-hint={item.image.imageHint} />} <CardHeader> <CardTitle className="text-lg">{item.title}</CardTitle> </CardHeader> <CardContent className="flex-grow"> <p className="text-sm text-muted-foreground line-clamp-3">{item.excerpt}</p> </CardContent> <CardContent> <Button variant="link" asChild className="p-0"> <Link href="#">Read More</Link> </Button> </CardContent> </Card> ) } export default function DiscoverPage() { const teams = [ { name: 'Man City', logo: liveMatches[0].homeLogo, league: 'Premier League'}, { name: 'Liverpool', logo: liveMatches[0].awayLogo, league: 'Premier League'}, { name: 'Arsenal', logo: liveMatches[1].homeLogo, league: 'Premier League'}, { name: 'Chelsea', logo: liveMatches[1].awayLogo, league: 'Premier League'}, { name: 'Real Madrid', logo: { imageUrl: 'https://picsum.photos/seed/rm/100/100', imageHint: 'team logo'}, league: 'La Liga'}, { name: 'Barcelona', logo: { imageUrl: 'https://picsum.photos/seed/bar/100/100', imageHint: 'team logo'}, league: 'La Liga'}, ] return ( <div className="space-y-6"> <h1 className="font-headline text-3xl font-bold">Discover</h1> <div className="relative"> <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" /> <Input placeholder="Search for anything..." className="pl-9" /> </div> <Tabs defaultValue="players" className="w-full"> <TabsList className="grid w-full grid-cols-2 md:grid-cols-4"> <TabsTrigger value="players">Players</TabsTrigger> <TabsTrigger value="teams">Teams</TabsTrigger> <TabsTrigger value="matches">Matches</TabsTrigger> <TabsTrigger value="news">News</TabsTrigger> </TabsList> <TabsContent value="players" className="mt-6"> <div className="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5"> {featuredPlayers.map((player) => ( <PlayerCard key={player.id} player={player} /> ))} {featuredPlayers.map((player) => ( <PlayerCard key={`${player.id}-2`} player={{...player, id: `${player.id}-2`, name: player.name.split(' ')[1]}} /> ))} </div> </TabsContent> <TabsContent value="teams" className="mt-6"> <div className="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5"> {teams.map((team) => ( <TeamCard key={team.name} team={team} /> ))} </div> </TabsContent> <TabsContent value="matches" className="mt-6"> <div className="space-y-4"> <h2 className="font-headline text-2xl font-semibold">Upcoming</h2> {upcomingMatches.map((match) => ( <MatchCard key={match.id} match={match} /> ))} <h2 className="font-headline text-2xl font-semibold mt-8">Live</h2> {liveMatches.map((match) => ( <MatchCard key={match.id} match={match} /> ))} </div> </TabsContent> <TabsContent value="news" className="mt-6"> <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> {newsItems.map((item) => <NewsCard key={item.id} item={item} />)} </div> </TabsContent> </Tabs> </div> ); }
src/app/dashboard/analytics/page.tsx
import StatCard from '@/components/dashboard/stat-card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Target, BarChart4, AreaChart, DollarSign, Percent } from 'lucide-react'; import { analyticsStats } from '@/lib/mock-data'; import PerformanceChart from '@/components/dashboard/performance-chart'; export default function AnalyticsPage() { return ( <div className="space-y-6"> <h1 className="font-headline text-3xl font-bold">Analytics</h1> <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-5"> <StatCard title="Total Predictions" value={String(analyticsStats.totalPredictions)} icon={BarChart4} /> <StatCard title="Correct Predictions" value={String(analyticsStats.correctPredictions)} icon={Target} /> <StatCard title="Accuracy" value={`${analyticsStats.accuracy}%`} icon={Percent} /> <StatCard title="Profit" value={`€${analyticsStats.profit}`} icon={DollarSign} /> <StatCard title="ROI" value={`${analyticsStats.roi}%`} icon={AreaChart} /> </div> <Card> <CardHeader> <CardTitle className="font-headline text-2xl">Performance Chart (Accuracy %)</CardTitle> </CardHeader> <CardContent> <PerformanceChart /> </CardContent> </Card> </div> ); }