11"""Plain chat integration for customer support.
22
33This module provides integration with Plain.com chat widget for live customer support.
4+ See: https://plain.support.site/article/chat-customization
45"""
56
7+ from typing import Any
8+
69import reflex as rx
10+ from reflex .components .tags .tag import Tag
11+
12+ PLAIN_APP_ID = "liveChatApp_01KGG4JD5JHG8JY8X5CCN7811V"
13+
14+
15+ class PlainChat (rx .Component ):
16+ """Plain chat widget component.
17+
18+ See: https://plain.support.site/article/live-chat-authentication
19+
20+ Note: If you provide email, you MUST also provide email_hash.
21+ The hash must be generated server-side using HMAC-SHA256 with your Plain secret.
22+ """
723
8- PLAIN_INIT_SCRIPT = """
9- (function(d, script) {{
10- script = d.createElement('script');
24+ tag = "PlainChat"
25+
26+ # Customer details (all optional)
27+ full_name : rx .Var [str ] = rx .Var .create ("" )
28+ short_name : rx .Var [str ] = rx .Var .create ("" )
29+ chat_avatar_url : rx .Var [str ] = rx .Var .create ("" )
30+ email : rx .Var [str ] = rx .Var .create ("" )
31+ email_hash : rx .Var [str ] = rx .Var .create ("" )
32+
33+ # Thread details (optional)
34+ external_id : rx .Var [str ] = rx .Var .create ("" )
35+
36+ # Options
37+ hide_launcher : rx .Var [bool ] = rx .Var .create (True )
38+ require_authentication : rx .Var [bool ] = rx .Var .create (False )
39+
40+ def add_hooks (self ) -> list [str | rx .Var ]:
41+ """Add hooks to initialize Plain chat widget."""
42+ return [
43+ rx .Var (
44+ f"""const PlainChatComponent = (() => {{
45+ // Load Plain chat script
46+ const script = document.createElement('script');
1147 script.async = false;
12- script.onload = function(){{
13- Plain.init({{
14- appId: 'liveChatApp_01KGG4JD5JHG8JY8X5CCN7811V',
15- hideLauncher: {hide_launcher},
48+ script.src = 'https://chat.cdn-plain.com/index.js';
49+ script.onload = function() {{
50+ // Build customer details, only including non-empty values
51+ const customerDetails = {{}};
52+ if ({ self .full_name !s} ) customerDetails.fullName = { self .full_name !s} ;
53+ if ({ self .short_name !s} ) customerDetails.shortName = { self .short_name !s} ;
54+ if ({ self .chat_avatar_url !s} ) customerDetails.chatAvatarUrl = { self .chat_avatar_url !s} ;
55+ if ({ self .email !s} ) customerDetails.email = { self .email !s} ;
56+ if ({ self .email_hash !s} ) customerDetails.emailHash = { self .email_hash !s} ;
57+
58+ // Build thread details, only including non-empty values
59+ const threadDetails = {{}};
60+ if ({ self .external_id !s} ) threadDetails.externalId = { self .external_id !s} ;
61+
62+ // Build init options
63+ const initOptions = {{
64+ appId: '{ PLAIN_APP_ID } ',
65+ hideLauncher: { self .hide_launcher !s} ,
1666 hideBranding: true,
1767 theme: 'auto',
18- }});
68+ }};
69+
70+ if ({ self .require_authentication !s} ) initOptions.requireAuthentication = true;
71+ if (Object.keys(customerDetails).length > 0) initOptions.customerDetails = customerDetails;
72+ if (Object.keys(threadDetails).length > 0) initOptions.threadDetails = threadDetails;
73+
74+ Plain.init(initOptions);
1975 }};
20- script.src = 'https://chat.cdn-plain.com/index.js';
21- d.getElementsByTagName('head')[0].appendChild(script);
22- }}(document));
23- """
76+ document.head.appendChild(script);
77+ return null;
78+ }})()"""
79+ )
80+ ]
81+
82+ def _render (self , props : dict [str , Any ] | None = None ) -> Tag :
83+ return Tag ("" )
84+
85+
86+ plain_chat = PlainChat .create
2487
2588
2689def open_plain_chat () -> rx .event .EventSpec :
@@ -32,15 +95,3 @@ def open_plain_chat() -> rx.event.EventSpec:
3295 return rx .call_script (
3396 "try { Plain.open(); } catch (e) { console.error('Plain chat not available:', e); }"
3497 )
35-
36-
37- def get_plain_script (hide_launcher : bool = True ) -> rx .Component :
38- """Get the Plain chat initialization script component.
39-
40- Args:
41- hide_launcher: Whether to hide the default Plain chat launcher button. Defaults to True.
42-
43- Returns:
44- A Reflex script component that initializes the Plain chat widget.
45- """
46- return rx .script (PLAIN_INIT_SCRIPT .format (hide_launcher = str (hide_launcher ).lower ()))
0 commit comments