linguabot commited on
Commit
a049f87
·
verified ·
1 Parent(s): 0bb7f5f

Upload folder using huggingface_hub

Browse files
client/src/pages/TutorialTasks.tsx CHANGED
@@ -85,9 +85,11 @@ const TutorialTasks: React.FC = () => {
85
  try {
86
  const y = window.scrollY;
87
  fn();
88
- // Restore immediately after next paint
89
  requestAnimationFrame(() => {
90
- window.scrollTo(0, y);
 
 
91
  });
92
  } catch {
93
  fn();
 
85
  try {
86
  const y = window.scrollY;
87
  fn();
88
+ // Restore scroll position after DOM updates
89
  requestAnimationFrame(() => {
90
+ requestAnimationFrame(() => {
91
+ window.scrollTo(0, y);
92
+ });
93
  });
94
  } catch {
95
  fn();
client/src/pages/WeeklyPractice.tsx CHANGED
@@ -1760,8 +1760,251 @@ const WeeklyPractice: React.FC = () => {
1760
  </div>
1761
  )}
1762
  {weeklyPractice.length > 0 ? weeklyPractice.map((practice) => (
1763
- <div key={practice._id} className="bg-white rounded-lg border border-gray-200 p-4 mb-4">
1764
- <div className="text-gray-900 whitespace-pre-wrap mb-2">{renderFormatted(practice.content)}</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1765
  </div>
1766
  )) : (
1767
  <div className="text-gray-600">No practice items for Week 2 yet.</div>
@@ -1968,7 +2211,6 @@ const WeeklyPractice: React.FC = () => {
1968
  )}
1969
  </div>
1970
  ) : (
1971
- /* Weekly Practice */
1972
  <div className="space-y-6">
1973
  {/* Add Practice Button for Admin */}
1974
  {(() => {
 
1760
  </div>
1761
  )}
1762
  {weeklyPractice.length > 0 ? weeklyPractice.map((practice) => (
1763
+ <div key={practice._id} className="bg-white rounded-xl shadow-lg border border-gray-100 p-8 hover:shadow-xl transition-shadow duration-300">
1764
+ <div className="mb-6">
1765
+ <div className="flex items-center justify-between mb-4">
1766
+ <div className="flex items-center space-x-3">
1767
+ <div className="bg-pink-100 rounded-full p-2">
1768
+ <DocumentTextIcon className="h-5 w-5 text-pink-600" />
1769
+ </div>
1770
+ <div>
1771
+ <h3 className="text-lg font-semibold text-gray-900">Source Text #{weeklyPractice.indexOf(practice) + 1}</h3>
1772
+ </div>
1773
+ </div>
1774
+ {((localStorage.getItem('viewMode')||'auto') === 'student') ? false : (JSON.parse(localStorage.getItem('user') || '{}').role === 'admin') && (
1775
+ <div className="flex items-center space-x-2">
1776
+ {editingPractice === practice._id ? (
1777
+ <>
1778
+ <button
1779
+ onClick={savePractice}
1780
+ disabled={saving}
1781
+ className="bg-green-100 hover:bg-green-200 text-green-700 px-3 py-1 rounded-lg transition-colors duration-200"
1782
+ >
1783
+ {saving ? (
1784
+ <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-green-600"></div>
1785
+ ) : (
1786
+ <CheckIcon className="h-4 w-4" />
1787
+ )}
1788
+ </button>
1789
+ <button
1790
+ onClick={cancelEditing}
1791
+ className="bg-red-100 hover:bg-red-200 text-red-700 px-3 py-1 rounded-lg transition-colors duration-200"
1792
+ >
1793
+ <XMarkIcon className="h-4 w-4" />
1794
+ </button>
1795
+ </>
1796
+ ) : (
1797
+ <>
1798
+ <button
1799
+ onClick={() => startEditing(practice)}
1800
+ className="bg-blue-100 hover:bg-blue-200 text-blue-700 px-3 py-1 rounded-lg transition-colors duration-200"
1801
+ >
1802
+ <PencilIcon className="h-4 w-4" />
1803
+ </button>
1804
+ <button
1805
+ onClick={() => deletePractice(practice._id)}
1806
+ className="bg-red-100 hover:bg-red-200 text-red-700 px-3 py-1 rounded-lg transition-colors duration-200"
1807
+ >
1808
+ <TrashIcon className="h-4 w-4" />
1809
+ </button>
1810
+ </>
1811
+ )}
1812
+ </div>
1813
+ )}
1814
+ </div>
1815
+
1816
+ {/* Content - Gradient underlay + glass overlay */}
1817
+ <div className="relative rounded-xl mb-6 p-0 border border-pink-200/60">
1818
+ <div className="absolute inset-0 rounded-xl bg-gradient-to-r from-pink-200/45 via-rose-200/40 to-pink-300/45" />
1819
+ <div className="relative rounded-xl bg-white/10 backdrop-blur-md ring-1 ring-inset ring-white/30 shadow-[inset_0_0.5px_0_rgba(255,255,255,0.5),inset_0_-1px_1.5px_rgba(0,0,0,0.12)] p-6">
1820
+ <div className="pointer-events-none absolute inset-0 rounded-xl opacity-50 [background:linear-gradient(to_bottom,rgba(255,255,255,0.3),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.28),rgba(255,255,255,0)_28%)]" />
1821
+ <div className="pointer-events-none absolute inset-0 rounded-xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-45" />
1822
+ <div className="pointer-events-none absolute inset-0 rounded-xl" style={{ background: 'radial-gradient(120% 120% at 50% 55%, rgba(0,0,0,0.03), rgba(0,0,0,0) 60%)' }} />
1823
+ <div className="pointer-events-none absolute inset-0 rounded-xl bg-pink-500/10 mix-blend-overlay opacity-25" />
1824
+ {editingPractice === practice._id ? (
1825
+ <div className="space-y-4">
1826
+ <textarea
1827
+ value={editForm.content}
1828
+ onChange={(e) => setEditForm({...editForm, content: e.target.value})}
1829
+ className="w-full px-4 py-3 border border-orange-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-orange-500 bg-white"
1830
+ rows={5}
1831
+ placeholder="Enter source text..."
1832
+ />
1833
+ {selectedWeek >= 2 && (
1834
+ <div className="space-y-3">
1835
+ <div>
1836
+ <label className="block text-sm font-medium text-orange-700 mb-1">Image URL</label>
1837
+ <input
1838
+ type="text"
1839
+ value={editForm.imageUrl}
1840
+ onChange={(e) => setEditForm({...editForm, imageUrl: e.target.value})}
1841
+ className="w-full px-3 py-2 border border-orange-300 rounded-md focus:outline-none focus:ring-2 focus:ring-orange-500"
1842
+ placeholder="Enter image URL..."
1843
+ />
1844
+ </div>
1845
+ <div>
1846
+ <label className="block text-sm font-medium text-orange-700 mb-1">Image Alt Text</label>
1847
+ <input
1848
+ type="text"
1849
+ value={editForm.imageAlt}
1850
+ onChange={(e) => setEditForm({...editForm, imageAlt: e.target.value})}
1851
+ className="w-full px-3 py-2 border border-orange-300 rounded-md focus:outline-none focus:ring-2 focus:ring-orange-500"
1852
+ placeholder="Enter alt text for accessibility..."
1853
+ />
1854
+ </div>
1855
+ <div>
1856
+ <label className="block text-sm font-medium text-orange-700 mb-1">Upload Local Image</label>
1857
+ <input
1858
+ type="file"
1859
+ accept="image/*"
1860
+ onChange={async (e) => {
1861
+ const file = e.target.files?.[0];
1862
+ if (file) {
1863
+ try {
1864
+ const imageUrl = await handleFileUpload(file);
1865
+ setEditForm({ ...editForm, imageUrl });
1866
+ } catch (error) {
1867
+ console.error('Error uploading file:', error);
1868
+ }
1869
+ }
1870
+ }}
1871
+ className="w-full px-3 py-2 border border-orange-300 rounded-md focus:outline-none focus:ring-2 focus:ring-orange-500"
1872
+ />
1873
+ {uploading && (
1874
+ <div className="mt-2 text-sm text-orange-600">
1875
+ <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-orange-600 inline-block mr-2"></div>
1876
+ Uploading...
1877
+ </div>
1878
+ )}
1879
+ </div>
1880
+ </div>
1881
+ )}
1882
+ </div>
1883
+ ) : (
1884
+ <div>
1885
+ {practice.imageUrl ? (
1886
+ selectedWeek >= 4 && practice.imageAlignment === 'portrait-split' ? (
1887
+ // Portrait split layout
1888
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6 items-start">
1889
+ <div className="w-full flex justify-center">
1890
+ <div className="inline-block rounded-lg shadow-md overflow-hidden">
1891
+ <img src={practice.imageUrl} alt={practice.imageAlt || 'Uploaded image'} className="w-full h-auto" style={{ maxHeight: '520px', objectFit: 'contain' }} />
1892
+ </div>
1893
+ </div>
1894
+ <div className="w-full">
1895
+ <div className="mb-4 text-ui-text leading-relaxed text-lg font-source-text whitespace-pre-wrap">{practice.content}</div>
1896
+ {localStorage.getItem('token') && (
1897
+ <div className="relative rounded-xl bg-white/10 backdrop-blur-md ring-1 ring-inset ring-white/30 shadow-[inset_0_0.5px_0_rgba(255,255,255,0.5),inset_0_-1px_1.5px_rgba(0,0,0,0.12)] p-4">
1898
+ <div className="pointer-events-none absolute inset-0 rounded-xl opacity-50 [background:linear-gradient(to_bottom,rgba(255,255,255,0.3),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.28),rgba(255,255,255,0)_28%)]" />
1899
+ <div className="pointer-events-none absolute inset-0 rounded-xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-45" />
1900
+ <h5 className="relative z-10 text-ui-text font-semibold mb-2">Your Translation</h5>
1901
+ <div className="relative z-10 flex items-center justify-end space-x-2 mb-2">
1902
+ <button onClick={() => applyInlineFormat(`weekly-translation-${practice._id}`, translationText[practice._id] || '', v => setTranslationText({ ...translationText, [practice._id]: v }), '**')} className="px-2 py-1 text-xs bg-white/40 text-ui-text rounded">B</button>
1903
+ <button onClick={() => applyInlineFormat(`weekly-translation-${practice._id}`, translationText[practice._id] || '', v => setTranslationText({ ...translationText, [practice._id]: v }), '*')} className="px-2 py-1 text-xs bg-white/40 text-ui-text rounded italic">I</button>
1904
+ <button onClick={() => applyLinkFormat(`weekly-translation-${practice._id}`, translationText[practice._id] || '', v => setTranslationText({ ...translationText, [practice._id]: v }))} className="px-2 py-1 text-xs bg-white/40 text-ui-text rounded">Link</button>
1905
+ </div>
1906
+ <textarea id={`weekly-translation-${practice._id}`} value={translationText[practice._id] || ''} onChange={(e) => setTranslationText({ ...translationText, [practice._id]: e.target.value })} className="relative z-10 w-full px-4 py-3 border border-ui-border rounded-lg focus:outline-none focus:ring-2 focus:ring-pink-500 focus:border-pink-500 bg-white" rows={4} placeholder="Enter your translation here..." />
1907
+ <div className="relative z-10 flex justify-end mt-2">
1908
+ <button onClick={() => handleSubmitTranslation(practice._id)} disabled={submitting[practice._id]} className="relative overflow-hidden inline-flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium rounded-2xl text-white ring-1 ring-inset ring-white/50 backdrop-blur-md backdrop-brightness-110 backdrop-saturate-150 bg-pink-600/70 disabled:bg-gray-400 active:translate-y-0.5 active:shadow-[inset_0_0.5px_0_rgba(255,255,255,0.6),inset_0_-1px_0_rgba(0,0,0,0.12)] transition-all duration-200">
1909
+ <div className="pointer-events-none absolute inset-0 rounded-2xl opacity-60 [background:linear-gradient(to_bottom,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%)]" />
1910
+ <div className="pointer-events-none absolute inset-0 rounded-2xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-50" />
1911
+ {submitting[practice._id] ? (
1912
+ <>
1913
+ <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
1914
+ Submitting...
1915
+ </>
1916
+ ) : (
1917
+ <>
1918
+ <CheckIcon className="h-4 w-4" />
1919
+ Submit Translation
1920
+ </>
1921
+ )}
1922
+ </button>
1923
+ </div>
1924
+ </div>
1925
+ )}
1926
+ </div>
1927
+ </div>
1928
+ ) : (
1929
+ // Regular image layout
1930
+ <div className="space-y-4">
1931
+ <div className="flex justify-center">
1932
+ <div className="inline-block rounded-lg shadow-md overflow-hidden">
1933
+ <img src={practice.imageUrl} alt={practice.imageAlt || 'Uploaded image'} className="w-full h-auto" style={{ maxHeight: '400px', objectFit: 'contain' }} />
1934
+ </div>
1935
+ </div>
1936
+ <div className="text-ui-text leading-relaxed text-lg font-source-text whitespace-pre-wrap">{practice.content}</div>
1937
+ {localStorage.getItem('token') && (
1938
+ <div className="relative rounded-xl bg-white/10 backdrop-blur-md ring-1 ring-inset ring-white/30 shadow-[inset_0_0.5px_0_rgba(255,255,255,0.5),inset_0_-1px_1.5px_rgba(0,0,0,0.12)] p-4">
1939
+ <div className="pointer-events-none absolute inset-0 rounded-xl opacity-50 [background:linear-gradient(to_bottom,rgba(255,255,255,0.3),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.28),rgba(255,255,255,0)_28%)]" />
1940
+ <div className="pointer-events-none absolute inset-0 rounded-xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-45" />
1941
+ <h5 className="relative z-10 text-ui-text font-semibold mb-2">Your Translation</h5>
1942
+ <div className="relative z-10 flex items-center justify-end space-x-2 mb-2">
1943
+ <button onClick={() => applyInlineFormat(`weekly-translation-${practice._id}`, translationText[practice._id] || '', v => setTranslationText({ ...translationText, [practice._id]: v }), '**')} className="px-2 py-1 text-xs bg-white/40 text-ui-text rounded">B</button>
1944
+ <button onClick={() => applyInlineFormat(`weekly-translation-${practice._id}`, translationText[practice._id] || '', v => setTranslationText({ ...translationText, [practice._id]: v }), '*')} className="px-2 py-1 text-xs bg-white/40 text-ui-text rounded italic">I</button>
1945
+ <button onClick={() => applyLinkFormat(`weekly-translation-${practice._id}`, translationText[practice._id] || '', v => setTranslationText({ ...translationText, [practice._id]: v }))} className="px-2 py-1 text-xs bg-white/40 text-ui-text rounded">Link</button>
1946
+ </div>
1947
+ <textarea id={`weekly-translation-${practice._id}`} value={translationText[practice._id] || ''} onChange={(e) => setTranslationText({ ...translationText, [practice._id]: e.target.value })} className="relative z-10 w-full px-4 py-3 border border-ui-border rounded-lg focus:outline-none focus:ring-2 focus:ring-pink-500 focus:border-pink-500 bg-white" rows={4} placeholder="Enter your translation here..." />
1948
+ <div className="relative z-10 flex justify-end mt-2">
1949
+ <button onClick={() => handleSubmitTranslation(practice._id)} disabled={submitting[practice._id]} className="relative overflow-hidden inline-flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium rounded-2xl text-white ring-1 ring-inset ring-white/50 backdrop-blur-md backdrop-brightness-110 backdrop-saturate-150 bg-pink-600/70 disabled:bg-gray-400 active:translate-y-0.5 active:shadow-[inset_0_0.5px_0_rgba(255,255,255,0.6),inset_0_-1px_0_rgba(0,0,0,0.12)] transition-all duration-200">
1950
+ <div className="pointer-events-none absolute inset-0 rounded-2xl opacity-60 [background:linear-gradient(to_bottom,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%)]" />
1951
+ <div className="pointer-events-none absolute inset-0 rounded-2xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-50" />
1952
+ {submitting[practice._id] ? (
1953
+ <>
1954
+ <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
1955
+ Submitting...
1956
+ </>
1957
+ ) : (
1958
+ <>
1959
+ <CheckIcon className="h-4 w-4" />
1960
+ Submit Translation
1961
+ </>
1962
+ )}
1963
+ </button>
1964
+ </div>
1965
+ </div>
1966
+ )}
1967
+ </div>
1968
+ )
1969
+ ) : (
1970
+ // Text-only layout
1971
+ <div className="mb-4 text-ui-text leading-relaxed text-lg font-source-text whitespace-pre-wrap">{practice.content}</div>
1972
+ )}
1973
+ {localStorage.getItem('token') && !practice.imageUrl && (
1974
+ <div className="relative rounded-xl bg-white/10 backdrop-blur-md ring-1 ring-inset ring-white/30 shadow-[inset_0_0.5px_0_rgba(255,255,255,0.5),inset_0_-1px_1.5px_rgba(0,0,0,0.12)] p-4">
1975
+ <div className="pointer-events-none absolute inset-0 rounded-xl opacity-50 [background:linear-gradient(to_bottom,rgba(255,255,255,0.3),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.28),rgba(255,255,255,0)_28%)]" />
1976
+ <div className="pointer-events-none absolute inset-0 rounded-xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-45" />
1977
+ <h5 className="relative z-10 text-ui-text font-semibold mb-2">Your Translation</h5>
1978
+ <div className="relative z-10 flex items-center justify-end space-x-2 mb-2">
1979
+ <button onClick={() => applyInlineFormat(`weekly-translation-${practice._id}`, translationText[practice._id] || '', v => setTranslationText({ ...translationText, [practice._id]: v }), '**')} className="px-2 py-1 text-xs bg-white/40 text-ui-text rounded">B</button>
1980
+ <button onClick={() => applyInlineFormat(`weekly-translation-${practice._id}`, translationText[practice._id] || '', v => setTranslationText({ ...translationText, [practice._id]: v }), '*')} className="px-2 py-1 text-xs bg-white/40 text-ui-text rounded italic">I</button>
1981
+ <button onClick={() => applyLinkFormat(`weekly-translation-${practice._id}`, translationText[practice._id] || '', v => setTranslationText({ ...translationText, [practice._id]: v }))} className="px-2 py-1 text-xs bg-white/40 text-ui-text rounded">Link</button>
1982
+ </div>
1983
+ <textarea id={`weekly-translation-${practice._id}`} value={translationText[practice._id] || ''} onChange={(e) => setTranslationText({ ...translationText, [practice._id]: e.target.value })} className="relative z-10 w-full px-4 py-3 border border-ui-border rounded-lg focus:outline-none focus:ring-2 focus:ring-pink-500 focus:border-pink-500 bg-white" rows={4} placeholder="Enter your translation here..." />
1984
+ <div className="relative z-10 flex justify-end mt-2">
1985
+ <button onClick={() => handleSubmitTranslation(practice._id)} disabled={submitting[practice._id]} className="relative overflow-hidden inline-flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium rounded-2xl text-white ring-1 ring-inset ring-white/50 backdrop-blur-md backdrop-brightness-110 backdrop-saturate-150 bg-pink-600/70 disabled:bg-gray-400 active:translate-y-0.5 active:shadow-[inset_0_0.5px_0_rgba(255,255,255,0.6),inset_0_-1px_0_rgba(0,0,0,0.12)] transition-all duration-200">
1986
+ <div className="pointer-events-none absolute inset-0 rounded-2xl opacity-60 [background:linear-gradient(to_bottom,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%)]" />
1987
+ <div className="pointer-events-none absolute inset-0 rounded-2xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-50" />
1988
+ {submitting[practice._id] ? (
1989
+ <>
1990
+ <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
1991
+ Submitting...
1992
+ </>
1993
+ ) : (
1994
+ <>
1995
+ <CheckIcon className="h-4 w-4" />
1996
+ Submit Translation
1997
+ </>
1998
+ )}
1999
+ </button>
2000
+ </div>
2001
+ </div>
2002
+ )}
2003
+ </div>
2004
+ )}
2005
+ </div>
2006
+ </div>
2007
+ </div>
2008
  </div>
2009
  )) : (
2010
  <div className="text-gray-600">No practice items for Week 2 yet.</div>
 
2211
  )}
2212
  </div>
2213
  ) : (
 
2214
  <div className="space-y-6">
2215
  {/* Add Practice Button for Admin */}
2216
  {(() => {