Files
gochat/frontend/app/javascript/widget/components/HeaderActions.vue
T
rogee 321c61aaae Vendor Chatwoot Vue 3 frontend into frontend/
Copy the Chatwoot (v4.14.0) frontend runnable subset into frontend/ for
customization:
- app/javascript/ (Vue SPA: dashboard, widget, sdk, portal, superadmin)
- app/views/ (ERB templates for vite-plugin-ruby entrypoint resolution)
- app/helpers/, app/assets/ (Rails view helpers, static assets)
- enterprise/ (Enterprise edition frontend overlay)
- config/vite.json, vite.config.ts, bin/vite (Vite-Rails toolchain)
- package.json, pnpm-lock.yaml, tailwind/postcss/eslint configs
- Gemfile, Gemfile.lock (vite_rails gem for bin/vite binstub)

Excluded Rails backend: controllers, models, services, jobs, mailers,
policies, db, lib, spec, public, node_modules.

Update references to the new frontend location:
- .gitignore: exclude frontend build artifacts (node_modules, tmp, packs),
  keep frontend/bin/ and frontend/vendor/ via negation
- backend/scripts/parity_frontend_smoke.sh: CHATWOOT_DIR default
  reference/chatwoot -> ../frontend
- backend/scripts/parity_frontend_browser_smoke.mjs: same default update
- AGENTS.md: add frontend section with Rails/Vite coupling notes
- README: architecture tree includes frontend/
2026-07-07 14:56:01 +08:00

126 lines
3.2 KiB
Vue

<script>
import { mapGetters } from 'vuex';
import { IFrameHelper, RNHelper } from 'widget/helpers/utils';
import { popoutChatWindow } from '../helpers/popoutHelper';
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
import configMixin from 'widget/mixins/configMixin';
import { CONVERSATION_STATUS } from 'shared/constants/messages';
export default {
name: 'HeaderActions',
components: { FluentIcon },
mixins: [configMixin],
props: {
showPopoutButton: {
type: Boolean,
default: false,
},
showEndConversationButton: {
type: Boolean,
default: true,
},
},
computed: {
...mapGetters({
conversationAttributes: 'conversationAttributes/getConversationParams',
canUserEndConversation: 'appConfig/getCanUserEndConversation',
}),
canLeaveConversation() {
return [
CONVERSATION_STATUS.OPEN,
CONVERSATION_STATUS.SNOOZED,
CONVERSATION_STATUS.PENDING,
].includes(this.conversationStatus);
},
isIframe() {
return IFrameHelper.isIFrame();
},
isRNWebView() {
return RNHelper.isRNWebView();
},
showHeaderActions() {
return this.isIframe || this.isRNWebView || this.hasWidgetOptions;
},
conversationStatus() {
return this.conversationAttributes.status;
},
hasWidgetOptions() {
return this.showPopoutButton || this.conversationStatus === 'open';
},
},
methods: {
popoutWindow() {
this.closeWindow();
const {
location: { origin },
chatwootWebChannel: { websiteToken },
authToken,
} = window;
popoutChatWindow(
origin,
websiteToken,
this.$root.$i18n.locale,
authToken
);
},
closeWindow() {
if (IFrameHelper.isIFrame()) {
IFrameHelper.sendMessage({ event: 'closeWindow' });
} else if (RNHelper.isRNWebView) {
RNHelper.sendMessage({ type: 'close-widget' });
}
},
resolveConversation() {
this.$store.dispatch('conversation/resolveConversation');
},
},
};
</script>
<!-- eslint-disable-next-line vue/no-root-v-if -->
<template>
<div v-if="showHeaderActions" class="actions flex items-center gap-3">
<button
v-if="
canLeaveConversation &&
canUserEndConversation &&
hasEndConversationEnabled &&
showEndConversationButton
"
class="button transparent compact"
:title="$t('END_CONVERSATION')"
@click="resolveConversation"
>
<FluentIcon icon="sign-out" size="22" class="text-n-slate-12" />
</button>
<button
v-if="showPopoutButton"
class="button transparent compact new-window--button"
@click="popoutWindow"
>
<FluentIcon icon="open" size="22" class="text-n-slate-12" />
</button>
<button
class="button transparent compact close-button"
:class="{
'rn-close-button': isRNWebView,
}"
@click="closeWindow"
>
<FluentIcon icon="dismiss" size="24" class="text-n-slate-12" />
</button>
</div>
</template>
<style scoped lang="scss">
.actions {
.close-button {
display: none;
}
.rn-close-button {
display: block !important;
}
}
</style>