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 ` +
+
+
+ ${bounty.status === 'open' ? 'Ready to work' : bounty.status === 'started' ? 'Work Started' : bounty.status === 'submitted' ? 'Work Submitted' : bounty.status === 'done' ? 'Work Done' : bounty.status === 'cancelled' ? 'Cancelled' : bounty.status === 'expired' ? 'Expired' : ''} + ${bounty.github_org_name} ${bounty.github_org_name} +
+
${bounty.title}
+
+ ${bounty.keywords.split(',').map((keyword) => `${keyword}`).join(' ')} +
+
+ ${moment(bounty.expires_date).diff(moment(), 'years') < 1 ? ` ${moment(bounty.expires_date) < moment() ? 'Expired' : 'Expires'} ${moment.utc(bounty.expires_date).fromNow()}` : 'Never expires' } +
+
+ ${ bounty.latest_activity ? ` + ` : ''} + +
`; + + } + } +}); + +Vue.component('bounty-explorer', Vue.extend({ + delimiters: [ '[[', ']]' ], + props: ['tribe'], + data: function() { + return { + bounties, + featuredBounties, + bountiesPage, + BountiesLimit, + BountiesOffset, + bountiesNumPages, + bountiesHasNext, + numBounties, + media_url, + orderByOptions: [ + { name: 'Created: Recent', value: '-web3_created'}, + { name: 'Created: Oldest', value: 'web3_created' }, + { name: 'Value: Highest', value: '-_val_usd_db'}, + { name: 'Value: Lowest', value: '_val_usd_db'} + ], + searchTerm: null, + bottom: false, + experienceSelected: [], + showExtraParams: false, + params: { + order_by: '-web3_created', + idx_status: 'open', + experience_level: [], + project_type: [], + applicants: '', + bounty_filter: '', + bounty_categories: [], + moderation_filter: '', + permission_type: [], + project_length: [], + bounty_type: [], + // network: [document.web3network || 'mainnet'], + network: ['rinkeby'], + keywords: [] + }, + showFilters: true, + noResults: false, + isLoading: true + }; + }, + mounted() { + if (this.tribe && this.tribe.handle) { + this.params.org = this.tribe.handle; + } + this.fetchBounties(); + this.fetchBounties(0, true); + this.$watch('params', function(newVal, oldVal) { + if (this.tribe && this.tribe.handle) { + this.params.org = this.tribe.handle; + } + this.searchBounties(); + }, { + deep: true + }); + }, + updated() { + $(function() { + $('[data-toggle="popover"]').popover(); + }), + scrollSlider($('#featured-card-container'), 288); + if (this.tribe && this.tribe.handle) { + // this.bounties = this.tribe.active_bounties; + this.params.org = this.tribe.handle; + } + }, + beforeMount() { + window.addEventListener('scroll', () => { + this.bottom = this.bottomVisible(); + }, false); + }, + beforeDestroy() { + window.removeEventListener('scroll', () => { + this.bottom = this.bottomVisible(); + }); + } +})); + +if (document.getElementById('gc-explorer')) { + var appExplorer = new Vue({ + delimiters: [ '[[', ']]' ], + el: '#gc-explorer', + data: { + bounties, + featuredBounties, + bountiesPage, + BountiesLimit, + BountiesOffset, + bountiesNumPages, + bountiesHasNext, + numBounties, + media_url, + searchTerm: null, + bottom: false, + experienceSelected: [], + showExtraParams: false, + params: { + order_by: '-web3_created', + idx_status: '', + experience_level: [], + project_type: [], + applicants: '', + bounty_filter: '', + bounty_categories: [], + moderation_filter: '', + permission_type: [], + project_length: [], + bounty_type: [], + network: ['mainnet'], + keywords: [] + }, + showFilters: true, + noResults: false, + isLoading: true + }, + mounted() { + this.getUrlParams(); + this.fetchBounties(); + this.fetchBounties(0, true); + this.$watch('params', function(newVal, oldVal) { + this.searchBounties(); + }, { + deep: true + }); + }, + updated() { + $(function() { + $('[data-toggle="popover"]').popover(); + }), + scrollSlider($('#featured-card-container'), 288); + + }, + beforeMount() { + window.addEventListener('scroll', () => { + this.bottom = this.bottomVisible(); + }, false); + }, + beforeDestroy() { + window.removeEventListener('scroll', () => { + this.bottom = this.bottomVisible(); + }); + } + }); +} + +function scrollSlider(element, cardSize) { + const arrowLeft = $('#arrowLeft'); + const arrowRight = $('#arrowRight'); + + arrowLeft.on('click', function() { + element[0].scrollBy({left: -cardSize, behavior: 'smooth'}); + }); + arrowRight.on('click', function() { + element[0].scrollBy({left: cardSize, behavior: 'smooth'}); + }); + + element.on('scroll mouseenter', function() { + if (this.clientWidth === (this.scrollWidth - this.scrollLeft)) { + arrowRight.hide(); + } else { + arrowRight.show(); + } + + if (this.scrollLeft < 10) { + arrowLeft.hide(); + } else { + arrowLeft.show(); + } + }); +} + diff --git a/app/assets/v2/js/pages/dashboard-hackathon.js b/app/assets/v2/js/pages/dashboard-hackathon.js index ef0f1d5e0b4..b4e55fdc430 100644 --- a/app/assets/v2/js/pages/dashboard-hackathon.js +++ b/app/assets/v2/js/pages/dashboard-hackathon.js @@ -864,122 +864,6 @@ hackathonSponsors: document.hackathonSponsors }) }); - Vue.component('project-directory', { - delimiters: [ '[[', ']]' ], - methods: { - openChat: function(handle) { - let vm = this; - const url = handle ? `${vm.chatURL}/hackathons/messages/@${handle}` : `${vm.chatURL}/`; - - chatWindow = window.open(url, 'Loading', 'top=0,left=0,width=400,height=600,status=no,toolbar=no,location=no,menubar=no,titlebar=no'); - }, - fetchProjects: function(newPage) { - let vm = this; - - vm.isLoading = true; - vm.noResults = false; - - if (newPage) { - vm.projectsPage = newPage; - } - vm.params.page = vm.projectsPage; - vm.params.hackathon = hackathonId; - - vm.params.filters = ''; - if (vm.params.lfm) { - vm.params.filters = 'lfm'; - } - - 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: () => ({ - hackathonSponsors, - hackathonProjects, - projectsPage, - hackathonId, - projectsNumPages, - projectsHasNext, - numProjects, - media_url, - chatURL: document.chatURL, - lfm: false, - 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(); - }); - } - }); var app = new Vue({ delimiters: [ '[[', ']]' ], el: '#dashboard-vue-app', diff --git a/app/assets/v2/js/pages/join_tribe.js b/app/assets/v2/js/pages/join_tribe.js index d3b25c63c1f..216f47e97b8 100644 --- a/app/assets/v2/js/pages/join_tribe.js +++ b/app/assets/v2/js/pages/join_tribe.js @@ -50,8 +50,13 @@ const joinTribeDirect = (elem) => { $.when(sendJoin).then(function(response) { $(elem).attr('disabled', false); - $(elem).attr('member', response.is_member); - $(elem).attr('hidden', true); + $('[data-jointribe]').each((idx, ele) => { + + if (window.hasOwnProperty('tribesApp')) { + window.tribesApp.is_on_tribe = true; + } + $(ele).attr('hidden', true); + }); }).fail(function(error) { $(elem).attr('disabled', false); }); diff --git a/app/assets/v2/js/pages/profile-tribes.js b/app/assets/v2/js/pages/profile-tribes.js new file mode 100644 index 00000000000..44afe16758c --- /dev/null +++ b/app/assets/v2/js/pages/profile-tribes.js @@ -0,0 +1,272 @@ +/* eslint-disable no-loop-func */ + + +const loadDynamicScript = (callback, url, id) => { + const existingScript = document.getElementById(id); + + if (!existingScript) { + const script = document.createElement('script'); + + script.src = url; + script.id = id; + document.body.appendChild(script); + + script.onload = () => { + if (callback) + callback(); + }; + } + + if (existingScript && callback) + callback(); +}; + +(function($) { + // doc ready + $(() => { + Vue.use(VueQuillEditor); + window.tribesApp = new Vue({ + delimiters: [ '[[', ']]' ], + el: '#tribes-vue-app', + methods: { + followTribe: function(tribe, event) { + event.preventDefault(); + let vm = this; + + const url = `/tribe/${tribe}/join/`; + const sendJoin = fetchData(url, 'POST', {}, {'X-CSRFToken': vm.csrf}); + + $.when(sendJoin).then((response) => { + if (response && response.is_member) { + $('[data-jointribe]').each((idx, ele) => { + $(ele).attr('hidden', true); + }); + vm.tribe.follower_count++; + vm.is_on_tribe = true; + } else { + vm.tribe.follower_count--; + vm.is_on_tribe = false; + $('[data-jointribe]').each((idx, ele) => { + $(ele).attr('hidden', false); + }); + } + }).fail((error) => { + console.log(error); + }); + }, + resetPreview: function() { + this.headerFilePreview = null; + }, + updateTribe: function() { + let vm = this; + const url = `/tribe/${vm.tribe.handle}/save/`; + + let data = new FormData(); + + if (vm.$refs.quillEditorDesc) { + data['tribe_description'] = vm.tribe.tribe_description; + } + + if (vm.$refs.quillEditorPriority) { + data.append('tribe_priority', vm.tribe.tribe_priority); + data.append('priority_html_text', vm.$refs.quillEditorPriority.quill.getText('')); + } + + if (vm.headerFile) { + data.append('cover_image', vm.headerFile); + } + + if (vm.params.publish_to_ts) { + data.append('publish_to_ts', vm.params.publish_to_ts); + } + const sendSave = async(url, data) => { + return $.ajax({ + type: 'POST', + url: url, + processData: false, + contentType: false, + data: data, + headers: {'X-CSRFToken': vm.csrf} + }); + }; + + $.when(sendSave(url, data)).then(function(response) { + _alert('Tribe has been updated'); + vm.tribe.tribes_cover_image = vm.headerFilePreview; + vm.$bvModal.hide('change-tribe-header'); + }).fail(function(error) { + _alert('Error saving priorites. Try again later', 'error'); + console.error('error: unable to save priority', error); + }); + }, + suggestBounty: function() { + let vm = this; + const githubUrl = vm.params.suggest.githubUrl; + const tokenName = tokenAddressToDetails(vm.params.suggest.token)['name']; + const comment = vm.params.suggest.comment; + const tribe = vm.tribe.handle; + const url = '/api/v1/bounty_request/create'; + const amount = vm.params.suggest.amount; + + fetchIssueDetailsFromGithub(githubUrl).then(result => { + const title = result.title; + + const createBountyRequest = fetchData( + url, + 'POST', + { + 'github_url': githubUrl, + 'tribe': tribe, + 'comment': comment, + 'token_name': tokenName, + 'amount': amount, + 'title': title + }, + {'X-CSRFToken': $("input[name='csrfmiddlewaretoken']").val()} + ); + + $.when(createBountyRequest).then(function(response) { + + if (response.status == 204) { + _alert('Bounty Request has been created'); + location.reload(); + } else { + _alert(`Error creating bounty request as ${response.message}`, 'error'); + console.error(response.message); + } + + }).fail(function(error) { + _alert(`Error creating bounty request as ${error}`, 'error'); + console.error('error: unable to creating bounty request', error); + }); + }).catch(error => { + _alert(`Error creating bounty request as ${error}`, 'error'); + console.error('error: unable to creating bounty request', error); + }); + }, + resetBountySuggestion: function() { + this.params.suggest = {}; + }, + tabChange: function(input) { + let vm = this; + + switch (input) { + default: + case 0: + newPathName = 'townsquare'; + break; + case 1: + newPathName = 'projects'; + break; + case 2: + newPathName = 'people'; + break; + case 3: + newPathName = 'bounties'; + break; + } + let newUrl = `/${vm.tribe.handle}/${newPathName}${window.location.search}`; + + history.pushState({}, `Tribe - @${vm.tribe.handle}`, newUrl); + }, + onEditorBlur(quill) { + console.log('editor blur!', quill); + }, + onEditorFocus(quill) { + console.log('editor focus!', quill); + }, + onEditorReady(quill) { + console.log('editor ready!', quill); + } + }, + computed: { + editorDesc() { + return this.$refs.quillEditorDesc.quill; + }, + editorPriority() { + return this.$refs.quillEditorPriority.quill; + }, + editorComment() { + return this.$refs.quillEditorComment.quill; + } + }, + mounted() { + console.log('we mounted'); + let vm = this; + + this.$watch('headerFile', function(newVal, oldVal) { + if (checkFileSize(this.headerFile, 4000000) === false) { + _alert(`Profile Header Image should not exceed ${(4000000 / 1024 / 1024).toFixed(2)} MB`, 'error'); + } else { + let reader = new FileReader(); + + reader.onload = function(e) { + vm.headerFilePreview = e.target.result; + $('#preview').css('width', '100%'); + $('#js-drop span').hide(); + $('#js-drop input').css('visible', 'invisible'); + $('#js-drop').css('padding', 0); + }; + reader.readAsDataURL(this.headerFile); + } + }, {deep: true}); + }, + data: function() { + return $.extend({ + chatURL: document.chatURL || 'https://chat.gitcoin.co/', + activePanel: document.activePanel, + tribe: document.currentProfile + }, { + tokens: document.tokens, + csrf: $("input[name='csrfmiddlewaretoken']").val(), + headerFile: null, + headerFilePreview: null, + is_my_org: document.is_my_org || false, + is_on_tribe: document.is_on_tribe || false, + params: { + suggest: {}, + publish_to_ts: false + }, + editorOptionPrio: { + 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' + }, + editorOptionDesc: { + modules: { + toolbar: [ + [ 'bold', 'italic', 'underline' ], + [{ 'align': [] }], + [ 'link', 'code-block' ], + ['clean'] + ] + }, + theme: 'snow', + placeholder: 'Describe your tribe so that people can follow you.' + }, + editorOptionComment: { + modules: { + toolbar: [ + [ 'bold', 'italic', 'underline' ], + [{ 'align': [] }], + [ 'link', 'code-block' ], + ['clean'] + ] + }, + theme: 'snow', + placeholder: 'What would you suggest as a bounty?' + } + }); + } + }); + }); + +})(jQuery); diff --git a/app/assets/v2/js/pages/tribe-edit.js b/app/assets/v2/js/pages/tribe-edit.js index 03da7709c68..c7ee76bdf1c 100644 --- a/app/assets/v2/js/pages/tribe-edit.js +++ b/app/assets/v2/js/pages/tribe-edit.js @@ -246,4 +246,4 @@ $('[data-cancelbountyrequest]').on('click', function() { _alert(`Error rejecting bounty request. ${error}`, 'error'); console.error('error: unable to reject bounty request', error); }); -}); \ No newline at end of file +}); diff --git a/app/assets/v2/js/status.js b/app/assets/v2/js/status.js index 56a664f3190..4d9682161d8 100644 --- a/app/assets/v2/js/status.js +++ b/app/assets/v2/js/status.js @@ -417,11 +417,15 @@ $(document).ready(function() { const message = $('#textarea'); const the_message = message.val().trim(); const ask = $('.activity_type_selector input:checked').val(); + let tab = getParam('tab'); + + if (!tab || typeof tab == 'undefined') + tab = window.localStorage['tab'] || ''; data.append('ask', ask); data.append('data', the_message); data.append('what', $('#status [name=what]').val()); - data.append('tab', getParam('tab')); + data.append('tab', tab); if ($('#video_container').length) { data.append('has_video', $('#video_container').length); data.append('video_gfx', $('#video_container').data('gfx')); diff --git a/app/assets/v2/js/users.js b/app/assets/v2/js/users.js index bbba724f2e3..406e66aeed4 100644 --- a/app/assets/v2/js/users.js +++ b/app/assets/v2/js/users.js @@ -24,7 +24,9 @@ Vue.mixin({ vm.usersPage = newPage; } vm.params.page = vm.usersPage; - vm.params.hackathon = hackathonId; + if (hackathonId) { + vm.params.hackathon = hackathonId; + } if (vm.searchTerm) { vm.params.search = vm.searchTerm; } else { @@ -42,6 +44,11 @@ Vue.mixin({ delete vm.params['skills']; } + if (vm.tribeFilter) { + vm.params.tribe = vm.tribeFilter; + } + + let searchParams = new URLSearchParams(vm.params); let apiUrlUsers = `/api/v0.1/users_fetch/?${searchParams.toString()}`; @@ -246,7 +253,7 @@ Vue.mixin({ --user.follower_count; } - event.target.classList.toggle('btn-gc-pink'); + event.target.classList.toggle('btn-outline-green'); event.target.classList.toggle('btn-gc-blue'); }).fail(function(error) { event.target.disabled = false; @@ -260,39 +267,55 @@ Vue = Vue.extend({ Vue.component('user-directory', { delimiters: [ '[[', ']]' ], - data: () => ({ - users, - usersPage, - hackathonId, - usersNumPages, - usersHasNext, - numUsers, - media_url, - chatURL: document.chatURL || 'https://chat.gitcoin.co/', - searchTerm: null, - bottom: false, - params: {}, - funderBounties: [], - currentBounty: undefined, - contributorInvite: undefined, - isFunder: false, - bountySelected: null, - userSelected: [], - showModal: false, - showFilters: !document.getElementById('explore_tribes'), - skills: document.keywords, - selectedSkills: [], - noResults: false, - isLoading: true, - gitcoinIssueUrl: '', - issueDetails: undefined, - errorIssueDetails: undefined, - showBanner: undefined, - persona: undefined, - hideFilterButton: !!document.getElementById('explore_tribes') - }), + props: [ 'tribe', 'is_my_org' ], + data: function() { + return { + orgOwner: this.is_my_org || false, + userFilter: { + options: [ + { text: 'All', value: 'all' }, + { text: 'Tribe Owners', value: 'owners' }, + { text: 'Tribe Members', value: 'members' }, + { text: 'Tribe Hackers', value: 'hackers' } + ] + }, + tribeFilter: this.tribe || '', + users, + usersPage, + hackathonId, + usersNumPages, + usersHasNext, + numUsers, + media_url, + chatURL: document.chatURL || 'https://chat.gitcoin.co/', + searchTerm: null, + bottom: false, + params: { + 'user_filter': 'all' + }, + funderBounties: [], + currentBounty: undefined, + contributorInvite: undefined, + isFunder: false, + bountySelected: null, + userSelected: [], + showModal: false, + showFilters: !document.getElementById('explore_tribes'), + skills: document.keywords, + selectedSkills: [], + noResults: false, + isLoading: true, + gitcoinIssueUrl: '', + issueDetails: undefined, + errorIssueDetails: undefined, + showBanner: undefined, + persona: undefined, + hideFilterButton: !!document.getElementById('explore_tribes') + }; + }, mounted() { this.fetchUsers(); + this.tribeFilter = this.tribe; this.$watch('params', function(newVal, oldVal) { this.searchUsers(); }, { diff --git a/app/assets/v2/js/vue-components.js b/app/assets/v2/js/vue-components.js index dad34cbdce3..356d7afa879 100644 --- a/app/assets/v2/js/vue-components.js +++ b/app/assets/v2/js/vue-components.js @@ -1,3 +1,22 @@ + +Vue.mixin({ + data: function() { + return { + chatURL: document.chatURL || 'https://chat.gitcoin.co' + }; + }, + methods: { + chatWindow: function(handle) { + let vm = this; + + const url = handle ? `${vm.chatURL}/hackathons/messages/@${handle}` : `${vm.chatURL}/`; + + window.open(url, 'Loading', 'top=0,left=0,width=400,height=600,status=no,toolbar=no,location=no,menubar=no,titlebar=no'); + } + } +}); + + Vue.component('modal', { props: [ 'user', 'size', 'id', 'issueDetails' ], template: ` @@ -744,18 +744,9 @@
- - + + [[ user.is_following ? "following" : "follow" ]] +
@@ -870,7 +861,7 @@
Invite [[numUsers]] Users to the Bounty
[[ sponsor.display_name ]] - [[ sponsor.followed ? "unfollow" : "follow" ]] + [[ sponsor.followed ? "following" : "follow" ]]
diff --git a/app/dashboard/templates/dashboard/users.html b/app/dashboard/templates/dashboard/users.html index dde6f693624..542bad140e0 100644 --- a/app/dashboard/templates/dashboard/users.html +++ b/app/dashboard/templates/dashboard/users.html @@ -243,10 +243,10 @@
+ {% if not skip_style %} \ No newline at end of file + + {% endif %} diff --git a/app/dashboard/templates/profiles/tribe/suggest_bounty.html b/app/dashboard/templates/profiles/tribe/suggest_bounty.html index da9c9cd7443..4ec4fe5678c 100644 --- a/app/dashboard/templates/profiles/tribe/suggest_bounty.html +++ b/app/dashboard/templates/profiles/tribe/suggest_bounty.html @@ -173,87 +173,3 @@

{% endif %}

- - \ No newline at end of file diff --git a/app/dashboard/templates/profiles/tribes-vue.html b/app/dashboard/templates/profiles/tribes-vue.html new file mode 100644 index 00000000000..b786fb2ed78 --- /dev/null +++ b/app/dashboard/templates/profiles/tribes-vue.html @@ -0,0 +1,1671 @@ +{% 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 avatar_tags add_url_schema email_obfuscator humanize %} + + + + + {% include 'shared/head.html' %} + {% if use_pic_card %} + {% include 'shared/cards_pic.html' %} + {% else %} + {% include 'shared/cards.html' %} + {% endif %} + + + + + + + + + + + + + + + + + + + + {% include 'shared/tag_manager_2.html' %} +
+ {% include 'shared/top_nav.html' with class='d-md-flex' %} + {% include 'home/nav.html' %} +
+ {% if is_staff %} +
+ Staff only + {% trans "Tribes Admin" %} +
+ {% endif %} +
+
+
+ {% csrf_token %} +
+
+
+
+ + + Update Header Image + +
+
+
+
+
+
+ + + +
+ +
+
+
+ [[ tribe.name ]] {% trans "Team" %} +
+ +
+
+
+ {% csrf_token %} + {% include 'townsquare/shared/shareactivity.html' %} + + +
+
+
+
+
+
+
+ + + +
+ +
+
+
+ + + +
+ +
+
+
+ + + + + + + + + + +
+
+

+

+ Share your priorities to encourage tribe members + to volunteer to work on bounties which matter +

+ + +
+
+ + +
+ + +

+ {% trans "Update Sidebar" %} +

+ +
+ tribe logo +
+
+
+
{% trans "Tribe Description" %}
+
+ +
+
+
+
{% trans "Tribe Priority" %}
+ +
+
+
+
+ + {% trans "Post to Townsquare" %} + +
+ + +
+
+
+
+
+ + + +

+ {% trans "Suggested Bounties" %} +

+ + +
+
+

[[ suggested_bounty.title ]]

+

+
+
+ + {% trans "View Github Issue" %} + + + + [[ suggested_bounty.requested_by.handle ]] + + + + [[ suggested_bounty.created_on ]] + +
+

+ [[ suggested_bounty.amount ]] + [[ suggested_bounty.token_name ]] +

+
+
+
+
+ +
+ + + {% trans "Fund Request" %} + + +
+
+
+
+
+

+

+ No Bounties have been suggested + + Update your tribe's priorities to guide contributors + + + to submit issues they'd wanna #BUDIL + +

+
+
+

+

+ No Bounties have been suggested + + Suggest an issue you'd like bountied out to + + + #BUDIL to grow {{ profile.handle }} + +

+
+
+
+
+
+
+
+
+
+ +
+ + +
+
+
+ {{footer_msg|safe}} +
+ + + + {% include 'shared/result.html' %} + {% include 'shared/featured_bounty_cards.html' %} + {% include 'shared/analytics.html' %} + {% include 'shared/footer_scripts.html' with vue=True %} + {% include 'shared/footer.html' %} + {% include 'shared/current_profile.html' %} + + + + + {{currentProfile|json_script:"current-profile"}} + + {% include 'shared/activity_scripts.html' %} + + + + + + + + + diff --git a/app/dashboard/templates/shared/status_tooltip.html b/app/dashboard/templates/shared/status_tooltip.html index 82cce22242f..16b7bf669d6 100644 --- a/app/dashboard/templates/shared/status_tooltip.html +++ b/app/dashboard/templates/shared/status_tooltip.html @@ -22,6 +22,7 @@
  • {% trans "Work Started" %}: {% trans "Work has been started by at least one person." %}
  • {% trans "Work Submitted" %}: {% trans "Completed work has been submitted by for review." %}
  • {% trans "Work Done" %}: {% trans "The submitted project has been accepted and the funds have been paid." %}
  • +
  • {% trans "Reserved" %}: {% trans "The project has been reserved for an specific hunter." %}
  • Possible Status

    @@ -30,4 +31,4 @@
  • {% trans "Canceled" %}: {% trans "A funded bounty was cancelled by the owner of the project" %}
  • - \ No newline at end of file + diff --git a/app/dashboard/views.py b/app/dashboard/views.py index 6186d860d87..2fbe5bf71e0 100644 --- a/app/dashboard/views.py +++ b/app/dashboard/views.py @@ -110,7 +110,7 @@ maybe_market_tip_to_email, maybe_market_tip_to_github, maybe_market_tip_to_slack, maybe_market_to_email, maybe_market_to_github, maybe_market_to_slack, maybe_market_to_user_slack, ) -from .router import HackathonEventSerializer, HackathonProjectSerializer +from .router import HackathonEventSerializer, HackathonProjectSerializer, TribesSerializer, TribesTeamSerializer from .utils import ( apply_new_bounty_deadline, get_bounty, get_bounty_id, get_context, get_custom_avatars, get_unrated_bounties_count, get_web3, has_tx_mined, is_valid_eth_address, re_market_bounty, record_user_action_on_interest, @@ -915,13 +915,26 @@ def projects_fetch(request): page = clean(request.GET.get('page', 1)) hackathon_id = clean(request.GET.get('hackathon', '')) - try: - hackathon_event = HackathonEvent.objects.get(id=hackathon_id) - except HackathonEvent.DoesNotExist: - hackathon_event = HackathonEvent.objects.last() + if hackathon_id: + try: + hackathon_event = HackathonEvent.objects.get(id=hackathon_id) + except HackathonEvent.DoesNotExist: + hackathon_event = HackathonEvent.objects.last() - projects = HackathonProject.objects.filter(hackathon=hackathon_event).exclude(status='invalid').prefetch_related('profiles', 'bounty').order_by(order_by, 'id') + projects = HackathonProject.objects.filter(hackathon=hackathon_event).exclude( + status='invalid').prefetch_related('profiles', 'bounty').order_by(order_by, 'id') + if sponsor: + projects = projects.filter( + Q(bounty__github_url__icontains=sponsor) + ) + elif sponsor: + sponsor_profile = Profile.objects.get(handle=sponsor) + if sponsor_profile: + projects = HackathonProject.objects.filter(hackathon__sponsor_profiles__in=[sponsor_profile]).exclude( + status='invalid').prefetch_related('profiles', 'bounty').order_by(order_by, 'id') + else: + projects = [] if q: projects = projects.filter( Q(name__icontains=q) | @@ -929,11 +942,6 @@ def projects_fetch(request): Q(profiles__handle__icontains=q) ) - if sponsor: - projects = projects.filter( - Q(bounty__github_url__icontains=sponsor) - ) - if skills: projects = projects.filter( Q(profiles__keywords__icontains=skills) @@ -974,7 +982,9 @@ def users_fetch(request): leaderboard_rank = request.GET.get('leaderboard_rank', '').strip().split(',') rating = int(request.GET.get('rating', '0')) organisation = request.GET.get('organisation', '') + tribe = request.GET.get('tribe', '') hackathon_id = request.GET.get('hackathon', '') + user_filter = request.GET.get('user_filter', '') user_id = request.GET.get('user', None) if user_id: @@ -1002,8 +1012,20 @@ def users_fetch(request): if q: profile_list = profile_list.filter(Q(handle__icontains=q) | Q(keywords__icontains=q)) - show_banner = None + if tribe: + + profile_list = profile_list.filter(Q(follower__org__handle__in=[tribe]) | Q(organizations_fk__handle__in=[tribe])) + + if user_filter and user_filter != 'all': + if user_filter == 'owners': + profile_list = profile_list.filter(Q(organizations_fk__handle__in=[tribe])) + elif user_filter == 'members': + profile_list = profile_list.exclude(Q(organizations_fk__handle__in=[tribe])) + elif user_filter == 'hackers': + profile_list = profile_list.filter(Q(fulfilled__bounty__github_url__icontains=tribe) | Q(project_profiles__hackathon__sponsor_profiles__handle__in=[tribe]) | Q(hackathon_registration__hackathon__sponsor_profiles__handle__in=[tribe])) + + show_banner = None if persona: if persona == 'funder': profile_list = profile_list.filter(dominant_persona='funder') @@ -1055,7 +1077,11 @@ def previous_worked(): this_page = Profile.objects_full.filter(pk__in=[ele.pk for ele in this_page]).order_by('-follower_count', 'id') else: - profile_list = profile_list.order_by(order_by, '-earnings_count', 'id') + try: + profile_list = profile_list.order_by(order_by, '-earnings_count', 'id') + except profile_list.FieldError: + profile_list = profile_list.order_by('-earnings_count', 'id') + profile_list = profile_list.values_list('pk', flat=True) all_pages = Paginator(profile_list, limit) @@ -2769,7 +2795,7 @@ def profile(request, handle, tab=None): context = { 'hidden': True, 'ratings': range(0,5), - 'followers': TribeMember.objects.filter(org=request.user.profile), + 'followers': TribeMember.objects.filter(org=request.user.profile) if request.user.is_authenticated and hasattr(request.user, 'profile') else [], 'profile': { 'handle': handle, 'avatar_url': f"/dynamic/avatar/Self", @@ -2810,18 +2836,29 @@ def profile(request, handle, tab=None): context['tab'] = tab context['show_activity'] = request.GET.get('p', False) != False context['is_my_org'] = request.user.is_authenticated and any([handle.lower() == org.lower() for org in request.user.profile.organizations ]) - context['is_on_tribe'] = False - if request.user.is_authenticated: - context['is_on_tribe'] = request.user.profile.tribe_members.filter(org__handle=handle.lower()) + if request.user.is_authenticated and hasattr(request.user, 'profile'): + context['is_on_tribe'] = request.user.profile.tribe_members.filter(org__handle=handle.lower()).exists() + else: + context['is_on_tribe'] = False context['ratings'] = range(0,5) context['feedbacks_sent'] = [fb.pk for fb in profile.feedbacks_sent.all() if fb.visible_to(request.user)] context['feedbacks_got'] = [fb.pk for fb in profile.feedbacks_got.all() if fb.visible_to(request.user)] context['all_feedbacks'] = context['feedbacks_got'] + context['feedbacks_sent'] context['tags'] = [('#announce','bullhorn'), ('#mentor','terminal'), ('#jobs','code'), ('#help','laptop-code'), ('#other','briefcase'), ] - context['followers'] = TribeMember.objects.filter(org=request.user.profile) - context['following'] = TribeMember.objects.filter(profile=request.user.profile) + context['followers'] = TribeMember.objects.filter(org=request.user.profile) if request.user.is_authenticated and hasattr(request.user, 'profile') else [] + context['following'] = TribeMember.objects.filter(profile=request.user.profile) if request.user.is_authenticated and hasattr(request.user, 'profile') else [] context['foltab'] = request.GET.get('sub', 'followers') + active_tab = 0 + if tab == "townsquare": + active_tab = 0 + elif tab == "projects": + active_tab = 1 + elif tab == "people": + active_tab = 2 + elif tab == "bounties": + active_tab = 3 + context['active_panel'] = active_tab tab = get_profile_tab(request, profile, tab, context) if type(tab) == dict: context.update(tab) @@ -2832,6 +2869,16 @@ def profile(request, handle, tab=None): if request.user.is_authenticated and not context['is_my_profile']: ProfileView.objects.create(target=profile, viewer=request.user.profile) + if profile.is_org and profile.handle.lower() in ['gitcoinco']: + + context['currentProfile'] = TribesSerializer(profile, context={'request': request}).data + context['target'] = f'/activity?what=tribe:{profile.handle}' + context['is_on_tribe'] = json.dumps(context['is_on_tribe']) + context['is_my_org'] = json.dumps(context['is_my_org']) + context['profile_handle'] = profile.handle + + return TemplateResponse(request, 'profiles/tribes-vue.html', context, status=status) + return TemplateResponse(request, 'profiles/profile.html', context, status=status) @@ -4499,13 +4546,18 @@ def join_tribe(request, handle): if request.user.is_authenticated: profile = request.user.profile if hasattr(request.user, 'profile') else None try: - TribeMember.objects.get(profile=profile, org__handle=handle.lower()).delete() + try: + TribeMember.objects.get(profile=profile, org__handle=handle.lower()).delete() + except TribeMember.MultipleObjectsReturned: + TribeMember.objects.filter(profile=profile, org__handle=handle.lower()).delete() + return JsonResponse( - { - 'success': True, - 'is_member': False, - }, - status=200) + { + 'success': True, + 'is_member': False, + }, + status=200 + ) except TribeMember.DoesNotExist: kwargs = { 'org': Profile.objects.filter(handle=handle.lower()).first(), @@ -4524,7 +4576,9 @@ def join_tribe(request, handle): ) else: return JsonResponse( - { 'error': _('You must be authenticated via github to use this feature!') }, + { + 'error': _('You must be authenticated via github to use this feature!') + }, status=401 ) @@ -4611,6 +4665,13 @@ def save_tribe(request,handle): tribe.tribe_description = tribe_description tribe.save() + if request.FILES.get('cover_image'): + cover_image = request.FILES.get('cover_image', None) + if cover_image: + tribe = Profile.objects.filter(handle=handle.lower()).first() + tribe.tribes_cover_image = cover_image + tribe.save() + if request.POST.get('tribe_priority'): tribe_priority = clean( diff --git a/app/retail/templates/shared/activity.html b/app/retail/templates/shared/activity.html index fa86f5cf2ff..0d79afcbcf9 100644 --- a/app/retail/templates/shared/activity.html +++ b/app/retail/templates/shared/activity.html @@ -15,7 +15,8 @@ along with this program. If not, see . {% endcomment %} -{% load humanize i18n static %} + +{% load humanize i18n static grants_extra %}
    {% if row.activity_type == 'new_bounty' %}
    @@ -54,7 +55,9 @@ {% endif %} {% if row.profile.data.type == 'Organization'%} {% if row.profile.handle not in my_tribes|join:"," %} -
    Join Tribe
    +
    + follow +
    {% endif %} {% endif %}
    @@ -365,12 +368,35 @@ {% endif %}
    +
    + {% if row.activity_type == 'new_bounty' %} +
    + + New bounty +
    + {% elif row.activity_type == 'new_kudos' %} +
    + + New kudos +
    + {% elif row.activity_type == 'new_hackathon_project' %} +
    + + New Project +
    + {% elif row.activity_type == 'new_grant' %} +
    + + New Grant +
    + {% endif %}
    {% if not hide_date %} {{ row.created_human_time }} {% endif %}
    +
    {% if row.kudos %} @@ -398,7 +424,98 @@
    {% endif %}
    - {% if row.metadata.poll_choices %} + {% if row.activity_type == 'new_bounty' %} +
    +
    +
    + {% if row.bounty.funding_organisation %} + + {% else %} + + {% endif %} +
    +
    +
    +
    + {{ row.bounty.title }} +
    + +
    +
    + {% if row.bounty.funding_organisation %} + {{ row.bounty.funding_organisation }} + {% else %} + {{ row.bounty.bounty_owner_github_username }} + {% endif %} +
    +
    +
    +
    +
    +
    +

    + {{ row.bounty.value_true }} + {{ row.bounty.token_name }} +

    +
    + {% if row.bounty.value_in_usdt_now %} +
    +

    + {{ row.bounty.value_in_usdt_now }} + USD +

    +
    + {% endif %} +
    +
    + {% elif row.activity_type == 'new_kudos' %} +
    +
    +
    + +
    +
    +

    {{ row.kudos.ui_name }}

    +

    {{ row.kudos.description|truncatechars:200}}

    +
    +
    +
    + {% elif row.activity_type == 'new_hackathon_project' %} +
    +
    +
    + {% if row.project.logo %} + Hackathon logo + {% else %} + {{row.project.bounty.org_name}} + {% endif %} +
    +
    +

    {{ row.project.name }}

    +

    for {% if row.project.bounty.funding_organisation %} + {{ row.project.bounty.funding_organisation }} + {% else %} + {{ row.project.bounty.bounty_owner_github_username }} + {% endif %} on {{ row.project.hackathon.name }}

    +
    +
    +

    Looking for
    team members

    +
    +
    +
    + {% elif row.activity_type == 'new_grant' %} +
    +
    +
    + +
    +
    +

    {{ row.grant.title }}

    +

    {{ row.grant.description|truncatechars:200}}

    +
    +
    +
    + {% elif row.metadata.poll_choices %}
    {% for ele in row.metadata.poll_choices %}