Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Grant Notes feed #3428 #3532

Merged
merged 32 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e06fbb3
feat: add API to retrieve grant notes for specific user via Finder API
sushilrajeeva Sep 5, 2024
6ef03d4
ES Lint fixes
sushilrajeeva Sep 5, 2024
96d4f1b
ES Lint fixes
sushilrajeeva Sep 5, 2024
12b414b
ES Lint fixes
sushilrajeeva Sep 5, 2024
2faf708
ES Lint fixes
sushilrajeeva Sep 5, 2024
5ab5934
ES Lint fixes
sushilrajeeva Sep 6, 2024
d85c8a4
Merge branch 'main' into sushilrajeeva/feat/retrieve-grant-notes-spec…
sushilrajeeva Sep 6, 2024
581d7c7
first pass
greg-adams Sep 18, 2024
61ba674
Merge remote-tracking branch 'origin/sushilrajeeva/feat/retrieve-gran…
greg-adams Sep 18, 2024
9afa3f2
add notes for user
greg-adams Sep 18, 2024
4005d0c
adjust styling
greg-adams Sep 18, 2024
f64c88f
Merge remote-tracking branch 'origin/main' into feat/grant-notes
greg-adams Sep 19, 2024
e3060ff
refine grant notes
greg-adams Sep 20, 2024
8be4f86
add FE tests
greg-adams Sep 20, 2024
ce1cc79
Merge remote-tracking branch 'origin/main' into feat/grant-notes
greg-adams Sep 20, 2024
2aadb7d
adjust grant notes
greg-adams Sep 20, 2024
347bfe3
adjust styling
greg-adams Sep 20, 2024
4ee7397
adjust notes display
greg-adams Sep 20, 2024
81e9af0
adjust styling
greg-adams Sep 20, 2024
f28863d
fix test
greg-adams Sep 20, 2024
153860b
extract header text to component
greg-adams Sep 23, 2024
371a192
adjust styling
greg-adams Sep 24, 2024
5518c9f
Merge remote-tracking branch 'origin/main' into feat/grant-notes
greg-adams Sep 24, 2024
fcc18f2
correct limit
greg-adams Sep 24, 2024
4ee1a00
remove unused
greg-adams Sep 26, 2024
10bbf41
adjust form validation
greg-adams Sep 26, 2024
a5ae245
use cursor naming
greg-adams Sep 30, 2024
8872538
include limit feature flag
greg-adams Sep 30, 2024
3173311
limit unbounded row query
greg-adams Sep 30, 2024
894e607
Merge remote-tracking branch 'origin/main' into feat/grant-notes
greg-adams Sep 30, 2024
3cb0e29
Merge remote-tracking branch 'origin/main' into feat/grant-notes
greg-adams Sep 30, 2024
c4e6139
Merge branch 'main' into feat/grant-notes
greg-adams Oct 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions packages/client/src/components/CopyButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
:variant="copySuccessTimeout === null ? '' : 'success'"
/>
<b-tooltip
v-if="$refs.container"
v-if="mounted"
ref="tooltip"
:target="$refs.container"
triggers=""
:show="copySuccessTimeout !== null"
triggers="manual"
boundary="window"
>
<b-icon
icon="check-circle-fill"
Expand All @@ -41,21 +42,25 @@ export default {
data() {
return {
copySuccessTimeout: null,
mounted: false,
};
},
mounted() {
this.mounted = true;
},
methods: {
copyToClipboard() {
navigator.clipboard.writeText(this.copyText);

this.$refs.tooltip.$emit('open');

// Show the success indicator
// (Clear previous timeout to ensure multiple clicks in quick succession don't cause issues)
clearTimeout(this.copySuccessTimeout);
this.copySuccessTimeout = setTimeout(
() => {
this.copySuccessTimeout = null;
},
1000,
);
this.copySuccessTimeout = setTimeout(() => {
this.$refs.tooltip.$emit('close');
this.copySuccessTimeout = null;
}, 1000);
},
},
};
Expand Down
25 changes: 15 additions & 10 deletions packages/client/src/components/GrantActivity.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<b-card
header-bg-variant="white"
footer-bg-variant="white"
footer-class="p-0"
>
<template #header>
<h3 class="my-2">
Expand All @@ -23,7 +24,6 @@
block
size="lg"
:variant="followBtnVariant"
class="mb-4"
data-follow-btn
:disabled="!followStateLoaded"
@click="toggleFollowState"
Expand All @@ -38,16 +38,17 @@
{{ followBtnLabel }}
</span>
</b-button>
<div>
<div
v-if="grantHasFollowers || showNotesSummary"
class="mt-4"
>
<b-link
v-if="grantHasFollowers"
:class="followSummaryClass"
data-follow-summary
@click="$bvModal.show('grant-followers-modal')"
>
{{ followSummaryText }}
</b-link>

<span
v-if="grantHasFollowers && showNotesSummary"
class="mx-1"
Expand All @@ -58,6 +59,7 @@

<template #footer>
<!-- Feed -->
<GrantNotes @noteSaved="fetchFollowAndNotes" />
</template>
</b-card>

Expand All @@ -72,10 +74,12 @@

<script>
import { mapActions, mapGetters } from 'vuex';
import GrantNotes from '@/components/GrantNotes.vue';
import GrantFollowersModal from '@/components/Modals/GrantFollowers.vue';

export default {
components: {
GrantNotes,
GrantFollowersModal,
},
data() {
Expand All @@ -97,9 +101,6 @@ export default {
followBtnVariant() {
return this.userIsFollowing ? 'success' : 'primary';
},
followSummaryClass() {
return this.followStateLoaded ? 'visible' : 'invisible';
},
followSummaryText() {
const userIsFollower = this.userIsFollowing;
const firstFollowerName = userIsFollower ? 'you' : this.followers[0].user.name;
Expand Down Expand Up @@ -131,12 +132,11 @@ export default {
textCount = '50+';
}

return `${textCount} notes`;
return `${textCount} ${this.notes.length === 1 ? 'note' : 'notes'}`;
},
},
async beforeMount() {
this.fetchFollowState();
this.fetchAllNotes();
this.fetchFollowAndNotes();
},
methods: {
...mapActions({
Expand All @@ -146,6 +146,11 @@ export default {
followGrantForCurrentUser: 'grants/followGrantForCurrentUser',
unfollowGrantForCurrentUser: 'grants/unfollowGrantForCurrentUser',
}),
fetchFollowAndNotes() {
this.followStateLoaded = false;
this.fetchFollowState();
this.fetchAllNotes();
},
async fetchFollowState() {
const followCalls = [
this.getFollowerForGrant({ grantId: this.currentGrant.grant_id }),
Expand Down
37 changes: 37 additions & 0 deletions packages/client/src/components/GrantNote.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import GrantNote from '@/components/GrantNote.vue';
import { id } from '@/helpers/testHelpers';

const note = {
id: id(),
createdAt: new Date().toISOString(),
isRevised: true,
text: 'Text',
grant: { id: id() },
user: {
id: id(),
name: 'User',
email: 'email@net',
avatarColor: 'red',
team: {
id: id(),
name: 'Team',
},
organization: {
id: id(),
name: 'Org',
},
},
};

describe('GrantNote component', () => {
it('renders', () => {
const wrapper = mount(GrantNote, {
props: {
note,
},
});
expect(wrapper.exists()).toBe(true);
});
});
123 changes: 123 additions & 0 deletions packages/client/src/components/GrantNote.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<template>
<div class="d-flex note-container">
<div class="d-flex flex-column">
<UserAvatar
:user-name="note.user.name"
size="2.5rem"
:color="note.user.avatarColor"
/>
<div class="note_vertical position-relative flex-grow-1" />
</div>

<div class="d-flex flex-column flex-grow-1 has-flexi-truncate ml-2">
<UserHeaderText
:name="note.user.name"
:team="note.user.team.name"
/>
<div class="text-gray-500">
<CopyButton
:copy-text="note.user.email"
hide-icon
>
{{ note.user.email }}
</CopyButton>
</div>
<div class="mt-1 text-gray-600">
{{ note.text }}
</div>
<div class="d-flex mt-1 align-items-end">
<span class="note-date-text">
{{ timeElapsedString }}
<span
v-if="note.isRevised"
class="text-gray-500"
>(edited)</span>
</span>
<div class="ml-auto">
<slot name="actions" />
</div>
</div>
</div>
</div>
</template>

<script>
import { DateTime } from 'luxon';
import UserAvatar from '@/components/UserAvatar.vue';
import CopyButton from '@/components/CopyButton.vue';
import UserHeaderText from '@/components/UserHeaderText.vue';

export const formatActivityDate = (createdAtISO) => {
const createdDate = DateTime.fromISO(createdAtISO);
const today = DateTime.now().endOf('day');

let dateText = '';
if (createdDate.hasSame(today, 'day')) {
dateText = createdDate.toRelativeCalendar({ base: today, unit: 'days' });
} else if (createdDate > today.minus({ days: 7 })) {
dateText = createdDate.toRelative({ base: today, unit: 'days' });
} else {
dateText = createdDate.toFormat('MMMM d');
}

return dateText;
};

export default {
components: {
UserAvatar,
CopyButton,
UserHeaderText,
},
props: {
note: {
type: Object,
required: true,
},
},
computed: {
timeElapsedString() {
return formatActivityDate(this.note.createdAt);
},
},
};

</script>

<style lang="scss" scoped>
@import '@/scss/colors-semantic-tokens.scss';
@import '@/scss/colors-base-tokens.scss';

.note-container {
padding: 1rem 1.25rem;
}

.text-gray-500 {
color: $raw-gray-500
}

.note-date-text {
font-size:0.75rem;
}

.note-date-text:first-letter {
text-transform: capitalize;
}

.text-gray-600 {
color: $raw-gray-600;
font-weight: 400;
}

.note_vertical:before {
content: "";
background: $raw-gray-600;
height: calc(100% - .5rem);
width: 1px;
position: absolute;
left: calc(2.5rem / 2);
top: 0.375rem;
bottom: 0;
}

</style>
Loading
Loading