Merge branch 'main' into bounty
This commit is contained in:
commit
97c0a5cfa8
|
@ -1,32 +1,33 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml"
|
||||||
xmlns="http://www.w3.org/1999/xhtml"
|
xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||||
xmlns:v="urn:schemas-microsoft-com:vml"
|
|
||||||
xmlns:o="urn:schemas-microsoft-com:office:office"
|
|
||||||
>
|
|
||||||
<head>
|
<head>
|
||||||
<title>Welcome to Manifold Markets</title>
|
<title>Welcome to Manifold Markets!</title>
|
||||||
<!--[if !mso]><!-->
|
<!--[if !mso]><!-->
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<!--<![endif]-->
|
<!--<![endif]-->
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
#outlook a {
|
#outlook a {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
-webkit-text-size-adjust: 100%;
|
-webkit-text-size-adjust: 100%;
|
||||||
-ms-text-size-adjust: 100%;
|
-ms-text-size-adjust: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
table,
|
table,
|
||||||
td {
|
td {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
mso-table-lspace: 0pt;
|
mso-table-lspace: 0pt;
|
||||||
mso-table-rspace: 0pt;
|
mso-table-rspace: 0pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
border: 0;
|
border: 0;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
@ -35,6 +36,7 @@
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
-ms-interpolation-mode: bicubic;
|
-ms-interpolation-mode: bicubic;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 13px 0;
|
margin: 13px 0;
|
||||||
|
@ -52,9 +54,7 @@
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
<!--[if lte mso 11]>
|
<!--[if lte mso 11]>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.mj-outlook-group-fix {
|
.mj-outlook-group-fix { width:100% !important; }
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
|
@ -63,10 +63,6 @@
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
.mj-column-per-50 {
|
|
||||||
width: 50% !important;
|
|
||||||
max-width: 50%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<style media="screen and (min-width:480px)">
|
<style media="screen and (min-width:480px)">
|
||||||
|
@ -74,615 +70,139 @@
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
.moz-text-html .mj-column-per-50 {
|
|
||||||
width: 50% !important;
|
|
||||||
max-width: 50%;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
[owa] .mj-column-per-100 {
|
[owa] .mj-column-per-100 {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
[owa] .mj-column-per-50 {
|
|
||||||
width: 50% !important;
|
|
||||||
max-width: 50%;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
@media only screen and (max-width:480px) {
|
@media only screen and (max-width:480px) {
|
||||||
table.mj-full-width-mobile {
|
table.mj-full-width-mobile {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
td.mj-full-width-mobile {
|
td.mj-full-width-mobile {
|
||||||
width: auto !important;
|
width: auto !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body style="word-spacing: normal; background-color: #f4f4f4">
|
|
||||||
<div style="background-color: #f4f4f4">
|
<body style="word-spacing:normal;background-color:#F4F4F4;">
|
||||||
|
<div style="background-color:#F4F4F4;">
|
||||||
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||||
<div
|
<div style="background:#ffffff;background-color:#ffffff;margin:0px auto;max-width:600px;">
|
||||||
style="
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation"
|
||||||
background: #ffffff;
|
style="background:#ffffff;background-color:#ffffff;width:100%;">
|
||||||
background-color: #ffffff;
|
|
||||||
margin: 0px auto;
|
|
||||||
max-width: 600px;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<table
|
|
||||||
align="center"
|
|
||||||
border="0"
|
|
||||||
cellpadding="0"
|
|
||||||
cellspacing="0"
|
|
||||||
role="presentation"
|
|
||||||
style="background: #ffffff; background-color: #ffffff; width: 100%"
|
|
||||||
>
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td
|
<td
|
||||||
style="
|
style="direction:ltr;font-size:0px;padding:0px 0px 0px 0px;padding-bottom:0px;padding-left:0px;padding-right:0px;padding-top:0px;text-align:center;">
|
||||||
direction: ltr;
|
|
||||||
font-size: 0px;
|
|
||||||
padding: 0px 0px 0px 0px;
|
|
||||||
padding-bottom: 0px;
|
|
||||||
padding-left: 0px;
|
|
||||||
padding-right: 0px;
|
|
||||||
padding-top: 0px;
|
|
||||||
text-align: center;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
|
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
|
||||||
|
<div class="mj-column-per-100 mj-outlook-group-fix"
|
||||||
|
style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;"
|
||||||
|
width="100%">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td align="center"
|
||||||
|
style="font-size:0px;padding:0px 25px 0px 25px;padding-top:0px;padding-right:25px;padding-bottom:0px;padding-left:25px;word-break:break-word;">
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
|
||||||
|
style="border-collapse:collapse;border-spacing:0px;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="width:550px;"><a href="https://manifold.markets/home" target="_blank"><img
|
||||||
|
alt="" height="auto" src="https://03jlj.mjt.lu/img/03jlj/b/urx/sjtz.gif"
|
||||||
|
style="border:none;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;"
|
||||||
|
width="550"></a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="left"
|
||||||
|
style="font-size:0px;padding:10px 25px;padding-top:0px;padding-bottom:0px;word-break:break-word;">
|
||||||
<div
|
<div
|
||||||
class="mj-column-per-100 mj-outlook-group-fix"
|
style="font-family:Arial, sans-serif;font-size:18px;letter-spacing:normal;line-height:1;text-align:left;color:#000000;">
|
||||||
style="
|
<p class="text-build-content"
|
||||||
font-size: 0px;
|
style="line-height: 24px; margin: 10px 0; margin-top: 10px; margin-bottom: 10px;"
|
||||||
text-align: left;
|
data-testid="4XoHRGw1Y"><span
|
||||||
direction: ltr;
|
style="color:#000000;font-family:Arial, Helvetica, sans-serif;font-size:18px;">
|
||||||
display: inline-block;
|
Hi {{name}},</span></p>
|
||||||
vertical-align: top;
|
|
||||||
width: 100%;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<table
|
|
||||||
border="0"
|
|
||||||
cellpadding="0"
|
|
||||||
cellspacing="0"
|
|
||||||
role="presentation"
|
|
||||||
style="vertical-align: top"
|
|
||||||
width="100%"
|
|
||||||
>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td
|
|
||||||
align="center"
|
|
||||||
style="
|
|
||||||
font-size: 0px;
|
|
||||||
padding: 0px 25px 0px 25px;
|
|
||||||
padding-top: 0px;
|
|
||||||
padding-right: 25px;
|
|
||||||
padding-bottom: 0px;
|
|
||||||
padding-left: 25px;
|
|
||||||
word-break: break-word;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<table
|
|
||||||
border="0"
|
|
||||||
cellpadding="0"
|
|
||||||
cellspacing="0"
|
|
||||||
role="presentation"
|
|
||||||
style="
|
|
||||||
border-collapse: collapse;
|
|
||||||
border-spacing: 0px;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td style="width: 550px">
|
|
||||||
<a
|
|
||||||
href="https://manifold.markets/home"
|
|
||||||
target="_blank"
|
|
||||||
><img
|
|
||||||
alt=""
|
|
||||||
height="auto"
|
|
||||||
src="https://03jlj.mjt.lu/img/03jlj/b/urx/sjtz.gif"
|
|
||||||
style="
|
|
||||||
border: none;
|
|
||||||
display: block;
|
|
||||||
outline: none;
|
|
||||||
text-decoration: none;
|
|
||||||
height: auto;
|
|
||||||
width: 100%;
|
|
||||||
font-size: 13px;
|
|
||||||
"
|
|
||||||
width="550"
|
|
||||||
/></a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
<tr>
|
||||||
</table>
|
<td align="left"
|
||||||
|
style="font-size:0px;padding:10px 25px;padding-top:0px;padding-bottom:0px;word-break:break-word;">
|
||||||
|
<div
|
||||||
|
style="font-family:Arial, sans-serif;font-size:18px;letter-spacing:normal;line-height:1;text-align:left;color:#000000;">
|
||||||
|
<p class="text-build-content"
|
||||||
|
style="line-height: 24px; margin: 10px 0; margin-top: 10px; margin-bottom: 10px;"
|
||||||
|
data-testid="4XoHRGw1Y"><span
|
||||||
|
style="color:#000000;font-family:Arial, Helvetica, sans-serif;font-size:18px;">
|
||||||
|
Welcome! Manifold Markets is a play-money prediction market platform where you can bet on
|
||||||
|
anything, from elections to Elon Musk to scientific papers to the NBA. </span></p>
|
||||||
</div>
|
</div>
|
||||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
</td>
|
||||||
<div
|
</tr>
|
||||||
style="
|
|
||||||
background: #ffffff;
|
|
||||||
background-color: #ffffff;
|
|
||||||
margin: 0px auto;
|
|
||||||
max-width: 600px;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<table
|
|
||||||
align="center"
|
|
||||||
border="0"
|
|
||||||
cellpadding="0"
|
|
||||||
cellspacing="0"
|
|
||||||
role="presentation"
|
|
||||||
style="background: #ffffff; background-color: #ffffff; width: 100%"
|
|
||||||
>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td
|
<td align="left"
|
||||||
style="
|
style="font-size:0px;padding:10px 25px;padding-top:0px;padding-bottom:0px;word-break:break-word;">
|
||||||
direction: ltr;
|
|
||||||
font-size: 0px;
|
|
||||||
padding: 0px 0px 0px 0px;
|
|
||||||
padding-bottom: 0px;
|
|
||||||
padding-left: 0px;
|
|
||||||
padding-right: 0px;
|
|
||||||
padding-top: 0px;
|
|
||||||
text-align: center;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:300px;" ><![endif]-->
|
|
||||||
<div
|
<div
|
||||||
class="mj-column-per-50 mj-outlook-group-fix"
|
style="font-family:Arial, sans-serif;font-size:18px;letter-spacing:normal;line-height:1;text-align:left;color:#000000;">
|
||||||
style="
|
<p class="text-build-content"
|
||||||
font-size: 0px;
|
style="line-height: 24px; margin: 10px 0; margin-top: 10px; margin-bottom: 10px;"
|
||||||
text-align: left;
|
data-testid="4XoHRGw1Y"><span
|
||||||
direction: ltr;
|
style="color:#000000;font-family:Arial, Helvetica, sans-serif;font-size:18px;">
|
||||||
display: inline-block;
|
|
||||||
vertical-align: top;
|
|
||||||
width: 100%;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<table
|
|
||||||
border="0"
|
|
||||||
cellpadding="0"
|
|
||||||
cellspacing="0"
|
|
||||||
role="presentation"
|
|
||||||
style="vertical-align: top"
|
|
||||||
width="100%"
|
|
||||||
>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td
|
|
||||||
align="left"
|
|
||||||
style="
|
|
||||||
background: #ffffff;
|
|
||||||
font-size: 0px;
|
|
||||||
padding: 0px 15px 0px 50px;
|
|
||||||
padding-top: 0px;
|
|
||||||
padding-right: 15px;
|
|
||||||
padding-bottom: 0px;
|
|
||||||
padding-left: 50px;
|
|
||||||
word-break: break-word;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style="
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
font-size: 18px;
|
|
||||||
letter-spacing: normal;
|
|
||||||
line-height: 1;
|
|
||||||
text-align: left;
|
|
||||||
color: #000000;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
class="text-build-content"
|
|
||||||
data-testid="e885nY30eE"
|
|
||||||
style="margin: 10px 0; margin-top: 10px"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
style="
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
font-size: 18px;
|
|
||||||
"
|
|
||||||
><b
|
|
||||||
>Hi {{name}}, thanks for joining Manifold
|
|
||||||
Markets!</b
|
|
||||||
></span
|
|
||||||
><br /><br /><span
|
|
||||||
style="
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
font-size: 18px;
|
|
||||||
"
|
|
||||||
>We can't wait to see what questions you
|
|
||||||
will ask!</span
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
class="text-build-content"
|
|
||||||
style="line-height: 23px; margin: 10px 0"
|
|
||||||
data-testid="e885nY30eE"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
style="
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
font-size: 18px;
|
|
||||||
"
|
|
||||||
>As a gift M$1000 has been credited to your
|
|
||||||
account - the equivalent of 10 USD.
|
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<p
|
|
||||||
class="text-build-content"
|
|
||||||
style="
|
|
||||||
line-height: 23px;
|
|
||||||
margin: 10px 0;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
"
|
|
||||||
data-testid="e885nY30eE"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
style="
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
font-size: 18px;
|
|
||||||
"
|
|
||||||
>Click the buttons to see what you can do with
|
|
||||||
it!</span
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<!--[if mso | IE]></td><td class="" style="vertical-align:top;width:300px;" ><![endif]-->
|
|
||||||
<div
|
|
||||||
class="mj-column-per-50 mj-outlook-group-fix"
|
|
||||||
style="
|
|
||||||
font-size: 0px;
|
|
||||||
text-align: left;
|
|
||||||
direction: ltr;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: top;
|
|
||||||
width: 100%;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<table
|
|
||||||
border="0"
|
|
||||||
cellpadding="0"
|
|
||||||
cellspacing="0"
|
|
||||||
role="presentation"
|
|
||||||
style="vertical-align: top"
|
|
||||||
width="100%"
|
|
||||||
>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td
|
<td>
|
||||||
align="center"
|
<p></p>
|
||||||
style="
|
</td>
|
||||||
font-size: 0px;
|
</tr>
|
||||||
padding: 0px 15px 0px 0px;
|
|
||||||
padding-top: 0px;
|
|
||||||
padding-right: 15px;
|
|
||||||
padding-bottom: 0px;
|
|
||||||
padding-left: 0px;
|
|
||||||
word-break: break-word;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<table
|
|
||||||
border="0"
|
|
||||||
cellpadding="0"
|
|
||||||
cellspacing="0"
|
|
||||||
role="presentation"
|
|
||||||
style="
|
|
||||||
border-collapse: collapse;
|
|
||||||
border-spacing: 0px;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td style="width: 285px">
|
<td align="center">
|
||||||
<a
|
<table cellspacing="0" cellpadding="0">
|
||||||
href="https://manifold.markets/home"
|
<tr>
|
||||||
target="_blank"
|
<td>
|
||||||
>
|
<table cellspacing="0" cellpadding="0">
|
||||||
<img
|
<tr>
|
||||||
alt=""
|
<td style="border-radius: 2px;" bgcolor="#4337c9">
|
||||||
height="auto"
|
<a href="https://manifold.markets" target="_blank"
|
||||||
src="https://03jlj.mjt.lu/img/03jlj/b/urx/1rsm.png"
|
style="padding: 12px 16px; border: 1px solid #4337c9;border-radius: 16px;font-family: Helvetica, Arial, sans-serif;font-size: 24px; color: #ffffff;text-decoration: none;font-weight:bold;display: inline-block;">
|
||||||
style="
|
Explore markets
|
||||||
border: none;
|
|
||||||
display: block;
|
|
||||||
outline: none;
|
|
||||||
text-decoration: none;
|
|
||||||
height: auto;
|
|
||||||
width: 100%;
|
|
||||||
font-size: 13px;
|
|
||||||
"
|
|
||||||
width="285"
|
|
||||||
/>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td
|
<td align="left"
|
||||||
align="center"
|
style="font-size:0px;padding:15px 25px 0px 25px;padding-top:15px;padding-right:25px;padding-bottom:0px;padding-left:25px;word-break:break-word;">
|
||||||
style="
|
|
||||||
font-size: 0px;
|
|
||||||
padding: 0px 15px 0px 0px;
|
|
||||||
padding-top: 0px;
|
|
||||||
padding-right: 15px;
|
|
||||||
padding-bottom: 0px;
|
|
||||||
padding-left: 0px;
|
|
||||||
word-break: break-word;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<table
|
|
||||||
border="0"
|
|
||||||
cellpadding="0"
|
|
||||||
cellspacing="0"
|
|
||||||
role="presentation"
|
|
||||||
style="
|
|
||||||
border-collapse: collapse;
|
|
||||||
border-spacing: 0px;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td style="width: 285px">
|
|
||||||
<a
|
|
||||||
href="https://manifold.markets/create"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt=""
|
|
||||||
height="auto"
|
|
||||||
src="https://03jlj.mjt.lu/img/03jlj/b/urx/1rs2.png"
|
|
||||||
style="
|
|
||||||
border: none;
|
|
||||||
display: block;
|
|
||||||
outline: none;
|
|
||||||
text-decoration: none;
|
|
||||||
height: auto;
|
|
||||||
width: 100%;
|
|
||||||
font-size: 13px;
|
|
||||||
"
|
|
||||||
width="285"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td
|
|
||||||
align="center"
|
|
||||||
style="
|
|
||||||
font-size: 0px;
|
|
||||||
padding: 0px 15px 0px 0px;
|
|
||||||
padding-top: 0px;
|
|
||||||
padding-right: 15px;
|
|
||||||
padding-bottom: 0px;
|
|
||||||
padding-left: 0px;
|
|
||||||
word-break: break-word;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<table
|
|
||||||
border="0"
|
|
||||||
cellpadding="0"
|
|
||||||
cellspacing="0"
|
|
||||||
role="presentation"
|
|
||||||
style="
|
|
||||||
border-collapse: collapse;
|
|
||||||
border-spacing: 0px;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td style="width: 285px">
|
|
||||||
<a
|
|
||||||
href="https://manifold.markets/charity"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt=""
|
|
||||||
height="auto"
|
|
||||||
src="https://03jlj.mjt.lu/img/03jlj/b/urx/1rsp.png"
|
|
||||||
style="
|
|
||||||
border: none;
|
|
||||||
display: block;
|
|
||||||
outline: none;
|
|
||||||
text-decoration: none;
|
|
||||||
height: auto;
|
|
||||||
width: 100%;
|
|
||||||
font-size: 13px;
|
|
||||||
"
|
|
||||||
width="285"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
|
||||||
<div
|
<div
|
||||||
style="
|
style="font-family:Arial, sans-serif;font-size:18px;letter-spacing:normal;line-height:1;text-align:left;color:#000000;">
|
||||||
background: #ffffff;
|
<p class="text-build-content" style="line-height: 23px; margin: 10px 0; margin-top: 10px;"
|
||||||
background-color: #ffffff;
|
data-testid="3Q8BP69fq"></a></li>
|
||||||
margin: 0px auto;
|
</ul>
|
||||||
max-width: 600px;
|
<p class="text-build-content" data-testid="3Q8BP69fq" style="margin: 10px 0;"> </p>
|
||||||
"
|
<p class="text-build-content" data-testid="3Q8BP69fq" style="margin: 10px 0;"><span
|
||||||
>
|
style="color:#000000;font-family:Arial;font-size:18px;">Cheers,</span></p>
|
||||||
<table
|
<p class="text-build-content" data-testid="3Q8BP69fq" style="margin: 10px 0;"><span
|
||||||
align="center"
|
style="color:#000000;font-family:Arial;font-size:18px;">David from Manifold</span></p>
|
||||||
border="0"
|
<p class="text-build-content" data-testid="3Q8BP69fq"
|
||||||
cellpadding="0"
|
style="margin: 10px 0; margin-bottom: 10px;"> </p>
|
||||||
cellspacing="0"
|
|
||||||
role="presentation"
|
|
||||||
style="background: #ffffff; background-color: #ffffff; width: 100%"
|
|
||||||
>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td
|
|
||||||
style="
|
|
||||||
direction: ltr;
|
|
||||||
font-size: 0px;
|
|
||||||
padding: 20px 0px 0px 0px;
|
|
||||||
padding-bottom: 0px;
|
|
||||||
padding-left: 0px;
|
|
||||||
padding-right: 0px;
|
|
||||||
padding-top: 20px;
|
|
||||||
text-align: center;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
|
|
||||||
<div
|
|
||||||
class="mj-column-per-100 mj-outlook-group-fix"
|
|
||||||
style="
|
|
||||||
font-size: 0px;
|
|
||||||
text-align: left;
|
|
||||||
direction: ltr;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: top;
|
|
||||||
width: 100%;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<table
|
|
||||||
border="0"
|
|
||||||
cellpadding="0"
|
|
||||||
cellspacing="0"
|
|
||||||
role="presentation"
|
|
||||||
style="vertical-align: top"
|
|
||||||
width="100%"
|
|
||||||
>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td
|
|
||||||
align="left"
|
|
||||||
style="
|
|
||||||
font-size: 0px;
|
|
||||||
padding: 10px 25px;
|
|
||||||
padding-top: 0px;
|
|
||||||
padding-bottom: 0px;
|
|
||||||
word-break: break-word;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style="
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
font-size: 18px;
|
|
||||||
letter-spacing: normal;
|
|
||||||
line-height: 1;
|
|
||||||
text-align: left;
|
|
||||||
color: #000000;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
class="text-build-content"
|
|
||||||
data-testid="3Q8BP69fq"
|
|
||||||
style="margin: 10px 0; margin-top: 10px"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
style="
|
|
||||||
color: #55575d;
|
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
|
||||||
font-size: 18px;
|
|
||||||
"
|
|
||||||
>I</span
|
|
||||||
><span
|
|
||||||
style="
|
|
||||||
color: #000000;
|
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
|
||||||
font-size: 18px;
|
|
||||||
"
|
|
||||||
>f you have any questions or feedback we'd
|
|
||||||
love to hear from you in our </span
|
|
||||||
><a
|
|
||||||
class="link-build-content"
|
|
||||||
style="color: inherit; text-decoration: none"
|
|
||||||
target="_blank"
|
|
||||||
href="https://discord.gg/VARzUpyCSa"
|
|
||||||
><span
|
|
||||||
style="
|
|
||||||
color: #1435b8;
|
|
||||||
font-family: Arial;
|
|
||||||
font-size: 18px;
|
|
||||||
"
|
|
||||||
><u>Discord server!</u></span
|
|
||||||
></a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
class="text-build-content"
|
|
||||||
data-testid="3Q8BP69fq"
|
|
||||||
style="margin: 10px 0"
|
|
||||||
></p>
|
|
||||||
<p
|
|
||||||
class="text-build-content"
|
|
||||||
data-testid="3Q8BP69fq"
|
|
||||||
style="margin: 10px 0"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
style="
|
|
||||||
color: #000000;
|
|
||||||
font-family: Arial;
|
|
||||||
font-size: 18px;
|
|
||||||
"
|
|
||||||
>Looking forward to seeing you,</span
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
class="text-build-content"
|
|
||||||
data-testid="3Q8BP69fq"
|
|
||||||
style="margin: 10px 0"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
style="
|
|
||||||
color: #000000;
|
|
||||||
font-family: Arial;
|
|
||||||
font-size: 18px;
|
|
||||||
"
|
|
||||||
>David from Manifold</span
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
<br />
|
|
||||||
<p
|
|
||||||
class="text-build-content"
|
|
||||||
data-testid="3Q8BP69fq"
|
|
||||||
style="margin: 10px 0; margin-bottom: 10px"
|
|
||||||
></p>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -696,112 +216,54 @@
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||||
<div style="margin: 0px auto; max-width: 600px">
|
<div style="margin:0px auto;max-width:600px;">
|
||||||
<table
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||||
align="center"
|
|
||||||
border="0"
|
|
||||||
cellpadding="0"
|
|
||||||
cellspacing="0"
|
|
||||||
role="presentation"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td
|
<td style="direction:ltr;font-size:0px;padding:0 0 20px 0;text-align:center;">
|
||||||
style="
|
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
|
||||||
direction: ltr;
|
<div class="mj-column-per-100 mj-outlook-group-fix"
|
||||||
font-size: 0px;
|
style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||||
padding: 0 0 20px 0;
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;"
|
||||||
text-align: center;
|
width="100%">
|
||||||
"
|
<tbody>
|
||||||
></td>
|
<tr>
|
||||||
</tr>
|
<td align="center" style="font-size:0px;padding:0px;word-break:break-word;">
|
||||||
</tbody>
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
|
||||||
</table>
|
style="border-collapse:collapse;border-spacing:0px;">
|
||||||
</div>
|
</div>
|
||||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||||
<div style="margin: 0px auto; max-width: 600px">
|
<div style="margin:0px auto;max-width:600px;">
|
||||||
<table
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation"
|
||||||
align="center"
|
style="width:100%;">
|
||||||
border="0"
|
|
||||||
cellpadding="0"
|
|
||||||
cellspacing="0"
|
|
||||||
role="presentation"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td
|
<td style="direction:ltr;font-size:0px;padding:20px 0px 20px 0px;text-align:center;">
|
||||||
style="
|
|
||||||
direction: ltr;
|
|
||||||
font-size: 0px;
|
|
||||||
padding: 20px 0px 20px 0px;
|
|
||||||
text-align: center;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
|
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
|
||||||
<div
|
<div class="mj-column-per-100 mj-outlook-group-fix"
|
||||||
class="mj-column-per-100 mj-outlook-group-fix"
|
style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||||
style="
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%">
|
||||||
font-size: 0px;
|
|
||||||
text-align: left;
|
|
||||||
direction: ltr;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: top;
|
|
||||||
width: 100%;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<table
|
|
||||||
border="0"
|
|
||||||
cellpadding="0"
|
|
||||||
cellspacing="0"
|
|
||||||
role="presentation"
|
|
||||||
width="100%"
|
|
||||||
>
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="vertical-align: top; padding: 0">
|
<td style="vertical-align:top;padding:0;">
|
||||||
<table
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%">
|
||||||
border="0"
|
|
||||||
cellpadding="0"
|
|
||||||
cellspacing="0"
|
|
||||||
role="presentation"
|
|
||||||
width="100%"
|
|
||||||
>
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td
|
<td align="center"
|
||||||
align="center"
|
style="font-size:0px;padding:10px 25px;padding-top:0px;padding-bottom:0px;word-break:break-word;">
|
||||||
style="
|
|
||||||
font-size: 0px;
|
|
||||||
padding: 10px 25px;
|
|
||||||
padding-top: 0px;
|
|
||||||
padding-bottom: 0px;
|
|
||||||
word-break: break-word;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
style="
|
style="font-family:Arial, sans-serif;font-size:11px;letter-spacing:normal;line-height:22px;text-align:center;color:#000000;">
|
||||||
font-family: Arial, sans-serif;
|
<p style="margin: 10px 0;">This e-mail has been sent to {{name}}, <a
|
||||||
font-size: 11px;
|
href="{{unsubscribeLink}}” style=" color:inherit;text-decoration:none;"
|
||||||
letter-spacing: normal;
|
target="_blank">click here to unsubscribe</a>.</p>
|
||||||
line-height: 22px;
|
</div>
|
||||||
text-align: center;
|
</td>
|
||||||
color: #000000;
|
</tr>
|
||||||
"
|
<tr>
|
||||||
>
|
<td align="center"
|
||||||
<p style="margin: 10px 0">
|
style="font-size:0px;padding:10px 25px;padding-top:0px;padding-bottom:0px;word-break:break-word;">
|
||||||
This e-mail has been sent to {{name}},
|
<div
|
||||||
<a
|
style="font-family:Arial, sans-serif;font-size:11px;letter-spacing:normal;line-height:22px;text-align:center;color:#000000;">
|
||||||
href="{{unsubscribeLink}}"
|
|
||||||
style="
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
"
|
|
||||||
target="_blank"
|
|
||||||
>click here to unsubscribe</a
|
|
||||||
>.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -821,4 +283,5 @@
|
||||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
|
@ -30,7 +30,15 @@ const bodySchema = z.object({
|
||||||
|
|
||||||
const binarySchema = z.object({
|
const binarySchema = z.object({
|
||||||
outcome: z.enum(['YES', 'NO']),
|
outcome: z.enum(['YES', 'NO']),
|
||||||
limitProb: z.number().gte(0.001).lte(0.999).optional(),
|
limitProb: z
|
||||||
|
.number()
|
||||||
|
.gte(0.001)
|
||||||
|
.lte(0.999)
|
||||||
|
.refine(
|
||||||
|
(p) => Math.round(p * 100) === p * 100,
|
||||||
|
'limitProb must be in increments of 0.01 (i.e. whole percentage points)'
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const freeResponseSchema = z.object({
|
const freeResponseSchema = z.object({
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Router from 'next/router'
|
import Router from 'next/router'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { MouseEvent } from 'react'
|
import { MouseEvent, useState } from 'react'
|
||||||
import { UserCircleIcon, UserIcon, UsersIcon } from '@heroicons/react/solid'
|
import { UserCircleIcon, UserIcon, UsersIcon } from '@heroicons/react/solid'
|
||||||
|
|
||||||
export function Avatar(props: {
|
export function Avatar(props: {
|
||||||
|
@ -10,7 +10,8 @@ export function Avatar(props: {
|
||||||
size?: number | 'xs' | 'sm'
|
size?: number | 'xs' | 'sm'
|
||||||
className?: string
|
className?: string
|
||||||
}) {
|
}) {
|
||||||
const { username, avatarUrl, noLink, size, className } = props
|
const { username, noLink, size, className } = props
|
||||||
|
const [avatarUrl, setAvatarUrl] = useState(props.avatarUrl)
|
||||||
const s = size == 'xs' ? 6 : size === 'sm' ? 8 : size || 10
|
const s = size == 'xs' ? 6 : size === 'sm' ? 8 : size || 10
|
||||||
|
|
||||||
const onClick =
|
const onClick =
|
||||||
|
@ -35,6 +36,11 @@ export function Avatar(props: {
|
||||||
src={avatarUrl}
|
src={avatarUrl}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
alt={username}
|
alt={username}
|
||||||
|
onError={() => {
|
||||||
|
// If the image doesn't load, clear the avatarUrl to show the default
|
||||||
|
// Mostly for localhost, when getting a 403 from googleusercontent
|
||||||
|
setAvatarUrl('')
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<UserCircleIcon
|
<UserCircleIcon
|
||||||
|
|
|
@ -4,11 +4,7 @@ import { SearchOptions } from '@algolia/client-search'
|
||||||
|
|
||||||
import { Contract } from 'common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { User } from 'common/user'
|
import { User } from 'common/user'
|
||||||
import {
|
import { Sort, useQuery, useSort } from '../hooks/use-sort-and-query-params'
|
||||||
QuerySortOptions,
|
|
||||||
Sort,
|
|
||||||
useQueryAndSortParams,
|
|
||||||
} from '../hooks/use-sort-and-query-params'
|
|
||||||
import {
|
import {
|
||||||
ContractHighlightOptions,
|
ContractHighlightOptions,
|
||||||
ContractsGrid,
|
ContractsGrid,
|
||||||
|
@ -24,11 +20,25 @@ import ContractSearchFirestore from 'web/pages/contract-search-firestore'
|
||||||
import { useMemberGroups } from 'web/hooks/use-group'
|
import { useMemberGroups } from 'web/hooks/use-group'
|
||||||
import { NEW_USER_GROUP_SLUGS } from 'common/group'
|
import { NEW_USER_GROUP_SLUGS } from 'common/group'
|
||||||
import { PillButton } from './buttons/pill-button'
|
import { PillButton } from './buttons/pill-button'
|
||||||
import { sortBy } from 'lodash'
|
import { debounce, sortBy } from 'lodash'
|
||||||
import { DEFAULT_CATEGORY_GROUPS } from 'common/categories'
|
import { DEFAULT_CATEGORY_GROUPS } from 'common/categories'
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
|
import { safeLocalStorage } from 'web/lib/util/local'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
|
|
||||||
|
// TODO: this obviously doesn't work with SSR, common sense would suggest
|
||||||
|
// that we should save things like this in cookies so the server has them
|
||||||
|
|
||||||
|
const MARKETS_SORT = 'markets_sort'
|
||||||
|
|
||||||
|
function setSavedSort(s: Sort) {
|
||||||
|
safeLocalStorage()?.setItem(MARKETS_SORT, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSavedSort() {
|
||||||
|
return safeLocalStorage()?.getItem(MARKETS_SORT) as Sort | null | undefined
|
||||||
|
}
|
||||||
|
|
||||||
const searchClient = algoliasearch(
|
const searchClient = algoliasearch(
|
||||||
'GJQPAYENIF',
|
'GJQPAYENIF',
|
||||||
'75c28fc084a80e1129d427d470cf41a3'
|
'75c28fc084a80e1129d427d470cf41a3'
|
||||||
|
@ -47,7 +57,6 @@ const sortOptions = [
|
||||||
{ label: 'Close date', value: 'close-date' },
|
{ label: 'Close date', value: 'close-date' },
|
||||||
{ label: 'Resolve date', value: 'resolve-date' },
|
{ label: 'Resolve date', value: 'resolve-date' },
|
||||||
]
|
]
|
||||||
export const DEFAULT_SORT = 'score'
|
|
||||||
|
|
||||||
type filter = 'personal' | 'open' | 'closed' | 'resolved' | 'all'
|
type filter = 'personal' | 'open' | 'closed' | 'resolved' | 'all'
|
||||||
|
|
||||||
|
@ -68,7 +77,8 @@ type AdditionalFilter = {
|
||||||
|
|
||||||
export function ContractSearch(props: {
|
export function ContractSearch(props: {
|
||||||
user?: User | null
|
user?: User | null
|
||||||
querySortOptions?: { defaultFilter?: filter } & QuerySortOptions
|
defaultSort?: Sort
|
||||||
|
defaultFilter?: filter
|
||||||
additionalFilter?: AdditionalFilter
|
additionalFilter?: AdditionalFilter
|
||||||
highlightOptions?: ContractHighlightOptions
|
highlightOptions?: ContractHighlightOptions
|
||||||
onContractClick?: (contract: Contract) => void
|
onContractClick?: (contract: Contract) => void
|
||||||
|
@ -79,10 +89,13 @@ export function ContractSearch(props: {
|
||||||
hideQuickBet?: boolean
|
hideQuickBet?: boolean
|
||||||
}
|
}
|
||||||
headerClassName?: string
|
headerClassName?: string
|
||||||
|
useQuerySortLocalStorage?: boolean
|
||||||
|
useQuerySortUrlParams?: boolean
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
user,
|
user,
|
||||||
querySortOptions,
|
defaultSort,
|
||||||
|
defaultFilter,
|
||||||
additionalFilter,
|
additionalFilter,
|
||||||
onContractClick,
|
onContractClick,
|
||||||
overrideGridClassName,
|
overrideGridClassName,
|
||||||
|
@ -90,6 +103,8 @@ export function ContractSearch(props: {
|
||||||
cardHideOptions,
|
cardHideOptions,
|
||||||
highlightOptions,
|
highlightOptions,
|
||||||
headerClassName,
|
headerClassName,
|
||||||
|
useQuerySortLocalStorage,
|
||||||
|
useQuerySortUrlParams,
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
const [numPages, setNumPages] = useState(1)
|
const [numPages, setNumPages] = useState(1)
|
||||||
|
@ -124,6 +139,7 @@ export function ContractSearch(props: {
|
||||||
setNumPages(results.nbPages)
|
setNumPages(results.nbPages)
|
||||||
if (freshQuery) {
|
if (freshQuery) {
|
||||||
setPages([newPage])
|
setPages([newPage])
|
||||||
|
window.scrollTo(0, 0)
|
||||||
} else {
|
} else {
|
||||||
setPages((pages) => [...pages, newPage])
|
setPages((pages) => [...pages, newPage])
|
||||||
}
|
}
|
||||||
|
@ -132,31 +148,33 @@ export function ContractSearch(props: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onSearchParametersChanged = useRef(
|
||||||
|
debounce((params) => {
|
||||||
|
searchParameters.current = params
|
||||||
|
performQuery(true)
|
||||||
|
}, 100)
|
||||||
|
).current
|
||||||
|
|
||||||
const contracts = pages
|
const contracts = pages
|
||||||
.flat()
|
.flat()
|
||||||
.filter((c) => !additionalFilter?.excludeContractIds?.includes(c.id))
|
.filter((c) => !additionalFilter?.excludeContractIds?.includes(c.id))
|
||||||
|
|
||||||
if (IS_PRIVATE_MANIFOLD || process.env.NEXT_PUBLIC_FIREBASE_EMULATE) {
|
if (IS_PRIVATE_MANIFOLD || process.env.NEXT_PUBLIC_FIREBASE_EMULATE) {
|
||||||
return (
|
return <ContractSearchFirestore additionalFilter={additionalFilter} />
|
||||||
<ContractSearchFirestore
|
|
||||||
querySortOptions={querySortOptions}
|
|
||||||
additionalFilter={additionalFilter}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col className="h-full">
|
<Col className="h-full">
|
||||||
<ContractSearchControls
|
<ContractSearchControls
|
||||||
className={headerClassName}
|
className={headerClassName}
|
||||||
|
defaultSort={defaultSort}
|
||||||
|
defaultFilter={defaultFilter}
|
||||||
additionalFilter={additionalFilter}
|
additionalFilter={additionalFilter}
|
||||||
hideOrderSelector={hideOrderSelector}
|
hideOrderSelector={hideOrderSelector}
|
||||||
querySortOptions={querySortOptions}
|
useQuerySortLocalStorage={useQuerySortLocalStorage}
|
||||||
|
useQuerySortUrlParams={useQuerySortUrlParams}
|
||||||
user={user}
|
user={user}
|
||||||
onSearchParametersChanged={(params) => {
|
onSearchParametersChanged={onSearchParametersChanged}
|
||||||
searchParameters.current = params
|
|
||||||
performQuery(true)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<ContractsGrid
|
<ContractsGrid
|
||||||
contracts={pages.length === 0 ? undefined : contracts}
|
contracts={pages.length === 0 ? undefined : contracts}
|
||||||
|
@ -173,23 +191,40 @@ export function ContractSearch(props: {
|
||||||
|
|
||||||
function ContractSearchControls(props: {
|
function ContractSearchControls(props: {
|
||||||
className?: string
|
className?: string
|
||||||
|
defaultSort?: Sort
|
||||||
|
defaultFilter?: filter
|
||||||
additionalFilter?: AdditionalFilter
|
additionalFilter?: AdditionalFilter
|
||||||
hideOrderSelector?: boolean
|
hideOrderSelector?: boolean
|
||||||
onSearchParametersChanged: (params: SearchParameters) => void
|
onSearchParametersChanged: (params: SearchParameters) => void
|
||||||
querySortOptions?: { defaultFilter?: filter } & QuerySortOptions
|
useQuerySortLocalStorage?: boolean
|
||||||
|
useQuerySortUrlParams?: boolean
|
||||||
user?: User | null
|
user?: User | null
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
className,
|
className,
|
||||||
|
defaultSort,
|
||||||
|
defaultFilter,
|
||||||
additionalFilter,
|
additionalFilter,
|
||||||
hideOrderSelector,
|
hideOrderSelector,
|
||||||
onSearchParametersChanged,
|
onSearchParametersChanged,
|
||||||
querySortOptions,
|
useQuerySortLocalStorage,
|
||||||
|
useQuerySortUrlParams,
|
||||||
user,
|
user,
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
const { query, setQuery, sort, setSort } =
|
const savedSort = useQuerySortLocalStorage ? getSavedSort() : null
|
||||||
useQueryAndSortParams(querySortOptions)
|
const initialSort = savedSort ?? defaultSort ?? 'score'
|
||||||
|
const querySortOpts = { useUrl: !!useQuerySortUrlParams }
|
||||||
|
const [sort, setSort] = useSort(initialSort, querySortOpts)
|
||||||
|
const [query, setQuery] = useQuery('', querySortOpts)
|
||||||
|
const [filter, setFilter] = useState<filter>(defaultFilter ?? 'open')
|
||||||
|
const [pillFilter, setPillFilter] = useState<string | undefined>(undefined)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (useQuerySortLocalStorage) {
|
||||||
|
setSavedSort(sort)
|
||||||
|
}
|
||||||
|
}, [sort])
|
||||||
|
|
||||||
const follows = useFollows(user?.id)
|
const follows = useFollows(user?.id)
|
||||||
const memberGroups = (useMemberGroups(user?.id) ?? []).filter(
|
const memberGroups = (useMemberGroups(user?.id) ?? []).filter(
|
||||||
|
@ -208,12 +243,6 @@ function ContractSearchControls(props: {
|
||||||
const pillGroups: { name: string; slug: string }[] =
|
const pillGroups: { name: string; slug: string }[] =
|
||||||
memberPillGroups.length > 0 ? memberPillGroups : DEFAULT_CATEGORY_GROUPS
|
memberPillGroups.length > 0 ? memberPillGroups : DEFAULT_CATEGORY_GROUPS
|
||||||
|
|
||||||
const [filter, setFilter] = useState<filter>(
|
|
||||||
querySortOptions?.defaultFilter ?? 'open'
|
|
||||||
)
|
|
||||||
|
|
||||||
const [pillFilter, setPillFilter] = useState<string | undefined>(undefined)
|
|
||||||
|
|
||||||
const additionalFilters = [
|
const additionalFilters = [
|
||||||
additionalFilter?.creatorId
|
additionalFilter?.creatorId
|
||||||
? `creatorId:${additionalFilter.creatorId}`
|
? `creatorId:${additionalFilter.creatorId}`
|
||||||
|
|
|
@ -78,7 +78,7 @@ export function ContractCard(props: {
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Col className="group relative flex-1 gap-3 py-4 pl-6">
|
<Col className="group relative flex-1 gap-3 py-4 pb-12 pl-6">
|
||||||
{onClick ? (
|
{onClick ? (
|
||||||
<a
|
<a
|
||||||
className="absolute top-0 left-0 right-0 bottom-0"
|
className="absolute top-0 left-0 right-0 bottom-0"
|
||||||
|
@ -106,7 +106,10 @@ export function ContractCard(props: {
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
<AvatarDetails contract={contract} />
|
<AvatarDetails
|
||||||
|
contract={contract}
|
||||||
|
className={'hidden md:inline-flex'}
|
||||||
|
/>
|
||||||
<p
|
<p
|
||||||
className="break-words font-semibold text-indigo-700 group-hover:underline group-hover:decoration-indigo-400 group-hover:decoration-2"
|
className="break-words font-semibold text-indigo-700 group-hover:underline group-hover:decoration-indigo-400 group-hover:decoration-2"
|
||||||
style={{ /* For iOS safari */ wordBreak: 'break-word' }}
|
style={{ /* For iOS safari */ wordBreak: 'break-word' }}
|
||||||
|
@ -125,13 +128,19 @@ export function ContractCard(props: {
|
||||||
) : (
|
) : (
|
||||||
<FreeResponseTopAnswer contract={contract} truncate="long" />
|
<FreeResponseTopAnswer contract={contract} truncate="long" />
|
||||||
))}
|
))}
|
||||||
|
<Row className={'absolute bottom-3 gap-2 md:gap-0'}>
|
||||||
|
<AvatarDetails
|
||||||
|
contract={contract}
|
||||||
|
short={true}
|
||||||
|
className={'block md:hidden'}
|
||||||
|
/>
|
||||||
<MiscDetails
|
<MiscDetails
|
||||||
contract={contract}
|
contract={contract}
|
||||||
showHotVolume={showHotVolume}
|
showHotVolume={showHotVolume}
|
||||||
showTime={showTime}
|
showTime={showTime}
|
||||||
hideGroupLink={hideGroupLink}
|
hideGroupLink={hideGroupLink}
|
||||||
/>
|
/>
|
||||||
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
{showQuickBet ? (
|
{showQuickBet ? (
|
||||||
<QuickBet contract={contract} user={user} />
|
<QuickBet contract={contract} user={user} />
|
||||||
|
|
|
@ -34,6 +34,7 @@ import { ContractGroupsList } from 'web/components/groups/contract-groups-list'
|
||||||
import { SiteLink } from 'web/components/site-link'
|
import { SiteLink } from 'web/components/site-link'
|
||||||
import { groupPath } from 'web/lib/firebase/groups'
|
import { groupPath } from 'web/lib/firebase/groups'
|
||||||
import { insertContent } from '../editor/utils'
|
import { insertContent } from '../editor/utils'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
|
||||||
export type ShowTime = 'resolve-date' | 'close-date'
|
export type ShowTime = 'resolve-date' | 'close-date'
|
||||||
|
|
||||||
|
@ -83,9 +84,8 @@ export function MiscDetails(props: {
|
||||||
{!hideGroupLink && groupLinks && groupLinks.length > 0 && (
|
{!hideGroupLink && groupLinks && groupLinks.length > 0 && (
|
||||||
<SiteLink
|
<SiteLink
|
||||||
href={groupPath(groupLinks[0].slug)}
|
href={groupPath(groupLinks[0].slug)}
|
||||||
className="line-clamp-1 text-sm text-gray-400"
|
className="truncate text-sm text-gray-400"
|
||||||
>
|
>
|
||||||
<UserGroupIcon className="mx-1 mb-0.5 inline h-4 w-4 shrink-0" />
|
|
||||||
{groupLinks[0].name}
|
{groupLinks[0].name}
|
||||||
</SiteLink>
|
</SiteLink>
|
||||||
)}
|
)}
|
||||||
|
@ -93,18 +93,24 @@ export function MiscDetails(props: {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AvatarDetails(props: { contract: Contract }) {
|
export function AvatarDetails(props: {
|
||||||
const { contract } = props
|
contract: Contract
|
||||||
|
className?: string
|
||||||
|
short?: boolean
|
||||||
|
}) {
|
||||||
|
const { contract, short, className } = props
|
||||||
const { creatorName, creatorUsername } = contract
|
const { creatorName, creatorUsername } = contract
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row className="items-center gap-2 text-sm text-gray-400">
|
<Row
|
||||||
|
className={clsx('items-center gap-2 text-sm text-gray-400', className)}
|
||||||
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
username={creatorUsername}
|
username={creatorUsername}
|
||||||
avatarUrl={contract.creatorAvatarUrl}
|
avatarUrl={contract.creatorAvatarUrl}
|
||||||
size={6}
|
size={6}
|
||||||
/>
|
/>
|
||||||
<UserLink name={creatorName} username={creatorUsername} />
|
<UserLink name={creatorName} username={creatorUsername} short={short} />
|
||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,11 +106,8 @@ export function CreatorContractsList(props: {
|
||||||
return (
|
return (
|
||||||
<ContractSearch
|
<ContractSearch
|
||||||
user={user}
|
user={user}
|
||||||
querySortOptions={{
|
defaultSort="newest"
|
||||||
defaultSort: 'newest',
|
defaultFilter="all"
|
||||||
defaultFilter: 'all',
|
|
||||||
shouldLoadFromStorage: false,
|
|
||||||
}}
|
|
||||||
additionalFilter={{
|
additionalFilter={{
|
||||||
creatorId: creator.id,
|
creatorId: creator.id,
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -51,17 +51,10 @@ export function ShareModal(props: {
|
||||||
color="gradient"
|
color="gradient"
|
||||||
className={'mb-2 flex max-w-xs self-center'}
|
className={'mb-2 flex max-w-xs self-center'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (window.navigator.share) {
|
|
||||||
window.navigator.share({
|
|
||||||
url: shareUrl,
|
|
||||||
title: contract.question,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
copyToClipboard(shareUrl)
|
copyToClipboard(shareUrl)
|
||||||
toast.success('Link copied!', {
|
toast.success('Link copied!', {
|
||||||
icon: linkIcon,
|
icon: linkIcon,
|
||||||
})
|
})
|
||||||
}
|
|
||||||
track('copy share link')
|
track('copy share link')
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -73,7 +66,7 @@ export function ShareModal(props: {
|
||||||
className="self-start"
|
className="self-start"
|
||||||
tweetText={getTweetText(contract, shareUrl)}
|
tweetText={getTweetText(contract, shareUrl)}
|
||||||
/>
|
/>
|
||||||
<ShareEmbedButton contract={contract} toastClassName={'-left-20'} />
|
<ShareEmbedButton contract={contract} />
|
||||||
<DuplicateContractButton contract={contract} />
|
<DuplicateContractButton contract={contract} />
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
|
@ -69,7 +69,6 @@ export function MarketModal(props: {
|
||||||
'flex grid grid-cols-1 sm:grid-cols-2 flex-col gap-3 p-1'
|
'flex grid grid-cols-1 sm:grid-cols-2 flex-col gap-3 p-1'
|
||||||
}
|
}
|
||||||
cardHideOptions={{ hideGroupLink: true, hideQuickBet: true }}
|
cardHideOptions={{ hideGroupLink: true, hideQuickBet: true }}
|
||||||
querySortOptions={{ disableQueryString: true }}
|
|
||||||
highlightOptions={{
|
highlightOptions={{
|
||||||
contractIds: contracts.map((c) => c.id),
|
contractIds: contracts.map((c) => c.id),
|
||||||
highlightClassName:
|
highlightClassName:
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import React, { Fragment } from 'react'
|
import React from 'react'
|
||||||
import { CodeIcon } from '@heroicons/react/outline'
|
import { CodeIcon } from '@heroicons/react/outline'
|
||||||
import { Menu, Transition } from '@headlessui/react'
|
import { Menu } from '@headlessui/react'
|
||||||
|
import toast from 'react-hot-toast'
|
||||||
|
|
||||||
import { Contract } from 'common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { contractPath } from 'web/lib/firebase/contracts'
|
import { contractPath } from 'web/lib/firebase/contracts'
|
||||||
import { DOMAIN } from 'common/envs/constants'
|
import { DOMAIN } from 'common/envs/constants'
|
||||||
import { copyToClipboard } from 'web/lib/util/copy'
|
import { copyToClipboard } from 'web/lib/util/copy'
|
||||||
import { ToastClipboard } from 'web/components/toast-clipboard'
|
|
||||||
import { track } from 'web/lib/service/analytics'
|
import { track } from 'web/lib/service/analytics'
|
||||||
|
|
||||||
export function embedCode(contract: Contract) {
|
export function embedCode(contract: Contract) {
|
||||||
|
@ -16,11 +16,10 @@ export function embedCode(contract: Contract) {
|
||||||
return `<iframe width="560" height="405" src="${src}" title="${title}" frameborder="0"></iframe>`
|
return `<iframe width="560" height="405" src="${src}" title="${title}" frameborder="0"></iframe>`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ShareEmbedButton(props: {
|
export function ShareEmbedButton(props: { contract: Contract }) {
|
||||||
contract: Contract
|
const { contract } = props
|
||||||
toastClassName?: string
|
|
||||||
}) {
|
const codeIcon = <CodeIcon className="mr-1.5 h-4 w-4" aria-hidden="true" />
|
||||||
const { contract, toastClassName } = props
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
|
@ -28,6 +27,9 @@ export function ShareEmbedButton(props: {
|
||||||
className="relative z-10 flex-shrink-0"
|
className="relative z-10 flex-shrink-0"
|
||||||
onMouseUp={() => {
|
onMouseUp={() => {
|
||||||
copyToClipboard(embedCode(contract))
|
copyToClipboard(embedCode(contract))
|
||||||
|
toast.success('Embed code copied!', {
|
||||||
|
icon: codeIcon,
|
||||||
|
})
|
||||||
track('copy embed code')
|
track('copy embed code')
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -39,25 +41,9 @@ export function ShareEmbedButton(props: {
|
||||||
color: '#9ca3af', // text-gray-400
|
color: '#9ca3af', // text-gray-400
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CodeIcon className="mr-1.5 h-4 w-4" aria-hidden="true" />
|
{codeIcon}
|
||||||
Embed
|
Embed
|
||||||
</Menu.Button>
|
</Menu.Button>
|
||||||
|
|
||||||
<Transition
|
|
||||||
as={Fragment}
|
|
||||||
enter="transition ease-out duration-100"
|
|
||||||
enterFrom="transform opacity-0 scale-95"
|
|
||||||
enterTo="transform opacity-100 scale-100"
|
|
||||||
leave="transition ease-in duration-75"
|
|
||||||
leaveFrom="transform opacity-100 scale-100"
|
|
||||||
leaveTo="transform opacity-0 scale-95"
|
|
||||||
>
|
|
||||||
<Menu.Items>
|
|
||||||
<Menu.Item>
|
|
||||||
<ToastClipboard className={toastClassName} />
|
|
||||||
</Menu.Item>
|
|
||||||
</Menu.Items>
|
|
||||||
</Transition>
|
|
||||||
</Menu>
|
</Menu>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,16 +34,25 @@ export function UserLink(props: {
|
||||||
username: string
|
username: string
|
||||||
showUsername?: boolean
|
showUsername?: boolean
|
||||||
className?: string
|
className?: string
|
||||||
justFirstName?: boolean
|
short?: boolean
|
||||||
}) {
|
}) {
|
||||||
const { name, username, showUsername, className, justFirstName } = props
|
const { name, username, showUsername, className, short } = props
|
||||||
|
const firstName = name.split(' ')[0]
|
||||||
|
const maxLength = 10
|
||||||
|
const shortName =
|
||||||
|
firstName.length >= 3
|
||||||
|
? firstName.length < maxLength
|
||||||
|
? firstName
|
||||||
|
: firstName.substring(0, maxLength - 3) + '...'
|
||||||
|
: name.length > maxLength
|
||||||
|
? name.substring(0, maxLength) + '...'
|
||||||
|
: name
|
||||||
return (
|
return (
|
||||||
<SiteLink
|
<SiteLink
|
||||||
href={`/${username}`}
|
href={`/${username}`}
|
||||||
className={clsx('z-10 truncate', className)}
|
className={clsx('z-10 truncate', className)}
|
||||||
>
|
>
|
||||||
{justFirstName ? name.split(' ')[0] : name}
|
{short ? shortName : name}
|
||||||
{showUsername && ` (@${username})`}
|
{showUsername && ` (@${username})`}
|
||||||
</SiteLink>
|
</SiteLink>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
import { debounce } from 'lodash'
|
import { useState } from 'react'
|
||||||
import { useRouter } from 'next/router'
|
import { NextRouter, useRouter } from 'next/router'
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
|
||||||
import { DEFAULT_SORT } from 'web/components/contract-search'
|
|
||||||
|
|
||||||
const MARKETS_SORT = 'markets_sort'
|
|
||||||
|
|
||||||
export type Sort =
|
export type Sort =
|
||||||
| 'newest'
|
| 'newest'
|
||||||
|
@ -15,92 +11,55 @@ export type Sort =
|
||||||
| 'last-updated'
|
| 'last-updated'
|
||||||
| 'score'
|
| 'score'
|
||||||
|
|
||||||
export function getSavedSort() {
|
type UpdatedQueryParams = { [k: string]: string }
|
||||||
// TODO: this obviously doesn't work with SSR, common sense would suggest
|
type QuerySortOpts = { useUrl: boolean }
|
||||||
// that we should save things like this in cookies so the server has them
|
|
||||||
if (typeof window !== 'undefined') {
|
function withURLParams(location: Location, params: UpdatedQueryParams) {
|
||||||
return localStorage.getItem(MARKETS_SORT) as Sort | null
|
const newParams = new URLSearchParams(location.search)
|
||||||
|
for (const [k, v] of Object.entries(params)) {
|
||||||
|
if (!v) {
|
||||||
|
newParams.delete(k)
|
||||||
} else {
|
} else {
|
||||||
return null
|
newParams.set(k, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const newUrl = new URL(location.href)
|
||||||
|
newUrl.search = newParams.toString()
|
||||||
|
return newUrl
|
||||||
|
}
|
||||||
|
|
||||||
export interface QuerySortOptions {
|
function updateURL(params: UpdatedQueryParams) {
|
||||||
defaultSort?: Sort
|
// see relevant discussion here https://github.com/vercel/next.js/discussions/18072
|
||||||
shouldLoadFromStorage?: boolean
|
const url = withURLParams(window.location, params).toString()
|
||||||
/** Use normal react state instead of url query string */
|
const updatedState = { ...window.history.state, as: url, url }
|
||||||
disableQueryString?: boolean
|
window.history.replaceState(updatedState, '', url)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useQueryAndSortParams({
|
function getStringURLParam(router: NextRouter, k: string) {
|
||||||
defaultSort = DEFAULT_SORT,
|
const v = router.query[k]
|
||||||
shouldLoadFromStorage = true,
|
return typeof v === 'string' ? v : null
|
||||||
disableQueryString,
|
}
|
||||||
}: QuerySortOptions = {}) {
|
|
||||||
|
export function useQuery(defaultQuery: string, opts?: QuerySortOpts) {
|
||||||
|
const useUrl = opts?.useUrl ?? false
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const initialQuery = useUrl ? getStringURLParam(router, 'q') : null
|
||||||
const { s: sort, q: query } = router.query as {
|
const [query, setQuery] = useState(initialQuery ?? defaultQuery)
|
||||||
q?: string
|
if (!useUrl) {
|
||||||
s?: Sort
|
return [query, setQuery] as const
|
||||||
}
|
} else {
|
||||||
|
return [query, (q: string) => (setQuery(q), updateURL({ q }))] as const
|
||||||
const setSort = (sort: Sort | undefined) => {
|
|
||||||
router.replace({ query: { ...router.query, s: sort } }, undefined, {
|
|
||||||
shallow: true,
|
|
||||||
})
|
|
||||||
if (shouldLoadFromStorage) {
|
|
||||||
localStorage.setItem(MARKETS_SORT, sort || '')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const [queryState, setQueryState] = useState(query)
|
export function useSort(defaultSort: Sort, opts?: QuerySortOpts) {
|
||||||
|
const useUrl = opts?.useUrl ?? false
|
||||||
useEffect(() => {
|
const router = useRouter()
|
||||||
setQueryState(query)
|
const initialSort = useUrl ? (getStringURLParam(router, 's') as Sort) : null
|
||||||
}, [query])
|
const [sort, setSort] = useState(initialSort ?? defaultSort)
|
||||||
|
if (!useUrl) {
|
||||||
// Debounce router query update.
|
return [sort, setSort] as const
|
||||||
const pushQuery = useMemo(
|
} else {
|
||||||
() =>
|
return [sort, (s: Sort) => (setSort(s), updateURL({ s }))] as const
|
||||||
debounce((query: string | undefined) => {
|
|
||||||
const queryObj = { ...router.query, q: query }
|
|
||||||
if (!query) delete queryObj.q
|
|
||||||
router.replace({ query: queryObj }, undefined, {
|
|
||||||
shallow: true,
|
|
||||||
})
|
|
||||||
}, 100),
|
|
||||||
[router]
|
|
||||||
)
|
|
||||||
|
|
||||||
const setQuery = (query: string | undefined) => {
|
|
||||||
setQueryState(query)
|
|
||||||
if (!disableQueryString) {
|
|
||||||
pushQuery(query)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// If there's no sort option, then set the one from localstorage
|
|
||||||
if (router.isReady && !sort && shouldLoadFromStorage) {
|
|
||||||
const localSort = localStorage.getItem(MARKETS_SORT) as Sort
|
|
||||||
if (localSort && localSort !== defaultSort) {
|
|
||||||
// Use replace to not break navigating back.
|
|
||||||
router.replace(
|
|
||||||
{ query: { ...router.query, s: localSort } },
|
|
||||||
undefined,
|
|
||||||
{ shallow: true }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// use normal state if querydisableQueryString
|
|
||||||
const [sortState, setSortState] = useState(defaultSort)
|
|
||||||
|
|
||||||
return {
|
|
||||||
sort: disableQueryString ? sortState : sort ?? defaultSort,
|
|
||||||
query: queryState ?? '',
|
|
||||||
setSort: disableQueryString ? setSortState : setSort,
|
|
||||||
setQuery,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,11 @@ import { searchInAny } from 'common/util/parse'
|
||||||
import { sortBy } from 'lodash'
|
import { sortBy } from 'lodash'
|
||||||
import { ContractsGrid } from 'web/components/contract/contracts-grid'
|
import { ContractsGrid } from 'web/components/contract/contracts-grid'
|
||||||
import { useContracts } from 'web/hooks/use-contracts'
|
import { useContracts } from 'web/hooks/use-contracts'
|
||||||
import {
|
import { Sort, useQuery, useSort } from 'web/hooks/use-sort-and-query-params'
|
||||||
QuerySortOptions,
|
|
||||||
Sort,
|
|
||||||
useQueryAndSortParams,
|
|
||||||
} from 'web/hooks/use-sort-and-query-params'
|
|
||||||
|
|
||||||
const MAX_CONTRACTS_RENDERED = 100
|
const MAX_CONTRACTS_RENDERED = 100
|
||||||
|
|
||||||
export default function ContractSearchFirestore(props: {
|
export default function ContractSearchFirestore(props: {
|
||||||
querySortOptions?: QuerySortOptions
|
|
||||||
additionalFilter?: {
|
additionalFilter?: {
|
||||||
creatorId?: string
|
creatorId?: string
|
||||||
tag?: string
|
tag?: string
|
||||||
|
@ -21,10 +16,9 @@ export default function ContractSearchFirestore(props: {
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
const contracts = useContracts()
|
const contracts = useContracts()
|
||||||
const { querySortOptions, additionalFilter } = props
|
const { additionalFilter } = props
|
||||||
|
const [query, setQuery] = useQuery('', { useUrl: true })
|
||||||
const { query, setQuery, sort, setSort } =
|
const [sort, setSort] = useSort('score', { useUrl: true })
|
||||||
useQueryAndSortParams(querySortOptions)
|
|
||||||
|
|
||||||
let matches = (contracts ?? []).filter((c) =>
|
let matches = (contracts ?? []).filter((c) =>
|
||||||
searchInAny(
|
searchInAny(
|
||||||
|
|
|
@ -31,7 +31,6 @@ import { CreateQuestionButton } from 'web/components/create-question-button'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { LoadingIndicator } from 'web/components/loading-indicator'
|
import { LoadingIndicator } from 'web/components/loading-indicator'
|
||||||
import { Modal } from 'web/components/layout/modal'
|
import { Modal } from 'web/components/layout/modal'
|
||||||
import { getSavedSort } from 'web/hooks/use-sort-and-query-params'
|
|
||||||
import { ChoicesToggleGroup } from 'web/components/choices-toggle-group'
|
import { ChoicesToggleGroup } from 'web/components/choices-toggle-group'
|
||||||
import { toast } from 'react-hot-toast'
|
import { toast } from 'react-hot-toast'
|
||||||
import { useCommentsOnGroup } from 'web/hooks/use-comments'
|
import { useCommentsOnGroup } from 'web/hooks/use-comments'
|
||||||
|
@ -196,11 +195,8 @@ export default function GroupPage(props: {
|
||||||
const questionsTab = (
|
const questionsTab = (
|
||||||
<ContractSearch
|
<ContractSearch
|
||||||
user={user}
|
user={user}
|
||||||
querySortOptions={{
|
defaultSort={'newest'}
|
||||||
shouldLoadFromStorage: true,
|
defaultFilter={'open'}
|
||||||
defaultSort: getSavedSort() ?? 'newest',
|
|
||||||
defaultFilter: 'open',
|
|
||||||
}}
|
|
||||||
additionalFilter={{ groupSlug: group.slug }}
|
additionalFilter={{ groupSlug: group.slug }}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,8 +4,7 @@ import { PlusSmIcon } from '@heroicons/react/solid'
|
||||||
|
|
||||||
import { Page } from 'web/components/page'
|
import { Page } from 'web/components/page'
|
||||||
import { Col } from 'web/components/layout/col'
|
import { Col } from 'web/components/layout/col'
|
||||||
import { getSavedSort } from 'web/hooks/use-sort-and-query-params'
|
import { ContractSearch } from 'web/components/contract-search'
|
||||||
import { ContractSearch, DEFAULT_SORT } from 'web/components/contract-search'
|
|
||||||
import { Contract } from 'common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { User } from 'common/user'
|
import { User } from 'common/user'
|
||||||
import { ContractPageContent } from './[username]/[contractSlug]'
|
import { ContractPageContent } from './[username]/[contractSlug]'
|
||||||
|
@ -35,10 +34,8 @@ const Home = (props: { auth: { user: User } }) => {
|
||||||
<Col className="mx-auto w-full p-2">
|
<Col className="mx-auto w-full p-2">
|
||||||
<ContractSearch
|
<ContractSearch
|
||||||
user={user}
|
user={user}
|
||||||
querySortOptions={{
|
useQuerySortLocalStorage={true}
|
||||||
shouldLoadFromStorage: true,
|
useQuerySortUrlParams={true}
|
||||||
defaultSort: getSavedSort() ?? DEFAULT_SORT,
|
|
||||||
}}
|
|
||||||
onContractClick={(c) => {
|
onContractClick={(c) => {
|
||||||
// Show contract without navigating to contract page.
|
// Show contract without navigating to contract page.
|
||||||
setContract(c)
|
setContract(c)
|
||||||
|
@ -104,13 +101,19 @@ const useContractPage = () => {
|
||||||
|
|
||||||
window.history.pushState = function () {
|
window.history.pushState = function () {
|
||||||
// eslint-disable-next-line prefer-rest-params
|
// eslint-disable-next-line prefer-rest-params
|
||||||
pushState.apply(history, arguments as any)
|
const args = [...(arguments as any)] as any
|
||||||
|
// Discard NextJS router state.
|
||||||
|
args[0] = null
|
||||||
|
pushState.apply(history, args)
|
||||||
updateContract()
|
updateContract()
|
||||||
}
|
}
|
||||||
|
|
||||||
window.history.replaceState = function () {
|
window.history.replaceState = function () {
|
||||||
// eslint-disable-next-line prefer-rest-params
|
// eslint-disable-next-line prefer-rest-params
|
||||||
replaceState.apply(history, arguments as any)
|
const args = [...(arguments as any)] as any
|
||||||
|
// Discard NextJS router state.
|
||||||
|
args[0] = null
|
||||||
|
replaceState.apply(history, args)
|
||||||
updateContract()
|
updateContract()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ import { safeLocalStorage } from 'web/lib/util/local'
|
||||||
import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth'
|
import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth'
|
||||||
import { SiteLink } from 'web/components/site-link'
|
import { SiteLink } from 'web/components/site-link'
|
||||||
import { NotificationSettings } from 'web/components/NotificationSettings'
|
import { NotificationSettings } from 'web/components/NotificationSettings'
|
||||||
|
import { SEO } from 'web/components/SEO'
|
||||||
|
|
||||||
export const NOTIFICATIONS_PER_PAGE = 30
|
export const NOTIFICATIONS_PER_PAGE = 30
|
||||||
const MULTIPLE_USERS_KEY = 'multipleUsers'
|
const MULTIPLE_USERS_KEY = 'multipleUsers'
|
||||||
|
@ -68,6 +69,8 @@ export default function Notifications(props: {
|
||||||
<Page>
|
<Page>
|
||||||
<div className={'px-2 pt-4 sm:px-4 lg:pt-0'}>
|
<div className={'px-2 pt-4 sm:px-4 lg:pt-0'}>
|
||||||
<Title text={'Notifications'} className={'hidden md:block'} />
|
<Title text={'Notifications'} className={'hidden md:block'} />
|
||||||
|
<SEO title="Notifications" description="Manifold user notifications" />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Tabs
|
<Tabs
|
||||||
currentPageForAnalytics={'notifications'}
|
currentPageForAnalytics={'notifications'}
|
||||||
|
@ -437,7 +440,7 @@ function IncomeNotificationItem(props: {
|
||||||
name={sourceUserName || ''}
|
name={sourceUserName || ''}
|
||||||
username={sourceUserUsername || ''}
|
username={sourceUserUsername || ''}
|
||||||
className={'mr-1 flex-shrink-0'}
|
className={'mr-1 flex-shrink-0'}
|
||||||
justFirstName={true}
|
short={true}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{getReasonForShowingIncomeNotification(false)} {' on'}
|
{getReasonForShowingIncomeNotification(false)} {' on'}
|
||||||
|
@ -606,7 +609,7 @@ function NotificationItem(props: {
|
||||||
name={sourceUserName || ''}
|
name={sourceUserName || ''}
|
||||||
username={sourceUserUsername || ''}
|
username={sourceUserUsername || ''}
|
||||||
className={'mr-0 flex-shrink-0'}
|
className={'mr-0 flex-shrink-0'}
|
||||||
justFirstName={true}
|
short={true}
|
||||||
/>
|
/>
|
||||||
<div className={'inline-flex overflow-hidden text-ellipsis pl-1'}>
|
<div className={'inline-flex overflow-hidden text-ellipsis pl-1'}>
|
||||||
<span className={'flex-shrink-0'}>
|
<span className={'flex-shrink-0'}>
|
||||||
|
@ -678,7 +681,7 @@ function NotificationItem(props: {
|
||||||
name={sourceUserName || ''}
|
name={sourceUserName || ''}
|
||||||
username={sourceUserUsername || ''}
|
username={sourceUserUsername || ''}
|
||||||
className={'relative mr-1 flex-shrink-0'}
|
className={'relative mr-1 flex-shrink-0'}
|
||||||
justFirstName={true}
|
short={true}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{getReasonForShowingNotification(
|
{getReasonForShowingNotification(
|
||||||
|
|
|
@ -15,11 +15,8 @@ export default function TagPage() {
|
||||||
<Title text={`#${tag}`} />
|
<Title text={`#${tag}`} />
|
||||||
<ContractSearch
|
<ContractSearch
|
||||||
user={user}
|
user={user}
|
||||||
querySortOptions={{
|
defaultSort="newest"
|
||||||
defaultSort: 'newest',
|
defaultFilter="all"
|
||||||
defaultFilter: 'all',
|
|
||||||
shouldLoadFromStorage: true,
|
|
||||||
}}
|
|
||||||
additionalFilter={{ tag }}
|
additionalFilter={{ tag }}
|
||||||
/>
|
/>
|
||||||
</Page>
|
</Page>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user