diff --git a/app/assets/v2/css/explorer.css b/app/assets/v2/css/explorer.css
new file mode 100644
index 00000000000..6f175fcb84a
--- /dev/null
+++ b/app/assets/v2/css/explorer.css
@@ -0,0 +1,81 @@
+.bounty_row {
+ color: #000000;
+ text-decoration: none;
+ padding: 10px 1rem;
+}
+
+.bounty_row:hover {
+ color: #000000;
+ text-decoration: none;
+ background-color: #F9F9F9;
+}
+
+.hiring-alt {
+ font-size: 11px;
+ color: #4A90E2;
+ font-weight: 700;
+ letter-spacing: 1.7px;
+}
+
+.hackathon-alt {
+ font-size: 11px;
+ color: #0d0764;
+ font-weight: 600;
+ letter-spacing: 1.7px;
+}
+
+.hackathon-explorer .hackathon-alt {
+ display: none;
+}
+
+.status-open {
+ color: #00A652;
+}
+
+.status-reserved {
+ color: #d58512;
+}
+
+.status-started {
+ color: #0D0764;
+}
+
+.status-submitted {
+ color: #008EFF;
+}
+
+.status-done {
+ color: #666666;
+}
+
+.status-cancelled,
+.status-expired {
+ color: #C70018;
+}
+
+.separator-bull {
+ margin: 0 0.6em;
+}
+
+.separator-bull:after {
+ content: '\2022'
+}
+
+.btn-expand-token {
+ position: absolute;
+ line-height: 1.5rem;
+ border: 0;
+ background-color: transparent;
+ font-weight: 400;
+ color: #007bff;
+}
+
+.btn-expand-token:focus {
+ color: #007bff;
+}
+
+.expired-icon {
+ font-size: 15px;
+ padding-right: 0.5em;
+ vertical-align: text-bottom;
+}
diff --git a/app/assets/v2/css/town_square.css b/app/assets/v2/css/town_square.css
index 7ecabc70179..f11dff91ef3 100644
--- a/app/assets/v2/css/town_square.css
+++ b/app/assets/v2/css/town_square.css
@@ -1054,3 +1054,48 @@ body.green.offer_view .announce {
.promo .close-promo i {
font-weight: 100;
}
+
+.new_grant {
+ border-top: 6px solid rgba(13, 7, 100, 0.25);
+}
+
+.new_kudos {
+ border-top: 6px solid rgba(0, 163, 255, 0.25);
+}
+
+.new_bounty {
+ border-top: 6px solid rgba(62, 0, 255, 0.4);
+}
+
+.new_hackathon_project {
+ border-top: 6px solid rgba(15, 206, 124, 0.25);
+}
+
+.bg-gc-blue {
+ background-color: var(--gc-blue);
+}
+
+.bg-gc-green {
+ background-color: var(--gc-green);
+}
+
+.bg-light-blue {
+ background-color: #00A3FF;
+}
+
+.bg-gc-dark-blue {
+ background-color: #0D0764;
+}
+
+.card-ribbon {
+ margin-top: -14px;
+ right: -10px;
+ width: 110px;
+ margin-bottom: 10px;
+ position: absolute;
+}
+
+.logo-metacard {
+ max-height: 4rem;
+ max-width: 4rem;
+}
diff --git a/app/assets/v2/css/users.css b/app/assets/v2/css/users.css
index f5d90544c20..bd80806fdd9 100644
--- a/app/assets/v2/css/users.css
+++ b/app/assets/v2/css/users.css
@@ -117,7 +117,6 @@
}
.btn-search {
- font-size: 1.3em;
background: white;
border-left: 0;
color: #3E00FF;
diff --git a/app/assets/v2/js/explorer.js b/app/assets/v2/js/explorer.js
new file mode 100644
index 00000000000..e935a087084
--- /dev/null
+++ b/app/assets/v2/js/explorer.js
@@ -0,0 +1,358 @@
+let bounties = [];
+let featuredBounties = [];
+let bountiesPage = 1;
+let BountiesLimit = 10;
+let BountiesOffset = 0;
+let bountiesNumPages = '';
+let bountiesHasNext = false;
+let numBounties = '';
+// let funderBounties = [];
+let paramsDefault = {
+ order_by: '-web3_created',
+ idx_status: '',
+ experience_level: [],
+ project_type: [],
+ applicants: '',
+ bounty_filter: '',
+ bounty_categories: [],
+ moderation_filter: '',
+ permission_type: [],
+ project_length: [],
+ bounty_type: [],
+ network: [document.web3network || 'mainnet'],
+ keywords: []
+};
+
+Vue.filter('stringFixer', (str) => {
+ return str.replace(/_/i, ' ');
+});
+
+Vue.mixin({
+ methods: {
+ fetchBounties: function(newPage, featured) {
+ let vm = this;
+
+ vm.isLoading = true;
+ vm.noResults = false;
+ vm.params.limit = vm.BountiesLimit;
+ if (newPage || newPage === 0) {
+ vm.BountiesOffset = newPage;
+ }
+ vm.params.offset = vm.BountiesOffset;
+ if (vm.searchTerm) {
+ vm.params.search = vm.searchTerm;
+ } else {
+ delete vm.params['search'];
+ }
+
+ let searchParams = new URLSearchParams(vm.params);
+
+ // window.history.replaceState({}, '', `${location.pathname}?${searchParams}`);
+
+ let apiUrlBounties = `/api/v0.1/bounties/slim/?${searchParams.toString()}`;
+
+ if (featured) {
+ apiUrlBounties = `/api/v0.1/bounties/slim/?${searchParams.toString()}&is_featured=True`;
+ }
+
+ var getBounties = fetchData (apiUrlBounties, 'GET');
+
+ $.when(getBounties).then(function(response) {
+
+ response.forEach(function(item) {
+ if (featured) {
+ vm.featuredBounties.push(item);
+ } else {
+ vm.bounties.push(item);
+ }
+ });
+
+ if (featured) {
+ return vm.featuredBounties;
+ }
+ vm.numBounties = response.length;
+ if (vm.numBounties < vm.BountiesLimit) {
+ vm.bountiesHasNext = false;
+ } else {
+ vm.bountiesHasNext = true;
+ }
+ if (vm.bountiesHasNext) {
+ vm.BountiesOffset = vm.BountiesOffset + vm.BountiesLimit;
+
+ } else {
+ vm.BountiesOffset = 0;
+ }
+
+ if (vm.bounties.length) {
+ vm.noResults = false;
+ } else {
+ vm.noResults = true;
+ }
+ vm.isLoading = false;
+ });
+ },
+ getUrlParams: function() {
+ let vm = this;
+
+ const url = new URL(location.href);
+ const params = new URLSearchParams(url.search);
+
+ for (let p of params) {
+ if (typeof vm.params[p[0]] === 'object') {
+ if (p[1].length > 0) {
+ vm.params[p[0]] = p[1].split(',');
+ } else {
+ vm.$delete(vm.params[p[0]]);
+ }
+ } else {
+ vm.params[p[0]] = p[1];
+ }
+ }
+ },
+ searchBounties: function() {
+ let vm = this;
+
+ vm.bounties = [];
+ vm.featuredBounties = [];
+ vm.fetchBounties(0);
+ vm.fetchBounties(0, true);
+ },
+ bottomVisible: function() {
+ let vm = this;
+
+ const scrollY = window.scrollY;
+ const visible = document.documentElement.clientHeight;
+ const pageHeight = document.documentElement.scrollHeight - 500;
+ const bottomOfPage = visible + scrollY >= pageHeight;
+
+ if (bottomOfPage || pageHeight < visible) {
+ if (vm.bountiesHasNext) {
+ vm.fetchBounties();
+ vm.bountiesHasNext = false;
+ }
+ }
+ },
+ clearParams() {
+ let vm = this;
+
+ vm.params = JSON.parse(JSON.stringify(paramsDefault));
+ },
+ closeModal() {
+ this.$refs['user-modal'].closeModal();
+ },
+ removeFilter(prop, index, obj) {
+ let vm = this;
+
+ if (obj) {
+ vm.$delete(vm.params[prop], index);
+ } else {
+ vm.$set(vm.params, prop, '');
+ }
+ },
+ popoverConfig(bounty) {
+ moment.locale('en');
+ return `
+
@@ -43,7 +62,7 @@ Vue.component('select2', {
mounted: function() {
let vm = this;
- $(this.$el).select2({ data: this.options })
+ $(this.$el).select2({data: this.options})
.val(this.value)
.trigger('change')
.on('change', function() {
@@ -53,13 +72,13 @@ Vue.component('select2', {
watch: {
value: function(value) {
if (value === undefined) {
- $(this.$el).empty().select2({ data: this.options });
+ $(this.$el).empty().select2({data: this.options});
} else if ([...value].sort().join(',') !== [...$(this.$el).val()].sort().join(',')) {
$(this.$el).val(value).trigger('change');
}
},
options: function(options) {
- $(this.$el).empty().select2({ data: options });
+ $(this.$el).empty().select2({data: options});
}
},
destroyed: function() {
@@ -124,3 +143,230 @@ Vue.component('qrcode', {
// }
}
});
+
+
+Vue.component('tribes-settings', {
+ delimiters: [ '[[', ']]' ],
+ props: ['tribe'],
+ data: function() {
+ return {
+ editorOptions: {
+ priority: {
+ modules: {
+ toolbar: [
+ [ 'bold', 'italic', 'underline' ],
+ [{ 'align': [] }],
+ [{ 'list': 'ordered'}, { 'list': 'bullet' }],
+ [ 'link', 'code-block' ],
+ ['clean']
+ ]
+ },
+ theme: 'snow',
+ placeholder: 'List out your tribe priorities to let contributors to know what they can request to work on'
+ },
+ description: {
+ modules: {
+ toolbar: [
+ [ 'bold', 'italic', 'underline' ],
+ [{ 'align': [] }],
+ [ 'link', 'code-block' ],
+ ['clean']
+ ]
+ },
+ theme: 'snow',
+ placeholder: 'Describe your tribe so that people can follow you.'
+ }
+ }
+ };
+ },
+ methods: {}
+});
+
+
+Vue.component('project-directory', {
+ delimiters: [ '[[', ']]' ],
+ props: ['tribe'],
+ methods: {
+ fetchProjects: function(newPage) {
+ let vm = this;
+
+ vm.isLoading = true;
+ vm.noResults = false;
+
+ if (newPage) {
+ vm.projectsPage = newPage;
+ }
+
+ vm.params.page = vm.projectsPage;
+
+ if (vm.hackathonId) {
+ vm.params.hackathon = hackathonId;
+ }
+
+ if (vm.sponsor) {
+ vm.params.sponsor = vm.sponsor;
+ }
+
+ if (vm.searchTerm) {
+ vm.params.search = vm.searchTerm;
+ } else {
+ delete vm.params['search'];
+ }
+
+ let searchParams = new URLSearchParams(vm.params);
+
+ let apiUrlProjects = `/api/v0.1/projects_fetch/?${searchParams.toString()}`;
+
+ var getProjects = fetchData(apiUrlProjects, 'GET');
+
+ $.when(getProjects).then(function(response) {
+ vm.hackathonProjects = [];
+ response.data.forEach(function(item) {
+ vm.hackathonProjects.push(item);
+ });
+
+ vm.projectsNumPages = response.num_pages;
+ vm.projectsHasNext = response.has_next;
+ vm.numProjects = response.count;
+ if (vm.projectsHasNext) {
+ vm.projectsPage = ++vm.projectsPage;
+
+ } else {
+ vm.projectsPage = 1;
+ }
+
+ if (vm.hackathonProjects.length) {
+ vm.noResults = false;
+ } else {
+ vm.noResults = true;
+ }
+ vm.isLoading = false;
+ });
+ },
+ searchProjects: function() {
+ let vm = this;
+
+ vm.hackathonProjects = [];
+
+ vm.fetchProjects(1);
+
+ }
+ },
+ data: function() {
+
+ return {
+ sponsor: this.tribe || null,
+ hackathonSponsors: document.hackathonSponsors || [],
+ hackathonProjects: document.hackathonProjects || [],
+ projectsPage: 1,
+ hackathonId: document.hackathon_id || null,
+ projectsNumPages: 0,
+ projectsHasNext: false,
+ numProjects: 0,
+ media_url,
+ searchTerm: null,
+ bottom: false,
+ params: {},
+ isFunder: false,
+ showModal: false,
+ showFilters: true,
+ skills: document.keywords || [],
+ selectedSkills: [],
+ noResults: false,
+ isLoading: true,
+ hideFilterButton: false
+ };
+ },
+ mounted() {
+ this.fetchProjects();
+ this.$watch('params', function(newVal, oldVal) {
+ this.searchProjects();
+ }, {
+ deep: true
+ });
+ },
+ created() {
+ // this.extractURLFilters();
+ },
+ beforeMount() {
+ window.addEventListener('scroll', () => {
+ this.bottom = this.bottomVisible();
+ }, false);
+ },
+ beforeDestroy() {
+ window.removeEventListener('scroll', () => {
+ this.bottom = this.bottomVisible();
+ });
+ }
+});
+
+
+Vue.component('suggested-profiles', {
+ props: ['profiles'],
+ template: `
`
+});
+
+
+Vue.component('suggested-profile', {
+ props: ['profile'],
+ data: function() {
+ return {
+ follow: this.profile.user_is_following || false,
+ follower_count: this.profile.followers_count || 0
+ };
+ },
+ computed: {
+ avatar_url: function() {
+ return `/dynamic/avatar/${this.profile.handle}`;
+ },
+ profile_url: function() {
+ return `/profile/${this.profile.handle}`;
+ }
+ },
+ methods: {
+ followTribe: function(handle, event) {
+ event.preventDefault();
+ let vm = this;
+
+ const url = `/tribe/${handle}/join/`;
+ const sendJoin = fetchData(url, 'POST', {}, {'X-CSRFToken': vm.csrf});
+
+ $.when(sendJoin).then((response) => {
+ if (response && response.is_member) {
+ vm.follow = true;
+ vm.follower_count += 1;
+ } else {
+ vm.follow = false;
+ vm.follower_count -= 1;
+ }
+ }).fail((error) => {
+ console.log(error);
+ });
+ }
+ },
+ template: `
+
+
+
+
+ {{profile.name}}
+
+
+ {{follower_count}} followers
+
+
+
+
+ following
+
+
+ follow
+
+ `
+});
diff --git a/app/dashboard/migrations/0105_auto_20200430_1352.py b/app/dashboard/migrations/0105_auto_20200430_1352.py
new file mode 100644
index 00000000000..46ecf60f8ab
--- /dev/null
+++ b/app/dashboard/migrations/0105_auto_20200430_1352.py
@@ -0,0 +1,30 @@
+# Generated by Django 2.2.4 on 2020-04-30 13:52
+
+import app.utils
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('dashboard', '0104_fundrequest'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='activity',
+ name='project',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='hackathon_projects', to='dashboard.HackathonProject'),
+ ),
+ migrations.AddField(
+ model_name='profile',
+ name='tribes_cover_image',
+ field=models.ImageField(blank=True, help_text='The Tribes Cover image.', null=True, upload_to=app.utils.get_upload_filename),
+ ),
+ 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'), ('new_hackathon_project', 'New Hackathon Project'), ('flagged_grant', 'Flagged Grant')], db_index=True, max_length=50),
+ ),
+ ]
diff --git a/app/dashboard/models.py b/app/dashboard/models.py
index 71671191b5d..d551d7e6061 100644
--- a/app/dashboard/models.py
+++ b/app/dashboard/models.py
@@ -66,6 +66,7 @@
from marketing.models import LeaderboardRank
from rest_framework import serializers
from web3 import Web3
+from bounty_requests.models import BountyRequest
from .notifications import maybe_market_to_github, maybe_market_to_slack, maybe_market_to_user_slack
from .signals import m2m_changed_interested
@@ -75,6 +76,7 @@
CROSS_CHAIN_STANDARD_BOUNTIES_OFFSET = 100000000
+
class BountyQuerySet(models.QuerySet):
"""Handle the manager queryset for Bounties."""
@@ -2101,6 +2103,7 @@ class Activity(SuperModel):
('consolidated_leaderboard_rank', 'Consolidated Leaderboard Rank'),
('consolidated_mini_clr_payout', 'Consolidated CLR Payout'),
('hackathon_registration', 'Hackathon Registration'),
+ ('new_hackathon_project', 'New Hackathon Project'),
('flagged_grant', 'Flagged Grant'),
]
@@ -2153,6 +2156,12 @@ class Activity(SuperModel):
on_delete=models.CASCADE,
blank=True, null=True
)
+ project = models.ForeignKey(
+ 'dashboard.HackathonProject',
+ related_name='hackathon_projects',
+ on_delete=models.CASCADE,
+ blank=True, null=True
+ )
created = models.DateTimeField(auto_now_add=True, blank=True, null=True, db_index=True)
activity_type = models.CharField(max_length=50, choices=ACTIVITY_TYPES, blank=True, db_index=True)
@@ -2646,6 +2655,13 @@ class Profile(SuperModel):
as_representation = JSONField(default=dict, blank=True)
tribe_priority = models.TextField(default='', blank=True, help_text=_('HTML rich description for what tribe priorities.'))
+ tribes_cover_image = models.ImageField(
+ upload_to=get_upload_filename,
+ null=True,
+ blank=True,
+ help_text=_('The Tribes Cover image.'),
+ )
+
is_org = models.BooleanField(
default=True,
help_text='Is this profile an org?',
@@ -2660,6 +2676,13 @@ class Profile(SuperModel):
objects = ProfileManager()
objects_full = ProfileQuerySet.as_manager()
+
+ @property
+ def suggested_bounties(self):
+ suggested_bounties = BountyRequest.objects.filter(tribe=self, status='o').order_by('created_on')
+
+ return suggested_bounties if suggested_bounties else []
+
@property
def subscribed_threads(self):
tips = Tip.objects.filter(Q(pk__in=self.received_tips.all()) | Q(pk__in=self.sent_tips.all())).filter(comments_priv__icontains="activity:").all()
diff --git a/app/dashboard/router.py b/app/dashboard/router.py
index 6fc303f86f2..8e2383d0036 100644
--- a/app/dashboard/router.py
+++ b/app/dashboard/router.py
@@ -29,8 +29,8 @@
from retail.helpers import get_ip
from .models import (
- Activity, Bounty, BountyFulfillment, BountyInvites, HackathonEvent, HackathonProject, Interest, Profile,
- ProfileSerializer, SearchHistory,
+ Activity, Bounty, BountyFulfillment, BountyInvites, BountyRequest, HackathonEvent, HackathonProject, Interest, Profile,
+ ProfileSerializer, SearchHistory, TribeMember,
)
logger = logging.getLogger(__name__)
@@ -210,7 +210,8 @@ class Meta:
'pk', 'url', 'title', 'experience_level', 'status', 'fulfillment_accepted_on', 'event',
'fulfillment_started_on', 'fulfillment_submitted_on', 'canceled_on', 'web3_created', 'bounty_owner_address',
'avatar_url', 'network', 'standard_bounties_id', 'github_org_name', 'interested', 'token_name', 'value_in_usdt',
- 'keywords', 'value_in_token', 'project_type', 'is_open', 'expires_date', 'latest_activity', 'token_address'
+ 'keywords', 'value_in_token', 'project_type', 'is_open', 'expires_date', 'latest_activity', 'token_address',
+ 'bounty_categories'
)
@@ -496,6 +497,52 @@ def get_queryset(self):
return queryset
+class TribesTeamSerializer(serializers.ModelSerializer):
+
+ user_is_following = serializers.SerializerMethodField(method_name='user_following')
+ followers_count = serializers.SerializerMethodField(method_name='follow_count')
+
+ def follow_count(self, instance):
+ return TribeMember.objects.filter(org=instance).exclude(status='rejected').exclude(profile__user=None).count()
+
+ def user_following(self, instance):
+ request = self.context.get('request')
+ user_profile = request.user.profile if request and request.user and hasattr(request.user, 'profile') else None
+ if user_profile:
+ return len(user_profile.tribe_members.filter(org__handle=instance.handle.lower())) > 0
+
+ class Meta:
+ model = Profile
+ fields = ('name', 'handle', 'avatar_url', 'followers_count', 'user_is_following')
+
+
+class BountyRequestSerializer(serializers.ModelSerializer):
+
+ requested_by = TribesTeamSerializer()
+
+ class Meta:
+ model = BountyRequest
+ fields = ('id', 'token_name', 'comment', 'github_url', 'title', 'requested_by', 'status')
+
+
+class TribesSerializer(serializers.ModelSerializer):
+ """Handle serializing the Profile object."""
+ active_bounties = BountySerializer(many=True)
+ team_or_none_if_timeout = TribesTeamSerializer(many=True, read_only=True)
+ suggested_bounties = BountyRequestSerializer(many=True)
+ tribes_cover_image = serializers.ImageField(allow_empty_file=True)
+
+ def __init__(self, *args, **kwargs):
+ super(TribesSerializer, self).__init__(*args, **kwargs)
+ # We pass the "upper serializer" context to the "nested one"
+ self.fields['team_or_none_if_timeout'].context.update(self.context)
+
+ class Meta:
+ model = Profile
+ """Define the profile serializer metadata."""
+ fields = ('profile_wallpaper', 'tribes_cover_image', 'name', 'linkedin_url', 'team_or_none_if_timeout', 'suggested_bounties', 'active_bounties', 'handle', 'tribe_description', 'avatar_url', 'follower_count', 'following_count', 'data', 'tribe_priority')
+
+
# Routers provide an easy way of automatically determining the URL conf.
router = routers.DefaultRouter()
router.register(r'bounties/slim', BountiesViewSetSlim)
diff --git a/app/dashboard/templates/dashboard/explorer.html b/app/dashboard/templates/dashboard/explorer.html
new file mode 100644
index 00000000000..4d6b406fb2d
--- /dev/null
+++ b/app/dashboard/templates/dashboard/explorer.html
@@ -0,0 +1,581 @@
+{% comment %}
+ Copyright (C) 2019 Gitcoin Core
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see
.
+
+{% endcomment %}
+{% load i18n static email_obfuscator add_url_schema avatar_tags %}
+
+
+
+
+ {% include 'shared/head.html' %}
+ {% include 'shared/cards.html' %}
+
+
+
+
+
+
+
+
+
+
+ {% include 'shared/tag_manager_2.html' %}
+
+
+
+
+
+ {% csrf_token %}
+ {% include 'shared/analytics.html' %}
+ {% include 'shared/footer_scripts.html' %}
+ {% include 'shared/footer.html' %}
+ {% include 'shared/messages.html' %}
+
+
+
+
+
+
diff --git a/app/dashboard/templates/dashboard/index-vue.html b/app/dashboard/templates/dashboard/index-vue.html
index 87d401507f2..e94102d5051 100644
--- a/app/dashboard/templates/dashboard/index-vue.html
+++ b/app/dashboard/templates/dashboard/index-vue.html
@@ -475,7 +475,7 @@
[[ project.name ]]
-
+
@ [[ profile.handle ]]
@@ -712,7 +712,7 @@
@@ -744,18 +744,9 @@