Skip to content

Commit e7e3d9e

Browse files
author
FalkWolsky
committed
Social Sharing, News Page refresh
1 parent d828825 commit e7e3d9e

File tree

9 files changed

+440
-44
lines changed

9 files changed

+440
-44
lines changed

client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"flag-icons": "^7.2.1",
8484
"number-precision": "^1.6.0",
8585
"react-countup": "^6.5.3",
86+
"react-github-btn": "^1.4.0",
8687
"react-player": "^2.11.0",
8788
"resize-observer-polyfill": "^1.5.1",
8889
"rollup": "^4.22.5",

client/packages/lowcoder-design/src/icons/index.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,6 @@ export { ReactComponent as BorderWidthIcon } from "./remix/space.svg";
221221
export { ReactComponent as BorderStyleIcon } from "./remix/separator.svg";
222222
export { ReactComponent as RotationIcon } from "./remix/clockwise-line.svg";
223223
export { ReactComponent as BorderRadiusIcon } from "./remix/rounded-corner.svg";
224-
225-
// Falk: TODO
226224
export { ReactComponent as ShadowIcon } from "./remix/shadow-line.svg";
227225
export { ReactComponent as OpacityIcon } from "./remix/contrast-drop-2-line.svg";
228226
export { ReactComponent as AnimationIcon } from "./remix/loader-line.svg";
@@ -257,6 +255,13 @@ export { ReactComponent as EnterpriseIcon } from "./remix/earth-line.svg";
257255
export { ReactComponent as VerticalIcon } from "./remix/vertical.svg";
258256
export { ReactComponent as HorizontalIcon } from "./remix/horizontal.svg";
259257

258+
// Social Sharing
259+
export { ReactComponent as TwitterIcon } from "./remix/twitter-x-line.svg";
260+
export { ReactComponent as LinkedInIcon } from "./remix/linkedin-box-fill.svg";
261+
export { ReactComponent as FacebookIcon } from "./remix/facebook-circle-fill.svg";
262+
export { ReactComponent as MediumIcon } from "./remix/medium-fill.svg";
263+
export { ReactComponent as RedditIcon } from "./remix/reddit-line.svg";
264+
260265

261266
// components
262267

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import Api from "api/api";
2+
import axios, { AxiosInstance, AxiosRequestConfig, CancelToken } from "axios";
3+
import { calculateFlowCode } from "./apiUtils";
4+
5+
export type ResponseType = {
6+
response: any;
7+
};
8+
9+
// Axios Configuration
10+
const lcHeaders = {
11+
"Lowcoder-Token": calculateFlowCode(),
12+
"Content-Type": "application/json"
13+
};
14+
15+
let axiosIns: AxiosInstance | null = null;
16+
17+
const getAxiosInstance = (clientSecret?: string) => {
18+
if (axiosIns && !clientSecret) {
19+
return axiosIns;
20+
}
21+
22+
const headers: Record<string, string> = {
23+
"Content-Type": "application/json",
24+
};
25+
26+
const apiRequestConfig: AxiosRequestConfig = {
27+
baseURL: "https://api-service.lowcoder.cloud/api/flow",
28+
headers,
29+
};
30+
31+
axiosIns = axios.create(apiRequestConfig);
32+
return axiosIns;
33+
};
34+
35+
class NewsApi extends Api {
36+
37+
static async secureRequest(body: any, timeout: number = 6000): Promise<any> {
38+
let response;
39+
const axiosInstance = getAxiosInstance();
40+
41+
// Create a cancel token and set timeout for cancellation
42+
const source = axios.CancelToken.source();
43+
const timeoutId = setTimeout(() => {
44+
source.cancel("Request timed out.");
45+
}, timeout);
46+
47+
// Request configuration with cancel token
48+
const requestConfig: AxiosRequestConfig = {
49+
method: "POST",
50+
withCredentials: true,
51+
data: body,
52+
cancelToken: source.token, // Add cancel token
53+
};
54+
55+
try {
56+
response = await axiosInstance.request(requestConfig);
57+
} catch (error) {
58+
if (axios.isCancel(error)) {
59+
// Retry once after timeout cancellation
60+
try {
61+
// Reset the cancel token and retry
62+
const retrySource = axios.CancelToken.source();
63+
const retryTimeoutId = setTimeout(() => {
64+
retrySource.cancel("Retry request timed out.");
65+
}, 10000);
66+
67+
response = await axiosInstance.request({
68+
...requestConfig,
69+
cancelToken: retrySource.token,
70+
});
71+
72+
clearTimeout(retryTimeoutId);
73+
} catch (retryError) {
74+
console.warn("Error at Secure Flow Request. Retry failed:", retryError);
75+
throw retryError;
76+
}
77+
} else {
78+
console.warn("Error at Secure Flow Request:", error);
79+
throw error;
80+
}
81+
} finally {
82+
clearTimeout(timeoutId); // Clear the initial timeout
83+
}
84+
85+
return response;
86+
}
87+
}
88+
89+
// API Functions
90+
91+
// secure/get-youtube-videos
92+
// secure/get-github-releases
93+
94+
export const getReleases = async () => {
95+
const apiBody = {
96+
path: "webhook/secure/get-github-releases",
97+
data: {},
98+
method: "post",
99+
headers: lcHeaders
100+
};
101+
try {
102+
const result = await NewsApi.secureRequest(apiBody);
103+
return result?.data[0]?.github?.length > 0 ? result.data[0].github as any[] : [];
104+
} catch (error) {
105+
console.error("Error getting news:", error);
106+
throw error;
107+
}
108+
};
109+
110+
export const getYoutubeVideos = async () => {
111+
const apiBody = {
112+
path: "webhook/secure/get-youtube-videos",
113+
data: {},
114+
method: "post",
115+
headers: lcHeaders
116+
};
117+
try {
118+
const result = await NewsApi.secureRequest(apiBody);
119+
return result?.data[0]?.youtube?.length > 0 ? result.data[0].youtube as any[] : [];
120+
} catch (error) {
121+
console.error("Error getting news:", error);
122+
throw error;
123+
}
124+
};
125+
126+
export const getHubspotContent = async () => {
127+
const apiBody = {
128+
path: "webhook/secure/get-hubspot-content",
129+
data: {},
130+
method: "post",
131+
headers: lcHeaders
132+
};
133+
try {
134+
const result = await NewsApi.secureRequest(apiBody);
135+
return result?.data[0]?.hubspot?.length > 0 ? result.data[0].hubspot as any[] : [];
136+
} catch (error) {
137+
console.error("Error getting news:", error);
138+
throw error;
139+
}
140+
};
141+
142+
export default NewsApi;

client/packages/lowcoder/src/components/PermissionDialog/AppPermissionDialog.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ import { TacoButton } from "components/button";
2727
import copy from "copy-to-clipboard";
2828
import { StyledLoading } from "./commonComponents";
2929
import { PermissionRole } from "./Permission";
30-
import { SHARE_TITLE } from "../../constants/apiConstants";
3130
import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
3231
import { default as Divider } from "antd/es/divider";
32+
import { SocialShareButtons } from "components/SocialShareButtons";
3333

3434
export const AppPermissionDialog = React.memo((props: {
3535
applicationId: string;
@@ -83,7 +83,7 @@ export const AppPermissionDialog = React.memo((props: {
8383
return (
8484
<PermissionDialog
8585
{...props}
86-
title={SHARE_TITLE}
86+
title={trans("home.appSharingDialogueTitle")}
8787
ownerLabel={trans("home.allPermissions")}
8888
viewBodyRender={(list) => {
8989
if (!appPermissionInfo) {
@@ -96,6 +96,7 @@ export const AppPermissionDialog = React.memo((props: {
9696
applicationId={applicationId}
9797
permissionInfo={appPermissionInfo!}
9898
/>
99+
<Divider/>
99100
{list}
100101
</>
101102
);
@@ -206,6 +207,8 @@ function AppShareView(props: {
206207
useEffect(() => {
207208
setPublicToMarketplace(permissionInfo.publicToMarketplace);
208209
}, [permissionInfo.publicToMarketplace]);
210+
const inviteLink = window.location.origin + APPLICATION_VIEW_URL(props.applicationId, "view");
211+
209212
return (
210213
<div style={{ marginBottom: "22px" }}>
211214

@@ -247,7 +250,19 @@ function AppShareView(props: {
247250
{trans("home.marketplaceGoodPublishing")}
248251
</div><Divider/></>}
249252

250-
{isPublic && <AppInviteView appId={applicationId} />}
253+
{isPublic && <AppInviteView appId={applicationId} />}
254+
255+
{isPublic &&
256+
<>
257+
<Divider />
258+
<SocialShareButtons
259+
url={inviteLink}
260+
text={trans("home.appSocialSharingMessage")}
261+
/>
262+
</>
263+
}
264+
265+
251266
</div>
252267
);
253268
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import React from "react";
2+
import styled from "styled-components";
3+
import { trans } from "../i18n";
4+
import {
5+
TwitterIcon,
6+
LinkedInIcon,
7+
FacebookIcon,
8+
MediumIcon,
9+
RedditIcon,
10+
} from "lowcoder-design";
11+
12+
const ShareWrapper = styled.div`
13+
margin-top: 0px;
14+
padding: 0px;
15+
`;
16+
17+
const ButtonGroup = styled.div`
18+
display: flex;
19+
gap: 12px;
20+
margin-top: 8px;
21+
22+
a {
23+
display: inline-flex;
24+
align-items: center;
25+
justify-content: center;
26+
width: 44px;
27+
height: 44px;
28+
border-radius: 4px;
29+
background-color: #f5f5f5;
30+
text-decoration: none;
31+
color: #333;
32+
33+
&:hover {
34+
background-color: #e6e6e6;
35+
}
36+
37+
svg {
38+
width: 20px;
39+
height: 20px;
40+
}
41+
}
42+
`;
43+
44+
export const SocialShareButtons: React.FC<{ url: string; text: string }> = ({
45+
url,
46+
text,
47+
}) => {
48+
const encodedUrl = encodeURIComponent(url);
49+
const encodedText = encodeURIComponent(text);
50+
51+
return (
52+
<ShareWrapper>
53+
<div style={{ fontWeight: 500, marginBottom: 4 }}>
54+
{trans("home.appSocialSharing")}
55+
</div>
56+
<ButtonGroup>
57+
{/* Twitter supports inline text and URL */}
58+
<a
59+
href={`https://twitter.com/intent/tweet?text=${encodedText}&url=${encodedUrl}`}
60+
target="_blank"
61+
title={trans("home.socialShare") + " Twitter"}
62+
rel="noopener noreferrer"
63+
>
64+
<TwitterIcon />
65+
</a>
66+
67+
{/* Facebook ONLY accepts the URL and reads OG metadata from it */}
68+
<a
69+
href={`https://www.facebook.com/sharer/sharer.php?u=${encodedUrl}`}
70+
target="_blank"
71+
title={trans("home.socialShare") + " Facebook"}
72+
rel="noopener noreferrer"
73+
>
74+
<FacebookIcon />
75+
</a>
76+
77+
{/* LinkedIn also only uses the URL; title/summary are ignored unless OG tags exist */}
78+
<a
79+
href={`https://www.linkedin.com/sharing/share-offsite/?url=${encodedUrl}`}
80+
target="_blank"
81+
title={trans("home.socialShare") + " LinkedIn"}
82+
rel="noopener noreferrer"
83+
>
84+
<LinkedInIcon />
85+
</a>
86+
87+
{/* Reddit sharing */}
88+
<a
89+
href={`https://www.reddit.com/submit?url=${encodedUrl}&title=${encodedText}`}
90+
target="_blank"
91+
title={trans("home.socialShare") + " Reddit"}
92+
rel="noopener noreferrer"
93+
>
94+
<RedditIcon />
95+
</a>
96+
97+
{/* Medium sharing - sharing the Medium article URL directly */}
98+
<a
99+
href={"https://medium.com/new-story"}
100+
target="_blank"
101+
title={trans("home.socialShare") + " Medium"}
102+
rel="noopener noreferrer"
103+
>
104+
<MediumIcon />
105+
</a>
106+
</ButtonGroup>
107+
</ShareWrapper>
108+
);
109+
};

client/packages/lowcoder/src/i18n/locales/en.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3383,6 +3383,10 @@ export const en = {
33833383
"fileFormatError": "File format error",
33843384
"groupWithSquareBrackets": "[Group] ",
33853385
"allPermissions": "Owner",
3386+
"appSharingDialogueTitle" : "App Sharing and Permissions",
3387+
"appSocialSharing" : "Share Your App and Experience on:",
3388+
"appSocialSharingMessage" : "I made this App with Lowcoder, check it out!",
3389+
"socialShare" : "Share on",
33863390
"shareLink": "Share link: ",
33873391
"copyLink": "Copy link",
33883392
"appPublicMessage": "Make the app public. Anyone can view.",

0 commit comments

Comments
 (0)