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; + }
@@ -297,26 +312,45 @@