File size: 7,823 Bytes
d988ae4
 
 
 
 
 
 
 
 
 
3bbb98d
d988ae4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
013a898
d988ae4
 
 
 
013a898
d988ae4
013a898
d988ae4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3bbb98d
 
 
 
 
d988ae4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
013a898
d988ae4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
013a898
 
d988ae4
 
 
 
 
 
 
013a898
d988ae4
 
013a898
d988ae4
 
013a898
d988ae4
 
 
 
 
 
013a898
d988ae4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
013a898
d988ae4
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
'use client';

import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import OtpInput from 'react-otp-input';
import axios from 'axios';
import { useSavedClipboards } from '../lib/hooks/useSavedClipboards';
import SavedClipboardsButton from './SavedClipboardsButton';
import { LogIn, AlertCircle, Loader } from 'lucide-react';
import { apiUrl } from '@/lib/constants';
import { getAdminToken } from '@/lib/adminAuth';

export default function JoinClipboardCard() {
  const [roomCode, setRoomCode] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const [clipboardExists, setClipboardExists] = useState(true);
  const [isComplete, setIsComplete] = useState(false);
  const [lastVisitedClipboard, setLastVisitedClipboard] = useState('');
  const router = useRouter();
  const { addClipboard, savedClipboards } = useSavedClipboards();

  // Get the last visited clipboard when component mounts and pre-fill the input
  useEffect(() => {
    if (savedClipboards.length > 0) {
      // Sort by lastVisited date and get the most recent one
      const sorted = [...savedClipboards].sort((a, b) =>
        new Date(b.lastVisited).getTime() - new Date(a.lastVisited).getTime()
      );
      const lastCode = sorted[0].roomCode;
      setLastVisitedClipboard(lastCode);

      // Pre-fill the input with the last visited clipboard code
      setRoomCode(lastCode);

      // Check if it's valid
      const isValid = lastCode.length === 4 && isValidRoomCode(lastCode);
      setIsComplete(isValid);
    }
  }, [savedClipboards]);

  // Validate room code format (6 alphanumeric characters, uppercase)
  const isValidRoomCode = (code: string): boolean => {
    const roomCodeRegex = /^[A-Z0-9]{4}$/;
    return roomCodeRegex.test(code);
  };

  const handleJoinClipboard = async (e?: React.FormEvent) => {
    // If called from a form submit, prevent default
    if (e) e.preventDefault();

    // Reset states
    setClipboardExists(true);

    // Validate room code is not empty
    if (!roomCode.trim()) {
      return;
    }

    // Validate room code format
    if (!isValidRoomCode(roomCode)) {
      return;
    }

    // Set loading state and check if clipboard exists
    setIsLoading(true);

    try {
      // Check if clipboard exists
      const token = getAdminToken();
      const response = await axios.get(`${apiUrl}/clipboard/${roomCode}/exists`, {
        headers: token ? { 'x-admin-token': token } : undefined,
        params: token ? { token } : undefined,
      });

      if (response.data.exists) {
        // Save to recently visited clipboards
        addClipboard(roomCode);

        // Clipboard exists, navigate to it
        router.push(`/${roomCode}`);
      } else {
        // Clipboard doesn't exist
        setClipboardExists(false);
        setIsLoading(false);
      }
    } catch (error) {
      // Error checking clipboard existence
      setClipboardExists(false);
      setIsLoading(false);
    }
  };

  return (
    <div className="flex-1 p-6 md:p-8 rounded-xl border border-surface-hover bg-surface/50 backdrop-blur-sm shadow-lg hover:shadow-secondary/20 hover:border-secondary/50 transition-all duration-300 ease-out relative overflow-hidden group">
      {/* Background elements */}
      <div className="absolute -top-10 -right-10 w-32 h-32 bg-secondary/10 rounded-full animate-pulse-slow group-hover:scale-110 transition-transform duration-500"></div>
      <div className="absolute -bottom-16 -left-16 w-40 h-40 bg-secondary/5 rounded-full opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>

      <div className="relative z-10">
        <div className="flex items-center mb-4">
          <div className="w-10 h-10 bg-secondary/10 rounded-full flex items-center justify-center mr-3">
            <LogIn className="h-5 w-5 text-secondary" />
          </div>
          <h2 className="text-2xl font-semibold text-text-primary">Join Clipboard</h2>
        </div>

        <p className="text-text-secondary mb-6 pl-1">
          Enter a 4-character code to join an existing clipboard.
        </p>

        <form onSubmit={handleJoinClipboard} className="space-y-4">
          <div>
            <div className="mb-2">


              <OtpInput
                inputStyle={{
                  width: '50px',
                  height: '50px',
                }}
                onPaste={(e) => {
                  e.preventDefault()
                  const pastedValue = e.clipboardData?.getData('text') || '';
                  const filteredValue = pastedValue.replace(/[^a-zA-Z0-9]/g, '').toUpperCase();
                  if (filteredValue.length === 4) {
                    setRoomCode(filteredValue);
                  }
                }}
                value={roomCode}
                placeholder={lastVisitedClipboard}
                onChange={(value: string) => {
                  // Convert to uppercase automatically
                  const uppercaseValue = value.toUpperCase();
                  setRoomCode(uppercaseValue);
                  setClipboardExists(true);
                  const isValid = uppercaseValue.length === 4 && isValidRoomCode(uppercaseValue);
                  setIsComplete(isValid)
                }}
                numInputs={4}
                renderInput={(props, index) => (
                  <input
                    {...props}
                    className={`w-full h-full text-center text-xl sm:text-2xl font-mono font-bold
                      border-2 rounded-lg shadow-none outline-none
                      transition-colors duration-200 ease-in-out                      
                      ${roomCode.length === 4 && isComplete
                        ? 'border-green-500 text-green-500 bg-green-500/5'
                        : props.value
                          ? 'border-secondary text-text-primary bg-surface/80'
                          : 'border-surface-hover text-text-primary bg-surface/50 hover:border-primary/30'
                      }`}
                    key={index}
                  />
                )}
                inputType="text"
                shouldAutoFocus
                containerStyle="flex justify-center gap-2 sm:gap-3"
              />
            </div>
            {!clipboardExists && (
              <div className="mt-3 text-error text-sm flex items-center justify-center">
                <AlertCircle className="h-4 w-4 mr-1 flex-shrink-0" />
                <span>Clipboard does not exist.</span>
              </div>
            )}
          </div>
          <div className="flex gap-2 mt-4">
            <button
              type="submit"
              disabled={isLoading || !isComplete}
              className="flex-1 py-3 px-4 bg-secondary hover:bg-secondary/90 text-white font-medium rounded-lg flex items-center justify-center transition-all duration-200 shadow-md hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-secondary/30 disabled:opacity-50 disabled:cursor-not-allowed"
            >
              {isLoading ? (
                <>
                  <Loader className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" />
                  Joining...
                </>
              ) : (
                <>
                  <LogIn className="h-5 w-5 mr-2" />
                  Join Clipboard
                </>
              )}
            </button>

            <SavedClipboardsButton
              onSelectClipboard={(code) => {
                setRoomCode(code);
                setClipboardExists(true);
                setIsComplete(true);
                // Don't automatically join - just fill the input
                // This allows users to see the code before joining
              }}
            />
          </div>
        </form>
      </div>
    </div>
  );
}