Merge
This commit is contained in:
commit
d8eed874c1
|
@ -19,8 +19,7 @@ export const charities: Charity[] = [
|
||||||
website: 'https://www.1daysooner.org/',
|
website: 'https://www.1daysooner.org/',
|
||||||
preview:
|
preview:
|
||||||
'Accelerating the development of each vaccine by even a couple of days via COVID-19 human challenge trials could save thousands of lives.',
|
'Accelerating the development of each vaccine by even a couple of days via COVID-19 human challenge trials could save thousands of lives.',
|
||||||
photo:
|
photo: 'https://i.imgur.com/bUDdzUE.png',
|
||||||
'https://images.squarespace-cdn.com/content/v1/5f5f8496d1d7713486b6075a/666cbb5a-5335-4323-b9ea-b764edc826e1/OFFICIAL+1Day+Sooner+Logo.png',
|
|
||||||
description: `1Day Sooner is a non-profit that advocates on behalf of COVID-19 challenge trial volunteers.
|
description: `1Day Sooner is a non-profit that advocates on behalf of COVID-19 challenge trial volunteers.
|
||||||
|
|
||||||
After a vaccine candidate is created in a lab, it is developed through a combination of pre-clinical evaluation and three phases of clinical trials that test its safety and efficacy. In traditional Phase III trials, participants receive the vaccine candidate or a placebo/active comparator, and efficacy is judged by comparing the prevalence of infection in the vaccine group and the placebo/comparator group, to test the hypothesis that significantly fewer participants in the vaccine group get infected. In these traditional trials, after receiving the treatment, participants return to their homes and their normal daily lives so as to test the treatment under real world conditions. Since only a small proportion of these participants may encounter the disease, it may take a large number of participants and a good deal of time for these trials to reveal differences between the vaccine and placebo groups.
|
After a vaccine candidate is created in a lab, it is developed through a combination of pre-clinical evaluation and three phases of clinical trials that test its safety and efficacy. In traditional Phase III trials, participants receive the vaccine candidate or a placebo/active comparator, and efficacy is judged by comparing the prevalence of infection in the vaccine group and the placebo/comparator group, to test the hypothesis that significantly fewer participants in the vaccine group get infected. In these traditional trials, after receiving the treatment, participants return to their homes and their normal daily lives so as to test the treatment under real world conditions. Since only a small proportion of these participants may encounter the disease, it may take a large number of participants and a good deal of time for these trials to reveal differences between the vaccine and placebo groups.
|
||||||
|
@ -35,8 +34,7 @@ export const charities: Charity[] = [
|
||||||
website: 'https://quantifieduncertainty.org/',
|
website: 'https://quantifieduncertainty.org/',
|
||||||
preview:
|
preview:
|
||||||
'The Quantified Uncertainty Research Institute advances forecasting and epistemics to improve the long-term future of humanity.',
|
'The Quantified Uncertainty Research Institute advances forecasting and epistemics to improve the long-term future of humanity.',
|
||||||
photo:
|
photo: 'https://i.imgur.com/ZsSXPjH.png',
|
||||||
'https://quantifieduncertainty.org/_next/image?url=https%3A%2F%2Fsuper-static-assets.s3.amazonaws.com%2F09bb1362-5e3f-4724-8ffd-f3235f67356f%2Fimages%2F6151ac3e-aed7-44c7-9827-399fe6e9222b.png&w=1920&q=80',
|
|
||||||
description: `QURI researches systematic practices to specify and estimate the most important parameters for the most important or scalable decisions. Research areas include forecasting, epistemics, evaluations, ontology, and estimation.
|
description: `QURI researches systematic practices to specify and estimate the most important parameters for the most important or scalable decisions. Research areas include forecasting, epistemics, evaluations, ontology, and estimation.
|
||||||
|
|
||||||
We emphasize technological solutions that can heavily scale in the next 5 to 30 years.
|
We emphasize technological solutions that can heavily scale in the next 5 to 30 years.
|
||||||
|
@ -47,7 +45,7 @@ export const charities: Charity[] = [
|
||||||
{
|
{
|
||||||
name: 'Long-Term Future Fund',
|
name: 'Long-Term Future Fund',
|
||||||
website: 'https://funds.effectivealtruism.org/funds/far-future',
|
website: 'https://funds.effectivealtruism.org/funds/far-future',
|
||||||
photo: 'https://app.effectivealtruism.org/logo-funds.svg',
|
photo: 'https://i.imgur.com/C2qka9g.png',
|
||||||
preview:
|
preview:
|
||||||
'Positively influence the long-term trajectory of civilization by making grants that address global catastrophic risks.',
|
'Positively influence the long-term trajectory of civilization by making grants that address global catastrophic risks.',
|
||||||
description: `The Fund has a broad remit to make grants that promote, implement and advocate for longtermist ideas. Many of our grants aim to address potential risks from advanced artificial intelligence and to build infrastructure and advocate for longtermist projects. However, we welcome applications related to long-term institutional reform or other global catastrophic risks (e.g., pandemics or nuclear conflict).
|
description: `The Fund has a broad remit to make grants that promote, implement and advocate for longtermist ideas. Many of our grants aim to address potential risks from advanced artificial intelligence and to build infrastructure and advocate for longtermist projects. However, we welcome applications related to long-term institutional reform or other global catastrophic risks (e.g., pandemics or nuclear conflict).
|
||||||
|
@ -58,10 +56,39 @@ export const charities: Charity[] = [
|
||||||
- Promoting long-term thinking`,
|
- Promoting long-term thinking`,
|
||||||
tags: ['Featured'] as CharityTag[],
|
tags: ['Featured'] as CharityTag[],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Nonlinear',
|
||||||
|
website: 'https://www.nonlinear.org/',
|
||||||
|
photo: 'https://i.imgur.com/Muifc1l.png',
|
||||||
|
preview:
|
||||||
|
'Incubate longtermist nonprofits by connecting founders with ideas, funding, and mentorship.',
|
||||||
|
description: `Problem: There are tens of thousands of people working full time to make AI powerful, but around one hundred working to make AI safe. This needs to change.
|
||||||
|
|
||||||
|
Longtermism is held back by two bottlenecks:
|
||||||
|
1. Lots of funding, but few charities to deploy it.
|
||||||
|
2. Lots of talent, but few charities creating jobs.
|
||||||
|
|
||||||
|
Solution: Longtermism needs more charities to deploy funding and create jobs. Our goal is to 10x the number of talented people working on longtermism by launching dozens of high impact charities.
|
||||||
|
|
||||||
|
This helps solve the bottlenecks because entrepreneurs “unlock” latent EA talent - if one person starts an organization that employs 100 people who weren’t previously working on AI safety, that doubles the number of people working on the problem.
|
||||||
|
|
||||||
|
Our process:
|
||||||
|
1. Research the highest leverage ideas
|
||||||
|
2. Find the right founders
|
||||||
|
3. Connect them with mentors and funding
|
||||||
|
|
||||||
|
We will be announcing more details about our incubation program soon.
|
||||||
|
|
||||||
|
A few of the ideas we’ve incubated so far:
|
||||||
|
- The Nonlinear Library: Listen to top EA content on your podcast player. We use text-to-speech software to create an automatically updating repository of audio content from the EA Forum, Alignment Forum, and LessWrong. You can find it on all major podcast players here.
|
||||||
|
- EA Hiring Agency: Helping EA orgs scalably hire talent.
|
||||||
|
- EA Houses: EA's Airbnb - Connecting EAs who have extra space with EAs who need space here.`,
|
||||||
|
tags: ['Featured'] as CharityTag[],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'GiveWell Maximum Impact Fund',
|
name: 'GiveWell Maximum Impact Fund',
|
||||||
website: 'https://www.givewell.org/maximum-impact-fund',
|
website: 'https://www.givewell.org/maximum-impact-fund',
|
||||||
photo: 'https://www.givewell.org/sites/all/themes/gw_basic/logo.png',
|
photo: 'https://i.imgur.com/xikuDMZ.png',
|
||||||
preview:
|
preview:
|
||||||
'We search for the charities that save or improve lives the most per dollar.',
|
'We search for the charities that save or improve lives the most per dollar.',
|
||||||
description: `
|
description: `
|
||||||
|
@ -98,8 +125,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
name: 'Give Directly',
|
name: 'Give Directly',
|
||||||
website: 'https://www.givedirectly.org/',
|
website: 'https://www.givedirectly.org/',
|
||||||
ein: '27-1661997',
|
ein: '27-1661997',
|
||||||
photo:
|
photo: 'https://i.imgur.com/lrdxSyd.jpg',
|
||||||
'https://www.givewell.org/sites/default/files/charity_logos/GiveDirectly.jpg',
|
|
||||||
preview: 'Send money directly to people living in poverty.',
|
preview: 'Send money directly to people living in poverty.',
|
||||||
description:
|
description:
|
||||||
'GiveDirectly is a nonprofit that lets donors like you send money directly to the world’s poorest households. We believe people living in poverty deserve the dignity to choose for themselves how best to improve their lives — cash enables that choice. Since 2009, we’ve delivered $500M+ in cash directly into the hands of over 1 million families living in poverty. We currently have operations in Kenya, Rwanda, Liberia, Malawi, Morocco, Mozambique, DRC, Uganda, and the United States.',
|
'GiveDirectly is a nonprofit that lets donors like you send money directly to the world’s poorest households. We believe people living in poverty deserve the dignity to choose for themselves how best to improve their lives — cash enables that choice. Since 2009, we’ve delivered $500M+ in cash directly into the hands of over 1 million families living in poverty. We currently have operations in Kenya, Rwanda, Liberia, Malawi, Morocco, Mozambique, DRC, Uganda, and the United States.',
|
||||||
|
@ -108,8 +134,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
name: 'Hellen Keller International',
|
name: 'Hellen Keller International',
|
||||||
website: 'https://www.hki.org/',
|
website: 'https://www.hki.org/',
|
||||||
ein: '13-5562162',
|
ein: '13-5562162',
|
||||||
photo:
|
photo: 'https://i.imgur.com/Dl97Abk.jpg',
|
||||||
'https://www.ntd-ngonetwork.org/sites/nnn/files/content/organisation/logos/2020-01-28/v2_HKLogo_Primary_RGB.jpg',
|
|
||||||
preview:
|
preview:
|
||||||
'We envision a world where no one is deprived of the opportunity to live a healthy life – and reach their true potential.',
|
'We envision a world where no one is deprived of the opportunity to live a healthy life – and reach their true potential.',
|
||||||
description:
|
description:
|
||||||
|
@ -119,8 +144,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
name: 'Against Malaria Foundation',
|
name: 'Against Malaria Foundation',
|
||||||
website: 'https://www.againstmalaria.com/',
|
website: 'https://www.againstmalaria.com/',
|
||||||
ein: '20-3069841',
|
ein: '20-3069841',
|
||||||
photo:
|
photo: 'https://i.imgur.com/F3JoZi9.png',
|
||||||
'https://media-exp1.licdn.com/dms/image/C4D0BAQFvdcum9KBNfg/company-logo_200_200/0?e=2159024400&v=beta&t=hxjJCKQkMp2irTOcuJEceW7x4l3c4PD7gYCQ6ulgYlg',
|
|
||||||
preview: 'We help protect people from malaria.',
|
preview: 'We help protect people from malaria.',
|
||||||
description:
|
description:
|
||||||
'AMF (againstmalaria.com) provides funding for long-lasting insecticide-treated net (LLIN) distributions (for protection against malaria) in developing countries. There is strong evidence that distributing LLINs reduces child mortality and malaria cases. AMF conducts post-distribution surveys of completed distributions to determine whether LLINs have reached their intended destinations and how long they remain in good condition.',
|
'AMF (againstmalaria.com) provides funding for long-lasting insecticide-treated net (LLIN) distributions (for protection against malaria) in developing countries. There is strong evidence that distributing LLINs reduces child mortality and malaria cases. AMF conducts post-distribution surveys of completed distributions to determine whether LLINs have reached their intended destinations and how long they remain in good condition.',
|
||||||
|
@ -128,8 +152,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
{
|
{
|
||||||
name: 'Rethink Charity',
|
name: 'Rethink Charity',
|
||||||
website: 'https://rethink.charity/',
|
website: 'https://rethink.charity/',
|
||||||
photo:
|
photo: 'https://i.imgur.com/Go7N7As.png',
|
||||||
'https://process.filestackapi.com/resize=width:600,height:315,fit:max/quality=value:90/jvYvq1JFQkOqo3J8hVcJ',
|
|
||||||
preview:
|
preview:
|
||||||
'Providing vital support to high-impact charities and charitable projects.',
|
'Providing vital support to high-impact charities and charitable projects.',
|
||||||
description: `At Rethink Charity, we’re excited about improving the world by providing vital support to high-impact charities and charitable projects. We equip them with tools to boost their impact, through our projects that empower their donors with tax-efficient giving options and strategically coordinated matching opportunities.
|
description: `At Rethink Charity, we’re excited about improving the world by providing vital support to high-impact charities and charitable projects. We equip them with tools to boost their impact, through our projects that empower their donors with tax-efficient giving options and strategically coordinated matching opportunities.
|
||||||
|
@ -143,8 +166,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
name: 'Malaria Consortium',
|
name: 'Malaria Consortium',
|
||||||
website: 'https://www.malariaconsortium.org/',
|
website: 'https://www.malariaconsortium.org/',
|
||||||
ein: '98-0627052',
|
ein: '98-0627052',
|
||||||
photo:
|
photo: 'https://i.imgur.com/LGwy9d8.png ',
|
||||||
'https://www.malariaconsortium.org/website-2013/images_template/malaria_consortium_logo.png',
|
|
||||||
preview:
|
preview:
|
||||||
'We specialise in the prevention, control and treatment of malaria and other communicable diseases.',
|
'We specialise in the prevention, control and treatment of malaria and other communicable diseases.',
|
||||||
description:
|
description:
|
||||||
|
@ -153,7 +175,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
{
|
{
|
||||||
name: 'The Center for the Study of Partisanship and Ideology',
|
name: 'The Center for the Study of Partisanship and Ideology',
|
||||||
website: 'https://cspicenter.org/',
|
website: 'https://cspicenter.org/',
|
||||||
photo: 'https://cspicenter.org/wp-content/uploads/2020/02/CSPI.png',
|
photo: 'https://i.imgur.com/O88tkOW.png',
|
||||||
preview:
|
preview:
|
||||||
'Support and fund research on how ideology and government policy contribute to scientific, technological, and social progress.',
|
'Support and fund research on how ideology and government policy contribute to scientific, technological, and social progress.',
|
||||||
description: `Over the last few decades, scientific and technological progress have stagnated. Scientists conduct more research than ever before, but groundbreaking innovation is scarce. At the same time, identity politics and political polarization have reached new extremes, and social trends such as family stability and crime are worse than in previous decades and in some cases moving in the wrong direction. What explains these trends, and how can we reverse them?
|
description: `Over the last few decades, scientific and technological progress have stagnated. Scientists conduct more research than ever before, but groundbreaking innovation is scarce. At the same time, identity politics and political polarization have reached new extremes, and social trends such as family stability and crime are worse than in previous decades and in some cases moving in the wrong direction. What explains these trends, and how can we reverse them?
|
||||||
|
@ -170,8 +192,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
name: 'Faunalytics',
|
name: 'Faunalytics',
|
||||||
website: 'https://faunalytics.org/',
|
website: 'https://faunalytics.org/',
|
||||||
ein: '01-0686889',
|
ein: '01-0686889',
|
||||||
photo:
|
photo: 'https://i.imgur.com/3JXhuXl.jpg',
|
||||||
'https://animalcharityevaluators.org/wp-content/uploads/2016/08/logo-faunalytics2400x2400-200x200@2x.jpg',
|
|
||||||
preview:
|
preview:
|
||||||
'Faunalytics conducts research and shares knowledge to help advocates help animals effectively.',
|
'Faunalytics conducts research and shares knowledge to help advocates help animals effectively.',
|
||||||
description:
|
description:
|
||||||
|
@ -181,8 +202,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
name: 'The Humane League',
|
name: 'The Humane League',
|
||||||
website: 'https://thehumaneleague.org/',
|
website: 'https://thehumaneleague.org/',
|
||||||
ein: '04-3817491',
|
ein: '04-3817491',
|
||||||
photo:
|
photo: 'https://i.imgur.com/za9Rwon.jpg',
|
||||||
'https://animalcharityevaluators.org/wp-content/uploads/2019/03/thl-mended-heart-logo@2x-200x200@2x.jpg',
|
|
||||||
preview:
|
preview:
|
||||||
'We exist to end the abuse of animals raised for food by influencing the policies of the world’s biggest companies, demanding legislation, and empowering others to take action and leave animals off their plates',
|
'We exist to end the abuse of animals raised for food by influencing the policies of the world’s biggest companies, demanding legislation, and empowering others to take action and leave animals off their plates',
|
||||||
description:
|
description:
|
||||||
|
@ -192,8 +212,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
name: 'Wild Animal Initiative',
|
name: 'Wild Animal Initiative',
|
||||||
website: 'https://www.wildanimalinitiative.org/',
|
website: 'https://www.wildanimalinitiative.org/',
|
||||||
ein: '82-2281466',
|
ein: '82-2281466',
|
||||||
photo:
|
photo: 'https://i.imgur.com/bOVUnDm.png',
|
||||||
'https://animalcharityevaluators.org/wp-content/uploads/2020/11/WAI-logo_square-gray-on-teal-1-630x630.png',
|
|
||||||
preview: 'We want to make life better for wild animals.',
|
preview: 'We want to make life better for wild animals.',
|
||||||
description:
|
description:
|
||||||
'Wild Animal Initiative (WAI) currently operates in the U.S., where they work to strengthen the animal advocacy movement through creating an academic field dedicated to wild animal welfare. They compile literature reviews, write theoretical and opinion articles, and publish research results on their website and/or in peer-reviewed journals. WAI focuses on identifying and sharing possible research avenues and connecting with more established fields. They also work with researchers from various academic and non-academic institutions to identify potential collaborators, and they recently launched a grant assistance program.',
|
'Wild Animal Initiative (WAI) currently operates in the U.S., where they work to strengthen the animal advocacy movement through creating an academic field dedicated to wild animal welfare. They compile literature reviews, write theoretical and opinion articles, and publish research results on their website and/or in peer-reviewed journals. WAI focuses on identifying and sharing possible research avenues and connecting with more established fields. They also work with researchers from various academic and non-academic institutions to identify potential collaborators, and they recently launched a grant assistance program.',
|
||||||
|
@ -202,8 +221,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
name: 'New Incentives',
|
name: 'New Incentives',
|
||||||
website: 'https://www.newincentives.org/',
|
website: 'https://www.newincentives.org/',
|
||||||
ein: '45-2368993',
|
ein: '45-2368993',
|
||||||
photo:
|
photo: 'https://i.imgur.com/bYl4tk3.png',
|
||||||
'https://uploads-ssl.webflow.com/5f7c51bf9fac9b5ed62aa37b/5f7c51bf9fac9b85c42aa3df_Group%20344%20(1).svg',
|
|
||||||
preview: 'Cash incentives to boost vaccination rates and save lives.',
|
preview: 'Cash incentives to boost vaccination rates and save lives.',
|
||||||
description:
|
description:
|
||||||
'New Incentives (newincentives.org) runs a conditional cash transfer (CCT) program in North West Nigeria which seeks to increase uptake of routine immunizations through cash transfers, raising public awareness of the benefits of vaccination and reducing the frequency of vaccine stockouts.',
|
'New Incentives (newincentives.org) runs a conditional cash transfer (CCT) program in North West Nigeria which seeks to increase uptake of routine immunizations through cash transfers, raising public awareness of the benefits of vaccination and reducing the frequency of vaccine stockouts.',
|
||||||
|
@ -212,8 +230,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
name: 'SCI foundation',
|
name: 'SCI foundation',
|
||||||
website: 'https://schistosomiasiscontrolinitiative.org/',
|
website: 'https://schistosomiasiscontrolinitiative.org/',
|
||||||
ein: '',
|
ein: '',
|
||||||
photo:
|
photo: 'https://i.imgur.com/sWD8zM5.png',
|
||||||
'https://images.easyfundraising.org.uk/cause/cropped/cause-logo-e99e0632a8a9572150fdcf3abf08ad45.png',
|
|
||||||
preview:
|
preview:
|
||||||
'SCI works with governments in sub-Saharan Africa to create or scale up programs that treat schistosomiasis and soil-transmitted helminthiasis ("deworming").',
|
'SCI works with governments in sub-Saharan Africa to create or scale up programs that treat schistosomiasis and soil-transmitted helminthiasis ("deworming").',
|
||||||
description:
|
description:
|
||||||
|
@ -223,8 +240,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
name: 'Wikimedia Foundation',
|
name: 'Wikimedia Foundation',
|
||||||
website: 'https://wikimediafoundation.org/',
|
website: 'https://wikimediafoundation.org/',
|
||||||
ein: '20-0049703',
|
ein: '20-0049703',
|
||||||
photo:
|
photo: 'https://i.imgur.com/klEzUbR.png',
|
||||||
'https://2.bp.blogspot.com/-jVseU39DW0s/VjmXVMOEEEI/AAAAAAAACK8/dwUP6sLqy-Q/s1600/wikimedia.png',
|
|
||||||
preview: 'We help everyone share in the sum of all knowledge.',
|
preview: 'We help everyone share in the sum of all knowledge.',
|
||||||
description:
|
description:
|
||||||
'We are the people who keep knowledge free. There is an amazing community of people around the world that makes great projects like Wikipedia. We help them do that work. We take care of the technical infrastructure, the legal challenges, and the growing pains.',
|
'We are the people who keep knowledge free. There is an amazing community of people around the world that makes great projects like Wikipedia. We help them do that work. We take care of the technical infrastructure, the legal challenges, and the growing pains.',
|
||||||
|
@ -233,8 +249,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
name: 'Rainforest Trust',
|
name: 'Rainforest Trust',
|
||||||
website: 'https://www.rainforesttrust.org/',
|
website: 'https://www.rainforesttrust.org/',
|
||||||
ein: '13-3500609',
|
ein: '13-3500609',
|
||||||
photo:
|
photo: 'https://i.imgur.com/6MzS530.png',
|
||||||
'https://ww1.prweb.com/prfiles/2019/05/29/16344590/Rrainforest%20Trust%20new%20logo%20tall-1%20copy.png',
|
|
||||||
preview:
|
preview:
|
||||||
'Rainforest Trust saves endangered wildlife and protects our planet by creating rainforest reserves through partnerships, community engagement and donor support.',
|
'Rainforest Trust saves endangered wildlife and protects our planet by creating rainforest reserves through partnerships, community engagement and donor support.',
|
||||||
description:
|
description:
|
||||||
|
@ -244,8 +259,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
name: 'The Nature Conservancy',
|
name: 'The Nature Conservancy',
|
||||||
website: 'https://www.nature.org/en-us/',
|
website: 'https://www.nature.org/en-us/',
|
||||||
ein: '53-0242652',
|
ein: '53-0242652',
|
||||||
photo:
|
photo: 'https://i.imgur.com/vjxkoGo.jpg',
|
||||||
'https://mma.prnewswire.com/media/1140905/The_Nature_Conservancy_Logo.jpg?p=facebook',
|
|
||||||
preview: 'A Future Where People and Nature Thrive',
|
preview: 'A Future Where People and Nature Thrive',
|
||||||
description:
|
description:
|
||||||
'The Nature Conservancy is a global environmental nonprofit working to create a world where people and nature can thrive. Founded in the U.S. through grassroots action in 1951, The Nature Conservancy has grown to become one of the most effective and wide-reaching environmental organizations in the world. Thanks to more than a million members and the dedicated efforts of our diverse staff and over 400 scientists, we impact conservation in 76 countries and territories: 37 by direct conservation impact and 39 through partners.',
|
'The Nature Conservancy is a global environmental nonprofit working to create a world where people and nature can thrive. Founded in the U.S. through grassroots action in 1951, The Nature Conservancy has grown to become one of the most effective and wide-reaching environmental organizations in the world. Thanks to more than a million members and the dedicated efforts of our diverse staff and over 400 scientists, we impact conservation in 76 countries and territories: 37 by direct conservation impact and 39 through partners.',
|
||||||
|
@ -254,7 +268,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
name: 'Doctors Without Borders',
|
name: 'Doctors Without Borders',
|
||||||
website: 'https://www.doctorswithoutborders.org/',
|
website: 'https://www.doctorswithoutborders.org/',
|
||||||
ein: '13-3433452',
|
ein: '13-3433452',
|
||||||
photo: 'https://www.doctorswithoutborders.org/themes/custom/msf/logo.svg',
|
photo: 'https://i.imgur.com/xqhH9FE.png',
|
||||||
preview:
|
preview:
|
||||||
'We provide independent, impartial medical humanitarian assistance to the people who need it most.',
|
'We provide independent, impartial medical humanitarian assistance to the people who need it most.',
|
||||||
description:
|
description:
|
||||||
|
@ -264,8 +278,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
name: 'World Wildlife Fund',
|
name: 'World Wildlife Fund',
|
||||||
website: 'https://www.worldwildlife.org/',
|
website: 'https://www.worldwildlife.org/',
|
||||||
ein: '52-1693387',
|
ein: '52-1693387',
|
||||||
photo:
|
photo: 'https://i.imgur.com/hDADuqW.png',
|
||||||
'https://www.worldwildlife.org/assets/structure/unique/logo-c562409bb6158bf64e5f8b1be066dbd5983d75f5ce7c9935a5afffbcc03f8e5d.png',
|
|
||||||
preview:
|
preview:
|
||||||
'WWF works to sustain the natural world for the benefit of people and wildlife, collaborating with partners from local to global levels in nearly 100 countries.',
|
'WWF works to sustain the natural world for the benefit of people and wildlife, collaborating with partners from local to global levels in nearly 100 countries.',
|
||||||
description:
|
description:
|
||||||
|
@ -274,7 +287,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
{
|
{
|
||||||
name: 'UNICEF USA',
|
name: 'UNICEF USA',
|
||||||
website: 'https://www.unicefusa.org/',
|
website: 'https://www.unicefusa.org/',
|
||||||
photo: 'https://www.unicefusa.org/sites/default/files/UNICEFUSA_DIG_C.svg',
|
photo: 'https://i.imgur.com/9cxuvZi.png',
|
||||||
ein: '13-1760110',
|
ein: '13-1760110',
|
||||||
preview:
|
preview:
|
||||||
"UNICEF USA helps save and protect the world's most vulnerable children.",
|
"UNICEF USA helps save and protect the world's most vulnerable children.",
|
||||||
|
@ -285,8 +298,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
name: 'Vitamin Angels',
|
name: 'Vitamin Angels',
|
||||||
website: 'https://www.vitaminangels.org/',
|
website: 'https://www.vitaminangels.org/',
|
||||||
ein: '77-0485881',
|
ein: '77-0485881',
|
||||||
photo:
|
photo: 'https://i.imgur.com/Mf35IOu.jpg',
|
||||||
'https://www.newhope.com/sites/newhope360.com/files/styles/article_featured_retina/public/vitamin-angels-logo.jpg?itok=pfNCPLE0',
|
|
||||||
preview:
|
preview:
|
||||||
'By improving access to vital nutrition, everyone gets an equal chance to grow, thrive, and prosper.',
|
'By improving access to vital nutrition, everyone gets an equal chance to grow, thrive, and prosper.',
|
||||||
description:
|
description:
|
||||||
|
@ -296,7 +308,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
name: 'Free Software Foundation',
|
name: 'Free Software Foundation',
|
||||||
website: 'https://www.fsf.org/',
|
website: 'https://www.fsf.org/',
|
||||||
ein: '04-2888848',
|
ein: '04-2888848',
|
||||||
photo: 'https://www.gnu.org/graphics/logo-fsf.org.png',
|
photo: 'https://i.imgur.com/z87sFDE.png',
|
||||||
preview:
|
preview:
|
||||||
'The Free Software Foundation (FSF) is a nonprofit with a worldwide mission to promote computer user freedom.',
|
'The Free Software Foundation (FSF) is a nonprofit with a worldwide mission to promote computer user freedom.',
|
||||||
description:
|
description:
|
||||||
|
@ -306,8 +318,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
name: 'Direct Relief',
|
name: 'Direct Relief',
|
||||||
website: 'https://www.directrelief.org/',
|
website: 'https://www.directrelief.org/',
|
||||||
ein: '95-1831116',
|
ein: '95-1831116',
|
||||||
photo:
|
photo: 'https://i.imgur.com/QS7kHAU.png',
|
||||||
'https://www.ngoadvisor.net/wp-content/uploads/2016/02/DirectRelief_Logo_RGB-2-1920x576.png',
|
|
||||||
preview:
|
preview:
|
||||||
'Direct Relief is a humanitarian aid organization, active in all 50 states and more than 80 countries, with a mission to improve the health and lives of people affected by poverty or emergencies – without regard to politics, religion, or ability to pay.',
|
'Direct Relief is a humanitarian aid organization, active in all 50 states and more than 80 countries, with a mission to improve the health and lives of people affected by poverty or emergencies – without regard to politics, religion, or ability to pay.',
|
||||||
description:
|
description:
|
||||||
|
@ -317,8 +328,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
name: 'World Resources Institute',
|
name: 'World Resources Institute',
|
||||||
website: 'https://www.wri.org/',
|
website: 'https://www.wri.org/',
|
||||||
ein: '52-1257057',
|
ein: '52-1257057',
|
||||||
photo:
|
photo: 'https://i.imgur.com/Bi6MgYI.png',
|
||||||
'https://www.americansecurityproject.org/wp-content/uploads/2016/11/WRI_logo_4c.png',
|
|
||||||
preview:
|
preview:
|
||||||
'WRI is a global nonprofit organization that works with leaders in government, business and civil society to research, design, and carry out practical solutions that simultaneously improve people’s lives and ensure nature can thrive.',
|
'WRI is a global nonprofit organization that works with leaders in government, business and civil society to research, design, and carry out practical solutions that simultaneously improve people’s lives and ensure nature can thrive.',
|
||||||
description:
|
description:
|
||||||
|
@ -328,8 +338,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
name: 'ProPublica',
|
name: 'ProPublica',
|
||||||
website: 'https://www.propublica.org/',
|
website: 'https://www.propublica.org/',
|
||||||
ein: '14-2007220',
|
ein: '14-2007220',
|
||||||
photo:
|
photo: 'https://i.imgur.com/R5Vt3Pb.png',
|
||||||
'https://seekvectorlogo.com/wp-content/uploads/2018/09/propublica-vector-logo.png',
|
|
||||||
preview:
|
preview:
|
||||||
'The mission: to expose abuses of power and betrayals of the public trust by government, business, and other institutions, using the moral force of investigative journalism to spur reform through the sustained spotlighting of wrongdoing.',
|
'The mission: to expose abuses of power and betrayals of the public trust by government, business, and other institutions, using the moral force of investigative journalism to spur reform through the sustained spotlighting of wrongdoing.',
|
||||||
description:
|
description:
|
||||||
|
@ -339,8 +348,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
name: 'Dana-Farber Cancer Institute',
|
name: 'Dana-Farber Cancer Institute',
|
||||||
website: 'https://www.dana-farber.org/',
|
website: 'https://www.dana-farber.org/',
|
||||||
ein: '04-2263040',
|
ein: '04-2263040',
|
||||||
photo:
|
photo: 'https://i.imgur.com/SQNn97p.png',
|
||||||
'https://www.danafarbermasterclass.com/assets/images/DFCI-logo-lens-stacked.png',
|
|
||||||
preview:
|
preview:
|
||||||
"For over 70 years, we've led the world by making life-changing breakthroughs in cancer research and patient care, providing the most advanced treatments available.",
|
"For over 70 years, we've led the world by making life-changing breakthroughs in cancer research and patient care, providing the most advanced treatments available.",
|
||||||
description:
|
description:
|
||||||
|
@ -350,8 +358,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
name: 'Save The Children',
|
name: 'Save The Children',
|
||||||
website: 'https://www.savethechildren.org/',
|
website: 'https://www.savethechildren.org/',
|
||||||
ein: '06-0726487',
|
ein: '06-0726487',
|
||||||
photo:
|
photo: 'https://i.imgur.com/GngYPBI.png',
|
||||||
'https://www.thisisclapham.co.uk/wp-content/uploads/2016/08/savethechildren.png',
|
|
||||||
preview:
|
preview:
|
||||||
'Through the decades, Save the Children has continued to work to save children’s lives, and that’s still what we do today.',
|
'Through the decades, Save the Children has continued to work to save children’s lives, and that’s still what we do today.',
|
||||||
description:
|
description:
|
||||||
|
@ -361,8 +368,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
name: 'World Central Kitchen Incorporated',
|
name: 'World Central Kitchen Incorporated',
|
||||||
website: 'https://wck.org/',
|
website: 'https://wck.org/',
|
||||||
ein: '27-3521132',
|
ein: '27-3521132',
|
||||||
photo:
|
photo: 'https://i.imgur.com/te93MaY.png',
|
||||||
'https://res.cloudinary.com/dktp1ybbx/image/upload/f_auto,fl_lossy,q_auto/v1560203222/organization/prod/924457/M0oxO9vaxO.png',
|
|
||||||
preview:
|
preview:
|
||||||
'WCK is first to the frontlines, providing meals in response to humanitarian, climate, and community crises. We build resilient food systems with locally led solutions.',
|
'WCK is first to the frontlines, providing meals in response to humanitarian, climate, and community crises. We build resilient food systems with locally led solutions.',
|
||||||
description:
|
description:
|
||||||
|
@ -372,8 +378,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
name: 'The Johns Hopkins Center for Health Security',
|
name: 'The Johns Hopkins Center for Health Security',
|
||||||
website: 'https://www.centerforhealthsecurity.org/',
|
website: 'https://www.centerforhealthsecurity.org/',
|
||||||
ein: '',
|
ein: '',
|
||||||
photo:
|
photo: 'https://i.imgur.com/gKZE2Xs.png',
|
||||||
'https://www.centerforhealthsecurity.org/sebin/d/d/CHS.logo.horizontal.blue.png',
|
|
||||||
preview:
|
preview:
|
||||||
'Our mission: to protect people’s health from epidemics and disasters and ensure that communities are resilient to major challenges.',
|
'Our mission: to protect people’s health from epidemics and disasters and ensure that communities are resilient to major challenges.',
|
||||||
description:
|
description:
|
||||||
|
@ -382,8 +387,7 @@ Future plans: We expect to focus on similar theoretical problems in alignment un
|
||||||
{
|
{
|
||||||
name: 'ALLFED',
|
name: 'ALLFED',
|
||||||
website: 'https://allfed.info/',
|
website: 'https://allfed.info/',
|
||||||
photo:
|
photo: 'https://i.imgur.com/p235vwF.jpg',
|
||||||
'https://images1.the-dots.com/1860424/allfed-logo-1.png?p=projectImageFullJpg',
|
|
||||||
ein: '27-6601178',
|
ein: '27-6601178',
|
||||||
preview: 'Feeding everyone no matter what.',
|
preview: 'Feeding everyone no matter what.',
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -4,6 +4,7 @@ export type Comment = {
|
||||||
id: string
|
id: string
|
||||||
contractId: string
|
contractId: string
|
||||||
betId?: string
|
betId?: string
|
||||||
|
answerOutcome?: string
|
||||||
userId: string
|
userId: string
|
||||||
|
|
||||||
text: string
|
text: string
|
||||||
|
|
|
@ -9,9 +9,7 @@ const formatter = new Intl.NumberFormat('en-US', {
|
||||||
|
|
||||||
export function formatMoney(amount: number) {
|
export function formatMoney(amount: number) {
|
||||||
const newAmount = Math.round(amount) === 0 ? 0 : Math.floor(amount) // handle -0 case
|
const newAmount = Math.round(amount) === 0 ? 0 : Math.floor(amount) // handle -0 case
|
||||||
return (
|
return ENV_CONFIG.moneyMoniker + formatter.format(newAmount).replace('$', '')
|
||||||
ENV_CONFIG.moneyMoniker + ' ' + formatter.format(newAmount).replace('$', '')
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatWithCommas(amount: number) {
|
export function formatWithCommas(amount: number) {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import * as functions from 'firebase-functions'
|
import * as functions from 'firebase-functions'
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
|
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { User } from '../../common/user'
|
import { User } from 'common/user'
|
||||||
import { removeUndefinedProps } from '../../common/util/object'
|
import { removeUndefinedProps } from 'common/util/object'
|
||||||
import { redeemShares } from './redeem-shares'
|
import { redeemShares } from './redeem-shares'
|
||||||
import { getNewLiquidityProvision } from '../../common/add-liquidity'
|
import { getNewLiquidityProvision } from 'common/add-liquidity'
|
||||||
|
|
||||||
export const addLiquidity = functions.runWith({ minInstances: 1 }).https.onCall(
|
export const addLiquidity = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||||
async (
|
async (
|
||||||
|
|
|
@ -2,12 +2,12 @@ import * as functions from 'firebase-functions'
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
|
|
||||||
import { getUser } from './utils'
|
import { getUser } from './utils'
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { Comment } from '../../common/comment'
|
import { Comment } from 'common/comment'
|
||||||
import { User } from '../../common/user'
|
import { User } from 'common/user'
|
||||||
import { cleanUsername } from '../../common/util/clean-username'
|
import { cleanUsername } from 'common/util/clean-username'
|
||||||
import { removeUndefinedProps } from '../../common/util/object'
|
import { removeUndefinedProps } from 'common/util/object'
|
||||||
import { Answer } from '../../common/answer'
|
import { Answer } from 'common/answer'
|
||||||
|
|
||||||
export const changeUserInfo = functions
|
export const changeUserInfo = functions
|
||||||
.runWith({ minInstances: 1 })
|
.runWith({ minInstances: 1 })
|
||||||
|
|
|
@ -1,18 +1,13 @@
|
||||||
import * as functions from 'firebase-functions'
|
import * as functions from 'firebase-functions'
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
|
|
||||||
import {
|
import { Contract, DPM, FreeResponse, FullContract } from 'common/contract'
|
||||||
Contract,
|
import { User } from 'common/user'
|
||||||
DPM,
|
import { getLoanAmount, getNewMultiBetInfo } from 'common/new-bet'
|
||||||
FreeResponse,
|
import { Answer, MAX_ANSWER_LENGTH } from 'common/answer'
|
||||||
FullContract,
|
|
||||||
} from '../../common/contract'
|
|
||||||
import { User } from '../../common/user'
|
|
||||||
import { getLoanAmount, getNewMultiBetInfo } from '../../common/new-bet'
|
|
||||||
import { Answer, MAX_ANSWER_LENGTH } from '../../common/answer'
|
|
||||||
import { getContract, getValues } from './utils'
|
import { getContract, getValues } from './utils'
|
||||||
import { sendNewAnswerEmail } from './emails'
|
import { sendNewAnswerEmail } from './emails'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
|
|
||||||
export const createAnswer = functions.runWith({ minInstances: 1 }).https.onCall(
|
export const createAnswer = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||||
async (
|
async (
|
||||||
|
|
|
@ -13,10 +13,10 @@ import {
|
||||||
MAX_QUESTION_LENGTH,
|
MAX_QUESTION_LENGTH,
|
||||||
MAX_TAG_LENGTH,
|
MAX_TAG_LENGTH,
|
||||||
outcomeType,
|
outcomeType,
|
||||||
} from '../../common/contract'
|
} from 'common/contract'
|
||||||
import { slugify } from '../../common/util/slugify'
|
import { slugify } from 'common/util/slugify'
|
||||||
import { randomString } from '../../common/util/random'
|
import { randomString } from 'common/util/random'
|
||||||
import { getNewContract } from '../../common/new-contract'
|
import { getNewContract } from 'common/new-contract'
|
||||||
import {
|
import {
|
||||||
FIXED_ANTE,
|
FIXED_ANTE,
|
||||||
getAnteBets,
|
getAnteBets,
|
||||||
|
@ -24,8 +24,8 @@ import {
|
||||||
getFreeAnswerAnte,
|
getFreeAnswerAnte,
|
||||||
HOUSE_LIQUIDITY_PROVIDER_ID,
|
HOUSE_LIQUIDITY_PROVIDER_ID,
|
||||||
MINIMUM_ANTE,
|
MINIMUM_ANTE,
|
||||||
} from '../../common/antes'
|
} from 'common/antes'
|
||||||
import { getNoneAnswer } from '../../common/answer'
|
import { getNoneAnswer } from 'common/answer'
|
||||||
|
|
||||||
export const createContract = functions
|
export const createContract = functions
|
||||||
.runWith({ minInstances: 1 })
|
.runWith({ minInstances: 1 })
|
||||||
|
@ -72,7 +72,6 @@ export const createContract = functions
|
||||||
)
|
)
|
||||||
return { status: 'error', message: 'Invalid initial probability' }
|
return { status: 'error', message: 'Invalid initial probability' }
|
||||||
|
|
||||||
const ante = FIXED_ANTE // data.ante
|
|
||||||
// uses utc time on server:
|
// uses utc time on server:
|
||||||
const today = new Date().setHours(0, 0, 0, 0)
|
const today = new Date().setHours(0, 0, 0, 0)
|
||||||
const userContractsCreatedTodaySnapshot = await firestore
|
const userContractsCreatedTodaySnapshot = await firestore
|
||||||
|
@ -82,6 +81,8 @@ export const createContract = functions
|
||||||
.get()
|
.get()
|
||||||
const isFree = userContractsCreatedTodaySnapshot.size === 0
|
const isFree = userContractsCreatedTodaySnapshot.size === 0
|
||||||
|
|
||||||
|
const ante = FIXED_ANTE // data.ante
|
||||||
|
|
||||||
if (
|
if (
|
||||||
ante === undefined ||
|
ante === undefined ||
|
||||||
ante < MINIMUM_ANTE ||
|
ante < MINIMUM_ANTE ||
|
||||||
|
|
|
@ -3,10 +3,10 @@ import * as admin from 'firebase-admin'
|
||||||
import * as _ from 'lodash'
|
import * as _ from 'lodash'
|
||||||
|
|
||||||
import { getUser } from './utils'
|
import { getUser } from './utils'
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { slugify } from '../../common/util/slugify'
|
import { slugify } from 'common/util/slugify'
|
||||||
import { randomString } from '../../common/util/random'
|
import { randomString } from 'common/util/random'
|
||||||
import { Fold } from '../../common/fold'
|
import { Fold } from 'common/fold'
|
||||||
|
|
||||||
export const createFold = functions.runWith({ minInstances: 1 }).https.onCall(
|
export const createFold = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||||
async (
|
async (
|
||||||
|
|
|
@ -6,15 +6,12 @@ import {
|
||||||
STARTING_BALANCE,
|
STARTING_BALANCE,
|
||||||
SUS_STARTING_BALANCE,
|
SUS_STARTING_BALANCE,
|
||||||
User,
|
User,
|
||||||
} from '../../common/user'
|
} from 'common/user'
|
||||||
import { getUser, getUserByUsername } from './utils'
|
import { getUser, getUserByUsername } from './utils'
|
||||||
import { randomString } from '../../common/util/random'
|
import { randomString } from 'common/util/random'
|
||||||
import {
|
import { cleanDisplayName, cleanUsername } from 'common/util/clean-username'
|
||||||
cleanDisplayName,
|
|
||||||
cleanUsername,
|
|
||||||
} from '../../common/util/clean-username'
|
|
||||||
import { sendWelcomeEmail } from './emails'
|
import { sendWelcomeEmail } from './emails'
|
||||||
import { isWhitelisted } from '../../common/envs/constants'
|
import { isWhitelisted } from 'common/envs/constants'
|
||||||
|
|
||||||
export const createUser = functions
|
export const createUser = functions
|
||||||
.runWith({ minInstances: 1 })
|
.runWith({ minInstances: 1 })
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import * as _ from 'lodash'
|
import * as _ from 'lodash'
|
||||||
|
|
||||||
import { DOMAIN, PROJECT_ID } from '../../common/envs/constants'
|
import { DOMAIN, PROJECT_ID } from 'common/envs/constants'
|
||||||
import { Answer } from '../../common/answer'
|
import { Answer } from 'common/answer'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { getProbability } from '../../common/calculate'
|
import { getProbability } from 'common/calculate'
|
||||||
import { Comment } from '../../common/comment'
|
import { Comment } from 'common/comment'
|
||||||
import { Contract, FreeResponseContract } from '../../common/contract'
|
import { Contract, FreeResponseContract } from 'common/contract'
|
||||||
import { CREATOR_FEE } from '../../common/fees'
|
import { CREATOR_FEE } from 'common/fees'
|
||||||
import { PrivateUser, User } from '../../common/user'
|
import { PrivateUser, User } from 'common/user'
|
||||||
import { formatMoney, formatPercent } from '../../common/util/format'
|
import { formatMoney, formatPercent } from 'common/util/format'
|
||||||
import { sendTemplateEmail, sendTextEmail } from './send-email'
|
import { sendTemplateEmail, sendTextEmail } from './send-email'
|
||||||
import { getPrivateUser, getUser } from './utils'
|
import { getPrivateUser, getUser } from './utils'
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as functions from 'firebase-functions'
|
import * as functions from 'firebase-functions'
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
|
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { getPrivateUser, getUserByUsername } from './utils'
|
import { getPrivateUser, getUserByUsername } from './utils'
|
||||||
import { sendMarketCloseEmail } from './emails'
|
import { sendMarketCloseEmail } from './emails'
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import * as admin from 'firebase-admin'
|
||||||
import * as _ from 'lodash'
|
import * as _ from 'lodash'
|
||||||
|
|
||||||
import { getContract } from './utils'
|
import { getContract } from './utils'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,10 @@ import * as admin from 'firebase-admin'
|
||||||
import * as _ from 'lodash'
|
import * as _ from 'lodash'
|
||||||
|
|
||||||
import { getContract, getUser, getValues } from './utils'
|
import { getContract, getUser, getValues } from './utils'
|
||||||
import { Comment } from '../../common/comment'
|
import { Comment } from 'common/comment'
|
||||||
import { sendNewCommentEmail } from './emails'
|
import { sendNewCommentEmail } from './emails'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { Answer } from '../../common/answer'
|
import { Answer } from 'common/answer'
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as functions from 'firebase-functions'
|
import * as functions from 'firebase-functions'
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import { View } from '../../common/tracking'
|
import { View } from 'common/tracking'
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import * as functions from 'firebase-functions'
|
import * as functions from 'firebase-functions'
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
|
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { User } from '../../common/user'
|
import { User } from 'common/user'
|
||||||
import {
|
import {
|
||||||
getNewBinaryCpmmBetInfo,
|
getNewBinaryCpmmBetInfo,
|
||||||
getNewBinaryDpmBetInfo,
|
getNewBinaryDpmBetInfo,
|
||||||
getNewMultiBetInfo,
|
getNewMultiBetInfo,
|
||||||
getLoanAmount,
|
getLoanAmount,
|
||||||
} from '../../common/new-bet'
|
} from 'common/new-bet'
|
||||||
import { addObjects, removeUndefinedProps } from '../../common/util/object'
|
import { addObjects, removeUndefinedProps } from 'common/util/object'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { redeemShares } from './redeem-shares'
|
import { redeemShares } from './redeem-shares'
|
||||||
import { Fees } from '../../common/fees'
|
import { Fees } from 'common/fees'
|
||||||
|
|
||||||
export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
|
export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||||
async (
|
async (
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import * as _ from 'lodash'
|
import * as _ from 'lodash'
|
||||||
|
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { getProbability } from '../../common/calculate'
|
import { getProbability } from 'common/calculate'
|
||||||
|
|
||||||
import { Binary, CPMM, FullContract } from '../../common/contract'
|
import { Binary, CPMM, FullContract } from 'common/contract'
|
||||||
import { noFees } from '../../common/fees'
|
import { noFees } from 'common/fees'
|
||||||
import { User } from '../../common/user'
|
import { User } from 'common/user'
|
||||||
|
|
||||||
export const redeemShares = async (userId: string, contractId: string) => {
|
export const redeemShares = async (userId: string, contractId: string) => {
|
||||||
return await firestore.runTransaction(async (transaction) => {
|
return await firestore.runTransaction(async (transaction) => {
|
||||||
|
|
|
@ -2,14 +2,14 @@ import * as functions from 'firebase-functions'
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import * as _ from 'lodash'
|
import * as _ from 'lodash'
|
||||||
|
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { User } from '../../common/user'
|
import { User } from 'common/user'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { getUser, isProd, payUser } from './utils'
|
import { getUser, isProd, payUser } from './utils'
|
||||||
import { sendMarketResolutionEmail } from './emails'
|
import { sendMarketResolutionEmail } from './emails'
|
||||||
import { getLoanPayouts, getPayouts } from '../../common/payouts'
|
import { getLoanPayouts, getPayouts } from 'common/payouts'
|
||||||
import { removeUndefinedProps } from '../../common/util/object'
|
import { removeUndefinedProps } from 'common/util/object'
|
||||||
import { LiquidityProvision } from '../../common/liquidity-provision'
|
import { LiquidityProvision } from 'common/liquidity-provision'
|
||||||
|
|
||||||
export const resolveMarket = functions
|
export const resolveMarket = functions
|
||||||
.runWith({ minInstances: 1 })
|
.runWith({ minInstances: 1 })
|
||||||
|
|
|
@ -5,9 +5,9 @@ import { initAdmin } from './script-init'
|
||||||
initAdmin()
|
initAdmin()
|
||||||
|
|
||||||
import { getValues } from '../utils'
|
import { getValues } from '../utils'
|
||||||
import { View } from '../../../common/tracking'
|
import { View } from 'common/tracking'
|
||||||
import { User } from '../../../common/user'
|
import { User } from 'common/user'
|
||||||
import { batchedWaitAll } from '../../../common/util/promise'
|
import { batchedWaitAll } from 'common/util/promise'
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,9 @@ import * as _ from 'lodash'
|
||||||
import { initAdmin } from './script-init'
|
import { initAdmin } from './script-init'
|
||||||
initAdmin()
|
initAdmin()
|
||||||
|
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { getDpmProbability } from '../../../common/calculate-dpm'
|
import { getDpmProbability } from 'common/calculate-dpm'
|
||||||
import { Binary, Contract, DPM, FullContract } from '../../../common/contract'
|
import { Binary, Contract, DPM, FullContract } from 'common/contract'
|
||||||
|
|
||||||
type DocRef = admin.firestore.DocumentReference
|
type DocRef = admin.firestore.DocumentReference
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
|
@ -4,7 +4,7 @@ import * as _ from 'lodash'
|
||||||
import { initAdmin } from './script-init'
|
import { initAdmin } from './script-init'
|
||||||
initAdmin()
|
initAdmin()
|
||||||
|
|
||||||
import { PrivateUser, STARTING_BALANCE, User } from '../../../common/user'
|
import { PrivateUser, STARTING_BALANCE, User } from 'common/user'
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
|
|
125
functions/src/scripts/denormalize-avatar-urls.ts
Normal file
125
functions/src/scripts/denormalize-avatar-urls.ts
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
// Script for lining up users and contracts/comments to make sure the denormalized avatar URLs in the contracts and
|
||||||
|
// comments match the user avatar URLs.
|
||||||
|
|
||||||
|
import * as admin from 'firebase-admin'
|
||||||
|
import { initAdmin } from './script-init'
|
||||||
|
import {
|
||||||
|
DocumentCorrespondence,
|
||||||
|
findDiffs,
|
||||||
|
describeDiff,
|
||||||
|
applyDiff,
|
||||||
|
} from './denormalize'
|
||||||
|
import { DocumentSnapshot, Transaction } from 'firebase-admin/firestore'
|
||||||
|
|
||||||
|
initAdmin()
|
||||||
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
|
async function getUsersById(transaction: Transaction) {
|
||||||
|
const results = new Map<string, DocumentSnapshot>()
|
||||||
|
const users = await transaction.get(firestore.collection('users'))
|
||||||
|
users.forEach((doc) => {
|
||||||
|
results.set(doc.get('id'), doc)
|
||||||
|
})
|
||||||
|
console.log(`Found ${results.size} unique users.`)
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getContractsByUserId(transaction: Transaction) {
|
||||||
|
let n = 0
|
||||||
|
const results = new Map<string, DocumentSnapshot[]>()
|
||||||
|
const contracts = await transaction.get(firestore.collection('contracts'))
|
||||||
|
contracts.forEach((doc) => {
|
||||||
|
const creatorId = doc.get('creatorId')
|
||||||
|
const creatorContracts = results.get(creatorId) || []
|
||||||
|
creatorContracts.push(doc)
|
||||||
|
results.set(creatorId, creatorContracts)
|
||||||
|
n++
|
||||||
|
})
|
||||||
|
console.log(`Found ${n} contracts from ${results.size} unique users.`)
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCommentsByUserId(transaction: Transaction) {
|
||||||
|
let n = 0
|
||||||
|
const results = new Map<string, DocumentSnapshot[]>()
|
||||||
|
const comments = await transaction.get(firestore.collectionGroup('comments'))
|
||||||
|
comments.forEach((doc) => {
|
||||||
|
const userId = doc.get('userId')
|
||||||
|
const userComments = results.get(userId) || []
|
||||||
|
userComments.push(doc)
|
||||||
|
results.set(userId, userComments)
|
||||||
|
n++
|
||||||
|
})
|
||||||
|
console.log(`Found ${n} comments from ${results.size} unique users.`)
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAnswersByUserId(transaction: Transaction) {
|
||||||
|
let n = 0
|
||||||
|
const results = new Map<string, DocumentSnapshot[]>()
|
||||||
|
const answers = await transaction.get(firestore.collectionGroup('answers'))
|
||||||
|
answers.forEach((doc) => {
|
||||||
|
const userId = doc.get('userId')
|
||||||
|
const userAnswers = results.get(userId) || []
|
||||||
|
userAnswers.push(doc)
|
||||||
|
results.set(userId, userAnswers)
|
||||||
|
n++
|
||||||
|
})
|
||||||
|
console.log(`Found ${n} answers from ${results.size} unique users.`)
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
admin.firestore().runTransaction(async (transaction) => {
|
||||||
|
const [usersById, contractsByUserId, commentsByUserId, answersByUserId] =
|
||||||
|
await Promise.all([
|
||||||
|
getUsersById(transaction),
|
||||||
|
getContractsByUserId(transaction),
|
||||||
|
getCommentsByUserId(transaction),
|
||||||
|
getAnswersByUserId(transaction),
|
||||||
|
])
|
||||||
|
|
||||||
|
const usersContracts = Array.from(
|
||||||
|
usersById.entries(),
|
||||||
|
([id, doc]): DocumentCorrespondence => {
|
||||||
|
return [doc, contractsByUserId.get(id) || []]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const contractDiffs = findDiffs(
|
||||||
|
usersContracts,
|
||||||
|
'avatarUrl',
|
||||||
|
'creatorAvatarUrl'
|
||||||
|
)
|
||||||
|
console.log(`Found ${contractDiffs.length} contracts with mismatches.`)
|
||||||
|
contractDiffs.forEach((d) => {
|
||||||
|
console.log(describeDiff(d))
|
||||||
|
applyDiff(transaction, d)
|
||||||
|
})
|
||||||
|
|
||||||
|
const usersComments = Array.from(
|
||||||
|
usersById.entries(),
|
||||||
|
([id, doc]): DocumentCorrespondence => {
|
||||||
|
return [doc, commentsByUserId.get(id) || []]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const commentDiffs = findDiffs(usersComments, 'avatarUrl', 'userAvatarUrl')
|
||||||
|
console.log(`Found ${commentDiffs.length} comments with mismatches.`)
|
||||||
|
commentDiffs.forEach((d) => {
|
||||||
|
console.log(describeDiff(d))
|
||||||
|
applyDiff(transaction, d)
|
||||||
|
})
|
||||||
|
|
||||||
|
const usersAnswers = Array.from(
|
||||||
|
usersById.entries(),
|
||||||
|
([id, doc]): DocumentCorrespondence => {
|
||||||
|
return [doc, answersByUserId.get(id) || []]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const answerDiffs = findDiffs(usersAnswers, 'avatarUrl', 'avatarUrl')
|
||||||
|
console.log(`Found ${answerDiffs.length} answers with mismatches.`)
|
||||||
|
answerDiffs.forEach((d) => {
|
||||||
|
console.log(describeDiff(d))
|
||||||
|
applyDiff(transaction, d)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
48
functions/src/scripts/denormalize.ts
Normal file
48
functions/src/scripts/denormalize.ts
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
// Helper functions for maintaining the relationship between fields in one set of documents and denormalized copies in
|
||||||
|
// another set of documents.
|
||||||
|
|
||||||
|
import { DocumentSnapshot, Transaction } from 'firebase-admin/firestore'
|
||||||
|
|
||||||
|
export type DocumentValue = {
|
||||||
|
doc: DocumentSnapshot
|
||||||
|
field: string
|
||||||
|
val: any
|
||||||
|
}
|
||||||
|
export type DocumentCorrespondence = [DocumentSnapshot, DocumentSnapshot[]]
|
||||||
|
export type DocumentDiff = {
|
||||||
|
src: DocumentValue
|
||||||
|
dest: DocumentValue
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findDiffs(
|
||||||
|
docs: DocumentCorrespondence[],
|
||||||
|
srcPath: string,
|
||||||
|
destPath: string
|
||||||
|
) {
|
||||||
|
const diffs: DocumentDiff[] = []
|
||||||
|
for (let [srcDoc, destDocs] of docs) {
|
||||||
|
const srcVal = srcDoc.get(srcPath)
|
||||||
|
for (let destDoc of destDocs) {
|
||||||
|
const destVal = destDoc.get(destPath)
|
||||||
|
if (destVal !== srcVal) {
|
||||||
|
diffs.push({
|
||||||
|
src: { doc: srcDoc, field: srcPath, val: srcVal },
|
||||||
|
dest: { doc: destDoc, field: destPath, val: destVal },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return diffs
|
||||||
|
}
|
||||||
|
|
||||||
|
export function describeDiff(diff: DocumentDiff) {
|
||||||
|
function describeDocVal(x: DocumentValue): string {
|
||||||
|
return `${x.doc.ref.path}.${x.field}: ${x.val}`
|
||||||
|
}
|
||||||
|
return `${describeDocVal(diff.src)} -> ${describeDocVal(diff.dest)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyDiff(transaction: Transaction, diff: DocumentDiff) {
|
||||||
|
const { src, dest } = diff
|
||||||
|
transaction.update(dest.doc.ref, dest.field, src.val)
|
||||||
|
}
|
|
@ -5,10 +5,10 @@ import * as fs from 'fs'
|
||||||
import { initAdmin } from './script-init'
|
import { initAdmin } from './script-init'
|
||||||
initAdmin()
|
initAdmin()
|
||||||
|
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { Contract } from '../../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { getValues } from '../utils'
|
import { getValues } from '../utils'
|
||||||
import { Comment } from '../../../common/comment'
|
import { Comment } from 'common/comment'
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { initAdmin } from './script-init'
|
||||||
initAdmin()
|
initAdmin()
|
||||||
|
|
||||||
import { getValues } from '../utils'
|
import { getValues } from '../utils'
|
||||||
import { Fold } from '../../../common/fold'
|
import { Fold } from 'common/fold'
|
||||||
|
|
||||||
async function lowercaseFoldTags() {
|
async function lowercaseFoldTags() {
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
|
@ -4,7 +4,7 @@ import * as _ from 'lodash'
|
||||||
import { initAdmin } from './script-init'
|
import { initAdmin } from './script-init'
|
||||||
initAdmin()
|
initAdmin()
|
||||||
|
|
||||||
import { Contract } from '../../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ import * as _ from 'lodash'
|
||||||
import { initAdmin } from './script-init'
|
import { initAdmin } from './script-init'
|
||||||
initAdmin()
|
initAdmin()
|
||||||
|
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { Contract } from '../../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
|
|
||||||
type DocRef = admin.firestore.DocumentReference
|
type DocRef = admin.firestore.DocumentReference
|
||||||
|
|
||||||
|
|
|
@ -4,22 +4,13 @@ import * as _ from 'lodash'
|
||||||
import { initAdmin } from './script-init'
|
import { initAdmin } from './script-init'
|
||||||
initAdmin()
|
initAdmin()
|
||||||
|
|
||||||
import {
|
import { Binary, Contract, CPMM, DPM, FullContract } from 'common/contract'
|
||||||
Binary,
|
import { Bet } from 'common/bet'
|
||||||
Contract,
|
import { calculateDpmPayout, getDpmProbability } from 'common/calculate-dpm'
|
||||||
CPMM,
|
import { User } from 'common/user'
|
||||||
DPM,
|
import { getCpmmInitialLiquidity } from 'common/antes'
|
||||||
FullContract,
|
import { noFees } from 'common/fees'
|
||||||
} from '../../../common/contract'
|
import { addObjects } from 'common/util/object'
|
||||||
import { Bet } from '../../../common/bet'
|
|
||||||
import {
|
|
||||||
calculateDpmPayout,
|
|
||||||
getDpmProbability,
|
|
||||||
} from '../../../common/calculate-dpm'
|
|
||||||
import { User } from '../../../common/user'
|
|
||||||
import { getCpmmInitialLiquidity } from '../../../common/antes'
|
|
||||||
import { noFees } from '../../../common/fees'
|
|
||||||
import { addObjects } from '../../../common/util/object'
|
|
||||||
|
|
||||||
type DocRef = admin.firestore.DocumentReference
|
type DocRef = admin.firestore.DocumentReference
|
||||||
|
|
||||||
|
|
|
@ -4,14 +4,11 @@ import * as _ from 'lodash'
|
||||||
import { initAdmin } from './script-init'
|
import { initAdmin } from './script-init'
|
||||||
initAdmin()
|
initAdmin()
|
||||||
|
|
||||||
import { Binary, Contract, DPM, FullContract } from '../../../common/contract'
|
import { Binary, Contract, DPM, FullContract } from 'common/contract'
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import {
|
import { calculateDpmShares, getDpmProbability } from 'common/calculate-dpm'
|
||||||
calculateDpmShares,
|
import { getSellBetInfo } from 'common/sell-bet'
|
||||||
getDpmProbability,
|
import { User } from 'common/user'
|
||||||
} from '../../../common/calculate-dpm'
|
|
||||||
import { getSellBetInfo } from '../../../common/sell-bet'
|
|
||||||
import { User } from '../../../common/user'
|
|
||||||
|
|
||||||
type DocRef = admin.firestore.DocumentReference
|
type DocRef = admin.firestore.DocumentReference
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,10 @@ import * as _ from 'lodash'
|
||||||
import { initAdmin } from './script-init'
|
import { initAdmin } from './script-init'
|
||||||
initAdmin()
|
initAdmin()
|
||||||
|
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { Contract } from '../../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { getLoanPayouts, getPayouts } from '../../../common/payouts'
|
import { getLoanPayouts, getPayouts } from 'common/payouts'
|
||||||
import { filterDefined } from '../../../common/util/array'
|
import { filterDefined } from 'common/util/array'
|
||||||
|
|
||||||
type DocRef = admin.firestore.DocumentReference
|
type DocRef = admin.firestore.DocumentReference
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ import * as _ from 'lodash'
|
||||||
import { initAdmin } from './script-init'
|
import { initAdmin } from './script-init'
|
||||||
initAdmin()
|
initAdmin()
|
||||||
|
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { Contract } from '../../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
|
|
||||||
type DocRef = admin.firestore.DocumentReference
|
type DocRef = admin.firestore.DocumentReference
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ import * as _ from 'lodash'
|
||||||
import { initAdmin } from './script-init'
|
import { initAdmin } from './script-init'
|
||||||
initAdmin()
|
initAdmin()
|
||||||
|
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { Contract } from '../../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { getValues } from '../utils'
|
import { getValues } from '../utils'
|
||||||
|
|
||||||
async function removeAnswerAnte() {
|
async function removeAnswerAnte() {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import * as _ from 'lodash'
|
||||||
import { initAdmin } from './script-init'
|
import { initAdmin } from './script-init'
|
||||||
initAdmin()
|
initAdmin()
|
||||||
|
|
||||||
import { Contract } from '../../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { getValues } from '../utils'
|
import { getValues } from '../utils'
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
|
@ -4,8 +4,8 @@ import * as _ from 'lodash'
|
||||||
import { initAdmin } from './script-init'
|
import { initAdmin } from './script-init'
|
||||||
initAdmin()
|
initAdmin()
|
||||||
|
|
||||||
import { Contract } from '../../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { parseTags } from '../../../common/util/parse'
|
import { parseTags } from 'common/util/parse'
|
||||||
import { getValues } from '../utils'
|
import { getValues } from '../utils'
|
||||||
|
|
||||||
async function updateContractTags() {
|
async function updateContractTags() {
|
||||||
|
|
|
@ -5,9 +5,9 @@ import { initAdmin } from './script-init'
|
||||||
initAdmin()
|
initAdmin()
|
||||||
|
|
||||||
import { getValues } from '../utils'
|
import { getValues } from '../utils'
|
||||||
import { User } from '../../../common/user'
|
import { User } from 'common/user'
|
||||||
import { batchedWaitAll } from '../../../common/util/promise'
|
import { batchedWaitAll } from 'common/util/promise'
|
||||||
import { Contract } from '../../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { updateWordScores } from '../update-recommendations'
|
import { updateWordScores } from '../update-recommendations'
|
||||||
import { getFeedContracts, doUserFeedUpdate } from '../update-feed'
|
import { getFeedContracts, doUserFeedUpdate } from '../update-feed'
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,9 @@ import * as _ from 'lodash'
|
||||||
import { initAdmin } from './script-init'
|
import { initAdmin } from './script-init'
|
||||||
initAdmin()
|
initAdmin()
|
||||||
|
|
||||||
import { Contract } from '../../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { getValues } from '../utils'
|
import { getValues } from '../utils'
|
||||||
import { Comment } from '../../../common/comment'
|
import { Comment } from 'common/comment'
|
||||||
|
|
||||||
async function updateLastCommentTime() {
|
async function updateLastCommentTime() {
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import * as functions from 'firebase-functions'
|
import * as functions from 'firebase-functions'
|
||||||
|
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { User } from '../../common/user'
|
import { User } from 'common/user'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { getSellBetInfo } from '../../common/sell-bet'
|
import { getSellBetInfo } from 'common/sell-bet'
|
||||||
import { addObjects, removeUndefinedProps } from '../../common/util/object'
|
import { addObjects, removeUndefinedProps } from 'common/util/object'
|
||||||
import { Fees } from '../../common/fees'
|
import { Fees } from 'common/fees'
|
||||||
|
|
||||||
export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall(
|
export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||||
async (
|
async (
|
||||||
|
|
|
@ -2,12 +2,12 @@ import * as _ from 'lodash'
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import * as functions from 'firebase-functions'
|
import * as functions from 'firebase-functions'
|
||||||
|
|
||||||
import { Binary, CPMM, FullContract } from '../../common/contract'
|
import { Binary, CPMM, FullContract } from 'common/contract'
|
||||||
import { User } from '../../common/user'
|
import { User } from 'common/user'
|
||||||
import { getCpmmSellBetInfo } from '../../common/sell-bet'
|
import { getCpmmSellBetInfo } from 'common/sell-bet'
|
||||||
import { addObjects, removeUndefinedProps } from '../../common/util/object'
|
import { addObjects, removeUndefinedProps } from 'common/util/object'
|
||||||
import { getValues } from './utils'
|
import { getValues } from './utils'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
|
|
||||||
export const sellShares = functions.runWith({ minInstances: 1 }).https.onCall(
|
export const sellShares = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||||
async (
|
async (
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import * as functions from 'firebase-functions'
|
import * as functions from 'firebase-functions'
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
|
|
||||||
import { User } from '../../common/user'
|
import { User } from 'common/user'
|
||||||
import { Txn } from '../../common/txn'
|
import { Txn } from 'common/txn'
|
||||||
import { removeUndefinedProps } from '../../common/util/object'
|
import { removeUndefinedProps } from 'common/util/object'
|
||||||
|
|
||||||
export const transact = functions
|
export const transact = functions
|
||||||
.runWith({ minInstances: 1 })
|
.runWith({ minInstances: 1 })
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as functions from 'firebase-functions'
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import * as _ from 'lodash'
|
import * as _ from 'lodash'
|
||||||
import { getUser } from './utils'
|
import { getUser } from './utils'
|
||||||
import { PrivateUser } from '../../common/user'
|
import { PrivateUser } from 'common/user'
|
||||||
|
|
||||||
export const unsubscribe = functions
|
export const unsubscribe = functions
|
||||||
.runWith({ minInstances: 1 })
|
.runWith({ minInstances: 1 })
|
||||||
|
|
|
@ -3,9 +3,9 @@ import * as admin from 'firebase-admin'
|
||||||
import * as _ from 'lodash'
|
import * as _ from 'lodash'
|
||||||
|
|
||||||
import { getValues } from './utils'
|
import { getValues } from './utils'
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { batchedWaitAll } from '../../common/util/promise'
|
import { batchedWaitAll } from 'common/util/promise'
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
|
|
|
@ -3,21 +3,21 @@ import * as functions from 'firebase-functions'
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
|
|
||||||
import { getValue, getValues } from './utils'
|
import { getValue, getValues } from './utils'
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { logInterpolation } from '../../common/util/math'
|
import { logInterpolation } from 'common/util/math'
|
||||||
import { DAY_MS } from '../../common/util/time'
|
import { DAY_MS } from 'common/util/time'
|
||||||
import {
|
import {
|
||||||
getProbability,
|
getProbability,
|
||||||
getOutcomeProbability,
|
getOutcomeProbability,
|
||||||
getTopAnswer,
|
getTopAnswer,
|
||||||
} from '../../common/calculate'
|
} from 'common/calculate'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { Comment } from '../../common/comment'
|
import { Comment } from 'common/comment'
|
||||||
import { User } from '../../common/user'
|
import { User } from 'common/user'
|
||||||
import {
|
import {
|
||||||
getContractScore,
|
getContractScore,
|
||||||
MAX_FEED_CONTRACTS,
|
MAX_FEED_CONTRACTS,
|
||||||
} from '../../common/recommended-contracts'
|
} from 'common/recommended-contracts'
|
||||||
import { callCloudFunction } from './call-cloud-function'
|
import { callCloudFunction } from './call-cloud-function'
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
|
@ -3,12 +3,12 @@ import * as admin from 'firebase-admin'
|
||||||
import * as _ from 'lodash'
|
import * as _ from 'lodash'
|
||||||
|
|
||||||
import { getValue, getValues } from './utils'
|
import { getValue, getValues } from './utils'
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { User } from '../../common/user'
|
import { User } from 'common/user'
|
||||||
import { ClickEvent } from '../../common/tracking'
|
import { ClickEvent } from 'common/tracking'
|
||||||
import { getWordScores } from '../../common/recommended-contracts'
|
import { getWordScores } from 'common/recommended-contracts'
|
||||||
import { batchedWaitAll } from '../../common/util/promise'
|
import { batchedWaitAll } from 'common/util/promise'
|
||||||
import { callCloudFunction } from './call-cloud-function'
|
import { callCloudFunction } from './call-cloud-function'
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
|
@ -3,11 +3,11 @@ import * as admin from 'firebase-admin'
|
||||||
import * as _ from 'lodash'
|
import * as _ from 'lodash'
|
||||||
|
|
||||||
import { getValues } from './utils'
|
import { getValues } from './utils'
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { User } from '../../common/user'
|
import { User } from 'common/user'
|
||||||
import { batchedWaitAll } from '../../common/util/promise'
|
import { batchedWaitAll } from 'common/util/promise'
|
||||||
import { calculatePayout } from '../../common/calculate'
|
import { calculatePayout } from 'common/calculate'
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
|
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { PrivateUser, User } from '../../common/user'
|
import { PrivateUser, User } from 'common/user'
|
||||||
|
|
||||||
export const isProd =
|
export const isProd =
|
||||||
admin.instanceId().app.options.projectId === 'mantic-markets'
|
admin.instanceId().app.options.projectId === 'mantic-markets'
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"baseUrl": "../",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"outDir": "lib",
|
"outDir": "lib",
|
||||||
|
|
|
@ -93,7 +93,8 @@ export function getHtml(parsedReq: ParsedRequest) {
|
||||||
creatorAvatarUrl,
|
creatorAvatarUrl,
|
||||||
} = parsedReq
|
} = parsedReq
|
||||||
const MAX_QUESTION_CHARS = 100
|
const MAX_QUESTION_CHARS = 100
|
||||||
const truncatedQuestion = question.length > MAX_QUESTION_CHARS
|
const truncatedQuestion =
|
||||||
|
question.length > MAX_QUESTION_CHARS
|
||||||
? question.slice(0, MAX_QUESTION_CHARS) + '...'
|
? question.slice(0, MAX_QUESTION_CHARS) + '...'
|
||||||
: question
|
: question
|
||||||
const hideAvatar = creatorAvatarUrl ? '' : 'hidden'
|
const hideAvatar = creatorAvatarUrl ? '' : 'hidden'
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { useUser } from '../hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { checkoutURL } from '../lib/service/stripe'
|
import { checkoutURL } from 'web/lib/service/stripe'
|
||||||
import { FundsSelector } from './yes-no-selector'
|
import { FundsSelector } from './yes-no-selector'
|
||||||
|
|
||||||
export function AddFundsButton(props: { className?: string }) {
|
export function AddFundsButton(props: { className?: string }) {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { formatMoney } from '../../common/util/format'
|
import { formatMoney } from 'common/util/format'
|
||||||
import { useUser } from '../hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { addLiquidity } from '../lib/firebase/api-call'
|
import { addLiquidity } from 'web/lib/firebase/api-call'
|
||||||
import { AmountInput } from './amount-input'
|
import { AmountInput } from './amount-input'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { useUser } from '../hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { formatMoney, formatWithCommas } from '../../common/util/format'
|
import { formatMoney, formatWithCommas } from 'common/util/format'
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { Spacer } from './layout/spacer'
|
import { Spacer } from './layout/spacer'
|
||||||
import { calculateCpmmSale } from '../../common/calculate-cpmm'
|
import { calculateCpmmSale } from 'common/calculate-cpmm'
|
||||||
import { Binary, CPMM, FullContract } from '../../common/contract'
|
import { Binary, CPMM, FullContract } from 'common/contract'
|
||||||
|
import { SiteLink } from './site-link'
|
||||||
|
|
||||||
export function AmountInput(props: {
|
export function AmountInput(props: {
|
||||||
amount: number | undefined
|
amount: number | undefined
|
||||||
|
@ -65,7 +66,16 @@ export function AmountInput(props: {
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="mb-2 mr-auto self-center whitespace-nowrap text-xs font-medium tracking-wide text-red-500">
|
<div className="mb-2 mr-auto self-center whitespace-nowrap text-xs font-medium tracking-wide text-red-500">
|
||||||
{error}
|
{error === 'Insufficient balance' ? (
|
||||||
|
<>
|
||||||
|
Not enough funds.
|
||||||
|
<span className="ml-1 text-indigo-500">
|
||||||
|
<SiteLink href="/add-funds">Buy more?</SiteLink>
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
error
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -168,9 +178,10 @@ export function SellAmountInput(props: {
|
||||||
]
|
]
|
||||||
|
|
||||||
const sellOutcome = yesShares ? 'YES' : noShares ? 'NO' : undefined
|
const sellOutcome = yesShares ? 'YES' : noShares ? 'NO' : undefined
|
||||||
const shares = yesShares || noShares
|
const shares = Math.round(yesShares) || Math.round(noShares)
|
||||||
|
|
||||||
|
const sharesSold = Math.min(amount ?? 0, shares)
|
||||||
|
|
||||||
const sharesSold = Math.min(amount ?? 0, yesShares || noShares)
|
|
||||||
const { saleValue } = calculateCpmmSale(
|
const { saleValue } = calculateCpmmSale(
|
||||||
contract,
|
contract,
|
||||||
sharesSold,
|
sharesSold,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { ResponsiveLine } from '@nivo/line'
|
import { ResponsiveLine } from '@nivo/line'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { useWindowSize } from '../../hooks/use-window-size'
|
import { useWindowSize } from 'web/hooks/use-window-size'
|
||||||
|
|
||||||
export function DailyCountChart(props: {
|
export function DailyCountChart(props: {
|
||||||
startDate: number
|
startDate: number
|
||||||
|
|
|
@ -3,28 +3,28 @@ import _ from 'lodash'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { XIcon } from '@heroicons/react/solid'
|
import { XIcon } from '@heroicons/react/solid'
|
||||||
|
|
||||||
import { Answer } from '../../../common/answer'
|
import { Answer } from 'common/answer'
|
||||||
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
|
import { DPM, FreeResponse, FullContract } from 'common/contract'
|
||||||
import { BuyAmountInput } from '../amount-input'
|
import { BuyAmountInput } from '../amount-input'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { placeBet } from '../../lib/firebase/api-call'
|
import { placeBet } from 'web/lib/firebase/api-call'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
import { Spacer } from '../layout/spacer'
|
import { Spacer } from '../layout/spacer'
|
||||||
import {
|
import {
|
||||||
formatMoney,
|
formatMoney,
|
||||||
formatPercent,
|
formatPercent,
|
||||||
formatWithCommas,
|
formatWithCommas,
|
||||||
} from '../../../common/util/format'
|
} from 'common/util/format'
|
||||||
import { InfoTooltip } from '../info-tooltip'
|
import { InfoTooltip } from '../info-tooltip'
|
||||||
import { useUser } from '../../hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import {
|
import {
|
||||||
getDpmOutcomeProbability,
|
getDpmOutcomeProbability,
|
||||||
calculateDpmShares,
|
calculateDpmShares,
|
||||||
calculateDpmPayoutAfterCorrectBet,
|
calculateDpmPayoutAfterCorrectBet,
|
||||||
getDpmOutcomeProbabilityAfterBet,
|
getDpmOutcomeProbabilityAfterBet,
|
||||||
} from '../../../common/calculate-dpm'
|
} from 'common/calculate-dpm'
|
||||||
import { firebaseLogin } from '../../lib/firebase/users'
|
import { firebaseLogin } from 'web/lib/firebase/users'
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
|
|
||||||
export function AnswerBetPanel(props: {
|
export function AnswerBetPanel(props: {
|
||||||
answer: Answer
|
answer: Answer
|
||||||
|
@ -174,7 +174,7 @@ export function AnswerBetPanel(props: {
|
||||||
className="btn self-stretch whitespace-nowrap border-none bg-gradient-to-r from-teal-500 to-green-500 px-10 text-lg font-medium normal-case hover:from-teal-600 hover:to-green-600"
|
className="btn self-stretch whitespace-nowrap border-none bg-gradient-to-r from-teal-500 to-green-500 px-10 text-lg font-medium normal-case hover:from-teal-600 hover:to-green-600"
|
||||||
onClick={firebaseLogin}
|
onClick={firebaseLogin}
|
||||||
>
|
>
|
||||||
Sign up to trade!
|
Sign up to bet!
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
import { Answer } from '../../../common/answer'
|
import { Answer } from 'common/answer'
|
||||||
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
|
import { DPM, FreeResponse, FullContract } from 'common/contract'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
import { Avatar } from '../avatar'
|
import { Avatar } from '../avatar'
|
||||||
import { SiteLink } from '../site-link'
|
import { SiteLink } from '../site-link'
|
||||||
import { formatPercent } from '../../../common/util/format'
|
import { formatPercent } from 'common/util/format'
|
||||||
import { getDpmOutcomeProbability } from '../../../common/calculate-dpm'
|
import { getDpmOutcomeProbability } from 'common/calculate-dpm'
|
||||||
import { tradingAllowed } from '../../lib/firebase/contracts'
|
import { tradingAllowed } from 'web/lib/firebase/contracts'
|
||||||
import { Linkify } from '../linkify'
|
import { Linkify } from '../linkify'
|
||||||
|
|
||||||
export function AnswerItem(props: {
|
export function AnswerItem(props: {
|
||||||
|
@ -68,7 +68,7 @@ export function AnswerItem(props: {
|
||||||
</Row>
|
</Row>
|
||||||
</SiteLink>
|
</SiteLink>
|
||||||
{/* TODO: Show total pool? */}
|
{/* TODO: Show total pool? */}
|
||||||
<div className="text-base">#{number}</div>
|
<div className="text-base">{showChoice && '#' + number}</div>
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,13 @@ import clsx from 'clsx'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
|
import { DPM, FreeResponse, FullContract } from 'common/contract'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { resolveMarket } from '../../lib/firebase/api-call'
|
import { resolveMarket } from 'web/lib/firebase/api-call'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
import { ChooseCancelSelector } from '../yes-no-selector'
|
import { ChooseCancelSelector } from '../yes-no-selector'
|
||||||
import { ResolveConfirmationButton } from '../confirmation-button'
|
import { ResolveConfirmationButton } from '../confirmation-button'
|
||||||
import { removeUndefinedProps } from '../../../common/util/object'
|
import { removeUndefinedProps } from 'common/util/object'
|
||||||
|
|
||||||
export function AnswerResolvePanel(props: {
|
export function AnswerResolvePanel(props: {
|
||||||
contract: FullContract<DPM, FreeResponse>
|
contract: FullContract<DPM, FreeResponse>
|
||||||
|
|
|
@ -4,11 +4,11 @@ import dayjs from 'dayjs'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
|
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
|
import { DPM, FreeResponse, FullContract } from 'common/contract'
|
||||||
import { getOutcomeProbability } from '../../../common/calculate'
|
import { getOutcomeProbability } from 'common/calculate'
|
||||||
import { useBets } from '../../hooks/use-bets'
|
import { useBets } from 'web/hooks/use-bets'
|
||||||
import { useWindowSize } from '../../hooks/use-window-size'
|
import { useWindowSize } from 'web/hooks/use-window-size'
|
||||||
|
|
||||||
const NUM_LINES = 6
|
const NUM_LINES = 6
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { useLayoutEffect, useState } from 'react'
|
import { useLayoutEffect, useState } from 'react'
|
||||||
|
|
||||||
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
|
import { DPM, FreeResponse, FullContract } from 'common/contract'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { useUser } from '../../hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { getDpmOutcomeProbability } from '../../../common/calculate-dpm'
|
import { getDpmOutcomeProbability } from 'common/calculate-dpm'
|
||||||
import { useAnswers } from '../../hooks/use-answers'
|
import { useAnswers } from 'web/hooks/use-answers'
|
||||||
import { tradingAllowed } from '../../lib/firebase/contracts'
|
import { tradingAllowed } from 'web/lib/firebase/contracts'
|
||||||
import { AnswerItem } from './answer-item'
|
import { AnswerItem } from './answer-item'
|
||||||
import { CreateAnswerPanel } from './create-answer-panel'
|
import { CreateAnswerPanel } from './create-answer-panel'
|
||||||
import { AnswerResolvePanel } from './answer-resolve-panel'
|
import { AnswerResolvePanel } from './answer-resolve-panel'
|
||||||
import { Spacer } from '../layout/spacer'
|
import { Spacer } from '../layout/spacer'
|
||||||
import { FeedItems } from '../feed/feed-items'
|
import { FeedItems } from '../feed/feed-items'
|
||||||
import { ActivityItem } from '../feed/activity-items'
|
import { ActivityItem } from '../feed/activity-items'
|
||||||
import { User } from '../../../common/user'
|
import { User } from 'common/user'
|
||||||
import { getOutcomeProbability } from '../../../common/calculate'
|
import { getOutcomeProbability } from 'common/calculate'
|
||||||
import { Answer } from '../../../common/answer'
|
import { Answer } from 'common/answer'
|
||||||
|
|
||||||
export function AnswersPanel(props: {
|
export function AnswersPanel(props: {
|
||||||
contract: FullContract<DPM, FreeResponse>
|
contract: FullContract<DPM, FreeResponse>
|
||||||
|
@ -24,7 +24,7 @@ export function AnswersPanel(props: {
|
||||||
const { creatorId, resolution, resolutions, totalBets } = contract
|
const { creatorId, resolution, resolutions, totalBets } = contract
|
||||||
|
|
||||||
const answers = useAnswers(contract.id) ?? contract.answers
|
const answers = useAnswers(contract.id) ?? contract.answers
|
||||||
const [winningAnswers, otherAnswers] = _.partition(
|
const [winningAnswers, losingAnswers] = _.partition(
|
||||||
answers.filter(
|
answers.filter(
|
||||||
(answer) => answer.id !== '0' && totalBets[answer.id] > 0.000000001
|
(answer) => answer.id !== '0' && totalBets[answer.id] > 0.000000001
|
||||||
),
|
),
|
||||||
|
@ -36,7 +36,7 @@ export function AnswersPanel(props: {
|
||||||
resolutions ? -1 * resolutions[answer.id] : 0
|
resolutions ? -1 * resolutions[answer.id] : 0
|
||||||
),
|
),
|
||||||
..._.sortBy(
|
..._.sortBy(
|
||||||
resolution ? [] : otherAnswers,
|
resolution ? [] : losingAnswers,
|
||||||
(answer) => -1 * getDpmOutcomeProbability(contract.totalShares, answer.id)
|
(answer) => -1 * getDpmOutcomeProbability(contract.totalShares, answer.id)
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -52,7 +52,11 @@ export function AnswersPanel(props: {
|
||||||
|
|
||||||
const chosenTotal = _.sum(Object.values(chosenAnswers))
|
const chosenTotal = _.sum(Object.values(chosenAnswers))
|
||||||
|
|
||||||
const answerItems = getAnswers(contract, user)
|
const answerItems = getAnswerItems(
|
||||||
|
contract,
|
||||||
|
losingAnswers.length > 0 ? losingAnswers : sortedAnswers,
|
||||||
|
user
|
||||||
|
)
|
||||||
|
|
||||||
const onChoose = (answerId: string, prob: number) => {
|
const onChoose = (answerId: string, prob: number) => {
|
||||||
if (resolveOption === 'CHOOSE') {
|
if (resolveOption === 'CHOOSE') {
|
||||||
|
@ -89,9 +93,7 @@ export function AnswersPanel(props: {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col className="gap-3">
|
<Col className="gap-3">
|
||||||
{(resolveOption === 'CHOOSE' ||
|
{(resolveOption || resolution) &&
|
||||||
resolveOption === 'CHOOSE_MULTIPLE' ||
|
|
||||||
resolution === 'MKT') &&
|
|
||||||
sortedAnswers.map((answer) => (
|
sortedAnswers.map((answer) => (
|
||||||
<AnswerItem
|
<AnswerItem
|
||||||
key={answer.id}
|
key={answer.id}
|
||||||
|
@ -105,11 +107,7 @@ export function AnswersPanel(props: {
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{sortedAnswers.length === 0 && (
|
{!resolveOption && (
|
||||||
<div className="pb-4 text-gray-500">No answers yet...</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!resolveOption && sortedAnswers.length > 0 && (
|
|
||||||
<FeedItems
|
<FeedItems
|
||||||
contract={contract}
|
contract={contract}
|
||||||
items={answerItems}
|
items={answerItems}
|
||||||
|
@ -118,6 +116,10 @@ export function AnswersPanel(props: {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{answers.length <= 1 && (
|
||||||
|
<div className="pb-4 text-gray-500">No answers yet...</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{tradingAllowed(contract) &&
|
{tradingAllowed(contract) &&
|
||||||
(!resolveOption || resolveOption === 'CANCEL') && (
|
(!resolveOption || resolveOption === 'CANCEL') && (
|
||||||
<CreateAnswerPanel contract={contract} />
|
<CreateAnswerPanel contract={contract} />
|
||||||
|
@ -138,12 +140,11 @@ export function AnswersPanel(props: {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAnswers(
|
function getAnswerItems(
|
||||||
contract: FullContract<DPM, FreeResponse>,
|
contract: FullContract<DPM, FreeResponse>,
|
||||||
|
answers: Answer[],
|
||||||
user: User | undefined | null
|
user: User | undefined | null
|
||||||
) {
|
) {
|
||||||
const { answers } = contract
|
|
||||||
|
|
||||||
let outcomes = _.uniq(
|
let outcomes = _.uniq(
|
||||||
answers.map((answer) => answer.number.toString())
|
answers.map((answer) => answer.number.toString())
|
||||||
).filter((outcome) => getOutcomeProbability(contract, outcome) > 0.0001)
|
).filter((outcome) => getOutcomeProbability(contract, outcome) > 0.0001)
|
||||||
|
|
|
@ -3,26 +3,26 @@ import _ from 'lodash'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import Textarea from 'react-expanding-textarea'
|
import Textarea from 'react-expanding-textarea'
|
||||||
|
|
||||||
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
|
import { DPM, FreeResponse, FullContract } from 'common/contract'
|
||||||
import { BuyAmountInput } from '../amount-input'
|
import { BuyAmountInput } from '../amount-input'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { createAnswer } from '../../lib/firebase/api-call'
|
import { createAnswer } from 'web/lib/firebase/api-call'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
import {
|
import {
|
||||||
formatMoney,
|
formatMoney,
|
||||||
formatPercent,
|
formatPercent,
|
||||||
formatWithCommas,
|
formatWithCommas,
|
||||||
} from '../../../common/util/format'
|
} from 'common/util/format'
|
||||||
import { InfoTooltip } from '../info-tooltip'
|
import { InfoTooltip } from '../info-tooltip'
|
||||||
import { useUser } from '../../hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import {
|
import {
|
||||||
calculateDpmShares,
|
calculateDpmShares,
|
||||||
calculateDpmPayoutAfterCorrectBet,
|
calculateDpmPayoutAfterCorrectBet,
|
||||||
getDpmOutcomeProbabilityAfterBet,
|
getDpmOutcomeProbabilityAfterBet,
|
||||||
} from '../../../common/calculate-dpm'
|
} from 'common/calculate-dpm'
|
||||||
import { firebaseLogin } from '../../lib/firebase/users'
|
import { firebaseLogin } from 'web/lib/firebase/users'
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { MAX_ANSWER_LENGTH } from '../../../common/answer'
|
import { MAX_ANSWER_LENGTH } from 'common/answer'
|
||||||
|
|
||||||
export function CreateAnswerPanel(props: {
|
export function CreateAnswerPanel(props: {
|
||||||
contract: FullContract<DPM, FreeResponse>
|
contract: FullContract<DPM, FreeResponse>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { useUser } from '../hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { Binary, CPMM, DPM, FullContract } from '../../common/contract'
|
import { Binary, CPMM, DPM, FullContract } from 'common/contract'
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
import { Spacer } from './layout/spacer'
|
import { Spacer } from './layout/spacer'
|
||||||
|
@ -11,11 +11,11 @@ import {
|
||||||
formatMoney,
|
formatMoney,
|
||||||
formatPercent,
|
formatPercent,
|
||||||
formatWithCommas,
|
formatWithCommas,
|
||||||
} from '../../common/util/format'
|
} from 'common/util/format'
|
||||||
import { Title } from './title'
|
import { Title } from './title'
|
||||||
import { firebaseLogin, User } from '../lib/firebase/users'
|
import { firebaseLogin, User } from 'web/lib/firebase/users'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { placeBet, sellShares } from '../lib/firebase/api-call'
|
import { placeBet, sellShares } from 'web/lib/firebase/api-call'
|
||||||
import { BuyAmountInput, SellAmountInput } from './amount-input'
|
import { BuyAmountInput, SellAmountInput } from './amount-input'
|
||||||
import { InfoTooltip } from './info-tooltip'
|
import { InfoTooltip } from './info-tooltip'
|
||||||
import { BinaryOutcomeLabel } from './outcome-label'
|
import { BinaryOutcomeLabel } from './outcome-label'
|
||||||
|
@ -24,13 +24,10 @@ import {
|
||||||
calculateShares,
|
calculateShares,
|
||||||
getProbability,
|
getProbability,
|
||||||
getOutcomeProbabilityAfterBet,
|
getOutcomeProbabilityAfterBet,
|
||||||
} from '../../common/calculate'
|
} from 'common/calculate'
|
||||||
import { useFocus } from '../hooks/use-focus'
|
import { useFocus } from 'web/hooks/use-focus'
|
||||||
import { useUserContractBets } from '../hooks/use-user-bets'
|
import { useUserContractBets } from 'web/hooks/use-user-bets'
|
||||||
import {
|
import { calculateCpmmSale, getCpmmProbability } from 'common/calculate-cpmm'
|
||||||
calculateCpmmSale,
|
|
||||||
getCpmmProbability,
|
|
||||||
} from '../../common/calculate-cpmm'
|
|
||||||
import { SellRow } from './sell-row'
|
import { SellRow } from './sell-row'
|
||||||
import { useSaveShares } from './use-save-shares'
|
import { useSaveShares } from './use-save-shares'
|
||||||
|
|
||||||
|
@ -72,7 +69,7 @@ export function BetPanel(props: {
|
||||||
className="btn flex-1 whitespace-nowrap border-none bg-gradient-to-r from-teal-500 to-green-500 px-10 text-lg font-medium normal-case hover:from-teal-600 hover:to-green-600"
|
className="btn flex-1 whitespace-nowrap border-none bg-gradient-to-r from-teal-500 to-green-500 px-10 text-lg font-medium normal-case hover:from-teal-600 hover:to-green-600"
|
||||||
onClick={firebaseLogin}
|
onClick={firebaseLogin}
|
||||||
>
|
>
|
||||||
Sign up to trade!
|
Sign up to bet!
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -187,7 +184,7 @@ export function BetPanelSwitcher(props: {
|
||||||
className="btn flex-1 whitespace-nowrap border-none bg-gradient-to-r from-teal-500 to-green-500 px-10 text-lg font-medium normal-case hover:from-teal-600 hover:to-green-600"
|
className="btn flex-1 whitespace-nowrap border-none bg-gradient-to-r from-teal-500 to-green-500 px-10 text-lg font-medium normal-case hover:from-teal-600 hover:to-green-600"
|
||||||
onClick={firebaseLogin}
|
onClick={firebaseLogin}
|
||||||
>
|
>
|
||||||
Sign up to trade!
|
Sign up to bet!
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -432,7 +429,13 @@ export function SellPanel(props: {
|
||||||
<SellAmountInput
|
<SellAmountInput
|
||||||
inputClassName="w-full"
|
inputClassName="w-full"
|
||||||
contract={contract}
|
contract={contract}
|
||||||
amount={amount ? Math.floor(amount) : undefined}
|
amount={
|
||||||
|
amount
|
||||||
|
? Math.round(amount) === 0
|
||||||
|
? 0
|
||||||
|
: Math.floor(amount)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
onChange={setAmount}
|
onChange={setAmount}
|
||||||
userBets={userBets}
|
userBets={userBets}
|
||||||
error={error}
|
error={error}
|
||||||
|
|
|
@ -3,11 +3,11 @@ import { useState } from 'react'
|
||||||
import { BetPanelSwitcher } from './bet-panel'
|
import { BetPanelSwitcher } from './bet-panel'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
import { YesNoSelector } from './yes-no-selector'
|
import { YesNoSelector } from './yes-no-selector'
|
||||||
import { Binary, CPMM, DPM, FullContract } from '../../common/contract'
|
import { Binary, CPMM, DPM, FullContract } from 'common/contract'
|
||||||
import { Modal } from './layout/modal'
|
import { Modal } from './layout/modal'
|
||||||
import { SellButton } from './sell-button'
|
import { SellButton } from './sell-button'
|
||||||
import { useUser } from '../hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { useUserContractBets } from '../hooks/use-user-bets'
|
import { useUserContractBets } from 'web/hooks/use-user-bets'
|
||||||
import { useSaveShares } from './use-save-shares'
|
import { useSaveShares } from './use-save-shares'
|
||||||
|
|
||||||
// Inline version of a bet panel. Opens BetPanel in a new modal.
|
// Inline version of a bet panel. Opens BetPanel in a new modal.
|
||||||
|
|
|
@ -4,14 +4,14 @@ import dayjs from 'dayjs'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
|
|
||||||
import { useUserBets } from '../hooks/use-user-bets'
|
import { useUserBets } from 'web/hooks/use-user-bets'
|
||||||
import { Bet } from '../lib/firebase/bets'
|
import { Bet } from 'web/lib/firebase/bets'
|
||||||
import { User } from '../lib/firebase/users'
|
import { User } from 'web/lib/firebase/users'
|
||||||
import {
|
import {
|
||||||
formatMoney,
|
formatMoney,
|
||||||
formatPercent,
|
formatPercent,
|
||||||
formatWithCommas,
|
formatWithCommas,
|
||||||
} from '../../common/util/format'
|
} from 'common/util/format'
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
import { Spacer } from './layout/spacer'
|
import { Spacer } from './layout/spacer'
|
||||||
import {
|
import {
|
||||||
|
@ -19,13 +19,13 @@ import {
|
||||||
getContractFromId,
|
getContractFromId,
|
||||||
contractPath,
|
contractPath,
|
||||||
getBinaryProbPercent,
|
getBinaryProbPercent,
|
||||||
} from '../lib/firebase/contracts'
|
} from 'web/lib/firebase/contracts'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
import { UserLink } from './user-page'
|
import { UserLink } from './user-page'
|
||||||
import { sellBet } from '../lib/firebase/api-call'
|
import { sellBet } from 'web/lib/firebase/api-call'
|
||||||
import { ConfirmationButton } from './confirmation-button'
|
import { ConfirmationButton } from './confirmation-button'
|
||||||
import { OutcomeLabel, YesLabel, NoLabel } from './outcome-label'
|
import { OutcomeLabel, YesLabel, NoLabel } from './outcome-label'
|
||||||
import { filterDefined } from '../../common/util/array'
|
import { filterDefined } from 'common/util/array'
|
||||||
import { LoadingIndicator } from './loading-indicator'
|
import { LoadingIndicator } from './loading-indicator'
|
||||||
import { SiteLink } from './site-link'
|
import { SiteLink } from './site-link'
|
||||||
import {
|
import {
|
||||||
|
@ -36,9 +36,9 @@ import {
|
||||||
getContractBetMetrics,
|
getContractBetMetrics,
|
||||||
resolvedPayout,
|
resolvedPayout,
|
||||||
getContractBetNullMetrics,
|
getContractBetNullMetrics,
|
||||||
} from '../../common/calculate'
|
} from 'common/calculate'
|
||||||
import { useTimeSinceFirstRender } from '../hooks/use-time-since-first-render'
|
import { useTimeSinceFirstRender } from 'web/hooks/use-time-since-first-render'
|
||||||
import { trackLatency } from '../lib/firebase/tracking'
|
import { trackLatency } from 'web/lib/firebase/tracking'
|
||||||
|
|
||||||
type BetSort = 'newest' | 'profit' | 'closeTime' | 'value'
|
type BetSort = 'newest' | 'profit' | 'closeTime' | 'value'
|
||||||
type BetFilter = 'open' | 'closed' | 'resolved' | 'all'
|
type BetFilter = 'open' | 'closed' | 'resolved' | 'all'
|
||||||
|
@ -553,10 +553,11 @@ function BetRow(props: { bet: Bet; contract: Contract; saleBet?: Bet }) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const warmUpSellBet = _.throttle(() => sellBet({}).catch(), 5000 /* ms */)
|
||||||
|
|
||||||
function SellButton(props: { contract: Contract; bet: Bet }) {
|
function SellButton(props: { contract: Contract; bet: Bet }) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// warm up cloud function
|
warmUpSellBet()
|
||||||
sellBet({}).catch()
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const { contract, bet } = props
|
const { contract, bet } = props
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { StarIcon } from '@heroicons/react/solid'
|
import { StarIcon } from '@heroicons/react/solid'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { Charity } from '../../../common/charity'
|
import Image from 'next/image'
|
||||||
import { useCharityTxns } from '../../hooks/use-charity-txns'
|
import { Charity } from 'common/charity'
|
||||||
|
import { useCharityTxns } from 'web/hooks/use-charity-txns'
|
||||||
import { manaToUSD } from '../../pages/charity/[charitySlug]'
|
import { manaToUSD } from '../../pages/charity/[charitySlug]'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
|
|
||||||
|
@ -15,17 +16,18 @@ export function CharityCard(props: { charity: Charity }) {
|
||||||
return (
|
return (
|
||||||
<Link href={`/charity/${slug}`} passHref>
|
<Link href={`/charity/${slug}`} passHref>
|
||||||
<div className="card card-compact transition:shadow flex-1 cursor-pointer border-2 bg-white hover:shadow-md">
|
<div className="card card-compact transition:shadow flex-1 cursor-pointer border-2 bg-white hover:shadow-md">
|
||||||
<Row className="mt-6">
|
<Row className="mt-6 mb-2">
|
||||||
{tags?.includes('Featured') && <FeaturedBadge />}
|
{tags?.includes('Featured') && <FeaturedBadge />}
|
||||||
</Row>
|
</Row>
|
||||||
|
<div className="px-8">
|
||||||
<figure className="h-32 px-4 pt-4">
|
<figure className="relative h-32">
|
||||||
{photo ? (
|
{photo ? (
|
||||||
<img className="h-full w-full object-contain" src={photo} alt="" />
|
<Image src={photo} alt="" layout="fill" objectFit="contain" />
|
||||||
) : (
|
) : (
|
||||||
<div className="h-full w-full bg-gradient-to-r from-slate-300 to-indigo-200" />
|
<div className="h-full w-full bg-gradient-to-r from-slate-300 to-indigo-200" />
|
||||||
)}
|
)}
|
||||||
</figure>
|
</figure>
|
||||||
|
</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
{/* <h3 className="card-title line-clamp-3">{name}</h3> */}
|
{/* <h3 className="card-title line-clamp-3">{name}</h3> */}
|
||||||
<div className="line-clamp-4 text-sm">{preview}</div>
|
<div className="line-clamp-4 text-sm">{preview}</div>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { Txn } from '../../../common/txn'
|
import { Txn } from 'common/txn'
|
||||||
import { Avatar } from '../avatar'
|
import { Avatar } from '../avatar'
|
||||||
import { useUserById } from '../../hooks/use-users'
|
import { useUserById } from 'web/hooks/use-users'
|
||||||
import { UserLink } from '../user-page'
|
import { UserLink } from '../user-page'
|
||||||
import { manaToUSD } from '../../pages/charity/[charitySlug]'
|
import { manaToUSD } from '../../pages/charity/[charitySlug]'
|
||||||
import { RelativeTimestamp } from '../feed/feed-items'
|
import { RelativeTimestamp } from '../relative-timestamp'
|
||||||
|
|
||||||
export function Donation(props: { txn: Txn }) {
|
export function Donation(props: { txn: Txn }) {
|
||||||
const { txn } = props
|
const { txn } = props
|
||||||
|
|
65
web/components/comments-list.tsx
Normal file
65
web/components/comments-list.tsx
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import { Comment } from 'common/comment'
|
||||||
|
import { Contract } from 'common/contract'
|
||||||
|
import { contractPath } from 'web/lib/firebase/contracts'
|
||||||
|
import { SiteLink } from './site-link'
|
||||||
|
import { Row } from './layout/row'
|
||||||
|
import { Avatar } from './avatar'
|
||||||
|
import { RelativeTimestamp } from './relative-timestamp'
|
||||||
|
import { UserLink } from './user-page'
|
||||||
|
import { User } from 'common/user'
|
||||||
|
import { Col } from './layout/col'
|
||||||
|
import { Linkify } from './linkify'
|
||||||
|
|
||||||
|
export function UserCommentsList(props: {
|
||||||
|
user: User
|
||||||
|
commentsByUniqueContracts: Map<Contract, Comment[]>
|
||||||
|
}) {
|
||||||
|
const { commentsByUniqueContracts } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col className={'bg-white'}>
|
||||||
|
{Array.from(commentsByUniqueContracts).map(([contract, comments]) => (
|
||||||
|
<div key={contract.id} className={'border-width-1 border-b p-5'}>
|
||||||
|
<div className={'mb-2 text-sm text-indigo-700'}>
|
||||||
|
<SiteLink href={contractPath(contract)}>
|
||||||
|
{contract.question}
|
||||||
|
</SiteLink>
|
||||||
|
</div>
|
||||||
|
{comments.map((comment) => (
|
||||||
|
<div key={comment.id} className={'relative pb-6'}>
|
||||||
|
<div className="relative flex items-start space-x-3">
|
||||||
|
<ProfileComment comment={comment} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Col>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ProfileComment(props: { comment: Comment }) {
|
||||||
|
const { comment } = props
|
||||||
|
const { text, userUsername, userName, userAvatarUrl, createdTime } = comment
|
||||||
|
// TODO: find and attach relevant bets by comment betId at some point
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Row className={'gap-4'}>
|
||||||
|
<Avatar username={userUsername} avatarUrl={userAvatarUrl} />
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<div>
|
||||||
|
<p className="mt-0.5 text-sm text-gray-500">
|
||||||
|
<UserLink
|
||||||
|
className="text-gray-500"
|
||||||
|
username={userUsername}
|
||||||
|
name={userName}
|
||||||
|
/>{' '}
|
||||||
|
<RelativeTimestamp time={createdTime} />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Linkify text={text} />
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -2,12 +2,12 @@ import clsx from 'clsx'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
import { formatPercent } from '../../../common/util/format'
|
import { formatPercent } from 'common/util/format'
|
||||||
import {
|
import {
|
||||||
Contract,
|
Contract,
|
||||||
contractPath,
|
contractPath,
|
||||||
getBinaryProbPercent,
|
getBinaryProbPercent,
|
||||||
} from '../../lib/firebase/contracts'
|
} from 'web/lib/firebase/contracts'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { Spacer } from '../layout/spacer'
|
import { Spacer } from '../layout/spacer'
|
||||||
import {
|
import {
|
||||||
|
@ -17,13 +17,13 @@ import {
|
||||||
FreeResponse,
|
FreeResponse,
|
||||||
FreeResponseContract,
|
FreeResponseContract,
|
||||||
FullContract,
|
FullContract,
|
||||||
} from '../../../common/contract'
|
} from 'common/contract'
|
||||||
import {
|
import {
|
||||||
AnswerLabel,
|
AnswerLabel,
|
||||||
BinaryContractOutcomeLabel,
|
BinaryContractOutcomeLabel,
|
||||||
FreeResponseOutcomeLabel,
|
FreeResponseOutcomeLabel,
|
||||||
} from '../outcome-label'
|
} from '../outcome-label'
|
||||||
import { getOutcomeProbability, getTopAnswer } from '../../../common/calculate'
|
import { getOutcomeProbability, getTopAnswer } from 'common/calculate'
|
||||||
import { AbbrContractDetails } from './contract-details'
|
import { AbbrContractDetails } from './contract-details'
|
||||||
|
|
||||||
export function ContractCard(props: {
|
export function ContractCard(props: {
|
||||||
|
|
|
@ -3,10 +3,10 @@ import dayjs from 'dayjs'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import Textarea from 'react-expanding-textarea'
|
import Textarea from 'react-expanding-textarea'
|
||||||
|
|
||||||
import { Contract } from '../../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { parseTags } from '../../../common/util/parse'
|
import { parseTags } from 'common/util/parse'
|
||||||
import { useAdmin } from '../../hooks/use-admin'
|
import { useAdmin } from 'web/hooks/use-admin'
|
||||||
import { updateContract } from '../../lib/firebase/contracts'
|
import { updateContract } from 'web/lib/firebase/contracts'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
import { Linkify } from '../linkify'
|
import { Linkify } from '../linkify'
|
||||||
|
|
||||||
|
|
|
@ -3,21 +3,21 @@ import _ from 'lodash'
|
||||||
import { ClockIcon, DatabaseIcon, PencilIcon } from '@heroicons/react/outline'
|
import { ClockIcon, DatabaseIcon, PencilIcon } from '@heroicons/react/outline'
|
||||||
import { TrendingUpIcon } from '@heroicons/react/solid'
|
import { TrendingUpIcon } from '@heroicons/react/solid'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
import { formatMoney } from '../../../common/util/format'
|
import { formatMoney } from 'common/util/format'
|
||||||
import { UserLink } from '../user-page'
|
import { UserLink } from '../user-page'
|
||||||
import {
|
import {
|
||||||
Contract,
|
Contract,
|
||||||
contractMetrics,
|
contractMetrics,
|
||||||
updateContract,
|
updateContract,
|
||||||
} from '../../lib/firebase/contracts'
|
} from 'web/lib/firebase/contracts'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { DateTimeTooltip } from '../datetime-tooltip'
|
import { DateTimeTooltip } from '../datetime-tooltip'
|
||||||
import { fromNow } from '../../lib/util/time'
|
import { fromNow } from 'web/lib/util/time'
|
||||||
import { Avatar } from '../avatar'
|
import { Avatar } from '../avatar'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { ContractInfoDialog } from './contract-info-dialog'
|
import { ContractInfoDialog } from './contract-info-dialog'
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import NewContractBadge from '../new-contract-badge'
|
import NewContractBadge from '../new-contract-badge'
|
||||||
|
|
||||||
export function AbbrContractDetails(props: {
|
export function AbbrContractDetails(props: {
|
||||||
|
|
|
@ -3,14 +3,11 @@ import clsx from 'clsx'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
|
|
||||||
import { Contract } from '../../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { formatMoney } from '../../../common/util/format'
|
import { formatMoney } from 'common/util/format'
|
||||||
import {
|
import { contractPath, getBinaryProbPercent } from 'web/lib/firebase/contracts'
|
||||||
contractPath,
|
|
||||||
getBinaryProbPercent,
|
|
||||||
} from '../../lib/firebase/contracts'
|
|
||||||
import { AddLiquidityPanel } from '../add-liquidity-panel'
|
import { AddLiquidityPanel } from '../add-liquidity-panel'
|
||||||
import { CopyLinkButton } from '../copy-link-button'
|
import { CopyLinkButton } from '../copy-link-button'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Contract, tradingAllowed } from '../../lib/firebase/contracts'
|
import { Contract, tradingAllowed } from 'web/lib/firebase/contracts'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { Spacer } from '../layout/spacer'
|
import { Spacer } from '../layout/spacer'
|
||||||
import { ContractProbGraph } from './contract-prob-graph'
|
import { ContractProbGraph } from './contract-prob-graph'
|
||||||
import { useUser } from '../../hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
import { Linkify } from '../linkify'
|
import { Linkify } from '../linkify'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
|
@ -10,11 +10,11 @@ import {
|
||||||
FreeResponseResolutionOrChance,
|
FreeResponseResolutionOrChance,
|
||||||
BinaryResolutionOrChance,
|
BinaryResolutionOrChance,
|
||||||
} from './contract-card'
|
} from './contract-card'
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { Comment } from '../../../common/comment'
|
import { Comment } from 'common/comment'
|
||||||
import BetRow from '../bet-row'
|
import BetRow from '../bet-row'
|
||||||
import { AnswersGraph } from '../answers/answers-graph'
|
import { AnswersGraph } from '../answers/answers-graph'
|
||||||
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
|
import { DPM, FreeResponse, FullContract } from 'common/contract'
|
||||||
import { ContractDescription } from './contract-description'
|
import { ContractDescription } from './contract-description'
|
||||||
import { ContractDetails } from './contract-details'
|
import { ContractDetails } from './contract-details'
|
||||||
import { ShareMarket } from '../share-market'
|
import { ShareMarket } from '../share-market'
|
||||||
|
|
|
@ -2,11 +2,11 @@ import { DatumValue } from '@nivo/core'
|
||||||
import { ResponsiveLine } from '@nivo/line'
|
import { ResponsiveLine } from '@nivo/line'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { getInitialProbability } from '../../../common/calculate'
|
import { getInitialProbability } from 'common/calculate'
|
||||||
import { Binary, CPMM, DPM, FullContract } from '../../../common/contract'
|
import { Binary, CPMM, DPM, FullContract } from 'common/contract'
|
||||||
import { useBetsWithoutAntes } from '../../hooks/use-bets'
|
import { useBetsWithoutAntes } from 'web/hooks/use-bets'
|
||||||
import { useWindowSize } from '../../hooks/use-window-size'
|
import { useWindowSize } from 'web/hooks/use-window-size'
|
||||||
|
|
||||||
export const ContractProbGraph = memo(function ContractProbGraph(props: {
|
export const ContractProbGraph = memo(function ContractProbGraph(props: {
|
||||||
contract: FullContract<DPM | CPMM, Binary>
|
contract: FullContract<DPM | CPMM, Binary>
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { Contract } from '../../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { Comment } from '../../lib/firebase/comments'
|
import { Comment } from 'web/lib/firebase/comments'
|
||||||
import { User } from '../../../common/user'
|
import { User } from 'common/user'
|
||||||
import { useBets } from '../../hooks/use-bets'
|
import { useBets } from 'web/hooks/use-bets'
|
||||||
import { ContractActivity } from '../feed/contract-activity'
|
import { ContractActivity } from '../feed/contract-activity'
|
||||||
import { ContractBetsTable, MyBetsSummary } from '../bets-list'
|
import { ContractBetsTable, MyBetsSummary } from '../bets-list'
|
||||||
import { Spacer } from '../layout/spacer'
|
import { Spacer } from '../layout/spacer'
|
||||||
import { Tabs } from '../layout/tabs'
|
import { Tabs } from '../layout/tabs'
|
||||||
|
import { Col } from '../layout/col'
|
||||||
|
|
||||||
export function ContractTabs(props: {
|
export function ContractTabs(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
|
@ -33,14 +34,34 @@ export function ContractTabs(props: {
|
||||||
)
|
)
|
||||||
|
|
||||||
const commentActivity = (
|
const commentActivity = (
|
||||||
|
<>
|
||||||
<ContractActivity
|
<ContractActivity
|
||||||
contract={contract}
|
contract={contract}
|
||||||
bets={bets}
|
bets={bets}
|
||||||
comments={comments}
|
comments={comments}
|
||||||
user={user}
|
user={user}
|
||||||
mode="comments"
|
mode={
|
||||||
|
contract.outcomeType === 'FREE_RESPONSE'
|
||||||
|
? 'free-response-comment-answer-groups'
|
||||||
|
: 'comments'
|
||||||
|
}
|
||||||
betRowClassName="!mt-0 xl:hidden"
|
betRowClassName="!mt-0 xl:hidden"
|
||||||
/>
|
/>
|
||||||
|
{contract.outcomeType === 'FREE_RESPONSE' && (
|
||||||
|
<Col className={'mt-8 flex w-full '}>
|
||||||
|
<div className={'text-md mt-8 mb-2 text-left'}>General Comments</div>
|
||||||
|
<div className={'mb-4 w-full border-b border-gray-200'} />
|
||||||
|
<ContractActivity
|
||||||
|
contract={contract}
|
||||||
|
bets={bets}
|
||||||
|
comments={comments}
|
||||||
|
user={user}
|
||||||
|
mode={'comments'}
|
||||||
|
betRowClassName="!mt-0 xl:hidden"
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
const yourTrades = (
|
const yourTrades = (
|
||||||
|
|
|
@ -2,10 +2,10 @@ import { Fragment } from 'react'
|
||||||
import { LinkIcon } from '@heroicons/react/outline'
|
import { LinkIcon } from '@heroicons/react/outline'
|
||||||
import { Menu, Transition } from '@headlessui/react'
|
import { Menu, Transition } from '@headlessui/react'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { copyToClipboard } from '../lib/util/copy'
|
import { copyToClipboard } from 'web/lib/util/copy'
|
||||||
import { contractPath } from '../lib/firebase/contracts'
|
import { contractPath } from 'web/lib/firebase/contracts'
|
||||||
import { ENV_CONFIG } from '../../common/envs/constants'
|
import { ENV_CONFIG } from 'common/envs/constants'
|
||||||
|
|
||||||
function copyContractUrl(contract: Contract) {
|
function copyContractUrl(contract: Contract) {
|
||||||
copyToClipboard(`https://${ENV_CONFIG.domain}${contractPath(contract)}`)
|
copyToClipboard(`https://${ENV_CONFIG.domain}${contractPath(contract)}`)
|
||||||
|
|
|
@ -4,54 +4,49 @@ import { Avatar } from './avatar'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { Spacer } from './layout/spacer'
|
import { Spacer } from './layout/spacer'
|
||||||
import { NewContract } from '../pages/create'
|
import { NewContract } from '../pages/create'
|
||||||
import { firebaseLogin, User } from '../lib/firebase/users'
|
import { firebaseLogin, User } from 'web/lib/firebase/users'
|
||||||
import { ContractsGrid } from './contract/contracts-list'
|
import { ContractsGrid } from './contract/contracts-list'
|
||||||
import { Contract, MAX_QUESTION_LENGTH } from '../../common/contract'
|
import { Contract, MAX_QUESTION_LENGTH } from 'common/contract'
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
import { ENV_CONFIG } from '../../common/envs/constants'
|
import { ENV_CONFIG } from '../../common/envs/constants'
|
||||||
|
import { SiteLink } from './site-link'
|
||||||
|
|
||||||
export function FeedPromo(props: { hotContracts: Contract[] }) {
|
export function FeedPromo(props: { hotContracts: Contract[] }) {
|
||||||
const { hotContracts } = props
|
const { hotContracts } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Col className="my-6 rounded-xl bg-white py-12 text-center sm:m-12">
|
<Col className="my-6 rounded-xl text-center sm:m-12">
|
||||||
<h1 className="text-4xl sm:text-6xl xl:text-6xl">
|
<h1 className="text-4xl sm:text-6xl xl:text-6xl">
|
||||||
<div className="font-semibold sm:mb-2">
|
<div className="font-semibold sm:mb-2">A market for</div>
|
||||||
A{' '}
|
|
||||||
<span className="bg-gradient-to-r from-teal-400 to-green-400 bg-clip-text font-bold text-transparent">
|
<span className="bg-gradient-to-r from-teal-400 to-green-400 bg-clip-text font-bold text-transparent">
|
||||||
market{' '}
|
every question
|
||||||
</span>
|
</span>
|
||||||
for
|
|
||||||
</div>
|
|
||||||
<div className="font-semibold">
|
|
||||||
every{' '}
|
|
||||||
<span className="bg-gradient-to-r from-teal-400 to-green-400 bg-clip-text font-bold text-transparent">
|
|
||||||
question
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</h1>
|
</h1>
|
||||||
<Spacer h={6} />
|
<Spacer h={6} />
|
||||||
<div className="mb-4 text-gray-500">
|
<div className="mb-4 px-2 text-gray-500">
|
||||||
Find markets on any topic imaginable. Or create your own!
|
Bet on any topic imaginable. Or create your own market!
|
||||||
<br />
|
<br />
|
||||||
Sign up to get M$ 1,000 and start trading.
|
Sign up and get M$1,000 - worth $10 to your{' '}
|
||||||
|
<SiteLink className="font-semibold" href="/charity">
|
||||||
|
favorite charity.
|
||||||
|
</SiteLink>
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
<Spacer h={6} />
|
<Spacer h={6} />
|
||||||
<button
|
<button
|
||||||
className="btn btn-lg self-center border-none bg-gradient-to-r from-teal-500 to-green-500 normal-case hover:from-teal-600 hover:to-green-600"
|
className="self-center rounded-md border-none bg-gradient-to-r from-teal-500 to-green-500 py-4 px-6 text-lg font-semibold normal-case text-white hover:from-teal-600 hover:to-green-600"
|
||||||
onClick={firebaseLogin}
|
onClick={firebaseLogin}
|
||||||
>
|
>
|
||||||
Sign up for free
|
Start betting now
|
||||||
</button>{' '}
|
</button>{' '}
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Row className="m-4 mb-6 items-center gap-1 text-xl font-semibold text-gray-800">
|
<Row className="m-4 mb-6 items-center gap-1 text-xl font-semibold text-gray-800">
|
||||||
<SparklesIcon className="inline h-5 w-5" aria-hidden="true" />
|
<SparklesIcon className="inline h-5 w-5" aria-hidden="true" />
|
||||||
Trending today
|
Trending markets
|
||||||
</Row>
|
</Row>
|
||||||
<ContractsGrid
|
<ContractsGrid
|
||||||
contracts={hotContracts?.slice(0, 10) || []}
|
contracts={hotContracts?.slice(0, 10) || []}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
import { Contract } from '../../lib/firebase/contracts'
|
import { Contract } from 'web/lib/firebase/contracts'
|
||||||
import { Comment } from '../../lib/firebase/comments'
|
import { Comment } from 'web/lib/firebase/comments'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { useUser } from '../../hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { ContractActivity } from './contract-activity'
|
import { ContractActivity } from './contract-activity'
|
||||||
|
|
||||||
export function ActivityFeed(props: {
|
export function ActivityFeed(props: {
|
||||||
|
|
|
@ -1,17 +1,12 @@
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
import { Answer } from '../../../common/answer'
|
import { Answer } from 'common/answer'
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { getOutcomeProbability } from '../../../common/calculate'
|
import { getOutcomeProbability } from 'common/calculate'
|
||||||
import { Comment } from '../../../common/comment'
|
import { Comment } from 'common/comment'
|
||||||
import {
|
import { Contract, DPM, FreeResponse, FullContract } from 'common/contract'
|
||||||
Contract,
|
import { User } from 'common/user'
|
||||||
DPM,
|
import { mapCommentsByBetId } from 'web/lib/firebase/comments'
|
||||||
FreeResponse,
|
|
||||||
FullContract,
|
|
||||||
} from '../../../common/contract'
|
|
||||||
import { User } from '../../../common/user'
|
|
||||||
import { mapCommentsByBetId } from '../../lib/firebase/comments'
|
|
||||||
|
|
||||||
export type ActivityItem =
|
export type ActivityItem =
|
||||||
| DescriptionItem
|
| DescriptionItem
|
||||||
|
@ -33,6 +28,7 @@ export type CommentInputItem = BaseActivityItem & {
|
||||||
type: 'commentInput'
|
type: 'commentInput'
|
||||||
betsByCurrentUser: Bet[]
|
betsByCurrentUser: Bet[]
|
||||||
comments: Comment[]
|
comments: Comment[]
|
||||||
|
answerOutcome?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DescriptionItem = BaseActivityItem & {
|
export type DescriptionItem = BaseActivityItem & {
|
||||||
|
@ -82,6 +78,7 @@ export type ResolveItem = BaseActivityItem & {
|
||||||
type: 'resolve'
|
type: 'resolve'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const GENERAL_COMMENTS_OUTCOME_ID = 'General Comments'
|
||||||
const DAY_IN_MS = 24 * 60 * 60 * 1000
|
const DAY_IN_MS = 24 * 60 * 60 * 1000
|
||||||
const ABBREVIATED_NUM_COMMENTS_OR_BETS_TO_SHOW = 3
|
const ABBREVIATED_NUM_COMMENTS_OR_BETS_TO_SHOW = 3
|
||||||
|
|
||||||
|
@ -263,6 +260,68 @@ function getAnswerGroups(
|
||||||
return answerGroups
|
return answerGroups
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAnswerAndCommentInputGroups(
|
||||||
|
contract: FullContract<DPM, FreeResponse>,
|
||||||
|
bets: Bet[],
|
||||||
|
comments: Comment[],
|
||||||
|
user: User | undefined | null
|
||||||
|
) {
|
||||||
|
let outcomes = _.uniq(bets.map((bet) => bet.outcome)).filter(
|
||||||
|
(outcome) => getOutcomeProbability(contract, outcome) > 0.0001
|
||||||
|
)
|
||||||
|
outcomes = _.sortBy(outcomes, (outcome) =>
|
||||||
|
getOutcomeProbability(contract, outcome)
|
||||||
|
)
|
||||||
|
|
||||||
|
function collateCommentsSectionForOutcome(outcome: string) {
|
||||||
|
const answerBets = bets.filter((bet) => bet.outcome === outcome)
|
||||||
|
const answerComments = comments.filter(
|
||||||
|
(comment) =>
|
||||||
|
comment.answerOutcome === outcome ||
|
||||||
|
answerBets.some((bet) => bet.id === comment.betId)
|
||||||
|
)
|
||||||
|
let items = []
|
||||||
|
items.push({
|
||||||
|
type: 'commentInput' as const,
|
||||||
|
id: 'commentInputFor' + outcome,
|
||||||
|
contract,
|
||||||
|
betsByCurrentUser: user
|
||||||
|
? bets.filter((bet) => bet.userId === user.id)
|
||||||
|
: [],
|
||||||
|
comments: comments,
|
||||||
|
answerOutcome: outcome,
|
||||||
|
})
|
||||||
|
items.push(
|
||||||
|
...getCommentsWithPositions(
|
||||||
|
answerBets,
|
||||||
|
answerComments,
|
||||||
|
contract
|
||||||
|
).reverse()
|
||||||
|
)
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
const answerGroups = outcomes
|
||||||
|
.map((outcome) => {
|
||||||
|
const answer = contract.answers?.find(
|
||||||
|
(answer) => answer.id === outcome
|
||||||
|
) as Answer
|
||||||
|
|
||||||
|
const items = collateCommentsSectionForOutcome(outcome)
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: outcome,
|
||||||
|
type: 'answergroup' as const,
|
||||||
|
contract,
|
||||||
|
answer,
|
||||||
|
items,
|
||||||
|
user,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((group) => group.answer) as ActivityItem[]
|
||||||
|
return answerGroups
|
||||||
|
}
|
||||||
|
|
||||||
function groupBetsAndComments(
|
function groupBetsAndComments(
|
||||||
bets: Bet[],
|
bets: Bet[],
|
||||||
comments: Comment[],
|
comments: Comment[],
|
||||||
|
@ -382,7 +441,7 @@ export function getAllContractActivityItems(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
items.push({
|
items.push({
|
||||||
type: 'commentInput',
|
type: 'commentInput' as const,
|
||||||
id: 'commentInput',
|
id: 'commentInput',
|
||||||
contract,
|
contract,
|
||||||
betsByCurrentUser: [],
|
betsByCurrentUser: [],
|
||||||
|
@ -408,7 +467,7 @@ export function getAllContractActivityItems(
|
||||||
|
|
||||||
if (outcomeType === 'BINARY') {
|
if (outcomeType === 'BINARY') {
|
||||||
items.push({
|
items.push({
|
||||||
type: 'commentInput',
|
type: 'commentInput' as const,
|
||||||
id: 'commentInput',
|
id: 'commentInput',
|
||||||
contract,
|
contract,
|
||||||
betsByCurrentUser: [],
|
betsByCurrentUser: [],
|
||||||
|
@ -479,7 +538,7 @@ export function getSpecificContractActivityItems(
|
||||||
comments: Comment[],
|
comments: Comment[],
|
||||||
user: User | null | undefined,
|
user: User | null | undefined,
|
||||||
options: {
|
options: {
|
||||||
mode: 'comments' | 'bets'
|
mode: 'comments' | 'bets' | 'free-response-comment-answer-groups'
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const { mode } = options
|
const { mode } = options
|
||||||
|
@ -501,18 +560,39 @@ export function getSpecificContractActivityItems(
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'comments':
|
case 'comments':
|
||||||
items.push(...getCommentsWithPositions(bets, comments, contract))
|
const nonFreeResponseComments = comments.filter(
|
||||||
|
(comment) => comment.answerOutcome === undefined
|
||||||
|
)
|
||||||
|
const nonFreeResponseBets =
|
||||||
|
contract.outcomeType === 'FREE_RESPONSE' ? [] : bets
|
||||||
|
items.push(
|
||||||
|
...getCommentsWithPositions(
|
||||||
|
nonFreeResponseBets,
|
||||||
|
nonFreeResponseComments,
|
||||||
|
contract
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
type: 'commentInput',
|
type: 'commentInput',
|
||||||
id: 'commentInput',
|
id: 'commentInput',
|
||||||
contract,
|
contract,
|
||||||
betsByCurrentUser: user
|
betsByCurrentUser: user
|
||||||
? bets.filter((bet) => bet.userId === user.id)
|
? nonFreeResponseBets.filter((bet) => bet.userId === user.id)
|
||||||
: [],
|
: [],
|
||||||
comments: comments,
|
comments: nonFreeResponseComments,
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
|
case 'free-response-comment-answer-groups':
|
||||||
|
items.push(
|
||||||
|
...getAnswerAndCommentInputGroups(
|
||||||
|
contract as FullContract<DPM, FreeResponse>,
|
||||||
|
bets,
|
||||||
|
comments,
|
||||||
|
user
|
||||||
|
)
|
||||||
|
)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return items.reverse()
|
return items.reverse()
|
||||||
|
|
|
@ -1,22 +1,28 @@
|
||||||
import { Contract } from '../../lib/firebase/contracts'
|
import { Contract } from 'web/lib/firebase/contracts'
|
||||||
import { Comment } from '../../lib/firebase/comments'
|
import { Comment } from 'web/lib/firebase/comments'
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { useBets } from '../../hooks/use-bets'
|
import { useBets } from 'web/hooks/use-bets'
|
||||||
import { useComments } from '../../hooks/use-comments'
|
import { useComments } from 'web/hooks/use-comments'
|
||||||
import {
|
import {
|
||||||
getAllContractActivityItems,
|
getAllContractActivityItems,
|
||||||
getRecentContractActivityItems,
|
getRecentContractActivityItems,
|
||||||
getSpecificContractActivityItems,
|
getSpecificContractActivityItems,
|
||||||
} from './activity-items'
|
} from './activity-items'
|
||||||
import { FeedItems } from './feed-items'
|
import { FeedItems } from './feed-items'
|
||||||
import { User } from '../../../common/user'
|
import { User } from 'common/user'
|
||||||
|
|
||||||
export function ContractActivity(props: {
|
export function ContractActivity(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
bets: Bet[]
|
bets: Bet[]
|
||||||
comments: Comment[]
|
comments: Comment[]
|
||||||
user: User | null | undefined
|
user: User | null | undefined
|
||||||
mode: 'only-recent' | 'abbreviated' | 'all' | 'comments' | 'bets'
|
mode:
|
||||||
|
| 'only-recent'
|
||||||
|
| 'abbreviated'
|
||||||
|
| 'all'
|
||||||
|
| 'comments'
|
||||||
|
| 'bets'
|
||||||
|
| 'free-response-comment-answer-groups'
|
||||||
contractPath?: string
|
contractPath?: string
|
||||||
className?: string
|
className?: string
|
||||||
betRowClassName?: string
|
betRowClassName?: string
|
||||||
|
@ -38,7 +44,9 @@ export function ContractActivity(props: {
|
||||||
? getRecentContractActivityItems(contract, bets, comments, user, {
|
? getRecentContractActivityItems(contract, bets, comments, user, {
|
||||||
contractPath,
|
contractPath,
|
||||||
})
|
})
|
||||||
: mode === 'comments' || mode === 'bets'
|
: mode === 'comments' ||
|
||||||
|
mode === 'bets' ||
|
||||||
|
mode === 'free-response-comment-answer-groups'
|
||||||
? getSpecificContractActivityItems(contract, bets, comments, user, {
|
? getSpecificContractActivityItems(contract, bets, comments, user, {
|
||||||
mode,
|
mode,
|
||||||
})
|
})
|
||||||
|
|
|
@ -19,43 +19,38 @@ import {
|
||||||
Contract,
|
Contract,
|
||||||
contractPath,
|
contractPath,
|
||||||
tradingAllowed,
|
tradingAllowed,
|
||||||
} from '../../lib/firebase/contracts'
|
} from 'web/lib/firebase/contracts'
|
||||||
import { useUser } from '../../hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { Linkify } from '../linkify'
|
import { Linkify } from '../linkify'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
import { createComment, MAX_COMMENT_LENGTH } from '../../lib/firebase/comments'
|
import { createComment, MAX_COMMENT_LENGTH } from 'web/lib/firebase/comments'
|
||||||
import { formatMoney, formatPercent } from '../../../common/util/format'
|
import { formatMoney, formatPercent } from 'common/util/format'
|
||||||
import { Comment } from '../../../common/comment'
|
import { Comment } from 'common/comment'
|
||||||
import { BinaryResolutionOrChance } from '../contract/contract-card'
|
import { BinaryResolutionOrChance } from '../contract/contract-card'
|
||||||
import { SiteLink } from '../site-link'
|
import { SiteLink } from '../site-link'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { UserLink } from '../user-page'
|
import { UserLink } from '../user-page'
|
||||||
import { DateTimeTooltip } from '../datetime-tooltip'
|
import { DateTimeTooltip } from '../datetime-tooltip'
|
||||||
import { Bet } from '../../lib/firebase/bets'
|
import { Bet } from 'web/lib/firebase/bets'
|
||||||
import { JoinSpans } from '../join-spans'
|
import { JoinSpans } from '../join-spans'
|
||||||
import { fromNow } from '../../lib/util/time'
|
import { fromNow } from 'web/lib/util/time'
|
||||||
import BetRow from '../bet-row'
|
import BetRow from '../bet-row'
|
||||||
import { Avatar } from '../avatar'
|
import { Avatar } from '../avatar'
|
||||||
import { Answer } from '../../../common/answer'
|
import { Answer } from 'common/answer'
|
||||||
import { ActivityItem } from './activity-items'
|
import { ActivityItem, GENERAL_COMMENTS_OUTCOME_ID } from './activity-items'
|
||||||
import {
|
import { Binary, CPMM, DPM, FreeResponse, FullContract } from 'common/contract'
|
||||||
Binary,
|
|
||||||
CPMM,
|
|
||||||
DPM,
|
|
||||||
FreeResponse,
|
|
||||||
FullContract,
|
|
||||||
} from '../../../common/contract'
|
|
||||||
import { BuyButton } from '../yes-no-selector'
|
import { BuyButton } from '../yes-no-selector'
|
||||||
import { getDpmOutcomeProbability } from '../../../common/calculate-dpm'
|
import { getDpmOutcomeProbability } from 'common/calculate-dpm'
|
||||||
import { AnswerBetPanel } from '../answers/answer-bet-panel'
|
import { AnswerBetPanel } from '../answers/answer-bet-panel'
|
||||||
import { useSaveSeenContract } from '../../hooks/use-seen-contracts'
|
import { useSaveSeenContract } from 'web/hooks/use-seen-contracts'
|
||||||
import { User } from '../../../common/user'
|
import { User } from 'common/user'
|
||||||
import { Modal } from '../layout/modal'
|
import { Modal } from '../layout/modal'
|
||||||
import { trackClick } from '../../lib/firebase/tracking'
|
import { trackClick } from 'web/lib/firebase/tracking'
|
||||||
import { firebaseLogin } from '../../lib/firebase/users'
|
import { firebaseLogin } from 'web/lib/firebase/users'
|
||||||
import { DAY_MS } from '../../../common/util/time'
|
import { DAY_MS } from 'common/util/time'
|
||||||
import NewContractBadge from '../new-contract-badge'
|
import NewContractBadge from '../new-contract-badge'
|
||||||
import { calculateCpmmSale } from '../../../common/calculate-cpmm'
|
import { RelativeTimestamp } from '../relative-timestamp'
|
||||||
|
import { calculateCpmmSale } from 'common/calculate-cpmm'
|
||||||
|
|
||||||
export function FeedItems(props: {
|
export function FeedItems(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
|
@ -222,52 +217,64 @@ export function CommentInput(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
betsByCurrentUser: Bet[]
|
betsByCurrentUser: Bet[]
|
||||||
comments: Comment[]
|
comments: Comment[]
|
||||||
|
// Only for free response comment inputs
|
||||||
|
answerOutcome?: string
|
||||||
}) {
|
}) {
|
||||||
const { contract, betsByCurrentUser, comments } = props
|
const { contract, betsByCurrentUser, comments, answerOutcome } = props
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
const [comment, setComment] = useState('')
|
const [comment, setComment] = useState('')
|
||||||
|
const [focused, setFocused] = useState(false)
|
||||||
async function submitComment() {
|
|
||||||
if (!comment) return
|
|
||||||
if (!user) {
|
|
||||||
return await firebaseLogin()
|
|
||||||
}
|
|
||||||
await createComment(contract.id, comment, user)
|
|
||||||
setComment('')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should this be oldest bet or most recent bet?
|
// Should this be oldest bet or most recent bet?
|
||||||
const mostRecentCommentableBet = betsByCurrentUser
|
const mostRecentCommentableBet = betsByCurrentUser
|
||||||
.filter(
|
.filter((bet) => {
|
||||||
(bet) =>
|
if (
|
||||||
canCommentOnBet(bet.userId, bet.createdTime, user) &&
|
canCommentOnBet(bet, user) &&
|
||||||
|
// The bet doesn't already have a comment
|
||||||
!comments.some((comment) => comment.betId == bet.id)
|
!comments.some((comment) => comment.betId == bet.id)
|
||||||
|
) {
|
||||||
|
if (!answerOutcome) return true
|
||||||
|
// If we're in free response, don't allow commenting on ante bet
|
||||||
|
return (
|
||||||
|
bet.outcome !== GENERAL_COMMENTS_OUTCOME_ID &&
|
||||||
|
answerOutcome === bet.outcome
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
.sort((b1, b2) => b1.createdTime - b2.createdTime)
|
.sort((b1, b2) => b1.createdTime - b2.createdTime)
|
||||||
.pop()
|
.pop()
|
||||||
|
|
||||||
if (mostRecentCommentableBet) {
|
const { id } = mostRecentCommentableBet || { id: undefined }
|
||||||
return (
|
|
||||||
<FeedBet
|
async function submitComment(betId: string | undefined) {
|
||||||
contract={contract}
|
if (!user) {
|
||||||
bet={mostRecentCommentableBet}
|
return await firebaseLogin()
|
||||||
hideOutcome={false}
|
|
||||||
smallAvatar={false}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
if (!comment) return
|
||||||
|
await createComment(contract.id, comment, user, betId, answerOutcome)
|
||||||
|
setComment('')
|
||||||
|
}
|
||||||
|
|
||||||
const { userPosition, userPositionMoney, yesFloorShares, noFloorShares } =
|
const { userPosition, userPositionMoney, yesFloorShares, noFloorShares } =
|
||||||
getBettorsPosition(contract, Date.now(), betsByCurrentUser)
|
getBettorsPosition(contract, Date.now(), betsByCurrentUser)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Row className={'flex w-full gap-2 pt-3'}>
|
<Row className={'flex w-full gap-2'}>
|
||||||
<div>
|
<div>
|
||||||
<Avatar avatarUrl={user?.avatarUrl} username={user?.username} />
|
<Avatar avatarUrl={user?.avatarUrl} username={user?.username} />
|
||||||
</div>
|
</div>
|
||||||
<div className={'min-w-0 flex-1 py-1.5'}>
|
<div className={'min-w-0 flex-1'}>
|
||||||
<div className="text-sm text-gray-500">
|
<div className="text-sm text-gray-500">
|
||||||
{user && userPosition > 0 && (
|
{mostRecentCommentableBet && (
|
||||||
|
<BetStatusText
|
||||||
|
contract={contract}
|
||||||
|
bet={mostRecentCommentableBet}
|
||||||
|
isSelf={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!mostRecentCommentableBet && user && userPosition > 0 && (
|
||||||
<>
|
<>
|
||||||
{'You have ' + userPositionMoney + ' '}
|
{'You have ' + userPositionMoney + ' '}
|
||||||
<>
|
<>
|
||||||
|
@ -280,47 +287,71 @@ export function CommentInput(props: {
|
||||||
</>
|
</>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{(answerOutcome === undefined || focused) && (
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<Textarea
|
<Textarea
|
||||||
value={comment}
|
value={comment}
|
||||||
onChange={(e) => setComment(e.target.value)}
|
onChange={(e) => setComment(e.target.value)}
|
||||||
className="textarea textarea-bordered w-full resize-none"
|
className="textarea textarea-bordered w-full resize-none"
|
||||||
placeholder="Add a comment..."
|
placeholder="Add a comment..."
|
||||||
rows={3}
|
autoFocus={focused}
|
||||||
|
rows={answerOutcome == undefined || focused ? 3 : 1}
|
||||||
|
onFocus={() => setFocused(true)}
|
||||||
|
onBlur={() => !comment && setFocused(false)}
|
||||||
maxLength={MAX_COMMENT_LENGTH}
|
maxLength={MAX_COMMENT_LENGTH}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
||||||
submitComment()
|
submitComment(id)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{!user && (
|
||||||
<button
|
<button
|
||||||
className={
|
className={
|
||||||
'btn btn-outline btn-sm text-transform: mt-1 capitalize'
|
'btn btn-outline btn-sm text-transform: mt-1 capitalize'
|
||||||
}
|
}
|
||||||
onClick={submitComment}
|
onClick={() => submitComment(id)}
|
||||||
>
|
>
|
||||||
{user ? 'Comment' : 'Sign in to comment'}
|
Sign in to Comment
|
||||||
</button>
|
</button>
|
||||||
</div>
|
)}
|
||||||
</div>
|
{user && answerOutcome === undefined && (
|
||||||
|
<button
|
||||||
|
className={
|
||||||
|
'btn btn-outline btn-sm text-transform: mt-1 capitalize'
|
||||||
|
}
|
||||||
|
onClick={() => submitComment(id)}
|
||||||
|
>
|
||||||
|
Comment
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{user && answerOutcome !== undefined && (
|
||||||
|
<button
|
||||||
|
className={
|
||||||
|
focused
|
||||||
|
? 'btn btn-outline btn-sm text-transform: mt-1 capitalize'
|
||||||
|
: 'btn btn-ghost btn-sm text-transform: mt-1 capitalize'
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
if (!focused) setFocused(true)
|
||||||
|
else {
|
||||||
|
submitComment(id)
|
||||||
|
setFocused(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{!focused ? 'Add Comment' : 'Comment'}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Row>
|
</Row>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RelativeTimestamp(props: { time: number }) {
|
|
||||||
const { time } = props
|
|
||||||
return (
|
|
||||||
<DateTimeTooltip time={time}>
|
|
||||||
<span className="ml-1 whitespace-nowrap text-gray-400">
|
|
||||||
{fromNow(time)}
|
|
||||||
</span>
|
|
||||||
</DateTimeTooltip>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBettorsPosition(
|
function getBettorsPosition(
|
||||||
contract: Contract,
|
contract: Contract,
|
||||||
createdTime: number,
|
createdTime: number,
|
||||||
|
@ -372,24 +403,12 @@ export function FeedBet(props: {
|
||||||
bet: Bet
|
bet: Bet
|
||||||
hideOutcome: boolean
|
hideOutcome: boolean
|
||||||
smallAvatar: boolean
|
smallAvatar: boolean
|
||||||
hideComment?: boolean
|
|
||||||
bettor?: User // If set: reveal bettor identity
|
bettor?: User // If set: reveal bettor identity
|
||||||
}) {
|
}) {
|
||||||
const { contract, bet, hideOutcome, smallAvatar, bettor, hideComment } = props
|
const { contract, bet, hideOutcome, smallAvatar, bettor } = props
|
||||||
const { id, amount, outcome, createdTime, userId } = bet
|
const { userId } = bet
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
const isSelf = user?.id === userId
|
const isSelf = user?.id === userId
|
||||||
const canComment = canCommentOnBet(userId, createdTime, user) && !hideComment
|
|
||||||
|
|
||||||
const [comment, setComment] = useState('')
|
|
||||||
async function submitComment() {
|
|
||||||
if (!user || !comment || !canComment) return
|
|
||||||
await createComment(contract.id, comment, user, id)
|
|
||||||
setComment('')
|
|
||||||
}
|
|
||||||
|
|
||||||
const bought = amount >= 0 ? 'bought' : 'sold'
|
|
||||||
const money = formatMoney(Math.abs(amount))
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -421,9 +440,36 @@ export function FeedBet(props: {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={'min-w-0 flex-1 py-1.5'}>
|
<div className={'min-w-0 flex-1 py-1.5'}>
|
||||||
|
<BetStatusText
|
||||||
|
bet={bet}
|
||||||
|
contract={contract}
|
||||||
|
isSelf={isSelf}
|
||||||
|
hideOutcome={hideOutcome}
|
||||||
|
bettor={bettor}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function BetStatusText(props: {
|
||||||
|
contract: Contract
|
||||||
|
bet: Bet
|
||||||
|
isSelf: boolean
|
||||||
|
hideOutcome?: boolean
|
||||||
|
bettor?: User
|
||||||
|
}) {
|
||||||
|
const { bet, contract, hideOutcome, bettor, isSelf } = props
|
||||||
|
const { amount, outcome, createdTime } = bet
|
||||||
|
|
||||||
|
const bought = amount >= 0 ? 'bought' : 'sold'
|
||||||
|
const money = formatMoney(Math.abs(amount))
|
||||||
|
|
||||||
|
return (
|
||||||
<div className="text-sm text-gray-500">
|
<div className="text-sm text-gray-500">
|
||||||
<span>{isSelf ? 'You' : bettor ? bettor.name : 'A trader'}</span>{' '}
|
<span>{isSelf ? 'You' : bettor ? bettor.name : 'A trader'}</span> {bought}{' '}
|
||||||
{bought} {money}
|
{money}
|
||||||
{!hideOutcome && (
|
{!hideOutcome && (
|
||||||
<>
|
<>
|
||||||
{' '}
|
{' '}
|
||||||
|
@ -436,34 +482,7 @@ export function FeedBet(props: {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<RelativeTimestamp time={createdTime} />
|
<RelativeTimestamp time={createdTime} />
|
||||||
{(canComment || comment) && (
|
|
||||||
<div className="mt-2">
|
|
||||||
<Textarea
|
|
||||||
value={comment}
|
|
||||||
onChange={(e) => setComment(e.target.value)}
|
|
||||||
className="textarea textarea-bordered w-full resize-none"
|
|
||||||
placeholder="Add a comment..."
|
|
||||||
rows={3}
|
|
||||||
maxLength={MAX_COMMENT_LENGTH}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
|
||||||
submitComment()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className="btn btn-outline btn-sm text-transform: mt-1 capitalize"
|
|
||||||
onClick={submitComment}
|
|
||||||
disabled={!canComment}
|
|
||||||
>
|
|
||||||
Comment
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Row>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -573,14 +592,11 @@ export function FeedQuestion(props: {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function canCommentOnBet(
|
function canCommentOnBet(bet: Bet, user?: User | null) {
|
||||||
userId: string,
|
const { userId, createdTime, isRedemption } = bet
|
||||||
createdTime: number,
|
|
||||||
user?: User | null
|
|
||||||
) {
|
|
||||||
const isSelf = user?.id === userId
|
const isSelf = user?.id === userId
|
||||||
// You can comment if your bet was posted in the last hour
|
// You can comment if your bet was posted in the last hour
|
||||||
return isSelf && Date.now() - createdTime < 60 * 60 * 1000
|
return !isRedemption && isSelf && Date.now() - createdTime < 60 * 60 * 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
function FeedDescription(props: { contract: Contract }) {
|
function FeedDescription(props: { contract: Contract }) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { Contract } from '../../lib/firebase/contracts'
|
import { Contract } from 'web/lib/firebase/contracts'
|
||||||
import { Comment } from '../../lib/firebase/comments'
|
import { Comment } from 'web/lib/firebase/comments'
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
|
|
||||||
const MAX_ACTIVE_CONTRACTS = 75
|
const MAX_ACTIVE_CONTRACTS = 75
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@ import clsx from 'clsx'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { PlusCircleIcon } from '@heroicons/react/solid'
|
import { PlusCircleIcon } from '@heroicons/react/solid'
|
||||||
import { parseWordsAsTags } from '../../../common/util/parse'
|
import { parseWordsAsTags } from 'common/util/parse'
|
||||||
import { createFold } from '../../lib/firebase/api-call'
|
import { createFold } from 'web/lib/firebase/api-call'
|
||||||
import { foldPath } from '../../lib/firebase/folds'
|
import { foldPath } from 'web/lib/firebase/folds'
|
||||||
import { toCamelCase } from '../../../common/util/format'
|
import { toCamelCase } from 'common/util/format'
|
||||||
import { ConfirmationButton } from '../confirmation-button'
|
import { ConfirmationButton } from '../confirmation-button'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { Spacer } from '../layout/spacer'
|
import { Spacer } from '../layout/spacer'
|
||||||
|
|
|
@ -3,10 +3,10 @@ import _ from 'lodash'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { PencilIcon } from '@heroicons/react/outline'
|
import { PencilIcon } from '@heroicons/react/outline'
|
||||||
|
|
||||||
import { Fold } from '../../../common/fold'
|
import { Fold } from 'common/fold'
|
||||||
import { parseWordsAsTags } from '../../../common/util/parse'
|
import { parseWordsAsTags } from 'common/util/parse'
|
||||||
import { deleteFold, updateFold } from '../../lib/firebase/folds'
|
import { deleteFold, updateFold } from 'web/lib/firebase/folds'
|
||||||
import { toCamelCase } from '../../../common/util/format'
|
import { toCamelCase } from 'common/util/format'
|
||||||
import { Spacer } from '../layout/spacer'
|
import { Spacer } from '../layout/spacer'
|
||||||
import { TagsList } from '../tags-list'
|
import { TagsList } from '../tags-list'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
|
|
|
@ -2,11 +2,11 @@ import clsx from 'clsx'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { SearchIcon } from '@heroicons/react/outline'
|
import { SearchIcon } from '@heroicons/react/outline'
|
||||||
|
|
||||||
import { User } from '../../../common/user'
|
import { User } from 'common/user'
|
||||||
import {
|
import {
|
||||||
followFoldFromSlug,
|
followFoldFromSlug,
|
||||||
unfollowFoldFromSlug,
|
unfollowFoldFromSlug,
|
||||||
} from '../../lib/firebase/folds'
|
} from 'web/lib/firebase/folds'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
import { Spacer } from '../layout/spacer'
|
import { Spacer } from '../layout/spacer'
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { Fold } from '../../../common/fold'
|
import { Fold } from 'common/fold'
|
||||||
|
|
||||||
export function FoldTag(props: { fold: Fold }) {
|
export function FoldTag(props: { fold: Fold }) {
|
||||||
const { fold } = props
|
const { fold } = props
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { Fold } from '../../../common/fold'
|
import { Fold } from 'common/fold'
|
||||||
import { useFollowedFoldIds } from '../../hooks/use-fold'
|
import { useFollowedFoldIds } from 'web/hooks/use-fold'
|
||||||
import { useUser } from '../../hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { followFold, unfollowFold } from '../../lib/firebase/folds'
|
import { followFold, unfollowFold } from 'web/lib/firebase/folds'
|
||||||
|
|
||||||
export function FollowFoldButton(props: { fold: Fold; className?: string }) {
|
export function FollowFoldButton(props: { fold: Fold; className?: string }) {
|
||||||
const { fold, className } = props
|
const { fold, className } = props
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
import { Row } from './row'
|
||||||
|
|
||||||
type Tab = {
|
type Tab = {
|
||||||
title: string
|
title: string
|
||||||
|
@ -10,8 +11,13 @@ type Tab = {
|
||||||
href?: string
|
href?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Tabs(props: { tabs: Tab[]; defaultIndex?: number }) {
|
export function Tabs(props: {
|
||||||
const { tabs, defaultIndex } = props
|
tabs: Tab[]
|
||||||
|
defaultIndex?: number
|
||||||
|
className?: string
|
||||||
|
onClick?: (tabName: string) => void
|
||||||
|
}) {
|
||||||
|
const { tabs, defaultIndex, className, onClick } = props
|
||||||
const [activeIndex, setActiveIndex] = useState(defaultIndex ?? 0)
|
const [activeIndex, setActiveIndex] = useState(defaultIndex ?? 0)
|
||||||
const activeTab = tabs[activeIndex]
|
const activeTab = tabs[activeIndex]
|
||||||
|
|
||||||
|
@ -28,19 +34,21 @@ export function Tabs(props: { tabs: Tab[]; defaultIndex?: number }) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
setActiveIndex(i)
|
setActiveIndex(i)
|
||||||
|
onClick?.(tab.title)
|
||||||
}}
|
}}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
activeIndex === i
|
activeIndex === i
|
||||||
? 'border-indigo-500 text-indigo-600'
|
? 'border-indigo-500 text-indigo-600'
|
||||||
: 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700',
|
: 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700',
|
||||||
'cursor-pointer whitespace-nowrap border-b-2 py-4 px-1 text-sm font-medium'
|
'cursor-pointer whitespace-nowrap border-b-2 py-3 px-1 text-sm font-medium',
|
||||||
|
className
|
||||||
)}
|
)}
|
||||||
aria-current={activeIndex === i ? 'page' : undefined}
|
aria-current={activeIndex === i ? 'page' : undefined}
|
||||||
>
|
>
|
||||||
{tab.tabIcon ? (
|
<Row className={'items-center justify-center gap-1'}>
|
||||||
<span className="mr-2">{tab.tabIcon}</span>
|
{tab.tabIcon && <span> {tab.tabIcon}</span>}
|
||||||
) : null}
|
|
||||||
{tab.title}
|
{tab.title}
|
||||||
|
</Row>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { User } from '../../common/user'
|
import { User } from 'common/user'
|
||||||
import { Avatar } from './avatar'
|
import { Avatar } from './avatar'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
import { SiteLink } from './site-link'
|
import { SiteLink } from './site-link'
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
|
|
||||||
import { useUser } from '../../hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { ENV_CONFIG } from '../../../common/envs/constants'
|
import { ENV_CONFIG } from 'common/envs/constants'
|
||||||
|
|
||||||
export function ManifoldLogo(props: {
|
export function ManifoldLogo(props: {
|
||||||
className?: string
|
className?: string
|
||||||
|
|
|
@ -10,8 +10,8 @@ import {
|
||||||
import { Transition, Dialog } from '@headlessui/react'
|
import { Transition, Dialog } from '@headlessui/react'
|
||||||
import { useState, Fragment } from 'react'
|
import { useState, Fragment } from 'react'
|
||||||
import Sidebar from './sidebar'
|
import Sidebar from './sidebar'
|
||||||
import { useUser } from '../../hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { formatMoney } from '../../../common/util/format'
|
import { formatMoney } from 'common/util/format'
|
||||||
import { Avatar } from '../avatar'
|
import { Avatar } from '../avatar'
|
||||||
|
|
||||||
// From https://codepen.io/chris__sev/pen/QWGvYbL
|
// From https://codepen.io/chris__sev/pen/QWGvYbL
|
||||||
|
@ -60,7 +60,7 @@ export function BottomNavBar() {
|
||||||
) : user ? (
|
) : user ? (
|
||||||
<>
|
<>
|
||||||
<Avatar
|
<Avatar
|
||||||
containerClassName="mx-auto my-1"
|
containerClassName={'mx-auto my-1'}
|
||||||
size="xs"
|
size="xs"
|
||||||
username={user.username}
|
username={user.username}
|
||||||
avatarUrl={user.avatarUrl}
|
avatarUrl={user.avatarUrl}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { firebaseLogout, User } from '../../lib/firebase/users'
|
import { firebaseLogout, User } from 'web/lib/firebase/users'
|
||||||
import { formatMoney } from '../../../common/util/format'
|
import { formatMoney } from 'common/util/format'
|
||||||
import { Avatar } from '../avatar'
|
import { Avatar } from '../avatar'
|
||||||
import { IS_PRIVATE_MANIFOLD } from '../../../common/envs/constants'
|
import { IS_PRIVATE_MANIFOLD } from 'common/envs/constants'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
|
|
||||||
export function getNavigationOptions(user?: User | null) {
|
export function getNavigationOptions(user?: User | null) {
|
||||||
|
@ -13,6 +13,7 @@ export function getNavigationOptions(user?: User | null) {
|
||||||
return [
|
return [
|
||||||
{ name: 'Leaderboards', href: '/leaderboards' },
|
{ name: 'Leaderboards', href: '/leaderboards' },
|
||||||
{ name: 'Discord', href: 'https://discord.gg/eHQBNBqXuh' },
|
{ name: 'Discord', href: 'https://discord.gg/eHQBNBqXuh' },
|
||||||
|
{ name: 'Twitter', href: 'https://twitter.com/ManifoldMarkets' },
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +21,7 @@ export function getNavigationOptions(user?: User | null) {
|
||||||
{ name: 'Add funds', href: '/add-funds' },
|
{ name: 'Add funds', href: '/add-funds' },
|
||||||
{ name: 'Leaderboards', href: '/leaderboards' },
|
{ name: 'Leaderboards', href: '/leaderboards' },
|
||||||
{ name: 'Discord', href: 'https://discord.gg/eHQBNBqXuh' },
|
{ name: 'Discord', href: 'https://discord.gg/eHQBNBqXuh' },
|
||||||
|
{ name: 'Twitter', href: 'https://twitter.com/ManifoldMarkets' },
|
||||||
{ name: 'About', href: 'https://docs.manifold.markets' },
|
{ name: 'About', href: 'https://docs.manifold.markets' },
|
||||||
{ name: 'Sign out', href: '#', onClick: () => firebaseLogout() },
|
{ name: 'Sign out', href: '#', onClick: () => firebaseLogout() },
|
||||||
]
|
]
|
||||||
|
@ -28,7 +30,7 @@ export function getNavigationOptions(user?: User | null) {
|
||||||
export function ProfileSummary(props: { user: User | undefined }) {
|
export function ProfileSummary(props: { user: User | undefined }) {
|
||||||
const { user } = props
|
const { user } = props
|
||||||
return (
|
return (
|
||||||
<Row className="group avatar items-center gap-4 rounded-md py-3 text-gray-500 group-hover:bg-gray-100 group-hover:text-gray-700">
|
<Row className="group items-center gap-4 rounded-md py-3 text-gray-500 group-hover:bg-gray-100 group-hover:text-gray-700">
|
||||||
<Avatar avatarUrl={user?.avatarUrl} username={user?.username} noLink />
|
<Avatar avatarUrl={user?.avatarUrl} username={user?.username} noLink />
|
||||||
|
|
||||||
<div className="truncate text-left">
|
<div className="truncate text-left">
|
||||||
|
|
|
@ -14,13 +14,20 @@ import clsx from 'clsx'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useFollowedFolds } from '../../hooks/use-fold'
|
import { useFollowedFolds } from 'web/hooks/use-fold'
|
||||||
import { useUser } from '../../hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { firebaseLogin, firebaseLogout } from '../../lib/firebase/users'
|
import { firebaseLogin, firebaseLogout } from 'web/lib/firebase/users'
|
||||||
import { ManifoldLogo } from './manifold-logo'
|
import { ManifoldLogo } from './manifold-logo'
|
||||||
import { MenuButton } from './menu'
|
import { MenuButton } from './menu'
|
||||||
import { getNavigationOptions, ProfileSummary } from './profile-menu'
|
import { getNavigationOptions, ProfileSummary } from './profile-menu'
|
||||||
import { useHasCreatedContractToday } from '../../hooks/use-has-created-contract-today'
|
import { useHasCreatedContractToday } from 'web/hooks/use-has-created-contract-today'
|
||||||
|
|
||||||
|
// Create an icon from the url of an image
|
||||||
|
function IconFromUrl(url: string): React.ComponentType<{ className?: string }> {
|
||||||
|
return function Icon(props) {
|
||||||
|
return <img src={url} className={clsx(props.className, 'h-6 w-6')} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const navigation = [
|
const navigation = [
|
||||||
{ name: 'Home', href: '/home', icon: HomeIcon },
|
{ name: 'Home', href: '/home', icon: HomeIcon },
|
||||||
|
@ -32,13 +39,23 @@ const navigation = [
|
||||||
const signedOutNavigation = [
|
const signedOutNavigation = [
|
||||||
{ name: 'Home', href: '/home', icon: HomeIcon },
|
{ name: 'Home', href: '/home', icon: HomeIcon },
|
||||||
{ name: 'Explore', href: '/markets', icon: SearchIcon },
|
{ name: 'Explore', href: '/markets', icon: SearchIcon },
|
||||||
|
{ name: 'Charity', href: '/charity', icon: HeartIcon },
|
||||||
{ name: 'About', href: 'https://docs.manifold.markets', icon: BookOpenIcon },
|
{ name: 'About', href: 'https://docs.manifold.markets', icon: BookOpenIcon },
|
||||||
]
|
]
|
||||||
|
|
||||||
const signedOutMobileNavigation = [
|
const signedOutMobileNavigation = [
|
||||||
{ name: 'Charity', href: '/charity', icon: HeartIcon },
|
{ name: 'Charity', href: '/charity', icon: HeartIcon },
|
||||||
{ name: 'Leaderboards', href: '/leaderboards', icon: CakeIcon },
|
{ name: 'Leaderboards', href: '/leaderboards', icon: CakeIcon },
|
||||||
{ name: 'Discord', href: 'https://discord.gg/eHQBNBqXuh', icon: ChatIcon },
|
{
|
||||||
|
name: 'Discord',
|
||||||
|
href: 'https://discord.gg/eHQBNBqXuh',
|
||||||
|
icon: IconFromUrl('/discord-logo.svg'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Twitter',
|
||||||
|
href: 'https://twitter.com/ManifoldMarkets',
|
||||||
|
icon: IconFromUrl('/twitter-logo.svg'),
|
||||||
|
},
|
||||||
{ name: 'About', href: 'https://docs.manifold.markets', icon: BookOpenIcon },
|
{ name: 'About', href: 'https://docs.manifold.markets', icon: BookOpenIcon },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { Answer } from '../../common/answer'
|
import { Answer } from 'common/answer'
|
||||||
import { getProbability } from '../../common/calculate'
|
import { getProbability } from 'common/calculate'
|
||||||
import {
|
import {
|
||||||
Binary,
|
Binary,
|
||||||
Contract,
|
Contract,
|
||||||
|
@ -9,8 +9,8 @@ import {
|
||||||
FreeResponse,
|
FreeResponse,
|
||||||
FreeResponseContract,
|
FreeResponseContract,
|
||||||
FullContract,
|
FullContract,
|
||||||
} from '../../common/contract'
|
} from 'common/contract'
|
||||||
import { formatPercent } from '../../common/util/format'
|
import { formatPercent } from 'common/util/format'
|
||||||
import { ClientRender } from './client-render'
|
import { ClientRender } from './client-render'
|
||||||
|
|
||||||
export function OutcomeLabel(props: {
|
export function OutcomeLabel(props: {
|
||||||
|
|
|
@ -32,7 +32,7 @@ export function Page(props: {
|
||||||
{children}
|
{children}
|
||||||
|
|
||||||
{/* If right sidebar is hidden, place its content at the bottom of the page. */}
|
{/* If right sidebar is hidden, place its content at the bottom of the page. */}
|
||||||
<div className="block xl:hidden">{rightSidebar}</div>
|
<div className="mt-4 block xl:hidden">{rightSidebar}</div>
|
||||||
</main>
|
</main>
|
||||||
<aside className="hidden xl:col-span-3 xl:block">
|
<aside className="hidden xl:col-span-3 xl:block">
|
||||||
<div className="sticky top-4 space-y-4">{rightSidebar}</div>
|
<div className="sticky top-4 space-y-4">{rightSidebar}</div>
|
||||||
|
|
14
web/components/relative-timestamp.tsx
Normal file
14
web/components/relative-timestamp.tsx
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { DateTimeTooltip } from './datetime-tooltip'
|
||||||
|
import { fromNow } from 'web/lib/util/time'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export function RelativeTimestamp(props: { time: number }) {
|
||||||
|
const { time } = props
|
||||||
|
return (
|
||||||
|
<DateTimeTooltip time={time}>
|
||||||
|
<span className="ml-1 whitespace-nowrap text-gray-400">
|
||||||
|
{fromNow(time)}
|
||||||
|
</span>
|
||||||
|
</DateTimeTooltip>
|
||||||
|
)
|
||||||
|
}
|
|
@ -3,16 +3,16 @@ import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
import { Title } from './title'
|
import { Title } from './title'
|
||||||
import { User } from '../lib/firebase/users'
|
import { User } from 'web/lib/firebase/users'
|
||||||
import { YesNoCancelSelector } from './yes-no-selector'
|
import { YesNoCancelSelector } from './yes-no-selector'
|
||||||
import { Spacer } from './layout/spacer'
|
import { Spacer } from './layout/spacer'
|
||||||
import { ResolveConfirmationButton } from './confirmation-button'
|
import { ResolveConfirmationButton } from './confirmation-button'
|
||||||
import { resolveMarket } from '../lib/firebase/api-call'
|
import { resolveMarket } from 'web/lib/firebase/api-call'
|
||||||
import { ProbabilitySelector } from './probability-selector'
|
import { ProbabilitySelector } from './probability-selector'
|
||||||
import { DPM_CREATOR_FEE } from '../../common/fees'
|
import { DPM_CREATOR_FEE } from 'common/fees'
|
||||||
import { getProbability } from '../../common/calculate'
|
import { getProbability } from 'common/calculate'
|
||||||
import { Binary, CPMM, DPM, FullContract } from '../../common/contract'
|
import { Binary, CPMM, DPM, FullContract } from 'common/contract'
|
||||||
import { formatMoney } from '../../common/util/format'
|
import { formatMoney } from 'common/util/format'
|
||||||
|
|
||||||
export function ResolutionPanel(props: {
|
export function ResolutionPanel(props: {
|
||||||
creator: User
|
creator: User
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Binary, CPMM, DPM, FullContract } from '../../common/contract'
|
import { Binary, CPMM, DPM, FullContract } from 'common/contract'
|
||||||
import { User } from '../../common/user'
|
import { User } from 'common/user'
|
||||||
import { useUserContractBets } from '../hooks/use-user-bets'
|
import { useUserContractBets } from 'web/hooks/use-user-bets'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { Binary, CPMM, FullContract } from '../../common/contract'
|
import { Binary, CPMM, FullContract } from 'common/contract'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { User } from '../../common/user'
|
import { User } from 'common/user'
|
||||||
import { Modal } from './layout/modal'
|
import { Modal } from './layout/modal'
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
import { Title } from './title'
|
import { Title } from './title'
|
||||||
import { formatWithCommas } from '../../common/util/format'
|
import { formatWithCommas } from 'common/util/format'
|
||||||
import { OutcomeLabel } from './outcome-label'
|
import { OutcomeLabel } from './outcome-label'
|
||||||
import { SellPanel } from './bet-panel'
|
import { SellPanel } from './bet-panel'
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { Binary, CPMM, DPM, FullContract } from '../../common/contract'
|
import { Binary, CPMM, DPM, FullContract } from 'common/contract'
|
||||||
import { User } from '../../common/user'
|
import { User } from 'common/user'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
import { formatWithCommas } from '../../common/util/format'
|
import { formatWithCommas } from 'common/util/format'
|
||||||
import { OutcomeLabel } from './outcome-label'
|
import { OutcomeLabel } from './outcome-label'
|
||||||
import { useUserContractBets } from '../hooks/use-user-bets'
|
import { useUserContractBets } from 'web/hooks/use-user-bets'
|
||||||
import { useSaveShares } from './use-save-shares'
|
import { useSaveShares } from './use-save-shares'
|
||||||
import { SellSharesModal } from './sell-modal'
|
import { SellSharesModal } from './sell-modal'
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { Fragment } from 'react'
|
import { Fragment } from 'react'
|
||||||
import { CodeIcon } from '@heroicons/react/outline'
|
import { CodeIcon } from '@heroicons/react/outline'
|
||||||
import { Menu, Transition } from '@headlessui/react'
|
import { Menu, Transition } from '@headlessui/react'
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { contractPath } from '../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 '../lib/util/copy'
|
import { copyToClipboard } from 'web/lib/util/copy'
|
||||||
|
|
||||||
function copyEmbedCode(contract: Contract) {
|
function copyEmbedCode(contract: Contract) {
|
||||||
const title = contract.question
|
const title = contract.question
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { Contract, contractUrl } from '../lib/firebase/contracts'
|
import { Contract, contractUrl } from 'web/lib/firebase/contracts'
|
||||||
import { CopyLinkButton } from './copy-link-button'
|
import { CopyLinkButton } from './copy-link-button'
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { parseWordsAsTags } from '../../common/util/parse'
|
import { parseWordsAsTags } from 'common/util/parse'
|
||||||
import { Contract, updateContract } from '../lib/firebase/contracts'
|
import { Contract, updateContract } from 'web/lib/firebase/contracts'
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
import { TagsList } from './tags-list'
|
import { TagsList } from './tags-list'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Binary, CPMM, DPM, FullContract } from '../../common/contract'
|
import { Binary, CPMM, DPM, FullContract } from 'common/contract'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { User } from '../lib/firebase/users'
|
import { User } from 'web/lib/firebase/users'
|
||||||
import { CreatorContractsList } from './contract/contracts-list'
|
import { CreatorContractsList } from './contract/contracts-list'
|
||||||
import { SEO } from './SEO'
|
import { SEO } from './SEO'
|
||||||
import { Page } from './page'
|
import { Page } from './page'
|
||||||
|
@ -10,8 +10,17 @@ import { Linkify } from './linkify'
|
||||||
import { Spacer } from './layout/spacer'
|
import { Spacer } from './layout/spacer'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
import { LinkIcon } from '@heroicons/react/solid'
|
import { LinkIcon } from '@heroicons/react/solid'
|
||||||
import { genHash } from '../../common/util/random'
|
import { genHash } from 'common/util/random'
|
||||||
import { PencilIcon } from '@heroicons/react/outline'
|
import { PencilIcon } from '@heroicons/react/outline'
|
||||||
|
import { Tabs } from './layout/tabs'
|
||||||
|
import { UserCommentsList } from './comments-list'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { Comment, getUsersComments } from 'web/lib/firebase/comments'
|
||||||
|
import { Contract } from 'common/contract'
|
||||||
|
import { getContractFromId, listContracts } from 'web/lib/firebase/contracts'
|
||||||
|
import { LoadingIndicator } from './loading-indicator'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
export function UserLink(props: {
|
export function UserLink(props: {
|
||||||
name: string
|
name: string
|
||||||
|
@ -29,10 +38,47 @@ export function UserLink(props: {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UserPage(props: { user: User; currentUser?: User }) {
|
export function UserPage(props: {
|
||||||
const { user, currentUser } = props
|
user: User
|
||||||
|
currentUser?: User
|
||||||
|
defaultTabTitle?: string
|
||||||
|
}) {
|
||||||
|
const router = useRouter()
|
||||||
|
const { user, currentUser, defaultTabTitle } = props
|
||||||
const isCurrentUser = user.id === currentUser?.id
|
const isCurrentUser = user.id === currentUser?.id
|
||||||
const bannerUrl = user.bannerUrl ?? defaultBannerUrl(user.id)
|
const bannerUrl = user.bannerUrl ?? defaultBannerUrl(user.id)
|
||||||
|
const [usersComments, setUsersComments] = useState<Comment[]>([] as Comment[])
|
||||||
|
const [usersContracts, setUsersContracts] = useState<Contract[] | 'loading'>(
|
||||||
|
'loading'
|
||||||
|
)
|
||||||
|
const [commentsByContract, setCommentsByContract] = useState<
|
||||||
|
Map<Contract, Comment[]> | 'loading'
|
||||||
|
>('loading')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!user) return
|
||||||
|
getUsersComments(user.id).then(setUsersComments)
|
||||||
|
listContracts(user.id).then(setUsersContracts)
|
||||||
|
}, [user])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const uniqueContractIds = _.uniq(
|
||||||
|
usersComments.map((comment) => comment.contractId)
|
||||||
|
)
|
||||||
|
Promise.all(
|
||||||
|
uniqueContractIds.map((contractId) => getContractFromId(contractId))
|
||||||
|
).then((contracts) => {
|
||||||
|
const commentsByContract = new Map<Contract, Comment[]>()
|
||||||
|
contracts.forEach((contract) => {
|
||||||
|
if (!contract) return
|
||||||
|
commentsByContract.set(
|
||||||
|
contract,
|
||||||
|
usersComments.filter((comment) => comment.contractId === contract.id)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
setCommentsByContract(commentsByContract)
|
||||||
|
})
|
||||||
|
}, [usersComments])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
|
@ -138,8 +184,47 @@ export function UserPage(props: { user: User; currentUser?: User }) {
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Spacer h={10} />
|
<Spacer h={10} />
|
||||||
|
{usersContracts !== 'loading' && commentsByContract != 'loading' ? (
|
||||||
<CreatorContractsList creator={user} />
|
<Tabs
|
||||||
|
className={'pb-2 pt-1 '}
|
||||||
|
defaultIndex={defaultTabTitle === 'Comments' ? 1 : 0}
|
||||||
|
onClick={(tabName) =>
|
||||||
|
router.push(
|
||||||
|
{
|
||||||
|
pathname: `/${user.username}`,
|
||||||
|
query: { tab: tabName },
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
{ shallow: true }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
tabs={[
|
||||||
|
{
|
||||||
|
title: 'Markets',
|
||||||
|
content: <CreatorContractsList creator={user} />,
|
||||||
|
tabIcon: (
|
||||||
|
<div className="px-0.5 font-bold">
|
||||||
|
{usersContracts.length}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Comments',
|
||||||
|
content: (
|
||||||
|
<UserCommentsList
|
||||||
|
user={user}
|
||||||
|
commentsByUniqueContracts={commentsByContract}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
tabIcon: (
|
||||||
|
<div className="px-0.5 font-bold">{usersComments.length}</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<LoadingIndicator />
|
||||||
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { formatMoney } from '../../common/util/format'
|
import { formatMoney } from 'common/util/format'
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user