1/** @jsxImportSource https://esm.sh/react@18.2.0 */
2import { createRoot } from "https://esm.sh/react-dom@18.2.0/client";
3import React from "https://esm.sh/react@18.2.0";
4
5/**
8function App() {
9 // --- State declarations ---
10 const [query, setQuery] = React.useState("");
11 const [targetAccount, setTargetAccount] = React.useState("");
12 const [apiKey, setApiKey] = React.useState("");
13 const [searchType, setSearchType] = React.useState("Latest");
14 const [sinceDate, setSinceDate] = React.useState("");
15 const [untilDate, setUntilDate] = React.useState("");
16 const [desiredCount, setDesiredCount] = React.useState(40);
17 const [tweets, setTweets] = React.useState([]);
18 const [loading, setLoading] = React.useState(false);
19 const [loadingMessage, setLoadingMessage] = React.useState("");
20 const [error, setError] = React.useState(null);
21 const [currentSearchQuery, setCurrentSearchQuery] = React.useState("");
22 const [showAdvanced, setShowAdvanced] = React.useState(false);
23 const [user, setUser] = React.useState(null);
24 const [authMode, setAuthMode] = React.useState("login"); // 'login' または 'register'
25 const [authFormData, setAuthFormData] = React.useState({
26 username: "",
27 email: "",
28 password: "",
29 });
30 const [authError, setAuthError] = React.useState(null);
31 const [savedSearches, setSavedSearches] = React.useState([]);
32
33 const isFetchingRef = React.useRef(false);
34 const sourceUrl = import.meta.url.replace("esm.town", "val.town");
35
36 // --- Effects ---
37 // Check authentication status on load
38 React.useEffect(() => {
39 const checkAuth = async () => {
40 try {
55
56 // Fetch saved searches when user logs in or out
57 React.useEffect(() => {
58 if (user) {
59 fetchSavedSearches();
64
65 // --- Helper Functions ---
66 const buildQuery = React.useCallback((keywords, account, since, until) => {
67 let finalQuery = keywords.trim();
68 if (account.trim()) {
254
255 // --- Tweet Fetching Logic ---
256 const fetchAllTweets = React.useCallback(
257 async (fullQuery, type, targetCount) => {
258 // *** LOGIN CHECK ADDED ***
398 if (!user) {
399 // --- Render Login/Register Form if not logged in ---
400 return React.createElement(
401 "div",
402 { className: "app" },
403 React.createElement("h1", { className: "title" }, "X Search - Login"),
404 React.createElement(
405 "div",
406 { className: "card auth-card" },
407 React.createElement(
408 "div",
409 { className: "auth-tabs" },
410 React.createElement(
411 "button",
412 {
416 "Login",
417 ),
418 React.createElement(
419 "button",
420 {
426 ),
427 authMode === "login"
428 ? React.createElement(
429 // Login Form
430 "form",
431 { onSubmit: handleLogin, className: "auth-form" },
432 React.createElement(
433 "div",
434 { className: "field" },
435 React.createElement(
436 "label",
437 { htmlFor: "auth-email", className: "label" },
438 "Email",
439 ),
440 React.createElement("input", {
441 type: "email",
442 id: "auth-email",
448 }),
449 ),
450 React.createElement(
451 "div",
452 { className: "field" },
453 React.createElement(
454 "label",
455 { htmlFor: "auth-password", className: "label" },
456 "Password",
457 ),
458 React.createElement("input", {
459 type: "password",
460 id: "auth-password",
467 ),
468 authError
469 && React.createElement(
470 "p",
471 { className: "error auth-error" },
472 authError,
473 ),
474 React.createElement(
475 "button",
476 { type: "submit", className: "button" },
478 ),
479 )
480 : React.createElement(
481 // Register Form
482 "form",
483 { onSubmit: handleRegister, className: "auth-form" },
484 React.createElement(
485 "div",
486 { className: "field" },
487 React.createElement(
488 "label",
489 { htmlFor: "auth-username", className: "label" },
490 "Username",
491 ),
492 React.createElement("input", {
493 type: "text",
494 id: "auth-username",
500 }),
501 ),
502 React.createElement(
503 "div",
504 { className: "field" },
505 React.createElement(
506 "label",
507 { htmlFor: "auth-email-reg", className: "label" },
508 "Email",
509 ),
510 React.createElement("input", {
511 type: "email",
512 id: "auth-email-reg",
518 }),
519 ),
520 React.createElement(
521 "div",
522 { className: "field" },
523 React.createElement(
524 "label",
525 { htmlFor: "auth-password-reg", className: "label" },
526 "Password",
527 ),
528 React.createElement("input", {
529 type: "password",
530 id: "auth-password-reg",
538 ),
539 authError
540 && React.createElement(
541 "p",
542 { className: "error auth-error" },
543 authError,
544 ),
545 React.createElement(
546 "button",
547 { type: "submit", className: "button" },
551 ),
552 // Footer for login page
553 React.createElement(
554 "footer",
555 null,
556 "Powered by ",
557 React.createElement(
558 "a",
559 {
565 ),
566 " | ",
567 React.createElement(
568 "a",
569 { href: sourceUrl, target: "_top" },
574 } else {
575 // --- Render Main Application UI if logged in ---
576 return React.createElement(
577 "div",
578 { className: "app" },
579 React.createElement("h1", { className: "title" }, "X Search"),
580 // --- User Info and Logout ---
581 React.createElement(
582 "div",
583 { className: "card user-card" },
584 React.createElement(
585 "div",
586 { className: "user-info-header" },
587 React.createElement(
588 "span",
589 { className: "welcome" },
590 `Welcome, ${user.username}`,
591 ),
592 React.createElement(
593 "button",
594 { onClick: handleLogout, className: "button small" },
596 ),
597 ),
598 React.createElement(
599 "div",
600 { className: "subscription-status" },
602 ),
603 user.subscriptionStatus !== "premium"
604 && React.createElement(
605 "div",
606 { className: "upgrade-prompt" },
607 "Upgrade to Premium for unlimited saved searches and more features!",
608 React.createElement(
609 "button",
610 {
617 ),
618 // --- API Key Input ---
619 React.createElement(
620 "div",
621 { className: "card" },
622 React.createElement(
623 "div",
624 { className: "field" },
625 React.createElement(
626 "label",
627 { htmlFor: "apiKey", className: "label" },
628 "API Key",
629 ),
630 React.createElement("input", {
631 type: "password",
632 id: "apiKey",
639 ),
640 // --- Search Form ---
641 React.createElement(
642 "form",
643 { onSubmit: handleSubmit, className: "card" },
644 // Basic search fields
645 React.createElement(
646 "div",
647 { className: "search-row" },
648 React.createElement(
649 "div",
650 { className: "field account-field" },
651 React.createElement(
652 "label",
653 { htmlFor: "targetAccount", className: "label" },
654 "Account",
655 ),
656 React.createElement(
657 "div",
658 { className: "input-wrapper" },
659 React.createElement("span", { className: "input-prefix" }, "@"),
660 React.createElement("input", {
661 type: "text",
662 id: "targetAccount",
669 ),
670 ),
671 React.createElement(
672 "div",
673 { className: "field query-field" },
674 React.createElement(
675 "label",
676 { htmlFor: "queryKeywords", className: "label" },
677 "Keywords",
678 ),
679 React.createElement("input", {
680 type: "text",
681 id: "queryKeywords",
689 ),
690 // Advanced options toggle
691 React.createElement(
692 "button",
693 {
700 // Advanced options
701 showAdvanced
702 && React.createElement(
703 "div",
704 { className: "advanced-options" },
705 React.createElement(
706 "div",
707 { className: "field-row" },
708 React.createElement(
709 "div",
710 { className: "field" },
711 React.createElement(
712 "label",
713 { htmlFor: "searchType", className: "label" },
714 "Type",
715 ),
716 React.createElement(
717 "select",
718 {
723 className: "select",
724 },
725 React.createElement("option", { value: "Latest" }, "Latest"),
726 React.createElement("option", { value: "Top" }, "Top"),
727 ),
728 ),
729 React.createElement(
730 "div",
731 { className: "field" },
732 React.createElement(
733 "label",
734 { htmlFor: "desiredCount", className: "label" },
735 "Count",
736 ),
737 React.createElement("input", {
738 type: "number",
739 id: "desiredCount",
749 ),
750 ),
751 React.createElement(
752 "div",
753 { className: "field-row" },
754 React.createElement(
755 "div",
756 { className: "field" },
757 React.createElement(
758 "label",
759 { htmlFor: "sinceDate", className: "label" },
760 "Since",
761 ),
762 React.createElement("input", {
763 type: "date",
764 id: "sinceDate",
769 }),
770 ),
771 React.createElement(
772 "div",
773 { className: "field" },
774 React.createElement(
775 "label",
776 { htmlFor: "untilDate", className: "label" },
777 "Until",
778 ),
779 React.createElement("input", {
780 type: "date",
781 id: "untilDate",
789 ),
790 // Search/Stop Button
791 React.createElement(
792 "div",
793 { className: "button-row" },
794 !loading
795 ? React.createElement(
796 "button",
797 { type: "submit", className: "button" },
798 "Search",
799 )
800 : React.createElement(
801 "button",
802 {
809 ),
810 // Save Search button
811 React.createElement(
812 "div",
813 { className: "save-search-row" },
814 React.createElement(
815 "button",
816 {
824 savedSearches.length >= 3
825 && user.subscriptionStatus !== "premium"
826 && React.createElement(
827 "p",
828 { className: "limit-note" },
833 // --- Status Messages ---
834 (loading || error)
835 && React.createElement(
836 "div",
837 { className: "card status-card" },
838 loading
839 && React.createElement("p", { className: "status" }, loadingMessage),
840 error && React.createElement("p", { className: "error" }, error),
841 ),
842 // --- Saved Searches List ---
843 savedSearches.length > 0
844 && React.createElement(
845 "div",
846 { className: "card saved-searches-card" },
847 React.createElement(
848 "h2",
849 { className: "section-title" },
850 "Saved Searches",
851 ),
852 React.createElement(
853 "div",
854 { className: "saved-searches-list" },
855 savedSearches.map((search) =>
856 React.createElement(
857 "div",
858 { key: search.id, className: "saved-search-item" },
859 React.createElement(
860 "div",
861 { className: "saved-search-info" },
862 React.createElement(
863 "div",
864 { className: "saved-search-name" },
865 search.name,
866 ),
867 React.createElement(
868 "div",
869 { className: "saved-search-query" },
873 ),
874 ),
875 React.createElement(
876 "div",
877 { className: "saved-search-actions" },
878 React.createElement(
879 "button",
880 {
884 "Run",
885 ),
886 React.createElement(
887 "button",
888 {
899 // --- Results ---
900 tweets.length > 0
901 && React.createElement(
902 "div",
903 { className: "card results-card" },
904 React.createElement(
905 "div",
906 { className: "results-header" },
907 React.createElement(
908 "div",
909 { className: "results-summary" },
910 React.createElement(
911 "span",
912 { className: "results-count" },
913 tweets.length,
914 ),
915 React.createElement(
916 "span",
917 { className: "results-label" },
920 ),
921 currentSearchQuery
922 && React.createElement(
923 "div",
924 { className: "query-display" },
926 ),
927 ),
928 React.createElement(
929 "div",
930 { className: "tweet-list" },
931 tweets.map((tweet) =>
932 React.createElement(
933 "div",
934 { key: tweet.id_str, className: "tweet" },
935 // Tweet Header
936 React.createElement(
937 "div",
938 { className: "tweet-header" },
939 React.createElement("img", {
940 src: tweet.user.profile_image_url_https,
941 alt: `${tweet.user.name}'s profile`,
942 className: "avatar",
943 }),
944 React.createElement(
945 "div",
946 { className: "user-info" },
947 React.createElement(
948 "div",
949 { className: "name-row" },
950 React.createElement(
951 "span",
952 { className: "name" },
953 tweet.user.name,
954 ),
955 React.createElement(
956 "span",
957 { className: "username" },
959 ),
960 ),
961 React.createElement(
962 "div",
963 { className: "date" },
970 ),
971 // Tweet Content
972 React.createElement(
973 "p",
974 { className: "tweet-text" },
977 // Media
978 tweet.entities?.media?.length > 0
979 && React.createElement(
980 "div",
981 { className: "media-container" },
982 tweet.entities.media.map((mediaItem) =>
983 React.createElement(
984 "div",
985 { key: mediaItem.id_str, className: "media-item" },
986 mediaItem.type === "photo"
987 || mediaItem.type === "animated_gif"
988 ? React.createElement(
989 "a",
990 {
993 rel: "noopener noreferrer",
994 },
995 React.createElement("img", {
996 src: mediaItem.media_url_https,
997 alt: `Media`,
1001 )
1002 : mediaItem.type === "video"
1003 ? React.createElement(
1004 "a",
1005 {
1009 className: "video-link",
1010 },
1011 React.createElement("img", {
1012 src: mediaItem.media_url_https,
1013 alt: `Video preview`,
1015 className: "media",
1016 }),
1017 React.createElement(
1018 "div",
1019 { className: "play-icon" },
1026 ),
1027 // Tweet Footer
1028 React.createElement(
1029 "div",
1030 { className: "tweet-footer" },
1031 React.createElement(
1032 "div",
1033 { className: "metrics" },
1034 React.createElement(
1035 "span",
1036 { className: "metric" },
1038 tweet.favorite_count ?? 0,
1039 ),
1040 React.createElement(
1041 "span",
1042 { className: "metric" },
1044 tweet.retweet_count ?? 0,
1045 ),
1046 React.createElement(
1047 "span",
1048 { className: "metric" },
1051 ),
1052 ),
1053 React.createElement(
1054 "a",
1055 {
1071 && currentSearchQuery
1072 && !error
1073 && React.createElement(
1074 "div",
1075 { className: "card empty-card" },
1077 ),
1078 // --- Footer for main app ---
1079 React.createElement(
1080 "footer",
1081 null,
1082 "Powered by ",
1083 React.createElement(
1084 "a",
1085 {
1091 ),
1092 " | ",
1093 React.createElement(
1094 "a",
1095 { href: sourceUrl, target: "_top" },
1107 const root = document.getElementById("root");
1108 if (root) {
1109 createRoot(root).render(React.createElement(App));
1110 } else {
1111 console.error("Root element not found");
1/** @jsxImportSource https://esm.sh/react@18.2.0 */
2import { createRoot } from "https://esm.sh/react-dom@18.2.0/client";
3import React from "https://esm.sh/react@18.2.0";
4
5/**
7 */
8function App() {
9 const [query, setQuery] = React.useState("");
10 const [targetAccount, setTargetAccount] = React.useState("");
11 const [apiKey, setApiKey] = React.useState("");
12 const [searchType, setSearchType] = React.useState("Latest");
13 const [sinceDate, setSinceDate] = React.useState("");
14 const [untilDate, setUntilDate] = React.useState("");
15 const [desiredCount, setDesiredCount] = React.useState(40);
16 const [tweets, setTweets] = React.useState([]);
17 const [loading, setLoading] = React.useState(false);
18 const [loadingMessage, setLoadingMessage] = React.useState("");
19 const [error, setError] = React.useState(null);
20 const [currentSearchQuery, setCurrentSearchQuery] = React.useState("");
21 const [showAdvanced, setShowAdvanced] = React.useState(false);
22 const [user, setUser] = React.useState(null);
23 const [authMode, setAuthMode] = React.useState("login"); // 'login' または 'register'
24 const [authFormData, setAuthFormData] = React.useState({
25 username: "",
26 email: "",
27 password: "",
28 });
29 const [authError, setAuthError] = React.useState(null);
30 const [savedSearches, setSavedSearches] = React.useState([]);
31
32 // ページロード時に認証状態を確認するためのuseEffectを追加します
33 React.useEffect(() => {
34 // ユーザー情報を取得する非同期関数
35 const checkAuth = async () => {
72
73 // fetchSavedSearchesをユーザー情報が変わった時に実行するように変更
74 React.useEffect(() => {
75 if (user) {
76 fetchSavedSearches();
253 };
254
255 const isFetchingRef = React.useRef(false);
256 const sourceUrl = import.meta.url.replace("esm.town", "val.town");
257
258 const buildQuery = React.useCallback((keywords, account, since, until) => {
259 let finalQuery = keywords.trim();
260 if (account.trim()) {
277 }, []);
278
279 const fetchAllTweets = React.useCallback(
280 async (fullQuery, type, targetCount) => {
281 if (!apiKey.trim()) {
393 };
394
395 // Using React.createElement instead of JSX
396 return React.createElement(
397 "div",
398 { className: "app" },
399 React.createElement("h1", { className: "title" }, "X Search"),
400 // --- ここから認証UI ---
401 user
402 ? React.createElement(
403 // ログイン済みの場合
404 "div",
405 { className: "card user-card" },
406 React.createElement(
407 "div",
408 { className: "user-info-header" },
409 React.createElement(
410 "span",
411 { className: "welcome" },
412 `Welcome, ${user.username}`
413 ),
414 React.createElement(
415 "button",
416 {
421 )
422 ),
423 React.createElement(
424 "div",
425 { className: "subscription-status" },
430 // 無料ユーザー向けのアップグレード案内(ダミー)
431 user.subscriptionStatus !== "premium" &&
432 React.createElement(
433 "div",
434 { className: "upgrade-prompt" },
435 "Upgrade to Premium for unlimited saved searches and more features!",
436 React.createElement(
437 "button",
438 {
445 )
446 )
447 : React.createElement(
448 // 未ログインの場合
449 "div",
450 { className: "card auth-card" },
451 React.createElement(
452 // タブ切り替え
453 "div",
454 { className: "auth-tabs" },
455 React.createElement(
456 "button",
457 {
462 "Login"
463 ),
464 React.createElement(
465 "button",
466 {
473 ),
474 authMode === "login" // ログインフォーム
475 ? React.createElement(
476 "form",
477 { onSubmit: handleLogin, className: "auth-form" },
478 React.createElement(
479 "div",
480 { className: "field" },
481 React.createElement(
482 "label",
483 { htmlFor: "auth-email", className: "label" },
484 "Email"
485 ), // idを重複させない
486 React.createElement("input", {
487 type: "email",
488 id: "auth-email",
494 })
495 ),
496 React.createElement(
497 "div",
498 { className: "field" },
499 React.createElement(
500 "label",
501 { htmlFor: "auth-password", className: "label" },
502 "Password"
503 ), // idを重複させない
504 React.createElement("input", {
505 type: "password",
506 id: "auth-password",
513 ),
514 authError &&
515 React.createElement(
516 "p",
517 { className: "error auth-error" },
518 authError
519 ), // エラー表示
520 React.createElement(
521 "button",
522 { type: "submit", className: "button" },
524 )
525 )
526 : React.createElement(
527 // 登録フォーム
528 "form",
529 { onSubmit: handleRegister, className: "auth-form" },
530 React.createElement(
531 "div",
532 { className: "field" },
533 React.createElement(
534 "label",
535 { htmlFor: "auth-username", className: "label" },
536 "Username"
537 ), // idを重複させない
538 React.createElement("input", {
539 type: "text",
540 id: "auth-username",
546 })
547 ),
548 React.createElement(
549 "div",
550 { className: "field" },
551 React.createElement(
552 "label",
553 { htmlFor: "auth-email-reg", className: "label" },
554 "Email"
555 ), // idを重複させない
556 React.createElement("input", {
557 type: "email",
558 id: "auth-email-reg",
564 })
565 ),
566 React.createElement(
567 "div",
568 { className: "field" },
569 React.createElement(
570 "label",
571 { htmlFor: "auth-password-reg", className: "label" },
572 "Password"
573 ), // idを重複させない
574 React.createElement("input", {
575 type: "password",
576 id: "auth-password-reg",
584 ),
585 authError &&
586 React.createElement(
587 "p",
588 { className: "error auth-error" },
589 authError
590 ), // エラー表示
591 React.createElement(
592 "button",
593 { type: "submit", className: "button" },
597 ),
598 // API Key Section
599 React.createElement(
600 "div",
601 { className: "card" },
602 React.createElement(
603 "div",
604 { className: "field" },
605 React.createElement(
606 "label",
607 { htmlFor: "apiKey", className: "label" },
608 "API Key"
609 ),
610 React.createElement("input", {
611 type: "password",
612 id: "apiKey",
619 ),
620 // Search Form
621 React.createElement(
622 "form",
623 {
626 },
627 // Basic search fields
628 React.createElement(
629 "div",
630 { className: "search-row" },
631 React.createElement(
632 "div",
633 { className: "field account-field" },
634 React.createElement(
635 "label",
636 { htmlFor: "targetAccount", className: "label" },
637 "Account"
638 ),
639 React.createElement(
640 "div",
641 { className: "input-wrapper" },
642 React.createElement("span", { className: "input-prefix" }, "@"),
643 React.createElement("input", {
644 type: "text",
645 id: "targetAccount",
652 )
653 ),
654 React.createElement(
655 "div",
656 { className: "field query-field" },
657 React.createElement(
658 "label",
659 { htmlFor: "queryKeywords", className: "label" },
660 "Keywords"
661 ),
662 React.createElement("input", {
663 type: "text",
664 id: "queryKeywords",
672 ),
673 // Advanced options toggle
674 React.createElement(
675 "button",
676 {
683 // Advanced options
684 showAdvanced &&
685 React.createElement(
686 "div",
687 { className: "advanced-options" },
688 React.createElement(
689 "div",
690 { className: "field-row" },
691 React.createElement(
692 "div",
693 { className: "field" },
694 React.createElement(
695 "label",
696 { htmlFor: "searchType", className: "label" },
697 "Type"
698 ),
699 React.createElement(
700 "select",
701 {
706 className: "select",
707 },
708 React.createElement("option", { value: "Latest" }, "Latest"),
709 React.createElement("option", { value: "Top" }, "Top")
710 )
711 ),
712 React.createElement(
713 "div",
714 { className: "field" },
715 React.createElement(
716 "label",
717 { htmlFor: "desiredCount", className: "label" },
718 "Count"
719 ),
720 React.createElement("input", {
721 type: "number",
722 id: "desiredCount",
732 )
733 ),
734 React.createElement(
735 "div",
736 { className: "field-row" },
737 React.createElement(
738 "div",
739 { className: "field" },
740 React.createElement(
741 "label",
742 { htmlFor: "sinceDate", className: "label" },
743 "Since"
744 ),
745 React.createElement("input", {
746 type: "date",
747 id: "sinceDate",
752 })
753 ),
754 React.createElement(
755 "div",
756 { className: "field" },
757 React.createElement(
758 "label",
759 { htmlFor: "untilDate", className: "label" },
760 "Until"
761 ),
762 React.createElement("input", {
763 type: "date",
764 id: "untilDate",
772 ),
773 // Button
774 React.createElement(
775 "div",
776 { className: "button-row" },
777 !loading
778 ? React.createElement(
779 "button",
780 {
784 "Search"
785 )
786 : React.createElement(
787 "button",
788 {
796 // Save Search button (for logged-in users only)
797 user &&
798 React.createElement(
799 "div",
800 { className: "save-search-row" },
801 React.createElement(
802 "button",
803 {
812 savedSearches.length >= 3 &&
813 user.subscriptionStatus !== "premium" &&
814 React.createElement(
815 "p",
816 { className: "limit-note" },
821 // Status messages
822 (loading || error) &&
823 React.createElement(
824 "div",
825 { className: "card status-card" },
826 loading &&
827 React.createElement("p", { className: "status" }, loadingMessage),
828 error && React.createElement("p", { className: "error" }, error)
829 ),
830 // Saved searches list (for logged-in users only)
831 user &&
832 savedSearches.length > 0 &&
833 React.createElement(
834 "div",
835 { className: "card saved-searches-card" },
836 React.createElement(
837 "h2",
838 { className: "section-title" },
839 "Saved Searches"
840 ),
841 React.createElement(
842 "div",
843 { className: "saved-searches-list" },
844 savedSearches.map((search) =>
845 React.createElement(
846 "div",
847 { key: search.id, className: "saved-search-item" },
848 React.createElement(
849 "div",
850 { className: "saved-search-info" },
851 React.createElement(
852 "div",
853 { className: "saved-search-name" },
854 search.name
855 ),
856 React.createElement(
857 "div",
858 { className: "saved-search-query" },
862 )
863 ),
864 React.createElement(
865 "div",
866 { className: "saved-search-actions" },
867 React.createElement(
868 "button",
869 {
873 "Run"
874 ),
875 React.createElement(
876 "button",
877 {
888 // Results
889 tweets.length > 0 &&
890 React.createElement(
891 "div",
892 { className: "card results-card" },
893 React.createElement(
894 "div",
895 { className: "results-header" },
896 React.createElement(
897 "div",
898 { className: "results-summary" },
899 React.createElement(
900 "span",
901 { className: "results-count" },
902 tweets.length
903 ),
904 React.createElement(
905 "span",
906 { className: "results-label" },
909 ),
910 currentSearchQuery &&
911 React.createElement(
912 "div",
913 { className: "query-display" },
915 )
916 ),
917 React.createElement(
918 "div",
919 { className: "tweet-list" },
920 tweets.map((tweet) =>
921 React.createElement(
922 "div",
923 { key: tweet.id_str, className: "tweet" },
924 // Tweet Header
925 React.createElement(
926 "div",
927 { className: "tweet-header" },
928 React.createElement("img", {
929 src: tweet.user.profile_image_url_https,
930 alt: `${tweet.user.name}'s profile`,
931 className: "avatar",
932 }),
933 React.createElement(
934 "div",
935 { className: "user-info" },
936 React.createElement(
937 "div",
938 { className: "name-row" },
939 React.createElement(
940 "span",
941 { className: "name" },
942 tweet.user.name
943 ),
944 React.createElement(
945 "span",
946 { className: "username" },
948 )
949 ),
950 React.createElement(
951 "div",
952 { className: "date" },
963 ),
964 // Tweet Content
965 React.createElement(
966 "p",
967 { className: "tweet-text" },
970 // Media
971 tweet.entities?.media?.length > 0 &&
972 React.createElement(
973 "div",
974 { className: "media-container" },
975 tweet.entities.media.map((mediaItem) =>
976 React.createElement(
977 "div",
978 { key: mediaItem.id_str, className: "media-item" },
979 mediaItem.type === "photo" ||
980 mediaItem.type === "animated_gif"
981 ? React.createElement(
982 "a",
983 {
986 rel: "noopener noreferrer",
987 },
988 React.createElement("img", {
989 src: mediaItem.media_url_https,
990 alt: `Media`,
994 )
995 : mediaItem.type === "video"
996 ? React.createElement(
997 "a",
998 {
1002 className: "video-link",
1003 },
1004 React.createElement("img", {
1005 src: mediaItem.media_url_https,
1006 alt: `Video preview`,
1008 className: "media",
1009 }),
1010 React.createElement(
1011 "div",
1012 { className: "play-icon" },
1019 ),
1020 // Tweet Footer
1021 React.createElement(
1022 "div",
1023 { className: "tweet-footer" },
1024 React.createElement(
1025 "div",
1026 { className: "metrics" },
1027 React.createElement(
1028 "span",
1029 { className: "metric" },
1031 tweet.favorite_count ?? 0
1032 ),
1033 React.createElement(
1034 "span",
1035 { className: "metric" },
1037 tweet.retweet_count ?? 0
1038 ),
1039 React.createElement(
1040 "span",
1041 { className: "metric" },
1044 )
1045 ),
1046 React.createElement(
1047 "a",
1048 {
1064 currentSearchQuery &&
1065 !error &&
1066 React.createElement(
1067 "div",
1068 { className: "card empty-card" },
1070 ),
1071 // Footer
1072 React.createElement(
1073 "footer",
1074 null,
1075 "Powered by ",
1076 React.createElement(
1077 "a",
1078 {
1084 ),
1085 " | ",
1086 React.createElement(
1087 "a",
1088 {
1102 const root = document.getElementById("root");
1103 if (root) {
1104 createRoot(root).render(React.createElement(App));
1105 } else {
1106 console.error("Root element not found");