diff --git a/app/assets/v2/css/colors.css b/app/assets/v2/css/colors.css
index 31837172c0e..35f2f7b2658 100644
--- a/app/assets/v2/css/colors.css
+++ b/app/assets/v2/css/colors.css
@@ -6,6 +6,7 @@ html {
--gc-purple-hover: #0b0d9d;
--gc-green: #0FCE7C;
--gc-green-hover: #25E899;
+ --gc-light-green: rgb(211, 250, 235);
--gc-pink: #f9006c;
--gc-pink-hover: #f167a3;
--gc-yellow: #F5A623;
@@ -52,6 +53,8 @@ html.dark-mode {
--gc-purple-hover: #0b0d9d;
--gc-green: #0FCE7C;
--gc-green-hover: #25E899;
+ --gc-light-green: transparent;
+
--gc-pink: #f9006c;
--gc-pink-hover: #f167a3;
--gc-yellow: #F5A623;
diff --git a/app/assets/v2/css/town_square.css b/app/assets/v2/css/town_square.css
index dc3c40e5e87..e4399146c27 100644
--- a/app/assets/v2/css/town_square.css
+++ b/app/assets/v2/css/town_square.css
@@ -1216,10 +1216,65 @@ body.green.offer_view .announce {
border-top: 6px solid rgba(62, 0, 255, 0.4);
}
+.activity.hackathon_new_hacker {
+ position: relative;
+ box-sizing: border-box;
+ z-index: 2;
+}
+
+.activity.hackathon_new_hacker:before {
+ content: '';
+ position: absolute;
+ top: -6px;
+ left: 0;
+ height: 6px;
+ width: 100%;
+ display: block;
+ background: linear-gradient(to right, #B572D4 0%, #F9DD5B 37%, #75EDB5 100%);
+}
+
+.activity.hackathon_new_hacker:after {
+ content: '';
+ position: absolute;
+ top: 58px;
+ left: 3%;
+ height: 143px;
+ width: 94%;
+ display: block;
+ background: --var(--gc-light-green);
+ z-index: -2;
+}
+
+.activity.hackathon_new_hacker .activity-avatar::after {
+ height: 6.1rem;
+ content: '';
+ background-image: url(/static/v2/images/townsquare-nh.svg);
+ width: 6.1rem;
+ position: absolute;
+ top: -24px;
+ left: 8px;
+ z-index: -1;
+}
+
+.activity.hackathon_new_hacker .activity_detail::after {
+ height: 100%;
+ content: '';
+ background: url(/static/v2/images/townsqure-party.png) 0 0 no-repeat;
+ width: 100%;
+ position: absolute;
+ top: 55px;
+ right: -309px;
+ z-index: -1;
+}
+
.new_hackathon_project {
border-top: 6px solid rgba(15, 206, 124, 0.25);
}
+.activity_main .bg_hackathon_new_hacker {
+ background-color: #D3FAEB;
+}
+
.bg-gc-blue {
background-color: var(--gc-blue);
}
@@ -1299,3 +1354,14 @@ body.green.offer_view .announce {
background: var(--gc-dark-violet);
color: white;
}
+
+.tag-list .tag-list__item {
+ background: #E8F0FA;
+ padding: 4px;
+ border-radius: 2px;
+ font-size: 11px;
+ color: #6487AE;
+ display: inline-block;
+ margin-bottom: 2px;
+ margin-top: 2px;
+}
diff --git a/app/assets/v2/images/dots.svg b/app/assets/v2/images/dots.svg
new file mode 100644
index 00000000000..767b654384e
--- /dev/null
+++ b/app/assets/v2/images/dots.svg
@@ -0,0 +1,28 @@
+
diff --git a/app/assets/v2/images/townsquare-nh.svg b/app/assets/v2/images/townsquare-nh.svg
new file mode 100644
index 00000000000..0fe8f3171be
--- /dev/null
+++ b/app/assets/v2/images/townsquare-nh.svg
@@ -0,0 +1,18 @@
+
diff --git a/app/assets/v2/images/townsqure-party.png b/app/assets/v2/images/townsqure-party.png
new file mode 100644
index 00000000000..7548fdbf031
Binary files /dev/null and b/app/assets/v2/images/townsqure-party.png differ
diff --git a/app/dashboard/admin.py b/app/dashboard/admin.py
index 32a798cab44..e21293ce253 100644
--- a/app/dashboard/admin.py
+++ b/app/dashboard/admin.py
@@ -29,8 +29,8 @@
Activity, Answer, BlockedURLFilter, BlockedUser, Bounty, BountyEvent, BountyFulfillment, BountyInvites,
BountySyncRequest, CoinRedemption, CoinRedemptionRequest, Coupon, Earning, FeedbackEntry, FundRequest,
HackathonEvent, HackathonProject, HackathonRegistration, HackathonSponsor, Interest, Investigation, LabsResearch,
- ObjectView, Option, Poll, PortfolioItem, Profile, ProfileVerification, ProfileView, Question, SearchHistory,
- Sponsor, Tip, TipPayout, TokenApproval, TribeMember, UserAction, UserVerificationModel,
+ ObjectView, Option, Poll, PollMedia, PortfolioItem, Profile, ProfileVerification, ProfileView, Question, SearchHistory,
+ Sponsor, Tip, TipPayout, TokenApproval, TribeMember, UserAction, UserVerificationModel,
)
@@ -445,7 +445,7 @@ class FundRequestAdmin(admin.ModelAdmin):
class QuestionInline(SortableInlineAdminMixin, admin.TabularInline):
- fields = ['id', 'poll', 'question_type', 'text']
+ fields = ['id', 'poll', 'question_type', 'text', 'hook']
readonly_fields = ['id']
raw_id_fields = ['poll']
show_change_link = True
@@ -470,11 +470,19 @@ class PollsAdmin(admin.ModelAdmin):
class QuestionsAdmin(admin.ModelAdmin):
- list_display = ['id', 'poll', 'question_type', 'text']
- raw_id_fields = ['poll']
+ list_display = ['id', 'poll', 'question_type', 'text', 'img']
+ raw_id_fields = ['poll', 'header']
search_fields = ['question_type', 'text']
inlines = [OptionsInline]
+ def img(self, instance):
+ header = instance.header
+ if not header or not header.image:
+ return 'n/a'
+ img_html = format_html('', mark_safe(header.image.url))
+ return img_html
+
+
class OptionsAdmin(admin.ModelAdmin):
list_display = ['id', 'question', 'text']
@@ -488,6 +496,17 @@ class AnswersAdmin(admin.ModelAdmin):
unique_together = ('user', 'question', 'choice')
+class PollMediaAdmin(admin.ModelAdmin):
+ list_display = ['id', 'name', 'img']
+
+ def img(self, instance):
+ image = instance.image
+ if not image:
+ return 'n/a'
+ img_html = format_html('
', mark_safe(image.url))
+ return img_html
+
+
class ProfileVerificationAdmin(admin.ModelAdmin):
list_display = ['id', 'profile', 'success', 'validation_passed', 'caller_type', 'mobile_network_code', 'country_code', 'carrier_name', 'carrier_type',
'phone_number', 'carrier_error_code']
@@ -531,4 +550,5 @@ class ProfileVerificationAdmin(admin.ModelAdmin):
admin.site.register(ObjectView, ObjectViewAdmin)
admin.site.register(Option, OptionsAdmin)
admin.site.register(Answer, AnswersAdmin)
+admin.site.register(PollMedia, PollMediaAdmin)
admin.site.register(ProfileVerification, ProfileVerificationAdmin)
diff --git a/app/dashboard/migrations/0123_auto_20200617_1549.py b/app/dashboard/migrations/0123_auto_20200617_1549.py
new file mode 100644
index 00000000000..bd7376ae6bd
--- /dev/null
+++ b/app/dashboard/migrations/0123_auto_20200617_1549.py
@@ -0,0 +1,59 @@
+# Generated by Django 2.2.4 on 2020-06-17 15:49
+
+import app.utils
+from django.db import migrations, models
+import django.db.models.deletion
+import economy.models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('dashboard', '0122_auto_20200615_1510'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='PollMedia',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created_on', models.DateTimeField(db_index=True, default=economy.models.get_time)),
+ ('modified_on', models.DateTimeField(default=economy.models.get_time)),
+ ('name', models.CharField(max_length=350)),
+ ('image', models.ImageField(blank=True, help_text='Poll media asset', null=True, upload_to=app.utils.get_upload_filename)),
+ ],
+ options={
+ 'abstract': False,
+ },
+ ),
+ migrations.AddField(
+ model_name='hackathonregistration',
+ name='looking_project',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='hackathonregistration',
+ name='looking_team_members',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='question',
+ name='hook',
+ field=models.CharField(choices=[('NO_ACTION', 'No trigger any action'), ('TOWNSQUARE_INTRO', 'Create intro on Townsquare'), ('LOOKING_TEAM_PROJECT', 'Looking for team or project')], default='NO_ACTION', max_length=50),
+ ),
+ migrations.AlterField(
+ model_name='activity',
+ name='activity_type',
+ field=models.CharField(blank=True, choices=[('wall_post', 'Wall Post'), ('status_update', 'Update status'), ('new_bounty', 'New Bounty'), ('start_work', 'Work Started'), ('stop_work', 'Work Stopped'), ('work_submitted', 'Work Submitted'), ('work_done', 'Work Done'), ('worker_approved', 'Worker Approved'), ('worker_rejected', 'Worker Rejected'), ('worker_applied', 'Worker Applied'), ('increased_bounty', 'Increased Funding'), ('killed_bounty', 'Canceled Bounty'), ('new_tip', 'New Tip'), ('receive_tip', 'Tip Received'), ('bounty_abandonment_escalation_to_mods', 'Escalated checkin from @gitcoinbot about bounty status'), ('bounty_abandonment_warning', 'Checkin from @gitcoinbot about bounty status'), ('bounty_removed_slashed_by_staff', 'Dinged and Removed from Bounty by Staff'), ('bounty_removed_by_staff', 'Removed from Bounty by Staff'), ('bounty_removed_by_funder', 'Removed from Bounty by Funder'), ('new_crowdfund', 'New Crowdfund Contribution'), ('new_grant', 'New Grant'), ('update_grant', 'Updated Grant'), ('killed_grant', 'Cancelled Grant'), ('negative_contribution', 'Negative Grant Contribution'), ('new_grant_contribution', 'Contributed to Grant'), ('new_grant_subscription', 'Subscribed to Grant'), ('killed_grant_contribution', 'Cancelled Grant Contribution'), ('new_kudos', 'New Kudos'), ('created_kudos', 'Created Kudos'), ('receive_kudos', 'Receive Kudos'), ('joined', 'Joined Gitcoin'), ('played_quest', 'Played Quest'), ('beat_quest', 'Beat Quest'), ('created_quest', 'Created Quest'), ('updated_avatar', 'Updated Avatar'), ('mini_clr_payout', 'Mini CLR Payout'), ('leaderboard_rank', 'Leaderboard Rank'), ('consolidated_leaderboard_rank', 'Consolidated Leaderboard Rank'), ('consolidated_mini_clr_payout', 'Consolidated CLR Payout'), ('hackathon_registration', 'Hackathon Registration'), ('hackathon_new_hacker', 'Hackathon Registration'), ('new_hackathon_project', 'New Hackathon Project'), ('flagged_grant', 'Flagged Grant')], db_index=True, max_length=50),
+ ),
+ migrations.AlterField(
+ model_name='question',
+ name='question_type',
+ field=models.CharField(choices=[('SINGLE_OPTION', 'Single option'), ('SINGLE_CHOICE', 'Single Choice'), ('MULTIPLE_CHOICE', 'Multiple Choices'), ('OPEN', 'Open')], max_length=50),
+ ),
+ migrations.AddField(
+ model_name='question',
+ name='header',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='dashboard.PollMedia'),
+ ),
+ ]
diff --git a/app/dashboard/models.py b/app/dashboard/models.py
index 68569ae4874..f71341b4ea7 100644
--- a/app/dashboard/models.py
+++ b/app/dashboard/models.py
@@ -2164,6 +2164,7 @@ class Activity(SuperModel):
('consolidated_leaderboard_rank', 'Consolidated Leaderboard Rank'),
('consolidated_mini_clr_payout', 'Consolidated CLR Payout'),
('hackathon_registration', 'Hackathon Registration'),
+ ('hackathon_new_hacker', 'Hackathon Registration'),
('new_hackathon_project', 'New Hackathon Project'),
('flagged_grant', 'Flagged Grant'),
]
@@ -2583,6 +2584,8 @@ class HackathonRegistration(SuperModel):
blank=True
)
referer = models.URLField(null=True, blank=True, help_text='Url comes from')
+ looking_team_members = models.BooleanField(default=False)
+ looking_project = models.BooleanField(default=False)
registrant = models.ForeignKey(
'dashboard.Profile',
related_name='hackathon_registration',
@@ -5026,17 +5029,39 @@ class Poll(SuperModel):
hackathon = models.ManyToManyField(HackathonEvent)
+class PollMedia(SuperModel):
+ name = models.CharField(max_length=350)
+ image = models.ImageField(
+ upload_to=get_upload_filename,
+ null=True,
+ blank=True,
+ help_text=_('Poll media asset')
+ )
+
+ def __str__(self):
+ return f'{self.id} - {self.name}'
+
class Question(SuperModel):
TYPE_QUESTIONS = (
+ ('SINGLE_OPTION', 'Single option'),
('SINGLE_CHOICE', 'Single Choice'),
('MULTIPLE_CHOICE', 'Multiple Choices'),
('OPEN', 'Open'),
)
+
+ TYPE_HOOKS = (
+ ('NO_ACTION', 'No trigger any action'),
+ ('TOWNSQUARE_INTRO', 'Create intro on Townsquare'),
+ ('LOOKING_TEAM_PROJECT', 'Looking for team or project')
+ )
+
+ hook = models.CharField(default='NO_ACTION', choices=TYPE_HOOKS, max_length=50)
poll = models.ForeignKey(Poll, on_delete=models.CASCADE, null=True, blank=True)
question_type = models.CharField(choices=TYPE_QUESTIONS, max_length=50, blank=False, null=False)
text = models.CharField(max_length=350, blank=True, null=True)
order = models.PositiveIntegerField(default=0, blank=False, null=False)
+ header = models.ForeignKey(PollMedia, null=True, on_delete=models.SET_NULL)
class Meta(object):
ordering = ['order']
@@ -5062,6 +5087,49 @@ class Answer(SuperModel):
hackathon = models.ForeignKey(HackathonEvent, null=True, on_delete=models.CASCADE)
+@receiver(post_save, sender=Answer, dispatch_uid='hooks_on_question_response')
+def psave_answer(sender, instance, created, **kwargs):
+ if created:
+ if instance.question.hook == 'TOWNSQUARE_INTRO':
+ registration = HackathonRegistration.objects.filter(hackathon=instance.hackathon,
+ registrant=instance.user.profile).first()
+
+ Activity.objects.create(
+ profile=instance.user.profile,
+ hackathonevent=instance.hackathon,
+ activity_type='hackathon_new_hacker',
+ metadata={
+ 'answer': instance.id,
+ 'intro_text': f'{instance.open_response or ""} #intro',
+ 'looking_members': registration.looking_team_members if registration else False,
+ 'looking_project': registration.looking_project if registration else False,
+ 'hackathon_registration': registration.id if registration else 0
+ }
+ )
+ elif instance.question.hook == 'LOOKING_TEAM_PROJECT':
+ registration = HackathonRegistration.objects.filter(hackathon=instance.hackathon,
+ registrant=instance.user.profile).first()
+ print(instance)
+ if registration:
+ if instance.choice.text.lower().find('team') != -1:
+ registration.looking_team_members = True
+
+ if instance.choice.text.lower().find('project') != -1:
+ registration.looking_project = True
+
+ registration.save()
+
+ activity = Activity.objects.filter(
+ profile=instance.user.profile,
+ hackathonevent=instance.hackathon,
+ activity_type='hackathon_new_hacker').last()
+
+ if activity:
+ activity.metadata['looking_team_members'] = registration.looking_team_members
+ activity.metadata['looking_project'] = registration.looking_project
+ activity.save()
+
+
class Investigation(SuperModel):
profile = models.ForeignKey(
'dashboard.Profile', on_delete=models.CASCADE, related_name='investigations', blank=True
diff --git a/app/dashboard/templates/dashboard/hackathon/onboard.html b/app/dashboard/templates/dashboard/hackathon/onboard.html
index 95390148509..d387fa3d140 100644
--- a/app/dashboard/templates/dashboard/hackathon/onboard.html
+++ b/app/dashboard/templates/dashboard/hackathon/onboard.html
@@ -70,6 +70,21 @@
.modal-header, .modal-body, .modal-footer {
border: none;
}
+
+ .gc-bg-blue {
+ background-color: #4100FF;
+ height: 100px;
+ background-image: url(/static/v2/images/dots.svg);
+ ackground-position: center;
+ }
+
+ .negative-offset {
+ top: -55px;
+ max-height: 160px;
+ }
+ .overflow-show {
+ overflow: visible !important;
+ }