TeamsModule moved to old repos
No files matched your search
|
After Width: | Height: | Size: 78 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 120 24"><defs><style>.cls-1,.cls-3{fill:none;}.cls-2{clip-path:url(#clip-path);}.cls-3{stroke:#2a96ff;stroke-miterlimit:10;stroke-width:0.5px;}.cls-4{fill:#fff;}.cls-5{fill:#e3493c;}</style><clipPath id="clip-path"><rect class="cls-1" x="272.54" y="79.59" width="600" height="600"/></clipPath></defs><title>aa-logo-dark</title><g id="Layer_1" data-name="Layer 1"><g class="cls-2"><circle class="cls-3" cx="338.14" cy="263.28" r="277.97"/></g></g><g id="Layer_4" data-name="Layer 4"><path class="cls-4" d="M27.67,15.74l-.19-1.58H26.29l-.2,1.58H24.51L25.7,7.79h2.39l1.19,8Zm-.75-6.39h-.08l-.4,3.41h.89Z"/><path class="cls-4" d="M32.15,7.79h1.93a2.39,2.39,0,0,1,1.46.35,1.68,1.68,0,0,1,.54,1.11,21,21,0,0,1,.1,2.52,20.93,20.93,0,0,1-.1,2.51,1.68,1.68,0,0,1-.54,1.11,2.39,2.39,0,0,1-1.46.35H32.15Zm1.93,6.51a.46.46,0,0,0,.37-.12,1.19,1.19,0,0,0,.13-.6c0-.32,0-.93,0-1.81s0-1.5,0-1.82a1.19,1.19,0,0,0-.13-.6.46.46,0,0,0-.37-.12h-.36V14.3Z"/><path class="cls-4" d="M39.94,15.74,38.87,7.79h1.59l.66,6.31h.12l.61-6.31h1.57l-1.07,8Z"/><path class="cls-4" d="M48.8,15.74l-.19-1.58H47.42l-.2,1.58H45.64l1.19-7.95h2.39l1.19,8Zm-.75-6.39H48l-.4,3.41h.89Z"/><path class="cls-4" d="M56.06,15.74l-1.23-4h0v4h-1.5V7.79h1.47l1.13,3.61h.06V7.79h1.51v8Z"/><path class="cls-4" d="M63.14,15.77a3.11,3.11,0,0,1-.59.06,2.06,2.06,0,0,1-1.36-.37,1.94,1.94,0,0,1-.58-1.19,17.1,17.1,0,0,1-.13-2.5,14.22,14.22,0,0,1,.15-2.5,1.94,1.94,0,0,1,.58-1.2,2,2,0,0,1,1.34-.37,4.21,4.21,0,0,1,.62,0,1.86,1.86,0,0,1,.45.12V9.2a5.77,5.77,0,0,0-.78-.06.77.77,0,0,0-.55.15,1.22,1.22,0,0,0-.18.64c0,.33,0,.94,0,1.84s0,1.5,0,1.83a1.22,1.22,0,0,0,.18.64.77.77,0,0,0,.55.15,4.74,4.74,0,0,0,.8-.06v1.29A1.56,1.56,0,0,1,63.14,15.77Z"/><path class="cls-4" d="M66.59,15.74V7.79h3.19V9.23h-1.6V11h1.41v1.45H68.18V14.3h1.6v1.44Z"/><path class="cls-4" d="M72.79,7.79h1.92a2.4,2.4,0,0,1,1.47.35,1.67,1.67,0,0,1,.53,1.11,20.46,20.46,0,0,1,.11,2.52,20.38,20.38,0,0,1-.11,2.51,1.67,1.67,0,0,1-.53,1.11,2.4,2.4,0,0,1-1.47.35H72.79Zm1.92,6.51a.48.48,0,0,0,.38-.12,1.19,1.19,0,0,0,.13-.6c0-.32,0-.93,0-1.81s0-1.5,0-1.82a1.19,1.19,0,0,0-.13-.6.48.48,0,0,0-.38-.12h-.35V14.3Z"/><path class="cls-5" d="M85.6,15.74l-.91-2.31H80.93L80,15.74h-.76l3.11-7.95h.89l3.12,8ZM82.84,8.66h-.06l-1.61,4.08h3.27Z"/><path class="cls-5" d="M88.44,15.74V7.79h.74V15H93v.72Z"/><path class="cls-5" d="M97.86,12.24v-.68h2.7v4c-.37.06-.8.12-1.26.16s-.87.06-1.22.06a5.68,5.68,0,0,1-2.14-.3A1.71,1.71,0,0,1,95,14.4a9.76,9.76,0,0,1-.26-2.63A9.85,9.85,0,0,1,95,9.13,1.71,1.71,0,0,1,96,8a5.91,5.91,0,0,1,2.13-.29c.38,0,.81,0,1.27.06s.86.1,1.18.16v.66a13.61,13.61,0,0,0-2-.19,8.76,8.76,0,0,0-2,.16,1.21,1.21,0,0,0-.79.85,8.45,8.45,0,0,0-.23,2.37A10.48,10.48,0,0,0,95.66,14a1.27,1.27,0,0,0,.7.91,4.41,4.41,0,0,0,1.72.24l.9,0c.35,0,.64,0,.87-.09V12.24Z"/><path class="cls-5" d="M103.84,15.54a1.48,1.48,0,0,1-.83-1.08,13.91,13.91,0,0,1-.17-2.69,14,14,0,0,1,.17-2.7A1.48,1.48,0,0,1,103.84,8a6,6,0,0,1,2.21-.29,5.94,5.94,0,0,1,2.2.29,1.46,1.46,0,0,1,.83,1.08,13.75,13.75,0,0,1,.18,2.7,13.7,13.7,0,0,1-.18,2.69,1.46,1.46,0,0,1-.83,1.08,5.94,5.94,0,0,1-2.2.29A6,6,0,0,1,103.84,15.54Zm3.93-.57a1,1,0,0,0,.61-.81,13.62,13.62,0,0,0,.14-2.39,13.68,13.68,0,0,0-.14-2.4,1,1,0,0,0-.61-.81,8.84,8.84,0,0,0-3.45,0,1,1,0,0,0-.6.81,12.47,12.47,0,0,0-.15,2.4,12.42,12.42,0,0,0,.15,2.39,1,1,0,0,0,.6.81,8.84,8.84,0,0,0,3.45,0Z"/><path class="cls-5" d="M112.94,15.79c-.5,0-.94-.06-1.31-.11V15l1.33.1c.48,0,.88.05,1.2.05a8.75,8.75,0,0,0,1-.05,1.78,1.78,0,0,0,.65-.22.68.68,0,0,0,.35-.47,3.13,3.13,0,0,0,.09-.83,4.54,4.54,0,0,0-.06-.9.57.57,0,0,0-.31-.37,3.72,3.72,0,0,0-.88-.18l-2-.22a2.08,2.08,0,0,1-1.06-.32,1.18,1.18,0,0,1-.44-.64,5,5,0,0,1-.09-1.07,2.57,2.57,0,0,1,.3-1.38,1.56,1.56,0,0,1,.9-.63,6.12,6.12,0,0,1,1.59-.16c.45,0,.9,0,1.35,0s.81.08,1.07.13v.66c-.31,0-.68-.07-1.11-.1s-.83,0-1.19,0a6.52,6.52,0,0,0-1.1.07,1.88,1.88,0,0,0-.66.22.73.73,0,0,0-.33.44,2.52,2.52,0,0,0-.08.75,2.94,2.94,0,0,0,.07.77.61.61,0,0,0,.29.36,2.05,2.05,0,0,0,.69.16l2,.21a4.86,4.86,0,0,1,1.17.24,1,1,0,0,1,.55.63,4.24,4.24,0,0,1,.13,1.27,3,3,0,0,1-.31,1.54,1.36,1.36,0,0,1-.85.64,5.78,5.78,0,0,1-1.47.14C113.93,15.83,113.45,15.82,112.94,15.79Z"/><path class="cls-5" d="M11.36,13.89a7.43,7.43,0,0,1,2.52.4c.82.58,2,.89,2.5.3s0-1.61-.79-2.38a7.64,7.64,0,0,1-.93-2.35c.11-1-.23-2.23-1-2.35s-1.47,1-1.65,1.88a7.38,7.38,0,0,1-1.61,2c-.9.42-1.77,1.3-1.49,2S10.48,14.16,11.36,13.89Z"/><path class="cls-5" d="M14.75,3.42C16,3.47,16.49,6,16.29,7.91a26.16,26.16,0,0,0,1.64,4.69,1.88,1.88,0,0,0,.79.79,10.55,10.55,0,0,1-.84-5c.79-2.19.44-5.15-.75-5.84a2.71,2.71,0,0,0-3.68,1.2A1.8,1.8,0,0,1,14.75,3.42Z"/><path class="cls-5" d="M5,14C4.4,13,6.3,11.26,8.09,10.45,8.66,10.2,11,7.2,11.33,6.7a2,2,0,0,0,.29-1.09A10.62,10.62,0,0,1,7.68,8.86C5.38,9.27,3,11.05,3,12.42A2.71,2.71,0,0,0,5.87,15,1.88,1.88,0,0,1,5,14Z"/><path class="cls-5" d="M19.12,17.42c-.66,1-3.07.23-4.67-.92a25.88,25.88,0,0,0-4.87-.92,1.79,1.79,0,0,0-1.08.29,10.37,10.37,0,0,1,4.Line truncated
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 98 140"><defs><style>.cls-1,.cls-3{fill:none;}.cls-2{clip-path:url(#clip-path);}.cls-3{stroke:#2a96ff;stroke-miterlimit:10;stroke-width:0.5px;}.cls-4{fill:#4d4d4d;}.cls-5{fill:#e3493c;}</style><clipPath id="clip-path"><rect class="cls-1" x="144.54" y="59.59" width="600" height="600"/></clipPath></defs><title>aa-logo-vert</title><g id="Layer_1" data-name="Layer 1"><g class="cls-2"><circle class="cls-3" cx="210.14" cy="243.28" r="277.97"/></g></g><g id="Layer_4" data-name="Layer 4"><path class="cls-4" d="M11.27,108.29l-.38-3H8.62l-.38,3h-3L7.49,93.1h4.57l2.28,15.19ZM9.83,96.08H9.68L8.9,102.6h1.71Z"/><path class="cls-4" d="M18,93.1h3.69a4.53,4.53,0,0,1,2.79.67,3.22,3.22,0,0,1,1,2.11,41.13,41.13,0,0,1,.19,4.82,41,41,0,0,1-.19,4.81,3.19,3.19,0,0,1-1,2.11,4.46,4.46,0,0,1-2.79.67H18Zm3.69,12.43a.89.89,0,0,0,.71-.22,2.31,2.31,0,0,0,.25-1.15c0-.61,0-1.77,0-3.46s0-2.85,0-3.47a2.31,2.31,0,0,0-.25-1.15.89.89,0,0,0-.71-.22H21v9.67Z"/><path class="cls-4" d="M31.16,108.29,29.11,93.1h3l1.25,12.06h.23L34.8,93.1h3l-2.05,15.19Z"/><path class="cls-4" d="M46.35,108.29l-.38-3H43.7l-.38,3h-3L42.57,93.1h4.57l2.28,15.19ZM44.91,96.08h-.15L44,102.6h1.7Z"/><path class="cls-4" d="M58.44,108.29l-2.36-7.62H56v7.62H53.12V93.1h2.8L58.09,100h.11V93.1h2.89v15.19Z"/><path class="cls-4" d="M70.19,108.36a6.43,6.43,0,0,1-1.12.11,4,4,0,0,1-2.61-.71,3.72,3.72,0,0,1-1.1-2.28,31.28,31.28,0,0,1-.26-4.78,29.46,29.46,0,0,1,.28-4.77,3.82,3.82,0,0,1,1.12-2.3,3.9,3.9,0,0,1,2.57-.71,7.72,7.72,0,0,1,1.18.1,3.21,3.21,0,0,1,.85.24v2.53a11.75,11.75,0,0,0-1.48-.11,1.48,1.48,0,0,0-1.05.29,2.16,2.16,0,0,0-.35,1.23c0,.62-.08,1.79-.08,3.5s0,2.87.08,3.5a2.15,2.15,0,0,0,.35,1.22,1.48,1.48,0,0,0,1.05.29,9.89,9.89,0,0,0,1.53-.11v2.47A4.23,4.23,0,0,1,70.19,108.36Z"/><path class="cls-4" d="M75,108.29V93.1h6.09v2.76H78v3.38h2.7V102H78v3.53h3.07v2.76Z"/><path class="cls-4" d="M85.08,93.1h3.69a4.55,4.55,0,0,1,2.79.67,3.16,3.16,0,0,1,1,2.11,38.45,38.45,0,0,1,.2,4.82,38.37,38.37,0,0,1-.2,4.81,3.14,3.14,0,0,1-1,2.11,4.48,4.48,0,0,1-2.79.67H85.08Zm3.69,12.43a.89.89,0,0,0,.71-.22,2.31,2.31,0,0,0,.25-1.15c0-.61,0-1.77,0-3.46s0-2.85,0-3.47a2.31,2.31,0,0,0-.25-1.15.89.89,0,0,0-.71-.22h-.69v9.67Z"/><path class="cls-5" d="M19.79,134.79l-2.1-5.3H9.08L7,134.79H5.24l7.15-18.23h2l7.16,18.23Zm-6.33-16.24h-.14L9.64,127.9h7.5Z"/><path class="cls-5" d="M26.41,134.79V116.56h1.7v16.58H37v1.65Z"/><path class="cls-5" d="M48.1,126.76V125.2h6.2v9.3c-.87.14-1.84.26-2.9.35s-2,.15-2.8.15a12.88,12.88,0,0,1-4.9-.69,3.89,3.89,0,0,1-2.15-2.6,22.5,22.5,0,0,1-.58-6,22.11,22.11,0,0,1,.6-6A3.87,3.87,0,0,1,43.72,117a13.15,13.15,0,0,1,4.88-.67c.88,0,1.85.05,2.92.14s2,.22,2.7.36v1.51a33.49,33.49,0,0,0-4.66-.42,18.69,18.69,0,0,0-4.55.37,2.7,2.7,0,0,0-1.81,1.93,18.81,18.81,0,0,0-.53,5.43,23.5,23.5,0,0,0,.39,5.13,3,3,0,0,0,1.59,2.09,10.15,10.15,0,0,0,4,.55c.56,0,1.25,0,2.07-.08s1.47-.12,2-.19v-6.41Z"/><path class="cls-5" d="M61.91,134.32A3.33,3.33,0,0,1,60,131.86a31.94,31.94,0,0,1-.41-6.19,31.89,31.89,0,0,1,.41-6.18A3.33,3.33,0,0,1,61.91,117a13.45,13.45,0,0,1,5.07-.67A13.42,13.42,0,0,1,72,117a3.33,3.33,0,0,1,1.89,2.47,31.89,31.89,0,0,1,.41,6.18,31.94,31.94,0,0,1-.41,6.19A3.33,3.33,0,0,1,72,134.32,13.38,13.38,0,0,1,67,135,13.4,13.4,0,0,1,61.91,134.32Zm9-1.29a2.34,2.34,0,0,0,1.37-1.86,29.55,29.55,0,0,0,.34-5.5,29.7,29.7,0,0,0-.34-5.5,2.32,2.32,0,0,0-1.37-1.85,13.69,13.69,0,0,0-4-.38,13.73,13.73,0,0,0-4,.38,2.32,2.32,0,0,0-1.37,1.85,28.82,28.82,0,0,0-.34,5.5,28.68,28.68,0,0,0,.34,5.5A2.34,2.34,0,0,0,63,133a13.73,13.73,0,0,0,4,.38A13.69,13.69,0,0,0,70.94,133Z"/><path class="cls-5" d="M82.89,134.91c-1.17-.07-2.18-.15-3-.25v-1.59c.91.08,1.93.16,3,.23s2,.11,2.76.11a17.72,17.72,0,0,0,2.32-.12,4,4,0,0,0,1.49-.49,1.67,1.67,0,0,0,.81-1.07,7.68,7.68,0,0,0,.2-1.92,9.6,9.6,0,0,0-.15-2.07,1.14,1.14,0,0,0-.71-.83,7.63,7.63,0,0,0-2-.42L83,126a4.91,4.91,0,0,1-2.42-.74,2.61,2.61,0,0,1-1-1.47,10.4,10.4,0,0,1-.23-2.45,5.81,5.81,0,0,1,.71-3.17,3.47,3.47,0,0,1,2-1.44,14.24,14.24,0,0,1,3.66-.37c1,0,2,0,3.09.1a19.49,19.49,0,0,1,2.44.29v1.51c-.7-.08-1.55-.16-2.54-.22s-1.9-.09-2.73-.09a15.54,15.54,0,0,0-2.52.16,4.18,4.18,0,0,0-1.51.5,1.74,1.74,0,0,0-.76,1,6,6,0,0,0-.19,1.72,6.83,6.83,0,0,0,.17,1.76,1.32,1.32,0,0,0,.67.82,4.9,4.9,0,0,0,1.57.39l4.51.47a10.3,10.3,0,0,1,2.69.56,2.23,2.23,0,0,1,1.25,1.43,9.9,9.9,0,0,1,.3,2.91,6.84,6.84,0,0,1-.72,3.54,3.17,3.17,0,0,1-2,1.47,14.17,14.17,0,0,1-3.38.32C85.14,135,84.05,135,82.89,134.91Z"/><path class="cls-5" d="M47.08,49.62c1.17-.36,8.61.83,9.61,1.53,3.13,2.21,7.71,3.41,9.55,1.15s-.08-6.13-3-9.1c-.88-.88-3.7-7.75-3.56-9,.42-3.87-.85-8.5-3.73-9s-5.61,3.66-6.3,7.16c-.24,1.22-5,7.11-6.14,7.62-3.48,1.62-6.77,5-5.73,7.69S43.7,50.65,47.08,49.62Z"/><path class="cls-5" d="M60,9.61c4.68.2,6.65,9.72,5.89,17.18-.24,2.37,5.27,15.71,6.24,17.91a7.29,7.29,0,0,0,3,3C71.63,38.07,71.32,30.32,72,28.51c3-8.4,1.71-19.69-2.85-22.32-3.07-1.77-9.79-2.48-14.07,4.56A6.91,6.91,0,0,1,60,9.61Z"/><path class="cls-5" d="M22.61,50.2C20.Line truncated
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/mstile-150x150.png"/>
|
||||
<TileColor>#da532c</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
|
After Width: | Height: | Size: 502 B |
|
After Width: | Height: | Size: 758 B |
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,9 @@
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="manifest" href="/site.webmanifest">
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#e3493c">
|
||||
<meta name="apple-mobile-web-app-title" content="Advanced Algos">
|
||||
<meta name="application-name" content="Advanced Algos">
|
||||
<meta name="msapplication-TileColor" content="#da532c">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg version="1" xmlns="http://www.w3.org/2000/svg" width="1365.333" height="1365.333" viewBox="0 0 1024.000000 1024.000000"><path d="M685.9 27.4c-.2.2-3 .7-6.1 1-12.6 1.5-34.6 8.5-45.2 14.5-2 1.2-5.3 3.1-7.3 4.2-14.3 8.1-30.2 22.3-43.5 38.8-5.9 7.4-16.2 22.1-15.5 22.1.2 0 3.6-1.7 7.5-3.9 17.4-9.6 28.8-12.1 52.2-11.7 13.4.3 17.7.8 23.1 2.5 25.1 8 45.2 36.6 58.9 83.7 1.8 6 3.3 11.6 3.5 12.4 2.3 10.8 3.5 17.1 4 20 .3 1.9 1 6 1.5 9 1.2 7.3 1.8 11.3 2.5 18.5.4 3.3.8 7.6 1.1 9.5 1.6 14.3 2.2 51.4 1 69.5-1.4 21.3-1.5 26-.5 31 .5 2.7 1.2 6.3 1.5 8 2.3 12.9 20.6 69 33.4 102.5 1.6 4.1 4.8 12.4 7.1 18.5 17.1 45.2 47.3 118.6 49.1 119.3.4.2.8.9.8 1.5.1 3.8 18.3 22 29.2 29.1 4.7 3.1 8.8 5.6 9 5.6.3 0-3.2-10.9-7.7-24.3-4.5-13.3-8.4-25.3-8.7-26.7-.4-1.4-2-7.2-3.7-13-4.3-14.5-12.2-45.3-13.7-54-.3-1.4-.7-3.3-1-4.2-.3-1-.7-3-1-4.5-.2-1.6-.9-4.8-1.4-7.3-.5-2.5-1.4-7.2-2-10.5-.5-3.3-1.2-7.1-1.5-8.5-.3-1.4-1-5.7-1.5-9.5-.6-3.9-1.2-8.1-1.5-9.5-.3-2.3-.7-5.4-2-16.3-3.1-25.8-3.8-58.6-1.5-73.2.7-4.4 2.2-10.7 3.5-14 2.3-6.1 9-28.9 9.9-33.5.3-1.4 1.2-5.2 2-8.5s1.7-7.4 1.9-9c.3-1.7 1.4-8.2 2.6-14.6 1.1-6.3 2.3-14.7 2.6-18.5.4-3.8.9-8.3 1.1-9.9 2.2-16.2 2.2-69.4 0-83.5-.2-1.4-.7-5-1.1-8-6.7-56.6-26.4-102.4-53.1-123.1-11.1-8.5-31-16.4-49.9-19.7-9.6-1.7-38.4-2.9-39.6-1.8zM465 215.4c-2.3 3-9.7 11.7-14.5 17-1.1 1.2-5.6 6.2-10 11.1-8.3 9.3-43.9 45.2-53.5 54-51.1 46.9-108.8 86-132.5 90-2.2.4-4.4.8-5 .9-.5.2-2.8.6-5 1.1-24.5 4.7-70 20.4-91 31.3-2.7 1.4-5.2 2.6-5.5 2.7-.7.1-12.3 6.2-16.7 8.7-2.1 1.2-5.4 3.1-7.5 4.2C62.4 471.4 16.3 519 4.3 559.8.9 571.3.5 590 3.4 603.5c2.6 12.1 7.2 25.6 11.8 35 19.6 39.1 52.7 65.8 95.3 76.8 5.5 1.4 12 2.9 14.5 3.2 2.5.4 5.4.9 6.5 1.1 2.8.5 11.7 1.4 19.5 1.9l6.4.4-3.4-2.1c-20.8-12.5-29.7-21.3-39-38.3-1.8-3.3-4.3-7.8-5.5-10-6.1-11.2-8.2-22.8-6.4-35.1 7.4-48 72.3-111.2 159.4-155 10.5-5.3 21-11.2 23.5-13.1 8.7-6.7 26.9-25.3 45.4-46.3 1.7-2 4.9-5.6 7.1-8.1 2.2-2.4 6-6.9 8.5-9.8 2.5-3 5-5.9 5.5-6.5.6-.6 3-3.6 5.5-6.6 6.5-8 7.1-8.6 15.2-18.5 12.7-15.4 51.5-64.5 61.3-77.6 1.1-1.5 5.9-7.8 10.6-14 11.5-15.2 13.3-18.1 16.8-26.9 3-7.7 3.5-9.3 5.1-19 1.2-7.1 1.9-21.9 1.1-22.4-.3-.2-1.7 1.1-3.1 2.8zM563.9 315c-21.5 5.4-43.9 28.6-59.4 61.3-5.2 11-10.1 24.9-12.5 35.7-2 8.8-6.1 16.2-21 37.4-1.8 2.6-17.8 23.2-20.7 26.6-14.7 17.6-22.4 26.2-31.2 35.1-8.7 8.6-12.2 11.3-19.6 15.2-16.7 8.7-25.6 14.5-37 23.8-22.2 18.1-36.8 38.2-41 56.4-1.6 6.6-2.2 17.6-1.1 19.3.3.5.7 2.1 1 3.7.8 4.3 5.3 12.7 9.1 16.8 10.1 11.1 27.7 18.5 50.5 21.3 12.2 1.5 36.1.8 49-1.6 11.6-2 12.6-2.3 20.5-4.5s31.7-2.2 43.5 0c.8.2 4.4.6 8 1 3.6.4 7.4.9 8.4 1.1 1.1.2 4.2.6 6.9.9 2.8.3 5.3.8 5.6.9.3.2 2.9.7 5.6 1 9.1 1.3 39 7.5 47.9 10.1 8.9 2.6 18.5 7 20.6 9.6 1.9 2.3 30.1 16.5 39.5 20 9.1 3.3 26.6 7.7 33.7 8.6 9.2 1.1 23.9.7 30-.7 19.5-4.6 33.3-18.5 36.3-36.7.7-4.2.8-19 .1-20.1-.2-.4-.7-2.4-1-4.6-1-6.1-5.5-18.3-10.3-28.1-4.2-8.3-15.4-26.1-20.1-31.6-5.8-7-14.5-17-18.6-21.4-2.6-2.8-6.5-8-8.7-11.7-6.3-10.8-17.6-35.1-25.4-55.3-1.5-3.9-3.3-8.4-4-10-1.4-3.5-4.9-12.8-5-13.5 0-.3-1.2-3.7-2.5-7.5-1.3-3.9-2.5-7.5-2.6-8-.1-.6-.9-3.5-1.8-6.6-2.6-8.9-2.8-10-2.6-17.4.7-25.5.3-39.3-1.3-49.5-.3-1.4-.8-4.5-1.2-7-.3-2.4-.8-4.7-1-5.1-.2-.3-.7-2.1-1-4-.9-5.4-6.3-20.6-9.7-27.3-7.5-14.9-16.6-24.9-27.5-30.3-6.3-3.1-8.3-3.6-16-3.9-4.8-.2-10.4.1-12.4.6zM340 752.1c-12.3 2.1-18.9 4.4-31.5 10.7-10.5 5.3-11.8 4.4 13 9.2 69 13.5 135.5 34.9 186.8 60.2 24.5 12.1 41 22.8 49.7 32.3 24.3 26.6 37.9 39.4 60 56.6 50.3 39.2 116.2 69.8 162.5 75.5 2.8.3 6.8.9 9 1.3 4.8.9 28.3.6 34-.3 23.2-4 37.1-11.8 56.6-31.7 33-33.8 46.8-81.2 37.4-128.4-3.2-16-8-29.9-16.2-46.5l-4.2-8.5-.1 8c-.1 4.4-.3 8.7-.4 9.5-.2.8-.6 3.8-1 6.6-1.8 14.4-15.1 40.8-26.4 52.7-17.8 18.7-53.6 23.1-99.2 12.2-5.8-1.4-11.5-2.7-12.6-3-1.2-.3-7.7-2.3-14.5-4.6-36.3-12-78-32.8-111.4-55.7-6-4.1-13-8.4-15.5-9.4-2.5-1.1-5.4-2.3-6.5-2.8-5.6-2.4-39.7-11.2-52.5-13.5-1.9-.3-4.2-.7-5-.9-.8-.1-4-.8-7-1.4-9.8-2-26.4-5.1-30-5.6-3-.5-29.1-4.8-37-6.1-1.4-.2-4.3-.7-6.5-1-6.9-1.1-9.8-1.5-15.5-2.5-3-.5-8.2-1.2-11.5-1.6-3.3-.3-6.2-.7-6.5-.9-.3-.2-4.4-.9-9-1.5-4.7-.6-9.6-1.3-11-1.5-1.4-.2-6.3-.9-11-1.5s-9.6-1.3-11-1.5c-5.9-.8-10.8-1.5-15-2-5.1-.6-9.4-1.2-16-2.1-7.1-.9-20.1-1.1-25-.3z"/></svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "Advanced Algos",
|
||||
"short_name": "Advanced Algos",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360 96"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#e3493c;}</style></defs><title>Superalgos-logo-horz-dark</title><g id="Layer_4" data-name="Layer 4"><path class="cls-1" d="M105.33,66.86a30.85,30.85,0,0,1-3.58-.6V61.84a49.59,49.59,0,0,0,5.92.42,11.06,11.06,0,0,0,2.47-.21,2.63,2.63,0,0,0,1.37-.78,3.4,3.4,0,0,0,.65-1.64,17.68,17.68,0,0,0,.18-2.83,24.15,24.15,0,0,0-.18-3.61,2.16,2.16,0,0,0-.8-1.45,6.77,6.77,0,0,0-2.24-.81l-4.41-.88a3.53,3.53,0,0,1-2.78-2.62,23,23,0,0,1-.7-6.57,16.26,16.26,0,0,1,1-6.24,6,6,0,0,1,2.86-3.17,11.16,11.16,0,0,1,4.91-.93,25.48,25.48,0,0,1,4,.28c1.19.2,2,.36,2.57.5v4.46a17,17,0,0,0-2.08-.28c-1-.09-2-.13-3-.13a11.47,11.47,0,0,0-2.78.26,2.19,2.19,0,0,0-1.37.93,3.69,3.69,0,0,0-.63,1.59,17.44,17.44,0,0,0-.15,2.57,20.72,20.72,0,0,0,.15,3,2.29,2.29,0,0,0,.6,1.3,3.23,3.23,0,0,0,1.48.59l3.9.83a7.33,7.33,0,0,1,2.78,1.15,4.9,4.9,0,0,1,1.63,2.59,19.4,19.4,0,0,1,.57,5.77,21.38,21.38,0,0,1-.85,6.83,5.84,5.84,0,0,1-2.68,3.38,10.64,10.64,0,0,1-4.93,1A36.1,36.1,0,0,1,105.33,66.86Z"/><path class="cls-1" d="M143.91,30.93V51a72.1,72.1,0,0,1-.5,10.12,6.77,6.77,0,0,1-2.31,4.52q-1.82,1.41-6,1.41t-6-1.41a6.68,6.68,0,0,1-2.29-4.49A77.15,77.15,0,0,1,126.3,51V30.93h5.3V53.27c0,2.91.06,4.93.18,6.08a3.46,3.46,0,0,0,.88,2.31,3.76,3.76,0,0,0,2.42.6,3.83,3.83,0,0,0,2.46-.6,3.51,3.51,0,0,0,.89-2.31c.12-1.15.18-3.17.18-6.08V30.93Z"/><path class="cls-1" d="M153.51,30.93h9.19a8.12,8.12,0,0,1,5.15,1.38,6.6,6.6,0,0,1,2.26,3.82,31.56,31.56,0,0,1,.54,6.54,31.4,31.4,0,0,1-.52,6.39,6.7,6.7,0,0,1-2.26,3.9,7.89,7.89,0,0,1-5.17,1.45h-3.89V66.67h-5.3Zm8.36,18.71a3.57,3.57,0,0,0,2.32-.6,3.32,3.32,0,0,0,.93-2,28.88,28.88,0,0,0,.23-4.37,28.19,28.19,0,0,0-.23-4.33,3.29,3.29,0,0,0-.93-2,3.63,3.63,0,0,0-2.32-.6h-3.06V49.64Z"/><path class="cls-1" d="M179.28,66.67V30.93h14.07v4.83h-8.73V46.21h7.64V51h-7.64v10.8h8.73v4.83Z"/><path class="cls-1" d="M214.78,66.67l-3.27-13.29h-4V66.67h-5.29V30.93h9.4a8.5,8.5,0,0,1,5.14,1.3A6.17,6.17,0,0,1,219,35.89a29.17,29.17,0,0,1,.55,6.27,26.68,26.68,0,0,1-.63,6.44,5.71,5.71,0,0,1-2.59,3.63l3.89,14.44Zm-1.69-18.54a3,3,0,0,0,.93-1.87,24.61,24.61,0,0,0,.24-4,24.07,24.07,0,0,0-.24-4,2.87,2.87,0,0,0-1-1.87,3.67,3.67,0,0,0-2.28-.58H207.5V48.7h3.28A3.69,3.69,0,0,0,213.09,48.13Z"/><path class="cls-2" d="M243.19,66.67l-1.51-8.57h-7l-1.46,8.57H228l6.44-35.74h7.68l6.45,35.74Zm-4.84-29.92H238l-2.75,16.63h5.82Z"/><path class="cls-2" d="M257,66.67V30.93h5.35V61.79h7.84v4.88Z"/><path class="cls-2" d="M286.87,51.4V46.73H295V66.05a22.89,22.89,0,0,1-3.64.75,30.25,30.25,0,0,1-4,.29q-4.05,0-6-1.43t-2.76-5.25a63.23,63.23,0,0,1-.77-11.61,60.57,60.57,0,0,1,.8-11.58q.81-3.8,2.88-5.25c1.39-1,3.5-1.45,6.34-1.45a26,26,0,0,1,3.66.28,27.48,27.48,0,0,1,3.46.71V36c-1-.17-2.05-.32-3.12-.44a23.88,23.88,0,0,0-2.44-.18,9.28,9.28,0,0,0-4.24.68c-.77.44-1.32,1.56-1.63,3.35A63.63,63.63,0,0,0,283,48.8q0,6.35.29,9a6.46,6.46,0,0,0,1.17,3.64,3.93,3.93,0,0,0,3,.93,10.23,10.23,0,0,0,2.7-.31V51.4Z"/><path class="cls-2" d="M306.66,65.58q-1.91-1.5-2.39-5a95.6,95.6,0,0,1-.5-11.74,95.52,95.52,0,0,1,.5-11.73,7.43,7.43,0,0,1,2.39-5q1.89-1.5,6.26-1.5t6.31,1.5a7.52,7.52,0,0,1,2.39,5,98.19,98.19,0,0,1,.49,11.73,98.27,98.27,0,0,1-.49,11.74q-.5,3.54-2.39,5t-6.31,1.51Q308.55,67.09,306.66,65.58Zm8.9-4c.49-.45.82-1.56,1-3.32s.26-4.92.26-9.46-.09-7.69-.26-9.45-.5-2.88-1-3.32a3.9,3.9,0,0,0-2.64-.68,3.83,3.83,0,0,0-2.63.68c-.47.44-.79,1.55-1,3.32s-.26,4.92-.26,9.45.09,7.69.26,9.46.49,2.87,1,3.32a3.83,3.83,0,0,0,2.63.68A3.9,3.9,0,0,0,315.56,61.58Z"/><path class="cls-2" d="M334.69,66.86a30.48,30.48,0,0,1-3.58-.6V61.84a49.59,49.59,0,0,0,5.92.42,11,11,0,0,0,2.46-.21,2.66,2.66,0,0,0,1.38-.78,3.5,3.5,0,0,0,.65-1.64,17.68,17.68,0,0,0,.18-2.83,24.15,24.15,0,0,0-.18-3.61,2.13,2.13,0,0,0-.81-1.45,6.69,6.69,0,0,0-2.23-.81l-4.41-.88a3.51,3.51,0,0,1-2.78-2.62,23,23,0,0,1-.7-6.57,16.26,16.26,0,0,1,1-6.24,6,6,0,0,1,2.85-3.17,11.19,11.19,0,0,1,4.91-.93,25.52,25.52,0,0,1,4,.28c1.19.2,2,.36,2.57.5v4.46a17,17,0,0,0-2.08-.28c-1-.09-2-.13-3-.13a11.47,11.47,0,0,0-2.78.26,2.24,2.24,0,0,0-1.38.93,3.56,3.56,0,0,0-.62,1.59,17.23,17.23,0,0,0-.16,2.57,20.35,20.35,0,0,0,.16,3,2.17,2.17,0,0,0,.6,1.3,3.19,3.19,0,0,0,1.48.59l3.89.83a7.26,7.26,0,0,1,2.78,1.15,4.91,4.91,0,0,1,1.64,2.59,19.4,19.4,0,0,1,.57,5.77,21.33,21.33,0,0,1-.86,6.83,5.79,5.79,0,0,1-2.67,3.38,10.66,10.66,0,0,1-4.94,1A35.92,35.92,0,0,1,334.69,66.86Z"/><path class="cls-2" d="M46.72,59c1.23-.38,9.08.88,10.13,1.62,3.3,2.33,8.14,3.58,10.08,1.2s-.09-6.47-3.19-9.59c-.92-.94-3.89-8.18-3.75-9.47.44-4.09-.9-9-3.94-9.46s-5.91,3.86-6.64,7.56c-.25,1.29-5.32,7.5-6.48,8-3.67,1.7-7.14,5.24-6,8.11S43.15,60.05,46.72,59Z"/><path class="cls-2" d="M60.38,16.76c4.93.21,7,10.25,6.21,18.12-.25,2.5,5.56,16.57,6.58,18.88A7.82,7.82,0,0,0,76.37,57C72.61,46.77,72.28,38.6,73,36.69c3.21-8.85,1.8-20.77-3-23.53-3.25-1.87-10.33-2.62-14.85,4.8A7.33,7.33,0,0,1,60.38,16.76Z"/><path class="cls-2" d="M20.91,59.57c-2.28-4.39,5.38-11.2,12.6-14.45C35.8,44.09,45.07,32,46.57,30a7.77,7.77,0,0,0,1.17-4.37c-7,8.35-13.86,Line truncated
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360 96"><defs><style>.cls-1{fill:#4d4d4d;}.cls-2{fill:#e3493c;}</style></defs><title>Superalgos-logo-horz</title><g id="Layer_4" data-name="Layer 4"><path class="cls-1" d="M105.33,66.86a30.85,30.85,0,0,1-3.58-.6V61.84a49.59,49.59,0,0,0,5.92.42,11.06,11.06,0,0,0,2.47-.21,2.63,2.63,0,0,0,1.37-.78,3.4,3.4,0,0,0,.65-1.64,17.68,17.68,0,0,0,.18-2.83,24.15,24.15,0,0,0-.18-3.61,2.16,2.16,0,0,0-.8-1.45,6.77,6.77,0,0,0-2.24-.81l-4.41-.88a3.53,3.53,0,0,1-2.78-2.62,23,23,0,0,1-.7-6.57,16.26,16.26,0,0,1,1-6.24,6,6,0,0,1,2.86-3.17,11.16,11.16,0,0,1,4.91-.93,25.48,25.48,0,0,1,4,.28c1.19.2,2,.36,2.57.5v4.46a17,17,0,0,0-2.08-.28c-1-.09-2-.13-3-.13a11.47,11.47,0,0,0-2.78.26,2.19,2.19,0,0,0-1.37.93,3.69,3.69,0,0,0-.63,1.59,17.44,17.44,0,0,0-.15,2.57,20.72,20.72,0,0,0,.15,3,2.29,2.29,0,0,0,.6,1.3,3.23,3.23,0,0,0,1.48.59l3.9.83a7.33,7.33,0,0,1,2.78,1.15,4.9,4.9,0,0,1,1.63,2.59,19.4,19.4,0,0,1,.57,5.77,21.38,21.38,0,0,1-.85,6.83,5.84,5.84,0,0,1-2.68,3.38,10.64,10.64,0,0,1-4.93,1A36.1,36.1,0,0,1,105.33,66.86Z"/><path class="cls-1" d="M143.91,30.93V51a72.1,72.1,0,0,1-.5,10.12,6.77,6.77,0,0,1-2.31,4.52q-1.82,1.41-6,1.41t-6-1.41a6.68,6.68,0,0,1-2.29-4.49A77.15,77.15,0,0,1,126.3,51V30.93h5.3V53.27c0,2.91.06,4.93.18,6.08a3.46,3.46,0,0,0,.88,2.31,3.76,3.76,0,0,0,2.42.6,3.83,3.83,0,0,0,2.46-.6,3.51,3.51,0,0,0,.89-2.31c.12-1.15.18-3.17.18-6.08V30.93Z"/><path class="cls-1" d="M153.51,30.93h9.19a8.12,8.12,0,0,1,5.15,1.38,6.6,6.6,0,0,1,2.26,3.82,31.56,31.56,0,0,1,.54,6.54,31.4,31.4,0,0,1-.52,6.39,6.7,6.7,0,0,1-2.26,3.9,7.89,7.89,0,0,1-5.17,1.45h-3.89V66.67h-5.3Zm8.36,18.71a3.57,3.57,0,0,0,2.32-.6,3.32,3.32,0,0,0,.93-2,28.88,28.88,0,0,0,.23-4.37,28.19,28.19,0,0,0-.23-4.33,3.29,3.29,0,0,0-.93-2,3.63,3.63,0,0,0-2.32-.6h-3.06V49.64Z"/><path class="cls-1" d="M179.28,66.67V30.93h14.07v4.83h-8.73V46.21h7.64V51h-7.64v10.8h8.73v4.83Z"/><path class="cls-1" d="M214.78,66.67l-3.27-13.29h-4V66.67h-5.29V30.93h9.4a8.5,8.5,0,0,1,5.14,1.3A6.17,6.17,0,0,1,219,35.89a29.17,29.17,0,0,1,.55,6.27,26.68,26.68,0,0,1-.63,6.44,5.71,5.71,0,0,1-2.59,3.63l3.89,14.44Zm-1.69-18.54a3,3,0,0,0,.93-1.87,24.61,24.61,0,0,0,.24-4,24.07,24.07,0,0,0-.24-4,2.87,2.87,0,0,0-1-1.87,3.67,3.67,0,0,0-2.28-.58H207.5V48.7h3.28A3.69,3.69,0,0,0,213.09,48.13Z"/><path class="cls-2" d="M243.19,66.67l-1.51-8.57h-7l-1.46,8.57H228l6.44-35.74h7.68l6.45,35.74Zm-4.84-29.92H238l-2.75,16.63h5.82Z"/><path class="cls-2" d="M257,66.67V30.93h5.35V61.79h7.84v4.88Z"/><path class="cls-2" d="M286.87,51.4V46.73H295V66.05a22.89,22.89,0,0,1-3.64.75,30.25,30.25,0,0,1-4,.29q-4.05,0-6-1.43t-2.76-5.25a63.23,63.23,0,0,1-.77-11.61,60.57,60.57,0,0,1,.8-11.58q.81-3.8,2.88-5.25c1.39-1,3.5-1.45,6.34-1.45a26,26,0,0,1,3.66.28,27.48,27.48,0,0,1,3.46.71V36c-1-.17-2.05-.32-3.12-.44a23.88,23.88,0,0,0-2.44-.18,9.28,9.28,0,0,0-4.24.68c-.77.44-1.32,1.56-1.63,3.35A63.63,63.63,0,0,0,283,48.8q0,6.35.29,9a6.46,6.46,0,0,0,1.17,3.64,3.93,3.93,0,0,0,3,.93,10.23,10.23,0,0,0,2.7-.31V51.4Z"/><path class="cls-2" d="M306.66,65.58q-1.91-1.5-2.39-5a95.6,95.6,0,0,1-.5-11.74,95.52,95.52,0,0,1,.5-11.73,7.43,7.43,0,0,1,2.39-5q1.89-1.5,6.26-1.5t6.31,1.5a7.52,7.52,0,0,1,2.39,5,98.19,98.19,0,0,1,.49,11.73,98.27,98.27,0,0,1-.49,11.74q-.5,3.54-2.39,5t-6.31,1.51Q308.55,67.09,306.66,65.58Zm8.9-4c.49-.45.82-1.56,1-3.32s.26-4.92.26-9.46-.09-7.69-.26-9.45-.5-2.88-1-3.32a3.9,3.9,0,0,0-2.64-.68,3.83,3.83,0,0,0-2.63.68c-.47.44-.79,1.55-1,3.32s-.26,4.92-.26,9.45.09,7.69.26,9.46.49,2.87,1,3.32a3.83,3.83,0,0,0,2.63.68A3.9,3.9,0,0,0,315.56,61.58Z"/><path class="cls-2" d="M334.69,66.86a30.48,30.48,0,0,1-3.58-.6V61.84a49.59,49.59,0,0,0,5.92.42,11,11,0,0,0,2.46-.21,2.66,2.66,0,0,0,1.38-.78,3.5,3.5,0,0,0,.65-1.64,17.68,17.68,0,0,0,.18-2.83,24.15,24.15,0,0,0-.18-3.61,2.13,2.13,0,0,0-.81-1.45,6.69,6.69,0,0,0-2.23-.81l-4.41-.88a3.51,3.51,0,0,1-2.78-2.62,23,23,0,0,1-.7-6.57,16.26,16.26,0,0,1,1-6.24,6,6,0,0,1,2.85-3.17,11.19,11.19,0,0,1,4.91-.93,25.52,25.52,0,0,1,4,.28c1.19.2,2,.36,2.57.5v4.46a17,17,0,0,0-2.08-.28c-1-.09-2-.13-3-.13a11.47,11.47,0,0,0-2.78.26,2.24,2.24,0,0,0-1.38.93,3.56,3.56,0,0,0-.62,1.59,17.23,17.23,0,0,0-.16,2.57,20.35,20.35,0,0,0,.16,3,2.17,2.17,0,0,0,.6,1.3,3.19,3.19,0,0,0,1.48.59l3.89.83a7.26,7.26,0,0,1,2.78,1.15,4.91,4.91,0,0,1,1.64,2.59,19.4,19.4,0,0,1,.57,5.77,21.33,21.33,0,0,1-.86,6.83,5.79,5.79,0,0,1-2.67,3.38,10.66,10.66,0,0,1-4.94,1A35.92,35.92,0,0,1,334.69,66.86Z"/><path class="cls-2" d="M46.72,59c1.23-.38,9.08.88,10.13,1.62,3.3,2.33,8.14,3.58,10.08,1.2s-.09-6.47-3.19-9.59c-.92-.94-3.89-8.18-3.75-9.47.44-4.09-.9-9-3.94-9.46s-5.91,3.86-6.64,7.56c-.25,1.29-5.32,7.5-6.48,8-3.67,1.7-7.14,5.24-6,8.11S43.15,60.05,46.72,59Z"/><path class="cls-2" d="M60.38,16.76c4.93.21,7,10.25,6.21,18.12-.25,2.5,5.56,16.57,6.58,18.88A7.82,7.82,0,0,0,76.37,57C72.61,46.77,72.28,38.6,73,36.69c3.21-8.85,1.8-20.77-3-23.53-3.25-1.87-10.33-2.62-14.85,4.8A7.33,7.33,0,0,1,60.38,16.76Z"/><path class="cls-2" d="M20.91,59.57c-2.28-4.39,5.38-11.2,12.6-14.45C35.8,44.09,45.07,32,46.57,30a7.77,7.77,0,0,0,1.17-4.37c-7,8.35-13.86,12Line truncated
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 216"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#e3493c;}</style></defs><title>Superalgos-logo-vert-dark</title><g id="Layer_4" data-name="Layer 4"><path class="cls-1" d="M13.23,207.58A28.44,28.44,0,0,1,10,207v-4a44.92,44.92,0,0,0,5.33.37,10.47,10.47,0,0,0,2.22-.18,2.42,2.42,0,0,0,1.23-.7,3.21,3.21,0,0,0,.59-1.48,15.56,15.56,0,0,0,.16-2.54,21.51,21.51,0,0,0-.16-3.25,1.91,1.91,0,0,0-.73-1.3,5.88,5.88,0,0,0-2-.73l-4-.79a3.17,3.17,0,0,1-2.5-2.36,20.78,20.78,0,0,1-.63-5.91,14.52,14.52,0,0,1,.86-5.6A5.4,5.4,0,0,1,13,175.75a10,10,0,0,1,4.41-.84,22.82,22.82,0,0,1,3.57.26c1.08.17,1.85.32,2.32.44v4a14.8,14.8,0,0,0-1.87-.26c-.88-.08-1.76-.12-2.66-.12a9.88,9.88,0,0,0-2.5.24,2,2,0,0,0-1.24.84,3.14,3.14,0,0,0-.56,1.42,15.61,15.61,0,0,0-.14,2.31,18.37,18.37,0,0,0,.14,2.69,1.94,1.94,0,0,0,.54,1.17,2.83,2.83,0,0,0,1.33.53l3.5.75a6.46,6.46,0,0,1,2.5,1,4.4,4.4,0,0,1,1.47,2.33,17.51,17.51,0,0,1,.51,5.18,19,19,0,0,1-.77,6.15,5.21,5.21,0,0,1-2.4,3,9.52,9.52,0,0,1-4.44.86A33.32,33.32,0,0,1,13.23,207.58Z"/><path class="cls-1" d="M47.9,175.29v18.07a66.38,66.38,0,0,1-.44,9.1,6.09,6.09,0,0,1-2.08,4.06q-1.64,1.26-5.42,1.26t-5.41-1.26a6,6,0,0,1-2.06-4,69.05,69.05,0,0,1-.42-9.13V175.29h4.76v20.07c0,2.62.06,4.44.17,5.47a3.11,3.11,0,0,0,.79,2.07,3.38,3.38,0,0,0,2.17.54,3.46,3.46,0,0,0,2.22-.54,3.11,3.11,0,0,0,.79-2.07c.11-1,.17-2.85.17-5.47V175.29Z"/><path class="cls-1" d="M56.53,175.29H64.8a7.32,7.32,0,0,1,4.62,1.23,5.85,5.85,0,0,1,2,3.43,28,28,0,0,1,.49,5.89,28.72,28.72,0,0,1-.46,5.74,6,6,0,0,1-2,3.5,7,7,0,0,1-4.64,1.31H61.3v11H56.53Zm7.52,16.81a3.2,3.2,0,0,0,2.08-.54,2.93,2.93,0,0,0,.84-1.8,26.3,26.3,0,0,0,.21-3.92,25.81,25.81,0,0,0-.21-3.9,2.86,2.86,0,0,0-.84-1.77,3.2,3.2,0,0,0-2.08-.54H61.3V192.1Z"/><path class="cls-1" d="M79.69,207.41V175.29H92.35v4.34H84.5V189h6.87v4.35H84.5v9.71h7.85v4.34Z"/><path class="cls-1" d="M111.61,207.41l-2.94-11.95h-3.6v11.95h-4.76V175.29h8.45a7.6,7.6,0,0,1,4.62,1.16,5.5,5.5,0,0,1,2,3.29,25.84,25.84,0,0,1,.49,5.63,23.83,23.83,0,0,1-.56,5.79,5.11,5.11,0,0,1-2.33,3.27l3.5,13Zm-1.52-16.67a2.69,2.69,0,0,0,.84-1.68,22.64,22.64,0,0,0,.21-3.64,22.33,22.33,0,0,0-.21-3.6,2.7,2.7,0,0,0-.86-1.68,3.35,3.35,0,0,0-2.06-.51h-2.94v11.63H108A3.25,3.25,0,0,0,110.09,190.74Z"/><path class="cls-2" d="M137.14,207.41l-1.35-7.7h-6.26l-1.31,7.7h-4.76l5.79-32.12h6.91L142,207.41Zm-4.34-26.9h-.28l-2.47,15h5.23Z"/><path class="cls-2" d="M149.53,207.41V175.29h4.8V203h7.06v4.39Z"/><path class="cls-2" d="M176.41,193.68v-4.2h7.28v17.37a20,20,0,0,1-3.27.68,27.12,27.12,0,0,1-3.59.25,9.24,9.24,0,0,1-5.42-1.28c-1.18-.86-2-2.43-2.47-4.72a56.38,56.38,0,0,1-.7-10.43,54.83,54.83,0,0,1,.72-10.41c.48-2.28,1.35-3.85,2.59-4.72a10,10,0,0,1,5.7-1.31,23.71,23.71,0,0,1,3.29.26,23,23,0,0,1,3.1.63v4c-.9-.15-1.83-.28-2.8-.39a21.18,21.18,0,0,0-2.19-.17,8.38,8.38,0,0,0-3.81.61c-.7.41-1.19,1.41-1.47,3a57.35,57.35,0,0,0-.42,8.48c0,3.8.09,6.5.26,8.12a5.8,5.8,0,0,0,1,3.27,3.55,3.55,0,0,0,2.66.84,9.17,9.17,0,0,0,2.43-.28v-9.62Z"/><path class="cls-2" d="M194.2,206.43a6.66,6.66,0,0,1-2.15-4.53,85.84,85.84,0,0,1-.44-10.55,85.73,85.73,0,0,1,.44-10.55,6.66,6.66,0,0,1,2.15-4.53c1.13-.91,3-1.36,5.62-1.36s4.54.45,5.68,1.36a6.75,6.75,0,0,1,2.15,4.53,87.83,87.83,0,0,1,.44,10.55,88,88,0,0,1-.44,10.55,6.75,6.75,0,0,1-2.15,4.53q-1.71,1.35-5.68,1.35C197.21,207.78,195.33,207.33,194.2,206.43Zm8-3.59a5.19,5.19,0,0,0,.88-3q.24-2.39.24-8.5t-.24-8.5a5.16,5.16,0,0,0-.88-3,3.58,3.58,0,0,0-2.39-.61,3.44,3.44,0,0,0-2.35.61,5.34,5.34,0,0,0-.87,3q-.22,2.39-.23,8.5t.23,8.5a5.37,5.37,0,0,0,.87,3,3.44,3.44,0,0,0,2.35.6A3.59,3.59,0,0,0,202.21,202.84Z"/><path class="cls-2" d="M219.4,207.58a27.86,27.86,0,0,1-3.22-.54v-4a44.63,44.63,0,0,0,5.32.37,10.47,10.47,0,0,0,2.22-.18,2.42,2.42,0,0,0,1.23-.7,3.21,3.21,0,0,0,.59-1.48,15.56,15.56,0,0,0,.16-2.54,21.51,21.51,0,0,0-.16-3.25,1.91,1.91,0,0,0-.73-1.3,5.89,5.89,0,0,0-2-.73l-4-.79a3.17,3.17,0,0,1-2.5-2.36,20.78,20.78,0,0,1-.63-5.91,14.71,14.71,0,0,1,.86-5.6,5.4,5.4,0,0,1,2.57-2.85,10,10,0,0,1,4.41-.84,23.05,23.05,0,0,1,3.58.26c1.07.17,1.84.32,2.31.44v4a14.8,14.8,0,0,0-1.87-.26c-.87-.08-1.76-.12-2.66-.12a9.88,9.88,0,0,0-2.5.24,2,2,0,0,0-1.24.84,3.14,3.14,0,0,0-.56,1.42,15.61,15.61,0,0,0-.14,2.31,18.37,18.37,0,0,0,.14,2.69,1.94,1.94,0,0,0,.54,1.17,2.88,2.88,0,0,0,1.33.53l3.5.75a6.46,6.46,0,0,1,2.5,1,4.4,4.4,0,0,1,1.47,2.33,17.51,17.51,0,0,1,.51,5.18,19,19,0,0,1-.77,6.15,5.21,5.21,0,0,1-2.4,3,9.52,9.52,0,0,1-4.44.86A33.32,33.32,0,0,1,219.4,207.58Z"/><path class="cls-2" d="M115.74,91.81c2.19-.67,16.14,1.56,18,2.88,5.86,4.13,14.45,6.37,17.9,2.14s-.16-11.49-5.67-17c-1.64-1.65-6.92-14.51-6.66-16.82.79-7.26-1.6-15.93-7-16.79S121.82,53,120.52,59.59c-.45,2.29-9.45,13.32-11.51,14.28-6.52,3-12.68,9.31-10.73,14.41S109.4,93.75,115.74,91.81Z"/><path class="cls-2" d="M140,16.85c8.77.37,12.46,18.21,11,32.2-.45,4.44,9.88,29.43,11.69,33.54,1.64,3.72,5.35,5.34,5.68,5.68-6.67-18.1-7.26-32.62-6-36,5.7-15.73,3.2-36.89-5.33-41.81C151.27,7.13,138.68,5.8,130.67,19,134.45,16.5,136.88,16.71,140,16.85Z"/><path class="clsLine truncated
|
||||
|
After Width: | Height: | Size: 5.4 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 216"><defs><style>.cls-1{fill:#4d4d4d;}.cls-2{fill:#e3493c;}</style></defs><title>Superalgos-logo-vert_1</title><g id="Layer_4" data-name="Layer 4"><path class="cls-1" d="M13.23,207.58A28.44,28.44,0,0,1,10,207v-4a44.92,44.92,0,0,0,5.33.37,10.47,10.47,0,0,0,2.22-.18,2.42,2.42,0,0,0,1.23-.7,3.21,3.21,0,0,0,.59-1.48,15.56,15.56,0,0,0,.16-2.54,21.51,21.51,0,0,0-.16-3.25,1.91,1.91,0,0,0-.73-1.3,5.88,5.88,0,0,0-2-.73l-4-.79a3.17,3.17,0,0,1-2.5-2.36,20.78,20.78,0,0,1-.63-5.91,14.52,14.52,0,0,1,.86-5.6A5.4,5.4,0,0,1,13,175.75a10,10,0,0,1,4.41-.84,22.82,22.82,0,0,1,3.57.26c1.08.17,1.85.32,2.32.44v4a14.8,14.8,0,0,0-1.87-.26c-.88-.08-1.76-.12-2.66-.12a9.88,9.88,0,0,0-2.5.24,2,2,0,0,0-1.24.84,3.14,3.14,0,0,0-.56,1.42,15.61,15.61,0,0,0-.14,2.31,18.37,18.37,0,0,0,.14,2.69,1.94,1.94,0,0,0,.54,1.17,2.83,2.83,0,0,0,1.33.53l3.5.75a6.46,6.46,0,0,1,2.5,1,4.4,4.4,0,0,1,1.47,2.33,17.51,17.51,0,0,1,.51,5.18,19,19,0,0,1-.77,6.15,5.21,5.21,0,0,1-2.4,3,9.52,9.52,0,0,1-4.44.86A33.32,33.32,0,0,1,13.23,207.58Z"/><path class="cls-1" d="M47.9,175.29v18.07a66.38,66.38,0,0,1-.44,9.1,6.09,6.09,0,0,1-2.08,4.06q-1.64,1.26-5.42,1.26t-5.41-1.26a6,6,0,0,1-2.06-4,69.05,69.05,0,0,1-.42-9.13V175.29h4.76v20.07c0,2.62.06,4.44.17,5.47a3.11,3.11,0,0,0,.79,2.07,3.38,3.38,0,0,0,2.17.54,3.46,3.46,0,0,0,2.22-.54,3.11,3.11,0,0,0,.79-2.07c.11-1,.17-2.85.17-5.47V175.29Z"/><path class="cls-1" d="M56.53,175.29H64.8a7.32,7.32,0,0,1,4.62,1.23,5.85,5.85,0,0,1,2,3.43,28,28,0,0,1,.49,5.89,28.72,28.72,0,0,1-.46,5.74,6,6,0,0,1-2,3.5,7,7,0,0,1-4.64,1.31H61.3v11H56.53Zm7.52,16.81a3.2,3.2,0,0,0,2.08-.54,2.93,2.93,0,0,0,.84-1.8,26.3,26.3,0,0,0,.21-3.92,25.81,25.81,0,0,0-.21-3.9,2.86,2.86,0,0,0-.84-1.77,3.2,3.2,0,0,0-2.08-.54H61.3V192.1Z"/><path class="cls-1" d="M79.69,207.41V175.29H92.35v4.34H84.5V189h6.87v4.35H84.5v9.71h7.85v4.34Z"/><path class="cls-1" d="M111.61,207.41l-2.94-11.95h-3.6v11.95h-4.76V175.29h8.45a7.6,7.6,0,0,1,4.62,1.16,5.5,5.5,0,0,1,2,3.29,25.84,25.84,0,0,1,.49,5.63,23.83,23.83,0,0,1-.56,5.79,5.11,5.11,0,0,1-2.33,3.27l3.5,13Zm-1.52-16.67a2.69,2.69,0,0,0,.84-1.68,22.64,22.64,0,0,0,.21-3.64,22.33,22.33,0,0,0-.21-3.6,2.7,2.7,0,0,0-.86-1.68,3.35,3.35,0,0,0-2.06-.51h-2.94v11.63H108A3.25,3.25,0,0,0,110.09,190.74Z"/><path class="cls-2" d="M137.14,207.41l-1.35-7.7h-6.26l-1.31,7.7h-4.76l5.79-32.12h6.91L142,207.41Zm-4.34-26.9h-.28l-2.47,15h5.23Z"/><path class="cls-2" d="M149.53,207.41V175.29h4.8V203h7.06v4.39Z"/><path class="cls-2" d="M176.41,193.68v-4.2h7.28v17.37a20,20,0,0,1-3.27.68,27.12,27.12,0,0,1-3.59.25,9.24,9.24,0,0,1-5.42-1.28c-1.18-.86-2-2.43-2.47-4.72a56.38,56.38,0,0,1-.7-10.43,54.83,54.83,0,0,1,.72-10.41c.48-2.28,1.35-3.85,2.59-4.72a10,10,0,0,1,5.7-1.31,23.71,23.71,0,0,1,3.29.26,23,23,0,0,1,3.1.63v4c-.9-.15-1.83-.28-2.8-.39a21.18,21.18,0,0,0-2.19-.17,8.38,8.38,0,0,0-3.81.61c-.7.41-1.19,1.41-1.47,3a57.35,57.35,0,0,0-.42,8.48c0,3.8.09,6.5.26,8.12a5.8,5.8,0,0,0,1,3.27,3.55,3.55,0,0,0,2.66.84,9.17,9.17,0,0,0,2.43-.28v-9.62Z"/><path class="cls-2" d="M194.2,206.43a6.66,6.66,0,0,1-2.15-4.53,85.84,85.84,0,0,1-.44-10.55,85.73,85.73,0,0,1,.44-10.55,6.66,6.66,0,0,1,2.15-4.53c1.13-.91,3-1.36,5.62-1.36s4.54.45,5.68,1.36a6.75,6.75,0,0,1,2.15,4.53,87.83,87.83,0,0,1,.44,10.55,88,88,0,0,1-.44,10.55,6.75,6.75,0,0,1-2.15,4.53q-1.71,1.35-5.68,1.35C197.21,207.78,195.33,207.33,194.2,206.43Zm8-3.59a5.19,5.19,0,0,0,.88-3q.24-2.39.24-8.5t-.24-8.5a5.16,5.16,0,0,0-.88-3,3.58,3.58,0,0,0-2.39-.61,3.44,3.44,0,0,0-2.35.61,5.34,5.34,0,0,0-.87,3q-.22,2.39-.23,8.5t.23,8.5a5.37,5.37,0,0,0,.87,3,3.44,3.44,0,0,0,2.35.6A3.59,3.59,0,0,0,202.21,202.84Z"/><path class="cls-2" d="M219.4,207.58a27.86,27.86,0,0,1-3.22-.54v-4a44.63,44.63,0,0,0,5.32.37,10.47,10.47,0,0,0,2.22-.18,2.42,2.42,0,0,0,1.23-.7,3.21,3.21,0,0,0,.59-1.48,15.56,15.56,0,0,0,.16-2.54,21.51,21.51,0,0,0-.16-3.25,1.91,1.91,0,0,0-.73-1.3,5.89,5.89,0,0,0-2-.73l-4-.79a3.17,3.17,0,0,1-2.5-2.36,20.78,20.78,0,0,1-.63-5.91,14.71,14.71,0,0,1,.86-5.6,5.4,5.4,0,0,1,2.57-2.85,10,10,0,0,1,4.41-.84,23.05,23.05,0,0,1,3.58.26c1.07.17,1.84.32,2.31.44v4a14.8,14.8,0,0,0-1.87-.26c-.87-.08-1.76-.12-2.66-.12a9.88,9.88,0,0,0-2.5.24,2,2,0,0,0-1.24.84,3.14,3.14,0,0,0-.56,1.42,15.61,15.61,0,0,0-.14,2.31,18.37,18.37,0,0,0,.14,2.69,1.94,1.94,0,0,0,.54,1.17,2.88,2.88,0,0,0,1.33.53l3.5.75a6.46,6.46,0,0,1,2.5,1,4.4,4.4,0,0,1,1.47,2.33,17.51,17.51,0,0,1,.51,5.18,19,19,0,0,1-.77,6.15,5.21,5.21,0,0,1-2.4,3,9.52,9.52,0,0,1-4.44.86A33.32,33.32,0,0,1,219.4,207.58Z"/><path class="cls-2" d="M115.74,91.81c2.19-.67,16.14,1.56,18,2.88,5.86,4.13,14.45,6.37,17.9,2.14s-.16-11.49-5.67-17c-1.64-1.65-6.92-14.51-6.66-16.82.79-7.26-1.6-15.93-7-16.79S121.82,53,120.52,59.59c-.45,2.29-9.45,13.32-11.51,14.28-6.52,3-12.68,9.31-10.73,14.41S109.4,93.75,115.74,91.81Z"/><path class="cls-2" d="M140,16.85c8.77.37,12.46,18.21,11,32.2-.45,4.44,9.88,29.43,11.69,33.54,1.64,3.72,5.35,5.34,5.68,5.68-6.67-18.1-7.26-32.62-6-36,5.7-15.73,3.2-36.89-5.33-41.81C151.27,7.13,138.68,5.8,130.67,19,134.45,16.5,136.88,16.71,140,16.85Z"/><path class="clsLine truncated
|
||||
|
After Width: | Height: | Size: 5.4 KiB |
@@ -0,0 +1,12 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const GetCurrentMember = gql`
|
||||
query currentMember {
|
||||
teams_CurrentMember {
|
||||
...MemberInfo
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default GetCurrentMember
|
||||
@@ -0,0 +1,12 @@
|
||||
#import "./Profile.graphql"
|
||||
|
||||
fragment MemberInfo on Member {
|
||||
id
|
||||
authId
|
||||
alias
|
||||
role
|
||||
isActive
|
||||
profile {
|
||||
...MemberProfileInfo
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
fragment MemberProfileInfo on MemberProfile {
|
||||
firstName
|
||||
lastName
|
||||
email
|
||||
avatar
|
||||
role {
|
||||
name
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { GetCurrentMember } from './GetCurrentMemberQuery'
|
||||
|
||||
export { GetCurrentMember }
|
||||
@@ -0,0 +1,22 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
const CREATE_TEAM = gql`
|
||||
mutation CreateTeamMutation($name: String!, $slug: String!, $botName: String!, $botSlug: String!) {
|
||||
teams_CreateTeam(name: $name, slug: $slug, botName: $botName, botSlug: $botSlug) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
owner
|
||||
members {
|
||||
role
|
||||
member {
|
||||
authId
|
||||
alias
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default CREATE_TEAM
|
||||
@@ -0,0 +1,12 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const DELETE_TEAM = gql`
|
||||
mutation deleteTeam($slug: String!, $botSlug: String!) {
|
||||
teams_DeleteTeam(slug: $slug, botSlug: $botSlug) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default DELETE_TEAM
|
||||
@@ -0,0 +1,58 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const GET_ALL_TEAMS_QUERY = gql`
|
||||
query teamsQuery {
|
||||
teams_Teams {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
slug
|
||||
owner
|
||||
status {
|
||||
status
|
||||
reason
|
||||
createdAt
|
||||
}
|
||||
createdAt
|
||||
profile {
|
||||
avatar
|
||||
banner
|
||||
description
|
||||
motto
|
||||
updatedAt
|
||||
}
|
||||
members {
|
||||
role
|
||||
email
|
||||
member {
|
||||
alias
|
||||
authId
|
||||
}
|
||||
status {
|
||||
id
|
||||
status
|
||||
reason
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
fb {
|
||||
id
|
||||
name
|
||||
slug
|
||||
avatar
|
||||
kind
|
||||
status {
|
||||
status
|
||||
reason
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default GET_ALL_TEAMS_QUERY
|
||||
@@ -0,0 +1,54 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const GET_TEAMS_BY_OWNER = gql`
|
||||
query teamsByOwnerQuery {
|
||||
teams_TeamsByOwner {
|
||||
id
|
||||
name
|
||||
slug
|
||||
owner
|
||||
status {
|
||||
status
|
||||
reason
|
||||
createdAt
|
||||
}
|
||||
createdAt
|
||||
profile {
|
||||
avatar
|
||||
banner
|
||||
description
|
||||
motto
|
||||
updatedAt
|
||||
}
|
||||
members {
|
||||
role
|
||||
email
|
||||
member {
|
||||
alias
|
||||
authId
|
||||
}
|
||||
status {
|
||||
id
|
||||
status
|
||||
reason
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
fb {
|
||||
id
|
||||
name
|
||||
slug
|
||||
avatar
|
||||
kind
|
||||
status {
|
||||
status
|
||||
reason
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default GET_TEAMS_BY_OWNER
|
||||
@@ -0,0 +1,12 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const REMOVE_TEAM_MEMBER = gql`
|
||||
mutation removeTeamMember($teamId: String!, $memberId: String!) {
|
||||
teams_RemoveTeamMember(teamId: $teamId, memberId: $memberId) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default REMOVE_TEAM_MEMBER
|
||||
@@ -0,0 +1,10 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const SEND_MEMBER_INVITE = gql`
|
||||
mutation sendMemberInvite($email: String!, $teamId: String!) {
|
||||
teams_SendMemberInviteSG(email: $email, teamId: $teamId)
|
||||
}
|
||||
`
|
||||
|
||||
export default SEND_MEMBER_INVITE
|
||||
@@ -0,0 +1,12 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const SET_TEAM_MEMBER_ROLE = gql`
|
||||
mutation setTeamMemberRole($teamId: String!, $memberId: String!, $role: String!) {
|
||||
teams_SetTeamMemberRole(teamId: $teamId, memberId: $memberId, role: $role) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default SET_TEAM_MEMBER_ROLE
|
||||
@@ -0,0 +1,52 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const TEAMS_FRAGMENT = gql`
|
||||
fragment TeamsResponse on teams {
|
||||
id
|
||||
name
|
||||
slug
|
||||
owner
|
||||
status {
|
||||
status
|
||||
reason
|
||||
createdAt
|
||||
}
|
||||
createdAt
|
||||
profile {
|
||||
avatar
|
||||
banner
|
||||
description
|
||||
motto
|
||||
updatedAt
|
||||
}
|
||||
members {
|
||||
role
|
||||
email
|
||||
member {
|
||||
alias
|
||||
authId
|
||||
}
|
||||
status {
|
||||
id
|
||||
status
|
||||
reason
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
fb {
|
||||
id
|
||||
name
|
||||
slug
|
||||
avatar
|
||||
kind
|
||||
status {
|
||||
status
|
||||
reason
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default TEAMS_FRAGMENT
|
||||
@@ -0,0 +1,12 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const UniqueTeamnameQuery = gql`
|
||||
query uniqueTeamname($name: String!) {
|
||||
teams_TeamByName(name: $name) {
|
||||
name
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default UniqueTeamnameQuery
|
||||
@@ -0,0 +1,16 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
const UPDATE_FB = gql`
|
||||
mutation UpdateFBMutation($fbId: String!, $avatar: String) {
|
||||
teams_UpdateFB(fbId: $fbId, avatar: $avatar) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
avatar
|
||||
kind
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default UPDATE_FB
|
||||
@@ -0,0 +1,54 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
const UPDATE_TEAM_PROFILE = gql`
|
||||
mutation UpdateTeamProfileMutation(
|
||||
$slug: String!,
|
||||
$description: String,
|
||||
$motto: String,
|
||||
$avatar: String,
|
||||
$banner: String
|
||||
) {
|
||||
teams_UpdateTeamProfile(
|
||||
slug: $slug,
|
||||
description: $description,
|
||||
motto: $motto
|
||||
avatar: $avatar,
|
||||
banner: $banner
|
||||
) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
owner
|
||||
status {
|
||||
status
|
||||
reason
|
||||
createdAt
|
||||
}
|
||||
createdAt
|
||||
profile {
|
||||
avatar
|
||||
banner
|
||||
description
|
||||
motto
|
||||
updatedAt
|
||||
}
|
||||
members {
|
||||
role
|
||||
email
|
||||
member {
|
||||
alias
|
||||
authId
|
||||
}
|
||||
status {
|
||||
id
|
||||
status
|
||||
reason
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default UPDATE_TEAM_PROFILE
|
||||
@@ -0,0 +1,79 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Route, Switch } from 'react-router-dom'
|
||||
import { hot } from 'react-hot-loader'
|
||||
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import {
|
||||
AcceptTeamInvite,
|
||||
ManageTeams,
|
||||
TeamMembers,
|
||||
Settings,
|
||||
Teams,
|
||||
Landing,
|
||||
globalStyles
|
||||
} from './views'
|
||||
|
||||
import { getItem } from './utils/local-storage'
|
||||
|
||||
class App extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
user: null
|
||||
}
|
||||
}
|
||||
|
||||
async componentDidMount () {
|
||||
let user = await getItem('user')
|
||||
|
||||
if (user !== null && user !== undefined && user !== 'undefined') {
|
||||
user = JSON.parse(user)
|
||||
this.setState({ user: user })
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='App'>
|
||||
<Switch>
|
||||
<Route exact path={`/teams`} component={Landing} />
|
||||
<Route exact path={`/teams/explore`} component={Teams} />
|
||||
<Route
|
||||
exact
|
||||
path={`/teams/manage-teams`}
|
||||
render={props => <ManageTeams {...props} auth={this.props.auth} />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`/teams/manage-teams/:slug`}
|
||||
render={props => <ManageTeams {...props} auth={this.props.auth} user={this.state.user} />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`/teams/team-members`}
|
||||
render={props => <TeamMembers {...props} auth={this.props.auth} />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`/teams/settings`}
|
||||
render={props => <Settings {...props} auth={this.props.auth} />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`/teams/activate-team-membership`}
|
||||
render={props => <AcceptTeamInvite {...props} auth={this.props.auth} />}
|
||||
/>
|
||||
<Route exact path='/teams/:slug' component={Teams} />
|
||||
</Switch>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
App.propTypes = {
|
||||
auth: PropTypes.object
|
||||
}
|
||||
|
||||
const StyledApp = withStyles(globalStyles)(App)
|
||||
export default hot(module)(StyledApp)
|
||||
@@ -0,0 +1,19 @@
|
||||
import log from './log'
|
||||
|
||||
const ErrorResponse = {
|
||||
'unique constraint': 'Sorry, already taken! Please create a unique '
|
||||
}
|
||||
|
||||
export const checkGraphQLError = error => {
|
||||
log.debug('checkGraphQLError ', ErrorResponse, error)
|
||||
const errorString = JSON.stringify(error)
|
||||
/*
|
||||
if (error.search(/unique constraint/)) {
|
||||
const field = error.substring(error.indexOf('=') + 1)
|
||||
log.debug('unique constraint', field.replace(/ +/g, ''))
|
||||
}
|
||||
*/
|
||||
return errorString
|
||||
}
|
||||
|
||||
export default checkGraphQLError
|
||||
@@ -0,0 +1,80 @@
|
||||
export function isDefined (d) {
|
||||
return d !== null && typeof d !== 'undefined'
|
||||
}
|
||||
|
||||
export function isNotDefined (d) {
|
||||
return !isDefined(d)
|
||||
}
|
||||
|
||||
export const isArray = Array.isArray
|
||||
|
||||
export function isObject (d) {
|
||||
return isDefined(d) && typeof d === 'object' && !Array.isArray(d)
|
||||
}
|
||||
|
||||
export function isEmpty (obj) {
|
||||
for (var key in obj) {
|
||||
if (obj.hasOwnProperty(key)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export function isString (value) {
|
||||
return typeof value === 'string' || value instanceof String
|
||||
}
|
||||
|
||||
export function validObject (object, ...keys) {
|
||||
return keys.reduce((a, b) => (a || {})[b], object) !== undefined
|
||||
}
|
||||
|
||||
export function mergeDeep (target, ...sources) {
|
||||
if (!sources.length) return target
|
||||
const source = sources.shift()
|
||||
|
||||
if (isObject(target) && isObject(source)) {
|
||||
for (const key in source) {
|
||||
if (isObject(source[key])) {
|
||||
if (!target[key]) Object.assign(target, { [key]: {} })
|
||||
mergeDeep(target[key], source[key])
|
||||
} else {
|
||||
Object.assign(target, { [key]: source[key] })
|
||||
}
|
||||
}
|
||||
}
|
||||
return mergeDeep(target, ...sources)
|
||||
}
|
||||
|
||||
export function deleteCookie (c) {
|
||||
document.cookie.split(';').forEach(function (c) {
|
||||
document.cookie = c
|
||||
.replace(/^ +/, '')
|
||||
.replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/')
|
||||
})
|
||||
}
|
||||
|
||||
export function slugify (string) {
|
||||
const a = 'àáäâãåèéëêìíïîòóöôùúüûñçßÿœæŕśńṕẃǵǹḿǘẍźḧ·/_,:;'
|
||||
const b = 'aaaaaaeeeeiiiioooouuuuncsyoarsnpwgnmuxzh------'
|
||||
const p = new RegExp(a.split('').join('|'), 'g')
|
||||
|
||||
return string
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '-') // Replace spaces with -
|
||||
.replace(p, c => b.charAt(a.indexOf(c))) // Replace special characters
|
||||
.replace(/&/g, '-and-') // Replace & with 'and'
|
||||
.replace(/[^\w-]+/g, '') // Remove all non-word characters
|
||||
.replace(/--+/g, '-') // Replace multiple - with single -
|
||||
.replace(/^-+/, '') // Trim - from start of text
|
||||
.replace(/-+$/, '') // Trim - from end of text
|
||||
}
|
||||
|
||||
export function parse (queryString) {
|
||||
var query = {}
|
||||
var pairs = (queryString[0] === '?' ? queryString.substr(1) : queryString).split('&')
|
||||
for (var i = 0; i < pairs.length; i++) {
|
||||
var pair = pairs[i].split('=')
|
||||
query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || '')
|
||||
}
|
||||
return query
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
const getItem = async name => window.localStorage.getItem(name)
|
||||
const setItem = async (name, value) => window.localStorage.setItem(name, value)
|
||||
const removeItem = async name => window.localStorage.removeItem(name)
|
||||
|
||||
export {
|
||||
getItem,
|
||||
setItem,
|
||||
removeItem
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import minilog from 'minilog'
|
||||
|
||||
if (process.env.NODE_ENV === 'development') minilog.enable()
|
||||
|
||||
const log = minilog('frontend')
|
||||
|
||||
export default log
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
|
||||
const BannerTopBar = ({
|
||||
size,
|
||||
backgroundUrl,
|
||||
title,
|
||||
text
|
||||
}) => (
|
||||
<div className={`bannerTopBar ${size}`} style={backgroundUrl ? { backgroundImage: `url(${backgroundUrl})` } : {}}>
|
||||
<div className='caption'>
|
||||
<Typography className='title' variant='h1' align='center'>{title}</Typography>
|
||||
<Typography className='text' variant='h2' align='center'>{text}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
BannerTopBar.propTypes = {
|
||||
backgroundUrl: PropTypes.string,
|
||||
size: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
text: PropTypes.string
|
||||
}
|
||||
|
||||
export default BannerTopBar
|
||||
@@ -0,0 +1,152 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import classNames from 'classnames'
|
||||
import AppBar from '@material-ui/core/AppBar'
|
||||
import Toolbar from '@material-ui/core/Toolbar'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import Button from '@material-ui/core/Button'
|
||||
|
||||
// icons
|
||||
import GroupIcon from '@material-ui/icons/Group'
|
||||
import UsersIcon from '@material-ui/icons/People'
|
||||
import SearchIcon from '@material-ui/icons/Search'
|
||||
import AdbIcon from '@material-ui/icons/Adb'
|
||||
// import SettingsApplicationsIcon from '@material-ui/icons/SettingsApplications'
|
||||
import BugReportIcon from '@material-ui/icons/BugReport'
|
||||
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
// const SettingsLink = props => <Link to='/teams/settings' {...props} />
|
||||
const FBLink = props => <Link to='/teams/financial-beings' {...props} />
|
||||
const MembersLink = props => <Link to='/teams/team-members' {...props} />
|
||||
const ManageLink = props => <Link {...props} />
|
||||
const ViewLink = props => <Link to='/teams' {...props} />
|
||||
|
||||
/*
|
||||
<Button
|
||||
variant='text'
|
||||
size='small'
|
||||
className={classNames(classes.button, classes.cssRoot)}
|
||||
title='Your Global Team Settings'
|
||||
component={SettingsLink}
|
||||
to={`${match.url}/settings`}>
|
||||
<SettingsApplicationsIcon className={classNames(classes.leftIcon, classes.iconSmall)} />
|
||||
Settings
|
||||
</Button>
|
||||
*/
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
flexGrow: 1
|
||||
},
|
||||
flex: {
|
||||
flexGrow: 1,
|
||||
marginLeft: 30
|
||||
},
|
||||
menuButton: {
|
||||
marginLeft: -12,
|
||||
marginRight: 20
|
||||
},
|
||||
cssRoot: {
|
||||
color: '#FFFFFF',
|
||||
backgroundColor: theme.palette.secondary,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.dark
|
||||
},
|
||||
whiteSpace: 'nowrap',
|
||||
paddingRight: 2 * theme.spacing.unit,
|
||||
paddingLeft: 2 * theme.spacing.unit
|
||||
},
|
||||
button: {
|
||||
margin: theme.spacing.unit
|
||||
},
|
||||
leftIcon: {
|
||||
marginRight: theme.spacing.unit
|
||||
},
|
||||
rightIcon: {
|
||||
marginLeft: theme.spacing.unit
|
||||
},
|
||||
iconSmall: {
|
||||
fontSize: 20
|
||||
}
|
||||
})
|
||||
|
||||
class TeamBar extends Component {
|
||||
render () {
|
||||
const { classes, user, match } = this.props
|
||||
log.debug('TeamBar: ', user, match)
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<AppBar position='static' color='secondary'>
|
||||
<Toolbar variant='dense'>
|
||||
<Typography variant='h5' color='inherit' className={classes.flex}>
|
||||
Teams
|
||||
</Typography>
|
||||
<Button
|
||||
variant='text'
|
||||
size='small'
|
||||
className={classNames(classes.button, classes.cssRoot)}
|
||||
title='View all teams'
|
||||
component={ViewLink}
|
||||
to={`${match.url}`}>
|
||||
<SearchIcon className={classNames(classes.leftIcon, classes.iconSmall)} />
|
||||
All Teams
|
||||
</Button>
|
||||
<Button
|
||||
variant='text'
|
||||
size='small'
|
||||
className={classNames(classes.button, classes.cssRoot)}
|
||||
title='Manage your teams'
|
||||
component={ManageLink}
|
||||
to={`${match.url}/manage-teams`}>
|
||||
<GroupIcon className={classNames(classes.leftIcon, classes.iconSmall)} />
|
||||
Your Teams
|
||||
</Button>
|
||||
<Button
|
||||
variant='text'
|
||||
size='small'
|
||||
className={classNames(classes.button, classes.cssRoot)}
|
||||
title='Manage team members'
|
||||
component={MembersLink}
|
||||
to={`${match.url}/team-members`}>
|
||||
<UsersIcon className={classNames(classes.leftIcon, classes.iconSmall)} />
|
||||
Team Members
|
||||
</Button>
|
||||
<Button
|
||||
variant='text'
|
||||
size='small'
|
||||
className={classNames(classes.button, classes.cssRoot)}
|
||||
title='Manage Financial Beings'
|
||||
component={FBLink}
|
||||
to={`${match.url}/financial-beings`}>
|
||||
<AdbIcon className={classNames(classes.leftIcon, classes.iconSmall)} />
|
||||
Financial Beings
|
||||
</Button>
|
||||
<Button
|
||||
variant='text'
|
||||
size='small'
|
||||
className={classNames(classes.button, classes.cssRoot)}
|
||||
title='Report a Teams Module bug'
|
||||
href='https://github.com/AdvancedAlgos/TeamsModule/issues/new'
|
||||
target='_blank'
|
||||
>
|
||||
<BugReportIcon className={classNames(classes.leftIcon, classes.iconSmall)} />
|
||||
Report
|
||||
</Button>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
TeamBar.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
user: PropTypes.object,
|
||||
match: PropTypes.object
|
||||
}
|
||||
|
||||
export default withStyles(styles)(TeamBar)
|
||||
@@ -0,0 +1,39 @@
|
||||
const styles = () => ({
|
||||
rootBig: {
|
||||
backgroundImage: 'url(https://aacorporatesitedevelop.azurewebsites.net/img/photos/connect.jpg)',
|
||||
width: '100%',
|
||||
height: '100vh'
|
||||
},
|
||||
rootMedium: {
|
||||
backgroundImage: 'url(https://aacorporatesitedevelop.azurewebsites.net/img/photos/connect.jpg)',
|
||||
width: '100%',
|
||||
height: '50vh'
|
||||
},
|
||||
rootSmall: {
|
||||
backgroundImage: 'url(https://aacorporatesitedevelop.azurewebsites.net/img/photos/connect.jpg)',
|
||||
width: '100%',
|
||||
height: '220px'
|
||||
},
|
||||
captions: {
|
||||
position: 'relative',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)'
|
||||
},
|
||||
title: {
|
||||
fontFamily: 'Saira',
|
||||
color: 'white',
|
||||
fontSize: '64px',
|
||||
lineHeight: '78px',
|
||||
fontWeight: 100
|
||||
},
|
||||
text: {
|
||||
fontFamily: 'Saira',
|
||||
color: 'white',
|
||||
fontSize: '20px',
|
||||
lineHeight: '30px',
|
||||
fontWeight: 600,
|
||||
padding: '20px 0 0.5em 0'
|
||||
}
|
||||
})
|
||||
|
||||
export default styles
|
||||
@@ -0,0 +1,4 @@
|
||||
import TeamBar from './components/TeamBar'
|
||||
import BannerTopBar from './components/BannerTopBar'
|
||||
|
||||
export { TeamBar, BannerTopBar }
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import ManageFBList from './components/ManageFBList'
|
||||
|
||||
const styles = theme => ({
|
||||
tableContainer: {
|
||||
height: 320
|
||||
}
|
||||
})
|
||||
|
||||
const FinancialBeings = ({ classes }) => (
|
||||
<div>
|
||||
<Typography variant='h4' gutterBottom>
|
||||
Financial Beings
|
||||
</Typography>
|
||||
<ManageFBList />
|
||||
</div>
|
||||
)
|
||||
|
||||
FinancialBeings.propTypes = {
|
||||
classes: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default withStyles(styles)(FinancialBeings)
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import ManageTeamsList from './components/ManageTeamsList'
|
||||
|
||||
const styles = theme => ({
|
||||
tableContainer: {
|
||||
height: 320
|
||||
}
|
||||
})
|
||||
|
||||
const ManageTeams = ({ classes }) => (
|
||||
<div>
|
||||
<Typography variant='h4' gutterBottom>
|
||||
Manage Your Teams
|
||||
</Typography>
|
||||
<ManageTeamsList />
|
||||
</div>
|
||||
)
|
||||
|
||||
ManageTeams.propTypes = {
|
||||
classes: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageTeams)
|
||||
@@ -0,0 +1,63 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
|
||||
import DashTeam from './components/DashTeam'
|
||||
import DashEditor from './components/DashEditor'
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
display: 'flex'
|
||||
},
|
||||
card: {
|
||||
display: 'flex',
|
||||
width: '100%'
|
||||
},
|
||||
cardDetails: {
|
||||
display: 'flex',
|
||||
flex: 1
|
||||
},
|
||||
cardContent: {
|
||||
flex: 1
|
||||
},
|
||||
cardMedia: {
|
||||
flex: 1,
|
||||
width: 160,
|
||||
height: 160,
|
||||
justifyContent: 'flex-start'
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
},
|
||||
aawebMedia: {
|
||||
width: '100%',
|
||||
height: 320
|
||||
},
|
||||
dashLink: {
|
||||
float: 'right',
|
||||
color: '#111',
|
||||
fontSize: 16,
|
||||
textDecoration: 'none',
|
||||
paddingRight: '1em'
|
||||
}
|
||||
})
|
||||
|
||||
export class Overview extends Component {
|
||||
render () {
|
||||
const { classes } = this.props
|
||||
return (
|
||||
<Grid container spacing={24}>
|
||||
<DashTeam classes={classes} />
|
||||
<DashEditor classes={classes} />
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Overview.propTypes = {
|
||||
classes: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default withStyles(styles)(Overview)
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
// import ManageTeamMembersList from './components/ManageTeamMembersList'
|
||||
import { MessageCard } from '@superalgos/web-components'
|
||||
|
||||
const styles = theme => ({
|
||||
tableContainer: {
|
||||
height: 320
|
||||
}
|
||||
})
|
||||
|
||||
const TeamMembers = ({ classes }) => (
|
||||
<div>
|
||||
<Typography variant='h4' gutterBottom>
|
||||
Team Members
|
||||
<MessageCard message='Coming soon. Invite and manage team members.' />
|
||||
</Typography>
|
||||
|
||||
</div>
|
||||
)
|
||||
|
||||
TeamMembers.propTypes = {
|
||||
classes: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default withStyles(styles)(TeamMembers)
|
||||
@@ -0,0 +1,102 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Mutation } from 'react-apollo'
|
||||
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
|
||||
import { getItem } from '../../../utils/local-storage'
|
||||
import { checkGraphQLError } from '../../../utils/graphql-errors'
|
||||
|
||||
import CREATE_TEAM from '../../../graphql/teams/CreateTeamMutation'
|
||||
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const CreateTeam = ({ authId }) => {
|
||||
let input
|
||||
log.debug('CreateTeam', authId)
|
||||
|
||||
return (
|
||||
<Mutation
|
||||
mutation={CREATE_TEAM}
|
||||
refetchQueries={[
|
||||
{
|
||||
query: GET_TEAMS_BY_OWNER,
|
||||
variables: { authId }
|
||||
}
|
||||
]}
|
||||
>
|
||||
{(createTeam, { loading, error, data }) => {
|
||||
let errors
|
||||
let loader
|
||||
if (loading) {
|
||||
loader = <Typography variant='caption'>Submitting team...</Typography>
|
||||
}
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
const displayMessage = checkGraphQLError(message)
|
||||
log.debug('createTeam error:', displayMessage)
|
||||
return (
|
||||
<Typography key={i} variant='caption'>
|
||||
{message}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<form
|
||||
onSubmit={e => {
|
||||
handleSubmit(e, createTeam, input)
|
||||
}}
|
||||
>
|
||||
<input
|
||||
ref={node => {
|
||||
input = node
|
||||
}}
|
||||
/>
|
||||
<button type='submit'>Create Team</button>
|
||||
</form>
|
||||
{loader}
|
||||
{errors}
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</Mutation>
|
||||
)
|
||||
}
|
||||
|
||||
CreateTeam.propTypes = {
|
||||
authId: PropTypes.any
|
||||
}
|
||||
|
||||
const handleSubmit = async (e, createTeam, input) => {
|
||||
e.preventDefault()
|
||||
const currentUser = await getItem('user')
|
||||
let authId = JSON.parse(currentUser)
|
||||
authId = authId.authId
|
||||
log.debug('createTeam submit: ', authId)
|
||||
const name = input.value
|
||||
const slug = slugify(name)
|
||||
await createTeam({ variables: { name, slug, owner: authId } })
|
||||
input.value = ''
|
||||
}
|
||||
|
||||
const slugify = string => {
|
||||
const a = 'àáäâãåèéëêìíïîòóöôùúüûñçßÿœæŕśńṕẃǵǹḿǘẍźḧ·/_,:;'
|
||||
const b = 'aaaaaaeeeeiiiioooouuuuncsyoarsnpwgnmuxzh------'
|
||||
const p = new RegExp(a.split('').join('|'), 'g')
|
||||
|
||||
return string
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '-') // Replace spaces with -
|
||||
.replace(p, c => b.charAt(a.indexOf(c))) // Replace special characters
|
||||
.replace(/&/g, '-and-') // Replace & with 'and'
|
||||
.replace(/[^\w-]+/g, '') // Remove all non-word characters
|
||||
.replace(/--+/g, '-') // Replace multiple - with single -
|
||||
.replace(/^-+/, '') // Trim - from start of text
|
||||
.replace(/-+$/, '') // Trim - from end of text
|
||||
}
|
||||
|
||||
export default CreateTeam
|
||||
@@ -0,0 +1,262 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Mutation } from 'react-apollo'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import Button from '@material-ui/core/Button'
|
||||
import AddIcon from '@material-ui/icons/Add'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import FormControl from '@material-ui/core/FormControl'
|
||||
import FormHelperText from '@material-ui/core/FormHelperText'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import { isEmpty } from '../../../utils/js-helpers'
|
||||
|
||||
import CREATE_TEAM from '../../../graphql/teams/CreateTeamMutation'
|
||||
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
dialogContainer: {
|
||||
display: 'block',
|
||||
margin: '3em',
|
||||
minWidth: 400
|
||||
},
|
||||
dialogStyle: {
|
||||
padding: '3em'
|
||||
},
|
||||
textField: {
|
||||
width: '60%',
|
||||
marginLeft: '20%',
|
||||
marginBottom: 10
|
||||
},
|
||||
buttonRight: {
|
||||
// position: 'absolute',
|
||||
// right: '3em'
|
||||
marginTop: '1em',
|
||||
marginRight: 'auto',
|
||||
marginLeft: 'auto',
|
||||
display: 'block',
|
||||
lineHeight: '1em'
|
||||
}
|
||||
})
|
||||
|
||||
export class CreateTeamDialog extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickOpen = this.handleClickOpen.bind(this)
|
||||
this.handleClose = this.handleClose.bind(this)
|
||||
this.handleChange = this.handleChange.bind(this)
|
||||
this.handleSubmit = this.handleSubmit.bind(this)
|
||||
this.validate = this.validate.bind(this)
|
||||
this.slugify = this.slugify.bind(this)
|
||||
|
||||
this.state = {
|
||||
open: false,
|
||||
name: '',
|
||||
botName: '',
|
||||
nameError: '',
|
||||
botNameError: ''
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { classes, authId } = this.props
|
||||
return (
|
||||
<Mutation
|
||||
mutation={CREATE_TEAM}
|
||||
refetchQueries={[{
|
||||
query: GET_TEAMS_BY_OWNER,
|
||||
variables: { authId: authId }
|
||||
}]}
|
||||
>
|
||||
{(createTeam, { loading, error, data }) => {
|
||||
let errors
|
||||
let loader = null
|
||||
if (loading) {
|
||||
loader = (
|
||||
<Typography variant='subtitle1'>Submitting team...</Typography>
|
||||
)
|
||||
}
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
// const displayMessage = checkGraphQLError(message)
|
||||
return (
|
||||
<Typography key={i} variant='caption'>
|
||||
{message}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
variant='fab'
|
||||
color='primary'
|
||||
aria-label='Add'
|
||||
className={classes.buttonRight}
|
||||
onClick={this.handleClickOpen}
|
||||
>
|
||||
<AddIcon />
|
||||
</Button>
|
||||
<Dialog
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby='form-dialog-title'
|
||||
>
|
||||
<div classes={classes.dialogContainer}>
|
||||
<DialogTitle id='form-dialog-title'>Create a Team</DialogTitle>
|
||||
<DialogContent>
|
||||
<FormControl
|
||||
required
|
||||
error={this.state.nameError !== '' || this.state.botNameError !== '' || error !== null}
|
||||
>
|
||||
<Typography variant='subtitle1' align='center'>Create a name for your team</Typography>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin='dense'
|
||||
id='name'
|
||||
label='Team Name'
|
||||
type='text'
|
||||
fullWidth
|
||||
className={classes.textField}
|
||||
value={this.state.name}
|
||||
onChange={this.handleChange}
|
||||
error={this.state.nameError !== ''}
|
||||
/>
|
||||
{this.state.nameError !== '' && (
|
||||
<FormHelperText>{this.state.nameError}</FormHelperText>
|
||||
)}
|
||||
<Typography variant='subtitle1' align='center' ><br />
|
||||
Create a name for your teams bot.
|
||||
</Typography>
|
||||
<Typography align='center' >
|
||||
<em>(Temporary: In the future, creating multiple Financial Beings <br />
|
||||
will occur in a separate module)</em>
|
||||
</Typography>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin='dense'
|
||||
id='botName'
|
||||
label='Team Bot Name'
|
||||
type='text'
|
||||
fullWidth
|
||||
className={classes.textField}
|
||||
value={this.state.botName}
|
||||
onChange={this.handleChange}
|
||||
error={this.state.botNameError !== ''}
|
||||
/>
|
||||
{this.state.botNameError !== '' && (
|
||||
<FormHelperText>{this.state.botNameError}</FormHelperText>
|
||||
)}
|
||||
{error && <FormHelperText>{errors}</FormHelperText>}
|
||||
{loader}
|
||||
</FormControl>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleClose} color='primary'>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={e => {
|
||||
this.handleSubmit(e, createTeam, this.state.name, this.state.botName)
|
||||
}}
|
||||
color='primary'
|
||||
>
|
||||
Create Team
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</Mutation>
|
||||
)
|
||||
}
|
||||
|
||||
handleClickOpen () {
|
||||
this.setState({ open: true })
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
let id = e.target.id
|
||||
let value = e.target.value
|
||||
let error = this.validate({ id, value })
|
||||
switch (id) {
|
||||
case 'name':
|
||||
if (!isEmpty(error)) this.setState({ nameError: error })
|
||||
this.setState({ name: value })
|
||||
break
|
||||
case 'botName':
|
||||
if (!isEmpty(error)) this.setState({ botNameError: error })
|
||||
this.setState({ botName: value })
|
||||
break
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
async handleSubmit (e, createTeam, name, botName) {
|
||||
e.preventDefault()
|
||||
log.debug('createTeam submit: ', name, botName)
|
||||
const slug = this.slugify(name)
|
||||
const botSlug = this.slugify(botName)
|
||||
await createTeam({ variables: { name, slug, botName, botSlug } })
|
||||
this.setState({ name: '', open: false })
|
||||
}
|
||||
|
||||
validate (data) {
|
||||
const errors = {}
|
||||
switch (data.id) {
|
||||
case 'name':
|
||||
if (data.value === '') {
|
||||
this.setState({ nameError: 'Please enter a team name' })
|
||||
} else {
|
||||
this.setState({ nameError: '' })
|
||||
}
|
||||
break
|
||||
case 'botName':
|
||||
if (data.value === '') {
|
||||
this.setState({ botNameError: 'Please enter a bot name' })
|
||||
} else {
|
||||
this.setState({ botNameError: '' })
|
||||
}
|
||||
break
|
||||
default:
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
slugify (string) {
|
||||
const a = 'àáäâãåèéëêìíïîòóöôùúüûñçßÿœæŕśńṕẃǵǹḿǘẍźḧ·/_,:;'
|
||||
const b = 'aaaaaaeeeeiiiioooouuuuncsyoarsnpwgnmuxzh------'
|
||||
const p = new RegExp(a.split('').join('|'), 'g')
|
||||
|
||||
return string
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '-') // Replace spaces with -
|
||||
.replace(p, c => b.charAt(a.indexOf(c))) // Replace special characters
|
||||
.replace(/&/g, '-and-') // Replace & with 'and'
|
||||
.replace(/[^\w-]+/g, '') // Remove all non-word characters
|
||||
.replace(/--+/g, '-') // Replace multiple - with single -
|
||||
.replace(/^-+/, '') // Trim - from start of text
|
||||
.replace(/-+$/, '') // Trim - from end of text
|
||||
}
|
||||
}
|
||||
|
||||
CreateTeamDialog.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
authId: PropTypes.string
|
||||
}
|
||||
|
||||
export default withStyles(styles)(CreateTeamDialog)
|
||||
@@ -0,0 +1,151 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import AddIcon from '@material-ui/icons/Add'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogContentText from '@material-ui/core/DialogContentText'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
export class CreateTeamDialog extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickOpen = this.handleClickOpen.bind(this)
|
||||
this.handleClose = this.handleClose.bind(this)
|
||||
this.handlePost = this.handlePost.bind(this)
|
||||
this.slugify = this.slugify.bind(this)
|
||||
this.handleChange = this.handleChange.bind(this)
|
||||
|
||||
this.state = {
|
||||
open: false,
|
||||
name: '',
|
||||
botName: ''
|
||||
}
|
||||
}
|
||||
|
||||
handleClickOpen () {
|
||||
this.setState({ open: true })
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
let value = e.target.value
|
||||
switch (e.target.id) {
|
||||
case 'name':
|
||||
this.setState({ name: value })
|
||||
break
|
||||
case 'botName':
|
||||
this.setState({ botName: value })
|
||||
break
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
log.debug(this.props)
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
variant='extendedFab'
|
||||
aria-label='AddTeam'
|
||||
className={this.props.classes.button}
|
||||
onClick={this.handleClickOpen}
|
||||
>
|
||||
<AddIcon className={this.props.classes.extendedIcon} />
|
||||
<Typography variant='subtitle1'>Create A Team</Typography>
|
||||
</Button>
|
||||
<Dialog
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby='form-dialog-title'
|
||||
>
|
||||
<DialogTitle id='form-dialog-title'>Subscribe</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography variant='subtitle1'>Team Creation</Typography>
|
||||
<DialogContentText>Name your team!</DialogContentText>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin='dense'
|
||||
id='teamname'
|
||||
label='Team Name'
|
||||
type='text'
|
||||
fullWidth
|
||||
value={this.state.name}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<DialogContentText>Create a name for you teams bot. (Temporary: In the future, creating multiple Financial Beings will occur in a separate module)</DialogContentText>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin='dense'
|
||||
id='botname'
|
||||
label='Team Bot Name'
|
||||
type='text'
|
||||
fullWidth
|
||||
value={this.state.botName}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleClose} color='primary'>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={this.handlePost} color='primary'>
|
||||
Create Team
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
slugify (string) {
|
||||
const a = 'àáäâãåèéëêìíïîòóöôùúüûñçßÿœæŕśńṕẃǵǹḿǘẍźḧ·/_,:;'
|
||||
const b = 'aaaaaaeeeeiiiioooouuuuncsyoarsnpwgnmuxzh------'
|
||||
const p = new RegExp(a.split('').join('|'), 'g')
|
||||
|
||||
return string
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '-') // Replace spaces with -
|
||||
.replace(p, c => b.charAt(a.indexOf(c))) // Replace special characters
|
||||
.replace(/&/g, '-and-') // Replace & with 'and'
|
||||
.replace(/[^\w-]+/g, '') // Remove all non-word characters
|
||||
.replace(/--+/g, '-') // Replace multiple - with single -
|
||||
.replace(/^-+/, '') // Trim - from start of text
|
||||
.replace(/-+$/, '') // Trim - from end of text
|
||||
}
|
||||
|
||||
async handlePost (e) {
|
||||
e.preventDefault()
|
||||
const { name, botName } = this.state
|
||||
const slug = this.slugify(name)
|
||||
const botSlug = this.slugify(botName)
|
||||
|
||||
await this.props.createTeamMutation({
|
||||
variables: {
|
||||
name,
|
||||
slug,
|
||||
botName,
|
||||
botSlug
|
||||
}
|
||||
})
|
||||
this.setState({ open: false, name: '', botName: '' })
|
||||
}
|
||||
}
|
||||
|
||||
CreateTeamDialog.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
createTeamMutation: PropTypes.function
|
||||
}
|
||||
|
||||
export default withRouter(CreateTeamDialog)
|
||||
@@ -0,0 +1,50 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import Card from '@material-ui/core/Card'
|
||||
import CardContent from '@material-ui/core/CardContent'
|
||||
|
||||
import CreateTeam from './CreateTeam'
|
||||
|
||||
const styles = theme => ({
|
||||
card: {
|
||||
display: 'flex',
|
||||
width: '100%'
|
||||
},
|
||||
cardDetails: {
|
||||
display: 'flex',
|
||||
flex: 1
|
||||
},
|
||||
cardContent: {
|
||||
flex: 1
|
||||
},
|
||||
cardMedia: {
|
||||
flex: 1,
|
||||
width: 160,
|
||||
height: 160,
|
||||
justifyContent: 'flex-start'
|
||||
}
|
||||
})
|
||||
|
||||
const CreateTeamWrapper = ({ classes, authId }) => (
|
||||
<Grid container spacing={24}>
|
||||
<Grid item>
|
||||
<Card className={classes.card}>
|
||||
<div className={classes.cardDetails}>
|
||||
<CardContent className={classes.createCardContent}>
|
||||
<CreateTeam classes={classes} authId={authId} />
|
||||
</CardContent>
|
||||
</div>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
|
||||
CreateTeamWrapper.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
authId: PropTypes.string
|
||||
}
|
||||
|
||||
export default withStyles(styles)(CreateTeamWrapper)
|
||||
@@ -0,0 +1,42 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import Card from '@material-ui/core/Card'
|
||||
import CardMedia from '@material-ui/core/CardMedia'
|
||||
import CardActions from '@material-ui/core/CardActions'
|
||||
import Button from '@material-ui/core/Button'
|
||||
|
||||
import aaweb from '../../../assets/AlgonetWebPlatform.jpg'
|
||||
|
||||
export const DashEditor = ({ classes }) => (
|
||||
<Grid item md={6}>
|
||||
<Typography variant='h4' gutterBottom>
|
||||
Develop
|
||||
</Typography>
|
||||
<Grid container spacing={24}>
|
||||
<Grid item md={12}>
|
||||
<Card>
|
||||
<CardMedia
|
||||
className={classes.aawebMedia}
|
||||
image={aaweb}
|
||||
title='Image title'
|
||||
/>
|
||||
<CardActions>
|
||||
<Typography variant='h6' color='inherit' aligh='right'>
|
||||
<Button>Start Developing ></Button>
|
||||
</Typography>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
|
||||
DashEditor.propTypes = {
|
||||
classes: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default DashEditor
|
||||
@@ -0,0 +1,125 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Query } from 'react-apollo'
|
||||
import { withStateHandlers, lifecycle, compose } from 'recompose'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
|
||||
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
|
||||
|
||||
import DashTeamItem from './DashTeamItem'
|
||||
import CreateTeamDialog from './CreateTeamDialog'
|
||||
import { MessageCard } from '@superalgos/web-components'
|
||||
|
||||
import { isDefined, isString } from '../../../utils/js-helpers'
|
||||
import { getItem } from '../../../utils/local-storage'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
export const DashTeam = ({ classes, user = null }) => {
|
||||
let owner
|
||||
let authId = null
|
||||
log.debug('DashTeam: ', user)
|
||||
if (user !== null && isString(user)) {
|
||||
owner = JSON.parse(user)
|
||||
if (isDefined(owner.authId)) authId = owner.authId
|
||||
}
|
||||
log.debug('DashTeam 2: ', owner, authId)
|
||||
|
||||
if (authId === undefined || authId === null) {
|
||||
return (
|
||||
<Grid item md={6}>
|
||||
<Typography variant='h4' gutterBottom>
|
||||
Teams
|
||||
</Typography>
|
||||
<Typography variant='caption'>Loading...</Typography>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Grid item md={6} style={{ position: 'relative' }}>
|
||||
<Typography variant='h4' gutterBottom>
|
||||
Teams{' '}
|
||||
<Link to='/manage-teams' className={classes.dashLink}>
|
||||
Manage Teams
|
||||
</Link>
|
||||
</Typography>
|
||||
<Query query={GET_TEAMS_BY_OWNER} variables={{ authId }}>
|
||||
{({ loading, error, data }) => {
|
||||
log.debug('GET_TEAMS_BY_OWNER: ', loading, error, data)
|
||||
let errors
|
||||
let queryLoader
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
return (
|
||||
<Typography key={i} variant='caption'>
|
||||
{message}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
}
|
||||
if (!loading && !error) {
|
||||
if (data.teamsByOwner.length > 0) {
|
||||
return (
|
||||
<Grid container spacing={24}>
|
||||
{!loading &&
|
||||
data.teamsByOwner.map(team => (
|
||||
<DashTeamItem key={team.id} team={team} />
|
||||
))}
|
||||
{queryLoader}
|
||||
{errors}
|
||||
</Grid>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Grid container spacing={40}>
|
||||
<Grid item xs={10}>
|
||||
<MessageCard message='No teams yet...'>
|
||||
<CreateTeamDialog />
|
||||
</MessageCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<Grid container spacing={24}>
|
||||
<Typography variant='subtitle1' gutterBottom>
|
||||
Loading...
|
||||
</Typography>
|
||||
{queryLoader}
|
||||
{errors}
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
}}
|
||||
</Query>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
DashTeam.propTypes = {
|
||||
user: PropTypes.any,
|
||||
classes: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
const getUserOnMount = lifecycle({
|
||||
componentDidMount () {
|
||||
getItem('user').then(user => {
|
||||
this.setState({ user })
|
||||
}) // Set user to state
|
||||
}
|
||||
})
|
||||
|
||||
const mapStateToProps = withStateHandlers(() => ({ user: null }), {
|
||||
user: ({ user }) => () => ({ user })
|
||||
})
|
||||
|
||||
const DashTeamAuthId = compose(
|
||||
mapStateToProps,
|
||||
getUserOnMount
|
||||
)(DashTeam)
|
||||
|
||||
export default DashTeamAuthId
|
||||
@@ -0,0 +1,71 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import Card from '@material-ui/core/Card'
|
||||
import CardContent from '@material-ui/core/CardContent'
|
||||
import CardMedia from '@material-ui/core/CardMedia'
|
||||
import CardActions from '@material-ui/core/CardActions'
|
||||
|
||||
import ManageTeamDetails from './ManageTeamDetails'
|
||||
|
||||
const styles = theme => ({
|
||||
card: {
|
||||
display: 'flex',
|
||||
width: '100%'
|
||||
},
|
||||
cardDetails: {
|
||||
display: 'flex',
|
||||
flex: 1
|
||||
},
|
||||
cardContent: {
|
||||
flex: 1
|
||||
},
|
||||
cardMedia: {
|
||||
flex: 1,
|
||||
width: 160,
|
||||
height: 160,
|
||||
justifyContent: 'flex-start'
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
}
|
||||
})
|
||||
|
||||
const placeholder = 'data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22288%22%20height%3D%22225%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20288%20225%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_164edaf95ee%20text%20%7B%20fill%3A%23eceeef%3Bfont-weight%3Abold%3Bfont-family%3AArial%2C%20Helvetica%2C%20Open%20Sans%2C%20sans-serif%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_164edaf95ee%22%3E%3Crect%20width%3D%22288%22%20height%3D%22225%22%20fill%3D%22%2355595c%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%2296.32500076293945%22%20y%3D%22118.8%22%3EThumbnail%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E' // eslint-disable-line max-len
|
||||
|
||||
export const DashTeamItem = ({ classes, team }) => (
|
||||
<Grid item>
|
||||
<Card className={classes.card}>
|
||||
<div className={classes.cardDetails}>
|
||||
<CardMedia
|
||||
className={classes.cardMedia}
|
||||
image={team.profile !== null && team.profile.avatar !== undefined && team.profile.avatar !== null ? team.profile.avatar : placeholder}
|
||||
title='Image title'
|
||||
/>
|
||||
<CardContent className={classes.cardContent}>
|
||||
<Typography variant='h5'>{team.name}</Typography>
|
||||
<Typography variant='subtitle1' color='textSecondary'>
|
||||
{team.createdAt}
|
||||
</Typography>
|
||||
<Typography variant='subtitle1' paragraph>
|
||||
Members: {team.members.length}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<ManageTeamDetails team={team} />
|
||||
</CardActions>
|
||||
</div>
|
||||
</Card>
|
||||
</Grid>
|
||||
)
|
||||
|
||||
DashTeamItem.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
team: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default withStyles(styles)(DashTeamItem)
|
||||
@@ -0,0 +1,207 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Mutation } from 'react-apollo'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import Button from '@material-ui/core/Button'
|
||||
import AddIcon from '@material-ui/icons/Add'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import FormControl from '@material-ui/core/FormControl'
|
||||
import FormHelperText from '@material-ui/core/FormHelperText'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import { isEmpty } from '../../../utils/js-helpers'
|
||||
|
||||
import SEND_MEMBER_INVITE from '../../../graphql/teams/SendMemberInvite'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
dialogStyle: {
|
||||
padding: '3em'
|
||||
},
|
||||
buttonRight: {
|
||||
// position: 'absolute',
|
||||
// right: '3em'
|
||||
marginTop: '1em',
|
||||
marginRight: 'auto',
|
||||
marginLeft: 'auto',
|
||||
display: 'block',
|
||||
lineHeight: '1em'
|
||||
}
|
||||
})
|
||||
|
||||
export class InviteMemberDialog extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickOpen = this.handleClickOpen.bind(this)
|
||||
this.handleClose = this.handleClose.bind(this)
|
||||
this.handleChange = this.handleChange.bind(this)
|
||||
this.handleSubmit = this.handleSubmit.bind(this)
|
||||
this.validate = this.validate.bind(this)
|
||||
|
||||
this.state = {
|
||||
open: false,
|
||||
email: '',
|
||||
errors: {
|
||||
email: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { classes, teamId } = this.props
|
||||
|
||||
if (this.state.open) {
|
||||
return (
|
||||
<Mutation mutation={SEND_MEMBER_INVITE} >
|
||||
{(sendMemberInvite, { loading, error, data }) => {
|
||||
log.debug('sendMemberInvite: ', data)
|
||||
let errors
|
||||
let loader = null
|
||||
if (loading) {
|
||||
loader = (
|
||||
<Typography variant='subtitle1'>Submitting invitation...</Typography>
|
||||
)
|
||||
}
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
// const displayMessage = checkGraphQLError(message)
|
||||
return (
|
||||
<Typography key={i} variant='caption'>
|
||||
{message}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
variant='fab'
|
||||
color='primary'
|
||||
aria-label='Add'
|
||||
className={classes.buttonRight}
|
||||
onClick={this.handleClickOpen}
|
||||
>
|
||||
<AddIcon />
|
||||
</Button>
|
||||
<Dialog
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby='form-dialog-title'
|
||||
>
|
||||
<DialogTitle id='form-dialog-title'>Invite a New Team Member</DialogTitle>
|
||||
<DialogContent>
|
||||
{(data === undefined || data.sendMemberInviteSG !== 'Success') && (
|
||||
<FormControl
|
||||
required
|
||||
error={this.state.errors.email !== '' || (error !== undefined || error !== null)}
|
||||
>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin='dense'
|
||||
id='email'
|
||||
label='Email Address'
|
||||
type='text'
|
||||
fullWidth
|
||||
value={this.state.email}
|
||||
onChange={e => this.handleChange(e)}
|
||||
error={this.state.errors.email !== ''}
|
||||
/>
|
||||
{this.state.errors.email !== '' && (
|
||||
<FormHelperText>{this.state.errors.email}</FormHelperText>
|
||||
)}
|
||||
{error && <FormHelperText>{errors}</FormHelperText>}
|
||||
{loader}
|
||||
</FormControl>
|
||||
)}
|
||||
{data !== undefined && data.sendMemberInviteSG === 'Success' && (
|
||||
<Typography variant='subtitle1'>Invitation successfully sent!</Typography>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{(data === undefined || data.sendMemberInviteSG !== 'Success') && (
|
||||
<React.Fragment>
|
||||
<Button onClick={this.handleClose} color='primary'>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={e => {
|
||||
this.handleSubmit(e, sendMemberInvite, this.state.email, teamId)
|
||||
}}
|
||||
color='primary'
|
||||
>
|
||||
Send Invitation
|
||||
</Button>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{data !== undefined && data.sendMemberInviteSG === 'Success' && (
|
||||
<Button onClick={this.handleClose} color='primary'>
|
||||
Close
|
||||
</Button>
|
||||
)}
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</Mutation>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Button
|
||||
variant='fab'
|
||||
color='primary'
|
||||
aria-label='Add'
|
||||
className={classes.buttonRight}
|
||||
onClick={this.handleClickOpen}
|
||||
>
|
||||
<AddIcon />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
handleClickOpen () {
|
||||
this.setState({ open: true })
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
const errors = this.validate(e.target.value)
|
||||
if (isEmpty(errors)) {
|
||||
this.setState({ errors: { email: '' } })
|
||||
this.setState({ email: e.target.value })
|
||||
} else {
|
||||
this.setState({ errors: errors })
|
||||
this.setState({ email: e.target.value })
|
||||
}
|
||||
}
|
||||
|
||||
async handleSubmit (e, sendMemberInvite, email, teamId) {
|
||||
e.preventDefault()
|
||||
log.debug('sendMemberInvite submit: ', email, teamId)
|
||||
await sendMemberInvite({ variables: { email, teamId } })
|
||||
this.setState({ email: '' })
|
||||
}
|
||||
|
||||
validate (data) {
|
||||
const errors = {}
|
||||
if (data === '') errors.email = 'Please enter an email address'
|
||||
return errors
|
||||
}
|
||||
}
|
||||
|
||||
InviteMemberDialog.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
teamId: PropTypes.string
|
||||
}
|
||||
|
||||
export default withStyles(styles)(InviteMemberDialog)
|
||||
@@ -0,0 +1,225 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Mutation } from 'react-apollo'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import Button from '@material-ui/core/Button'
|
||||
import EditIcon from '@material-ui/icons/Edit'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogContentText from '@material-ui/core/DialogContentText'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
|
||||
import { MessageCard, ImageUpload } from '@superalgos/web-components'
|
||||
|
||||
import UPDATE_FB from '../../../graphql/teams/UpdateFBMutation'
|
||||
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
|
||||
|
||||
import { checkGraphQLError } from '../../../utils/graphql-errors'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
import GET_AZURE_SAS from '../../../graphql/teams/GetAzureSASMutation'
|
||||
|
||||
const styles = theme => ({
|
||||
dialogContainer: {
|
||||
display: 'block',
|
||||
margin: '3em',
|
||||
minWidth: 400
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
}
|
||||
})
|
||||
|
||||
export class ManageFBEdit extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickOpen = this.handleClickOpen.bind(this)
|
||||
this.handleClose = this.handleClose.bind(this)
|
||||
this.handleSubmit = this.handleSubmit.bind(this)
|
||||
this.handleAvatar = this.handleAvatar.bind(this)
|
||||
|
||||
const avatar = props.fb.avatar || ''
|
||||
this.authId = props.authId
|
||||
|
||||
this.state = {
|
||||
open: false,
|
||||
avatar: avatar
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
log.debug('ManageFBEdit ', this.props, this.props.slug, this.props.fb)
|
||||
const { classes, fb, slug } = this.props
|
||||
return (
|
||||
<Mutation
|
||||
mutation={UPDATE_FB}
|
||||
refetchQueries={[
|
||||
{
|
||||
query: GET_TEAMS_BY_OWNER,
|
||||
variables: { authId: this.authId }
|
||||
}
|
||||
]}
|
||||
>
|
||||
{(updateFB, { loading, error, data }) => {
|
||||
let errors
|
||||
let loader
|
||||
if (loading) {
|
||||
loader = (
|
||||
<Typography variant='caption'>Submitting team...</Typography>
|
||||
)
|
||||
}
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
const displayMessage = checkGraphQLError(message)
|
||||
log.debug('updateTeamProfile error:', displayMessage)
|
||||
return (
|
||||
<Typography key={i} variant='caption'>
|
||||
{message}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
size='small'
|
||||
color='primary'
|
||||
className={classes.buttonRight}
|
||||
onClick={this.handleClickOpen}
|
||||
>
|
||||
<EditIcon /> Edit
|
||||
</Button>
|
||||
<Dialog
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby='form-dialog-title'
|
||||
>
|
||||
<div classes={classes.dialogContainer}>
|
||||
<DialogTitle id='form-dialog-title'>
|
||||
Edit Financial Being
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Mutation mutation={GET_AZURE_SAS} >
|
||||
{(getAzureSAS, { loading, error, data }) => {
|
||||
log.debug('getAzureSAS: ', loading, error, data, fb)
|
||||
const AzureStorageUrl = process.env.AZURE_STORAGE_URL
|
||||
const containerName = slug
|
||||
let AzureSASURL
|
||||
if (!loading && data !== undefined) {
|
||||
AzureSASURL = data.getAzureSAS
|
||||
} else {
|
||||
getAzureSAS({ variables: { teamSlug: containerName } })
|
||||
}
|
||||
|
||||
let avatar = null
|
||||
if (fb.avatar !== undefined && fb.avatar !== null) avatar = fb.avatar
|
||||
if (this.state.avatar !== null) avatar = this.state.avatar
|
||||
if (this.state.avatar === null && fb.avatar === undefined) avatar = process.env.STORAGE_URL + '/module-teams/module-default/aa-avatar-default.png'
|
||||
|
||||
if (loading || data === undefined) {
|
||||
return (<MessageCard message='Loading...' />)
|
||||
} else {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DialogContentText>Update Financial Beings's Avatar</DialogContentText>
|
||||
<ImageUpload
|
||||
key='avatar'
|
||||
handleUrl={this.handleAvatar}
|
||||
fileName={`${fb.slug}-fb-avatar.jpg`}
|
||||
containerName={containerName}
|
||||
existingImage={avatar}
|
||||
cropContainer={{ x: 10, y: 10, width: 200, height: 200 }}
|
||||
cropPreviewBox={{ width: 350, height: 350 }}
|
||||
saveImageConfig={{
|
||||
quality: 0.6,
|
||||
maxWidth: 200,
|
||||
maxHeight: 200,
|
||||
autoRotate: true,
|
||||
mimeType: 'image/jpeg'
|
||||
}}
|
||||
AzureStorageUrl={AzureStorageUrl}
|
||||
AzureSASURL={AzureSASURL}
|
||||
cropRatio={1}
|
||||
debug
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}}
|
||||
</Mutation>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin='dense'
|
||||
id='name'
|
||||
label='Team Name'
|
||||
type='text'
|
||||
fullWidth
|
||||
disabled
|
||||
value={fb.name}
|
||||
/>
|
||||
{loader}
|
||||
{errors}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleClose} color='primary'>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={e => {
|
||||
this.handleSubmit(e, updateFB, fb.id, slug)
|
||||
}}
|
||||
color='primary'
|
||||
>
|
||||
Update Financial Being
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</Mutation>
|
||||
)
|
||||
}
|
||||
|
||||
handleClickOpen () {
|
||||
this.setState({ open: true })
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
|
||||
handleAvatar (avatarUrl) {
|
||||
log.debug('handleAvatar: ', avatarUrl)
|
||||
this.setState({ avatar: `${avatarUrl}?${Math.random()}` })
|
||||
}
|
||||
|
||||
async handleSubmit (e, updateFB, fbId, slug) {
|
||||
log.debug('handleSubmit: ', this.state)
|
||||
e.preventDefault()
|
||||
|
||||
await updateFB({
|
||||
variables: {
|
||||
fbId,
|
||||
avatar: this.state.avatar
|
||||
}
|
||||
})
|
||||
this.setState({ open: false })
|
||||
}
|
||||
}
|
||||
|
||||
ManageFBEdit.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
slug: PropTypes.string.isRequired,
|
||||
fb: PropTypes.object,
|
||||
authId: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageFBEdit)
|
||||
@@ -0,0 +1,55 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import Card from '@material-ui/core/Card'
|
||||
import CardActions from '@material-ui/core/CardActions'
|
||||
import CardContent from '@material-ui/core/CardContent'
|
||||
import CardMedia from '@material-ui/core/CardMedia'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
|
||||
import ManageFBEdit from './ManageFBEdit'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
export const ManageFBItem = ({ classes, team, authId }) => {
|
||||
log.debug('ManageFBItem', team, team.fb[0], team.profile.avatar)
|
||||
let avatar
|
||||
if (team.fb[0].avatar !== undefined && team.fb[0].avatar !== 'a') {
|
||||
avatar = team.fb[0].avatar
|
||||
} else {
|
||||
avatar = process.env.STORAGE_URL+'/module-teams/module-default/aa-avatar-default.png'
|
||||
}
|
||||
return (
|
||||
<Grid item xs={12}>
|
||||
<Card className={classes.card}>
|
||||
<div className={classes.cardDetails}>
|
||||
<CardMedia
|
||||
className={classes.cardMedia}
|
||||
image={avatar}
|
||||
title={team.name}
|
||||
/>
|
||||
<CardContent className={classes.cardContent}>
|
||||
<Typography gutterBottom variant='h5' component='h2'>
|
||||
{team.fb[0].name}
|
||||
</Typography>
|
||||
<Typography variant='caption' color='textSecondary'>
|
||||
{team.fb[0].kind}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<ManageFBEdit slug={team.slug} fb={team.fb[0]} authId={authId} />
|
||||
</CardActions>
|
||||
</div>
|
||||
</Card>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
ManageFBItem.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
team: PropTypes.object.isRequired,
|
||||
authId: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default ManageFBItem
|
||||
@@ -0,0 +1,152 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Query } from 'react-apollo'
|
||||
import { withStateHandlers, lifecycle, compose } from 'recompose'
|
||||
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import { MessageCard } from '@superalgos/web-components'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import { isDefined, isString } from '../../../utils/js-helpers'
|
||||
import { getItem } from '../../../utils/local-storage'
|
||||
|
||||
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
import ManageFBItem from './ManageFBItem'
|
||||
import CreateTeamDialog from './CreateTeamDialog'
|
||||
|
||||
const styles = theme => ({
|
||||
heroContent: {
|
||||
maxWidth: 600,
|
||||
margin: '0 auto',
|
||||
padding: `${theme.spacing.unit * 8}px 0 ${theme.spacing.unit * 6}px`
|
||||
},
|
||||
cardGrid: {
|
||||
padding: `${theme.spacing.unit * 8}px 0`
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
},
|
||||
card: {
|
||||
display: 'flex',
|
||||
width: '100%'
|
||||
},
|
||||
cardDetails: {
|
||||
display: 'flex',
|
||||
flex: 1
|
||||
},
|
||||
cardContent: {
|
||||
flex: 1
|
||||
},
|
||||
cardMedia: {
|
||||
flex: 1,
|
||||
width: 100,
|
||||
height: 100,
|
||||
maxWidth: 100,
|
||||
justifyContent: 'flex-start'
|
||||
}
|
||||
})
|
||||
|
||||
export const ManageFBList = ({ classes, user = null }) => {
|
||||
let owner
|
||||
let authId = null
|
||||
log.debug('ManageTeamsList: ', user)
|
||||
if (user !== null && isString(user)) {
|
||||
owner = JSON.parse(user)
|
||||
if (isDefined(owner.authId)) authId = owner.authId
|
||||
}
|
||||
log.debug('ManageTeamsList 2: ', owner, authId)
|
||||
if (authId === undefined || authId === null) {
|
||||
return (
|
||||
<Grid container spacing={40}>
|
||||
<Grid>
|
||||
<MessageCard message='Loading...' />
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Query
|
||||
query={GET_TEAMS_BY_OWNER}
|
||||
variables={{ authId }}
|
||||
fetchPolicy='network-only'
|
||||
>
|
||||
{({ loading, error, data }) => {
|
||||
log.debug('GET_TEAMS_BY_OWNER: ', loading, error, data)
|
||||
|
||||
let errors = null
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
return <MessageCard message={message} />
|
||||
})
|
||||
}
|
||||
if (!loading && !error) {
|
||||
if (data.teamsByOwner.length > 0) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid container spacing={40}>
|
||||
{!loading &&
|
||||
data.teamsByOwner.map(team => (
|
||||
<ManageFBItem
|
||||
key={team.id}
|
||||
team={team}
|
||||
classes={classes}
|
||||
authId={authId}
|
||||
/>
|
||||
))}
|
||||
{errors}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Grid container spacing={40}>
|
||||
<Grid item xs={10}>
|
||||
<MessageCard message='You don’t have any teams. Create one!'>
|
||||
<CreateTeamDialog authId={authId} />
|
||||
</MessageCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<Grid container spacing={40}>
|
||||
<Grid item xs={12}>
|
||||
<MessageCard message='Loading...' />
|
||||
{errors !== null && <MessageCard message={errors} />}
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
}}
|
||||
</Query>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ManageFBList.propTypes = {
|
||||
user: PropTypes.any,
|
||||
classes: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
const getUserOnMount = lifecycle({
|
||||
componentDidMount () {
|
||||
getItem('user').then(user => {
|
||||
this.setState({ user })
|
||||
}) // Set user to state
|
||||
}
|
||||
})
|
||||
|
||||
const mapStateToProps = withStateHandlers(() => ({ user: null }), {
|
||||
user: ({ user }) => () => ({ user })
|
||||
})
|
||||
|
||||
const ManageFBListAuthId = compose(
|
||||
mapStateToProps,
|
||||
getUserOnMount
|
||||
)(ManageFBList)
|
||||
|
||||
export default withStyles(styles)(ManageFBListAuthId)
|
||||
@@ -0,0 +1,153 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import { Mutation } from 'react-apollo'
|
||||
|
||||
import Button from '@material-ui/core/Button'
|
||||
import PersonAddDisabledIcon from '@material-ui/icons/PersonAddDisabled'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
|
||||
import REMOVE_TEAM_MEMBER from '../../../graphql/teams/RemoveTeamMemberMutation'
|
||||
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
|
||||
|
||||
import { checkGraphQLError } from '../../../utils/graphql-errors'
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
dialogContainer: {
|
||||
display: 'block',
|
||||
margin: '3em',
|
||||
minWidth: 400
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
}
|
||||
})
|
||||
|
||||
export class ManageMemberDelete extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickOpen = this.handleClickOpen.bind(this)
|
||||
this.handleClose = this.handleClose.bind(this)
|
||||
this.handleChange = this.handleChange.bind(this)
|
||||
this.handleSubmit = this.handleSubmit.bind(this)
|
||||
|
||||
this.state = {
|
||||
open: false
|
||||
}
|
||||
}
|
||||
|
||||
handleClickOpen () {
|
||||
this.setState({ open: true })
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
this.setState({ name: e.target.value })
|
||||
}
|
||||
|
||||
render () {
|
||||
log.debug(this.props, this.props.teamId)
|
||||
const { classes, teamId, authId } = this.props
|
||||
return (
|
||||
<Mutation
|
||||
mutation={REMOVE_TEAM_MEMBER}
|
||||
refetchQueries={[{ query: GET_TEAMS_BY_OWNER }]}
|
||||
>
|
||||
{(deleteTeam, { loading, error, data }) => {
|
||||
let errors
|
||||
let loader
|
||||
if (loading) {
|
||||
loader = (
|
||||
<Typography variant='caption'>Submitting team...</Typography>
|
||||
)
|
||||
}
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
const displayMessage = checkGraphQLError(message)
|
||||
log.debug('createTeam error:', displayMessage)
|
||||
return (
|
||||
<Typography key={i} variant='caption'>
|
||||
{message}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Button
|
||||
size='small'
|
||||
color='primary'
|
||||
className={classes.buttonRight}
|
||||
onClick={this.handleClickOpen}
|
||||
>
|
||||
<PersonAddDisabledIcon /> Remove
|
||||
</Button>
|
||||
<Dialog
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby='form-dialog-title'
|
||||
>
|
||||
<div classes={classes.dialogContainer}>
|
||||
<DialogTitle id='form-dialog-title'>
|
||||
Remove Team Member
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography variant='subtitle1' color='primary'>
|
||||
DANGER - Removing your team cannot be undone
|
||||
</Typography>
|
||||
<Typography variant='subtitle1'>
|
||||
Are you sure you want to remove this team member?
|
||||
</Typography>
|
||||
{loader}
|
||||
{errors}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleClose} color='primary'>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={e => {
|
||||
this.handleSubmit(
|
||||
e,
|
||||
deleteTeam,
|
||||
teamId,
|
||||
authId
|
||||
)
|
||||
}}
|
||||
color='primary'
|
||||
>
|
||||
Remove Member
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</div>
|
||||
</Dialog>
|
||||
</React.Fragment>
|
||||
)
|
||||
}}
|
||||
</Mutation>
|
||||
)
|
||||
}
|
||||
|
||||
async handleSubmit (e, deleteTeam, teamId, memberId) {
|
||||
e.preventDefault()
|
||||
await deleteTeam({ variables: { teamId, memberId } })
|
||||
this.setState({ open: false })
|
||||
}
|
||||
}
|
||||
|
||||
ManageMemberDelete.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
teamId: PropTypes.string.isRequired,
|
||||
authId: PropTypes.string
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageMemberDelete)
|
||||
@@ -0,0 +1,170 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import { Mutation } from 'react-apollo'
|
||||
|
||||
import Button from '@material-ui/core/Button'
|
||||
import AssignmentIndIcon from '@material-ui/icons/AssignmentInd'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import MenuItem from '@material-ui/core/MenuItem'
|
||||
|
||||
import SET_TEAM_MEMBER_ROLE from '../../../graphql/teams/SetTeamMemberRoleMutation'
|
||||
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
|
||||
|
||||
import { checkGraphQLError } from '../../../utils/graphql-errors'
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
dialogContainer: {
|
||||
display: 'block',
|
||||
margin: '3em',
|
||||
minWidth: 400
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
}
|
||||
})
|
||||
|
||||
const roles = [
|
||||
{ role: 'Admin' },
|
||||
{ role: 'Member' }
|
||||
]
|
||||
|
||||
export class ManageMemberRole extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickOpen = this.handleClickOpen.bind(this)
|
||||
this.handleClose = this.handleClose.bind(this)
|
||||
this.handleChange = this.handleChange.bind(this)
|
||||
this.handleSubmit = this.handleSubmit.bind(this)
|
||||
|
||||
this.state = {
|
||||
open: false,
|
||||
role: ''
|
||||
}
|
||||
}
|
||||
|
||||
handleClickOpen () {
|
||||
this.setState({ open: true })
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
this.setState({ name: e.target.value })
|
||||
}
|
||||
|
||||
render () {
|
||||
log.debug(this.props, this.props.teamId)
|
||||
const { classes, teamId, authId } = this.props
|
||||
return (
|
||||
<Mutation
|
||||
mutation={SET_TEAM_MEMBER_ROLE}
|
||||
refetchQueries={[{ query: GET_TEAMS_BY_OWNER }]}
|
||||
>
|
||||
{(setTeamMemberRole, { loading, error, data }) => {
|
||||
let errors
|
||||
let loader
|
||||
if (loading) {
|
||||
loader = (
|
||||
<Typography variant='caption'>Submitting team...</Typography>
|
||||
)
|
||||
}
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
const displayMessage = checkGraphQLError(message)
|
||||
log.debug('createTeam error:', displayMessage)
|
||||
return (
|
||||
<Typography key={i} variant='caption'>
|
||||
{message}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Button
|
||||
size='small'
|
||||
color='primary'
|
||||
className={classes.buttonRight}
|
||||
onClick={this.handleClickOpen}
|
||||
>
|
||||
<AssignmentIndIcon /> Set Role
|
||||
</Button>
|
||||
<Dialog
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby='form-dialog-title'
|
||||
>
|
||||
<div classes={classes.dialogContainer}>
|
||||
<DialogTitle id='form-dialog-title'>
|
||||
Set Team Member Role
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography variant='subtitle1'>
|
||||
Admin members can approve new team members, send team invitations, and remove team members.
|
||||
</Typography>
|
||||
<TextField
|
||||
select
|
||||
label='Team Role'
|
||||
value={this.state.role}
|
||||
onChange={(e) => this.setState({ role: e.target.value })}
|
||||
fullWidth
|
||||
>
|
||||
{roles.map(role => (
|
||||
<MenuItem key={role.role} value={role.role}>{role.role}</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
{loader}
|
||||
{errors}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleClose} color='primary'>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={e => {
|
||||
this.handleSubmit(
|
||||
e,
|
||||
setTeamMemberRole,
|
||||
teamId,
|
||||
authId
|
||||
)
|
||||
}}
|
||||
color='primary'
|
||||
>
|
||||
Set Member Role
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</div>
|
||||
</Dialog>
|
||||
</React.Fragment>
|
||||
)
|
||||
}}
|
||||
</Mutation>
|
||||
)
|
||||
}
|
||||
|
||||
async handleSubmit (e, setTeamMemberRole, teamId, authId) {
|
||||
e.preventDefault()
|
||||
const role = this.state.role
|
||||
await setTeamMemberRole({ variables: { teamId, memberId: authId, role: role } })
|
||||
this.setState({ open: false })
|
||||
}
|
||||
}
|
||||
|
||||
ManageMemberRole.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
teamId: PropTypes.string.isRequired,
|
||||
authId: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageMemberRole)
|
||||
@@ -0,0 +1,101 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import Button from '@material-ui/core/Button'
|
||||
import HistoryIcon from '@material-ui/icons/History'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import Card from '@material-ui/core/Card'
|
||||
import CardContent from '@material-ui/core/CardContent'
|
||||
import CardActions from '@material-ui/core/CardActions'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
dialogContainer: {
|
||||
display: 'block',
|
||||
margin: '3em',
|
||||
minWidth: 400
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
}
|
||||
})
|
||||
|
||||
export class ManageMemberStatus extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickOpen = this.handleClickOpen.bind(this)
|
||||
this.handleClose = this.handleClose.bind(this)
|
||||
|
||||
this.state = {
|
||||
open: false,
|
||||
role: ''
|
||||
}
|
||||
}
|
||||
|
||||
handleClickOpen () {
|
||||
this.setState({ open: true })
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
this.setState({ name: e.target.value })
|
||||
}
|
||||
|
||||
render () {
|
||||
log.debug(this.props, this.props.status)
|
||||
const { classes, status } = this.props
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
size='small'
|
||||
color='primary'
|
||||
className={this.props.classes.buttonRight}
|
||||
onClick={this.handleClickOpen}
|
||||
>
|
||||
<HistoryIcon /> Status Details
|
||||
</Button>
|
||||
<Dialog
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby='form-dialog-title'
|
||||
>
|
||||
<div classes={classes.dialogContainer}>
|
||||
<Card className={classes.card}>
|
||||
<CardContent>
|
||||
<Typography variant='h6' id='form-dialog-title'>
|
||||
Member Status Details
|
||||
</Typography>
|
||||
{status !== null && status.map(stat => {
|
||||
return (
|
||||
<Typography variant='h6' id='form-dialog-title' key={stat.createdAt}>
|
||||
{stat.status} | {stat.reason} | {stat.createdAt}
|
||||
</Typography>
|
||||
)
|
||||
})}
|
||||
<CardActions>
|
||||
<Button onClick={this.handleClose} color='primary'>
|
||||
Close
|
||||
</Button>
|
||||
</CardActions>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ManageMemberStatus.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
status: PropTypes.array
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageMemberStatus)
|
||||
@@ -0,0 +1,66 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
|
||||
import ManageMemberDelete from './ManageMemberDelete'
|
||||
import ManageMemberRole from './ManageMemberRole'
|
||||
import ManageMemberStatus from './ManageMemberStatus'
|
||||
import { MessageCard } from '@superalgos/web-components'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
export const ManageMembersItem = ({ classes, member, teamId, authId }) => {
|
||||
log.debug('ManageMembersItem', member)
|
||||
let profile = null
|
||||
let email = null
|
||||
let memberStatus = null
|
||||
if (member.member !== null) {
|
||||
profile = member.member
|
||||
}
|
||||
if (member.member === null && member.email !== null) {
|
||||
email = member.email
|
||||
}
|
||||
if (member.status.length > 0) memberStatus = member.status
|
||||
if (profile !== null || email !== null) {
|
||||
return (
|
||||
<Grid container className={classes.memberContainer}>
|
||||
<Grid item xs={4}>
|
||||
<Typography gutterBottom variant='subtitle1' component='h2' align='left' >
|
||||
{profile !== null ? profile.alias : email}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<Typography color='textSecondary' variant='body1' component='h4' align='left' >
|
||||
{email !== null ? `${memberStatus[memberStatus.length - 1].status} | Sent: ${memberStatus[memberStatus.length - 1].createdAt}` : `${member.role} | Member since: `}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
{ profile !== null && member.role !== 'OWNER' && (
|
||||
<ManageMemberRole teamId={teamId} authId={profile !== null ? profile.authId : null} className={classes.buttonRight} />
|
||||
)}
|
||||
{(email !== null || member.role !== 'OWNER') && (
|
||||
<ManageMemberStatus status={member.status} className={classes.buttonRight} />
|
||||
)}
|
||||
{(email !== null || member.role !== 'OWNER') && (
|
||||
<ManageMemberDelete teamId={teamId} authId={profile !== null ? profile.authId : null} email={email !== null ? email : null} className={classes.buttonRight} />
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<MessageCard message='No Member info found' />
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ManageMembersItem.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
member: PropTypes.object.isRequired,
|
||||
teamId: PropTypes.string.isRequired,
|
||||
authId: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default ManageMembersItem
|
||||
@@ -0,0 +1,118 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import Card from '@material-ui/core/Card'
|
||||
import CardContent from '@material-ui/core/CardContent'
|
||||
import CardMedia from '@material-ui/core/CardMedia'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
|
||||
import InviteMemberDialog from './InviteMemberDialog'
|
||||
import ManageMembersItem from './ManageMembersItem'
|
||||
import { MessageCard } from '@superalgos/web-components'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
heroContent: {
|
||||
maxWidth: 600,
|
||||
margin: '0 auto',
|
||||
padding: `${theme.spacing.unit * 8}px 0 ${theme.spacing.unit * 6}px`
|
||||
},
|
||||
cardGrid: {
|
||||
padding: `${theme.spacing.unit * 8}px 0`
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
},
|
||||
card: {
|
||||
display: 'flex',
|
||||
width: '100%'
|
||||
},
|
||||
cardDetails: {
|
||||
display: 'flex',
|
||||
flex: 1
|
||||
},
|
||||
cardContent: {
|
||||
flex: 1
|
||||
},
|
||||
cardMedia: {
|
||||
flex: 1,
|
||||
width: 100,
|
||||
height: 100,
|
||||
maxWidth: 100,
|
||||
justifyContent: 'flex-start'
|
||||
},
|
||||
cardMemberMedia: {
|
||||
flex: 1,
|
||||
width: 36,
|
||||
height: 36,
|
||||
maxWidth: 36,
|
||||
justifyContent: 'flex-start'
|
||||
},
|
||||
memberContainer: {
|
||||
margin: 10,
|
||||
padding: 6
|
||||
}
|
||||
})
|
||||
|
||||
export const ManageMembersTeamItem = ({ classes, team, authId }) => {
|
||||
log.debug('ManageMembersItem', team, team.profile, team.profile.avatar)
|
||||
let avatar
|
||||
if (team.profile.avatar !== undefined && team.profile.avatar !== 'a') {
|
||||
avatar = team.profile.avatar
|
||||
} else {
|
||||
avatar = process.env.STORAGE_URL + '/module-teams/module-default/aa-avatar-default.png'
|
||||
}
|
||||
if (team.members.length > 0) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12} key={team.id}>
|
||||
<Card className={classes.card}>
|
||||
<div className={classes.cardDetails}>
|
||||
<CardContent className={classes.cardContent}>
|
||||
<Grid container>
|
||||
<Grid item xs={2}>
|
||||
<CardMedia
|
||||
className={classes.cardMedia}
|
||||
image={avatar}
|
||||
title={team.name}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<Typography gutterBottom variant='h5' component='h2'>
|
||||
{team.name}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<InviteMemberDialog teamId={team.id} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
{team.members.length > 0 && team.members.map((member, index) => (
|
||||
<ManageMembersItem
|
||||
member={member}
|
||||
teamId={team.id}
|
||||
authId={authId}
|
||||
classes={classes}
|
||||
key={`${team.id}-${index}`}
|
||||
/>
|
||||
))}
|
||||
</CardContent>
|
||||
</div>
|
||||
</Card>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)
|
||||
} else {
|
||||
return <MessageCard message='No Member info found' />
|
||||
}
|
||||
}
|
||||
|
||||
ManageMembersTeamItem.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
team: PropTypes.object.isRequired,
|
||||
authId: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageMembersTeamItem)
|
||||
@@ -0,0 +1,157 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import { Mutation } from 'react-apollo'
|
||||
|
||||
import Button from '@material-ui/core/Button'
|
||||
import DeleteIcon from '@material-ui/icons/Delete'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
|
||||
import DELETE_TEAM from '../../../graphql/teams/DeleteTeamMutation'
|
||||
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
|
||||
|
||||
import { checkGraphQLError } from '../../../utils/graphql-errors'
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
dialogContainer: {
|
||||
display: 'block',
|
||||
margin: '3em',
|
||||
minWidth: 400
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
}
|
||||
})
|
||||
|
||||
export class ManageTeamDelete extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickOpen = this.handleClickOpen.bind(this)
|
||||
this.handleClose = this.handleClose.bind(this)
|
||||
this.handleChange = this.handleChange.bind(this)
|
||||
this.handleSubmit = this.handleSubmit.bind(this)
|
||||
|
||||
this.state = {
|
||||
open: false
|
||||
}
|
||||
}
|
||||
|
||||
handleClickOpen () {
|
||||
this.setState({ open: true })
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
this.setState({ name: e.target.value })
|
||||
}
|
||||
|
||||
render () {
|
||||
log.debug(this.props, this.props.slug)
|
||||
const { classes, authId } = this.props
|
||||
return (
|
||||
<Mutation
|
||||
mutation={DELETE_TEAM}
|
||||
refetchQueries={[
|
||||
{
|
||||
query: GET_TEAMS_BY_OWNER,
|
||||
variables: { authId }
|
||||
}
|
||||
]}
|
||||
>
|
||||
{(deleteTeam, { loading, error, data }) => {
|
||||
let errors
|
||||
let loader
|
||||
if (loading) {
|
||||
loader = (
|
||||
<Typography variant='caption'>Submitting team...</Typography>
|
||||
)
|
||||
}
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
const displayMessage = checkGraphQLError(message)
|
||||
log.debug('createTeam error:', displayMessage)
|
||||
return (
|
||||
<Typography key={i} variant='caption'>
|
||||
{message}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
size='small'
|
||||
color='primary'
|
||||
className={classes.buttonRight}
|
||||
onClick={this.handleClickOpen}
|
||||
>
|
||||
<DeleteIcon /> Delete
|
||||
</Button>
|
||||
<Dialog
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby='form-dialog-title'
|
||||
>
|
||||
<div classes={classes.dialogContainer}>
|
||||
<DialogTitle id='form-dialog-title'>
|
||||
Delete Team Team
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography variant='subtitle1' color='primary'>
|
||||
DANGER - Deleting your team cannot be undone
|
||||
</Typography>
|
||||
<Typography variant='subtitle1'>
|
||||
Are you sure you want to delete this team?
|
||||
</Typography>
|
||||
{loader}
|
||||
{errors}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleClose} color='primary'>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={e => {
|
||||
this.handleSubmit(
|
||||
e,
|
||||
deleteTeam,
|
||||
this.props.slug
|
||||
)
|
||||
}}
|
||||
color='primary'
|
||||
>
|
||||
Delete Team
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</Mutation>
|
||||
)
|
||||
}
|
||||
|
||||
async handleSubmit (e, deleteTeam, slug) {
|
||||
e.preventDefault()
|
||||
await deleteTeam({ variables: { slug } })
|
||||
this.setState({ open: false })
|
||||
}
|
||||
}
|
||||
|
||||
ManageTeamDelete.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
slug: PropTypes.string.isRequired,
|
||||
authId: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageTeamDelete)
|
||||
@@ -0,0 +1,173 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import Button from '@material-ui/core/Button'
|
||||
import DetailsIcon from '@material-ui/icons/Details'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import Card from '@material-ui/core/Card'
|
||||
import CardContent from '@material-ui/core/CardContent'
|
||||
import CardActions from '@material-ui/core/CardActions'
|
||||
import CardMedia from '@material-ui/core/CardMedia'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
dialogContainer: {
|
||||
display: 'block',
|
||||
margin: '3em',
|
||||
minWidth: 400
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
},
|
||||
cardGrid: {
|
||||
padding: `${theme.spacing.unit * 8}px 0`
|
||||
},
|
||||
card: {
|
||||
display: 'flex',
|
||||
width: '100%'
|
||||
},
|
||||
cardDetails: {
|
||||
display: 'flex',
|
||||
flex: 1
|
||||
},
|
||||
cardContent: {
|
||||
flex: 1
|
||||
},
|
||||
cardAvatarMedia: {
|
||||
flex: 1,
|
||||
width: 100,
|
||||
height: 100,
|
||||
maxWidth: 100,
|
||||
justifyContent: 'flex-start'
|
||||
},
|
||||
cardBannerMedia: {
|
||||
flex: 1,
|
||||
width: 600,
|
||||
height: 150,
|
||||
justifyContent: 'flex-start'
|
||||
}
|
||||
})
|
||||
|
||||
export class ManageTeamDetails extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickOpen = this.handleClickOpen.bind(this)
|
||||
this.handleClose = this.handleClose.bind(this)
|
||||
|
||||
this.state = {
|
||||
open: false
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
log.debug(this.props.team)
|
||||
const { classes } = this.props
|
||||
const { name, members, profile, createdAt } = this.props.team
|
||||
let avatar
|
||||
let banner
|
||||
if (profile.avatar !== undefined && profile.avatar !== null) {
|
||||
avatar = profile.avatar
|
||||
} else {
|
||||
avatar = process.env.STORAGE_URL + '/module-teams/module-default/aa-avatar-default.png'
|
||||
}
|
||||
if (profile.banner !== undefined && profile.banner !== null) {
|
||||
banner = profile.banner
|
||||
} else {
|
||||
banner = process.env.STORAGE_URL + '/module-teams/module-default/aa-banner-default.png'
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
size='small'
|
||||
color='primary'
|
||||
className={this.props.classes.buttonRight}
|
||||
onClick={this.handleClickOpen}
|
||||
>
|
||||
<DetailsIcon /> Details
|
||||
</Button>
|
||||
<Dialog
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby='form-dialog-title'
|
||||
>
|
||||
<div classes={classes.dialogContainer}>
|
||||
<Card className={classes.card}>
|
||||
<CardContent>
|
||||
<Typography variant='h6' id='form-dialog-title'>
|
||||
{name} Team Details
|
||||
</Typography>
|
||||
<CardMedia
|
||||
className={classes.cardBannerMedia}
|
||||
image={banner}
|
||||
title={`${name} ${banner}`}
|
||||
/>
|
||||
<CardMedia
|
||||
className={classes.cardAvatarMedia}
|
||||
image={avatar}
|
||||
title={`${name} ${avatar}`}
|
||||
/>
|
||||
<Typography
|
||||
variant='h2'
|
||||
align='center'
|
||||
color='textPrimary'
|
||||
gutterBottom
|
||||
>
|
||||
{name}
|
||||
</Typography>
|
||||
<Typography variant='subtitle1' color='textSecondary'>
|
||||
{createdAt}
|
||||
</Typography>
|
||||
<Typography variant='subtitle1' color='textSecondary'>
|
||||
Motto: {profile.motto}
|
||||
</Typography>
|
||||
<Typography variant='subtitle1' color='textSecondary'>
|
||||
Description: {profile.description}
|
||||
</Typography>
|
||||
<Typography variant='subtitle1' paragraph gutterBottom>
|
||||
Members: {members.length}
|
||||
</Typography>
|
||||
<Typography variant='subtitle1' color='primary'>
|
||||
Team Admin:
|
||||
{members.map(member => {
|
||||
if (member.role === 'OWNER' || member.role === 'ADMIN') {
|
||||
if (member.member !== null &&
|
||||
member.member.alias !== undefined &&
|
||||
member.member.alias !== null
|
||||
) {
|
||||
return member.member.alias
|
||||
}
|
||||
}
|
||||
})}
|
||||
</Typography>
|
||||
<CardActions>
|
||||
<Button onClick={this.handleClose} color='primary'>
|
||||
Close
|
||||
</Button>
|
||||
</CardActions>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
handleClickOpen () {
|
||||
this.setState({ open: true })
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
}
|
||||
|
||||
ManageTeamDetails.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
team: PropTypes.any
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageTeamDetails)
|
||||
@@ -0,0 +1,301 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Mutation } from 'react-apollo'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import Button from '@material-ui/core/Button'
|
||||
import EditIcon from '@material-ui/icons/Edit'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogContentText from '@material-ui/core/DialogContentText'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
|
||||
import { MessageCard, ImageUpload } from '@superalgos/web-components'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
import UPDATE_TEAM_PROFILE from '../../../graphql/teams/UpdateTeamProfileMutation'
|
||||
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
|
||||
|
||||
import { checkGraphQLError } from '../../../utils/graphql-errors'
|
||||
|
||||
import GET_AZURE_SAS from '../../../graphql/teams/GetAzureSASMutation'
|
||||
|
||||
const styles = theme => ({
|
||||
dialogContainer: {
|
||||
display: 'block',
|
||||
margin: '3em',
|
||||
minWidth: 400
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
}
|
||||
})
|
||||
|
||||
export class ManageTeamEdit extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickOpen = this.handleClickOpen.bind(this)
|
||||
this.handleClose = this.handleClose.bind(this)
|
||||
this.handleChange = this.handleChange.bind(this)
|
||||
this.handleSubmit = this.handleSubmit.bind(this)
|
||||
this.handleAvatar = this.handleAvatar.bind(this)
|
||||
this.handleBanner = this.handleBanner.bind(this)
|
||||
|
||||
log.debug('ManageTeamEdit', props.team)
|
||||
const motto = props.team.profile.motto || ''
|
||||
const description = props.team.profile.description || ''
|
||||
const avatar = props.team.profile.avatar || ''
|
||||
const banner = props.team.profile.banner || ''
|
||||
this.authId = props.authId
|
||||
|
||||
this.state = {
|
||||
open: false,
|
||||
motto: motto,
|
||||
description: description,
|
||||
avatar: avatar,
|
||||
banner: banner
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
log.debug('ManageTeamEdit ', this.props, this.props.slug, this.props.team)
|
||||
const { classes, team } = this.props
|
||||
return (
|
||||
<Mutation
|
||||
mutation={UPDATE_TEAM_PROFILE}
|
||||
refetchQueries={[
|
||||
{
|
||||
query: GET_TEAMS_BY_OWNER,
|
||||
variables: { authId: this.authId }
|
||||
}
|
||||
]}
|
||||
>
|
||||
{(updateTeamProfile, { loading, error, data }) => {
|
||||
let errors
|
||||
let loader
|
||||
if (loading) {
|
||||
loader = (
|
||||
<Typography variant='caption'>Submitting team...</Typography>
|
||||
)
|
||||
}
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
const displayMessage = checkGraphQLError(message)
|
||||
log.debug('updateTeamProfile error:', displayMessage)
|
||||
return (
|
||||
<Typography key={i} variant='caption'>
|
||||
{message}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
size='small'
|
||||
color='primary'
|
||||
className={classes.buttonRight}
|
||||
onClick={this.handleClickOpen}
|
||||
>
|
||||
<EditIcon /> Edit
|
||||
</Button>
|
||||
<Dialog
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby='form-dialog-title'
|
||||
>
|
||||
<div classes={classes.dialogContainer}>
|
||||
<DialogTitle id='form-dialog-title'>
|
||||
Edit Team Details
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Mutation mutation={GET_AZURE_SAS} >
|
||||
{(getAzureSAS, { loading, error, data }) => {
|
||||
log.debug('getAzureSAS: ', loading, error, data, team.profile)
|
||||
const AzureStorageUrl = process.env.AZURE_STORAGE_URL
|
||||
const containerName = team.slug
|
||||
let AzureSASURL
|
||||
if (!loading && data !== undefined) {
|
||||
AzureSASURL = data.getAzureSAS
|
||||
} else {
|
||||
getAzureSAS({ variables: { teamSlug: containerName } })
|
||||
}
|
||||
|
||||
let avatar = null
|
||||
if (team.profile !== null && team.profile.avatar !== undefined && team.profile.avatar !== null) avatar = team.profile.avatar
|
||||
if (this.state.avatar !== null) avatar = this.state.avatar
|
||||
if (this.state.avatar === null && team.profile !== null && team.profile.avatar === undefined) avatar = process.env.STORAGE_URL + '/module-teams/module-default/aa-avatar-default.png'
|
||||
let banner = null
|
||||
if (team.profile !== null && team.profile.banner !== undefined && team.profile.banner !== null) banner = team.profile.banner
|
||||
if (this.state.banner !== null) banner = this.state.banner
|
||||
if (this.state.banner === null && team.profile !== null && team.profile.banner === undefined) banner = process.env.STORAGE_URL + '/module-teams/module-default/aa-banner-default.png'
|
||||
log.debug('team images: ', avatar, banner)
|
||||
|
||||
if (loading || data === undefined) {
|
||||
return (<MessageCard message='Loading...' />)
|
||||
} else {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ImageUpload
|
||||
key='banner'
|
||||
handleUrl={this.handleBanner}
|
||||
fileName={`${team.slug}-banner.jpg`}
|
||||
containerName={containerName}
|
||||
existingImage={banner}
|
||||
cropContainer={{ x: 10, y: 10, width: 800, height: 200 }}
|
||||
cropPreviewBox={{ width: 650, height: 200 }}
|
||||
saveImageConfig={{
|
||||
quality: 0.6,
|
||||
maxWidth: 800,
|
||||
maxHeight: 200,
|
||||
autoRotate: true,
|
||||
mimeType: 'image/jpeg'
|
||||
}}
|
||||
AzureStorageUrl={AzureStorageUrl}
|
||||
AzureSASURL={AzureSASURL}
|
||||
cropRatio={4}
|
||||
debug
|
||||
/>
|
||||
<ImageUpload
|
||||
key='avatar'
|
||||
handleUrl={this.handleAvatar}
|
||||
fileName={`${team.slug}-avatar.jpg`}
|
||||
containerName={containerName}
|
||||
existingImage={avatar}
|
||||
cropContainer={{ x: 10, y: 10, width: 200, height: 200 }}
|
||||
cropPreviewBox={{ width: 350, height: 350 }}
|
||||
saveImageConfig={{
|
||||
quality: 0.6,
|
||||
maxWidth: 200,
|
||||
maxHeight: 200,
|
||||
autoRotate: true,
|
||||
mimeType: 'image/jpeg'
|
||||
}}
|
||||
AzureStorageUrl={AzureStorageUrl}
|
||||
AzureSASURL={AzureSASURL}
|
||||
cropRatio={1}
|
||||
debug
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}}
|
||||
</Mutation>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin='dense'
|
||||
id='name'
|
||||
label='Team Name'
|
||||
type='text'
|
||||
fullWidth
|
||||
disabled
|
||||
value={team.name}
|
||||
/>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin='dense'
|
||||
id='motto'
|
||||
label='Team Motto'
|
||||
type='text'
|
||||
fullWidth
|
||||
value={this.state.motto}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<DialogContentText>Team description:</DialogContentText>
|
||||
<TextField
|
||||
margin='dense'
|
||||
id='description'
|
||||
label='Team Description'
|
||||
type='text'
|
||||
rows={4}
|
||||
multiline
|
||||
fullWidth
|
||||
value={this.state.description}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
{loader}
|
||||
{errors}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleClose} color='primary'>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={e => {
|
||||
this.handleSubmit(e, updateTeamProfile, this.props.slug)
|
||||
}}
|
||||
color='primary'
|
||||
>
|
||||
Update Team
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</Mutation>
|
||||
)
|
||||
}
|
||||
|
||||
handleClickOpen () {
|
||||
this.setState({ open: true })
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
switch (e.target.id) {
|
||||
case 'motto':
|
||||
this.setState({ motto: e.target.value })
|
||||
break
|
||||
case 'description':
|
||||
this.setState({ description: e.target.value })
|
||||
break
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
handleAvatar (avatarUrl) {
|
||||
log.debug('handleAvatar: ', avatarUrl)
|
||||
this.setState({ avatar: `${avatarUrl}?${Math.random()}` })
|
||||
}
|
||||
|
||||
handleBanner (bannerUrl) {
|
||||
log.debug('handleBanner: ', bannerUrl)
|
||||
this.setState({ banner: `${bannerUrl}?${Math.random()}` })
|
||||
}
|
||||
|
||||
async handleSubmit (e, updateTeamProfile, slug) {
|
||||
log.debug('handleSubmit: ', this.state)
|
||||
e.preventDefault()
|
||||
|
||||
await updateTeamProfile({
|
||||
variables: {
|
||||
slug,
|
||||
owner: this.authId,
|
||||
description: this.state.description,
|
||||
motto: this.state.motto,
|
||||
avatar: this.state.avatar,
|
||||
banner: this.state.banner
|
||||
}
|
||||
})
|
||||
this.setState({ description: '', motto: '', open: false })
|
||||
}
|
||||
}
|
||||
|
||||
ManageTeamEdit.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
slug: PropTypes.string.isRequired,
|
||||
team: PropTypes.object,
|
||||
authId: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageTeamEdit)
|
||||
@@ -0,0 +1,144 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Query } from 'react-apollo'
|
||||
import { withStateHandlers, lifecycle, compose } from 'recompose'
|
||||
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import { isDefined, isString } from '../../../utils/js-helpers'
|
||||
import { getItem } from '../../../utils/local-storage'
|
||||
|
||||
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
|
||||
|
||||
import ManageMembersTeamItem from './ManageMembersTeamItem'
|
||||
import { MessageCard } from '@superalgos/web-components'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
heroContent: {
|
||||
maxWidth: 600,
|
||||
margin: '0 auto',
|
||||
padding: `${theme.spacing.unit * 8}px 0 ${theme.spacing.unit * 6}px`
|
||||
},
|
||||
cardGrid: {
|
||||
padding: `${theme.spacing.unit * 8}px 0`
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
},
|
||||
card: {
|
||||
display: 'flex',
|
||||
width: '100%'
|
||||
},
|
||||
cardDetails: {
|
||||
display: 'flex',
|
||||
flex: 1
|
||||
},
|
||||
cardContent: {
|
||||
flex: 1
|
||||
},
|
||||
cardMedia: {
|
||||
flex: 1,
|
||||
width: 100,
|
||||
height: 100,
|
||||
maxWidth: 100,
|
||||
justifyContent: 'flex-start'
|
||||
}
|
||||
})
|
||||
|
||||
export const ManageTeamMembersList = ({ classes, user = null }) => {
|
||||
let owner
|
||||
let authId = null
|
||||
log.debug('ManageTeamsList: ', user)
|
||||
if (user !== null && isString(user)) {
|
||||
owner = JSON.parse(user)
|
||||
if (isDefined(owner.authId)) authId = owner.authId
|
||||
}
|
||||
log.debug('ManageTeamsList 2: ', owner, authId)
|
||||
if (authId === undefined || authId === null) {
|
||||
return (
|
||||
<Grid container spacing={40}>
|
||||
<Grid>
|
||||
<MessageCard message='Loading...' />
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Query
|
||||
query={GET_TEAMS_BY_OWNER}
|
||||
variables={{ authId }}
|
||||
fetchPolicy='network-only'
|
||||
>
|
||||
{({ loading, error, data }) => {
|
||||
log.debug('GET_TEAMS_BY_OWNER: ', loading, error, data)
|
||||
let errors = null
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
return <MessageCard message={message} />
|
||||
})
|
||||
}
|
||||
if (!loading && !error) {
|
||||
if (data.teamsByOwner.length > 0) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid container spacing={40}>
|
||||
{!loading &&
|
||||
data.teamsByOwner.map(team => (
|
||||
<ManageMembersTeamItem key={team.id} team={team} authId={authId} />
|
||||
))
|
||||
}
|
||||
{errors}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Grid container spacing={40}>
|
||||
<Grid item xs={10}>
|
||||
<MessageCard message='You don’t have any members. Somethings not right...' />
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<Grid container spacing={40}>
|
||||
<Grid item xs={12}>
|
||||
<MessageCard message='Loading...' />
|
||||
{errors !== null && <MessageCard message={errors} />}
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
}}
|
||||
</Query>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ManageTeamMembersList.propTypes = {
|
||||
user: PropTypes.any,
|
||||
classes: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
const getUserOnMount = lifecycle({
|
||||
componentDidMount () {
|
||||
getItem('user').then(user => {
|
||||
this.setState({ user })
|
||||
}) // Set user to state
|
||||
}
|
||||
})
|
||||
|
||||
const mapStateToProps = withStateHandlers(() => ({ user: null }), {
|
||||
user: ({ user }) => () => ({ user })
|
||||
})
|
||||
|
||||
const ManageTeamMembersListAuthId = compose(
|
||||
mapStateToProps,
|
||||
getUserOnMount
|
||||
)(ManageTeamMembersList)
|
||||
|
||||
export default withStyles(styles)(ManageTeamMembersListAuthId)
|
||||
@@ -0,0 +1,59 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import Card from '@material-ui/core/Card'
|
||||
import CardActions from '@material-ui/core/CardActions'
|
||||
import CardContent from '@material-ui/core/CardContent'
|
||||
import CardMedia from '@material-ui/core/CardMedia'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
|
||||
import ManageTeamDetails from './ManageTeamDetails'
|
||||
import ManageTeamDelete from './ManageTeamDelete'
|
||||
import ManageTeamEdit from './ManageTeamEdit'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
export const ManageTeamsItem = ({ classes, team, authId }) => {
|
||||
log.debug('ManageTeamsItem', team, team.profile, team.profile.avatar)
|
||||
let avatar
|
||||
if (team.profile.avatar !== undefined && team.profile.avatar !== 'a') {
|
||||
avatar = team.profile.avatar
|
||||
} else {
|
||||
avatar = process.env.STORAGE_URL + '/module-teams/module-default/aa-avatar-default.png'
|
||||
}
|
||||
return (
|
||||
<Grid item xs={12}>
|
||||
<Card className={classes.card}>
|
||||
<div className={classes.cardDetails}>
|
||||
<CardMedia
|
||||
className={classes.cardMedia}
|
||||
image={avatar}
|
||||
title={team.name}
|
||||
/>
|
||||
<CardContent className={classes.cardContent}>
|
||||
<Typography gutterBottom variant='h5' component='h2'>
|
||||
{team.name}
|
||||
</Typography>
|
||||
<Typography variant='caption' color='textSecondary'>
|
||||
{team.createdAt} | Members: {team.members.length}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<ManageTeamDetails team={team} />
|
||||
<ManageTeamEdit slug={team.slug} team={team} authId={authId} />
|
||||
<ManageTeamDelete slug={team.slug} authId={authId} />
|
||||
</CardActions>
|
||||
</div>
|
||||
</Card>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
ManageTeamsItem.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
team: PropTypes.object.isRequired,
|
||||
authId: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default ManageTeamsItem
|
||||
@@ -0,0 +1,152 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Query } from 'react-apollo'
|
||||
import { withStateHandlers, lifecycle, compose } from 'recompose'
|
||||
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import { MessageCard } from '@superalgos/web-components'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import { isDefined, isString } from '../../../utils/js-helpers'
|
||||
import { getItem } from '../../../utils/local-storage'
|
||||
|
||||
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
|
||||
|
||||
import ManageTeamsItem from './ManageTeamsItem'
|
||||
import CreateTeamDialog from './CreateTeamDialog'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
heroContent: {
|
||||
maxWidth: 600,
|
||||
margin: '0 auto',
|
||||
padding: `${theme.spacing.unit * 8}px 0 ${theme.spacing.unit * 6}px`
|
||||
},
|
||||
cardGrid: {
|
||||
padding: `${theme.spacing.unit * 8}px 0`
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
},
|
||||
card: {
|
||||
display: 'flex',
|
||||
width: '100%'
|
||||
},
|
||||
cardDetails: {
|
||||
display: 'flex',
|
||||
flex: 1
|
||||
},
|
||||
cardContent: {
|
||||
flex: 1
|
||||
},
|
||||
cardMedia: {
|
||||
flex: 1,
|
||||
width: 100,
|
||||
height: 100,
|
||||
maxWidth: 100,
|
||||
justifyContent: 'flex-start'
|
||||
}
|
||||
})
|
||||
|
||||
export const ManageTeamsList = ({ classes, user = null }) => {
|
||||
let owner
|
||||
let authId = null
|
||||
log.debug('ManageTeamsList: ', user)
|
||||
if (user !== null && isString(user)) {
|
||||
owner = JSON.parse(user)
|
||||
if (isDefined(owner.authId)) authId = owner.authId
|
||||
}
|
||||
log.debug('ManageTeamsList 2: ', owner, authId)
|
||||
if (authId === undefined || authId === null) {
|
||||
return (
|
||||
<Grid container spacing={40}>
|
||||
<Grid>
|
||||
<MessageCard message='Loading...' />
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Query
|
||||
query={GET_TEAMS_BY_OWNER}
|
||||
variables={{ authId }}
|
||||
fetchPolicy='network-only'
|
||||
>
|
||||
{({ loading, error, data }) => {
|
||||
log.debug('GET_TEAMS_BY_OWNER: ', loading, error, data)
|
||||
|
||||
let errors = null
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
return <MessageCard message={message} />
|
||||
})
|
||||
}
|
||||
if (!loading && !error) {
|
||||
if (data.teamsByOwner.length > 0) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid container spacing={40}>
|
||||
{!loading &&
|
||||
data.teamsByOwner.map(team => (
|
||||
<ManageTeamsItem
|
||||
key={team.id}
|
||||
team={team}
|
||||
classes={classes}
|
||||
authId={authId}
|
||||
/>
|
||||
))}
|
||||
{errors}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Grid container spacing={40}>
|
||||
<Grid item xs={10}>
|
||||
<MessageCard message='You don’t have any teams. Create one!'>
|
||||
<CreateTeamDialog authId={authId} />
|
||||
</MessageCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<Grid container spacing={40}>
|
||||
<Grid item xs={12}>
|
||||
<MessageCard message='Loading...' />
|
||||
{errors !== null && <MessageCard message={errors} />}
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
}}
|
||||
</Query>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ManageTeamsList.propTypes = {
|
||||
user: PropTypes.any,
|
||||
classes: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
const getUserOnMount = lifecycle({
|
||||
componentDidMount () {
|
||||
getItem('user').then(user => {
|
||||
this.setState({ user })
|
||||
}) // Set user to state
|
||||
}
|
||||
})
|
||||
|
||||
const mapStateToProps = withStateHandlers(() => ({ user: null }), {
|
||||
user: ({ user }) => () => ({ user })
|
||||
})
|
||||
|
||||
const ManageTeamsListAuthId = compose(
|
||||
mapStateToProps,
|
||||
getUserOnMount
|
||||
)(ManageTeamsList)
|
||||
|
||||
export default withStyles(styles)(ManageTeamsListAuthId)
|
||||
@@ -0,0 +1,54 @@
|
||||
import React from 'react'
|
||||
import Table from '@material-ui/core/Table'
|
||||
import TableBody from '@material-ui/core/TableBody'
|
||||
import TableCell from '@material-ui/core/TableCell'
|
||||
import TableHead from '@material-ui/core/TableHead'
|
||||
import TableRow from '@material-ui/core/TableRow'
|
||||
import Paper from '@material-ui/core/Paper'
|
||||
|
||||
let id = 0
|
||||
const createData = (name, calories, fat, carbs, protein) => {
|
||||
id += 1
|
||||
return { id, name, calories, fat, carbs, protein }
|
||||
}
|
||||
|
||||
const data = [
|
||||
createData('Frozen yoghurt', 159, 6.0, 24, 4.0),
|
||||
createData('Ice cream sandwich', 237, 9.0, 37, 4.3),
|
||||
createData('Eclair', 262, 16.0, 24, 6.0),
|
||||
createData('Cupcake', 305, 3.7, 67, 4.3),
|
||||
createData('Gingerbread', 356, 16.0, 49, 3.9)
|
||||
]
|
||||
|
||||
export const TeamTable = () => (
|
||||
<Paper>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Dessert (100g serving)</TableCell>
|
||||
<TableCell numeric>Calories</TableCell>
|
||||
<TableCell numeric>Fat (g)</TableCell>
|
||||
<TableCell numeric>Carbs (g)</TableCell>
|
||||
<TableCell numeric>Protein (g)</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{data.map(n => {
|
||||
return (
|
||||
<TableRow key={n.id}>
|
||||
<TableCell component='th' scope='row'>
|
||||
{n.name}
|
||||
</TableCell>
|
||||
<TableCell numeric>{n.calories}</TableCell>
|
||||
<TableCell numeric>{n.fat}</TableCell>
|
||||
<TableCell numeric>{n.carbs}</TableCell>
|
||||
<TableCell numeric>{n.protein}</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Paper>
|
||||
)
|
||||
|
||||
export default TeamTable
|
||||
@@ -0,0 +1,93 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Route, Switch } from 'react-router-dom'
|
||||
import { lifecycle } from 'recompose'
|
||||
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import Drawer from '@material-ui/core/Drawer'
|
||||
import List from '@material-ui/core/List'
|
||||
import Divider from '@material-ui/core/Divider'
|
||||
|
||||
import { MainDrawerItems, SecondaryDrawerItems } from './components/DrawerLinks'
|
||||
|
||||
import Overview from './Overview'
|
||||
import ManageTeams from './ManageTeams'
|
||||
import FinancialBeings from './FinancialBeings'
|
||||
import TeamMembers from './TeamMembers'
|
||||
import Settings from './settings'
|
||||
|
||||
import log from '../../utils/log'
|
||||
|
||||
const drawerWidth = 240
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
display: 'flex'
|
||||
},
|
||||
'@global': {
|
||||
body: {
|
||||
backgroundColor: theme.palette.common.white
|
||||
}
|
||||
},
|
||||
appBar: {
|
||||
zIndex: theme.zIndex.drawer + 1
|
||||
},
|
||||
drawerPaper: {
|
||||
position: 'relative',
|
||||
width: drawerWidth,
|
||||
whiteSpace: 'nowrap',
|
||||
zIndex: 1000
|
||||
},
|
||||
content: {
|
||||
flexGrow: 1,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
padding: theme.spacing.unit * 3,
|
||||
height: '100vh',
|
||||
overflow: 'auto',
|
||||
width: `calc(90% - ${drawerWidth}px)`
|
||||
},
|
||||
appBarSpacer: theme.mixins.toolbar
|
||||
})
|
||||
|
||||
const Dashboard = ({ classes }) => (
|
||||
<div className={classes.root}>
|
||||
<Drawer
|
||||
variant='permanent'
|
||||
classes={{
|
||||
paper: classes.drawerPaper
|
||||
}}
|
||||
>
|
||||
<div className={classes.appBarSpacer} />
|
||||
<List>{MainDrawerItems}</List>
|
||||
<Divider />
|
||||
<List>{SecondaryDrawerItems}</List>
|
||||
</Drawer>
|
||||
<main className={classes.content}>
|
||||
<div className={classes.appBarSpacer} />
|
||||
<Switch>
|
||||
<Route exact path='/dashboard' component={Overview} />
|
||||
<Route exact path='/manage-teams' component={ManageTeams} />
|
||||
<Route exact path='/team-members' component={TeamMembers} />
|
||||
<Route exact path='/financial-beings' component={FinancialBeings} />
|
||||
<Route exact path='/settings' component={Settings} />
|
||||
</Switch>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
|
||||
Dashboard.propTypes = {
|
||||
classes: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
const DashboardCheckSession = lifecycle({
|
||||
async componentDidMount () {
|
||||
try {
|
||||
const authenticated = await this.props.auth.isAuthenticated()
|
||||
log.debug('DashboardCheckSession res: ', authenticated)
|
||||
} catch (err) {
|
||||
log.debug('DashboardCheckSession err: ', err)
|
||||
}
|
||||
}
|
||||
})(Dashboard)
|
||||
|
||||
export default withStyles(styles)(DashboardCheckSession)
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import { MessageCard } from '@superalgos/web-components'
|
||||
|
||||
const styles = theme => ({
|
||||
tableContainer: {
|
||||
height: 320
|
||||
}
|
||||
})
|
||||
|
||||
export const Settings = ({ classes }) => (
|
||||
<div>
|
||||
<Typography variant='h4' gutterBottom>
|
||||
Settings
|
||||
</Typography>
|
||||
<div className={classes.tableContainer}>
|
||||
<MessageCard message='Coming soon. Management settings.' />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Settings.propTypes = {
|
||||
classes: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default withStyles(styles)(Settings)
|
||||
@@ -0,0 +1,204 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Mutation } from 'react-apollo'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import Button from '@material-ui/core/Button'
|
||||
import EditIcon from '@material-ui/icons/Edit'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogContentText from '@material-ui/core/DialogContentText'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
|
||||
import { ImageUpload } from '@superalgos/web-components'
|
||||
|
||||
import UPDATE_FB from '../../../graphql/teams/UpdateFBMutation'
|
||||
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
|
||||
|
||||
import { checkGraphQLError } from '../../../utils/graphql-errors'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
dialogContainer: {
|
||||
display: 'block',
|
||||
margin: '3em',
|
||||
minWidth: 400
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
}
|
||||
})
|
||||
|
||||
export class ManageFBEdit extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickOpen = this.handleClickOpen.bind(this)
|
||||
this.handleClose = this.handleClose.bind(this)
|
||||
this.handleSubmit = this.handleSubmit.bind(this)
|
||||
this.handleAvatar = this.handleAvatar.bind(this)
|
||||
|
||||
const avatar = props.fb.avatar || ''
|
||||
|
||||
this.state = {
|
||||
open: false,
|
||||
avatar: avatar
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
log.debug('ManageFBEdit ', this.props, this.props.slug, this.props.fb)
|
||||
const { classes, fb, slug } = this.props
|
||||
return (
|
||||
<Mutation
|
||||
mutation={UPDATE_FB}
|
||||
refetchQueries={[
|
||||
{
|
||||
query: GET_TEAMS_BY_OWNER
|
||||
}
|
||||
]}
|
||||
>
|
||||
{(updateFB, { loading, error, data }) => {
|
||||
const AzureStorageUrl = process.env.AZURE_STORAGE_URL_TEAMS
|
||||
const AzureStorageSAS = process.env.AZURE_STORAGE_SAS_TEAMS
|
||||
const containerName = slug
|
||||
|
||||
let avatar = null
|
||||
if (this.state.avatar === null && fb.avatar === undefined) avatar = process.env.STORAGE_URL + '/module-teams/module-default/aa-avatar-default.png'
|
||||
if (fb.avatar !== undefined && fb.avatar !== null) avatar = fb.avatar
|
||||
if (this.state.avatar !== null) avatar = this.state.avatar
|
||||
|
||||
let errors
|
||||
let loader
|
||||
if (loading) {
|
||||
loader = (
|
||||
<Typography variant='caption'>Submitting team...</Typography>
|
||||
)
|
||||
}
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
const displayMessage = checkGraphQLError(message)
|
||||
log.debug('updateTeamProfile error:', displayMessage)
|
||||
return (
|
||||
<Typography key={i} variant='caption'>
|
||||
{message}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
size='small'
|
||||
color='primary'
|
||||
className={classes.buttonRight}
|
||||
onClick={this.handleClickOpen}
|
||||
>
|
||||
<EditIcon /> Edit
|
||||
</Button>
|
||||
<Dialog
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby='form-dialog-title'
|
||||
>
|
||||
<div classes={classes.dialogContainer}>
|
||||
<DialogTitle id='form-dialog-title'>
|
||||
Edit Financial Being
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>Update Financial Beings's Avatar</DialogContentText>
|
||||
<ImageUpload
|
||||
key='avatar'
|
||||
handleUrl={this.handleAvatar}
|
||||
fileName={`${fb.slug}-fb-avatar.jpg`}
|
||||
containerName={containerName}
|
||||
existingImage={avatar}
|
||||
imagePreviewConfig={{ width: 200, title: 'Change FB Avatar' }}
|
||||
cropContainerConfig={{ x: 10, y: 10, width: 200, height: 200 }}
|
||||
cropPreviewBox={{ width: 350, height: 350 }}
|
||||
saveImageConfig={{
|
||||
quality: 0.6,
|
||||
maxWidth: 200,
|
||||
maxHeight: 200,
|
||||
autoRotate: true,
|
||||
mimeType: 'image/jpeg'
|
||||
}}
|
||||
AzureStorageUrl={AzureStorageUrl}
|
||||
AzureSASURL={AzureStorageSAS}
|
||||
cropRatio={1}
|
||||
debug
|
||||
/>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin='dense'
|
||||
id='name'
|
||||
label='Team Name'
|
||||
type='text'
|
||||
fullWidth
|
||||
disabled
|
||||
value={fb.name}
|
||||
/>
|
||||
{loader}
|
||||
{errors}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleClose} color='primary'>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={e => {
|
||||
this.handleSubmit(e, updateFB, fb.id, slug)
|
||||
}}
|
||||
color='primary'
|
||||
>
|
||||
Update Financial Being
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</Mutation>
|
||||
)
|
||||
}
|
||||
|
||||
handleClickOpen () {
|
||||
this.setState({ open: true })
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
|
||||
handleAvatar (avatarUrl) {
|
||||
log.debug('handleAvatar: ', avatarUrl)
|
||||
this.setState({ avatar: `${avatarUrl}?${Math.random()}` })
|
||||
}
|
||||
|
||||
async handleSubmit (e, updateFB, fbId, slug) {
|
||||
log.debug('handleSubmit: ', this.state)
|
||||
e.preventDefault()
|
||||
|
||||
await updateFB({
|
||||
variables: {
|
||||
fbId,
|
||||
avatar: this.state.avatar
|
||||
}
|
||||
})
|
||||
window.canvasApp.eventHandler.raiseEvent('User Profile Changed')
|
||||
this.setState({ open: false })
|
||||
}
|
||||
}
|
||||
|
||||
ManageFBEdit.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
slug: PropTypes.string.isRequired,
|
||||
fb: PropTypes.object
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageFBEdit)
|
||||
@@ -0,0 +1,54 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import Card from '@material-ui/core/Card'
|
||||
import CardActions from '@material-ui/core/CardActions'
|
||||
import CardContent from '@material-ui/core/CardContent'
|
||||
import CardMedia from '@material-ui/core/CardMedia'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
|
||||
import ManageFBEdit from './ManageFBEdit'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
export const ManageFBItem = ({ classes, team }) => {
|
||||
log.debug('ManageFBItem', team, team.fb[0], team.profile.avatar)
|
||||
let avatar
|
||||
if (team.fb[0].avatar !== undefined && team.fb[0].avatar !== 'a') {
|
||||
avatar = team.fb[0].avatar
|
||||
} else {
|
||||
avatar = process.env.STORAGE_URL + '/module-teams/module-default/aa-avatar-default.png'
|
||||
}
|
||||
return (
|
||||
<Grid item xs={12}>
|
||||
<Card className={classes.card}>
|
||||
<div className={classes.cardDetails}>
|
||||
<CardMedia
|
||||
className={classes.cardMedia}
|
||||
image={avatar}
|
||||
title={team.name}
|
||||
/>
|
||||
<CardContent className={classes.cardContent}>
|
||||
<Typography gutterBottom variant='h5' component='h2'>
|
||||
{team.fb[0].name}
|
||||
</Typography>
|
||||
<Typography variant='caption' color='textSecondary'>
|
||||
{team.fb[0].kind}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<ManageFBEdit slug={team.slug} fb={team.fb[0]} />
|
||||
</CardActions>
|
||||
</div>
|
||||
</Card>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
ManageFBItem.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
team: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default ManageFBItem
|
||||
@@ -0,0 +1,128 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Query } from 'react-apollo'
|
||||
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import Paper from '@material-ui/core/Paper'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import { MessageCard } from '@superalgos/web-components'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
import ManageFBItem from './ManageFBItem'
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
flexGrow: 1
|
||||
},
|
||||
heroContent: {
|
||||
maxWidth: 800,
|
||||
margin: `${theme.spacing.unit * 4}px auto`,
|
||||
padding: 0
|
||||
},
|
||||
cardGrid: {
|
||||
padding: `${theme.spacing.unit * 8}px 0`
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
},
|
||||
card: {
|
||||
display: 'flex',
|
||||
width: '100%'
|
||||
},
|
||||
cardDetails: {
|
||||
display: 'flex',
|
||||
flex: 1
|
||||
},
|
||||
cardContent: {
|
||||
flex: 1
|
||||
},
|
||||
cardMedia: {
|
||||
flex: 1,
|
||||
width: 100,
|
||||
height: 100,
|
||||
maxWidth: 100,
|
||||
justifyContent: 'flex-start'
|
||||
}
|
||||
})
|
||||
|
||||
export const ManageFBList = ({ classes }) => (
|
||||
<Query
|
||||
query={GET_TEAMS_BY_OWNER}
|
||||
fetchPolicy='network-only'
|
||||
>
|
||||
{({ loading, error, data }) => {
|
||||
log.debug('GET_TEAMS_BY_OWNER: ', loading, error, data)
|
||||
|
||||
let errors = null
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
return <MessageCard message={message} />
|
||||
})
|
||||
}
|
||||
if (!loading && !error) {
|
||||
if (data.teams_TeamsByOwner.length > 0) {
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<div className={classes.heroContent}>
|
||||
<Grid container spacing={8} direction='column' alignItems='stretch'>
|
||||
{!loading &&
|
||||
data.teams_TeamsByOwner.map(team => (
|
||||
<ManageFBItem
|
||||
key={team.id}
|
||||
team={team}
|
||||
classes={classes}
|
||||
/>
|
||||
))}
|
||||
{errors}
|
||||
</Grid>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Paper>
|
||||
<Grid container spacing={0} direction='column' justify='center' alignItems='center' className={classes.container}>
|
||||
<Grid item xs={10}>
|
||||
<Typography variant='h5' align='center'>
|
||||
You don’t have any financial being. To create one, create a team first!
|
||||
</Typography>
|
||||
<Typography variant='body1' align='center' gutterBottom>
|
||||
After you create a team, a default trading algobot will be cloned and added to your team so
|
||||
that you can begin experimenting right away.
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<Grid spacing={0} direction='column' justify='center' alignItems='center'>
|
||||
<Grid item xs={12}>
|
||||
{errors === null &&
|
||||
<Typography variant='h5' align='center' gutterBottom>
|
||||
Loading...
|
||||
</Typography>
|
||||
}
|
||||
{errors !== null &&
|
||||
<Typography variant='h5' align='center' gutterBottom>
|
||||
{errors}
|
||||
</Typography>
|
||||
}
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
}}
|
||||
</Query>
|
||||
)
|
||||
|
||||
ManageFBList.propTypes = {
|
||||
classes: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageFBList)
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import { BannerTopBar } from '../common'
|
||||
import ManageFBList from './components/ManageFBList'
|
||||
|
||||
const styles = theme => ({
|
||||
tableContainer: {
|
||||
height: 320
|
||||
}
|
||||
})
|
||||
|
||||
const FinancialBeings = ({ classes }) => (
|
||||
<div>
|
||||
<BannerTopBar size='medium' title='Manage Your Financial Beings' text='Manage your Superalgos Financial Beings' backgroundUrl='https://superalgos.org/img/photos/teams.jpg' />
|
||||
<ManageFBList />
|
||||
</div>
|
||||
)
|
||||
|
||||
FinancialBeings.propTypes = {
|
||||
classes: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default withStyles(styles)(FinancialBeings)
|
||||
@@ -0,0 +1,24 @@
|
||||
import ManageTeams from './manage-teams'
|
||||
import TeamMembers from './team-members'
|
||||
import FinancialBeings from './financial-beings'
|
||||
import Settings from './settings'
|
||||
import Teams from './teams'
|
||||
import AcceptTeamInvite from './teams/AcceptTeamInvite'
|
||||
import Landing from './landing'
|
||||
|
||||
import { TeamBar } from './common'
|
||||
|
||||
import { theme, globalStyles } from './theme'
|
||||
|
||||
export {
|
||||
AcceptTeamInvite,
|
||||
ManageTeams,
|
||||
TeamMembers,
|
||||
FinancialBeings,
|
||||
Settings,
|
||||
Teams,
|
||||
TeamBar,
|
||||
Landing,
|
||||
theme,
|
||||
globalStyles
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import { BannerTopBar } from '../common'
|
||||
|
||||
const styles = theme => ({
|
||||
container: {
|
||||
width: 'auto',
|
||||
marginTop: theme.spacing.unit * 3,
|
||||
marginLeft: theme.spacing.unit * 3,
|
||||
marginRight: theme.spacing.unit * 3,
|
||||
marginBottom: theme.spacing.unit * 6,
|
||||
[theme.breakpoints.up(800 + theme.spacing.unit * 3 * 2)]: {
|
||||
width: 800,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const Landing = ({ classes, ...props }) => (
|
||||
<React.Fragment>
|
||||
<BannerTopBar size='big' title='Teams Module' text='Responsible for all teams at Advanced Algos.' backgroundUrl='https://superalgos.org/img/photos/teams.jpg' />
|
||||
<div className='homePage container'>
|
||||
<Typography variant='h1' align='center' className='title'>
|
||||
Welcome to the Teams Module!
|
||||
</Typography>
|
||||
<Typography variant='h2' align='center' className='subtitle'>
|
||||
Multidisciplinary groups of humans joining forces to breed financial beings and compete.
|
||||
</Typography>
|
||||
<div className='column'>
|
||||
<Typography align='justify'>
|
||||
One of the core propositions of the Superalgos projects revolves around people teaming up to breed financial beings and compete in algorithmic trading competitions. Both developing and competing are team activities.
|
||||
</Typography>
|
||||
</div>
|
||||
<div className='column'>
|
||||
<Typography align='justify'>
|
||||
You may join and participate in as many teams as you wish.
|
||||
</Typography>
|
||||
<Typography align='justify'>
|
||||
When you create your own team, you become the team’s owner and may allow more people in the team at your sole discretion. It has been shown that larger teams tend to become more intelligent.
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)
|
||||
|
||||
Landing.propTypes = {
|
||||
classes: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default withStyles(styles)(Landing)
|
||||
@@ -0,0 +1,256 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Mutation } from 'react-apollo'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import AddIcon from '@material-ui/icons/Add'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import FormControl from '@material-ui/core/FormControl'
|
||||
import FormHelperText from '@material-ui/core/FormHelperText'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import { isEmpty } from '../../../utils/js-helpers'
|
||||
|
||||
import CREATE_TEAM from '../../../graphql/teams/CreateTeamMutation'
|
||||
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
dialogContainer: {
|
||||
display: 'block',
|
||||
margin: '3em',
|
||||
minWidth: 400
|
||||
},
|
||||
dialogStyle: {
|
||||
padding: '3em'
|
||||
},
|
||||
textField: {
|
||||
width: '60%',
|
||||
marginLeft: '20%',
|
||||
marginBottom: 10
|
||||
},
|
||||
buttonFab: {
|
||||
backgroundColor: '#CCCCCC'
|
||||
}
|
||||
})
|
||||
|
||||
export class CreateTeamDialog extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickOpen = this.handleClickOpen.bind(this)
|
||||
this.handleClose = this.handleClose.bind(this)
|
||||
this.handleChange = this.handleChange.bind(this)
|
||||
this.handleSubmit = this.handleSubmit.bind(this)
|
||||
this.validate = this.validate.bind(this)
|
||||
this.slugify = this.slugify.bind(this)
|
||||
|
||||
this.state = {
|
||||
open: false,
|
||||
name: '',
|
||||
botName: '',
|
||||
nameError: '',
|
||||
botNameError: ''
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { classes } = this.props
|
||||
return (
|
||||
<Mutation
|
||||
mutation={CREATE_TEAM}
|
||||
refetchQueries={[{
|
||||
query: GET_TEAMS_BY_OWNER
|
||||
}]}
|
||||
>
|
||||
{(createTeam, { loading, error, data }) => {
|
||||
let errors
|
||||
let loader = null
|
||||
if (loading) {
|
||||
loader = (
|
||||
<Typography variant='subtitle1'>Submitting team...</Typography>
|
||||
)
|
||||
}
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
// const displayMessage = checkGraphQLError(message)
|
||||
return (
|
||||
<Typography key={i} variant='caption'>
|
||||
{message}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
}
|
||||
return (
|
||||
<Grid container justify='center'>
|
||||
<Button
|
||||
variant='fab'
|
||||
color='primary'
|
||||
aria-label='Add'
|
||||
className={classes.buttonFab}
|
||||
onClick={this.handleClickOpen}
|
||||
>
|
||||
<AddIcon />
|
||||
</Button>
|
||||
<Dialog
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby='form-dialog-title'
|
||||
>
|
||||
<div classes={classes.dialogContainer}>
|
||||
<DialogTitle id='form-dialog-title'>Create a Team</DialogTitle>
|
||||
<DialogContent>
|
||||
<FormControl
|
||||
required
|
||||
error={this.state.nameError !== '' || this.state.botNameError !== '' || error !== null}
|
||||
>
|
||||
<Typography variant='subtitle1' align='center'>Create a name for your team</Typography>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin='dense'
|
||||
id='name'
|
||||
label='Team Name'
|
||||
type='text'
|
||||
fullWidth
|
||||
className={classes.textField}
|
||||
value={this.state.name}
|
||||
onChange={this.handleChange}
|
||||
error={this.state.nameError !== ''}
|
||||
/>
|
||||
{this.state.nameError !== '' && (
|
||||
<FormHelperText>{this.state.nameError}</FormHelperText>
|
||||
)}
|
||||
<Typography variant='subtitle1' align='center' ><br />
|
||||
Create a name for your teams bot.
|
||||
</Typography>
|
||||
<Typography align='center' >
|
||||
<em>(Temporary: In the future, creating multiple Financial Beings <br />
|
||||
will occur in a separate module)</em>
|
||||
</Typography>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin='dense'
|
||||
id='botName'
|
||||
label='Team Bot Name'
|
||||
type='text'
|
||||
fullWidth
|
||||
className={classes.textField}
|
||||
value={this.state.botName}
|
||||
onChange={this.handleChange}
|
||||
error={this.state.botNameError !== ''}
|
||||
/>
|
||||
{this.state.botNameError !== '' && (
|
||||
<FormHelperText>{this.state.botNameError}</FormHelperText>
|
||||
)}
|
||||
{error && <FormHelperText>{errors}</FormHelperText>}
|
||||
{loader}
|
||||
</FormControl>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleClose} color='primary'>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={e => {
|
||||
this.handleSubmit(e, createTeam, this.state.name, this.state.botName)
|
||||
}}
|
||||
color='primary'
|
||||
>
|
||||
Create Team
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Grid>
|
||||
)
|
||||
}}
|
||||
</Mutation>
|
||||
)
|
||||
}
|
||||
|
||||
handleClickOpen () {
|
||||
this.setState({ open: true })
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
let id = e.target.id
|
||||
let value = e.target.value
|
||||
let error = this.validate({ id, value })
|
||||
switch (id) {
|
||||
case 'name':
|
||||
if (!isEmpty(error)) this.setState({ nameError: error })
|
||||
this.setState({ name: value })
|
||||
break
|
||||
case 'botName':
|
||||
if (!isEmpty(error)) this.setState({ botNameError: error })
|
||||
this.setState({ botName: value })
|
||||
break
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
async handleSubmit (e, createTeam, name, botName) {
|
||||
e.preventDefault()
|
||||
log.debug('createTeam submit: ', name, botName)
|
||||
const slug = this.slugify(name)
|
||||
const botSlug = this.slugify(botName)
|
||||
await createTeam({ variables: { name, slug, botName, botSlug } })
|
||||
window.canvasApp.eventHandler.raiseEvent('User Profile Changed')
|
||||
this.setState({ name: '', open: false })
|
||||
}
|
||||
|
||||
validate (data) {
|
||||
const errors = {}
|
||||
switch (data.id) {
|
||||
case 'name':
|
||||
if (data.value === '') {
|
||||
this.setState({ nameError: 'Please enter a team name' })
|
||||
} else {
|
||||
this.setState({ nameError: '' })
|
||||
}
|
||||
break
|
||||
case 'botName':
|
||||
if (data.value === '') {
|
||||
this.setState({ botNameError: 'Please enter a bot name' })
|
||||
} else {
|
||||
this.setState({ botNameError: '' })
|
||||
}
|
||||
break
|
||||
default:
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
slugify (string) {
|
||||
const a = 'àáäâãåèéëêìíïîòóöôùúüûñçßÿœæŕśńṕẃǵǹḿǘẍźḧ·/_,:;'
|
||||
const b = 'aaaaaaeeeeiiiioooouuuuncsyoarsnpwgnmuxzh------'
|
||||
const p = new RegExp(a.split('').join('|'), 'g')
|
||||
|
||||
return string
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '-') // Replace spaces with -
|
||||
.replace(p, c => b.charAt(a.indexOf(c))) // Replace special characters
|
||||
.replace(/&/g, '-and-') // Replace & with 'and'
|
||||
.replace(/[^\w-]+/g, '') // Remove all non-word characters
|
||||
.replace(/--+/g, '-') // Replace multiple - with single -
|
||||
.replace(/^-+/, '') // Trim - from start of text
|
||||
.replace(/-+$/, '') // Trim - from end of text
|
||||
}
|
||||
}
|
||||
|
||||
CreateTeamDialog.propTypes = {
|
||||
classes: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default withStyles(styles)(CreateTeamDialog)
|
||||
@@ -0,0 +1,238 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Mutation } from 'react-apollo'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import Button from '@material-ui/core/Button'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import FormControl from '@material-ui/core/FormControl'
|
||||
import FormHelperText from '@material-ui/core/FormHelperText'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import { isEmpty } from '../../../utils/js-helpers'
|
||||
|
||||
import CREATE_TEAM from '../../../graphql/teams/CreateTeamMutation'
|
||||
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
formContainer: {
|
||||
width: '100%',
|
||||
margin: '3em 0 1em'
|
||||
},
|
||||
typography: {
|
||||
width: '80%',
|
||||
marginLeft: '10%',
|
||||
marginTop: 40
|
||||
},
|
||||
dialogStyle: {
|
||||
padding: '3em'
|
||||
},
|
||||
textField: {
|
||||
width: '80%',
|
||||
marginLeft: '10%',
|
||||
marginTop: 25
|
||||
},
|
||||
submitButton: {
|
||||
margin: theme.spacing.unit,
|
||||
marginTop: theme.spacing.unit * 3
|
||||
}
|
||||
})
|
||||
|
||||
export class CreateTeamForm extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickOpen = this.handleClickOpen.bind(this)
|
||||
this.handleClose = this.handleClose.bind(this)
|
||||
this.handleChange = this.handleChange.bind(this)
|
||||
this.handleSubmit = this.handleSubmit.bind(this)
|
||||
this.validate = this.validate.bind(this)
|
||||
this.slugify = this.slugify.bind(this)
|
||||
|
||||
this.state = {
|
||||
open: false,
|
||||
name: '',
|
||||
botName: '',
|
||||
nameError: '',
|
||||
botNameError: ''
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { classes } = this.props
|
||||
return (
|
||||
<Mutation
|
||||
mutation={CREATE_TEAM}
|
||||
refetchQueries={[{
|
||||
query: GET_TEAMS_BY_OWNER
|
||||
}]}
|
||||
>
|
||||
{(createTeam, { loading, error, data }) => {
|
||||
let errors
|
||||
let loader = null
|
||||
if (loading) {
|
||||
loader = (
|
||||
<Typography variant='subtitle1'>Submitting team...</Typography>
|
||||
)
|
||||
}
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
// const displayMessage = checkGraphQLError(message)
|
||||
return (
|
||||
<Typography key={i} variant='caption'>
|
||||
{message}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
}
|
||||
return (
|
||||
<FormControl
|
||||
required
|
||||
error={this.state.nameError !== '' || this.state.botNameError !== '' || error !== null}
|
||||
className={classes.formContainer}
|
||||
>
|
||||
<Typography className={classes.typography} variant='body1' gutterBottom align='left'>
|
||||
Give a nice and memorable name to your team.
|
||||
</Typography>
|
||||
|
||||
<TextField
|
||||
autoFocus
|
||||
id='name'
|
||||
label='Team Name'
|
||||
type='text'
|
||||
fullWidth
|
||||
className={classes.textField}
|
||||
value={this.state.name}
|
||||
onChange={this.handleChange}
|
||||
error={this.state.nameError !== ''}
|
||||
/>
|
||||
{this.state.nameError !== '' && (
|
||||
<FormHelperText>{this.state.nameError}</FormHelperText>
|
||||
)}
|
||||
<Typography className={classes.typography} variant='body1' gutterBottom align='left'>
|
||||
Give a name for to the bot you are forking.
|
||||
</Typography>
|
||||
<TextField
|
||||
autoFocus
|
||||
id='botName'
|
||||
label='Team Bot Name'
|
||||
type='text'
|
||||
fullWidth
|
||||
className={classes.textField}
|
||||
value={this.state.botName}
|
||||
onChange={this.handleChange}
|
||||
error={this.state.botNameError !== ''}
|
||||
/>
|
||||
|
||||
<Grid container justify='center' >
|
||||
<Grid item>
|
||||
{this.state.botNameError !== '' && (
|
||||
<FormHelperText>{this.state.botNameError}</FormHelperText>
|
||||
)}
|
||||
{error && <FormHelperText>{errors}</FormHelperText>}
|
||||
{loader}
|
||||
</Grid>
|
||||
<Grid item>
|
||||
|
||||
<Button
|
||||
onClick={e => {
|
||||
this.handleSubmit(e, createTeam, this.state.name, this.state.botName)
|
||||
}}
|
||||
variant='contained'
|
||||
color='secondary'
|
||||
className={classes.submitButton}
|
||||
>
|
||||
Create Team
|
||||
</Button>
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
</FormControl>
|
||||
)
|
||||
}}
|
||||
</Mutation>
|
||||
)
|
||||
}
|
||||
|
||||
handleClickOpen () {
|
||||
this.setState({ open: true })
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
let id = e.target.id
|
||||
let value = e.target.value
|
||||
let error = this.validate({ id, value })
|
||||
switch (id) {
|
||||
case 'name':
|
||||
if (!isEmpty(error)) this.setState({ nameError: error })
|
||||
this.setState({ name: value })
|
||||
break
|
||||
case 'botName':
|
||||
if (!isEmpty(error)) this.setState({ botNameError: error })
|
||||
this.setState({ botName: value })
|
||||
break
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
async handleSubmit (e, createTeam, name, botName) {
|
||||
e.preventDefault()
|
||||
log.debug('createTeam submit: ', name, botName)
|
||||
const slug = this.slugify(name)
|
||||
const botSlug = this.slugify(botName)
|
||||
await createTeam({ variables: { name, slug, botName, botSlug } })
|
||||
window.canvasApp.eventHandler.raiseEvent('User Profile Changed')
|
||||
this.setState({ name: '', open: false })
|
||||
}
|
||||
|
||||
validate (data) {
|
||||
const errors = {}
|
||||
switch (data.id) {
|
||||
case 'name':
|
||||
if (data.value === '') {
|
||||
this.setState({ nameError: 'Please enter a team name' })
|
||||
} else {
|
||||
this.setState({ nameError: '' })
|
||||
}
|
||||
break
|
||||
case 'botName':
|
||||
if (data.value === '') {
|
||||
this.setState({ botNameError: 'Please enter a bot name' })
|
||||
} else {
|
||||
this.setState({ botNameError: '' })
|
||||
}
|
||||
break
|
||||
default:
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
slugify (string) {
|
||||
const a = 'àáäâãåèéëêìíïîòóöôùúüûñçßÿœæŕśńṕẃǵǹḿǘẍźḧ·/_,:;'
|
||||
const b = 'aaaaaaeeeeiiiioooouuuuncsyoarsnpwgnmuxzh------'
|
||||
const p = new RegExp(a.split('').join('|'), 'g')
|
||||
|
||||
return string
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '-') // Replace spaces with -
|
||||
.replace(p, c => b.charAt(a.indexOf(c))) // Replace special characters
|
||||
.replace(/&/g, '-and-') // Replace & with 'and'
|
||||
.replace(/[^\w-]+/g, '') // Remove all non-word characters
|
||||
.replace(/--+/g, '-') // Replace multiple - with single -
|
||||
.replace(/^-+/, '') // Trim - from start of text
|
||||
.replace(/-+$/, '') // Trim - from end of text
|
||||
}
|
||||
}
|
||||
|
||||
CreateTeamForm.propTypes = {
|
||||
classes: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default withStyles(styles)(CreateTeamForm)
|
||||
@@ -0,0 +1,157 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import { Mutation } from 'react-apollo'
|
||||
|
||||
import Button from '@material-ui/core/Button'
|
||||
import DeleteIcon from '@material-ui/icons/Delete'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
|
||||
import DELETE_TEAM from '../../../graphql/teams/DeleteTeamMutation'
|
||||
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
|
||||
|
||||
import { checkGraphQLError } from '../../../utils/graphql-errors'
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
dialogContainer: {
|
||||
display: 'block',
|
||||
margin: '3em',
|
||||
minWidth: 400
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
}
|
||||
})
|
||||
|
||||
export class ManageTeamDelete extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickOpen = this.handleClickOpen.bind(this)
|
||||
this.handleClose = this.handleClose.bind(this)
|
||||
this.handleChange = this.handleChange.bind(this)
|
||||
this.handleSubmit = this.handleSubmit.bind(this)
|
||||
|
||||
this.state = {
|
||||
open: false
|
||||
}
|
||||
}
|
||||
|
||||
handleClickOpen () {
|
||||
this.setState({ open: true })
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
this.setState({ name: e.target.value })
|
||||
}
|
||||
|
||||
render () {
|
||||
log.debug(this.props, this.props.slug)
|
||||
const { classes } = this.props
|
||||
return (
|
||||
<Mutation
|
||||
mutation={DELETE_TEAM}
|
||||
refetchQueries={[
|
||||
{
|
||||
query: GET_TEAMS_BY_OWNER
|
||||
}
|
||||
]}
|
||||
>
|
||||
{(deleteTeam, { loading, error, data }) => {
|
||||
let errors
|
||||
let loader
|
||||
if (loading) {
|
||||
loader = (
|
||||
<Typography variant='caption'>Deleting team and financial being...</Typography>
|
||||
)
|
||||
}
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
log.debug('deleteTeam error:', message)
|
||||
return (
|
||||
<Typography key={i} variant='caption'>
|
||||
{message}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
size='small'
|
||||
color='primary'
|
||||
className={classes.buttonRight}
|
||||
onClick={this.handleClickOpen}
|
||||
>
|
||||
<DeleteIcon /> Delete
|
||||
</Button>
|
||||
<Dialog
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby='form-dialog-title'
|
||||
>
|
||||
<div classes={classes.dialogContainer}>
|
||||
<DialogTitle id='form-dialog-title'>
|
||||
Delete Team Team
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography variant='subtitle1' color='primary'>
|
||||
DANGER - Deleting your team cannot be undone
|
||||
</Typography>
|
||||
<Typography variant='subtitle1'>
|
||||
Are you sure you want to delete this team?
|
||||
</Typography>
|
||||
{loader}
|
||||
{errors}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleClose} color='primary'>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={e => {
|
||||
this.handleSubmit(
|
||||
e,
|
||||
deleteTeam,
|
||||
this.props.slug,
|
||||
this.props.botSlug
|
||||
)
|
||||
}}
|
||||
color='primary'
|
||||
>
|
||||
Delete Team
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</Mutation>
|
||||
)
|
||||
}
|
||||
|
||||
async handleSubmit (e, deleteTeam, slug, botSlug) {
|
||||
e.preventDefault()
|
||||
await deleteTeam({ variables: { slug, botSlug } })
|
||||
window.canvasApp.eventHandler.raiseEvent('User Profile Changed')
|
||||
this.setState({ open: false })
|
||||
}
|
||||
}
|
||||
|
||||
ManageTeamDelete.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
slug: PropTypes.string.isRequired,
|
||||
botSlug: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageTeamDelete)
|
||||
@@ -0,0 +1,173 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import Button from '@material-ui/core/Button'
|
||||
import DetailsIcon from '@material-ui/icons/Details'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import Card from '@material-ui/core/Card'
|
||||
import CardContent from '@material-ui/core/CardContent'
|
||||
import CardActions from '@material-ui/core/CardActions'
|
||||
import CardMedia from '@material-ui/core/CardMedia'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
dialogContainer: {
|
||||
display: 'block',
|
||||
margin: '3em',
|
||||
minWidth: 400
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
},
|
||||
cardGrid: {
|
||||
padding: `${theme.spacing.unit * 8}px 0`
|
||||
},
|
||||
card: {
|
||||
display: 'flex',
|
||||
width: '100%'
|
||||
},
|
||||
cardDetails: {
|
||||
display: 'flex',
|
||||
flex: 1
|
||||
},
|
||||
cardContent: {
|
||||
flex: 1
|
||||
},
|
||||
cardAvatarMedia: {
|
||||
flex: 1,
|
||||
width: 100,
|
||||
height: 100,
|
||||
maxWidth: 100,
|
||||
justifyContent: 'flex-start'
|
||||
},
|
||||
cardBannerMedia: {
|
||||
flex: 1,
|
||||
width: 600,
|
||||
height: 150,
|
||||
justifyContent: 'flex-start'
|
||||
}
|
||||
})
|
||||
|
||||
export class ManageTeamDetails extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickOpen = this.handleClickOpen.bind(this)
|
||||
this.handleClose = this.handleClose.bind(this)
|
||||
|
||||
this.state = {
|
||||
open: false
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
log.debug(this.props.team)
|
||||
const { classes } = this.props
|
||||
const { name, members, profile, createdAt } = this.props.team
|
||||
let avatar
|
||||
let banner
|
||||
if (profile.avatar !== undefined && profile.avatar !== null) {
|
||||
avatar = profile.avatar
|
||||
} else {
|
||||
avatar = process.env.STORAGE_URL + '/module-teams/module-default/aa-avatar-default.png'
|
||||
}
|
||||
if (profile.banner !== undefined && profile.banner !== null) {
|
||||
banner = profile.banner
|
||||
} else {
|
||||
banner = process.env.STORAGE_URL + '/module-teams/module-default/aa-banner-default.png'
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
size='small'
|
||||
color='primary'
|
||||
className={this.props.classes.buttonRight}
|
||||
onClick={this.handleClickOpen}
|
||||
>
|
||||
<DetailsIcon /> Details
|
||||
</Button>
|
||||
<Dialog
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby='form-dialog-title'
|
||||
>
|
||||
<div classes={classes.dialogContainer}>
|
||||
<Card className={classes.card}>
|
||||
<CardContent>
|
||||
<Typography variant='h6' id='form-dialog-title'>
|
||||
{name} Team Details
|
||||
</Typography>
|
||||
<CardMedia
|
||||
className={classes.cardBannerMedia}
|
||||
image={banner}
|
||||
title={`${name} ${banner}`}
|
||||
/>
|
||||
<CardMedia
|
||||
className={classes.cardAvatarMedia}
|
||||
image={avatar}
|
||||
title={`${name} ${avatar}`}
|
||||
/>
|
||||
<Typography
|
||||
variant='h2'
|
||||
align='center'
|
||||
color='textPrimary'
|
||||
gutterBottom
|
||||
>
|
||||
{name}
|
||||
</Typography>
|
||||
<Typography variant='subtitle1' color='textSecondary'>
|
||||
{createdAt}
|
||||
</Typography>
|
||||
<Typography variant='subtitle1' color='textSecondary'>
|
||||
Motto: {profile.motto}
|
||||
</Typography>
|
||||
<Typography variant='subtitle1' color='textSecondary'>
|
||||
Description: {profile.description}
|
||||
</Typography>
|
||||
<Typography variant='subtitle1' paragraph gutterBottom>
|
||||
Members: {members.length}
|
||||
</Typography>
|
||||
<Typography variant='subtitle1' color='primary'>
|
||||
Team Admin:
|
||||
{members.map(member => {
|
||||
if (member.role === 'OWNER' || member.role === 'ADMIN') {
|
||||
if (member.member !== null &&
|
||||
member.member.alias !== undefined &&
|
||||
member.member.alias !== null
|
||||
) {
|
||||
return member.member.alias
|
||||
}
|
||||
}
|
||||
})}
|
||||
</Typography>
|
||||
<CardActions>
|
||||
<Button onClick={this.handleClose} color='primary'>
|
||||
Close
|
||||
</Button>
|
||||
</CardActions>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
handleClickOpen () {
|
||||
this.setState({ open: true })
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
}
|
||||
|
||||
ManageTeamDetails.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
team: PropTypes.any
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageTeamDetails)
|
||||
@@ -0,0 +1,281 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Mutation } from 'react-apollo'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import Button from '@material-ui/core/Button'
|
||||
import EditIcon from '@material-ui/icons/Edit'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogContentText from '@material-ui/core/DialogContentText'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
|
||||
import { ImageUpload } from '@superalgos/web-components'
|
||||
|
||||
import UPDATE_TEAM_PROFILE from '../../../graphql/teams/UpdateTeamProfileMutation'
|
||||
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
|
||||
|
||||
import { checkGraphQLError } from '../../../utils/graphql-errors'
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
dialogContainer: {
|
||||
display: 'block',
|
||||
margin: '3em',
|
||||
minWidth: 400
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
}
|
||||
})
|
||||
|
||||
export class ManageTeamEdit extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickOpen = this.handleClickOpen.bind(this)
|
||||
this.handleClose = this.handleClose.bind(this)
|
||||
this.handleChange = this.handleChange.bind(this)
|
||||
this.handleSubmit = this.handleSubmit.bind(this)
|
||||
this.handleAvatar = this.handleAvatar.bind(this)
|
||||
this.handleBanner = this.handleBanner.bind(this)
|
||||
|
||||
log.debug('ManageTeamEdit', props.team)
|
||||
const motto = props.team.profile.motto || ''
|
||||
const description = props.team.profile.description || ''
|
||||
const avatar = props.team.profile.avatar || null
|
||||
const banner = props.team.profile.banner || null
|
||||
|
||||
this.state = {
|
||||
open: false,
|
||||
motto: motto,
|
||||
description: description,
|
||||
avatar: avatar,
|
||||
banner: banner
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { classes, team, match } = this.props
|
||||
log.debug('ManageTeamEdit ', this.props, this.props.slug, this.props.team, match)
|
||||
return (
|
||||
<Mutation
|
||||
mutation={UPDATE_TEAM_PROFILE}
|
||||
refetchQueries={[
|
||||
{
|
||||
query: GET_TEAMS_BY_OWNER
|
||||
}
|
||||
]}
|
||||
>
|
||||
{(updateTeamProfile, { loading, error, data }) => {
|
||||
const AzureStorageUrl = process.env.AZURE_STORAGE_URL_TEAMS
|
||||
const AzureStorageSAS = process.env.AZURE_STORAGE_SAS_TEAMS
|
||||
const containerName = team.slug
|
||||
let avatar = null
|
||||
if (this.state.avatar === null && team.profile !== null && (team.profile.avatar === undefined || team.profile.avatar === null)) avatar = process.env.STORAGE_URL + '/module-teams/module-default/aa-avatar-default.png'
|
||||
if (team.profile !== null && team.profile.avatar !== undefined && team.profile.avatar !== null) avatar = team.profile.avatar
|
||||
if (this.state.avatar !== null) avatar = this.state.avatar
|
||||
|
||||
let banner = null
|
||||
if (this.state.banner === null && team.profile !== null && (team.profile.banner === undefined || team.profile.banner === null)) banner = process.env.STORAGE_URL + '/module-teams/module-default/aa-banner-default.png'
|
||||
if (team.profile !== null && team.profile.banner !== undefined && team.profile.banner !== null) banner = team.profile.banner
|
||||
if (this.state.banner !== null) banner = this.state.banner
|
||||
log.debug('team images: ', avatar, banner)
|
||||
|
||||
let errors
|
||||
let loader
|
||||
if (loading) {
|
||||
loader = (
|
||||
<Typography variant='caption'>Submitting team...</Typography>
|
||||
)
|
||||
}
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
const displayMessage = checkGraphQLError(message)
|
||||
log.debug('updateTeamProfile error:', displayMessage)
|
||||
return (
|
||||
<Typography key={i} variant='caption'>
|
||||
{message}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
size='small'
|
||||
color='primary'
|
||||
className={classes.buttonRight}
|
||||
onClick={this.handleClickOpen}
|
||||
>
|
||||
<EditIcon /> Edit
|
||||
</Button>
|
||||
<Dialog
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby='form-dialog-title'
|
||||
>
|
||||
<div classes={classes.dialogContainer}>
|
||||
<DialogTitle id='form-dialog-title'>
|
||||
Edit Team Details
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<ImageUpload
|
||||
key='banner'
|
||||
handleUrl={this.handleBanner}
|
||||
fileName={`${team.slug}-banner.jpg`}
|
||||
containerName={containerName}
|
||||
existingImage={banner}
|
||||
imagePreviewConfig={{ width: 450, title: 'Change Banner' }}
|
||||
cropContainerConfig={{ x: 10, y: 10, width: 800, height: 200 }}
|
||||
cropPreviewBox={{ width: 650, height: 200 }}
|
||||
saveImageConfig={{
|
||||
quality: 0.6,
|
||||
maxWidth: 800,
|
||||
maxHeight: 200,
|
||||
autoRotate: true,
|
||||
mimeType: 'image/jpeg'
|
||||
}}
|
||||
AzureStorageUrl={AzureStorageUrl}
|
||||
AzureSASURL={AzureStorageSAS}
|
||||
cropRatio={4}
|
||||
debug
|
||||
/>
|
||||
<ImageUpload
|
||||
key='avatar1'
|
||||
handleUrl={this.handleAvatar}
|
||||
fileName={`${team.slug}-avatar.jpg`}
|
||||
containerName={containerName}
|
||||
existingImage={avatar}
|
||||
imagePreviewConfig={{ width: 200, title: 'Change Avatar' }}
|
||||
cropContainerConfig={{ x: 10, y: 10, width: 200, height: 200 }}
|
||||
cropPreviewBox={{ width: 350, height: 350 }}
|
||||
saveImageConfig={{
|
||||
quality: 0.6,
|
||||
maxWidth: 200,
|
||||
maxHeight: 200,
|
||||
autoRotate: true,
|
||||
mimeType: 'image/jpeg'
|
||||
}}
|
||||
AzureStorageUrl={AzureStorageUrl}
|
||||
AzureSASURL={AzureStorageSAS}
|
||||
cropRatio={1}
|
||||
debug
|
||||
/>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin='dense'
|
||||
id='name'
|
||||
label='Team Name'
|
||||
type='text'
|
||||
fullWidth
|
||||
disabled
|
||||
value={team.name}
|
||||
/>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin='dense'
|
||||
id='motto'
|
||||
label='Team Motto'
|
||||
type='text'
|
||||
fullWidth
|
||||
value={this.state.motto}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<DialogContentText>Team description:</DialogContentText>
|
||||
<TextField
|
||||
margin='dense'
|
||||
id='description'
|
||||
label='Team Description'
|
||||
type='text'
|
||||
rows={4}
|
||||
multiline
|
||||
fullWidth
|
||||
value={this.state.description}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
{loader}
|
||||
{errors}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleClose} color='primary'>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={e => {
|
||||
this.handleSubmit(e, updateTeamProfile, this.props.slug)
|
||||
}}
|
||||
color='primary'
|
||||
>
|
||||
Update Team
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</Mutation>
|
||||
)
|
||||
}
|
||||
|
||||
handleClickOpen () {
|
||||
this.setState({ open: true })
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
switch (e.target.id) {
|
||||
case 'motto':
|
||||
this.setState({ motto: e.target.value })
|
||||
break
|
||||
case 'description':
|
||||
this.setState({ description: e.target.value })
|
||||
break
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
handleAvatar (avatarUrl) {
|
||||
log.debug('handleAvatar: ', avatarUrl)
|
||||
this.setState({ avatar: `${avatarUrl}?${Math.random()}` })
|
||||
}
|
||||
|
||||
handleBanner (bannerUrl) {
|
||||
log.debug('handleBanner: ', bannerUrl)
|
||||
this.setState({ banner: `${bannerUrl}?${Math.random()}` })
|
||||
}
|
||||
|
||||
async handleSubmit (e, updateTeamProfile, slug) {
|
||||
log.debug('handleSubmit: ', this.state)
|
||||
e.preventDefault()
|
||||
|
||||
await updateTeamProfile({
|
||||
variables: {
|
||||
slug,
|
||||
owner: this.authId,
|
||||
description: this.state.description,
|
||||
motto: this.state.motto,
|
||||
avatar: this.state.avatar,
|
||||
banner: this.state.banner
|
||||
}
|
||||
})
|
||||
window.canvasApp.eventHandler.raiseEvent('User Profile Changed')
|
||||
this.setState({ description: '', motto: '', open: false })
|
||||
}
|
||||
}
|
||||
|
||||
ManageTeamEdit.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
slug: PropTypes.string.isRequired,
|
||||
team: PropTypes.object,
|
||||
match: PropTypes.object
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageTeamEdit)
|
||||
@@ -0,0 +1,143 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Query } from 'react-apollo'
|
||||
import { withStateHandlers, lifecycle, compose } from 'recompose'
|
||||
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import { isDefined, isString } from '../../../utils/js-helpers'
|
||||
import { getItem } from '../../../utils/local-storage'
|
||||
|
||||
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
|
||||
|
||||
import ManageMembersTeamItem from './ManageMembersTeamItem'
|
||||
import { MessageCard } from '@superalgos/web-components'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
heroContent: {
|
||||
maxWidth: 600,
|
||||
margin: '0 auto',
|
||||
padding: `${theme.spacing.unit * 8}px 0 ${theme.spacing.unit * 6}px`
|
||||
},
|
||||
cardGrid: {
|
||||
padding: `${theme.spacing.unit * 8}px 0`
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
},
|
||||
card: {
|
||||
display: 'flex',
|
||||
width: '100%'
|
||||
},
|
||||
cardDetails: {
|
||||
display: 'flex',
|
||||
flex: 1
|
||||
},
|
||||
cardContent: {
|
||||
flex: 1
|
||||
},
|
||||
cardMedia: {
|
||||
flex: 1,
|
||||
width: 100,
|
||||
height: 100,
|
||||
maxWidth: 100,
|
||||
justifyContent: 'flex-start'
|
||||
}
|
||||
})
|
||||
|
||||
export const ManageTeamMembersList = ({ classes, user = null }) => {
|
||||
let owner
|
||||
let authId = null
|
||||
log.debug('ManageTeamsList: ', user)
|
||||
if (user !== null && isString(user)) {
|
||||
owner = JSON.parse(user)
|
||||
if (isDefined(owner.authId)) authId = owner.authId
|
||||
}
|
||||
log.debug('ManageTeamsList 2: ', owner, authId)
|
||||
if (authId === undefined || authId === null) {
|
||||
return (
|
||||
<Grid container spacing={40}>
|
||||
<Grid>
|
||||
<MessageCard message='Loading...' />
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Query
|
||||
query={GET_TEAMS_BY_OWNER}
|
||||
fetchPolicy='network-only'
|
||||
>
|
||||
{({ loading, error, data }) => {
|
||||
log.debug('GET_TEAMS_BY_OWNER: ', loading, error, data)
|
||||
let errors = null
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
return <MessageCard message={message} />
|
||||
})
|
||||
}
|
||||
if (!loading && !error) {
|
||||
if (data.teams_TeamsByOwner.length > 0) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid container spacing={40}>
|
||||
{!loading &&
|
||||
data.teams_TeamsByOwner.map(team => (
|
||||
<ManageMembersTeamItem key={team.id} team={team} authId={authId} />
|
||||
))
|
||||
}
|
||||
{errors}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Grid container spacing={40}>
|
||||
<Grid item xs={10}>
|
||||
<MessageCard message='You don’t have any members. Somethings not right...' />
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<Grid container spacing={40}>
|
||||
<Grid item xs={12}>
|
||||
<MessageCard message='Loading...' />
|
||||
{errors !== null && <MessageCard message={errors} />}
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
}}
|
||||
</Query>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ManageTeamMembersList.propTypes = {
|
||||
user: PropTypes.any,
|
||||
classes: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
const getUserOnMount = lifecycle({
|
||||
componentDidMount () {
|
||||
getItem('user').then(user => {
|
||||
this.setState({ user })
|
||||
}) // Set user to state
|
||||
}
|
||||
})
|
||||
|
||||
const mapStateToProps = withStateHandlers(() => ({ user: null }), {
|
||||
user: ({ user }) => () => ({ user })
|
||||
})
|
||||
|
||||
const ManageTeamMembersListAuthId = compose(
|
||||
mapStateToProps,
|
||||
getUserOnMount
|
||||
)(ManageTeamMembersList)
|
||||
|
||||
export default withStyles(styles)(ManageTeamMembersListAuthId)
|
||||
@@ -0,0 +1,180 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import Paper from '@material-ui/core/Paper'
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import ManageTeamProfileView from './ManageTeamProfileView'
|
||||
import ManageTeamProfileEdit from './ManageTeamProfileEdit'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
flexGrow: 1
|
||||
},
|
||||
'@global': {
|
||||
body: {
|
||||
backgroundColor: theme.palette.common.white
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
width: 'auto',
|
||||
marginLeft: theme.spacing.unit * 3,
|
||||
marginRight: theme.spacing.unit * 3,
|
||||
marginBottom: theme.spacing.unit * 6,
|
||||
[theme.breakpoints.up(800 + theme.spacing.unit * 3 * 2)]: {
|
||||
width: 800,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto'
|
||||
}
|
||||
},
|
||||
paper: {
|
||||
marginTop: 0,
|
||||
marginBottom: theme.spacing.unit * 1,
|
||||
padding: 0,
|
||||
[theme.breakpoints.up(800 + theme.spacing.unit * 3 * 2)]: {
|
||||
marginTop: theme.spacing.unit * 3,
|
||||
marginBottom: theme.spacing.unit * 2,
|
||||
padding: `0 0 ${theme.spacing.unit * 2}px`
|
||||
}
|
||||
},
|
||||
heroContent: {
|
||||
maxWidth: 800,
|
||||
margin: '0 auto',
|
||||
padding: 0
|
||||
},
|
||||
teamContent: {
|
||||
margin: `${theme.spacing.unit * 1}px ${theme.spacing.unit * 3}px 0 0`
|
||||
},
|
||||
banner: {
|
||||
maxWidth: '100%',
|
||||
height: 'auto'
|
||||
},
|
||||
avatar: {
|
||||
maxWidth: 100,
|
||||
height: 100,
|
||||
margin: `${theme.spacing.unit * 1}px 0 0 ${theme.spacing.unit * 3}px `
|
||||
},
|
||||
profileButtons: {
|
||||
width: '90%',
|
||||
margin: `${theme.spacing.unit * 3}px auto 0`,
|
||||
padding: `${theme.spacing.unit * 2}px ${theme.spacing.unit * 2}px 0`,
|
||||
borderTop: '1px solid #CCCCCC'
|
||||
}
|
||||
})
|
||||
|
||||
export class ManageTeamProfile extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.toggleEdit = this.toggleEdit.bind(this)
|
||||
this.handleSave = this.handleSave.bind(this)
|
||||
this.handleUpdate = this.handleUpdate.bind(this)
|
||||
|
||||
this.state = {
|
||||
edit: false,
|
||||
save: false
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { classes, team, slug, match } = this.props
|
||||
log.debug('ManageTeamProfile render', this.state)
|
||||
return (
|
||||
<div className='container'>
|
||||
<main className={classes.layout}>
|
||||
<Paper className={classes.paper}>
|
||||
<div className={classes.heroContent}>
|
||||
{!this.state.edit && <ManageTeamProfileView team={team} />}
|
||||
{this.state.edit &&
|
||||
<ManageTeamProfileEdit
|
||||
team={team}
|
||||
slug={slug}
|
||||
match={match}
|
||||
save={this.state.save}
|
||||
handleUpdate={this.handleUpdate}
|
||||
/>
|
||||
}
|
||||
<Grid container justify='flex-end' direction='row' spacing={24} className={classes.profileButtons}>
|
||||
{!this.state.edit &&
|
||||
<React.Fragment>
|
||||
<Grid item>
|
||||
<Button
|
||||
component={Link}
|
||||
to='/teams/manage-teams'
|
||||
color='primary'
|
||||
variant='contained'
|
||||
>
|
||||
Back to all teams
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Button
|
||||
onClick={e => this.toggleEdit()}
|
||||
color='secondary'
|
||||
variant='contained'
|
||||
>
|
||||
Edit Profile
|
||||
</Button>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
}
|
||||
{this.state.edit &&
|
||||
<React.Fragment>
|
||||
<Grid item>
|
||||
<Button
|
||||
onClick={e => this.toggleEdit()}
|
||||
variant='contained'
|
||||
color='primary'
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Button
|
||||
onClick={e => this.handleSave(e)}
|
||||
variant='contained'
|
||||
color='secondary'
|
||||
>
|
||||
Save Changes
|
||||
</Button>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
}
|
||||
</Grid>
|
||||
</div>
|
||||
</Paper>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
toggleEdit () {
|
||||
this.setState({ edit: !this.state.edit })
|
||||
}
|
||||
|
||||
handleSave (e) {
|
||||
e.preventDefault()
|
||||
log.debug('handleSave')
|
||||
this.setState({ save: true })
|
||||
}
|
||||
|
||||
handleUpdate () {
|
||||
log.debug('handleUpdate')
|
||||
this.setState({ edit: false, save: false })
|
||||
}
|
||||
}
|
||||
|
||||
ManageTeamProfile.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
team: PropTypes.object.isRequired,
|
||||
match: PropTypes.object.isRequired,
|
||||
slug: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageTeamProfile)
|
||||
@@ -0,0 +1,379 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Mutation } from 'react-apollo'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
|
||||
import { ImageUpload } from '@superalgos/web-components'
|
||||
|
||||
import UPDATE_TEAM_PROFILE from '../../../graphql/teams/UpdateTeamProfileMutation'
|
||||
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
|
||||
|
||||
import { checkGraphQLError } from '../../../utils/graphql-errors'
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
flexGrow: 1
|
||||
},
|
||||
'@global': {
|
||||
body: {
|
||||
backgroundColor: theme.palette.common.white
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
width: '100%',
|
||||
marginBottom: theme.spacing.unit * 6,
|
||||
[theme.breakpoints.up(800 + theme.spacing.unit * 3 * 2)]: {
|
||||
width: 800,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto'
|
||||
}
|
||||
},
|
||||
paper: {
|
||||
marginTop: 0,
|
||||
marginBottom: theme.spacing.unit * 3,
|
||||
padding: 0,
|
||||
[theme.breakpoints.up(800 + theme.spacing.unit * 3 * 2)]: {
|
||||
marginTop: theme.spacing.unit * 3,
|
||||
marginBottom: theme.spacing.unit * 6,
|
||||
padding: `0 0 ${theme.spacing.unit * 2}px`
|
||||
}
|
||||
},
|
||||
heroContent: {
|
||||
maxWidth: 800,
|
||||
margin: '0 auto',
|
||||
padding: `0 0 ${theme.spacing.unit * 2}px`
|
||||
},
|
||||
editContentContainer: {
|
||||
marginBottom: `${theme.spacing.unit * 3}px`
|
||||
},
|
||||
metaContainer: {
|
||||
marginTop: `${theme.spacing.unit * 1}px`,
|
||||
alignSelf: 'stretch',
|
||||
borderRight: '1px solid #CCCCCC'
|
||||
},
|
||||
teamMeta: {
|
||||
margin: `${theme.spacing.unit * 3}px 0 0`,
|
||||
paddingLeft: `${theme.spacing.unit * 2}px`,
|
||||
textAlign: 'center',
|
||||
height: '100%'
|
||||
},
|
||||
teamContent: {
|
||||
'& h2': {
|
||||
padding: `${theme.spacing.unit * 4}px ${theme.spacing.unit * 3}px 0`
|
||||
},
|
||||
'& h4': {
|
||||
padding: `0 ${theme.spacing.unit * 3}px 0`
|
||||
},
|
||||
'& h6': {
|
||||
padding: `0 ${theme.spacing.unit * 3}px 0`
|
||||
}
|
||||
},
|
||||
editInput: {
|
||||
padding: `0 ${theme.spacing.unit * 3}px ${theme.spacing.unit * 2}px`,
|
||||
'& label': {
|
||||
display: 'none',
|
||||
padding: `0 ${theme.spacing.unit * 4}px 0`
|
||||
}
|
||||
},
|
||||
banner: {
|
||||
maxWidth: '100%',
|
||||
height: 'auto',
|
||||
width: '100%'
|
||||
},
|
||||
avatar: {
|
||||
maxWidth: 125,
|
||||
height: 125,
|
||||
margin: `0 auto ${theme.spacing.unit * 2}px`,
|
||||
verticalAlign: 'middle',
|
||||
borderRadius: '50%'
|
||||
}
|
||||
})
|
||||
|
||||
export class ManageTeamProfileEdit extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickOpen = this.handleClickOpen.bind(this)
|
||||
this.handleClose = this.handleClose.bind(this)
|
||||
this.handleChange = this.handleChange.bind(this)
|
||||
this.handleSubmit = this.handleSubmit.bind(this)
|
||||
this.handleAvatar = this.handleAvatar.bind(this)
|
||||
this.handleBanner = this.handleBanner.bind(this)
|
||||
this.startSave = this.startSave.bind(this)
|
||||
|
||||
log.debug('ManageTeamProfileEdit constructor', props.team)
|
||||
const motto = props.team.profile.motto || ''
|
||||
const description = props.team.profile.description || ''
|
||||
const avatar = props.team.profile.avatar || null
|
||||
const banner = props.team.profile.banner || null
|
||||
|
||||
this.state = {
|
||||
open: false,
|
||||
motto: motto,
|
||||
description: description,
|
||||
avatar: avatar,
|
||||
banner: banner,
|
||||
save: false
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { classes, team, match, save, handleUpdate } = this.props
|
||||
log.debug('ManageTeamProfileEdit render', this.props, this.props.slug, this.props.team, match)
|
||||
|
||||
const created = new Date(team.createdAt)
|
||||
|
||||
return (
|
||||
<Mutation
|
||||
mutation={UPDATE_TEAM_PROFILE}
|
||||
refetchQueries={[
|
||||
{
|
||||
query: GET_TEAMS_BY_OWNER
|
||||
}
|
||||
]}
|
||||
>
|
||||
{(updateTeamProfile, { loading, error, data }) => {
|
||||
const AzureStorageUrl = process.env.AZURE_STORAGE_URL_TEAMS
|
||||
const AzureStorageSAS = process.env.AZURE_STORAGE_SAS_TEAMS
|
||||
const containerName = team.slug
|
||||
let avatar = null
|
||||
if (this.state.avatar === null && team.profile !== null && (team.profile.avatar === undefined || team.profile.avatar === null)) avatar = process.env.STORAGE_URL + '/module-teams/module-default/aa-avatar-default.png'
|
||||
if (team.profile !== null && team.profile.avatar !== undefined && team.profile.avatar !== null) avatar = team.profile.avatar
|
||||
if (this.state.avatar !== null) avatar = this.state.avatar
|
||||
|
||||
let banner = null
|
||||
if (this.state.banner === null && team.profile !== null && (team.profile.banner === undefined || team.profile.banner === null)) banner = process.env.STORAGE_URL + '/module-teams/module-default/aa-banner-default.png'
|
||||
if (team.profile !== null && team.profile.banner !== undefined && team.profile.banner !== null) banner = team.profile.banner
|
||||
if (this.state.banner !== null) banner = this.state.banner
|
||||
log.debug('team images: ', avatar, banner)
|
||||
log.debug('saving profile status: ', save)
|
||||
if (save && !this.state.save) {
|
||||
log.debug('saving profile: ', save)
|
||||
this.startSave(updateTeamProfile, team.slug, handleUpdate)
|
||||
}
|
||||
|
||||
let errors
|
||||
let loader
|
||||
if (loading) {
|
||||
loader = (
|
||||
<Typography variant='caption'>Updating profile...</Typography>
|
||||
)
|
||||
}
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
const displayMessage = checkGraphQLError(message)
|
||||
log.debug('updateTeamProfile error:', displayMessage)
|
||||
return (
|
||||
<Typography key={i} variant='caption'>
|
||||
{message}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ImageUpload
|
||||
key='banner'
|
||||
handleUrl={this.handleBanner}
|
||||
fileName={`${team.slug}-banner.jpg`}
|
||||
containerName={containerName}
|
||||
existingImage={banner}
|
||||
imagePreviewConfig={{
|
||||
width: '800px',
|
||||
height: '200px',
|
||||
title: 'Change Banner',
|
||||
fontSize: '1.5em'
|
||||
}}
|
||||
cropContainerConfig={{ x: 10, y: 10, width: 800, height: 200 }}
|
||||
cropPreviewBox={{ width: 650, height: 200 }}
|
||||
saveImageConfig={{
|
||||
quality: 0.6,
|
||||
maxWidth: 800,
|
||||
maxHeight: 200,
|
||||
autoRotate: true,
|
||||
mimeType: 'image/jpeg'
|
||||
}}
|
||||
containerStyle={{
|
||||
display: 'block',
|
||||
margin: 0,
|
||||
height: '200px',
|
||||
width: '800px',
|
||||
overflow: 'visible'
|
||||
}}
|
||||
AzureStorageUrl={AzureStorageUrl}
|
||||
AzureSASURL={AzureStorageSAS}
|
||||
cropRatio={4}
|
||||
/>
|
||||
<Grid container className={classes.editContentContainer}>
|
||||
<Grid item md={3} className={classes.metaContainer}>
|
||||
<Grid container className={classes.teamMeta} direction='column' justify='flex-start'>
|
||||
<Grid item>
|
||||
<ImageUpload
|
||||
key='avatar1'
|
||||
handleUrl={this.handleAvatar}
|
||||
fileName={`${team.slug}-avatar.jpg`}
|
||||
containerName={containerName}
|
||||
existingImage={avatar}
|
||||
imagePreviewConfig={{
|
||||
width: '125px',
|
||||
height: '125px',
|
||||
title: 'Change Avatar',
|
||||
fontSize: '1.5em'
|
||||
}}
|
||||
cropContainerConfig={{ x: 10, y: 10, width: 200, height: 200 }}
|
||||
cropPreviewBox={{ width: 350, height: 350 }}
|
||||
saveImageConfig={{
|
||||
quality: 0.6,
|
||||
maxWidth: 200,
|
||||
maxHeight: 200,
|
||||
autoRotate: true,
|
||||
mimeType: 'image/jpeg'
|
||||
}}
|
||||
containerStyle={{
|
||||
display: 'block',
|
||||
margin: '0 auto 12px',
|
||||
height: '125px',
|
||||
width: '125px',
|
||||
overflow: 'visible'
|
||||
}}
|
||||
dropzoneStyle={{
|
||||
height: 125,
|
||||
title: 'Drop new image or click to select'
|
||||
}}
|
||||
AzureStorageUrl={AzureStorageUrl}
|
||||
AzureSASURL={AzureStorageSAS}
|
||||
cropRatio={1}
|
||||
/>
|
||||
</Grid>
|
||||
<Typography variant='subtitle1' paragraph gutterBottom>
|
||||
Members: {team.members.length}
|
||||
</Typography>
|
||||
<Typography variant='subtitle1' paragraph gutterBottom>
|
||||
Financial Beings: 1
|
||||
</Typography>
|
||||
<Typography variant='subtitle1' paragraph gutterBottom>
|
||||
Created: {created.toLocaleDateString()}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item md={9}>
|
||||
<Grid container className={classes.teamContent} direction='column'>
|
||||
<Typography
|
||||
variant='h2'
|
||||
color='textPrimary'
|
||||
gutterBottom
|
||||
>
|
||||
{team.name}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant='h4'
|
||||
color='textSecondary'
|
||||
>
|
||||
Motto:
|
||||
</Typography>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin='dense'
|
||||
id='motto'
|
||||
type='text'
|
||||
fullWidth
|
||||
value={this.state.motto}
|
||||
onChange={this.handleChange}
|
||||
className={classes.editInput}
|
||||
/>
|
||||
<Typography variant='h6' color='textPrimary'>
|
||||
Description:
|
||||
</Typography>
|
||||
<TextField
|
||||
margin='dense'
|
||||
id='description'
|
||||
type='text'
|
||||
rows={4}
|
||||
multiline
|
||||
fullWidth
|
||||
value={this.state.description}
|
||||
onChange={this.handleChange}
|
||||
className={classes.editInput}
|
||||
/>
|
||||
<Typography variant='h6' color='secondary'>
|
||||
{loader}
|
||||
{errors}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)
|
||||
}}
|
||||
</Mutation>
|
||||
)
|
||||
}
|
||||
|
||||
handleClickOpen () {
|
||||
this.setState({ open: true })
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
log.debug('handleChange: ', e.target.id, e.target.value)
|
||||
switch (e.target.id) {
|
||||
case 'motto':
|
||||
this.setState({ motto: e.target.value })
|
||||
break
|
||||
case 'description':
|
||||
this.setState({ description: e.target.value })
|
||||
break
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
handleAvatar (avatarUrl) {
|
||||
log.debug('handleAvatar: ', avatarUrl)
|
||||
this.setState({ avatar: `${avatarUrl}?${Math.random()}` })
|
||||
}
|
||||
|
||||
handleBanner (bannerUrl) {
|
||||
log.debug('handleBanner: ', bannerUrl)
|
||||
this.setState({ banner: `${bannerUrl}?${Math.random()}` })
|
||||
}
|
||||
|
||||
startSave (updateTeamProfile, slug, handleUpdate) {
|
||||
this.setState({ save: 'done' })
|
||||
this.handleSubmit(updateTeamProfile, slug, handleUpdate)
|
||||
}
|
||||
|
||||
async handleSubmit (updateTeamProfile, slug, handleUpdate) {
|
||||
log.debug('updateTeamProfile, handleSubmit: ', this.state)
|
||||
await updateTeamProfile({
|
||||
variables: {
|
||||
slug,
|
||||
owner: this.authId,
|
||||
description: this.state.description,
|
||||
motto: this.state.motto,
|
||||
avatar: this.state.avatar,
|
||||
banner: this.state.banner
|
||||
}
|
||||
})
|
||||
window.canvasApp.eventHandler.raiseEvent('User Profile Changed')
|
||||
handleUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
ManageTeamProfileEdit.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
slug: PropTypes.string.isRequired,
|
||||
team: PropTypes.object,
|
||||
match: PropTypes.object,
|
||||
save: PropTypes.bool,
|
||||
handleUpdate: PropTypes.func
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageTeamProfileEdit)
|
||||
@@ -0,0 +1,210 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Mutation } from 'react-apollo'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import EditIcon from '@material-ui/icons/Edit'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
|
||||
import { ImageUpload } from '@superalgos/web-components'
|
||||
|
||||
import UPDATE_FB from '../../../graphql/teams/UpdateFBMutation'
|
||||
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
|
||||
|
||||
import { checkGraphQLError } from '../../../utils/graphql-errors'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
dialogTitle: {
|
||||
textAlign: 'center',
|
||||
paddingBottom: `${theme.spacing.unit * 2}px`
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
}
|
||||
})
|
||||
|
||||
export class ManageTeamProfileFBEdit extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickOpen = this.handleClickOpen.bind(this)
|
||||
this.handleClose = this.handleClose.bind(this)
|
||||
this.handleSubmit = this.handleSubmit.bind(this)
|
||||
this.handleAvatar = this.handleAvatar.bind(this)
|
||||
|
||||
const avatar = props.fb.avatar || ''
|
||||
|
||||
this.state = {
|
||||
open: false,
|
||||
avatar: avatar
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
log.debug('ManageTeamProfileFBEdit ', this.props, this.props.slug, this.props.fb)
|
||||
const { classes, fb, slug } = this.props
|
||||
return (
|
||||
<Mutation
|
||||
mutation={UPDATE_FB}
|
||||
refetchQueries={[
|
||||
{
|
||||
query: GET_TEAMS_BY_OWNER
|
||||
}
|
||||
]}
|
||||
>
|
||||
{(updateFB, { loading, error, data }) => {
|
||||
const AzureStorageUrl = process.env.AZURE_STORAGE_URL_TEAMS
|
||||
const AzureStorageSAS = process.env.AZURE_STORAGE_SAS_TEAMS
|
||||
const containerName = slug
|
||||
|
||||
let avatar = null
|
||||
if (this.state.avatar === null && fb.avatar === undefined) avatar = process.env.STORAGE_URL + '/module-teams/module-default/aa-avatar-default.png'
|
||||
if (fb.avatar !== undefined && fb.avatar !== null) avatar = fb.avatar
|
||||
if (this.state.avatar !== null) avatar = this.state.avatar
|
||||
|
||||
let errors
|
||||
let loader
|
||||
if (loading) {
|
||||
loader = (
|
||||
<Typography variant='caption'>Updating avatar...</Typography>
|
||||
)
|
||||
}
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
const displayMessage = checkGraphQLError(message)
|
||||
log.debug('updateTeamProfile error:', displayMessage)
|
||||
return (
|
||||
<Typography key={i} variant='caption'>
|
||||
{message}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
}
|
||||
log.debug('ManageTeamProfileFBEdit mutation:', classes)
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
size='small'
|
||||
color='primary'
|
||||
className={classes.buttonRight}
|
||||
onClick={this.handleClickOpen}
|
||||
>
|
||||
<EditIcon /> Edit
|
||||
</Button>
|
||||
<Dialog
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby='form-dialog-title'
|
||||
>
|
||||
<DialogContent>
|
||||
<Grid container justify='center' direction='column'>
|
||||
<Grid item className={classes.dialogTitle}>
|
||||
<Typography variant='h4'>
|
||||
Edit Financial Being
|
||||
</Typography>
|
||||
</Grid>
|
||||
<ImageUpload
|
||||
key='avatar'
|
||||
handleUrl={this.handleAvatar}
|
||||
fileName={`${fb.slug}-fb-avatar.jpg`}
|
||||
containerName={containerName}
|
||||
existingImage={avatar}
|
||||
imagePreviewConfig={{
|
||||
width: '125px',
|
||||
height: '125px',
|
||||
title: 'Change FB Avatar',
|
||||
fontSize: '1.5em'
|
||||
}}
|
||||
cropContainerConfig={{ x: 10, y: 10, width: 200, height: 200 }}
|
||||
cropPreviewBox={{ width: 350, height: 350 }}
|
||||
saveImageConfig={{
|
||||
quality: 0.6,
|
||||
maxWidth: 200,
|
||||
maxHeight: 200,
|
||||
autoRotate: true,
|
||||
mimeType: 'image/jpeg'
|
||||
}}
|
||||
containerStyle={{
|
||||
display: 'block',
|
||||
margin: '0 auto 3em',
|
||||
height: '125px',
|
||||
width: '125px',
|
||||
overflow: 'visible'
|
||||
}}
|
||||
dropzoneStyle={{
|
||||
height: 125,
|
||||
title: 'Drop new image or click to select'
|
||||
}}
|
||||
AzureStorageUrl={AzureStorageUrl}
|
||||
AzureSASURL={AzureStorageSAS}
|
||||
cropRatio={1}
|
||||
debug
|
||||
/>
|
||||
{loader}
|
||||
{errors}
|
||||
<Grid container justify='center' alignItems='center' direction='column'>
|
||||
<Button
|
||||
onClick={e => {
|
||||
this.handleSubmit(e, updateFB, fb.id, slug)
|
||||
}}
|
||||
color='secondary'
|
||||
variant='contained'
|
||||
size='small'
|
||||
>
|
||||
Update Financial Being
|
||||
</Button>
|
||||
<Button onClick={this.handleClose} color='primary'>
|
||||
Cancel
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</Mutation>
|
||||
)
|
||||
}
|
||||
|
||||
handleClickOpen () {
|
||||
this.setState({ open: true })
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
|
||||
handleAvatar (avatarUrl) {
|
||||
log.debug('handleAvatar: ', avatarUrl)
|
||||
this.setState({ avatar: `${avatarUrl}?${Math.random()}` })
|
||||
}
|
||||
|
||||
async handleSubmit (e, updateFB, fbId, slug) {
|
||||
log.debug('handleSubmit: ', this.state)
|
||||
e.preventDefault()
|
||||
|
||||
await updateFB({
|
||||
variables: {
|
||||
fbId,
|
||||
avatar: this.state.avatar
|
||||
}
|
||||
})
|
||||
window.canvasApp.eventHandler.raiseEvent('User Profile Changed')
|
||||
this.setState({ open: false })
|
||||
}
|
||||
}
|
||||
|
||||
ManageTeamProfileFBEdit.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
slug: PropTypes.string.isRequired,
|
||||
fb: PropTypes.object
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageTeamProfileFBEdit)
|
||||
@@ -0,0 +1,78 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import Card from '@material-ui/core/Card'
|
||||
import CardContent from '@material-ui/core/CardContent'
|
||||
import CardActions from '@material-ui/core/CardActions'
|
||||
import CardMedia from '@material-ui/core/CardMedia'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import ManageTeamProfileFBEdit from './ManageTeamProfileFBEdit'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
fbContainer: {
|
||||
margin: `0 0 ${theme.spacing.unit * 2}px`
|
||||
},
|
||||
card: {
|
||||
display: 'flex',
|
||||
width: '100%'
|
||||
},
|
||||
cardDetails: {
|
||||
display: 'flex',
|
||||
flex: 1
|
||||
},
|
||||
cardContent: {
|
||||
flex: 1
|
||||
},
|
||||
cardMedia: {
|
||||
flex: 1,
|
||||
width: 100,
|
||||
height: 100,
|
||||
maxWidth: 100,
|
||||
justifyContent: 'flex-start'
|
||||
}
|
||||
})
|
||||
|
||||
export const ManageTeamProvileFBView = ({ classes, team }) => {
|
||||
log.debug('ManageTeamProvileFBView', team, team.fb[0], team.fb[0].avatar)
|
||||
|
||||
let fbs = team.fb;
|
||||
return fbs.map((fb, index) => {
|
||||
let avatar = (fb.avatar !== undefined && fb.avatar !== null) ? fb.avatar : process.env.STORAGE_URL + '/module-teams/module-default/aa-avatar-default.png'
|
||||
return (
|
||||
<Grid item xs={12} className={classes.fbContainer} key={`fb-${index}`}>
|
||||
<Card className={classes.card}>
|
||||
<div className={classes.cardDetails}>
|
||||
<CardMedia
|
||||
className={classes.cardMedia}
|
||||
image={avatar}
|
||||
title={fb.name}
|
||||
/>
|
||||
<CardContent className={classes.cardContent}>
|
||||
<Typography gutterBottom variant='h5' component='h2'>
|
||||
{fb.name}
|
||||
</Typography>
|
||||
<Typography variant='caption' color='textSecondary'>
|
||||
{fb.kind}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<ManageTeamProfileFBEdit slug={team.slug} fb={fb} />
|
||||
</CardActions>
|
||||
</div>
|
||||
</Card>
|
||||
</Grid>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
ManageTeamProvileFBView.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
team: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageTeamProvileFBView)
|
||||
@@ -0,0 +1,155 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import ManageTeamProfileFBView from './ManageTeamProfileFBView'
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
flexGrow: 1
|
||||
},
|
||||
'@global': {
|
||||
body: {
|
||||
backgroundColor: theme.palette.common.white
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
width: '100%',
|
||||
marginBottom: theme.spacing.unit * 6,
|
||||
[theme.breakpoints.up(800 + theme.spacing.unit * 3 * 2)]: {
|
||||
width: 800,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto'
|
||||
}
|
||||
},
|
||||
paper: {
|
||||
marginTop: 0,
|
||||
marginBottom: theme.spacing.unit * 3,
|
||||
padding: 0,
|
||||
[theme.breakpoints.up(800 + theme.spacing.unit * 3 * 2)]: {
|
||||
marginTop: theme.spacing.unit * 3,
|
||||
marginBottom: theme.spacing.unit * 6,
|
||||
padding: `0 0 ${theme.spacing.unit * 2}px`
|
||||
}
|
||||
},
|
||||
heroContent: {
|
||||
maxWidth: 800,
|
||||
margin: '0 auto',
|
||||
padding: `0 0 ${theme.spacing.unit * 2}px`
|
||||
},
|
||||
metaContainer: {
|
||||
marginTop: `${theme.spacing.unit * 1}px`,
|
||||
alignSelf: 'stretch',
|
||||
borderRight: '1px solid #CCCCCC'
|
||||
},
|
||||
teamMeta: {
|
||||
margin: `${theme.spacing.unit * 3}px 0 0`,
|
||||
paddingLeft: `${theme.spacing.unit * 2}px`,
|
||||
textAlign: 'center'
|
||||
},
|
||||
teamContent: {
|
||||
'& h2': {
|
||||
padding: `${theme.spacing.unit * 4}px ${theme.spacing.unit * 3}px 0`
|
||||
},
|
||||
'& h4': {
|
||||
padding: `0 ${theme.spacing.unit * 3}px ${theme.spacing.unit * 1}px`
|
||||
},
|
||||
'& h6': {
|
||||
padding: `0 ${theme.spacing.unit * 3}px 0`
|
||||
}
|
||||
},
|
||||
fbContent: {
|
||||
borderTop: '1px solid #CCCCCC',
|
||||
width: '90%',
|
||||
margin: `${theme.spacing.unit * 3}px auto`,
|
||||
paddingTop: `${theme.spacing.unit * 3}px`
|
||||
},
|
||||
banner: {
|
||||
maxWidth: '100%',
|
||||
height: 'auto',
|
||||
width: '100%'
|
||||
},
|
||||
avatar: {
|
||||
maxWidth: 125,
|
||||
height: 125,
|
||||
margin: `0 auto ${theme.spacing.unit * 2}px`,
|
||||
verticalAlign: 'middle',
|
||||
borderRadius: '50%'
|
||||
},
|
||||
backLink: {
|
||||
display: 'block',
|
||||
textDecoration: 'none',
|
||||
margin: `${theme.spacing.unit * 3}px 0 0`,
|
||||
color: theme.palette.secondary.main
|
||||
}
|
||||
})
|
||||
|
||||
const ManageTeamProfileView = ({ classes, team }) => {
|
||||
let avatar = team.profile.avatar || process.env.STORAGE_URL + '/module-teams/module-default/aa-avatar-default.png'
|
||||
let banner = team.profile.banner || process.env.STORAGE_URL + '/module-teams/module-default/aa-banner-default.png'
|
||||
|
||||
const created = new Date(team.createdAt)
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<img src={banner} alt={team.name} className={classes.banner} />
|
||||
<Grid container>
|
||||
<Grid item md={3} className={classes.metaContainer}>
|
||||
<Grid container className={classes.teamMeta} direction='column' justify='center'>
|
||||
<img src={avatar} alt={team.name} className={classes.avatar} />
|
||||
<Typography variant='subtitle1' paragraph gutterBottom>
|
||||
Members: {team.members.length}
|
||||
</Typography>
|
||||
<Typography variant='subtitle1' paragraph gutterBottom>
|
||||
Financial Beings: 1
|
||||
</Typography>
|
||||
<Typography variant='subtitle1' paragraph gutterBottom>
|
||||
Created: {created.toLocaleDateString()}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item md={9}>
|
||||
<Grid container className={classes.teamContent} direction='column'>
|
||||
<Typography
|
||||
variant='h2'
|
||||
color='textPrimary'
|
||||
gutterBottom
|
||||
>
|
||||
{team.name}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant='h4'
|
||||
color='textSecondary'
|
||||
gutterBottom
|
||||
>
|
||||
Motto: {team.profile.motto}
|
||||
</Typography>
|
||||
<Typography variant='h6' color='textPrimary' gutterBottom>
|
||||
Description: {team.profile.description}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid container className={classes.fbContent} direction='column'>
|
||||
<Typography
|
||||
variant='h4'
|
||||
color='textSecondary'
|
||||
gutterBottom
|
||||
>
|
||||
Financial Beings
|
||||
</Typography>
|
||||
<ManageTeamProfileFBView team={team} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
ManageTeamProfileView.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
team: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageTeamProfileView)
|
||||
@@ -0,0 +1,121 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import Paper from '@material-ui/core/Paper'
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import CreateTeamForm from './CreateTeamForm'
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
flexGrow: 1
|
||||
},
|
||||
'@global': {
|
||||
body: {
|
||||
backgroundColor: theme.palette.common.white
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
width: 'auto',
|
||||
marginLeft: theme.spacing.unit * 3,
|
||||
marginRight: theme.spacing.unit * 3,
|
||||
marginBottom: theme.spacing.unit * 6,
|
||||
[theme.breakpoints.up(800 + theme.spacing.unit * 3 * 2)]: {
|
||||
width: 800,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto'
|
||||
}
|
||||
},
|
||||
paper: {
|
||||
marginTop: 0,
|
||||
marginBottom: theme.spacing.unit * 3,
|
||||
padding: 0,
|
||||
[theme.breakpoints.up(800 + theme.spacing.unit * 3 * 2)]: {
|
||||
marginTop: theme.spacing.unit * 3,
|
||||
marginBottom: theme.spacing.unit * 6,
|
||||
padding: `0 0 ${theme.spacing.unit * 6}px`
|
||||
}
|
||||
},
|
||||
heroContent: {
|
||||
maxWidth: 800,
|
||||
margin: '0 auto',
|
||||
padding: 0
|
||||
},
|
||||
teamContent: {
|
||||
margin: `${theme.spacing.unit * 1}px ${theme.spacing.unit * 3}px 0 0`
|
||||
},
|
||||
banner: {
|
||||
maxWidth: '100%',
|
||||
height: 'auto'
|
||||
},
|
||||
avatar: {
|
||||
maxWidth: 100,
|
||||
height: 100,
|
||||
margin: `${theme.spacing.unit * 1}px 0 0 ${theme.spacing.unit * 3}px `
|
||||
},
|
||||
backLink: {
|
||||
display: 'block',
|
||||
textDecoration: 'none',
|
||||
margin: `${theme.spacing.unit * 3}px 0 0`,
|
||||
color: theme.palette.secondary.main
|
||||
}
|
||||
})
|
||||
|
||||
export class ManageTeamProfile extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.toggleEdit = this.toggleEdit.bind(this)
|
||||
this.handleSave = this.handleSave.bind(this)
|
||||
|
||||
this.state = {
|
||||
edit: false,
|
||||
save: false
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { classes, team, slug, match } = this.props
|
||||
|
||||
return (
|
||||
<div className='container'>
|
||||
<Paper>
|
||||
<Grid container spacing={0} direction='column' justify='stretch' alignItems='center'>
|
||||
<Grid item xs={10}>
|
||||
<Typography variant='h5' align='center'>
|
||||
You don’t have any teams. Create one!
|
||||
</Typography>
|
||||
<Typography variant='body1' align='center' gutterBottom>
|
||||
To begin developing on the Superalgos platform, as well as participate in Algobot competitions,
|
||||
you'll need to create a team. A default trading algobot will be cloned and added to your team so
|
||||
that you can begin experimenting right away.
|
||||
</Typography>
|
||||
<CreateTeamForm />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
toggleEdit () {
|
||||
this.setState({ edit: !this.state.edit })
|
||||
}
|
||||
|
||||
handleSave () {
|
||||
this.setState({ save: !this.state.edit })
|
||||
}
|
||||
}
|
||||
|
||||
ManageTeamProfile.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
team: PropTypes.object.isRequired,
|
||||
match: PropTypes.object.isRequired,
|
||||
slug: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageTeamProfile)
|
||||
@@ -0,0 +1,70 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import Card from '@material-ui/core/Card'
|
||||
import CardActions from '@material-ui/core/CardActions'
|
||||
import CardContent from '@material-ui/core/CardContent'
|
||||
import CardMedia from '@material-ui/core/CardMedia'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import EditIcon from '@material-ui/icons/Edit'
|
||||
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import ManageTeamDelete from './ManageTeamDelete'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
export const ManageTeamsItem = ({ classes, team, match, ...props }) => {
|
||||
log.debug('ManageTeamsItem', team, team.profile, team.profile.avatar)
|
||||
let avatar
|
||||
if (team.profile.avatar !== undefined && team.profile.avatar !== 'a') {
|
||||
avatar = team.profile.avatar
|
||||
} else {
|
||||
avatar = process.env.STORAGE_URL + '/module-teams/module-default/aa-avatar-default.png'
|
||||
}
|
||||
return (
|
||||
<Grid item xs={12}>
|
||||
<Card className={classes.card}>
|
||||
<div className={classes.cardDetails}>
|
||||
<CardMedia
|
||||
className={classes.cardMedia}
|
||||
image={avatar}
|
||||
title={team.name}
|
||||
/>
|
||||
<CardContent className={classes.cardContent}>
|
||||
<Typography gutterBottom variant='h5' component='h2'>
|
||||
{team.name}
|
||||
</Typography>
|
||||
<Typography variant='caption' color='textSecondary'>
|
||||
Members: {team.members.length} | Financial Beings: {1}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Grid container direction='row' justify='flex-end'>
|
||||
<Button
|
||||
size='small'
|
||||
color='primary'
|
||||
className={classes.buttonRight}
|
||||
component={Link}
|
||||
to={`${match.url}/${team.slug}`}
|
||||
>
|
||||
<EditIcon /> Manage Profile
|
||||
</Button>
|
||||
<ManageTeamDelete slug={team.slug} botSlug={team.fb[0].slug} />
|
||||
</Grid>
|
||||
</CardActions>
|
||||
</div>
|
||||
</Card>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
ManageTeamsItem.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
team: PropTypes.object.isRequired,
|
||||
match: PropTypes.object
|
||||
}
|
||||
|
||||
export default ManageTeamsItem
|
||||
@@ -0,0 +1,174 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Query } from 'react-apollo'
|
||||
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import Paper from '@material-ui/core/Paper'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import { MessageCard } from '@superalgos/web-components'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import { isEmpty } from '../../../utils/js-helpers'
|
||||
|
||||
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
|
||||
|
||||
import ManageTeamsItem from './ManageTeamsItem'
|
||||
import ManageTeamProfile from './ManageTeamProfile'
|
||||
import CreateTeamForm from './CreateTeamForm'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
flexGrow: 1
|
||||
},
|
||||
typography: {
|
||||
width: '80%',
|
||||
marginLeft: '10%',
|
||||
marginTop: 40
|
||||
},
|
||||
heroContent: {
|
||||
maxWidth: 800,
|
||||
margin: `${theme.spacing.unit * 4}px auto`,
|
||||
padding: 0
|
||||
},
|
||||
newTeamContainer: {
|
||||
padding: `${theme.spacing.unit * 4}px 0`
|
||||
},
|
||||
cardGrid: {
|
||||
padding: `${theme.spacing.unit * 8}px 0`
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
},
|
||||
card: {
|
||||
display: 'flex',
|
||||
width: '100%'
|
||||
},
|
||||
cardDetails: {
|
||||
display: 'flex',
|
||||
flex: 1
|
||||
},
|
||||
cardContent: {
|
||||
flex: 1
|
||||
},
|
||||
cardMedia: {
|
||||
flex: 1,
|
||||
width: 100,
|
||||
height: 100,
|
||||
maxWidth: 100,
|
||||
justifyContent: 'flex-start'
|
||||
},
|
||||
paper: {
|
||||
width: '100%',
|
||||
flexGrow: 1,
|
||||
padding: 10,
|
||||
marginTop: '5%',
|
||||
marginBottom: '10%'
|
||||
}
|
||||
})
|
||||
|
||||
export const ManageTeamsList = ({ classes, match, ...props }) => (
|
||||
<Query
|
||||
query={GET_TEAMS_BY_OWNER}
|
||||
fetchPolicy='network-only'
|
||||
>
|
||||
{({ loading, error, data }) => {
|
||||
log.debug('GET_TEAMS_BY_OWNER: ', loading, error, data)
|
||||
let slug = null
|
||||
if (!isEmpty(match.params) && match.params.slug) slug = match.params.slug
|
||||
|
||||
let errors = null
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
return <MessageCard message={message} />
|
||||
})
|
||||
}
|
||||
if (!loading && !error) {
|
||||
if (data.teams_TeamsByOwner.length > 0) {
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<div className={slug === null ? classes.heroContent : ''}>
|
||||
<Grid container spacing={8} direction='column' alignItems='stretch'>
|
||||
{!loading &&
|
||||
data.teams_TeamsByOwner.map(team => {
|
||||
if (slug === team.slug) {
|
||||
return (
|
||||
<ManageTeamProfile
|
||||
key={`team-profile-${team.id}`}
|
||||
team={team}
|
||||
slug={team.slug}
|
||||
match={match}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<ManageTeamsItem
|
||||
key={`team-list-${team.id}`}
|
||||
team={team}
|
||||
classes={classes}
|
||||
match={match}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
})}
|
||||
{errors}
|
||||
</Grid>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div className='container'>
|
||||
<Paper className={classes.paper} >
|
||||
|
||||
<Typography className={classes.typography} variant='h5' gutterBottom>
|
||||
Your First Team
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.typography} variant='body1' gutterBottom align='left'>
|
||||
Whether you are a trader willing to try out the Strategizer or a developer with more complex aspirations, the first thing you will need is to create a Team.
|
||||
</Typography>
|
||||
<Typography className={classes.typography} variant='body1' gutterBottom align='left'>
|
||||
At the time of Team creation, the platform will fork two template Finacial Beings to get you started.
|
||||
</Typography>
|
||||
<CreateTeamForm />
|
||||
|
||||
</Paper>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<div className={classes.root} >
|
||||
<Grid container spacing={0} direction='column' justify='center' alignItems='center'>
|
||||
<Grid item xs={12}>
|
||||
{errors === null &&
|
||||
<Typography variant='h5' align='center' gutterBottom>
|
||||
Loading...
|
||||
</Typography>
|
||||
}
|
||||
{errors !== null &&
|
||||
<Typography variant='h5' align='center' gutterBottom>
|
||||
{errors}
|
||||
</Typography>
|
||||
}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}}
|
||||
</Query>
|
||||
)
|
||||
|
||||
ManageTeamsList.propTypes = {
|
||||
user: PropTypes.any,
|
||||
classes: PropTypes.object.isRequired,
|
||||
match: PropTypes.object
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageTeamsList)
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { BannerTopBar } from '../common'
|
||||
import ManageTeamsList from './components/ManageTeamsList'
|
||||
|
||||
const ManageTeams = ({ match, ...props }) => (
|
||||
<div>
|
||||
<BannerTopBar
|
||||
size={match.params.slug !== undefined ? 'small' : 'medium'}
|
||||
title='Your Teams'
|
||||
text='Create and manage your teams here.'
|
||||
backgroundUrl='https://superalgos.org/img/photos/teams.jpg'
|
||||
/>
|
||||
<div className='container'>
|
||||
<ManageTeamsList {...props} match={match} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
ManageTeams.propTypes = {
|
||||
match: PropTypes.object
|
||||
}
|
||||
|
||||
export default ManageTeams
|
||||
@@ -0,0 +1,59 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import { MessageCard } from '@superalgos/web-components'
|
||||
|
||||
const styles = theme => ({
|
||||
heroContent: {
|
||||
maxWidth: 600,
|
||||
margin: '0 auto',
|
||||
padding: `${theme.spacing.unit * 8}px 0 ${theme.spacing.unit * 6}px`
|
||||
},
|
||||
cardGrid: {
|
||||
padding: `${theme.spacing.unit * 8}px 0`
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
},
|
||||
card: {
|
||||
display: 'flex',
|
||||
width: '100%'
|
||||
},
|
||||
cardDetails: {
|
||||
display: 'flex',
|
||||
flex: 1
|
||||
},
|
||||
cardContent: {
|
||||
flex: 1
|
||||
},
|
||||
cardMedia: {
|
||||
flex: 1,
|
||||
width: 100,
|
||||
height: 100,
|
||||
maxWidth: 100,
|
||||
justifyContent: 'flex-start'
|
||||
}
|
||||
})
|
||||
|
||||
export const Settings = ({ classes }) => (
|
||||
<Grid container spacing={40}>
|
||||
<Grid>
|
||||
<Typography variant='h4' gutterBottom>
|
||||
Settings
|
||||
</Typography>
|
||||
<div className={classes.tableContainer}>
|
||||
<MessageCard message='Coming soon. Management settings.' />
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
|
||||
Settings.propTypes = {
|
||||
classes: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default withStyles(styles)(Settings)
|
||||
@@ -0,0 +1,207 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Mutation } from 'react-apollo'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import Button from '@material-ui/core/Button'
|
||||
import AddIcon from '@material-ui/icons/Add'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import FormControl from '@material-ui/core/FormControl'
|
||||
import FormHelperText from '@material-ui/core/FormHelperText'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import { isEmpty } from '../../../utils/js-helpers'
|
||||
|
||||
import SEND_MEMBER_INVITE from '../../../graphql/teams/SendMemberInvite'
|
||||
|
||||
// import { checkGraphQLError } from '../../../utils/graphql-errors'
|
||||
|
||||
const styles = theme => ({
|
||||
dialogStyle: {
|
||||
padding: '3em'
|
||||
},
|
||||
buttonRight: {
|
||||
// position: 'absolute',
|
||||
// right: '3em'
|
||||
marginTop: '1em',
|
||||
marginRight: 'auto',
|
||||
marginLeft: 'auto',
|
||||
display: 'block',
|
||||
lineHeight: '1em'
|
||||
}
|
||||
})
|
||||
|
||||
export class InviteMemberDialog extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickOpen = this.handleClickOpen.bind(this)
|
||||
this.handleClose = this.handleClose.bind(this)
|
||||
this.handleChange = this.handleChange.bind(this)
|
||||
this.handleSubmit = this.handleSubmit.bind(this)
|
||||
this.validate = this.validate.bind(this)
|
||||
|
||||
this.state = {
|
||||
open: false,
|
||||
email: '',
|
||||
errors: {
|
||||
email: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { classes, teamId } = this.props
|
||||
|
||||
if (this.state.open) {
|
||||
return (
|
||||
<Mutation mutation={SEND_MEMBER_INVITE} >
|
||||
{(sendMemberInvite, { loading, error, data }) => {
|
||||
console.log('sendMemberInvite: ', data)
|
||||
let errors
|
||||
let loader = null
|
||||
if (loading) {
|
||||
loader = (
|
||||
<Typography variant='subtitle1'>Submitting invitation...</Typography>
|
||||
)
|
||||
}
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
// const displayMessage = checkGraphQLError(message)
|
||||
return (
|
||||
<Typography key={i} variant='caption'>
|
||||
{message}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
variant='fab'
|
||||
color='primary'
|
||||
aria-label='Add'
|
||||
className={classes.buttonRight}
|
||||
onClick={this.handleClickOpen}
|
||||
>
|
||||
<AddIcon />
|
||||
</Button>
|
||||
<Dialog
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby='form-dialog-title'
|
||||
>
|
||||
<DialogTitle id='form-dialog-title'>Invite a New Team Member</DialogTitle>
|
||||
<DialogContent>
|
||||
{(data === undefined || data.sendMemberInviteSG !== 'Success') && (
|
||||
<FormControl
|
||||
required
|
||||
error={this.state.errors.email !== '' || (error !== undefined || error !== null)}
|
||||
>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin='dense'
|
||||
id='email'
|
||||
label='Email Address'
|
||||
type='text'
|
||||
fullWidth
|
||||
value={this.state.email}
|
||||
onChange={e => this.handleChange(e)}
|
||||
error={this.state.errors.email !== ''}
|
||||
/>
|
||||
{this.state.errors.email !== '' && (
|
||||
<FormHelperText>{this.state.errors.email}</FormHelperText>
|
||||
)}
|
||||
{error && <FormHelperText>{errors}</FormHelperText>}
|
||||
{loader}
|
||||
</FormControl>
|
||||
)}
|
||||
{data !== undefined && data.sendMemberInviteSG === 'Success' && (
|
||||
<Typography variant='subtitle1'>Invitation successfully sent!</Typography>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{(data === undefined || data.sendMemberInviteSG !== 'Success') && (
|
||||
<React.Fragment>
|
||||
<Button onClick={this.handleClose} color='primary'>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={e => {
|
||||
this.handleSubmit(e, sendMemberInvite, this.state.email, teamId)
|
||||
}}
|
||||
color='primary'
|
||||
>
|
||||
Send Invitation
|
||||
</Button>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{data !== undefined && data.sendMemberInviteSG === 'Success' && (
|
||||
<Button onClick={this.handleClose} color='primary'>
|
||||
Close
|
||||
</Button>
|
||||
)}
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</Mutation>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Button
|
||||
variant='fab'
|
||||
color='primary'
|
||||
aria-label='Add'
|
||||
className={classes.buttonRight}
|
||||
onClick={this.handleClickOpen}
|
||||
>
|
||||
<AddIcon />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
handleClickOpen () {
|
||||
this.setState({ open: true })
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
const errors = this.validate(e.target.value)
|
||||
if (isEmpty(errors)) {
|
||||
this.setState({ errors: { email: '' } })
|
||||
this.setState({ email: e.target.value })
|
||||
} else {
|
||||
this.setState({ errors: errors })
|
||||
this.setState({ email: e.target.value })
|
||||
}
|
||||
}
|
||||
|
||||
async handleSubmit (e, sendMemberInvite, email, teamId) {
|
||||
e.preventDefault()
|
||||
console.log('sendMemberInvite submit: ', email, teamId)
|
||||
await sendMemberInvite({ variables: { email, teamId } })
|
||||
this.setState({ email: '' })
|
||||
}
|
||||
|
||||
validate (data) {
|
||||
const errors = {}
|
||||
if (data === '') errors.email = 'Please enter an email address'
|
||||
return errors
|
||||
}
|
||||
}
|
||||
|
||||
InviteMemberDialog.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
teamId: PropTypes.string
|
||||
}
|
||||
|
||||
export default withStyles(styles)(InviteMemberDialog)
|
||||
@@ -0,0 +1,153 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import { Mutation } from 'react-apollo'
|
||||
|
||||
import Button from '@material-ui/core/Button'
|
||||
import PersonAddDisabledIcon from '@material-ui/icons/PersonAddDisabled'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
|
||||
import REMOVE_TEAM_MEMBER from '../../../graphql/teams/RemoveTeamMemberMutation'
|
||||
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
|
||||
|
||||
import { checkGraphQLError } from '../../../utils/graphql-errors'
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
dialogContainer: {
|
||||
display: 'block',
|
||||
margin: '3em',
|
||||
minWidth: 400
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
}
|
||||
})
|
||||
|
||||
export class ManageMemberDelete extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickOpen = this.handleClickOpen.bind(this)
|
||||
this.handleClose = this.handleClose.bind(this)
|
||||
this.handleChange = this.handleChange.bind(this)
|
||||
this.handleSubmit = this.handleSubmit.bind(this)
|
||||
|
||||
this.state = {
|
||||
open: false
|
||||
}
|
||||
}
|
||||
|
||||
handleClickOpen () {
|
||||
this.setState({ open: true })
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
this.setState({ name: e.target.value })
|
||||
}
|
||||
|
||||
render () {
|
||||
log.debug(this.props, this.props.teamId)
|
||||
const { classes, teamId, authId } = this.props
|
||||
return (
|
||||
<Mutation
|
||||
mutation={REMOVE_TEAM_MEMBER}
|
||||
refetchQueries={[{ query: GET_TEAMS_BY_OWNER }]}
|
||||
>
|
||||
{(deleteTeam, { loading, error, data }) => {
|
||||
let errors
|
||||
let loader
|
||||
if (loading) {
|
||||
loader = (
|
||||
<Typography variant='caption'>Submitting team...</Typography>
|
||||
)
|
||||
}
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
const displayMessage = checkGraphQLError(message)
|
||||
log.debug('createTeam error:', displayMessage)
|
||||
return (
|
||||
<Typography key={i} variant='caption'>
|
||||
{message}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Button
|
||||
size='small'
|
||||
color='primary'
|
||||
className={classes.buttonRight}
|
||||
onClick={this.handleClickOpen}
|
||||
>
|
||||
<PersonAddDisabledIcon /> Remove
|
||||
</Button>
|
||||
<Dialog
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby='form-dialog-title'
|
||||
>
|
||||
<div classes={classes.dialogContainer}>
|
||||
<DialogTitle id='form-dialog-title'>
|
||||
Remove Team Member
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography variant='subtitle1' color='primary'>
|
||||
DANGER - Removing your team cannot be undone
|
||||
</Typography>
|
||||
<Typography variant='subtitle1'>
|
||||
Are you sure you want to remove this team member?
|
||||
</Typography>
|
||||
{loader}
|
||||
{errors}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleClose} color='primary'>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={e => {
|
||||
this.handleSubmit(
|
||||
e,
|
||||
deleteTeam,
|
||||
teamId,
|
||||
authId
|
||||
)
|
||||
}}
|
||||
color='primary'
|
||||
>
|
||||
Remove Member
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</div>
|
||||
</Dialog>
|
||||
</React.Fragment>
|
||||
)
|
||||
}}
|
||||
</Mutation>
|
||||
)
|
||||
}
|
||||
|
||||
async handleSubmit (e, deleteTeam, teamId, memberId) {
|
||||
e.preventDefault()
|
||||
await deleteTeam({ variables: { teamId, memberId } })
|
||||
this.setState({ open: false })
|
||||
}
|
||||
}
|
||||
|
||||
ManageMemberDelete.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
teamId: PropTypes.string.isRequired,
|
||||
authId: PropTypes.string
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageMemberDelete)
|
||||
@@ -0,0 +1,170 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import { Mutation } from 'react-apollo'
|
||||
|
||||
import Button from '@material-ui/core/Button'
|
||||
import AssignmentIndIcon from '@material-ui/icons/AssignmentInd'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import MenuItem from '@material-ui/core/MenuItem'
|
||||
|
||||
import SET_TEAM_MEMBER_ROLE from '../../../graphql/teams/SetTeamMemberRoleMutation'
|
||||
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
|
||||
|
||||
import { checkGraphQLError } from '../../../utils/graphql-errors'
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
dialogContainer: {
|
||||
display: 'block',
|
||||
margin: '3em',
|
||||
minWidth: 400
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
}
|
||||
})
|
||||
|
||||
const roles = [
|
||||
{ role: 'Admin' },
|
||||
{ role: 'Member' }
|
||||
]
|
||||
|
||||
export class ManageMemberRole extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickOpen = this.handleClickOpen.bind(this)
|
||||
this.handleClose = this.handleClose.bind(this)
|
||||
this.handleChange = this.handleChange.bind(this)
|
||||
this.handleSubmit = this.handleSubmit.bind(this)
|
||||
|
||||
this.state = {
|
||||
open: false,
|
||||
role: ''
|
||||
}
|
||||
}
|
||||
|
||||
handleClickOpen () {
|
||||
this.setState({ open: true })
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
this.setState({ name: e.target.value })
|
||||
}
|
||||
|
||||
render () {
|
||||
log.debug(this.props, this.props.teamId)
|
||||
const { classes, teamId, authId } = this.props
|
||||
return (
|
||||
<Mutation
|
||||
mutation={SET_TEAM_MEMBER_ROLE}
|
||||
refetchQueries={[{ query: GET_TEAMS_BY_OWNER }]}
|
||||
>
|
||||
{(setTeamMemberRole, { loading, error, data }) => {
|
||||
let errors
|
||||
let loader
|
||||
if (loading) {
|
||||
loader = (
|
||||
<Typography variant='caption'>Submitting team...</Typography>
|
||||
)
|
||||
}
|
||||
if (error) {
|
||||
errors = error.graphQLErrors.map(({ message }, i) => {
|
||||
const displayMessage = checkGraphQLError(message)
|
||||
log.debug('createTeam error:', displayMessage)
|
||||
return (
|
||||
<Typography key={i} variant='caption'>
|
||||
{message}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Button
|
||||
size='small'
|
||||
color='primary'
|
||||
className={classes.buttonRight}
|
||||
onClick={this.handleClickOpen}
|
||||
>
|
||||
<AssignmentIndIcon /> Set Role
|
||||
</Button>
|
||||
<Dialog
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby='form-dialog-title'
|
||||
>
|
||||
<div classes={classes.dialogContainer}>
|
||||
<DialogTitle id='form-dialog-title'>
|
||||
Set Team Member Role
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography variant='subtitle1'>
|
||||
Admin members can approve new team members, send team invitations, and remove team members.
|
||||
</Typography>
|
||||
<TextField
|
||||
select
|
||||
label='Team Role'
|
||||
value={this.state.role}
|
||||
onChange={(e) => this.setState({ role: e.target.value })}
|
||||
fullWidth
|
||||
>
|
||||
{roles.map(role => (
|
||||
<MenuItem key={role.role} value={role.role}>{role.role}</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
{loader}
|
||||
{errors}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleClose} color='primary'>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={e => {
|
||||
this.handleSubmit(
|
||||
e,
|
||||
setTeamMemberRole,
|
||||
teamId,
|
||||
authId
|
||||
)
|
||||
}}
|
||||
color='primary'
|
||||
>
|
||||
Set Member Role
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</div>
|
||||
</Dialog>
|
||||
</React.Fragment>
|
||||
)
|
||||
}}
|
||||
</Mutation>
|
||||
)
|
||||
}
|
||||
|
||||
async handleSubmit (e, setTeamMemberRole, teamId, authId) {
|
||||
e.preventDefault()
|
||||
const role = this.state.role
|
||||
await setTeamMemberRole({ variables: { teamId, memberId: authId, role: role } })
|
||||
this.setState({ open: false })
|
||||
}
|
||||
}
|
||||
|
||||
ManageMemberRole.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
teamId: PropTypes.string.isRequired,
|
||||
authId: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageMemberRole)
|
||||
@@ -0,0 +1,101 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
import Button from '@material-ui/core/Button'
|
||||
import HistoryIcon from '@material-ui/icons/History'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import Card from '@material-ui/core/Card'
|
||||
import CardContent from '@material-ui/core/CardContent'
|
||||
import CardActions from '@material-ui/core/CardActions'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
const styles = theme => ({
|
||||
dialogContainer: {
|
||||
display: 'block',
|
||||
margin: '3em',
|
||||
minWidth: 400
|
||||
},
|
||||
buttonRight: {
|
||||
justifyContent: 'flex-end'
|
||||
}
|
||||
})
|
||||
|
||||
export class ManageMemberStatus extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickOpen = this.handleClickOpen.bind(this)
|
||||
this.handleClose = this.handleClose.bind(this)
|
||||
|
||||
this.state = {
|
||||
open: false,
|
||||
role: ''
|
||||
}
|
||||
}
|
||||
|
||||
handleClickOpen () {
|
||||
this.setState({ open: true })
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
this.setState({ name: e.target.value })
|
||||
}
|
||||
|
||||
render () {
|
||||
log.debug(this.props, this.props.status)
|
||||
const { classes, status } = this.props
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
size='small'
|
||||
color='primary'
|
||||
className={this.props.classes.buttonRight}
|
||||
onClick={this.handleClickOpen}
|
||||
>
|
||||
<HistoryIcon /> Status Details
|
||||
</Button>
|
||||
<Dialog
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby='form-dialog-title'
|
||||
>
|
||||
<div classes={classes.dialogContainer}>
|
||||
<Card className={classes.card}>
|
||||
<CardContent>
|
||||
<Typography variant='h6' id='form-dialog-title'>
|
||||
Member Status Details
|
||||
</Typography>
|
||||
{status !== null && status.map(stat => {
|
||||
return (
|
||||
<Typography variant='h6' id='form-dialog-title' key={stat.createdAt}>
|
||||
{stat.status} | {stat.reason} | {stat.createdAt}
|
||||
</Typography>
|
||||
)
|
||||
})}
|
||||
<CardActions>
|
||||
<Button onClick={this.handleClose} color='primary'>
|
||||
Close
|
||||
</Button>
|
||||
</CardActions>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ManageMemberStatus.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
status: PropTypes.array
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ManageMemberStatus)
|
||||
@@ -0,0 +1,66 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
|
||||
import ManageMemberDelete from './ManageMemberDelete'
|
||||
import ManageMemberRole from './ManageMemberRole'
|
||||
import ManageMemberStatus from './ManageMemberStatus'
|
||||
import { MessageCard } from '@superalgos/web-components'
|
||||
|
||||
import log from '../../../utils/log'
|
||||
|
||||
export const ManageMembersItem = ({ classes, member, teamId, authId }) => {
|
||||
log.debug('ManageMembersItem', member)
|
||||
let profile = null
|
||||
let email = null
|
||||
let memberStatus = null
|
||||
if (member.member !== null) {
|
||||
profile = member.member
|
||||
}
|
||||
if (member.member === null && member.email !== null) {
|
||||
email = member.email
|
||||
}
|
||||
if (member.status.length > 0) memberStatus = member.status
|
||||
if (profile !== null || email !== null) {
|
||||
return (
|
||||
<Grid container className={classes.memberContainer}>
|
||||
<Grid item xs={4}>
|
||||
<Typography gutterBottom variant='subtitle1' component='h2' align='left' >
|
||||
{profile !== null ? profile.alias : email}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<Typography color='textSecondary' variant='body1' component='h4' align='left' >
|
||||
{email !== null ? `${memberStatus[memberStatus.length - 1].status} | Sent: ${memberStatus[memberStatus.length - 1].createdAt}` : `${member.role} | Member since: `}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
{ profile !== null && member.role !== 'OWNER' && (
|
||||
<ManageMemberRole teamId={teamId} authId={profile !== null ? profile.authId : null} className={classes.buttonRight} />
|
||||
)}
|
||||
{(email !== null || member.role !== 'OWNER') && (
|
||||
<ManageMemberStatus status={member.status} className={classes.buttonRight} />
|
||||
)}
|
||||
{(email !== null || member.role !== 'OWNER') && (
|
||||
<ManageMemberDelete teamId={teamId} authId={profile !== null ? profile.authId : null} email={email !== null ? email : null} className={classes.buttonRight} />
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<MessageCard message='No Member info found' />
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ManageMembersItem.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
member: PropTypes.object.isRequired,
|
||||
teamId: PropTypes.string.isRequired,
|
||||
authId: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default ManageMembersItem
|
||||