Add to calendar button (#1225)

This commit is contained in:
Luke Vella 2024-08-04 14:04:07 +01:00 committed by GitHub
parent 2798ba65ce
commit 1a4eaf7e27
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 434 additions and 87 deletions

View file

@ -44,6 +44,7 @@
"@vercel/kv": "^2.0.0",
"accept-language-parser": "^1.5.0",
"autoprefixer": "^10.4.13",
"calendar-link": "^2.6.0",
"class-variance-authority": "^0.7.0",
"color-hash": "^2.0.2",
"cookie": "^0.6.0",

View file

@ -263,5 +263,11 @@
"activePollCount": "{{activePollCount}} Live",
"createPoll": "Create poll",
"yearlyDiscount": "Save {amount}",
"yearlyBillingDescription": "per year"
"yearlyBillingDescription": "per year",
"addToCalendar": "Add to Calendar",
"microsoft365": "Microsoft 365",
"outlook": "Outlook",
"yahoo": "Yahoo",
"downloadICSFile": "Download ICS File",
"schedulateDate": "Scheduled Date"
}

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Livello_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<g>
<g transform="translate(3.75 3.75)">
<path fill="#FFFFFF" d="M148.882,43.618l-47.368-5.263l-57.895,5.263L38.355,96.25l5.263,52.632l52.632,6.579l52.632-6.579
l5.263-53.947L148.882,43.618z"/>
<path fill="#1A73E8" d="M65.211,125.276c-3.934-2.658-6.658-6.539-8.145-11.671l9.132-3.763c0.829,3.158,2.276,5.605,4.342,7.342
c2.053,1.737,4.553,2.592,7.474,2.592c2.987,0,5.553-0.908,7.697-2.724s3.224-4.132,3.224-6.934c0-2.868-1.132-5.211-3.395-7.026
s-5.105-2.724-8.5-2.724h-5.276v-9.039H76.5c2.921,0,5.382-0.789,7.382-2.368c2-1.579,3-3.737,3-6.487
c0-2.447-0.895-4.395-2.684-5.855s-4.053-2.197-6.803-2.197c-2.684,0-4.816,0.711-6.395,2.145s-2.724,3.197-3.447,5.276
l-9.039-3.763c1.197-3.395,3.395-6.395,6.618-8.987c3.224-2.592,7.342-3.895,12.342-3.895c3.697,0,7.026,0.711,9.974,2.145
c2.947,1.434,5.263,3.421,6.934,5.947c1.671,2.539,2.5,5.382,2.5,8.539c0,3.224-0.776,5.947-2.329,8.184
c-1.553,2.237-3.461,3.947-5.724,5.145v0.539c2.987,1.25,5.421,3.158,7.342,5.724c1.908,2.566,2.868,5.632,2.868,9.211
s-0.908,6.776-2.724,9.579c-1.816,2.803-4.329,5.013-7.513,6.618c-3.197,1.605-6.789,2.421-10.776,2.421
C73.408,129.263,69.145,127.934,65.211,125.276z"/>
<path fill="#1A73E8" d="M121.25,79.961l-9.974,7.25l-5.013-7.605l17.987-12.974h6.895v61.197h-9.895L121.25,79.961z"/>
<path fill="#EA4335" d="M148.882,196.25l47.368-47.368l-23.684-10.526l-23.684,10.526l-10.526,23.684L148.882,196.25z"/>
<path fill="#34A853" d="M33.092,172.566l10.526,23.684h105.263v-47.368H43.618L33.092,172.566z"/>
<path fill="#4285F4" d="M12.039-3.75C3.316-3.75-3.75,3.316-3.75,12.039v136.842l23.684,10.526l23.684-10.526V43.618h105.263
l10.526-23.684L148.882-3.75H12.039z"/>
<path fill="#188038" d="M-3.75,148.882v31.579c0,8.724,7.066,15.789,15.789,15.789h31.579v-47.368H-3.75z"/>
<path fill="#FBBC04" d="M148.882,43.618v105.263h47.368V43.618l-23.684-10.526L148.882,43.618z"/>
<path fill="#1967D2" d="M196.25,43.618V12.039c0-8.724-7.066-15.789-15.789-15.789h-31.579v47.368H196.25z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" ?>
<svg xmlns="http://www.w3.org/2000/svg" id="image-2" fill="none" viewBox="4 2 40 44">
<title>Microsoft 365 logo (2022)</title>
<path d="M20.0842 3.02588L19.8595 3.16179C19.5021 3.37799 19.1654 3.61972 18.8512 3.88385L19.4993 3.42798H25L26 11L21 16L16 19.4754V23.4829C16 26.2819 17.4629 28.8774 19.8574 30.3268L25.1211 33.5129L14 40.0002H11.8551L7.85737 37.5804C5.46286 36.131 4 33.5355 4 30.7365V17.2606C4 14.4607 5.46379 11.8645 7.85952 10.4154L19.8595 3.15687C19.9339 3.11189 20.0088 3.06823 20.0842 3.02588Z" fill="url(#paint0_radial_2994_8373)"/>
<path d="M20.0842 3.02588L19.8595 3.16179C19.5021 3.37799 19.1654 3.61972 18.8512 3.88385L19.4993 3.42798H25L26 11L21 16L16 19.4754V23.4829C16 26.2819 17.4629 28.8774 19.8574 30.3268L25.1211 33.5129L14 40.0002H11.8551L7.85737 37.5804C5.46286 36.131 4 33.5355 4 30.7365V17.2606C4 14.4607 5.46379 11.8645 7.85952 10.4154L19.8595 3.15687C19.9339 3.11189 20.0088 3.06823 20.0842 3.02588Z" fill="url(#paint1_linear_2994_8373)"/>
<path d="M32 19V23.4803C32 26.2793 30.5371 28.8748 28.1426 30.3242L16.1426 37.5878C13.6878 39.0737 10.6335 39.1273 8.1355 37.7487L19.8573 44.844C22.4039 46.3855 25.5959 46.3855 28.1426 44.844L40.1426 37.5803C42.5371 36.1309 43.9999 33.5354 43.9999 30.7364V27.5L42.9999 26L32 19Z" fill="url(#paint2_radial_2994_8373)"/>
<path d="M32 19V23.4803C32 26.2793 30.5371 28.8748 28.1426 30.3242L16.1426 37.5878C13.6878 39.0737 10.6335 39.1273 8.1355 37.7487L19.8573 44.844C22.4039 46.3855 25.5959 46.3855 28.1426 44.844L40.1426 37.5803C42.5371 36.1309 43.9999 33.5354 43.9999 30.7364V27.5L42.9999 26L32 19Z" fill="url(#paint3_linear_2994_8373)"/>
<path d="M40.1405 10.4153L28.1405 3.15678C25.6738 1.66471 22.6021 1.61849 20.0979 3.01811L19.8595 3.16231C17.4638 4.61143 16 7.20757 16 10.0075V19.4914L19.8595 17.1568C22.4051 15.6171 25.5949 15.6171 28.1405 17.1568L40.1405 24.4153C42.4613 25.8192 43.9076 28.2994 43.9957 30.9985C43.9986 30.9113 44 30.824 44 30.7364V17.2605C44 14.4606 42.5362 11.8644 40.1405 10.4153Z" fill="url(#paint4_radial_2994_8373)"/>
<path d="M40.1405 10.4153L28.1405 3.15678C25.6738 1.66471 22.6021 1.61849 20.0979 3.01811L19.8595 3.16231C17.4638 4.61143 16 7.20757 16 10.0075V19.4914L19.8595 17.1568C22.4051 15.6171 25.5949 15.6171 28.1405 17.1568L40.1405 24.4153C42.4613 25.8192 43.9076 28.2994 43.9957 30.9985C43.9986 30.9113 44 30.824 44 30.7364V17.2605C44 14.4606 42.5362 11.8644 40.1405 10.4153Z" fill="url(#paint5_linear_2994_8373)"/>
<path d="M4.00428 30.9984C4.00428 30.9984 4.00428 30.9984 4.00428 30.9984Z" fill="url(#paint6_radial_2994_8373)"/>
<path d="M4.00428 30.9984C4.00428 30.9984 4.00428 30.9984 4.00428 30.9984Z" fill="url(#paint7_linear_2994_8373)"/>
<defs>
<radialGradient id="paint0_radial_2994_8373" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(17.4186 10.6383) rotate(110.528) scale(33.3657 58.1966)">
<stop offset="0.06441" stop-color="#AE7FE2"/>
<stop offset="1" stop-color="#0078D4"/>
</radialGradient>
<linearGradient id="paint1_linear_2994_8373" x1="17.5119" y1="37.8685" x2="12.7513" y2="29.6347" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#114A8B"/>
<stop offset="1" stop-color="#0078D4" stop-opacity="0"/>
</linearGradient>
<radialGradient id="paint2_radial_2994_8373" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(10.4299 36.3511) rotate(-8.36717) scale(31.0503 20.5108)">
<stop offset="0.133928" stop-color="#D59DFF"/>
<stop offset="1" stop-color="#5E438F"/>
</radialGradient>
<linearGradient id="paint3_linear_2994_8373" x1="40.3566" y1="25.3768" x2="35.2552" y2="32.6916" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#493474"/>
<stop offset="1" stop-color="#8C66BA" stop-opacity="0"/>
</linearGradient>
<radialGradient id="paint4_radial_2994_8373" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(41.0552 26.504) rotate(-165.772) scale(24.9228 41.9552)">
<stop offset="0.0584996" stop-color="#50E6FF"/>
<stop offset="1" stop-color="#436DCD"/>
</radialGradient>
<linearGradient id="paint5_linear_2994_8373" x1="16.9758" y1="3.05655" x2="24.4868" y2="3.05655" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#2D3F80"/>
<stop offset="1" stop-color="#436DCD" stop-opacity="0"/>
</linearGradient>
<radialGradient id="paint6_radial_2994_8373" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(41.0552 26.504) rotate(-165.772) scale(24.9228 41.9552)">
<stop offset="0.0584996" stop-color="#50E6FF"/>
<stop offset="1" stop-color="#436DCD"/>
</radialGradient>
<linearGradient id="paint7_linear_2994_8373" x1="16.9758" y1="3.05655" x2="24.4868" y2="3.05655" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#2D3F80"/>
<stop offset="1" stop-color="#436DCD" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.3 KiB

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Livello_1" xmlns:x="http://ns.adobe.com/Extensibility/1.0/" xmlns:i="http://ns.adobe.com/AdobeIllustrator/10.0/" xmlns:graph="http://ns.adobe.com/Graphs/1.0/" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1831.085 1703.335" enable-background="new 0 0 1831.085 1703.335" xml:space="preserve">
<path fill="#0A2767" d="M1831.083,894.25c0.1-14.318-7.298-27.644-19.503-35.131h-0.213l-0.767-0.426l-634.492-375.585 c-2.74-1.851-5.583-3.543-8.517-5.067c-24.498-12.639-53.599-12.639-78.098,0c-2.934,1.525-5.777,3.216-8.517,5.067L446.486,858.693 l-0.766,0.426c-19.392,12.059-25.337,37.556-13.278,56.948c3.553,5.714,8.447,10.474,14.257,13.868l634.492,375.585 c2.749,1.835,5.592,3.527,8.517,5.068c24.498,12.639,53.599,12.639,78.098,0c2.925-1.541,5.767-3.232,8.517-5.068l634.492-375.585 C1823.49,922.545,1831.228,908.923,1831.083,894.25z"/>
<path fill="#0364B8" d="M520.453,643.477h416.38v381.674h-416.38V643.477z M1745.917,255.5V80.908 c1-43.652-33.552-79.862-77.203-80.908H588.204C544.552,1.046,510,37.256,511,80.908V255.5l638.75,170.333L1745.917,255.5z"/>
<path fill="#0078D4" d="M511,255.5h425.833v383.25H511V255.5z"/>
<path fill="#28A8EA" d="M1362.667,255.5H936.833v383.25L1362.667,1022h383.25V638.75L1362.667,255.5z"/>
<path fill="#0078D4" d="M936.833,638.75h425.833V1022H936.833V638.75z"/>
<path fill="#0364B8" d="M936.833,1022h425.833v383.25H936.833V1022z"/>
<path fill="#14447D" d="M520.453,1025.151h416.38v346.969h-416.38V1025.151z"/>
<path fill="#0078D4" d="M1362.667,1022h383.25v383.25h-383.25V1022z"/>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="1128.4584" y1="811.0833" x2="1128.4584" y2="1.9982" gradientTransform="matrix(1 0 0 -1 0 1705.3334)">
<stop offset="0" style="stop-color:#35B8F1"/>
<stop offset="1" style="stop-color:#28A8EA"/>
</linearGradient>
<path fill="url(#SVGID_1_)" d="M1811.58,927.593l-0.809,0.426l-634.492,356.848c-2.768,1.703-5.578,3.321-8.517,4.769 c-10.777,5.132-22.481,8.029-34.407,8.517l-34.663-20.27c-2.929-1.47-5.773-3.105-8.517-4.897L447.167,906.003h-0.298 l-21.036-11.753v722.384c0.328,48.196,39.653,87.006,87.849,86.7h1230.914c0.724,0,1.363-0.341,2.129-0.341 c10.18-0.651,20.216-2.745,29.808-6.217c4.145-1.756,8.146-3.835,11.966-6.217c2.853-1.618,7.75-5.152,7.75-5.152 c21.814-16.142,34.726-41.635,34.833-68.772V894.25C1831.068,908.067,1823.616,920.807,1811.58,927.593z"/>
<path opacity="0.5" fill="#0A2767" enable-background="new " d="M1797.017,891.397v44.287l-663.448,456.791L446.699,906.301 c0-0.235-0.191-0.426-0.426-0.426l0,0l-63.023-37.899v-31.938l25.976-0.426l54.932,31.512l1.277,0.426l4.684,2.981 c0,0,645.563,368.346,647.267,369.197l24.698,14.478c2.129-0.852,4.258-1.703,6.813-2.555 c1.278-0.852,640.879-360.681,640.879-360.681L1797.017,891.397z"/>
<path fill="#1490DF" d="M1811.58,927.593l-0.809,0.468l-634.492,356.848c-2.768,1.703-5.578,3.321-8.517,4.769 c-24.641,12.038-53.457,12.038-78.098,0c-2.918-1.445-5.76-3.037-8.517-4.769L446.657,928.061l-0.766-0.468 c-12.25-6.642-19.93-19.409-20.057-33.343v722.384c0.305,48.188,39.616,87.004,87.803,86.7c0.001,0,0.002,0,0.004,0h1229.636 c48.188,0.307,87.5-38.509,87.807-86.696c0-0.001,0-0.002,0-0.004V894.25C1831.068,908.067,1823.616,920.807,1811.58,927.593z"/>
<path opacity="0.1" enable-background="new " d="M1185.52,1279.629l-9.496,5.323c-2.752,1.752-5.595,3.359-8.517,4.812 c-10.462,5.135-21.838,8.146-33.47,8.857l241.405,285.479l421.107,101.476c11.539-8.716,20.717-20.178,26.7-33.343L1185.52,1279.629 z"/>
<path opacity="0.05" enable-background="new " d="M1228.529,1255.442l-52.505,29.51c-2.752,1.752-5.595,3.359-8.517,4.812 c-10.462,5.135-21.838,8.146-33.47,8.857l113.101,311.838l549.538,74.989c21.649-16.254,34.394-41.743,34.407-68.815v-9.326 L1228.529,1255.442z"/>
<path fill="#28A8EA" d="M514.833,1703.333h1228.316c18.901,0.096,37.335-5.874,52.59-17.033l-697.089-408.331 c-2.929-1.47-5.773-3.105-8.517-4.897L447.125,906.088h-0.298l-20.993-11.838v719.914 C425.786,1663.364,465.632,1703.286,514.833,1703.333C514.832,1703.333,514.832,1703.333,514.833,1703.333z"/>
<path opacity="0.1" enable-background="new " d="M1022,418.722v908.303c-0.076,31.846-19.44,60.471-48.971,72.392 c-9.148,3.931-19,5.96-28.957,5.962H425.833V383.25H511v-42.583h433.073C987.092,340.83,1021.907,375.702,1022,418.722z"/>
<path opacity="0.2" enable-background="new " d="M979.417,461.305v908.302c0.107,10.287-2.074,20.469-6.388,29.808 c-11.826,29.149-40.083,48.273-71.54,48.417H425.833V383.25h475.656c12.356-0.124,24.533,2.958,35.344,8.943 C962.937,405.344,979.407,432.076,979.417,461.305z"/>
<path opacity="0.2" enable-background="new " d="M979.417,461.305v823.136c-0.208,43-34.928,77.853-77.927,78.225H425.833V383.25 h475.656c12.356-0.124,24.533,2.958,35.344,8.943C962.937,405.344,979.407,432.076,979.417,461.305z"/>
<path opacity="0.2" enable-background="new " d="M936.833,461.305v823.136c-0.046,43.067-34.861,78.015-77.927,78.225H425.833 V383.25h433.072c43.062,0.023,77.951,34.951,77.927,78.013C936.833,461.277,936.833,461.291,936.833,461.305z"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="162.7469" y1="1383.0741" x2="774.0864" y2="324.2592" gradientTransform="matrix(1 0 0 -1 0 1705.3334)">
<stop offset="0" style="stop-color:#1784D9"/>
<stop offset="0.5" style="stop-color:#107AD5"/>
<stop offset="1" style="stop-color:#0A63C9"/>
</linearGradient>
<path fill="url(#SVGID_2_)" d="M78.055,383.25h780.723c43.109,0,78.055,34.947,78.055,78.055v780.723 c0,43.109-34.946,78.055-78.055,78.055H78.055c-43.109,0-78.055-34.947-78.055-78.055V461.305 C0,418.197,34.947,383.25,78.055,383.25z"/>
<path fill="#FFFFFF" d="M243.96,710.631c19.238-40.988,50.29-75.289,89.17-98.495c43.057-24.651,92.081-36.94,141.675-35.515 c45.965-0.997,91.321,10.655,131.114,33.683c37.414,22.312,67.547,55.004,86.742,94.109c20.904,43.09,31.322,90.512,30.405,138.396 c1.013,50.043-9.706,99.628-31.299,144.783c-19.652,40.503-50.741,74.36-89.425,97.388c-41.327,23.734-88.367,35.692-136.011,34.578 c-46.947,1.133-93.303-10.651-134.01-34.067c-37.738-22.341-68.249-55.07-87.892-94.28c-21.028-42.467-31.57-89.355-30.745-136.735 C212.808,804.859,223.158,755.686,243.96,710.631z M339.006,941.858c10.257,25.912,27.651,48.385,50.163,64.812 c22.93,16.026,50.387,24.294,78.353,23.591c29.783,1.178,59.14-7.372,83.634-24.358c22.227-16.375,39.164-38.909,48.715-64.812 c10.677-28.928,15.946-59.572,15.543-90.404c0.33-31.127-4.623-62.084-14.649-91.554c-8.855-26.607-25.246-50.069-47.182-67.537 c-23.88-17.79-53.158-26.813-82.91-25.55c-28.572-0.74-56.644,7.593-80.184,23.804c-22.893,16.496-40.617,39.168-51.1,65.365 c-23.255,60.049-23.376,126.595-0.341,186.728L339.006,941.858z"/>
<path fill="#50D9FF" d="M1362.667,255.5h383.25v383.25h-383.25V255.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 6.9 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3386.34 3010.5" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd"><path d="M0 732.88h645.84l376.07 962.1 380.96-962.1h628.76l-946.8 2277.62H451.98l259.19-603.53L.02 732.88zm2763.84 768.75h-704.26L2684.65 0l701.69.03-622.5 1501.6zm-519.78 143.72c216.09 0 391.25 175.17 391.25 391.22 0 216.06-175.16 391.23-391.25 391.23-216.06 0-391.19-175.17-391.19-391.23 0-216.05 175.16-391.22 391.19-391.22z" fill="#5f01d1" fill-rule="nonzero"/></svg>

After

Width:  |  Height:  |  Size: 587 B

View file

@ -7,9 +7,9 @@ import { EventCard } from "@/components/event-card";
import { PollFooter } from "@/components/poll/poll-footer";
import { PollHeader } from "@/components/poll/poll-header";
import { ResponsiveResults } from "@/components/poll/responsive-results";
import { ScheduledEvent } from "@/components/poll/scheduled-event";
import { useTouchBeacon } from "@/components/poll/use-touch-beacon";
import { VotingForm } from "@/components/poll/voting-form";
import { ScheduledEvent } from "@/components/scheduled-event";
import { Trans } from "@/components/trans";
import { useUser } from "@/components/user-provider";
import { usePoll } from "@/contexts/poll";

View file

@ -4,9 +4,9 @@ import { EventCard } from "@/components/event-card";
import { PollFooter } from "@/components/poll/poll-footer";
import { PollHeader } from "@/components/poll/poll-header";
import { ResponsiveResults } from "@/components/poll/responsive-results";
import { ScheduledEvent } from "@/components/poll/scheduled-event";
import { useTouchBeacon } from "@/components/poll/use-touch-beacon";
import { VotingForm } from "@/components/poll/voting-form";
import { ScheduledEvent } from "@/components/scheduled-event";
import { GuestPollAlert } from "./guest-poll-alert";

View file

@ -0,0 +1,146 @@
"use client";
import { Button } from "@rallly/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@rallly/ui/dropdown-menu";
import { Icon } from "@rallly/ui/icon";
import {
CalendarEvent,
google,
ics,
office365,
outlook,
yahoo,
} from "calendar-link";
import { DownloadIcon, PlusIcon } from "lucide-react";
import Image from "next/image";
import { Trans } from "@/components/trans";
export function AddToCalendarButton({
title,
description,
location,
start,
duration,
organizer,
guests,
}: {
title: string;
description?: string;
location?: string;
start: Date;
duration: number;
organizer?: {
name: string;
email: string;
};
guests?: string[];
}) {
const calendarEvent: CalendarEvent = {
title,
description,
start,
allDay: duration === 0,
duration: duration > 0 ? [duration, "minutes"] : undefined,
location,
organizer,
guests,
busy: true,
};
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
onClick={() => {
const res = ics(calendarEvent);
// download the file
const blob = new Blob([res], { type: "text/calendar" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.setAttribute("href", url);
link.setAttribute(
"download",
`${title.toLocaleLowerCase().replace(/\s/g, "-")}.ics`,
);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}}
>
<Icon>
<PlusIcon />
</Icon>
<Trans i18nKey="addToCalendar" defaults="Add to Calendar" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent forceMount={true} align="start">
<DropdownMenuItem
onClick={() => {
const res = google(calendarEvent);
window.open(res, "_blank");
}}
>
<Image
src="/static/google-calendar.svg"
width={16}
height={16}
alt="Google Calendar"
/>
Google Calendar
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
const res = office365(calendarEvent);
window.open(res, "_blank");
}}
>
<Image
src="/static/microsoft-365.svg"
width={16}
height={16}
alt="Microsoft 365"
/>
<Trans i18nKey="microsoft365" defaults="Microsoft 365" />
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
const res = outlook(calendarEvent);
window.open(res, "_blank");
}}
>
<Image
src="/static/outlook.svg"
width={16}
height={16}
alt="Outlook"
/>
<Trans i18nKey="outlook" defaults="Outlook" />
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
const res = yahoo(calendarEvent);
window.open(res, "_blank");
}}
>
<Image src="/static/yahoo.svg" width={16} height={16} alt="Yahoo" />
<Trans i18nKey="yahoo" defaults="Yahoo" />
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icon>
<DownloadIcon />
</Icon>
<Trans i18nKey="downloadICSFile" defaults="Download ICS File" />
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}

View file

@ -0,0 +1,124 @@
"use client";
import { AddToCalendarButton } from "@/components/add-to-calendar-button";
import { ParticipantAvatarBar } from "@/components/participant-avatar-bar";
import { useVisibleParticipants } from "@/components/participants-provider";
import { Trans } from "@/components/trans";
import { IfParticipantsVisible } from "@/components/visibility";
import { usePoll } from "@/contexts/poll";
import { useDayjs } from "@/utils/dayjs";
function FinalDate({ start }: { start: Date }) {
const poll = usePoll();
const { adjustTimeZone } = useDayjs();
return <span>{adjustTimeZone(start, !poll.timeZone).format("LL")}</span>;
}
function DateIcon({ start }: { start: Date }) {
const poll = usePoll();
const { adjustTimeZone } = useDayjs();
const d = adjustTimeZone(start, !poll.timeZone);
return (
<time
className="inline-flex size-12 flex-col rounded-lg border border-green-600/10 bg-green-600/5 text-center text-green-800/75"
dateTime={d.toISOString()}
>
<div className="border-b border-green-600/10 p-px text-xs">
{d.format("MMM")}
</div>
<div className="inline-flex grow items-center justify-center text-sm font-bold">
{d.format("D")}
</div>
</time>
);
}
function FinalTime({ start, duration }: { start: Date; duration: number }) {
const poll = usePoll();
const { adjustTimeZone, dayjs } = useDayjs();
if (duration === 0) {
return <Trans i18nKey="allDay" />;
}
return (
<span>{`${adjustTimeZone(start, !poll.timeZone).format("LT")} - ${adjustTimeZone(dayjs(start).add(duration, "minutes"), !poll.timeZone).format(poll.timeZone ? "LT z" : "LT")}`}</span>
);
}
function useAttendees() {
const participants = useVisibleParticipants();
const poll = usePoll();
return participants.filter((participant) =>
participant.votes.some(
(vote) =>
vote.optionId === poll?.event?.optionId &&
(vote.type === "yes" || vote.type === "ifNeedBe"),
),
);
}
function Attendees() {
const attendees = useAttendees();
return <ParticipantAvatarBar participants={attendees} max={5} />;
}
export function ScheduledEvent() {
const poll = usePoll();
const { event } = poll;
const attendees = useAttendees();
if (!event) {
return null;
}
return (
<>
<div className="rounded-lg border border-green-400/20 bg-gradient-to-r from-green-200/15 to-green-200/5 p-0.5 shadow-sm">
<div className="flex items-center gap-x-2 rounded-md border-b bg-green-500/10 p-3">
<h2 className="text-sm font-medium text-green-800">
<Trans i18nKey="schedulateDate" defaults="Scheduled Date" />
</h2>
</div>
<div className="flex flex-col justify-between gap-4 p-4 sm:flex-row sm:items-center">
<div className="flex items-center gap-6">
<div>
<DateIcon start={event.start} />
</div>
<div className="items-center gap-x-4 text-green-800">
<div className="space-y-1">
<div className="text-sm font-medium">
<FinalDate start={event.start} />
</div>
<div className="text-sm opacity-75">
<FinalTime start={event.start} duration={event.duration} />
</div>
</div>
</div>
</div>
<div className="flex items-center gap-4">
<IfParticipantsVisible>
<Attendees />
</IfParticipantsVisible>
<AddToCalendarButton
title={poll.title}
description={poll.description ?? undefined}
start={event.start}
duration={event.duration}
location={poll.location ?? undefined}
organizer={
poll.user
? { name: poll.user.name, email: poll.user.email }
: undefined
}
guests={attendees
.filter((participant) => !!participant.email)
.map((participant) => participant.email!)}
/>
</div>
</div>
</div>
<hr />
</>
);
}

View file

@ -1,83 +0,0 @@
"use client";
import { Card, CardContent } from "@rallly/ui/card";
import { DateIconInner } from "@/components/date-icon";
import { ParticipantAvatarBar } from "@/components/participant-avatar-bar";
import { useParticipants } from "@/components/participants-provider";
import { Trans } from "@/components/trans";
import { IfParticipantsVisible } from "@/components/visibility";
import { usePoll } from "@/contexts/poll";
import { useDayjs } from "@/utils/dayjs";
function FinalDate({ start }: { start: Date }) {
const poll = usePoll();
const { adjustTimeZone } = useDayjs();
return <span>{adjustTimeZone(start, !poll.timeZone).format("LL")}</span>;
}
function DateIcon({ start }: { start: Date }) {
const poll = usePoll();
const { adjustTimeZone } = useDayjs();
const d = adjustTimeZone(start, !poll.timeZone);
return <DateIconInner dow={d.format("ddd")} day={d.format("D")} />;
}
function FinalTime({ start, duration }: { start: Date; duration: number }) {
const poll = usePoll();
const { adjustTimeZone, dayjs } = useDayjs();
if (duration === 0) {
return <Trans i18nKey="allDay" />;
}
return (
<span>{`${adjustTimeZone(start, !poll.timeZone).format("LT")} - ${adjustTimeZone(dayjs(start).add(duration, "minutes"), !poll.timeZone).format(poll.timeZone ? "LT z" : "LT")}`}</span>
);
}
function Attendees() {
const { participants } = useParticipants();
const poll = usePoll();
const attendees = participants.filter((participant) =>
participant.votes.some(
(vote) =>
vote.optionId === poll?.event?.optionId &&
(vote.type === "yes" || vote.type === "ifNeedBe"),
),
);
return <ParticipantAvatarBar participants={attendees} max={5} />;
}
export function ScheduledEvent() {
const poll = usePoll();
if (!poll.event) {
return null;
}
return (
<Card className="bg-gray-50">
<CardContent>
<div className="flex justify-between gap-4">
<div className="flex items-center gap-x-4">
<DateIcon start={poll.event.start} />
<div>
<div className="text-sm font-medium">
<FinalDate start={poll.event.start} />
</div>
<div className="text-muted-foreground text-sm">
<FinalTime
start={poll.event.start}
duration={poll.event.duration}
/>
</div>
</div>
</div>
<div>
<IfParticipantsVisible>
<Attendees />
</IfParticipantsVisible>
</div>
</div>
</CardContent>
</Card>
);
}

View file

@ -96,7 +96,7 @@ const DropdownMenuItem = React.forwardRef<
ref={ref}
className={cn(
"flex items-center gap-x-2.5",
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm font-medium outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded px-2 py-1.5 text-sm font-medium outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className,
)}

View file

@ -6811,6 +6811,14 @@ cac@^6.7.14:
resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959"
integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==
calendar-link@^2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/calendar-link/-/calendar-link-2.6.0.tgz#47cec3d9a2d0c13ac87f732898867e92ea423c98"
integrity sha512-ypgYoFBz2w0WkJV1m6LoO31i8F5te3/rsTdJp/ONacVmr+C4Ny7rHhvwmIZS3yrbG2abbcohn2D8DZbcFZvwfA==
dependencies:
dayjs "^1.9.3"
query-string "^6.13.6"
call-bind@^1.0.0, call-bind@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz"
@ -7395,6 +7403,11 @@ dayjs@^1.11.7:
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz"
integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==
dayjs@^1.9.3:
version "1.11.12"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.12.tgz#5245226cc7f40a15bf52e0b99fd2a04669ccac1d"
integrity sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg==
debounce@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/debounce/-/debounce-2.0.0.tgz#b2f914518a1481466f4edaee0b063e4d473ad549"
@ -7441,6 +7454,11 @@ decode-named-character-reference@^1.0.0:
dependencies:
character-entities "^2.0.0"
decode-uri-component@^0.2.0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==
deep-eql@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.3.tgz#7c7775513092f7df98d8df9996dd085eb668cc6d"
@ -8535,6 +8553,11 @@ fill-range@^7.1.1:
dependencies:
to-regex-range "^5.0.1"
filter-obj@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b"
integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==
find-up@^4.1.0:
version "4.1.0"
resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz"
@ -12050,6 +12073,16 @@ qs@^6.11.0:
dependencies:
side-channel "^1.0.4"
query-string@^6.13.6:
version "6.14.1"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.14.1.tgz#7ac2dca46da7f309449ba0f86b1fd28255b0c86a"
integrity sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==
dependencies:
decode-uri-component "^0.2.0"
filter-obj "^1.1.0"
split-on-first "^1.0.0"
strict-uri-encode "^2.0.0"
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
@ -13052,6 +13085,11 @@ spdx-license-ids@^3.0.0:
resolved "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz"
integrity sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==
split-on-first@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f"
integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz"
@ -13123,6 +13161,11 @@ streamsearch@^1.1.0:
resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz"
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
strict-uri-encode@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"